Spring源码之AOP的使用

Spring往期精彩文章

前言

我们都知道Java是一门面向对象(OOP)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给多个不具有继承关系的对象引入同一个公共的行为的时候,例如日志,安全检测等等,我们只能在每个对象中去引入这个公共行为,这样就产生了大量的重复代码,并且耦合度也会很高,不利于维护。正因如此就产生了面向切面(AOP)编程。可以说有了AOP使得面向对象更加完善,是对其的一个补充,AOP所关注的方式是横向的,不同于OOP的纵向,接下来我们详细讲解一下spring中的AOP。

AOP的使用

我们先从动态AOP开始

  • 首先引入 Aspect

    org.aspectj
    aspectjweaver
    1.9.7

  • 创建用于拦截的测试Bean
package com.vipbbo.selfdemo.spring.aop.test;

public class TestBean {
    private String message = "Test Message";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.message);
    }
}
  • 创建 Advisor

spring中一改以往摒弃了它最原始的繁杂的配置方式,目前采用 @AspectJ注解的方式对POJO进行标注,使得AOP的工作大大简化。例如在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台输出 beforeTest,在所有类的test方法执行后打印 afterTest,同时又使用环绕通知的方式在所有类的方法执行前后在此分别打印 around……beforearound……after

AspectJTest代码

@Aspect
public class AspectJTest {
    @Pointcut("execution(* *.test(..))")
    public void test(){

    }

    @Before("test()")
    public void beforeTest(){
        System.out.println("beforeTest");
    }

    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint joinPoint){
        System.out.println("around.........before");
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("around.........after");
        return proceed;
    }

    @After("test()")
    public void afterTest(){
        System.out.println("afterTest");
    }
}
  • 创建配置文件

在编写配置文件中要注意图中的声明、命名空间:

Spring源码之AOP的使用
  • 测试类
public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-aop.xml");
        TestBean testBean = (TestBean) ac.getBean("testBean");
        testBean.test();
    }
}

运行结果如下:

Spring源码之AOP的使用

通过上述代码可以看出,Spring实现了对所有类的test方法进行了增强,使得辅助功能(日志等)可以独立出来,也做到了解耦和对程序的扩展。 那么Spring是如何实现AOP的呢?实现我们知道,Spring是由一个配置文件控制是否支持注解的AOP,也就是 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>,当配置文件有了这句声明的时候,Spring就会支持注解的AOP,那么分析从这里开始。

AOP自定义注解源码解读

我们知道Spring中的 自定义注解,如果声明了自定义注解,那么在Spring中的一个地方一定注册了对应的解析器,我们从 aspectj-autoProxy入手:

&#x5728;Spring&#x6E90;&#x7801;&#x4E2D;&#x5168;&#x5C40;&#x641C;&#x7D22;&#xFF0C;&#x6211;&#x4EEC;&#x53D1;&#x73B0;&#x4E86;&#x5728;&#x5305;org.springframework.aop.config&#x4E0B;&#x7684;AopNamespaceHandler,&#x7136;&#x540E;&#x6211;&#x4EEC;&#x6253;&#x5F00;&#x8FD9;&#x4E2A;&#x7C7B;

Spring源码之AOP的使用

AopNamespaceHandler类中我们发现了这个 init函数

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    /**
     * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
     * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
     * and '{@code scoped-proxy}' tags.
     */
    @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());
    }

}

从上述代码可以看出,在解析配置文件的时候,一旦遇到 aspectj-autoproxy就会使用 AspectJAutoProxyBeanDefinitionParser解析器进行解析,接下来我们该函数的具体实现:

注册AnnotationAwareAspectJAutoProxyCreator

所有的解析器都是对接口 BeanDefinitionParser的实现,入口都是从 parse函数开始的, AnnotationAwareAspectJAutoProxyCreatorparse函数如下:

  • 看源码(具体实现在 AspectJAutoProxyBeanDefinitionParser.class)
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 注册AnnotationAwareAspectJAutoProxyCreator
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        // 对于注解中的子类进行处理
        extendBeanDefinition(element, parserContext);
        return null;
}

从上述代码我们又看出具体实现逻辑是在 registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中实现的,继续进入到函数方法体内:

  • 看源码(具体实现在 AopNamespaceUtils.class)
/**
     * 注册AnnotationAwareAspectJAutoProxyCreator
     * @param parserContext
     * @param sourceElement
     */
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        ParserContext parserContext, Element sourceElement) {

        // 注册或升级 AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        // 对于proxy-target-class以及expose-proxy属性的处理
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        // 注册组件并通知,便于监听器作进一步处理
      registerComponentIfNecessary(beanDefinition, parserContext);
}

看上述源码可知在函数 registerAspectJAnnotationAutoProxyCreatorIfNecessary中主要做了三件事,基本是每行代码做了一件。接下来我们一一解析:

函数体内的registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法

注册或升级AnnotationAwareAspectJAutoProxyCreator

对于AOP的实现基本都是靠AnnotationAwareAspectJAutoProxyCreator来完成的,它可以根据 @Pointcut注解定义的节点来自动代理相匹配的bean,但是为了配置简单,Spring使用了自动配置来帮我们自动注册AnnotationAwareAspectJAutoProxyCreator,其过程就是在这里实现的。我们继续跟进方法内部:

  • 看源码(具体实现在 AopConfigUtils.class)
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

