Spring AOP 和 Spring 1.2 中AOP的配置

目录

一、AOP, AspectJ, Spring AOP 前世今生

1、AOP

2、AspectJ

3、Spring AOP

二、Spring 1.2 中的配置

1、FactoryBean方式,创建动态代理

2、Advisor,实现更细粒度的方法增强

3、autoproxy 自动代理


一、AOP, AspectJ, Spring AOP 前世今生

1、AOP

AOP 是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。

AOP 的实现需要实现 一个代理,实际运行的实例其实是生成的代理类的实例。

AOP 基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。

Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖。

Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理。 Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。

Spring 提供了 AspectJ 的支持,但只用到的AspectJ的切点解析和匹配。 很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好。

2、AspectJ

AspectJ来自于 Eclipse 基金会,属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:

  • Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
  • Post-compile weaving:编译后织入,也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
  • Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法:(1)自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。(2)在 JVM 启动的时候指定 AspectJ 提供的 agent:-java agent:xxx/xxx/aspectjweaver.jar。

AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。

因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。

3、Spring AOP

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

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

  1. Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于接口的,看源码可以从这里起步。
  2. Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用命名空间 <aop ></aop>
  3. Spring 2.0 @AspectJ 配置:使用注解的方式来配置,另外,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。

二、Spring 1.2 中的配置

Spring 1.2 中的配置,是最古老的配置,但是由于 Spring 提供了很好的向后兼容,有时候不知道什么配置是什么版本,以及是否有更新,有更好的配置替代方法,所以还是会有很多代码采用这种古老的配置方式(比如声明式事务)。

下面用一个简单的例子来演示怎么使用 Spring 1.2 的配置方式。

首先需要定义被增强的类,接口:Calculate.java,实现类:SimpleCalculate.Java

// 目标接口
public interface Calculate {
    // 加法
    int add(int numA, int numB);
    // 减法
    int sub(int numA, int numB);
    // 乘法
    int multi(int numA, int numB);
    // 除法
    int div(int numA, int numB);
}

// 目标接口实现类
@Component
public class SimpleCalculate implements Calculate {
    public int add(int numA, int numB) {
        System.out.println("执行目标方法:add");
        return numA+numB;
    }
    public int sub(int numA, int numB) {
        System.out.println("执行目标方法:sub");
        return numA-numB;
    }
    public int multi(int numA, int numB) {
        System.out.println("执行目标方法:multi");
        return numA*numB;
    }
    public int div(int numA, int numB) {
        System.out.println("执行目标方法:div");
        return numA/numB;
    }
}

接下来,我们定义 advice 或 Interceptor,其中 advice(通知) 是一个非常重要的概念

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;

public class LogAdvice implements MethodBeforeAdvice { // 前置通知
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        String methodName = method.getName();
        System.out.println("执行目标方法【" + methodName + "】的<前置通知>,入参" + Arrays.asList(args));
    }
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogInterceptor implements MethodInterceptor { // 相当于环绕通知
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println(getClass() + " 调用方法前执行");
        Object ret = invocation.proceed(); // 调用目标方法
        System.out.println(getClass() + " 调用方法后执行");
        return ret;
    }
}

MethodBeforeAdvice 或者 MethodInterceptor 等接口有个共同的特征,就是它们都实现了Advice接口。

上面的两个 Advice 分别用于方法调用前输出参数和方法调用前后输出类名。

现在可以通过EarlyAopMainConfig.java配置类开始配置了

1、FactoryBean方式,创建动态代理

import com.swadian.aopdemo.Calculate;
import com.swadian.aopdemo.SimpleCalculate;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;

public class EarlyAopMainConfig {
    // 被代理对象
    @Bean
    public Calculate simpleCalculate() {
        return new SimpleCalculate();
    }
    // Advice方式
    @Bean
    public LogAdvice logAdvice(){
        return new LogAdvice();
    }
    // Interceptor方式,可以理解为环绕通知
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }
    /**
     * FactoryBean方式单个: ProxyFactoryBean
     *
     * 此方法有个致命的问题:
     * 只能指定单一的Bean的AOP,多个Bean需要创建多个ProxyFactoryBean
     * 而且,拦截器的粒度只控制到了类级别,对类中所有的方法都进行了拦截。
     * @return*/
    @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        // 传入"logAdvice"和"logInterceptor",根据指定的顺序执行
        proxyFactoryBean.setInterceptorNames("logAdvice", "logInterceptor");  
        proxyFactoryBean.setTarget(simpleCalculate());
        return proxyFactoryBean;
    }
}

接下来,测试下效果

public class MainClass {
    public static void main(String[] args) {
        /*ProxyFactoryBean*/
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("calculateProxy", Calculate.class);
        System.out.println(calculateProxy.getClass());
        calculateProxy.div(1, 1);
    }
}

