spring5 源码深度解析----- AOP的使用及AOP自定义标签

摘要:
接下来我们就详细分析下spring中的AOP。AOP的使用在开始前,先引入Aspect。那么,Spring是如何实现AOP的呢?首先我们知道,SPring是否支持注解的AOP是由一个配置文件控制的,也就是,当在配置文件中声明了这句配置的时候,Spring就会支持注解的AOP,那么我们的分析就从这句注解开始。AOP自定义标签之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。

我们知道在面向对象OOP编程存在一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志,安全检测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对象编程的补充,面向切面编程(AOP),AOP所关注的方向是横向的,不同于OOP的纵向。接下来我们就详细分析下spring中的AOP。首先我们从动态AOP的使用开始。

AOP的使用

在开始前,先引入Aspect。

<!-- aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>

创建用于拦截的bean

public classTestBean {
    private String message = "test bean";
    publicString getMessage() {
        returnmessage;
    }
    public voidsetMessage(String message) {
        this.message =message;
    }
    public voidtest(){
        System.out.println(this.message);
    }
}

创建Advisor

Spring中摒弃了最原始的繁杂配置方式而采用@AspectJ注解对POJO进行标注,使AOP的工作大大简化,例如,在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台beforeTest。而在所有类的test方法执行后打印afterTest,同时又使用环绕的方式在所有类的方法执行前后在此分别打印before1和after1,以下是AspectJTest的代码:

@Aspect
public classAspectJTest {
    @Pointcut("execution(* *.test(..))")
    public voidtest(){
    }
    @Before("test()")
    public voidbeforeTest(){
        System.out.println("beforeTest");
    }
    @Around("test()")
    publicObject aroundTest(ProceedingJoinPoint p){
        System.out.println("around.....before");
        Object o = null;
        try{
            o =p.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("around.....after");
        returno;
    }
    @After("test()")
    public voidafterTest()
    {
        System.out.println("afterTest");
    }
 }

创建配置文件

要在Spring中开启AOP功能,,还需要在配置文件中作如下声明:

<?xml version="1.0" encoding="UTF-8" ?>
<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">
    <aop:aspectj-autoproxy/>
    <bean   class="com.yhl.myspring.demo.aop.TestBean">
       <property name="message" value="这是一个苦逼的程序员"/>
    </bean>
    <bean   class="com.yhl.myspring.demo.aop.AspectJTest"/>
</beans>

测试

public classTest {
    public static voidmain(String[] args) {
        ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean bean = (TestBean)bf.getBean("test");
        bean.test();
    }
}

执行后输出如下:

spring5 源码深度解析----- AOP的使用及AOP自定义标签第1张

Spring实现了对所有类的test方法进行增强,使辅助功能可以独立于核心业务之外,方便与程序的扩展和解耦。
那么,Spring是如何实现AOP的呢?首先我们知道,SPring是否支持注解的AOP是由一个配置文件控制的,也就是<aop:aspectj-autoproxy/>,当在配置文件中声明了这句配置的时候,Spring就会支持注解的AOP,那么我们的分析就从这句注解开始。

AOP自定义标签

之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。我们搜索 aspectj-autoproxy 这个代码,尝试找到注册的地方,全局搜索后我们发现了在org.springframework.aop.config包下的AopNamespaceHandler中对应着这样一段函数:

@Override
public voidinit() {
    //In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", newConfigBeanDefinitionParser());
   registerBeanDefinitionParser("aspectj-autoproxy", newAspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", newScopedProxyBeanDefinitionDecorator());
    //Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", newSpringConfiguredBeanDefinitionParser());
}

这里我们就不再对spring中的自定义注解方式进行讨论了。从这段代码中我们可以得知,在解析配置文件的时候,一旦遇到了aspectj-autoproxy注解的时候会使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,接下来我们就详细分析下其内部实现。

注册AnnotationAwareAspectJAutoProxyCreator

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

@Override
@Nullable
publicBeanDefinition parse(Element element, ParserContext parserContext) {
    //注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    //对于注解中子类的处理
extendBeanDefinition(element, parserContext);
    return null;
}

通过代码可以了解到函数的具体逻辑是在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中实现的,继续进入到函数体内:

/**
 * 注册AnnotationAwareAspectJAutoProxyCreator
 * @paramparserContext
 * @paramsourceElement
 */
public static voidregisterAspectJAnnotationAutoProxyCreatorIfNecessary(
        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);
}

在registerAspectJAnnotationAutoProxyCreatorIfNeccessary方法中主要完成了3件事情,基本上每行代码都是一个完整的逻辑。接下来我们详细分析每一行代码。

注册或升级AnnotationAwareAspectJAutoProxyCreator

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

public staticBeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
        @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
public static final String AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";
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 = newRootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    returnbeanDefinition;
}

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

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

