1、Spring的循环依赖及解决方式:
循环依赖:就是循环引用,两个或者两个以上的bean互相持有对方,最终形成闭环。
Spring中循环依赖的场景:1、构造器的循环依赖 2、field属性的循环依赖 Spring可以解决field属性的循环依赖,并且必须是单例模式
如何检测:bean在创建的时候会给bean打标,如果递归调用回来发现正在创建中,则说明循环依赖了
具体:Spring容器会将所有正在创建的bean标识符放在一个“当前创建bean池”中,即singletonsCurrentlyInCreation。如果在创建bean过程中发现自己已经在“当前创建bean池”里时,将抛出BeanCurrentlyInCreationException异常表示循环依赖,bean创建完毕后将从singletonsCurrentlyInCreation中清除掉
以单例对象的初始化为例,右图为具体实例化的图
1、createBeanInstance 实例化,其实就是调用对象的构造方法实例化对象
2、populateBean 填充属性,这一步主要是对bean的依赖属性进行填充
3、initializeBean 调用Spring xml中的init方法
循环依赖主要发生在第一二步,即构造器循环依赖和field循环依赖
如何解决:三级缓存
/** Cache of singleton objects: bean name --> bean instance */
// 单例对象的cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
// 单例对象工厂的cache
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
// 提前曝光的单例对象的cache
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
我们在创建bean的时候,首先就会从cache中获取这个单例的bean,这个cache即为singletonObjects,具体方法为:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1、从一级缓存中取bean
Object singletonObject = this.singletonObjects.get(beanName);
// 2、如果获取不到,并且对象正在创建中(即还没有初始化完成,比如A在populateBean的过程中依赖了B对象,得先去创建B对象,这时候A即处于创建中状态)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 3、从二级缓存中获取,如果获取不到,并且允许singletonFactories通过getObject获取,就从三级缓存中获取
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//4、如果从三级缓存中获取到了,则从singletonFactories中移除,并放到earlySingletonObjects,就是从三级缓存移动到了二级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
其中三级缓存在createBeanInstance之后被如下方法引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
这里就是解决循环依赖的关键:当对象通过createBeanInstance创造出来,但是还没有进行初始化的第二步和第三步,但是已经可以通过对象引用定位到堆中的对象,这时候放到三级缓存中,这样如果A的某个field依赖了B的实例对象,同时B的field依赖了A的实例对象,A先初始化完成第一步,然后发现B还没有被create,所以走B的create,B初始化的时候发现自己依赖了A,于是尝试get(A),这时候可以通过三级缓存拿到A,B拿到A对象后顺利完成初始化123,完成后会放到一级缓存中,这样A也能拿到B的对象进行23初始化,然后放入一级缓存中,完成初始化
所以Spring不能解决:构造器循环依赖问题,因为加入三级缓存的前提是执行了构造器,所以构造器的循环依赖无法解决,而且只能解决单例模式下创建bean的循环引用问题,如果是prototype作用域Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的bean,就无法解决循环依赖。关于bean的作用域:
对于singleton作用域,Spring管理了bean的创建初始化和销毁,但是对于prototype作用域,Spring只负责创建,创建了bean实例后,后续就交给客户端代码管理,即销毁需要客户端来处理,Spring只负责new功能
三级缓存图解:
A对象的创建过程:
1. 创建对象A,实例化的时候把A对象⼯⼚放⼊三级缓存
2. A注⼊属性时,发现依赖B,转⽽去实例化B
3. 同样创建对象B,注⼊属性时发现依赖A,⼀次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放⼊⼀级缓存。
4. 接着继续创建A,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存
5. 最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象
因此,由于把实例化和初始化的流程分开了,所以如果都是⽤构造器的话,就没法分离这个操作,所以都是构造器的话就⽆法解决循环依赖的问题了。
为什么一定要三级缓存,二级是否可以:
不可以,因为AOP的原因,生成的可能是代理对象
三级缓存singletonFactory是传入的一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,如果bean被AOP切面代理则返回的是beanPorxy对象,否则就是bean实例,而如果是代理,每次都会产生一个新的代理对象,这样就会有问题,因为对象是单例的,所以需要二级缓存,将三级缓存产生的对象放到二级缓存中去,后面直接去二级缓存拿即可,避免再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象
关键代码:
2、BeanFactory和ApplicationContext区别:
1、BeanFactory是Spring最底层的接口,只提供了最简单的容器功能,即实例化对象和拿对象,而ApplicationContext(应用上下文),继承了BeanFactory接口,提供了更多功能,比如国际化,访问资源,载入多个上下文,消息发送,响应机制,AOP等 2、BeanFactory在启动的时候不会实例化bean,只有从容器中拿Bean的时候才会去实例化,而ApplicationContext在启动的时候就把所有的bean实例化了,不过可以为bean配置lazy-init=true来让bean延迟实例化
FactoryBean 和 BeanFactory有什么区别?
BeanFactory 是 Bean 的⼯⼚, ApplicationContext 的⽗类,IOC 容器的核⼼,负责⽣产和管理 Bean对象。
FactoryBean是个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理后,生产的对象是由getObject()方法决定的(从容器中获取到的对象不是
“ FactoryBeanTest ” 对象)。
*/
public interface FactoryBean<T> {
//创建的具体bean对象的类型
@Nullable
T getObject() throws Exception;
//工厂bean 具体创建具体对象是由此getObject()方法来返回的
@Nullable
Class<?> getObjectType();
//是否单例
default boolean isSingleton() {
return true;
}
}
Spring容器提供的扩展方法:
0)BeanFactoryPostProcessor接口(在容器启动阶段)
1)各种的Aware接口,可以获取Spring框架的一些对象,比如ApplicationContextAware,可以获取ApplicationContext对象,并对这些对象做自定义初始化操作
2)BeanPostProcessor接口
3)InitializingBean接口(@PostConstruct, init-method)
4)DisposableBean接口(@PreDestroy, destory-method)
@PostConstruct和InitializingBean的执行顺序
Constructor > @PostConstruct > InitializingBean > init-method
原因是PostConstruct在BeanPostProcessor前置处理器中就被执行了
https://blog.csdn.net/chendaoqiu/article/details/50700246
3、SpringMVC 工作原理:
流程说明:1、客户端(浏览器)发送请求,请求到DispatcherServlet
2、DispatcherServlet根据请求信息调用HandlerMapping,解析对应的Handler ,然后会返回一个包含interceptor,handler等的执行链
3、解析到对应的handler(也就是controller)以后,开始由HandlerAdapter适配器处理
4、HandlerAdapter根据handler来调用真正的处理器来处理请求,并处理相应的业务逻辑
5、处理完请求后,会返回一个ModelAndView对象,model是返回的数据对象,view是逻辑上的view
6、ViewResolver 会根据逻辑view来查找实际的view
7、DispaterServlet把返回的model传给view(视图渲染)
8、把view返回给请求者
4、Spring用到了哪些设计模式
1、工厂设计模式,Spring通过BeanFactory
、ApplicationContext
创建bean对象
2、代理设计模式, AOP功能的实现
3、单例设计模式,Spring 中的bean默认为单例
4、模版方法模式,Spring中jdbcTemplate
、hibernateTemplate等以template结尾的类,使用到了模版模式
5、包装器设计模式,连接多个数据库
6、观察者模式,Spring的事件驱动模型
7、适配器模式 Spring AOP中的增强或者通知,controller也用到了适配器模式
5、SpringBean的⽣命周期
SpringBean ⽣命周期简单概括为4个阶段:
1. 实例化,创建⼀个Bean对象
2. 填充属性,为属性赋值
3. 初始化
如果实现了xxxAware 接⼝,通过不同类型的Aware接⼝拿到Spring容器的资源,如果实现了BeanPostProcessor接⼝,则会回调该接⼝的postProcessBeforeInitialzation 和postProcessAfterInitialization ⽅法。如果配置了init-method ⽅法,则会执⾏init-method 配置的⽅法
4. 销毁
容器关闭后,如果Bean实现了DisposableBean 接⼝,则会回调该接⼝的destroy ⽅法。如果配置了destroy-method ⽅法,则会执⾏destroy-method 配置的⽅法
6、JDK和Cglib的区别:
JDK 动态代理主要是针对类实现了某个接⼝,AOP 则会使⽤ JDK 动态代理。他基于反射的机制实现,⽣成⼀个实现同样接⼝的⼀个代理类,然后通过重写⽅法的⽅式,实现对代码的增强。
⽽如果某个类没有实现接⼝,AOP 则会使⽤ CGLIB 代理。他的底层原理是基于 asm 第三⽅框架,通过修改字节码⽣成成成⼀个⼦类,然后重写⽗类的⽅法,实现对代码的增强。
Cglib | JDK | |
是否提供子类代理 | 是 | 否 |
是否提供接口代理 | 是(可强制) | 是 |
区别 | 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法 | 实现InvocationHandler 使用Proxy.newProxyInstance产生代理对象 被代理的对象必须要实现接口 |
7、Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 基于动态代理实现,属于运⾏时增强。
AspectJ 则属于编译时增强,主要有3种⽅式:
1. 编译时织⼊:指的是增强的代码和源代码我们都有,直接使⽤ AspectJ 编译器编译就⾏了,编译之后⽣成⼀个新的类,他也会作为⼀个正常的 Java 类装载到JVM。
2. 编译后织⼊:指的是代码已经被编译成 class ⽂件或者已经打成 jar 包,这时候要增强的话,就是编译后织⼊,⽐如你依赖了第三⽅的类库,⼜想对他增强的话,就可以通过这种⽅式
3. 加载时织⼊:指的是在 JVM 加载类的时候进⾏织⼊。
总结下来的话,就是 Spring AOP 只能在运⾏时织⼊,不需要单独编译,性能相⽐ AspectJ 编译织⼊的⽅
式慢,⽽ AspectJ 只⽀持编译前后和类加载时织⼊,性能更好,功能更加强⼤。
8、Springboot启动流程
1、执行注解SpringBootApplication,此注解包括三个注解(@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan)
2、初始化SpringApplication,加载各种资源,比如ApplicationContextInitializer,ApplicationListener,具体方式是通过SpringFactoriesLoader加载META-INF/spring.factories下的配置,比如如下配置
3、执行SpringApplication.run(),执行listeners.starting()启动监听器模块, 准备环境,根据不同的环境创建不同的Environment
2. 准备、加载上下⽂,为不同的环境选择不同的Spring Context,然后加载资源 加载所有配置了EnableAutoConfiguration的资源,配置Bean
3. 初始化,这个阶段刷新Spring Context,启动应⽤
4. 最后结束流程