Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • 手写Spring

    • IOC
    • AOP
      • 1.基于动态代理实现AOP切面
      • 2.将AOP扩展到Bean生命周期
      • 3.包扫描与占位符
      • 4.Autowired注解注入
      • 5.AOP代理对象属性注入
      • 6.三级缓存实现循环依赖
        • 架构设计
        • 三级缓存下的循环依赖与AOP代理
      • 7.数据类型转换工厂
    • 总结
  • SSM

  • SpringBoot

  • JavaWeb

  • Spring
  • 手写Spring
phan
2023-05-14
目录

AOP

# AOP

# 1.基于动态代理实现AOP切面

功能:实现AOP的核心是通过利用动态代理的方法增强实现。在InvocationHandler接口的invoke方法(MethodInterceptor接口的intercept方法)中实现对切面方法的拦截处理。

  • 动态代理

①jdk动态代理:Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)创建代理对象。其中第二个参数指明需要增强的类都实现了哪些接口,或哪些父类。通过jdk代理调用的所有接口方法,都会进入到InvocationHandler接口的invoke中执行增强。

②Cglib代理:通过Enhancer对象和配置对应的参数创建代理对象。setSuperclass表示所要代理对象的Class,setInterfaces设置代理对象实现的一组接口,setCallback设置调用目标方法前执行的回调拦截器MethodInterceptor,所有代理对象方法执行都会进入intercept方法进行增强。

③Cglib和jdk代理两者区别在于,jdk代理只能代理实现类的接口方法(第二个参数interfaces),而Cglib代理对象可以是一个实现类,对于实现类以及接口的所有方法都能够感知并拦截。

此外两种代理方式的方法增强最终都是通过调用方法拦截器MethodInterceptor的invoke方法,并传入MethodInvocation类型对象(重写proceed方法实现拦截方法放行,可以修改方法参数)。但是区别在于Cglib所有方法的invoke是通过MethodProxy方法代理对象进行,而jdk代理在封装类ReflectiveMethodInvocation中通过Method.invoke放行。

  • 架构设计

要实现AOP切面和动态代理方法的解耦,关键是要抽离各自的方法和职能。关于动态代理类,只暴露代理方法进行调用的api,同时考虑到不同的代理方法需要提供统一的代理对象创建方法。而对于AOP切面类,需要完成拦截方法的判断、用户自定义拦截方法的织入、代理对象封装(invoke形参Object只能拿到CGLib动态生成的代理类实例,而不是代理的对象)等等。

用户定义拦截器时,需要①自定义拦截类,实现拦截接口②指定切面方法③通过代理对象调用切面方法,触发拦截器。

此处用户使用AOP还存在冗余的地方,首先自定义的拦截器还需要手动编程加入到切面封装类,此外用户触发拦截需要手动创建封装的代理对象(而不是通过从Spring容器中取出Bean调用就可以触发)。

image-20230412140643768

# 2.将AOP扩展到Bean生命周期

功能:将创建AOP代理实例的过程扩展到Spring容器每个Bean的生命周期中。

  • 架构设计

因为只有通过动态代理创建的Bean对象才具有AOP拦截功能,所以需要在createBean中创建实例之前判断用户在哪些Bean中创造了切点,这些Bean需要通过动态代理工厂创建对象。而区分普通Bean和代理Bean是通过切面包装类的classFilter类拦截器匹配实现。因为切面包装对象中包含拦截器方法对象,故切面包装类+切面方法类都要在spring.xml配置(一对一关系),交给容器管理。

DefaultAdvisorAutoProxyCreator实现了BeanPostProcessor接口和Aware接口,在进行拦截类匹配之前,通过getBeansOfType方法把切面包装类对象放入缓存(这里扩大了BeanPostProcessor增强方法的职能范围,不仅限于初始化前,实例化前也可以)。

注意:虽然整个拦截匹配过程可以直接封装一个方法(直接用getBeansOfType预加载),直接在createBean开头调用,没必要封装一个类,但实际上是做不到的。因为AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的父类,它拿不到子类的getBeansOfType方法和beanDefinitionMap对象的,也就不能进行切面类预加载。这就是为什么DefaultAdvisorAutoProxyCreator要实现工厂容器感知接口的原因。

  • 细节

AspectJExpressionPointcutAdvisor:Pointcut用于获取切面,而 Advice切面方法决定执行的拦截操作。

MethodBeforeAdviceInterceptor:定义切面拦截器。代理对象触发拦截时具体的调用链:

