likes
comments
collection
share

spring aop(一):aop 示例 demo 及 @EnableAspectJAutoProxy

作者站长头像
站长
· 阅读数 44

注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:gitee.com/funcy/sprin….

前面的文章分析了spring的启动流程,从本文开始,我们来分析spring aop功能。

1. 示例demo

1.1 准备demo

aop的示例demo依旧位于spring-learn模块,包名是org.springframework.learn.demo02,目录结构如下:

spring aop(一):aop 示例 demo 及 @EnableAspectJAutoProxy

  1. 准备 annotation 首先,我们准备一个annotation用来标注切面:
package org.springframework.learn.demo02;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AopAnnotation {
}
  1. 编写切面方法
package org.springframework.learn.demo02;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * {这里添加描述}
 *
 * @author chengyan
 * @date 2020-07-19 6:00 下午
 */
@Aspect
@Component
public class AopAspectj {

    @Pointcut("@annotation(org.springframework.learn.demo02.AopAnnotation)")
    public void testAop(){

    }

    @Around("testAop()")
    public Object around(ProceedingJoinPoint p){
        System.out.println("before execute...");
        Object o = null;
        try {
            o = p.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("after execute...");
        return o;
    }
}

这个类表示,标注了 @AopAnnotation 的方法,将运行其运行前后运行一些代码,around(ProceedingJoinPoint)指定了运行前后的代码。

  1. 启用proxy
package org.springframework.learn.demo02;

import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@EnableAspectJAutoProxy
@Component
public class AopAnnotationConfig {

}

这个类关键在于上面的两个注解:@EnableAspectJAutoProxy@Component,前者表示启用代理,后者表示该类为spring bean.

  1. 准备三个类
  • 继承了接口且标有注解AopAnnotation的类AopBean1
  • 未继承接口但标有注解AopAnnotation的类AopBean2
  • 未继承接口也未标有注解AopAnnotation的类AopBean3

这几个类的内容分别如下:

IAopBean.java

package org.springframework.learn.demo02;

public interface IAopBean {

    void hello();

}

AopBean1.java

package org.springframework.learn.demo02;

import org.springframework.stereotype.Component;

@Component
public class AopBean1 implements IAopBean {

    @Override
    @AopAnnotation
    public void hello() {
        System.out.println("实现了接口IAopService的hello()");
    }
}

AopBean2.java

package org.springframework.learn.demo02;

import org.springframework.stereotype.Component;

@Component
public class AopBean2 {

    @AopAnnotation
    public void hello() {
        System.out.println("未实现接口IAopService的hello()");
    }
}

AopBean3.java

package org.springframework.learn.demo02;

import org.springframework.stereotype.Component;

@Component
public class AopBean3 {

    public void hello() {
        System.out.println("未实现接口IAopService、也未进行aop的hello()");
    }
}

  1. 启动类
package org.springframework.learn.demo02;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02Main {

    public static void main(String[] args) {
         ApplicationContext context = new AnnotationConfigApplicationContext(
              "org.springframework.learn.demo02");
         IAopBean obj1 = (IAopBean)context.getBean("aopBean1");
         AopBean2 obj2 = (AopBean2)context.getBean("aopBean2");
         AopBean3 obj3 = (AopBean3)context.getBean("aopBean3");

         System.out.println("obj1:" + obj1.getClass());
         obj1.hello();

         System.out.println("-----------------------");
         System.out.println("obj2:" + obj2.getClass());
         obj2.hello();

         System.out.println("-----------------------");
         System.out.println("obj3:" + obj3.getClass());
         obj3.hello();

    }
}

1.2 运行结果及说明

运行main(String[])方法,结果如下:

obj1:class com.sun.proxy.$Proxy19
before execute...
实现了接口IAopService的hello()
after execute...
-----------------------
obj2:class org.springframework.learn.demo02.AopBean2$$EnhancerBySpringCGLIB$$e068ccef
before execute...
未实现接口IAopService的hello()
after execute...
-----------------------
obj3:class org.springframework.learn.demo02.AopBean3
未实现接口IAopService、也未进行aop的hello()

首先,可以看到,AopBean1AopBean2在执行hello()方法前后均输出了内容,表明已正确实现了aop功能;AopBean3方法执行前后未实现任何内容,因为我们并未在AopBean3hello()方法上添加@AopAnnotation

接着再来看的class:AopBean1的class是com.sun.proxy.$Proxy19,这是jdk提供的动态代理类;AopBean2的class是org.springframework.learn.demo02.AopBean2$$EnhancerBySpringCGLIB$$e068ccef,从名称上EnhancerBySpringCGLIB可以看出,这个类通过spring cglib类增强的类;AopBean3的class是org.springframework.learn.demo02.AopBean3,因为我们并未在AopBean3hello()方法上添加@AopAnnotation

对以上现象,总结如下:

  1. spring 开启了 aop 后,只会对需要代理的类创建代理对象。在上述代码中,通过@EnableAspectJAutoProxy开启了aop功能,但并不是所有的类都创建了代理对象,只有AopBean1AopBean2创建了代理对象,AopBean3还是原来的对象,可见代理对象也是“按需”创建的;

