容器与bean
1)容器接口
- BeanFactory 接口,典型功能有:
- getBean
- ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能,如:
- 国际化
- 通配符方式获取一组 Resource 资源
- 整合 Environment 环境(能通过它获取各种来源的配置信息)
- 事件发布与监听,实现组件之间的解耦
- beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象
- 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中
- bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等
- beanFactory 需要手动调用 beanFactory 后处理器对它做增强
- 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition
- beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强
- 例如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的
- bean 后处理的添加顺序会对解析结果有影响
- beanFactory 需要手动调用方法来初始化单例
- beanFactory 需要额外设置才能解析 ${} 与 #{}
2)Bean 的生命周期
- 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
- scope 为 singleton 时,bean 是随着容器一起被创建好并且实例化的
- scope 为 prototype 时,bean 是随着它被调用的时候才创建和实例化
- 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
- 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
- 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
- prototype 对象也能够销毁,不过需要容器这边主动调用
3)Bean后处理器
(1)bean 后处理器排序
- 实现了 PriorityOrdered 接口的优先级最高
- 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
- 其它的排在最后
(2)后处理器作用
- @Autowired @Value 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能,这些扩展功能由 bean 后处理器来完成
(3)@Value装配底层
- 查看需要的类型是否为 Optional,是,则进行封装(非延迟),否则向下走
- 查看需要的类型是否为 ObjectFactory 或 ObjectProvider,是,则进行封装(延迟),否则向下走
- 查看需要的类型(成员或参数)上是否用 @Lazy 修饰,是,则返回代理,否则向下走
- 解析 @Value 的值
- 如果需要的值是字符串,先解析 ${ },再解析 #{ }
- 不是字符串,需要用 TypeConverter 转换
- 看需要的类型是否为 Stream、Array、Collection、Map,是,则按集合处理,否则向下走
- 在 BeanFactory 的 resolvableDependencies 中找有没有类型合适的对象注入,没有向下走
- 在 BeanFactory 及父工厂中找类型匹配的 bean 进行筛选,筛选时会考虑 @Qualifier 及泛型
- 结果个数为 0 抛出 NoSuchBeanDefinitionException 异常
- 如果结果 > 1,再根据 @Primary 进行筛选
- 如果结果仍 > 1,再根据成员名或变量名进行筛选
- 结果仍 > 1,抛出 NoUniqueBeanDefinitionException 异常
(4)@Autowired装配底层
- @Autowired 本质上是根据成员变量或方法参数的类型进行装配
- 如果待装配类型是 Optional,需要根据 Optional 泛型找到 bean,再封装为 Optional 对象装配
- 如果待装配的类型是 ObjectFactory,需要根据 ObjectFactory 泛型创建 ObjectFactory 对象装配
- 此方法可以延迟 bean 的获取
- 如果待装配的成员变量或方法参数上用 @Lazy 标注,会创建代理对象装配
- 此方法可以延迟真实 bean 的获取
- 被装配的代理不作为 bean
- 如果待装配类型是数组,需要获取数组元素类型,根据此类型找到多个 bean 进行装配
- 如果待装配类型是 Collection 或其子接口,需要获取 Collection 泛型,根据此类型找到多个 bean
- 如果待装配类型是 ApplicationContext 等特殊类型
- 会在 BeanFactory 的 resolvableDependencies 成员按类型查找装配
- resolvableDependencies 是 map 集合,key 是特殊类型,value 是其对应对象
- 不能直接根据 key 进行查找,而是用 isAssignableFrom 逐一尝试右边类型是否可以被赋值给左边的 key 类型
- 如果待装配类型有泛型参数
- 需要利用 ContextAnnotationAutowireCandidateResolver 按泛型参数类型筛选
- 如果待装配类型有 @Qualifier
- 需要利用 ContextAnnotationAutowireCandidateResolver 按注解提供的 bean 名称筛选
- 有 @Primary 标注的 @Component 或 @Bean 的处理
- 与成员变量名或方法参数名同名 bean 的处理
4)BeanFactory 后处理器
(1)BeanFactory 后处理器的作用
- @ComponentScan, @Bean, @Mapper,@Import ,@Configuration 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
- 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义
- Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
(2)@Configuration
- 配置类其实相当于一个工厂,标注@Bean注解的方法相当于工厂方法
- @Bean不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法
- @Configuration默认会为标注的类生成代理,其目的是保证@Bean方法相互调用时,仍能保持其单例特性
- @Configuration中如果含有BeanFactory后处理器,则实例工厂方法会导致配置类提前创建,造成其依赖注入失败。
- 解决方法:改用静态工厂方法或直接为@Bean依赖注入(局部变量)
(3)@Import
- 可以引入单个bean
- 可以引入一个配置类
- 可以通过selector引入多个类
- 可以通过beanDefinition注册器引入
- @import和@Bean引入同一个对象问题:
- 统一配置类中,@Import先解析@Bean后解析
- 同名定义,默认后面解析的会覆盖前面解析的
- @Import引入的类实现DeferredImportSelector接口,则会先解析@Bean,再解析@Import
5)Aware 接口
- Aware 接口提供了一种【内置】 的注入手段,例如
- BeanNameAware 注入 bean 的名字
- BeanFactoryAware 注入 BeanFactory 容器
- ApplicationContextAware 注入 ApplicationContext 容器
- EmbeddedValueResolverAware 注入 ${} 解析器
- InitializingBean 接口提供了一种【内置】的初始化手段
- DisposableBean 接口提供了一种【内置】的销毁手段
- 对比:
- 内置的注入和初始化不受扩展功能的影响,总会被执行
- 而扩展功能受某些情况影响可能会失效,因此 Spring 框架内部的类常用内置注入和初始化
(1)@Autowired失效分析
- 如果出现Java配置类包含BeanFactory后处理器的情况,因此要创建其中的BeanFactory后处理器必须提前创建Java配置类,而此时的Bean后处理器还未准备好,导致@Autowired等注解失效
- 解决方法:
- 用内置依赖注入和初始化取代拓展依赖注入和初始化
- 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建
6)初始化与销毁
(1)初始化销毁顺序
初始化方法顺序
- @PostConstruct 标注的初始化方法
- InitializingBean 接口的初始化方法
- @Bean(initMethod) 指定的初始化方法
销毁方法顺序
- @PreDestroy 标注的销毁方法
- DisposableBean 接口的销毁方法
- @Bean(destroyMethod) 指定的销毁方法
7)Scope
-
在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
- singleton,容器启动时创建(未设置延迟),容器关闭时销毁
- prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
- request,每次请求用到此 bean 时创建,请求结束时销毁
- session,每个会话用到此 bean 时创建,会话结束时销毁
- application,web 容器用到此 bean 时创建,容器停止时销毁
-
但要注意,如果在 singleton 注入其它 scope 会发现注入的都是同一个对象,解决方法有:
- @Lazy
- @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
- ObjectFactory
- ApplicationContext.getBean
8)FactoryBean
- 它的作用是用制造创建过程较为复杂的产品, 如 SqlSessionFactory, 但 @Bean 已具备等价功能
- 被 FactoryBean 创建的产品
- 会认为创建、依赖注入、Aware 接口回调、前初始化这些都是 FactoryBean 的职责, 这些流程都不会走
- 唯有后初始化的流程会走, 也就是产品可以被代理增强
- 单例的产品不会存储于 BeanFactory 的 singletonObjects 成员中, 而是另一个 factoryBeanObjectCache 成员中
- 按名字去获取时, 拿到的是产品对象, 名字前面加 & 获取的是工厂对象
AOP
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
- 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
- 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
- 作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
- aspectj 在编译和加载时,修改目标字节码,性能较高
- aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
- 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
1)JDK动态代理
- jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系。代理增强是借助多态来实现,因此成员变量、静态方法、final方法均不能通过代理实现
- 反射方法优化:
- 前16次反射性能较低
- 第17次调用会生成代理类,优化为非反射调用
2)Cglib代理
- 不要求目标实现接口,它生成的代理类是目标的子类,因此代理目标之间是子父关系,因此final类无法被cglib增强
- 调用目标方法:
- method.invoke 是反射调用,必须调用到足够次数才会进行优化
- methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
- methodProxy.invokeSuper 是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
3)Spring选择代理
ProxyFactory 用来创建代理
- 如果指定了接口,且 proxyTargetClass = false(默认为false),使用 JdkDynamicAopProxy
- 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
- 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy
4)从 @Aspect 到 Advisor
(1)代理创建器
- AnnotationAwareAspectJAutoProxyCreator 的作用
- 将高级 @Aspect 切面统一为低级 Advisor 切面
- 在合适的时机创建代理
- findEligibleAdvisors 找到有【资格】的 Advisors
- 有【资格】的 Advisor 一部分是低级的, 可以由自己编写
- 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得
- wrapIfNecessary
- 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理
- 它的调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
(2)代理创建时机
- 代理的创建时机
- 初始化之后 (无循环依赖时)
- 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存
- 依赖注入与初始化不应该被增强, 仍应被施加于原始对象
5)Spring代理特点
- 依赖注入和初始化影响的是原始对象,因此 cglib 不能用 MethodProxy.invokeSuper()
- 代理与目标是两个对象,二者成员变量并不共用数据
- static 方法、final 方法、private 方法均无法增强,代理增强基于方法重写
IOC
- IOC是反转控制,指的是创建对象由Spring容器管理,并由Spring根据配置文件创建对象实例,管理各个实例之间的依赖关系,有利于功能的复用
- ID依赖注入和IOC是一个概念,使用不同的角度描述,其应用程序在运行时依赖IOC容器动态注入外部对象
- 以前创建对象是由程序员主动创建的,IOC让对象创建不用再 new 了,有Spring容器自动生产,使用Java反射机制完成,程序在运行时根据配置文件动态创建对象以及管理对象,并调用对象的属性和方法
- Spring IOC的注入方式:
- 构造器注入
- setter注入
- 注解注入
Spring循环依赖
- Spring是如何解决循环依赖问题的?
- Spring是通过三级缓存解决了循环依赖,其中以及缓存为单例池(singletonObjects),二级缓存为早期曝光对象(earlySingletonObjects),三级缓存为早期曝光对象工厂(singletonFactories)
- 当A、B两个类发生循环依赖时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化后的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取所需要的依赖,此时的getBean(a)会从缓存中获取。第一步,先获取到三级缓存中的工厂,第二步,调用对象工厂的getObject方法来获取到对应的对象,在将对象放入到二级缓存中,同时移除三级缓存中的工厂,最后将这个对象注入到B中,紧接着B会走完它剩下的流程。当B创建完成后,会将B再注入到A中,此时A再进行初始化,之后清除二级缓存,将初始化的Bean放入一级缓存,最后A创建完成。
- 为什么要用三级缓存?二级缓存能解决问题依赖吗?
- 如果要使用二级缓存解决循环依赖问题,意味着所有的Bean在实例化就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理
- 构造方法及多例循环依赖解决办法:
- @Lazy
- @Scope
- ObjectFactory 或 ObjextProvider
- Provider