本人小白一个,不能保证博客中内容都准确,如果博客中有错误的地方,望各位多多指教,请指正。欢迎找我一起讨论
Spring知识总结
1、Spring中IOC的加载过程
IOC的加载过程其实就是配置的类去创建成一个bean的过程。
将一个类创建成一个bean,我们通常是怎么做的呢?
①配置类 (通过XML或者注解)
②加载Spring容器(ClassPathXmlApplicationContext 、AnnotationConfigApplicationContext )
ClassPathXmlApplicationContext:读取XML配置文件
AnnotationConfigApplicationContext :读取注解
不管是通过上面的哪种方式,都是将类的一些信息读取到 IOC 容器,存入BeanDefinition
③getBean()
下面以注解的方式讲解IOC的加载过程:
① new AnnotationConfigApplicationContext()
(1)this(): new了三个对象:DefaultListableBeanFactory 、AnnotatedBeanDefinitionReader 、ClassPathBeanDefinitionScanner
DefaultListableBeanFactory 是整个bean加载的核心部分,是Spring注册及加载bean的默认实现
AnnotatedBeanDefinitionReader 用来加载class类型的配置,将配置的bean定义信息一个一个的注册到BeanDefinition,一个类对应一个BeanDefinition
ClassPathBeanDefinitionScanner 扫描@ComponentScan("路径") 设置了路径下的文件/类,将@Component、@Controller、@Service、@Repository标记的类的定义信息一个一个的注册到BeanDefinition,一个类对应一个BeanDefinition
父类 GenericApplicationContext
当前类 AnnotationConfigApplicationContext
(2)register(componentClasses):使用AnnotatedBeanDefinitionReader将配置的类注册到BeanDefinition
(3)refresh()
2、bean的生命周期(单例bean)
简单的说就是: 实例化(相当于new,开内存空间。属性值为默认值)--->初始化(set注入,给属性赋值)--->销毁
稍微详细点说就是:对着下面这张图一顿bb,具体怎么bb,自行脑补。
1、实例化bean对象
2、给bean对象填充属性
3、检查一大堆xxxAware接口,通过重写它们的set方法,获取相对应的容器对象,设置相关的依赖
普通对象(你自定义的 你写的bean标签的东西)
容器对象(容器内部自己使用的对象)
那么问题来了,通过实现XxxAware接口,自定义设置的相关依赖,是什么时候注入进去的呢?又是怎么注入进去的呢?
在调用 initializeBean()初始化的时候,在该方法内调用的 invokeAwareMethods()方法将图片上面设置的依赖注入进去的
AbstractAutowireCapableBeanFactory#initializeBean()初始化方法部分代码:
AbstractAutowireCapableBeanFactory#invokeAwareMethods()方法:
这里仅仅是注入了BeanName、classLoader、beanFactory,那么其他的在哪里注入的呢?
在调用 initializeBean()初始化的时候,在该方法内调用的 postProcessBeforeInitialization()方法将其他设置的依赖注入进去的
AbstractAutowireCapableBeanFactory#postProcessBeforeInitialization()方法部分代码:
ApplicationContextAwareProcessor#invokeAwareInterface()方法:
4、调用bean前置处理器。 applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)
5、如果用户自定义了init方法,调用用户自定义的init()方法 。 invokeInitMethods(beanName, wrappedBean, mbd)
在<bean>标签中 init-method 属性设置的方法
6、调用bean的后置处理器。 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
在此处进行了AOP增强
7、bean初始化结束,创建好的bean被放入单例池中
8、注册必要的Destruction相关的回调接口
9、如果用户自定义了destroy方法,就调用用户自定义的destroy方法
在<bean>标签中 destroy-method 属性设置的方法
10、bean销毁,就这样结束了自己的一生
3、bean的循环依赖问题
1、什么是循环依赖?
循环依赖就是引用依赖,也就是两个或多个bean相互持有对方,如下图,Aservice中引用了Bservice,而Bservice中又引用的Aservice。此时Aservice实例的创建需要先有Bservice,而Bservice实例的创建又需要先有Aservice,此时就自然形成了一个环状结构。
2、怎么解决循环依赖?
以上面图中代码为例:
1)、构造器循环依赖(单例)--------- spring解决不了
Spring容器创建Aservice的实例,首先会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中查找当前bean是否在创建,如果没发现,就往下执行,而此时构造器参数需要Bservice的实例,但是此时没有Bservice,将Aservice标识符放到“正在创建bean池”,去创建Bservice的实例。
Spring容器创建Bservice的实例,首先会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中查找当前bean是否在创建,如果没发现,就往下执行,而此时构造器参数需要Aservice的实例,但是此时没有Aservice,将Bservice标识符放到“正在创建bean池”,去创建Aservice的实例。
Spring容器创建Aservice的实例,首先会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中查找当前bean是否在创建,而此时在“正在创建bean池”发现了Aservice标识符,表示了循环依赖,于是抛出BeanCurrentlyInCreationException异常。对于创建完毕的Bean将从singletonsCurrentlyInCreation中清除掉。
由于源码中是先创建了bean的实例(实例化),才将这个bean提前曝光,也就是将bean添加到 Map<String, ObjectFactory<?>> singletonFactories中,所以构造方法的循环依赖Spring解决不了。
为什么叫提前曝光呢?
因为这个缓存中的bean是一个未进行赋值的bean,仅仅是一个引用。
具体代码看下面的图,都是在 AbstractAutowireCapableBeanFactory 类中,图一和图三在同一个方法中(doCreateBean方法),图二是图一中调用的createBeanInstance()方法也是就在这个方法中实例化了bean,所以结合下面三个图中的源码,可以看出spring容器是先实例化bean之后才将bean提前曝光的,所以构造依赖解决不了。
2)、setter构造循环依赖(单例)
Spring提供了三级缓存来解决循环依赖。
DefaultSingletonBeanRegistry类中 :
一级缓存(单例池): Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
二级缓存: Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
三级缓存: Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
Spring容器创建Aservice的实例,首先会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中查找当前bean是否在创建,如果没发现,就往下执行,将Aservice标识符放到“正在创建bean池”中,根据无参构造创建实例bean,提前暴露到三级缓存singletonFactories 中,然后进行setter注入Bservice。
Spring容器创建Bservice的实例,首先会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中查找当前bean是否在创建,如果没发现,就往下执行,将Bservice标识符放到“正在创建bean池”中,根据无参构造创建实例bean,提前暴露到三级缓存singletonFactories 中,然后进行setter注入Aservice,在注入Aservice的时候,由于Aservice已经提前暴露在了三级缓存singletonFactories 中,此时Bservice就可以去三级缓存singletonFactories 中拿到Aservice的bean实例,进行setter注入Aservice。
最后完成Aervice对Bservice的注入。
AbstractAutowireCapableBeanFactory#doCreateBean()
DefaultSingletonBeanRegistry#addSingletonFactory()
AbstractBeanFactory#doGetBean()
DefaultSingletonBeanRegistry#getSingleton()
①先从singletonObjects(一级缓存)中获取实例,如果可以获取到则直接返回singletonObject实例。
②如果从singletonObjects(一级缓存)中获取不对实例,再从earlySingletonObjects(二级缓存)中获取实例,如果可以获取到则直接返回singletonObject实例。
③如果从earlySingletonObjects(二级缓存)中获取不对实例,则从singletonFactories(三级缓存)中获取singletonFactory,如果获取到则调用getObject方法创建实例,把创建好的实例放到earlySingletonObjects(二级缓存)中,并且从singletonFactories(三级缓存)删除singletonFactory实例,然后返回singletonObject实例。
④如果从singletonObjects、earlySingletonObjects和singletonFactories中都获取不到实例,则singletonObject对象为空。
3)、原型模式下的循环依赖(spring解决不了)
在创建bean的源码中直接判断 如果是原型模式下的循环依赖,直接抛出异常。
AbstractBeanFactory#doGetBean()
3、经过你的描述,二级缓存也可以解决循环依赖,那为什么还要用三级缓存呢?
上面描述的,就相当于, Aservice a = new Aservice(); Bservice b = new Bservice(); b.a = a; a.b=b; ,但是此时Bservice中填充的Aservice是原始对象,也就是说此时Aservice的属性是没有进行赋值的,但好在Aservice是单例的,再Bservice创建之后,Aservice会给它的属性赋值。
再想一个问题,如果Aservice需要进行AOP增强,那么此时放入单例池中的对象,应该是Aservice的代理对象,然而此时Bservice填充它自己的Aservice属性时,却是填充的是Aservice的原始对象,那么这样就有问题了,此时怎么将Aservice的代理对象填充给Bservice的Aservice属性呢?
此时就应该想想,我能不能AOP提前呢,能不能在Aservice创建的时候,就提前AOP生成代理对象放入一个map中,然后Bservice在注入它的Aservice属性时直接将去Map中去取Aservice的代理对象,然后注入进行。正常情况下,对象的AOP增强应该是在属性赋完值之后,在调用 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) 方法的时候进行Aop增强,然后由于此时Aservice出现了循环依赖,所以需要提前AOP生成代理对象。那么此时问题又来了,我应该怎么判断Aservice出现了循环依赖呢?
在Bservice的创建过程中,需要对它的属性Aservice进行注入,那么就会去“正在创建bean池(Set<String> singletonsCurrentlyInCreation)”中找,Aservice是否正在创建,如果此时Aservice正在创建,那么就说明此时出现了循环依赖,所以此时就可以让Aservice提前进行Aop增强,然后得到Aservice的代理对象。
此时有这样一个场景,现在在Aservice,Bservice的基础上,加了一个Cservice,此时这个Cservice与Aservice相互依赖,也就是说,Aservice中有个Cservice的属性,Cservice中有个Aservice的属性,那么此时在Cservice的创建过程中,跟Bservice创建过程一样,也会去“正在创建bean池”中Aservice是否正在创建,发现Aservice正在创建,此时就出现了循环依赖,此时Aservice又会去进行Aop增强,然后得到一个代理对象。那么此时就有问题了呀!Bservice的创建的时候得到一个Aservice的代理对象,Cservice的创建的时候又得到一个Aservice的代理对象,此时有两个不同的代理对象,此时问题就大发了呀!所以说这个问题该怎么解决呢?怎么才能让这两个代理对象是同一个代理对象呢?
所以此时,在Bservice的创建的时候得到一个Aservice的代理对象的时候就得那一个Map存在,当Cservice的创建的时候出现循环依赖的时候,就先去这个Map中去找,如果能找到就直接拿过来用,如果找不到,再去让Aservice进行Aopp增强。此时这个Map就是真正的二级缓存。代理对象中包含原始对象。此时问题又来了,这个Map中的代理对象能直接放入单例池(也就是一级缓存)中吗?
不行,因为此时的代理对象是个半成品,属性还没赋值呢,如果直接丢到单例池中,丢到单例池中去了,就说明此时这个bean已经初始化完成了,是一个完整的bean,然而此时如果直接丢进去,就相当于丢进去一个半成品,到时候别的bean拿到的都是Aservice的半成品就有问题了呀。那么问题来了,这个Map中的代理对象什么时候丢入一级缓存单例池中?--------在属性赋值完成之后,去二级缓存中拿对象,将该对象丢入单例池中。
Spring的AOP中target目标对象是bean的原始对象,也就是说,如果Aservice想要进行AOP,就需要拿到Aservice的原始对象,所以此时就需要一个Map来存,然后这个Map(三级缓存)存的不仅仅是原始对象,存的是一个lambda表达式,这个lambda表达式中包含了原始对象,看下图:
此时Bservice、Cservice创建过程中如果出现了循环依赖,就会去找三级缓存,然后就会执行三级缓存中的lambda表达式,在执行lambda表达式的过程中就会判断是否需要进行AOP增强,此时如果需要Aop增强,就会进行AOP增强得到代理对象,不需要Aop增强,就会得到原始对象,不管执行lambda表达式得到的是什么对象,都会将这个对象存入二级缓存。
Aservice的创建过程中,当bean属性值全部赋值之后,怎么判断Aservice是否已经进行了AOP增强了呢?是谁进行了判断呢?是在spring源码中判断的还是AOP插件中判断的呢?由于我们要使用Aop增强,就需要添加@EnableAspectJAutoProxy注解插件,所以肯定是在插件中判断的,在AbstractAutoProxyCreator类中有两个方法。
插件中为什么正常进行AOP的方法(postProcessAfterInitialization),如果提前进行了AOP,返回的是原始对象呢?因为源码中回去缓存中拿对象,不用它的返回对象。如果出现了循环依赖进行了提前AOP,那么源码中调用了getSingleton()方法,会去缓存中拿对象,此时拿到的是原始对象还是代理对象就看这个bean是否需要进行AOP。
4、多线程情况下,怎么避免获取到不完整的bean?
第一个bean还没有被创建完, 第二个bean就开始了. 这是典型的并发问题。这种情况下第一想法不就是加锁嘛。
spring源码中也是加锁:
第一处:在getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法中,在创建bean的时候,加了一把锁,锁的是一级缓存,保证只创建一个实例。
第二处:在getSingleton(String beanName, boolean allowEarlyReference)方法中,在从三级缓存中拿数据的时候,加了一把锁,因为要保证要保证二级缓存和三级缓存的原子性,也就是二级缓存的添加 和 市三级缓存的删除 整体的原子性。
5、Spring不支持扫描接口:
比如以下代码:
错误信息:No bean named 'userMapper' availale ======> NoSuch BeanDefinition Exception
========> 这个错误可能出现在哪里?
1)配有配置这个bean
2) 容器没有识别出来
==========>原因就是容器识别不出来,此时@Component标识的是接口,spring不支持扫描接口。
===========》能解决spring不能扫描接口的问题吗 ?
在 ClassPathScanningCandidateComponentProvider#isCandidateComponent方法中加下面代码,就可以让容器扫描接口,
但是会报错,以为容器处理不了接口
那怎么才能让容器能处理呢?
通过UserMapper的BeanDefinition中beanClass的值,给他一个实例对象不就Ok了嘛,
但是这个 UserMapper 是一个接口,怎么给它弄个实例对象???
使用动态代理技术给它一个动态代理对象就OK啦,但是这个代理对象怎么创建,又怎么给它呢?
此时就需要用到 FactoryBean 接口了,spring实例化 FactoryBean 的实现类的时候,会调用 getObject( ) ,最终会生成一个 getObject( )方法中创建的对象而不是 FactoryBean 实现类的对象。
此时,通过以上方法就是让spring扫描接口了,并生成接口的代理对象,就可以通过getBean()获取到。
=======》想想上面这样实现有什么缺点?
①修改了spring底层的源代码(ClassPathScanningCandidateComponentProvider#isCandidateComponent方法中加了一段代码)
②改变的Bean的定义属性BD,不灵活,每个接口都得改,来一个改一个
③ 每一个接口都得写一个对应的FactoryBean 生成代理对象 ,如果接口一多,人都傻了
=======》上面这些缺点怎么解决呢?
改spring源码肯定 是不现实的,那要怎么办才能在ClassPathScanningCandidateComponentProvider#isCandidateComponent方法中加一段代码呢?
自定义一个类去继承它,由于它还有个子类,不能去掉子类的功能,所以继承它的子类ClassPathBeanDefinitionScanner,再去重写它的这个方法
/**
* @author 啊涛
* @version 1.0
* @date 2021/1/4 11:48
*/
//@Component// 这里不能加 @Component 这个注解 不然会报错 , 鬼知道什么原因
public class ClassPathScanningCandidateComponentProviderGrandson extends ClassPathBeanDefinitionScanner {
public ClassPathScanningCandidateComponentProviderGrandson( BeanDefinitionRegistry registry ) {
super(registry);
}
@Override
protected boolean isCandidateComponent( AnnotatedBeanDefinition beanDefinition ) {
//在这个方法中加上这个if判断就可以让容器支持扫描你配置的接口,但是会报错,容器处理不了
return beanDefinition.getMetadata( ).isInterface( );
}
@Override
protected Set< BeanDefinitionHolder > doScan( String... basePackages ) {
return super.doScan( basePackages );
}
}
===》但是此时有个问题,你这个类怎么加入到spring中,让spring知道你这个类是干嘛的呢?
使用用@Component注解标识这个类, 肯定是不行滴,这样标识它就是个普通的bean,起不了作用 ,那怎么办?
=======》 自定义一个注解 EnableMapperScanner 用来导入你自定义的注册类,以及指定扫描哪个路径
自定义一个注册类 AtaoImportBeanDefinitionRegistrar ,在里面创获取 EnableMapperScanner 注解指定的路径,创建上面那个类的ClassPathScanningCandidateComponentProviderGrandson对象,通过它取开启可以扫描接口,并且扫描给定路径下的所有bean定义。 解决了上面说的①的缺点。
然后修改bean定义的属性,beanDefinition.setBeanClass( UserMapperFactoryBean.class ),这里的bean都是接口类型。此处通过遍历直接就处理了给定路径下所有的bean定义,解决了上面说的②出现的缺点。
然后通过 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue( beanClassName ); 指定使用哪个构造方式,只有直接把全类名传过去,可以在FactoryBean中,你传什么类型就生成什么类型的代理对象,就解决了上面说的③的缺点。
代码如下:
/**
* @author 啊涛
* @version 1.0
* @date 2021/1/4 14:51
*/
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Import( value = AtaoImportBeanDefinitionRegistrar.class )
public @interface EnableMapperScanner {
String basePackage();
}
/**
* @author 啊涛
* @version 1.0
* @date 2021/1/4 14:52
*/
//ImportBeanDefinitionRegistrar 解析bean定义的接口
// 使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,则会调用接口方法,将其中要注册的类注册成bean
public class AtaoImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata , BeanDefinitionRegistry registry ) {
AnnotationAttributes attributes = ( AnnotationAttributes ) importingClassMetadata.getAnnotationAttributes( EnableMapperScanner.class.getName( ) );
//配置了EnableMapperScanner注解
if ( attributes == null ){
return;
}
String basePackage = attributes.getString( "basePackage" );
//扫描bean定义
ClassPathScanningCandidateComponentProviderGrandson mapperScanner = new ClassPathScanningCandidateComponentProviderGrandson( registry );
mapperScanner.addIncludeFilter( new TypeFilter( ) {
@Override
public boolean match( MetadataReader metadataReader , MetadataReaderFactory metadataReaderFactory ) throws IOException {
return true;
}
} );
//此时扫描出来的bean定义 是一个一个的接口 //我们指定的interface1包下的接口类型bean定义
//批量导入bean定义(接口类型)
//扫描bean定义对象还是接口类型 doScan方法就是 给我一个路径,我给你路径下所有bean定义
Set< BeanDefinitionHolder > scannedBds = mapperScanner.doScan( basePackage );
for ( BeanDefinitionHolder bdh : scannedBds ) {
//获取bean定义
GenericBeanDefinition beanDefinition = ( GenericBeanDefinition ) bdh.getBeanDefinition( );
//拿到bean定义中的接口的Class字符串
String beanClassName = beanDefinition.getBeanClassName( );
System.out.println("原生接口的Class类型 " + beanClassName );
//如果不进行beanClass修改,会去实例化接口,就报错,所以此处需要对beanClass进行修改
beanDefinition.setBeanClass( UserMapperFactoryBean.class );
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
// 这行代码就是控制调用哪个构造函数
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue( beanClassName );
}
}
}
/**
* @author 啊涛
* @version 1.0
* @date 2021/1/3 20:42
*/
@SuppressWarnings("rawtypes")
//这里可以不用将这个类注入到spring , 运行的时候,spring应该会扫描到吧 不懂 不用加@Component
public class UserMapperFactoryBean<T> implements FactoryBean<T> {
private Class<T> targetClass;
public UserMapperFactoryBean( Class<T> targetClass ) {
System.out.println("targetClass ------------------ " +targetClass);
this.targetClass = targetClass;
}
@Override
@SuppressWarnings("unchecked")
public T getObject( ) throws Exception {
return (T) Proxy.newProxyInstance( targetClass.getClassLoader( ) , new Class[]{ targetClass } , new InvocationHandler( ) {
@Override
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {
return null;
}
} );
}
@Override
public Class< ? > getObjectType( ) {
return targetClass;
}
}
测试代码:
/**
* @author 啊涛
* @version 1.0
* @date 2020/12/22 9:49
*/
@ComponentScan("com.dt")
@EnableMapperScanner( basePackage = "com.dt.test.ioc.interface1")
public class NewBeanTest {
public static void main( String[] args ) {
ApplicationContext ac = new AnnotationConfigApplicationContext( NewBeanTest.class );
System.out.println( ac.getBean( "userMapper").getClass() );
}
}
6、Spring有 多个构造方法 怎么识别 用哪一个 呢?
默认是无参构造
如果你不想用无参构造,而是让它走你自己的写的有参构造 , 你在你写的有参构造方法上 加 @Autowred注解 ,就不会走无参,而是走你写的有参
如果类中 没有无参构造,但是有两个有参构造 ,此时运行会报错,因为spring不知道走哪个有参构造
spring 怎么走有参构造呢 ?有参构造的参数 怎么来的 ? 先byType 再 byName
如果直接byName去拿,可以会造成类型不匹配
7、 @Autowired底层大概是什么样的 ?
@Autowired工作原理 :
先 byType , 剔除掉 autocandidate = false的
看有没有 @Qualifier(名字 ) ,有的话,直接去找这个名字的bean
看 有没有 @Primary注解 -- - 主bean ,有的话,直接去找这个名字的bean
看 有没有配置bean的优先级 @Priority(1) 注解 设置优先级 ,值越大越高 ,有的话,直接去找优先级最高的bean
再 byName 找到这个值
再通过反射 赋值
注意:
@Resource 标识 方法时 ,会根据方法的名字来找 ,比如 方法名是 setUser , 会根据 user 去找
@Autowired 标识方法时,会根据方法中形参的类型找,在根据形参的 名字找
8、@Resource 底层大概是什么样的 ?
@Resource 工作原理 :
1、指定的name ,就根据 name去找 ,找不到就报错
2、 没指定 name,会根据 属性的 名字, 去map中找,判断存不存在这个名字的key
存在的话,就直接拿出value
不存在的话,就会按类型找 ,但是按类型找,找到多个就会报错,它不知道选哪个
注意:
@Resource 标识 方法时 ,会根据方法的名字来找 ,比如 方法名是 setUser , 会根据 user 去找
@Autowired 标识方法时,会根据方法中形参的类型找,在根据形参的 名字找
9、spring中的单例bean 不等同于 单例模式
单例bean 指的的是 根据bean的名字 getBean的时候,只有一个,也就是说,根据bean的名字来拿bean只能拿到一个 , 只要名字相同,拿到的bean就是一样的
也就是 去 单例池中去拿 map < bean名字 ,bean对象>
10、InitializingBean 接口
InitializingBean 接口 有一个 afterPropertiesSet()方法 ====== 相当于 xml 中的 init 配置
可以实现该接口 手动 来初始化 bean的属性值