MethodBeforeAdviceInterceptor#invoke——>UserServiceBeforeAdvice#before+MethodInvocetion#proceed

DefaultAdvisorAutoProxyCreator:可以定义一个切面包装类缓存List<AspectJExpressionPointcutAdvisor>,每次对当前Bean进行拦截判断时,直接从缓存取出切面进行匹配。

动态代理实例所代理的对象通过beanClass反射使用无参构造器来创建。

拦截方法类+切面包装类+自动代理创建类都需要定义无参构造方法,否则Spring创建实例时找不到无参构造器会报错。

存在的问题:如果同个方法被多个AOP拦截,那么就会对该目标类重复生成多个AOP代理对象。此处没有解决AOP代理嵌套问题。

image-20230412220109818

# 3.包扫描与占位符

  • 使用占位符对property属性值填充

功能:Bean配置property的属性值value时,用户希望从一个统一的资源文件读取。占位符作用是将properties文件配置值引入到Bean的属性配置中,也就是对BeanDefinition的信息进行修改。两种方案,一种直接在解析XML文件过程中添加对占位符value的过滤操作,问题在于添加后XML解析过程的耦合度增加。基于职权分离解耦的思想,这里采用BeanFactoryPostProcessor实现占位符的替换,只要在Bean实例化之前完成对BeanDefinition的修改和填充都是可以的。

PropertyPlaceholderConfigurer :①实现了BeanFactoryPostProcessor接口,因此需要在spring.xml添加配置Bean,同时指定占位符填充资源文件的路径location。②Bean定义修改方法中,获取location资源文件的输入流并加载资源,通过getProperty拿到真正的字段值。注意完成修改后需要删掉BeanDefinition中含有占位符的PropertyValue。

  • 包扫描+@Component实现自动装配

功能:配置包扫描注册bean的功能。核心在于通过Hutool包的ClassUtil.scanPackageByAnnotation方法,可以返回指定包下含有指定注解的所有Class对象。并把整个包扫描过程添加到XML解析过程doLoadBeanDefinitions中。对扫描到的每个类创建对应的BeanDefinition。

ClassPathBeanDefinitionScanner#doScan:①获取指定包名下的所有Component注解的Class对象②Scope注解解析,判断Bean作用域③初始化BeanName(优先component指定,没有则初始化为类名首字母小写)④封装BeanDefinition并加入Bean工厂集合中。

问题:包扫描方式实际上开启了注解开发的模式。因为包扫描方式不能指定Bean的初始化属性以及初始化方法,仅仅只是根据无参构造器进行实例化。想要进一步给Bean添加定制化服务,只能通过注解的方法。

# 4.Autowired注解注入

功能:解决包扫描自动注册Bean的属性注入问题。对象类型通过@Autowired注入,而具体注入的对象通过getBean获取(Autowired是ByType获取,因此需要另外封装一个getBean方法)。属性值注入通过@Value注入,通过容器的字符串解析器解析配置文件获取。

  • 架构设计

AutowiredAnnotationBeanPostProcessor:属性注入、对象注入需要在对象属性填充applyPropertyValues之前进行。因此属性注入类需要实现InstantiationAwareBeanPostProcessor接口(此处BeanPostProcessor的方法执行时机已经不是狭义的Bean初始化前后了,而是扩大到createBean整个生命周期)。

StringValueResolver:Spring把从配置文件中解析占位符字段属性值这一操作方法抽象出来,创建字符串解析器集合,用于解析不同类型资源的字符串。在BeanFactoryPostProcessor修改定义方法中,处理解析XML的占位符后将占位符解析器加入到Spring的字符串解析器缓存中,给后续在Bean属性填充前解析@Value使用。

  • 细节

如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解。

@Qualifier指明BeanName按名注入,解决容器中有多个相同类型的对象的注入问题。

postProcessPropertyValues在执行属性注入前,需要检查Spring实例化的对象是否为CGlib的对象,否则不能正确获取到对象的Field。

此处区分两个属性注入:

①XML配置文件的占位符属性注入:执行时机为BeanFactoryPostProcessor修改Bean定义中进行,这里注入填充的实际上是BeanDefinition中的value值。

②类内部@Value注解的属性实现注入:执行时机是在Bean声明周期的属性填充之前进行,此处注入的对象是Bean的实例化对象,通过反射设置对象的属性值。

image-20230413201640749

# 5.AOP代理对象属性注入

  • 功能

