AOP流程源码分析-配置信息解析和代理对象创建

本次文章就算是开始对于aop 的流程源码分析了。区别于IOC,aop 相对来说简单一点,我们后续的主要内容就是从aop 相关配置信息的解析、代理的创建、代理流程的调用,这三个部分着手来简单说一下aop 的源码分析。

简述AOP的前世今生

首先系列文章开头还老套路,先说一下aop 的一些概念知识,其信息来源主还要是网络和官方。对aop 有一定了解的跳过这段内容。

  1. AOP(Aspect Orient Programming):面向切面编程;
  2. 用途:用于系统中的横切关注点,比如日志管理,事务管理;
  3. 实现:利用代理模式,通过代理对象,对被代理的对象增加功能。所以,关键在于AOP 框架自动创建AOP 代理对象,代理模式分为静态代理和动态代理;
  4. 框架:
    AspectJ使用静态代理,编译时增强,在编译期生成代理对象;
    SpringAOP使用动态代理,运行时增强,在运行时,动态生成代理对象;

目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以可以放心使用。

  • Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
  • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做@AspectJ ,但是这个和AspectJ 其实没啥关系。

要说明的是,这里介绍的 Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。如@Aspect、@Pointcut、@Before、@After 等注解都是来自于AspectJ,但是功能的实现还是Spring AOP 自己实现的。

Spring AOP 底层实现机制目前有两种:JDK 动态代理、CGLIB 动态字节码生成。在阅读源码前对这两种机制的使用有个认识,有利于更好的理解源码。

源码分析

源码的话,还是用之前分析IOC 的时候使用的源码,然后创建一个测试类。

public static void main(String[] args) {

  ClassPathXmlApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("classpath:applicationContext-aop.xml");

  UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
  userService.findAll();
}

我们今天主要源码分析目的就是测试类中applicationContext 的创建,也就是容器启动时候的内容,后面再说具体方法的调用。

因为之前说IOC 就已经将核心方法refresh 方法全部分析过一次了,对于IOC 没有了解的小伙伴可以从IOC 的第二篇文章:IOC流程解析-BeanFactory的创建 开始看一下。既然看过了,那么我们就直接定位到具体方法。

AOP配置信息解析

首先aop 的标签设置和bean 标签有点不一样,所以我们要看的话,就是先从标签的解析内容开始,即BeanDefinition 的构建。

<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans"
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd>
		
	<context:component-scan base-package="com.itheima"/>
	
	<!--@EnableAspectJAutoProxy-->
	<aop:aspectj-autoproxy />
</beans>

这里就直接定位到代码了,parseBeanDefinitions 方法(这里全局搜索就行,具体怎么找到还是看IOC 的第二篇文章),这里可以看到的是会有两个解析方法的分支,这个我们之前也解释过spring 的schema 机制,所以aop 标签走的应该是第二个方法parseCustomElement 方法。

if (node instanceof Element) {
  Element ele = (Element) node;
  //下面有两个分支
  if (delegate.isDefaultNamespace(ele)) {
    // 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
    //分支1:代表解析标准元素 <import />、<alias />、<bean />、<beans /> 这几个
    //标准节点
    parseDefaultElement(ele, delegate);
  }
  else {
    // 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
    //分支2:代表解析 <mvc />、<task />、<context />、<aop /> 、<component-scan />等
    //特殊节点
    delegate.parseCustomElement(ele);
  }
}

跟进方法。

这里的第一个重点就是去获取一个命名空间处理器,而之前介绍schema 机制的时候,就已经说过这个解析器是由对应的标签设置提供的,所以我们要找到aop 对应的“http://www.springframework.org/schema/aop” 标识后,再去根据标识找到对应的spring.handlers 文件。

//解析命名空间,得到一个命名空间处理器
//重点
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
//开始解析
//主线 重点
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

要是全局搜索不好找,因为可能有很多配置文件都引入了aop,那么可以直接在xml 中点击标识,跳转到对应的jar 包下面,再去找inf 文件夹下面对应的spring.handlers 文件

image-20220428154603852

这里文件中就存放这aop 标签的解析器。

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

接着直接跟进AopNamespaceHandler 类。

可以看到关于各个标签不同解析器的生成,我们上面配置的是aop 的基础标签aspectj-autoproxy 标签,所以直接找到对应的AspectJAutoProxyBeanDefinitionParser 类。至于这个init 方法是什么时候调用的,我们继续往下看。

public class AopNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.5+ XSDs
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace in 2.5+
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}
}