输出结果

该方式使用ProxyFactoryBean去创建代理对象,ProxyFactoryBean实现了FactoryBean接口,我们知道FactoryBean作为一种特殊的Bean,它不会由Spring直接创建实例,而是会调用getObject()方法去获取。

如下,是 ProxyFactoryBean.java 中 getObject() 的源码

跟进去,其中创建代理的方法有两种,Cglib和JDK。在这里,因为我们的目标类是有接口的,所以创建代理使用的是JDK代理。

使用ProxyFactoryBean去创建代理对象,有个致命的问题。我们只能指定单一的Bean的AOP, 如果多个Bean就需要创建多个ProxyFactoryBean 。而且,拦截器的粒度只控制到了类级别,对类中所有的方法都进行了拦截。接下来,我们看看怎么样只拦截特定的方法。

2、Advisor,实现更细粒度的方法增强

在上面的配置中,配置拦截器的时候,interceptorNames 除了可以指定为 Advice,还可以指定为 Interceptor 和 Advisor 。 Advisor 内部需要指定一个 Advice,Advisor 决定该拦截哪些方法,拦截后需要完成的工作还是由内部的 Advice 来做。

Advisor 有好几个实现类,此处,我们使用实现类 NameMatchMethodPointcutAdvisor 来演示,从名字上就可以看出来,它需要提供方法名字,这样符合该配置的方法才能被拦截。

public class EarlyAopMainConfig {
    // 被代理对象
    @Bean
    public Calculate simpleCalculate() {
        return new SimpleCalculate();
    }
    // Advice方式
    @Bean
    public LogAdvice logAdvice() {
        return new LogAdvice();
    }
    // Interceptor方式,可以理解为环绕通知 -> Advice的实现
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }
    /**
     * Advisor 种类很多:
     * RegexpMethodPointcutAdvisor 按正则匹配类
     * NameMatchMethodPointcutAdvisor 按方法名匹配
     * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor   <aop:before />
     * InstantiationModelAwarePointcutAdvisorImpl  注解解析的advisor(@Before @After....)
     */
    @Bean
    public NameMatchMethodPointcutAdvisor logAspectAdvisor() {
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        // 通知(Advice):是我们的通知类,没有带切点
        // 通知者(Advisor):是经过包装后的细粒度控制方式,带了切点
        advisor.setAdvice(logInterceptor());
        advisor.setMappedNames("div"); // 精确到方法
        return advisor;
    }
    /**
     * FactoryBean方式,控制粒度到方法
     * 问题:只能指定单一的Bean的AOP,如果多个Bean需要创建多个ProxyFactoryBean 。
     */
    @Bean
    public ProxyFactoryBean calculateProxy() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setInterceptorNames("logAspectAdvisor");
        proxyFactoryBean.setTarget(simpleCalculate());
        return proxyFactoryBean;
    }
}
public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("calculateProxy", Calculate.class);
        System.out.println(calculateProxy.getClass());
        calculateProxy.div(1, 1);  // 除法
        calculateProxy.add(1,1);   // 加法
    }
}

介绍完了 Advice、Advisor、Interceptor 三个概念,发现它们有个共同的问题,那就是得为每个 bean 都配置一个代理,之后获取 bean 的时候需要获取这个代理类的 bean 实例(如 ctx.getBean("calculateProxy",Calculate.class)),这显然非常不方便,不利于之后要使用的自动根据类型注入。下面介绍 autoproxy 的解决方案。

3、autoproxy 自动代理

autoproxy:从名字我们也可以看出来,它是实现自动代理,也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。

这里强调自动,也就是说 Spring 会自动做这件事,而不用像前面介绍的,我们需要显式地指定代理类的 bean。 我们去掉原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置

import com.swadian.aopdemo.Calculate;
import com.swadian.aopdemo.SimpleCalculate;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.context.annotation.Bean;