  2. 如果类实现了接口,那么spring在创建代理对象时,会采用jdk提供的动态代理功能;如果类未实现接口,spring会使用cglib来创建代理对象。

1.3 强制使用 cglib 创建代理对象

实际上,是否需要采用jdk提供的动态代理功能来创建代理对象,spring也提供了一个配置,该配置位于@EnableAspectJAutoProxy中:

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

    /**
     * proxyTargetClass属性,默认false,尝试采用JDK动态代理织入增强(如果当前类没有实现接口则还是会使用CGLIB);
     * 如果设为true,则强制采用CGLIB动态代理织入增强.
     */
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;

}

如果我们需要强制使用cglib创建代理对象,可以这样设置:

package org.springframework.learn.demo02;

import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
 * {这里添加描述}
 *
 * @author chengyan
 * @date 2020-07-19 5:59 下午
 */
// 强制使用cglib创建动态代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Component
public class AopAnnotationConfig {

}

运行结果如下:

obj1:class org.springframework.learn.demo02.AopBean1$$EnhancerBySpringCGLIB$$cccd444c
before execute...
实现了接口IAopService的hello()
after execute...
-----------------------
obj2:class org.springframework.learn.demo02.AopBean2$$EnhancerBySpringCGLIB$$e068ccef
before execute...
未实现接口IAopService的hello()
after execute...
-----------------------
obj3:class org.springframework.learn.demo02.AopBean3
未实现接口IAopService、也未进行aop的hello()

可以看到,AopBean1也使用cglib来创建代理对象了。

2. @EnableAspectJAutoProxy

在spring中,@EnableXxx 表示启用某项功能,如@EnableAsync表示启用异步功能,@EnableCaching表示启用缓存功能,这里的@EnableAspectJAutoProxy表示启用 aspectJ的自动代理。熟悉@EnableXxx注解功能的伙伴们应该知道,EnableXxx类其上的@Import注解才是启用该功能的关键,如@EnableAspectJAutoProxy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// Import 进来的类才是关键
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    ...
}

这里@Import进来的类是 AspectJAutoProxyRegistrar.class,我们可以看看这个类做了什么。

如果不清楚 @EnableXxx 注解的功能,可以查看 spring探秘之spring是如何实现 @EnableXxx 功能的?

2.1 AspectJAutoProxyRegistrar 功能

AspectJAutoProxyRegistrar内容如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {    
        //注册一个AOP代理实现的Bean
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);    
        AnnotationAttributes enableAspectJAutoProxy =
           AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,在ImportBeanDefinitionRegistrar提供的registerBeanDefinitions中,调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)往spring容器中注册了自动代理实现类。

我们跟进AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法,看看这个类做了什么。一中跟下去,最终执行的方法为AopConfigUtils#registerOrEscalateApcAsRequired

// 自动代理创建类的名称
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
        "org.springframework.aop.config.internalAutoProxyCreator";

// 这里的 cls 是 AnnotationAwareAspectJAutoProxyCreator.class
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
        Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    //如果已存在这个bean
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        //判断优先级,如果优先级较高则替换原先的bean
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    //注册AnnotationAwareAspectJAutoProxyCreator到容器中,此类负责基于注解的AOP动态代理实现
    // 这里的cls是AnnotationAwareAspectJAutoProxyCreator
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

这个方法主要做的事就是往spring容器中注册AnnotationAwareAspectJAutoProxyCreatorbeanDefinition,后续spring在处理bean时,就能实例化该bean了。

2.2 AspectJAutoProxyRegistrar 执行时机

AspectJAutoProxyRegistrar中只有一个方法:registerBeanDefinitions(...),讨论AspectJAutoProxyRegistrar的执行时机,其实就是讨论org.springframework.context.annotation.AspectJAutoProxyRegistrar#registerBeanDefinitions的执行时机。

我们这里通过打断点调度的方式来探索:

spring aop(一):aop 示例 demo 及 @EnableAspectJAutoProxy

调用栈如下:

|-AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)
 |-AbstractApplicationContext#refresh
  // 注意这一行代码,这里是ApplicationContext的refresh里的流程了
  |-AbstractApplicationContext#invokeBeanFactoryPostProcessors
   |-PostProcessorRegistrationDelegate
      #invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)
    |-PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
     |-ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
      |-ConfigurationClassPostProcessor#processConfigBeanDefinitions
       |-ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
        |-ConfigurationClassBeanDefinitionReader
          #loadBeanDefinitionsForConfigurationClass
         |-ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
          |-ImportBeanDefinitionRegistrar#registerBeanDefinitions(
            AnnotationMetadata, BeanDefinitionRegistry, BeanNameGenerator)
           |-AspectJAutoProxyRegistrar#registerBeanDefinitions(
             AnnotationMetadata, BeanDefinitionRegistry)

可以看到,AspectJAutoProxyRegistrar 执行是在AbstractApplicationContext#refreshinvokeBeanFactoryPostProcessors中执行的。

3. 总结

本文提供了一个aop的示例,同时分析了@EnableAspectJAutoProxy 注解的作用,分析就先到这里了。


本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录