createBean中AOP代理实例最先解析创建并返回,后续的XML属性、自动注入属性这些步骤都不会进行。因此这里要实现AOP代理实例的属性注入,主要在createBean生命周期中进行,核心有两点:①代理对象需要执行执行所有的属性注入方法②createBean方法返回时,通过ProxyFactory代理工厂创建的对象不能被Spring容器内部createBeanInstance创建的实例给覆盖。

  • 架构设计

把resolveBeforeInstantiation解析创建AOP代理对象的步骤放到beanPostProcessorAfterInitialization初始化方法后置执行,这样可以同时保证上述两点。所有的Bean都会经过如下转换过程:Spring实例——>属性填充——>解析并创建AOP代理实例。

这里TargetSource的构建方法和之前不同的地方在于,之前是根据beanClass直接暴力new一个对象进行代理(没有任何属性)。而修改后可以直接把前面完成属性填充的Spring对象作为要代理的目标对象TargetSource。

另外如果Spring实例如果是通过Cglib创建的,在根据TargetSource反射创建动态代理时会出错。因此通过TargetSource获取Class对象需要进一步转换拿到实际所定义的Class。

# 6.三级缓存实现循环依赖

一级缓存解决循环依赖:缓存读取对象存在则返回——>创建新的对象——>对象创建后马上写入缓存——>属性注入。

三级缓存整体思路类似,解决了AOP动态代理对象的属性初始化问题,更便于分层管理。

# 架构设计

  • 缓存设计

singletonObjects:一级缓存,存放成品对象。

earlySingletonObjects:二级缓存。此处实际上存放两种对象,一种是循环依赖过程当中属性未注入完的半成品对象。而如果不存在属性依赖,存放的是成品对象。事实上这个地方是一个动态的过程,因为在循环依赖问题中,二级缓存保存的对象(AOP代理的TargetSource真实对象)和applyPropertyValues属性注入过程使用对象都是同一个bean,在执行的过程中都会从半成品自动转化为成品。

二级缓存读取的时机(非空):在A对象完成整个属性的循环依赖后,最终添加到一级缓存之前的getSingleton中进行。

singletonFactories:三级缓存。此处存放的是代理工厂对象(Lambda表达式),通过调用getObject方法触发Lambda表达式的延迟加载机制,代理工厂会对当前Bean解析并最终返回AOP代理对象或者普通Bean对象。

三级缓存的读取时机(非空):不存在循环依赖的对象最终添加到一级缓存之前的getSingleton中进行。

  • 流程设计

doCreateBean生命周期中,spring容器创造对象后,需要立刻放入三级缓存暴露出来:

if (beanDefinition.isSingleton()) {
    Object finalBean = bean;
    addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,beanDefinition,finalBean));
}
1
2
3
4

在普通bean都初始化完后,需要从三级缓存中取出AOP代理工厂,并通过getObject取出解析的成品对象(此时触发Lambda延迟回调)。并将成品对象注册到一级缓存中(清空二三级缓存)。

if (beanDefinition.isSingleton()) {
    exposedObject = getSingleton(beanName);
    registerSingleton(beanName, exposedObject);
}
1
2
3
4
  • AOP代理设计

在DefaultAdvisorAutoProxyCreator动态代理类中,设计了earlyProxyReferences作为代理缓存(存放解析过的beanName)。getEarlyBeanReference方法用于对bean对象的延迟动态代理解析,结果作为三级缓存的bean返回。

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    earlyProxyReferences.add(beanName);
    /** 解析并返回代理对象*/
    return wrapIfNecessary(bean, beanName);
}
1
2
3
4
5
6

此处代理缓存的优点是解决了AOP代理对象在循环依赖中的多次解析生成重复代理对象的问题,但是并没有解决普通代理对象的多次解析问题。

  • 细节

Spring采用Cglib实例化+AOP采用Cglib则在对象创建时会出错(Spring解决嵌套代理的方式是通过拦截器链)。

AOP采用jdk代理则会在setFieldValue反射赋值时出错,因为Hutool工具包直接通过jdk代理对象的getClass拿不到真正的目标类。

earlyProxyReferences遗留问题:代理缓存的作用可以保证循环依赖的代理对象只进行一次代理解析过程。虽然引入了缓存之后,所有单例对象的AOP代理解析创建都可以交给三级缓存singletonFactories进行,因此对于非循环依赖的普通代理对象实际上执行了两次代理对象创建(一次是initializeBean,另一次是getEarlyBeanReference),存在OOM的隐患,这里去掉初始化后置代理处理方法虽然可以解决,但是原型对象的整个Bean生命周期不会走缓存,因此原型对象的代理解析只能交给initializeBean进行,所以不能去掉初始化后置的解析过程。