回到parseCustomElement 方法,跟进其resolve 方法。

我们可以看到init 方法就是这个时机点调用的,也就说这里对创建出不同的解析器对象,然后返回。

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;

再次回到parseCustomElement 方法。

既然已经知道了目前的解析器是AspectJAutoProxyBeanDefinitionParser 对象,那么我们就可以继续跟进其parse 方法。

跟进方法。我们重点要看的就是第一个注册AspectJAnnotationAutoProxyCreator 方法。

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
  // 1.注册AspectJAnnotationAutoProxyCreator
  AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  // 2.对于注解中子节点的处理
  extendBeanDefinition(element, parserContext);
  return null;
}

继续跟进。

这里的目的就是在BeanFactory 中注册AnnotationAwareAspectJAutoProxyCreator 对象。注意这里不是注册到单例池之中,具体是为什么,往下看。

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  ParserContext parserContext, Element sourceElement) {

  // 1.注册AnnotationAwareAspectJAutoProxyCreator
  BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    parserContext.getRegistry(), parserContext.extractSource(sourceElement));
  // 2.对于proxy-target-class以及expose-proxy属性的处理
  useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
  // 3.注册组件并通知,便于监听器做进一步处理
  registerComponentIfNecessary(beanDefinition, parserContext);
}

我们可以看下AnnotationAwareAspectJAutoProxyCreator 对象的结构。

image-20220428160623332

这个对象的顶层接口是BeanPostProcessor 接口,这说明该对象为一个Bean 的后置处理器对象。所以在BeanFactory 中注册的话,不是注册到单力池中,而是注册到BeanPostProcessor 的集合中。

小结

这里的aop 标签解析就是这样,目的就是往BeanPostProcessor 集合中注册第一个Bean 的后置处理器。

AOP对应的BeanPostProcessor的执行

在上面我们看到已经构建出了AOP 相关的BeanPostProcessor,那么下一步就是要看这些Bean 的后置处理器的执行,所以我们直接定位到Bean 的初始化内容doCreateBean 方法。

定位到该方法后直接找到对应的BeanPostProcessor 执行的相关内容initializeBean 方法的调用。

前置方法解析所有的advisor-postProcessBeforeInitialization方法

跟进该方法。第一个前置方法的调用 ,还是继续跟进applyBeanPostProcessorsBeforeInitialization 方法。不过该方法我们之前已经看过多次了,没什么内容,就是循环出所有的BeanPostProcessor 然后执行对应的postProcessBeforeInitialization 方法。

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
  // 2.在初始化前应用BeanPostProcessor的postProcessBeforeInitialization方法,允许对bean实例进行包装
  wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

我们直接跟进上面构建的AnnotationAwareAspectJAutoProxyCreator 对象的Before 方法,注意这里跟进去就是其上父类AbstractAutoProxyCreator 对象的实现,因为该对象是没有对Before 方法进行实现的。

跟进代码。找到其重点调用shouldSkip 方法,注意Before 方法是不是创建出代理对象的,但是会加载所有的Advisor。

if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
  this.advisedBeans.put(cacheKey, Boolean.FALSE);
  return null;
}

继续跟进。注意这里要跟进后找到其对应的子类重写。然后找到重点方法findCandidateAdvisors方法调用

List<Advisor> candidateAdvisors = findCandidateAdvisors();

还是跟进。还是找其对应的子类重写。

这里会首先根据父类规则找到的所有advisor,但是这里是没有的,也就是null,所以要看的是buildAspectJAdvisors 方法的调用。

@Override
protected List<Advisor> findCandidateAdvisors() {
  // Add all the Spring advisors found according to superclass rules.
  // 1.添加根据父类规则找到的所有advisor
  List<Advisor> advisors = super.findCandidateAdvisors();
  // Build Advisors for all AspectJ aspects in the bean factory.
  // 2.为bean工厂中的所有AspectJ方面构建advisor
  if (this.aspectJAdvisorsBuilder != null) {
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  }
  return advisors;
}

跟进方法。

首先会获取一次所有的advisors 对象缓存,如果已经有值就直接返回,如果没有值才会进行解析,目的就是为了只解析一次,因为所有的bean 初始化都会走这个BeanPostProcessor,所以要避免重复解析。

List<String> aspectNames = this.aspectBeanNames;

// 1.如果aspectNames为空,则进行解析
if (aspectNames == null) {

然后是获取所有的beanName,循环遍历。

// 1.1 获取所有的beanName
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
  this.beanFactory, Object.class, true, false);

