1、Spring框架概述
- 轻量级开源javaEE框架
- 解决企业开发应用复杂性
- IOC,AOP
- 方便解耦
- 方便集合各种框架
2、IOC
概念
降低代码耦合度,对象创建,对象之间的引用交给Spring
底层原理
xml解析,工厂模式,反射
大致流程:例如xml文件中有以下bean
<bean id="dao" class="com.tao.UserDao"></bean>
伪代码流程:
class UserDaoFactory {
public static UserDao getUserDao(){
String classValue = class属性值 // xml文件解析,获取class
Class clazz = Class.forName(classValue) // 反射获取class实例
return clazz.newInstance();
}
}
Spring两种IOC实现方法
BeanFactory
IOC最基本的实现方式,一般是Spring内部使用,开发人员并不适用,不同于ApplicationContext,加载配置文件的时候不会预先创建对象,获取对象的的时候才会创建对象(懒加载,节约资源)
ApplicationContext
BeanFactory子接口,提供更多更强大的功能,加载配置文件的时候就会对对象进行创建(耗时耗时间的都先创建,适合实际生产)
ApplicationContext实现类
1、FileSystemXmlApplicationContext: xml文件系统路径
2、ClassPathXmlApplicationContext: xml文件相对项目的路径
IOC操作Bean管理
主要是Spring创建对象,Spring注入属性
基于xml配置文件方式实现
<bean id="dao" class="com.tao.UserDao"></bean>
- id属性:唯一标识
- class属性:类全路径
- name属性:和id一样,并且可以加入特殊符号如/,但是是早期属性,基本没人用了
默认执行bean的无参构造方法
基于xml注入属性:
- DI:依赖注入,就是注入属性,DI是IOC的一种实现,在创建对象的基础上进行完成。
- 创建方法setXXX this.XXX = xxx,使用setXXX注入
- 有参构造器注入
- 在类中创建有参构造器,通过xml文件中的name,value对应
<bean id="user1" class="com.tao.User"> <constructor-arg name="name" value="abcde"></constructor-arg> </bean>
- xml文件中配置属性注入
- 在bean标签里面使用标签property(对象中要有对应的setXXX方法)
- name:属性名
- value:属性值
<property name = "" value = ""></property>
- 外部bean ref:对象注入(对象也需要创建setXXX方法),
<property name = "" ref = ""></property>
- 内部bean 嵌套使用以上两种就行
<peoperty name=""> <bean id="" value=""> <property name=""></property> </bean> </property>
- 内部bean的属性可以使用上面的property设置,也可以在类中设置好get方法后,name=类.属性 value=""设置
- 集合类属性赋值 使用
<array><list>
等标签赋值
FactoryBean
除了普通的bean还有工厂bean即FactoryBean
- 普通bean以上介绍的就是普通bean,定义类型就是返回类型
- 工厂bean,配置文件中定义类型和返回类型可以不同
流程:
- 创建类,类做成工厂bean,实现接口FactoryBean
- 实现接口的方法,方法中定义返回的bean类型
- 相当于前面的bean定义的时候返回值就是你真正的返回值,而实现Factory接口之后可以自定义返回值
public class MyBean implements FactoryBean {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("MyBean", User.class);
System.out.println(user);
}
@Override
public Object getObject() throws Exception {
User user = new User("test");
return user;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
bean管理(作用域和生命周期)
- 可以设置bean实例是单例还是多例,默认是单例
- 通过scope标签设置
- 默认singleton单例
- 加载配置文件就会创建该单例(自己创建单例也是static)
- 可选prototype多例
- 不是在加载配置文件创建对象,调用getBean方法创建
- 默认singleton单例
- request:一次请求
- session:一次会话
bean生命周期
- 通过构造器创建bean实例(无参构造器)
- 为bean属性设置值和其他bean的引用(调用set方法)
- 调用bean的初始化方法(需要进行配置)
- 可以使用了(对象获取了)
- 当容器关闭的时候调用bean销毁的方法(需要进行配置销毁的方法)
具体演示:
public class Orders {
private String oname;
public Orders() {
System.out.println("第一步,通过无参构造器创建bean");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步,通过set方法设置值");
}
public void init() {
System.out.println("第三步,执行初始化方法");
}
public void destroy(){
System.out.println("第五步,执行销毁方法");
}
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步,获取创建bean实例");
System.out.println(orders);
((ClassPathXmlApplicationContext)context).close();
}
}
<bean id="orders" class="com.tao.bean.Orders" init-method="init" destroy-method="destroy">
<property name="oname" value="手机"></property>
</bean>
输出
- 第一步,通过无参构造器创建bean
- 第一步,通过无参构造器创建bean
- 第二步,通过set方法设置值
- 第三步,执行初始化方法
- 第四步,获取创建bean实例
- com.tao.bean.Orders@4671e53b
- 第五步,执行销毁方法
实际上还有另外两步,在第三步前后还有两个方法
- 初始化之前会把bean实例传递bean后置处理器的方法
- 初始化之后会把bean实例传递bean后置处理器的方法
- 注意: 所有bean都会执行这两个方法
public class MybeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化后执行的方法");
return bean;
}
}
<bean id="mybeanPost" class="com.tao.bean.MybeanPost"></bean>
自动装配
- 使用xml自动装配:根据属性名称类型自动装配值
- bean中 autowire="byName"根据名称自动装配,注意bean的id名称和你的属性名要一致
- bean中 autowire="byType"根据类型自动装配,但是如果一个类型有多个bean,会报错,无法对应。
IOC操作Bean管理(外部属性文件)
解决如果property变动需要大范围改动xml文件
- 直接配置数据库信息:麻烦耦合度高。
- 引入外部属性文件配置数据库连接池:方便修改,耦合度低,设立properties文件,表达式${val}
基于注解方式实现(可以简化xml配置)
- @Component
- @Service
- @Controller
- @Repository 流程:
- 引入依赖 aop
- 组件扫描,也可以将use-default-filters设置为false使用自定义的过滤器扫描
<context:component-scan base-package="扫描的路径"></context:component-scan>
- 创建类,类上添加创建对象注解
注解的属性注入
- @AutoWired 根据属性类型自动装配
- @Qualifier 根据属性名称自动注入(多个实现类实现接口可能无法导根据属性注入,此时根据类型注入,指定value)
- @Resource 可以根据类型也可以根据名称注入,加入name可以根据名称注入
- @Value 注入普通类型 通过value给如String设置值
完全注解开发
创建配置类,替代xml文件
- 创建类,添加@Configuration注解,以及@ComponentScan(basePackages = {""})
AOP
不改变源码,在主干功能里添加功能,比如登录流程中添加判断权限模块
原理:两种情况的动态代理
1、有接口的情况,使用JDK动态代理
interface UserDao{
public void login();
}
class UserDaoImpl implementsUserDao{
public void login(){
//登录流程
}
}
创建UserDao接口实现类的代理对象,增强类的方法。
具体步骤:
- 使用JDK动态代理,使用Proxy类里的方法创建代理对象
- 调用newProxyInstance方法,此方法返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。方法中有三个参数。
- ClassLoader 类加载器
- 增强方法所在的类,这个类实现的接口,支持多个接口
- 实现这个接口InvocationHanlder,创建代理对象,写增强的方法。
public interface UserDao {
public String update(String id);
public int add(int a, int b);
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public String update(String id) {
return id + "*********";
}
@Override
public int add(int a, int b) {
return a + b;
}
}
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法前
System.out.println("方法前执行的" + method.getName() + "传递的参数" + Arrays.toString(args));
// 被增强的方法执行
Object res = method.invoke(userDao, args);
// 方法后
System.out.println("方法之后执行" + userDao);
return res;
}
});
dao.update("123");
dao.add(1,4);
}
- 以上是偏底层的代码,spring为我们做了封装
2、没有接口的情况,使用CGLIB动态代理
class User{
public void add() {
//add方法
}
}
正常来说需要创建当前类的子类来增强方法,也可以使用CGLIB动态代理,创建子类的代理对象即可
3、AspectJ实现
AOP术语
- 连接点
- 类里面的哪些方法可以被增强,这些方法称为连接点
- 切入点
- 实际增强的方法被称为切入点
- 通知(增强)
- 增强的逻辑部分(切入点前后添加的部分)
- 前置通知、后置通知、环绕通知、异常通知、最终通知(finally)
- 切面
- 是动作,把通知应用到切入点的过程叫切面。
Spring具体操作
- 一般基于AspectJ实现AOP操作
- AspectJ不是Spring组成部分,是独立AOP框架,一般把二者一起使用进行AOP操作。
- AspectJ可以基于xml配置文件,也可以基于注解方式实现AOP操作
- 项目中引入AOP依赖
- 切入点表达式:要对哪个类中的哪个方法进行增强
execution([权限修饰符(*代表都行)][返回类型(可以不写)][类全路径][方法名称] ([参数列表]))
- 例:对com.tao.dao.BookDao类里面的add方法进行增强
execution(* com.tao.dao.BookDao.add(..))
- 例: 对com.tao.dao.BookDao类里面的所有方法进行增强
execution(* com.tao.dao.BookDao.*(..))
- 例: 对com.tao.dao里所有类所有方法增强
execution(* com.tao.dao.*.*(..))
- 基于注解方式
- Spring配置文件中开启注解的扫描
- 使用注解创建User和UserProxy对象
- 增强类上增加注解@Aspect
- Spring配置文件中开启生成代理对象
- 增强类的方法添加@Before,@After,@AfterReturning,@AfterThrowing,@Around
- 顺序 环绕前-Before-方法-环绕后-after-afterReturning
- 如果有异常 环绕前-Before-方法-after-AfterThrowing
- after也叫最终通知,有没有异常都执行
- afterReturning 后置通知,有异常不执行
- spring5中做出修改:顺序:环绕前-before-方法-afterReturning-after-环绕后
- 有异常则是 环绕前-before-方法-afterThrowing-after
@Component
public class User {
public void add() {
System.out.println("add***************");
}
}
@Component
@Aspect // 生成代理对象
public class UserProxy {
@Before(value = "execution(* com.tao.aop.User.add(..))")
public void before() {
System.out.println("before.........");
}
@Around(value = "execution(* com.tao.aop.User.add(..))")
public void around(ProceedingJoinPoint p) throws Throwable {
System.out.println("环绕前");
p.proceed();
System.out.println("环绕后");
}
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(Conf.class);
User user = context.getBean("user", User.class);
user.add();
}
}
@Configuration
@ComponentScan(basePackages = {"com.tao"})
@EnableAspectJAutoProxy
public class Conf {
}
上面conf配置也可以在xml文件中设置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tao"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
公共切入点的提取
多个切入点相同的提取出来
代理方法中添加方法
@Pointcut(value = "execution(* com.tao.aop.User.add(..))")
public void pointdemo(){
}
@Before(value = "pointdemo()")
public void beforePointDemo() {
System.out.println("before.........");
}
- 多个增强类对同一个方法进行增强,设置增强优先级
- 增强类添加注解@Order(num) 0~n 0最优先
- Aop操作(配置文件)
- 创建两个类,增强类,被增强类,方法
- spring配置文件中创建两个两个类的对象
- spring配置文件中配置切入点