useClassProxyingIfNecessary实现了proxy-target-class属性以及expose-proxy属性的处理,进入到方法内部:

private static voiduseClassProxyingIfNecessary(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);
        }
    }
}

在上述代码中用到了两个强制使用的方法,强制使用的过程其实也是一个属性设置的过程,两个函数的方法如下:

public static voidforceAutoProxyCreatorToUseClassProxying(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 voidforceAutoProxyCreatorToExposeProxy(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-class:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理),如果被代理的目标对象实现了至少一个接口, 則会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。如果你希望强制使用CGLIB代理,(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下两个问题。
  1. 无法通知(advise) Final方法,因为它们不能被覆写。
  2. 你需要将CGLIB二进制发行包放在classpath下面。

与之相较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop:config>的 proxy-target-class 厲性设为 true:

<aop:config proxy-target-class = "true">...</aop:config>

当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照以下方式设罝<aop:aspectj- autoproxy>的 proxy-target-class 属性:

<aop:aspectj-autoproxy proxy-target-class = "true"/>
  • JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
  • CGIJB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM (开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强。
  • expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下示例:
public interfaceAService { 
    public voida(); 
    public voidb();
}
@Service()
public class AServicelmpll implementsAService {
    @Transactional(propagation =Propagation.REQUIRED) 
    public voida() { 
        this.b{);
    }
    @Transactional(propagation =Propagation.REQUIRES_NEW) 
    public voidb() {
    }
}

此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强, 因此 b 方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)” 将不会实施,为了解决这个问题,我们可以这样做:

<aop:aspectj-autoproxy expose-proxy = "true"/>

然后将以上代码中的 “this.b();” 修改为 “((AService) AopContext.currentProxy()).b();” 即可。 通过以上的修改便可以完成对a和b方法的同时增强。

免责声明:文章转载自《spring5 源码深度解析----- AOP的使用及AOP自定义标签》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇毫米雷达波概述docsify制作在线说明文档的轻量级神器下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SpringAOP

什么是spring Aop AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也...

linux下关闭桌面模式使用命令行模式及其它模式

vim/etc/inittab 把其中的id:5:initdefault中的5改为3。 Linux操作系统有六种不同的运行级(run level),在不同的运行级下,系统有着不同的状态,这六种运行级分别为:0:停机(记住不要把initdefault 设置为0,因为这样会使Linux无法启动)1:单用户模式,就像Win9X下的安全模式。2:多用户,但是没有...

SpringMVC2

1       SpringMVC架构 1.1     Spring web mvc介绍 Spring web mvc和Struts2都属于表现层的框架,它是Spring框架的一部分,我们可以从Spring的整体结构中看得出来: 1.2     Web MVC mvc设计模式在b/s系统下应用: 1、  用户发起request请求至控制器(Contro...

iptables使用总结

参考: 1、朱双印博客:https://www.zsythink.net/archives/category/%e8%bf%90%e7%bb%b4%e7%9b%b8%e5%85%b3/iptables 2、https://blog.51cto.com/u_10630401/2089708 本篇文章内容基本来自于朱双印博客,这里只是把一些内容归纳在此,方便查...

spring的后置处理器——BeanPostProcessor以及spring的生命周期

后置处理器的调用时机 BeanPostProcessor是spring提供的接口,它有两个方法——postProcessBeforeInitialization、postProcessAfterInitialization。关于这两个方法的调用时机,可以参考spring源码注释。 /** * Apply this BeanPostProc...

Django——Session源码分析

首先我们导入django.contrib.sessions.middleware这个中间件,查看里面的Session源码 from django.contrib.sessions.middleware import SessionMiddleware 我们可以看到一个类,可以把他分为3部分: class SessionMiddleware(Middlewa...