// 1.2 循环遍历所有的beanName,找出对应的增强方法
for (String beanName : beanNames) {

第一个重点来了,一定是要被Aspect 注解修改的接口,才会被构建成advisor 对象。

// 1.4 如果beanType存在Aspect注解则进行处理
if (this.advisorFactory.isAspect(beanType)) {

第二个重点,调用getAdvisors 方法获取由当前对象的所有方法封装而来的Advisor 对象集合。

// 使用BeanFactory和beanName创建一个BeanFactoryAspectInstanceFactory,主要用来创建切面对象实例
MetadataAwareAspectInstanceFactory factory =
  new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
// 1.5 解析标记AspectJ注解中的增强方法===》》》》
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

跟进getAdvisors 方法,看下具体封装内容。

这里是根据方法上修饰的不同注解,来封装成不同的Advisor 对象。

// 2.获取Aspect()标注的类名
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();

// 5.获取切面类中的方法(也就是我们用来进行逻辑增强的方法,被@Around、@After等注解修饰的方法,使用@Pointcut的方法不处理)==>
for (Method method : getAdvisorMethods(aspectClass)) {
  // 6.处理method,获取增强器==>
  Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
  if (advisor != null) {
    // 7.如果增强器不为空,则添加到advisors
    advisors.add(advisor);
  }
}

继续跟进就没有必要了,我们看下大致的写法。

首先是@Aspect 注解修饰类,说明这个类是切面类,可以被aop 找到

然后是@Pointcut 注解修改第一个方法,所有需要被拦截的具体对象

最后就是@Before 注解修改前置方法、@After 注解修饰后置方法、@Around 注解修改环绕方法。

@Component
@Aspect // 切面
public class AopAspect {
  
	@Pointcut("execution(* com.itheima.service..*.*(..))")
	public void pointcut() {
	}

	@Before("pointcut()")
	public void before() {
		System.out.println("before");
	}

	@After("pointcut()")
	public void after() {
		System.out.println("after");
	}

	@Around("pointcut()")
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
		System.out.println("around advice start");
		try {
			Object result = proceedingJoinPoint.proceed();
			System.out.println("around advice end");
			return result;
		} catch (Throwable throwable) {
			throwable.printStackTrace();
			return null;
		}
	}
}
后置方法生成代理对象-postProcessAfterInitialization方法

还是回到AbstractAutoProxyCreator 对象,直接找到postProcessAfterInitialization 方法。

跟进代码。

首先是生成用于缓存的key,然后调用wrapIfNecessary 方法。

//2.如果beanName为空,使用Class对象作为缓存的key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 1.判断当前bean是否需要被代理,如果需要则进行封装
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
  return wrapIfNecessary(bean, beanName, cacheKey);
}

继续跟进。

这里先获取到上面解析缓存的所有Advisors,我们可以先看下getAdvicesAndAdvisorsForBean 方法。

// 4.获取当前bean的Advices和Advisors===》
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

跟进后直接就是重点调用findCandidateAdvisors 方法,这里我们就不看了,直接再次跟进。

这部分可以看到,第一步是获取所有的Advisor 对象,然后筛选出拦截本对象的Advisor。注意我们目前还是在Bean 对象的初始化过程中,那么这里指的对象就是正在初始化的对象。最后再将符合条件的Advisor 对象排个序即可,这里的排序是为了将环绕、前置、后置方法进行排序,方便后面代理对象执行时的调用顺序。

至于findCandidateAdvisors 方法,这里跟上面的调用时一模一样的,唯一的不同就是这次不用再次解析了,而是直接返回上面的缓存集合即可。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
  // 1.查找所有的候选Advisor
  //将所有拥有@Aspect注解的类转换为advisors(aspectJAdvisorsBuilder.buildAspectJAdvisors)
  // 2.从所有候选的Advisor中找出符合条件的
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  // 3.扩展方法,留个子类实现
  extendAdvisors(eligibleAdvisors);
  if (!eligibleAdvisors.isEmpty()) {
    // 4.对符合条件的Advisor进行排序
    eligibleAdvisors = sortAdvisors(eligibleAdvisors);
  }
  return eligibleAdvisors;
}

回到wrapIfNecessary 方法。

继续就是创建代理的部分了,最后会将创建好的代理存入缓存,并且返回结果,这个方法后面我们就不再回来了,要看好。

