AOP 与 AspectJ

前面我们学习了AOP的具体实现,今天我们学习使用Aspectj框架实现Spring AOP,前面也说了Spring的AOP并不是全面的解决方案,Aspectj才是。

我们再回顾下Spring AOP中的三个概念,Advice:向程序内部注入的代码。Pointcut:注入 Advice 的位置,切入点,一般为某方法。Advisor: Advice 和 Pointcut 的结合单元,以便将 Advice 和 Pointcut 分开实现灵活配置。 AspectJ 是基于注解(Annotation)的,Spring 新版也非常提倡这种方式,但是我们不需要局限于某种方式,每种配置都有利与弊,只有适合的才是最好的。xml对于项目配置分离的环境可是很方便的。注解需要JDK5.0以上的支持。


AspectJ 支持的注解类型

  • @Before
  • @After
  • @AfterReturning
  • AfterThrowing
  • @Around

首先没有引入 Pointcut 之前,Advice 和 Pointcut 是混在一起的步骤,只需要两步,如下:

  • 创建一个Aspect类
  • 配置Spring配置文件

由于AspectJ是第三方包。我们需要下载导入,目前是1.8.x。

wget http://labfile.oss.aliyuncs.com/courses/578/aspectJLib.tar.gz

解压:

tar -zvxf aspectJ-Lib.tar.gz

不知道是什么问题,我用win10和Git的bash的都解压不了,用linux没有问题。解压完成导入到Build pathIntelliJproject上按F12配置。

创建AspectJ类

package boy.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by Boy on 2016/12/16 16:57.
 */

@Aspect
public class LoggingAspect {

    @Before("execution(public * boy.aop.aspectj.CustomerBo.addCustomer(..))")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ... ");
        System.out.println("Pointcut : " + joinPoint.getSignature().getName());
        System.out.println("---------------");
    }

    @After("execution(public * boy.aop.aspectj.CustomerBo.deleteCustomer(..))")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ... ");
        System.out.println("Pointcut : " + joinPoint.getSignature().getName());
        System.out.println("---------------");
    }

}

必须在LoggingAspect声明前使用@Aspect注解,以便让框架扫描到。上面AdvicePointcut 结合在一起,类中的具体方法 logBeforelogAfter 即为 Advice ,是要注入的代码,Advice 方法上的表达式为 Pointcut 表达式,即定义了切入点,上例中 @Before 注解的表达式代表执行 CustomerBo.addCustomer 方法时注入 logBefore 代码;在 LoggingAspect 方法上加入 @Before 或者 @After 等注解。


execution()切入点表达式

在配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点。execution()是最常用的切点函数,其语法如下所示:

** 整个表达式可以分为五个部分:**

execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)

除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

  • execution(public * boy.aop.aspectj...(..))): 表达式主体。
  • 匹配所有目标类的public方法
  • 第一个号:表示返回类型,号表示所有的类型。
  • 包名:表示需要拦截的包名,有两个句点表示当前包和当前包的所有子包下所有类的方法。
  • 第二个号:表示类名,号表示所有的类。
  • (..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

配置文件

<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启动AspectJ支持,自动寻找使用@Aspect注解的类 -->
    <aop:aspectj-autoproxy/>

    <bean id="customerBo" class="boy.aop.aspectj.CustomerBo"></bean>

    <bean id="logAspect" class="boy.aop.aspectj.LoggingAspect"></bean>

</beans>

简单测试类

package boy.aop.aspectj;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Created by Boy on 2016/12/16 21:49.
 */
public class AspectJTest {

    public static void main(String arg[]){

        ApplicationContext ctx = new FileSystemXmlApplicationContext("/src/main/resources/SpringAOPAspectJ.xml");

        ICustomerBo ctb = (ICustomerBo) ctx.getBean("customerBo");

        ctb.addCustomer();

        System.out.println("----------------------");

        ctb.deleteCustomer();


    }

}

运行结果

22:00:47.472 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'customerBo'
22:00:47.472 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'customerBo'
22:00:47.505 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void boy.aop.aspectj.LoggingAspect.logBefore(org.aspectj.lang.JoinPoint)
22:00:47.507 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void boy.aop.aspectj.LoggingAspect.logAfter(org.aspectj.lang.JoinPoint)
22:00:47.509 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'customerBo' to allow for resolving potential circular references
22:00:47.633 [main] DEBUG org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'customerBo' with 0 common interceptors and 3 specific interceptors
22:00:47.634 [main] DEBUG org.springframework.aop.framework.JdkDynamicAopProxy - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [boy.aop.aspectj.CustomerBo@1b1473ab]
22:00:47.639 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'customerBo'
22:00:47.639 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'logAspect'
22:00:47.639 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'logAspect'
22:00:47.640 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'logAspect' to allow for resolving potential circular references
22:00:47.642 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'logAspect'
22:00:47.644 [main] DEBUG org.springframework.context.support.FileSystemXmlApplicationContext - Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor@69b2283a]
22:00:47.644 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
22:00:47.646 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
22:00:47.648 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'customerBo'
22:00:47.651 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'logAspect'
logBefore() is running ... 
Pointcut : addCustomer
---------------
addCustomer() is running ... 
----------------------
deleteCustomer() is running ... 
logAfter() is running ... 
Pointcut : deleteCustomer
---------------