要解决这个问题可以在后置解析过程中加一层单例判断,如果为单例模式则直接跳过解析过程。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
    if (beanDefinition.isPrototype()&&!earlyProxyReferences.contains(beanName)) {
        return wrapIfNecessary(bean, beanName);
    }
    return bean;
}
1
2
3
4
5
6
7
8

# 三级缓存下的循环依赖与AOP代理

在doGetBean中有一个缓存预读取,如果不存在才执行createBean创建bean整个生命周期。下面以整个A—>B—>A的循环依赖顺序为例,此处A注册了切面需要AOP代理:

①A创建对象,并将代理工厂对象存入三级缓存

②A执行applyPropertyValues进行属性依赖注入,getBean(B)

③B创建对象,并将代理工厂对象存入三级缓存

④B执行applyPropertyValues进行属性依赖注入,getBean(A)

⑤在doGetBean入口处从缓存读取A,触发①中的三级缓存代理工厂解析调用,添加代理缓存earlyProxyReferences,拿到AOP代理解析结果,并存入二级缓存。返回给④

⑥B属性填充完毕,从③取出代理工厂对象,触发代理解析方法执行,再将B成品对象的返回结果(普通Bean)存入一级缓存。最后返回②

⑦A属性填充完毕,postProcessAfterInitialization执行后置的代理解析,因为代理缓存存在,故直接返回。最后从二级缓存取出成品对象,并存入一级缓存。流程结束。

cache

# 7.数据类型转换工厂

  • 功能

在属性填充applyPropertyValues和@Value解析postProcessPropertyValues两个方法中会通过setField反射给对象设置属性,为了保证value类型和对象属性类型(包括不同格式)匹配一致,Spring中使用一个全局的类型转换器。

  • 架构设计

转换器需要在Bean的生命周期属性注入时使用,因此类型转换服务对象在引入时应该存放在BeanFactory中,而不是放在上下文对象中。

ConversionServiceFactoryBean:将FactoryBean和InitializingBean联合使用,实现成员对象赋值与剥离,非常精彩。

①类型转换工厂,维护了一个Set集合用来保存用户自定义转换器集合,此处将用户自定义转换器引入Spring的方式是通过在XML中将工厂的Set属性通过ref关联到用户定义的FactoryBean对象(为了保证类型匹配getObject也需要为Set集合)。

②此外工厂Bean还实现了InitializingBean接口会在afterPropertySet中new创建DefaultConversionService转换服务对象,并将用户自定义创建的转换器集合注册到转换服务对象的缓存Map中。

③这里通过FactoryBean#getObject方法,巧妙的把ConversionServiceFactoryBean工厂对象转化为DefaultConversionService服务对象。在afterPropertySet阶段把工厂对象里面的所有转化器对象全部搬到服务对象里完成注册,最后直接把成员属性(服务对象)返回。

finishBeanFactoryInitialization:上下文操作类会将类型转换工厂初始化并将该Bean对象存放入BeanFactory中,然后再执行容器预加载。在Bean生命周期中BeanFactory通过调用DefaultConversionService类型转换服务对象实现格式转换。

convert

  • 细节

Collections.singleton:返回一个Set集合,其中的元素为一个只包含object(obj)的不可变集合。

Converter<S,T>:用户需要指定接口泛型和实现convert转换方法,来定制化实现不同类型的转化方法。

GenericConversionService实现了整个转换服务的关键方法:

①getRequiredTypeInfo:用于解析Converter对象接口泛型参数的源和目的转换类型,并封装成GenericConverter.ConvertiblePair对象(内部类,保存源&目的类型转换对)返回。

②converters:维护一个转换器缓存Map。key为源&目的类型对,value为对应进行类型转换的转换器适配器。

③getConverter:根据源目的类型获取对应转换器。这里类型转换需要考虑到子父类型的转换问题。

④采用适配器模式(用户自定义的Converter对象都需要先转为ConverterAdapter)基于GenericConverter接口抽象出两种类型转换适配器,一种是转换的目标类型可以是所指定Target类型的子类,另一种是直接类型转换。

#

编辑 (opens new window)
#Spring
上次更新: 2023/12/15, 15:49:57
IOC
总结

← IOC 总结→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式