// 5.如果存在增强器则创建代理
if (specificInterceptors != DO_NOT_PROXY) {
  this.advisedBeans.put(cacheKey, Boolean.TRUE);
  // 5.1 创建代理对象:这边SingletonTargetSource的target属性存放的就是我们原来的bean实例(也就是被代理对象),
  // 用于最后增加逻辑执行完毕后,通过反射执行我们真正的方法时使用(method.invoke(bean, args))
  Object proxy = createProxy(
    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
  // 5.2 创建完代理后,将cacheKey -> 代理类的class放到缓存
  this.proxyTypes.put(cacheKey, proxy.getClass());
  // 返回代理对象
  return proxy;
}

继续跟进createProxy 方法。

这里的代码多,可以都不用管,就看后面的最后一句。

proxyFactory.getProxy(getProxyClassLoader());

跟进代码。

不要急着看getProxy,先看createAopProxy 方法。

public Object getProxy(@Nullable ClassLoader classLoader) {
  // 1.createAopProxy:创建AopProxy
  // 2.getProxy(classLoader):获取代理对象实例
  return createAopProxy().getProxy(classLoader);
}

这里的调用比较简单,我就不一步步跟了,直接看结果。

这里会判断出使用的到底是Cglib 代理还是JDK 代理。直接说结论就是默认使用JDK 代理,配置了使用Cglib 代理。JDK 代理对应对象:JdkDynamicAopProxy,Cglib 代理对应对象:ObjenesisCglibAopProxy。这里一定要记住这个两个代理对应的对象,后面我们走流程的时候,看代理直接看对应对象的invoke 方法。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  // 1.判断使用JDK动态代理还是Cglib代理
  // optimize:用于控制通过cglib创建的代理是否使用激进的优化策略。除非完全了解AOP如何处理代理优化,否则不推荐使用这个配置,目前这个属性仅用于cglib代理,对jdk动态代理无效
  // proxyTargetClass:默认为false,设置为true时,强制使用cglib代理,设置方式:<aop:aspectj-autoproxy proxy-target-class="true" />
  // hasNoUserSuppliedProxyInterfaces:config是否存在代理接口或者只有SpringProxy一个接口
  if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    // 拿到要被代理的对象的类型
    Class<?> targetClass = config.getTargetClass();
    if (targetClass == null) {
      // TargetSource无法确定目标类:代理创建需要接口或目标。
      throw new AopConfigException("TargetSource cannot determine target class: " +
                                   "Either an interface or a target is required for proxy creation.");
    }
    // 要被代理的对象是接口 || targetClass是Proxy class
    // 当且仅当使用getProxyClass方法或newProxyInstance方法动态生成指定的类作为代理类时,才返回true。
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
      // JDK动态代理,这边的入参config(AdvisedSupport)实际上是ProxyFactory对象
      // 具体为:AbstractAutoProxyCreator中的proxyFactory.getProxy发起的调用,在ProxyCreatorSupport使用了this作为参数,
      // 调用了的本方法,这边的this就是发起调用的proxyFactory对象,而proxyFactory对象中包含了要执行的的拦截器
      return new JdkDynamicAopProxy(config);
    }
    // Cglib代理
    return new ObjenesisCglibAopProxy(config);
  }
  else {
    // JDK动态代理
    return new JdkDynamicAopProxy(config);
  }
}

再回看getProxy 方法就简单了,因为我们没有配置,所以这里就是jdk 代理的使用了。

就是通过classLoader、接口、InvocationHandler实现类,来获取到代理对象。

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
  if (logger.isTraceEnabled()) {
    logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
  }
  // 1.拿到要被代理对象的所有接口
  Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
  findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
  // 2.通过classLoader、接口、InvocationHandler实现类,来获取到代理对象
  return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
小结

这样生成代理的内容就结束了,其主要就是看到底是用cglib 还是jdk 来做代理,还有就是advisors 的解析。

总结

本次就是了解aop 的一些概念,当然网上找的肯定比我全,因为我也是找的,这个不是重点,主要还是aop 的代理创建和执行时机的描述,后面我们再分析请求的完整流程。

附录Spring 源码分析系列文章

IOC

时间文章
2022-03-09Spring的基本概念和IOC流程的简述
2022-03-11IOC流程解析-BeanFactory的创建
2022-03-14IOC流程解析-BeanFactoyPostProcessor和BeanPostProcessor
2022-03-15IOC流程解析-实例化和初始化
2022-03-17IOC流程解析-循环依赖

AOP

时间文章
2022-03-19Spring的基本概念和IOC流程的简述
2022-03-20AOP流程源码分析-请求调用全流程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值