Spring作为开源的框架目前正被互联网行业广泛的使用,在我们在面试的过程中也经常会被问到。这篇博客记录了我在学习spring的IOC容器中遇到的一些琐碎的知识点,并对相关文档进行参考,把它记录下来,巩固和学习,如有错误,欢迎指正。我们经常会被问 什么是spring? 其实很多人都认为spring就是IOC和AOP,其实不然,spring是产品,里面包含很多技术,包括springcloud,springdata,springfremwork等等,IOC和AOP只是spring中核心技术的一部分。
目录
1.9、ApplicationContext 创建并初始化spring容器
2.2、Singleton的bean中引用了一个Prototype的bean的时候会出现Prototype失效的情况
2.3、spring生命周期回调有哪几种方式,执行顺序是什么
2.4、当spring容器完成初始化之后,想执行某个方法,怎么做
2.5、在spring源码中生命周期初始化回调方法initializeBean做了哪些事情
4.2、AutowiredAnnotationBeanPostProcessor
4.3、CommonAnnotationBeanPostProcessor
4.5、BeanDefinitionRegistryPostProcessor
4.6、ConfigurationClassPostProcessor
5.8、为什么Appconfig类是通过register(Appconfig.class);手动put到map当中呢?为什么不是扫描出来的呢?
5.9、为什么spring当中默认支持循环依赖?或者spring在哪里体现了默认支持循环依赖?
一、spring IOC容器
1、基本内容
1.1、什么是Spring IOC?
首先IOC(Inversion of Control)就是控制反转,官网说法是,IOC也被称为DI(依赖注入)。官网截图如下:
Spring IOC是指:将对象的实例化反转给spring容器,在传统的开发模式中,当我们创建一个对象时,需要new或者newInstance等直接或者间接的方式调用构造方法创建这个对象,这样对象的实例化控制权是由应用程序控制的。而反转就是将这种权利反转给spring容器,spring容器通过工厂模式为程序员创建了所需的对象,程序员无需创建对象(只需要提供对象的类型即可),直接调用spring容器提供的对象就行了,这就是控制反转。
1.2、什么是DI(依赖注入)
依赖注入(DI)是指:spring 使用 java 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
1.3、为什么要使用spring IOC
第一:对象的实例化不是一件简单的事情,比如对象的关系比较复杂,依赖关系往往需要程序员去维护,这是一件非常头疼的事;
第二:解耦,由容器去维护具体的对象;
第三:托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分过程交给容器,应用程序则无需去关心类是如何完成代理的。
1.4、spring有几种编程风格
官网上说spring在1.0 使用xml配置方式,在2.5引入了注解 annotation(注解)配置方式,在3.0引入了javaconfig配置方式。官网说明如下:
1.5、Spring依赖注入方式有哪些
查看spring官方文档可知,注入方式有两种:
1、Constructor-based Dependency Injection(构造方法注入)
2、Setter-based Dependency Injection(基于setter的注入方式)
1.6、spring的自动装配模式有哪些
Spring的自动注入模型有四种:
no:(默认)无自动装配。
byName:按照名称进行装配,<bean id="dao" name="dao" class=""> spring会根据name的名字进行装配(默认 id 和name相同),在类中,name的值要与set方法一致 :例如: name="zlu" setter的方法应该为setZlu() 才能进行自动装配。
byType:按类型进行装配,spring会找 依赖的属性的接口或者父类下的实现类或者子类,进行注入,例如: private IndexDao dao; spring会找实现了IndexDao接口的类进行注入。
Constructor:类似于byType,但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,则会引发致命错误,使用Constructor的方式,系统会推断构造方法,选择参数最多的构造方法,解析构造方法的参数(每个参数表示一个依赖项)去找bean。
1.7、自动装配方式有哪些
有两种方式:在XML中有如下两种配置方式,按照setter的名字对应:
spring也提供了相应的api:
1.8、自动装配的优缺点
优点:
(1) 自动装配可以显著减少指定属性或构造函数参数的需要(减少依赖的注入的配置)
(2) 当对象发生改变会自动更新配置。例如,如果需要向类添加依赖项,在容器中添加bean即可。
缺点:(但是这些缺点对于我来说不能算作缺点)
(1) property和constructor-arg设置中的显式依赖项能够覆盖自动装配(xml中配置的依赖能够使自动装配无效)。
(2)自动装配不如显式注入精确,无法明确记录Spring管理对象之间的关系。
(3) 可能无法为可能从Spring容器生成文档的工具提供连线信息。
(4)容器中的多个bean定义可以匹配setter方法或构造函数参数指定的类型以进行自动装配。对于数组,集合或Map实例,这不
一定是个问题。但是,对于期望单个值的依赖关系,这种模糊性不是任意解决的。如果没有可用的唯一bean定义,则抛出异常。
1.9、ApplicationContext 创建并初始化spring容器
ApplicationContext可以通过ClassPathXmlApplicationContext和AnnotationConfigApplicationContext创建并初始化容器。
代码如图:
//java config的方式
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
//xml的方式
ApplicationContext context1 = new ClassPathXmlApplicationContext("spring.xml");
2、spring的beans
2.1、单例和原型
官方参考文档;https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scope。我们用的最多的是: singleton:单例(默认),每次获取的hashcode相同; prototype:原型:每次获取的hashcode不同。单例的对象在spring创建和初始化的时候实例化的,spring会将对象放入到一个单例池中(源码中的singletonMap中)缓存起来,在使用时直接去map中获取即可,原型的对象是在getBean的时候实例化的。
2.2、Singleton的bean中引用了一个Prototype的bean的时候会出现Prototype失效的情况
原因是:假设A是单例的,B是原型的,A依赖了B,那么虽然被依赖的对象B是prototype的,但是A对象是singleton的,在运行的过程中 这个A对象只是实例化了一次,因此只有一次机会设置B,每次需要时,容器不能为A提供B的实例。
解决方案:就是放弃控制反转,每次调用B时,为B创建新的实例。
1、在service类中新写一个方法,在方法上添加@Lookup ,代码如下:
@Service
@Scope(value = "singleton")
public class IndexServiceImpl {
@Autowired
private IndexDao dao;
public void query(){
System.out.println("service:"+this.hashCode());
getDao().test();
}
@Lookup()
public IndexDao getDao() {
return null;
}
}
(注意:Spring提供了一个名为@Lookup的注解,这是一个作用在方法上的注解,被其标注的方法会被重写,然后根据其返回值的类型,容器调用BeanFactory的getBean()方法来返回一个bean。源码中 在实例化的过程中,会将加了@LookUp的方法放到MethodOverrides set中,beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);生成实例策略时会判断,如果set里面有值,则return instantiateWithMethodInjection(bd, beanName, owner);进入这个方法,cglib代理)
2、实现接口ApplicationContextAware
代码如下:
@Service
@Scope(value = "singleton")
public class IndexServiceImpl1 implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public void query(){
System.out.println("service:"+this.hashCode());
((IndexDao)applicationContext.getBean("indexDaoImpl")).test();
}
}
2.3、spring生命周期回调有哪几种方式,执行顺序是什么
Spring中实现生命周期的回调有三种方式,一种是注解@PostConstruct和 @PreDestroy,一种是实现接口,InitializingBean,重写afterPropertiesSet()方法,实现接口DisposableBean,重写destroy()方法,还有一种是通过spring提供的xml里面配置一个default-init-method="init"或者 default-destroy-method=“destory”标签,指明方法。这三种方式都能实现spring生命周期的回调,但是他们有一个执行顺序,是注解最先,接口第二,xml指定是第三,为什么呢?因为spring处理生命周期回调是基于spring的生命周期来的,有源码得知,spring先通过一个后置处理器(InitDestroyAnnotationBeanPostProcessor)拿到这个注解解析执行,然后再执行invokeInitMethods方法,在invokeInitMethods方法里面依次执行接口方法(判断bean是否实现了InitializingBean接口)和xml方法
2.4、当spring容器完成初始化之后,想执行某个方法,怎么做
采用生命周期回调。(spring源码)在bea