Spring Bean的创建过程
SpringIOC容器它会以某种方式,加载配置文件中的 信息,将其解析为一个个的BeanDefinition.之后将BeanDefinition注册到容器之中。
分为两个主要部分: 其一 :容器启动阶段 其二:bean实例化阶段。
在容器的启动阶段主要做的是一些信息收集的过程(主要依赖于工具类BeanDefinitionReader),将收集的信息组成BeanDefinition.将BeanDefinition注册到相应的BeaneanRegistry。 Bean实例化的过程在请求方通过容器的getBean方法明确请求某个对象时候触发/隐式依赖关系调用时候也会触发该动作。此阶段做的操作主要是判断当前的请求对象是否已经被实例化过了。根据情况进行注入,当该对象实现某些回调接口,也会根据回调函数接口装配它。
bean对象的前置处理器:
bean的后前置处理器BeanFactoryPostProcess 这个机制允许我们在实例化相应对象之前对注册到容器中的BeanDefinition的存储信息进行修改。 可以根据这个机制对Bean增加其它信息。修改Bean定义的某些属性值。 * 自定义前置处理器需要实现BeanFactoryPostProcess接口
实例化和初始化的区别?
1、实例化----实例化的过程是一个创建Bean的过程,即调用Bean的构造函数,单例的Bean放入单例池中。
2、初始化----初始化的过程是一个赋值的过程,即调用Bean的setter,设置Bean的属性。
( bean 的 实 例 化 ) |(属性赋值)| ( bean 的 初 始 化) | (bean是否实现Aop:@Aspect) | 存入单例池 XXX.class--->推断构造方法--->普通对象--->依赖注入--->afterPropertiesSet()--->初始化后(AOP)--->代理对象--->Map<beanName,bean对象> 单例池
1.bean对象的实例化
-
创建.class对象,并将该类添加到Spring容器中(@Component/xml中添加<bean>标签)
-
推断构造方法
-
1、Spring会先查看类中是否有无参构造方法,如果有默认使用使用无参构造方法创建该对象
-
2、如果该类没有无参构造方法,只有单独一个有参构造方法,Spring会正常创建,Spring会根据构造方法的形参类型和形参名称去单例池中查找是否有构造方法所需要的bean对象。
-
3、Spring单例池中找到一个同类型的对象,会自动返回,不会判断bean对象名称是否相同。
-
4、Spring单例池中查找多个同类型bean对象是以先byType-->后byName形式查找,会先根据构造器形参类型匹配查找,如果单例池中有一个该类型的对象,就使用该类型对象进行返回,如果有多个该类型的对象,就会根据构造器形参名字匹配查找,找到返回该对象,找不到报创建bean异常。
-
5、在Spring中单例和java基础中的学的单例模式有些不同,java基础中是使用该类只能返回同一个对象,在Spring中是根据对象名来进行匹配是否单例,同一个类型可以有多个对象,但多个对象名不同,Spring中单例模式范围更大,就是说,在配置文件中或配置类中添加多个同类型的对象,但是bean的名称不同,不会报错。按照bean的名字定义单例bean
-
6、如果该类有多个有参构造方法,程序会创建bean异常。
-
7、程序可以使用@Autowired来标注使用哪个构造器来创建对象
-
属性赋值
-
依赖查找
-
@Autowired -->Spring提供
-
依赖注入之前会先从Spring容器中查找该类所依赖的对象的实例。
-
@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用
-
spring容器中只有一个类型的的bean,不会再判断名称。
-
-
@Resource -->java本身提供
-
依赖查找是根据先byName-->后byType形式查找。
-
@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个
-
-
依赖注入:查找后进行反射注入
其实从依赖注入的字面意思就可以知道,要将对象p注入到对象a,那么首先就必须得生成对象a和对象p,才能执行注入。所以,如果一个类A中有个成员变量p被@Autowried注解,那么@Autowired注入是发生在A的构造方法执行完之后的。
如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么久无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
Constructor >> @Autowired >> @PostConstruct
bean对象初始化前:
bean对象的后置处理器:
bean的后置处理器BeanPostProcessor * bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口, * 且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
在bean对象初始化前后都可以通过bean 的后置处理器进行一些方法的调用。
需要在IOC容器中注入一个实现BeanPostProcessor接口的后置处理器类。
其中接口方法postProcessBeforeInitialization就是bean对象初始化之前调用。接口方法postProcessAfterInitialization就是bean对象初始化之后调用。
各种 Aware
类型的接口
实现
Aware
接口的目的是让程序可以拿到 Spring 容器的当前的运行环境(如当前 Bean 的名称、当前的 BeanFactory、当前的 ApplicationContext 等等资源)。这类接口的调用时机是在 Bean 实例化、属性注入之后,初始化之前。
常用子接口:
BeanNameAware 接口:接口中只有一个方法 void setBeanName(String name) 用于获取当前 Bean 的名称。 BeanFactoryAware 接口:接口中只有一个方法 void setBeanFactory(BeanFactory beanFactory) 用于获取当前的 BeanFactory。 ApplicationContextAware 接口:接口中只有一个方法 void setApplicationContext(ApplicationContext applicationContext) 用于获取当前的 ApplicationContext。
2.bean对象的初始化
1、bean的初始化需要程序员自己设定
2、在bean类中实现InitializingBean接口,实现afterPropertiesSet()方法,类在实例化完成后,判断是否实现这个接口,实现就进行bean的初始化,没有实现就不会进行bean的初始化。跟@PostConstruct注解作用类似
3、在afterPropertiesSet方法中可以加入自己的逻辑,进行属性判断、方法判断等等方法
方式二:@Bean注解上使用initMethod方法指定初始化方法
3.初始化后(AOP):cglib
cglib动态代理是子父类继承实现。
普通bean对象初始化后,进行了Aop管理,最后存入单例池中的是普通bean对象还是代理对象?
答案:代理对象,当所添加的bean对象,实现了aop管理,就会将普通bean的代理对象放入单例池中,以后获取的bean都是代理对象
-
@Transactional的AOP实现原理:
-
1、创建一个代理对象继承被代理对象。
-
2、先创建一个父类(被代理对象)的实例作为属性。
-
3、重写父类中的切面逻辑方法,执行完切面逻辑,使用创建的父类实例再调用切面方法。
-
为什么要在子类中创建一个父类的实例呢?为什么不使用super.方法()来实现呢?
super关键字表示使用父类中的同名方法,但是操作的对象依旧是子类,并不是直接调用父类对象.test()方法,如果test()方法中调用了父类的属性,而子类实例的相同属性没有进行依赖注入,就会显示属性为空。
此时就需要手动创建一个父类对象,这个对象是从spring容器中注入的,然后进行方法调用(此时就是父类实例直接调用,属性已经注入),保证代理类进行方法调用时不会遇到空指针异常。
-
Spring事务大概工作原理:
-
1、给操作数据库的方法添加@Transactional注解,调用方法时会创建该类的代理对象(cglib)。
-
2、代理对象执行切面逻辑
-
切面逻辑:
-
①开启事务:begin commit rollback或setAutoCommit=false
-
②事务管理器创建一个连接conn -->存储在ThreadLocal<Map<dataSource,conn>>,这可以存放多个不同数据库的连接
-
③jdbcTemplate或Mybatis等执行sql语句 -->jdbc会从ThreadLocal中拿数据库连接
-
④无异常 commit
-
⑤有异常 rollback
-
为什么需要事务管理器来创建数据库连接呢?
因为Jdbc、Mybatis等操作数据的框架,只有在sql语句执行的时候才会创建一个新的数据库连接,创建的连接都是自动提交,他们不会更改提交方式,只有执行sql语句的作用。
而Spring中如果多条sql一起执行,每条语句只有在执行时才会创建连接,而且会自动提交,当系统出现异常时,导致事务已经提交,不会再进行回滚。
所以Spring在执行sql语句之前,先让事务管理器创建一个连接,打开事务,然后再执行所有sql,无异常提交,有异常回滚
Spring事务失效:
-
1、配置类没有添加@Configuration
没有@Configuration注解,会导致在配置类中创建的事务管理器的bean的数据库连接和jdbcTemplate或mybatis中使用的数据库连接不同,jdbcjdbcTemplate执行sql时,在当前线程的ThreadLocal中查到数据库的连接.get()获取不到,就会在jdbcTemplate执行sql时自动创建一个新的数据库连接,事务就会失效。
解决:为什么加上@Configuration就会事务成功?
因为@Configuration也实现了AOP,添加上AOP后,容器中放入的配置类的代理对象,此时,@Configuration实现的AOP与@Transactional实现的AOP不同,@Transactional是在代理类调用方法时,使用的是手动注入的被代理对象的方法,并没有用super.()的形式,所以@Transactional调用的是同一个被代理类的方法。
在@Configuration实现AOP的原理是使用super.()的方式,使用同一个代理对象创建bean,此时在切面逻辑中,判断了是否已经存在了dataSource,如果存在使用容器中有的,不存在再创建,由此保障了jdbcjdbcTemplate执行时使用的是与事务管理器中的数据库相同的连接。
-
2、同一个类里的方法自调用会导致事务失效,也就是被代理类中调用了其他标注@Transcation注解的方法,这个方法上的注解就会失效,因为这是通过被代理类实例调用的方法,而不是经过代理类调用,就不能完成@Transcation的使用
同一个类里的方法自调用会导致事务失效解决方法:(原因就是类中其他方法没经过代理类调用)
①可以创建一个新的代理类,将被代理类中的其他方法抽离出去,调用时使用新的代理类调用实现。
②另一中方法就是在被代理类中,手动注入一个被代理类对象属性,此时注入的被代理类对象并不是真正的被代理对象,实际上是代理对象,此时调用被代理类对象属性的其他方法,就会进行事务管理。
4.bean的销毁
方式一:方法上标注@PreDestory注解,就会在jvm关闭之前调用
方式二:@Bean注解上使用destroyMethod方法指定初始化方法
方式三:Bean对象实现DisposableBean接口来重写destory方法(要调用ApplicationContext的registerShutdownHook()方法才生效)
方式四:Bean对象实现Closeable接口来重写close方法(要调用ApplicationContext的registerShutdownHook()方法才生效)