【从零构建Spring|第十三节】 加载,解析资源并注册Bean对象

【从零构建Spring|第十三节】 加载,解析资源并注册Bean对象

目标

在上一章节我们通过基于 Proxy.newProxyInstance 代理操作中处理方法匹配和方法拦截,对匹配的对象进行自定义的处理操作。并把这样的技术核心内容拆解到 Spring 中,用于实现 AOP 部分,通过拆分后基本可以明确各个类的职责,包括你的代理目标对象属性、拦截器属性、方法匹配属性,以及两种不同的代理操作 JDK 和 CGlib 的方式。

再有了一个 AOP 核心功能的实现后,我们可以通过单元测试的方式进行验证切面功能对方法进行拦截,但如果这是一个面向用户使用的功能,就不太可能让用户这么复杂且没有与 Spring 结合的方式单独使用 AOP,虽然可以满足需求,但使用上还是过去分散。

因此我们需要在本章节完成 AOP 核心功能与 Spring 框架的整合,最终能通过在 Spring 配置的方式完成切面的操作

设计

将 AOP 融入 Spring 需要解决几个问题

  • 怎么通过 BeanPostProcessor 将动态代理融入 Bean 的生命周期中?
  • 如何组装各项切点、拦截、前置以及适配对应的代理器?

image-20230214153324289

  • 为了可以让对象创建过程中,能把xml中配置的代理对象也就是切面的一些类对象实例化,就需要用到 BeanPostProcessor 提供的方法 postProcessBeforeInitializationpostProcessAfterInitialization,因为这个类的中的方法可以分别作用与 Bean 对象执行初始化前后修改 Bean 的对象的扩展信息。但这里需要集合于 BeanPostProcessor 实现新的接口和实现类,这样才能定向获取对应的类信息
  • 但因为创建的是代理对象不是之前流程里的普通对象,所以我们需要前置于其他对象的创建,所以在实际开发的过程中,需要在 AbstractAutowireCapableBeanFactory#createBean 优先完成 Bean 对象的判断,是否需要代理,有则直接返回代理对象。在Spring的源码中会有 createBean 和 doCreateBean 的方法拆分
  • 这里还包括要解决方法拦截器的具体功能,提供一些 BeforeAdvice、AfterAdvice 的实现,让用户可以更简化的使用切面功能。除此之外还包括需要包装切面表达式以及拦截方法的整合,以及提供不同类型的代理方式的代理工厂,来包装我们的切面服务。

Advice、Advisor、PointcutAdvisor

image-20230214220424854

  • Advice 是 aop 提供的一个基类接口。本节用它定义 BeforeAdvice 在方法拦截前执行逻辑处理。也可以定义 AfterAdvice
  • Advisor 是 Advice 的访问者接口,只定义获取 Advice 方法。具体实现在 AspectJExpressionPointcutAdvisor,其也包装了切面、具体拦截方法、规则表达式
  • PointcutAdvisor 包装了 Advisor、Pointcut,它既能获取 advice,也可以获取 pointcut

代理工厂 ProxyFactory

抽象出代理工厂,使用 getProxy() 直接获取代理对象。代替了上一节中需要用户自己实例化具体的代理方式

public Object getProxy() {
return createAopProxy().getProxy();
}

其中,在 AdvisedSupport 新增 boolean:proxyTargetClass ,其为 true 代表使用 Cglib、否则选择使用 Jdk 方式实例化代理对象

public AopProxy createAopProxy() {
if (advisedSupport.isProxyTargetClass()) {
return new Cglib2AopProxy(advisedSupport);
}
return new JdkDynamicAopProxy(advisedSupport);
}

融入Bean生命周期的自动代理创建者

在 bean 实例化(初始化)之前,调用 postProcessorBeforeInstantiation 方法。该方法先执行 getBeansOfType 获取包装好的 Advisor :AspectJExpressionPointcutAdvisor。之后基本就是将上节测试的逻辑放在了该方法里。

值得注意的是,在上节中,我们使用 new 的方式创建出需要代理的类 UserService,再将其注入到 targetSource;但还记得 AOP 的定义吗,不通过 new 的方式创建出用户所需要的对象。精妙之处就在这里,此时需要代理类信息已经保存到 bean 里了,可以直接通过 beanClass.getDeclaredConstructor().newInstance() 这种反射的方式去创建实例完成注入

详细代码:

  • 先判断当前 bean 是否为基础结构类:Advice、Pointcut、Advisor。通过 isAssignableFrom(beanClass) 进行判断,只有 UserService 这种类型的数据结构才能够被代理。

总结

回过头来,之前提出的两个疑问我们是怎么解决?

怎么通过 BeanPostProcessor 将动态代理融入 Bean 的生命周期中?

定义 BeanPostProcessor 的子接口 InstantiationAwareBeanPostProcessor,它定义了接口方法 postProcessorBeforeInstantiation 该方法在 Bean 进行初始化(实例化)之前就会调用,目的是将该 bean 的实例化从普通实例化转变为代理实例化,需要填充 AOP 逻辑。

如何转变?

通过 getBeansOfType 获取 AspectJExpressionPointcutAdvisors 集合,而 AspectJExpressionPointcutAdvisor 是在 xml 中配置,在上下文加载时期被读取,我们知道,在 Spring IoC 中万物皆是 Bean 对象。

遍历每个 advisor,这时 ClassFilter 接口就排上用场了,它来检测当前 advisor 是否符合匹配规则。

匹配之后填充对应的 advisedSupport 属性。

最后调用 ProxyFactory 代理工厂的逻辑。选择是使用 Jdk 还是 Cglib 动态代理 bean。

+++

AOP的调用在 bean 对象执行其方法逻辑后。当没有调用 bean 对象逻辑或者 bean 对象没有对应逻辑时,自然也不会触发 AOP 切面方法

如何组装各项切点、拦截、前置以及适配对应的代理器?

通过接口继承接口的方法让逻辑更加通顺以及代码简洁。

在 AspectJExpressionPointcutAdvisor 里包装了 切面 Pointcut、具体拦截方法 advice、规则表达式 expression。

将上一节测试代码中的 UserServiceInterceptor 实现的逻辑转移到 MethodBeforeAdvice。它继承了 BeforeAdvice,在逻辑上为在方法调用前的通知。当然还有 AfterAdvice以及 MethodAfterAdvice,可以自己实现,它在逻辑上为在方法调用后的通知。