在上面代码中我们看到了函数 registerOrEscalateApcAsRequired继续跟进:

  • 看源码(具体实现在 AopConfigUtils.class)
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
        Class cls, BeanDefinitionRegistry registry, @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        // 如果已经存在了自动代理创建器 且存在的自动代理创建器与现在的不一致,那么需要根据优先级判断到底需要使用哪一个
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    // 改变bean最重要的就是改变bean所对应的className属性
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }

        // 注册BeanDefinition,Class为AnnotationAwareAspectJAutoProxyCreator.class,beanName为internalAutoProxyCreator
        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;
}

同时我们也要看一下 AopConfigUtils类中的这部分代码:

    /**
     * The bean name of the internally managed auto-proxy creator.
     */
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
            "org.springframework.aop.config.internalAutoProxyCreator";

以上代码实现了自动注册 AnnotationAwareAspectJAutoProxyCreator类的功能, 同时这里还设计到一个优先级的问题,假设如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底使用哪一个

处理proxy-target-class以及expose-proxy属性

useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);这部分做了对proxy-target-class以及expose-proxy属性的处理。

  • 看源码(具体实现在 AopNamespaceUtils&#x3002;class)
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
        if (sourceElement != null) {
            // 实现了对 proxy-target-class的处理
            boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
            if (proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            // 对expose-proxy的处理
            boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
            if (exposeProxy) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
}
&#x5728;&#x4E0A;&#x8FF0;&#x4EE3;&#x7801;&#x4E2D;&#x4F7F;&#x7528;&#x5230;&#x4E86;**&#x4E24;&#x4E2A;&#x5F3A;&#x5236;&#x4F7F;&#x7528;&#x7684;&#x65B9;&#x6CD5;**&#x5206;&#x522B;&#x662F;forceAutoProxyCreatorToUseClassProxying&#x548C;forceAutoProxyCreatorToExposeProxy&#xFF0C;&#x5F3A;&#x5236;&#x4F7F;&#x7528;&#x7684;&#x8FC7;&#x7A0B;&#x5176;&#x5B9E;&#x4E5F;&#x662F;&#x4E00;&#x4E2A;&#x5C5E;&#x6027;&#x8BBE;&#x7F6E;&#x7684;&#x8FC7;&#x7A0B;&#xFF0C;&#x4E24;&#x4E2A;&#x51FD;&#x6570;&#x7684;&#x5177;&#x4F53;&#x5B9E;&#x73B0;&#x5982;&#x4E0B;&#xFF08;&#x5177;&#x4F53;&#x5B9E;&#x73B0;&#x5728;AopConfigUtils.class&#xFF09;&#xFF1A;
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
        }
    }

    public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
        }
}

接下来让我们说一下 proxy-target-classexpose-proxy这两个属性

  • proxy-target-proxy :Spring AOP部分使用的 JDK&#x52A8;&#x6001;&#x4EE3;&#x7406;或者是 CGLIB代理来为目标对象创建代理。(这里建议尽量使用JDK动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有目标类型实现的接口都将被代理;倘若目标对象没有实现任何接口,则会创建一个CGLIB代理。 另外如果你想强制使用CGLIB代理的话,(例如希望代理目标对象的所有方法,而不只是实现子接口的方法)那也是可以的, 但是需要考虑两个问题
  • 无法通知(advise)Final方法,因为它们不能被重写
  • 你需要将CGLIB二进制发行包放在classpath下面 与之相比较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将 <aop-config></aop-config>中的proxy-target-class属性设置为true。

当你使用 CGLIB&#x4EE3;&#x7406;@AspectJ自动代理支持,可以按照以下方式设置


  • expose-proxy: *有时候目标对象内部的自我调用将无法实施切面中的增强,如下:
public interface AService {
    public void a();
    public void b();
}

@Service()
public class AServicelmpll implements AService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void a() {
        this.b{);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
    }
}
&#x6B64;&#x5904;&#x7684;this&#x6307;&#x5411;&#x76EE;&#x6807;&#x5BF9;&#x8C61;&#xFF0C;&#x56E0;&#x6B64;&#x8C03;&#x7528;this.b&#x5C06;&#x4E0D;&#x4F1A;&#x6267;&#x884C;b&#x7684;&#x4E8B;&#x52A1;&#x5207;&#x9762;&#xFF0C;&#x5373;&#x4E0D;&#x4F1A;&#x6267;&#x884C;&#x4E8B;&#x52A1;&#x589E;&#x5F3A;&#xFF0C;&#x56E0;&#x6B64;b&#x65B9;&#x6CD5;&#x7684;&#x4E8B;&#x52A1;&#x5B9A;&#x4E49; @Transactional(propagation = Propagation.REQUIRES_NEW) &#x5C06;&#x4E0D;&#x4F1A;&#x5B9E;&#x65BD;&#xFF0C;&#x4E3A;&#x4E86;&#x89E3;&#x51B3;&#x8FD9;&#x4E2A;&#x95EE;&#x9898;&#xFF0C;&#x6211;&#x4EEC;&#x53EF;&#x4EE5;&#x8FD9;&#x6837;&#x505A;&#xFF1A;

&#x7136;&#x540E;&#x5C06;&#x4EE5;&#x4E0A;&#x4EE3;&#x7801;&#x4E2D;&#x7684;this.b()&#x4FEE;&#x6539;&#x4E3A;((AService)AopContext.currentProxy()).b()&#x5373;&#x53EF;

通过以上的修改便可完成对 ab 方法的同时增强

简单说一下JDK动态代理的CGLIB代理

  • JDK动态代理:其对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的创建。
  • CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠 ASM (开源的Java字节码编辑类库)操作字节码类实现的,性能要比JDK强

微信搜索【 码上遇见你】获取更多精彩文章,以及学习资料

Original: https://www.cnblogs.com/java-wang/p/15362421.html
Author: 码上遇见你
Title: Spring源码之AOP的使用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580136/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球