public class EarlyAopMainConfig {
    // 被代理对象
    @Bean
    public Calculate simpleCalculate() {
        return new SimpleCalculate();
    }
    // Advice方式
    @Bean
    public LogAdvice logAdvice() {
        return new LogAdvice();
    }
    // Interceptor方式,可以理解为环绕通知
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }
    /**
     * Advisor 种类很多:
     * RegexpMethodPointcutAdvisor 按正则匹配类
     * NameMatchMethodPointcutAdvisor 按方法名匹配
     * DefaultBeanFactoryPointcutAdvisor xml解析的Advisor   <aop:before />
     * InstantiationModelAwarePointcutAdvisorImpl  注解解析的advisor(@Before @After....)
     */
    @Bean
    public NameMatchMethodPointcutAdvisor logAspectAdvisor() {
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        // 通知(Advice):是我们的通知类,没有带切点
        // 通知者(Advisor):是经过包装后的细粒度控制方式,带了切点
        advisor.setAdvice(logInterceptor());
        advisor.setMappedNames("div"); // 精确到方法
        return advisor;
    }
    /**
     * autoProxy: BeanPostProcessor手动指定Advice方式
     */
    @Bean
    public BeanNameAutoProxyCreator autoProxyCreator() {
        BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
        //设置要创建代理的那些Bean的名字,匹配以simple开头的Bean
        beanNameAutoProxyCreator.setBeanNames("simple*");
        //设置拦截链名字(这些拦截器是有先后顺序的)
        beanNameAutoProxyCreator.setInterceptorNames("logAspectAdvisor");
        return beanNameAutoProxyCreator;
    }
}

配置很简单,beanNames 中可以使用正则来匹配 bean 的名字来增强多个类。 也就是说不再是配置某个 bean 的代理了。

注意,这里的 InterceptorNames 和前面一样,也是可以配置成 Advisor 和 Interceptor 。

然后我们修改下使用的地方:

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        // 使用被代理类的类名,不再是生成的代理类名称
        Calculate calculateProxy = ctx.getBean("simpleCalculate", Calculate.class);
        System.out.println(calculateProxy.getClass());
        calculateProxy.div(1, 1); // 除法
        calculateProxy.add(1, 1); // 加法
    }
}

发现没有,我们使用的时候,完全不需要关心代理了,直接使用原来的类型就可以了,这是非常方便的。BeanNameAutoProxyCreator是Bean后置处理器的一种实现,它的继承关系如下

另外,在 BeanNameAutoProxyCreator 同一个包中,还有一个非常有用的类 DefaultAdvisorAutoProxyCreator, 比上面的 BeanNameAutoProxyCreator 还要方便。

之前我们说过,advisor 内部包装了 advice,advisor 负责决定拦截哪些方法,内部 advice 定义拦截后的逻辑。所以, 仔细想想其实就是只要让我们的 advisor 全局生效就能实现我们需要的自定义拦截功能、拦截后的逻辑处理

BeanNameAutoProxyCreator 是自己匹配方法,然后交由内部配置 advice 来拦截处理; 而 DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理。

回头看下 Advisor 的配置,上面我们用了 NameMatchMethodPointcutAdvisor 这个类:

    @Bean
    public NameMatchMethodPointcutAdvisor logAspectAdvisor() {
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setAdvice(logInterceptor());
        advisor.setMappedNames("div"); // 精确到方法
        return advisor;
    }
    @Bean
    public BeanNameAutoProxyCreator autoProxyCreator() {
        BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
        //设置要创建代理的那些Bean的名字,匹配以simple开头的Bean
        beanNameAutoProxyCreator.setBeanNames("simple*");
        //设置拦截链名字(这些拦截器是有先后顺序的) -> logAspectAdvisor
        beanNameAutoProxyCreator.setInterceptorNames("logAspectAdvisor");
        return beanNameAutoProxyCreator;
    }

其实 Advisor 还有一个更加灵活的实现类 RegexpMethodPointcutAdvisor,它能实现正则匹配,如:

    // RegexpMethodPointcutAdvisor 按正则匹配类
    @Bean
    public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor() {
        RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
        advisor.setAdvice(logInterceptor());
        advisor.setPattern("com.swadian.aopdemo.SimpleCalculate.*");
        return advisor;
    }

也就是说,我们能通过配置 Advisor,精确定位到需要被拦截的方法,然后使用内部的 Advice 执行逻辑处理。

之后,我们需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常简单,直接使用下面这段配置就可以了,它使得所有的 Advisor 自动生效,无须其他配置。(记得把之前的autoProxyCreator配置去掉,无需创建2次代理)

    /**
     * BeanPostProcessor自动扫描Advisor方式  DefaultAdvisorAutoProxyCreator
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator autoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(EarlyAopMainConfig.class);
        Calculate calculateProxy = ctx.getBean("simpleCalculate", Calculate.class);
        System.out.println(calculateProxy.getClass());
        calculateProxy.div(1, 1); // 除法
    }
}

到这里,Spring 1.2 的配置就介绍完了。本文没有面面俱到,主要是关注最核心的配置,如果读者感兴趣,要学会自己去摸索,比如这里的 Advisor 就不只有文中介绍的 NameMatchMethodPointcutAdvisor 和 RegexpMethodPointcutAdvisor,AutoProxyCreator 也不仅仅是 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator。

读到这里,对于很多人来说,基本就知道怎么去阅读 Spring AOP 的源码了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值