从Spring的运行日志我们看到,首先初始化customerBoBean,然后分别found到我们定义的AspectJ logBefore 和 logAfter method。

将 Advice 和 Pointcut 分开

  1. 创建 Pointcut
  2. 创建 Advice
  3. 配置 Spring 的配置文件

创建定义Pointcut

package boy.aop.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

/**
 * Created by Boy on 2016/12/16 22:11.
 */

@Aspect
public class PointcutDefinition {

    @Pointcut("execution(* boy.aop.aspectj.CustomerBo.*(..))")
    public void customerLog(){

    }

}

@Pointcut 是切入点声明,指定需要注入的代码的位置。customerLog起到一个签名作用,在 Advice 中可以用此签名代替切入点表达式,所以不需要在方法体内编写实际代码,此处代表操作CustomBo类是需要切入的点

修改LoggingAspect类

package boy.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by Boy on 2016/12/16 16:57.
 */

@Aspect
public class LoggingAspect {

//    @Before("execution(public * boy.aop.aspectj.CustomerBo.addCustomer(..))")
    @Before("boy.aop.aspectj.PointcutDefinition.customerLog()")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ... ");
        System.out.println("Pointcut : " + joinPoint.getSignature().getName());
        System.out.println("---------------");
    }

//    @After("execution(public * boy.aop.aspectj.CustomerBo.deleteCustomer(..))")
    @After("boy.aop.aspectj.PointcutDefinition.customerLog()")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ... ");
        System.out.println("Pointcut : " + joinPoint.getSignature().getName());
        System.out.println("---------------");
    }

}

@Before@After 使用 PointcutsDefinition 中的方法签名代替 Pointcut 表达式找到相应的签名切入点。PointcutsDefinition 主要是定义POIPointcut,可定义多个签名切入点。这样自定义方法名更容易维护

配置文件和测试类代码都保持不变

运行结果

22:26:32.629 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'customerBo'
22:26:32.629 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'customerBo'
22:26:32.660 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void boy.aop.aspectj.LoggingAspect.logBefore(org.aspectj.lang.JoinPoint)
22:26:32.661 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void boy.aop.aspectj.LoggingAspect.logAfter(org.aspectj.lang.JoinPoint)
22:26:32.662 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'customerBo' to allow for resolving potential circular references
22:26:32.815 [main] DEBUG org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'customerBo' with 0 common interceptors and 3 specific interceptors
22:26:32.816 [main] DEBUG org.springframework.aop.framework.JdkDynamicAopProxy - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [boy.aop.aspectj.CustomerBo@29626d54]
22:26:32.821 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'customerBo'
22:26:32.821 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'logAspect'
22:26:32.821 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'logAspect'
22:26:32.821 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'logAspect' to allow for resolving potential circular references
22:26:32.822 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'logAspect'
22:26:32.824 [main] DEBUG org.springframework.context.support.FileSystemXmlApplicationContext - Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor@64cd705f]
22:26:32.824 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
22:26:32.828 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
22:26:32.830 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'customerBo'
22:26:32.833 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'logAspect'
logBefore() is running ... 
Pointcut : addCustomer
---------------
addCustomer() is running ... 
logAfter() is running ... 
Pointcut : addCustomer
---------------
----------------------
logBefore() is running ... 
Pointcut : deleteCustomer
---------------
deleteCustomer() is running ... 
logAfter() is running ... 
Pointcut : deleteCustomer
---------------

可以看到,和前面的效果是一样的。通过对比,使用AspectJ开发,代码比较简洁,给我们提供了完整的解决方案,以后再继续学习深入的知识吧。

最后由 不一样的少年 编辑于2016年12月16日 23:52