Spring-AOP配置切入点方式及配置各种类型增强

来源:互联网 发布:业务流程优化案例分析 编辑:程序博客网 时间:2024/06/06 16:44

AOP(Aspect-Oriented Programming):面向切面编程 
是一种通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的技术

AOP相关jar包: 
spring-aop-4.2.5.RELEASE.jar 
aopalliance-1.0.jar 
aspectjweaver-1.6.9.jar 
cglib-nodep-2.1_3.jar

使用AOP之前先配置切入点:

    <aop:config>        <!-- 配置切入点 -->        <!-- execution()语法:execution (* com.xx.xx.impl..*.*(..))        首先明白这个表达式是用来匹配方法的,各种条件是为了筛选整个项目的方法。        (类的访问修饰符        第一个*表示方法返回值类型[*表示所有类型]         com.xx.xx.impl表示包路径[*表示所有包]        .[.表示当前包下所有类的方法,..表示当前包下及此包下所有子包中的所有类的方法]         第二个*表示类名[*表示所有类,可以匹配以X开头或结尾如X*、*X、X*X的类名]        第三个*表示方法名[*表示所有方法,可以匹配以X开头或结尾的如X*、*X、X*X的方法名]        (..)表示方法参数[..表示任何参数]        )-->        <aop:pointcut expression="execution(public * com.bc.aop..*.*(..))" id="pointcut"/>    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一、通过Advisor配置增强处理(不推荐)

  • 新建一个类MethodBeforeAdvice接口的before方法
/**前置增强*/public class BeforeLogger implements MethodBeforeAdvice {    private Logger logger = Logger.getLogger(BeforeLogger.class);    @Override    public void before(Method arg0, Object[] arg1, Object arg2)            throws Throwable {        logger.info("Method:" + arg0.getName());        logger.info("Object[]:" + Arrays.toString(arg1));        logger.info("Object:" + arg2.toString());        logger.info("这是BeforeLogger类的before方法!");    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 用来测试的切入点类
/**测试业务类*/public class UserBiz {    public String addUser(String uname, String pwd) {        System.out.println("这是UserBiz类的addUser方法!");        return "UserBiz类的addUser方法返回值";    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 使用<aop:advisor>配置切入点和前置增强方法
<aop:advisor advice-ref="beforeLogger" pointcut-ref="pointcut"/>
  • 1
  • 相关实体bean配置略

  • 测试代码

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");        UserBiz user = (UserBiz) ac.getBean("userBiz01");        user.addUser("张三", "李四");        System.out.println("user:" + user.toString());
  • 1
  • 2
  • 3
  • 4
  • 执行结果

这里写图片描述

  • 这种方式不推荐使用,其他增强略

二、通过<aop:aspect>配置实现增强

先介绍下用来访问连接点上下文信息的对象

AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:

1)JoinPoint 
- java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
- Signature getSignature() :获取连接点的方法签名对象; 
- java.lang.Object getTarget() :获取连接点所在的目标对象; 
- java.lang.Object getThis() :获取代理对象本身;

2)ProceedingJoinPoint 
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: 
- java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; 
- java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。

接下来是各种增强测试,相关对象bean配置略

增强类代码

public class MyLogger {    private Logger logger = Logger.getLogger(MyLogger.class);    /**前置增强方法*/    public void beforeLogger(JoinPoint jp) {        logger.info("这是MyLogger类的before方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());    }    /**后置增强方法*/    public void afterReturning(JoinPoint jp, Object result) {        logger.info("这是MyLogger类的after-returning方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("切入点方法返回对象:" + result);    }    /**后置异常增强方法*/    public void afterThrowing(JoinPoint jp, Exception e) {        logger.info("这是MyLogger类的after-Throwing方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("异常:" + e);    }    /**最终增强方法*/    public void after(JoinPoint jp) {        logger.info("这是MyLogger类的after方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());    }    /**环绕增强方法*/    public void aroundLogger(ProceedingJoinPoint jp) {        logger.info("这是MyLogger类的around方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("-------------------------------");        try {            System.out.println("执行切入点方法");            jp.proceed();            System.out.println("-------------------------------");            System.out.println("执行切入点方法并改变参数");            jp.proceed(new Object[]{7});        } catch (Throwable e) {            e.printStackTrace();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

切入点类代码

/**用于AOP测试的切入点类*/public class UserInfo {    public String beforeTest(int i) {        System.out.println("这是UserInfo类的beforeTest方法!");        return "beforeTest方法返回值";    }    public String afterReturningTest(int i) {        System.out.println("这是UserInfo类的afterReturningTest方法!");        return "afterReturningTest方法返回值";    }    public String afterThrowingTest(int i) {        System.out.println("这是UserInfo类的afterThrowingTest方法!");        throw new RuntimeException("afterThrowingTest方法抛出的异常");    }    public String afterTest(int i) {        System.out.println("这是UserInfo类的afterTest方法!");        throw new RuntimeException("afterTest方法抛出的异常");    }    public String aroundTest(int i) {        System.out.println("这是UserInfo类的aroundTest方法!参数值:" + i);        return "aroundTest方法返回值";    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

applicationContext.xml配置

<!-- 配置多个增强方式 ref引用配置的增强类bean id--><aop:config>    <!--配置切入点-->    <aop:aspect ref="myLogger">        <!--配置各种类型增强-->    </aop:aspect></aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 前置增强

配置

<aop:before method="beforeLogger" pointcut-ref="pointcut"/>
  • 1

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");UserInfo user = (UserInfo) ac.getBean("userInfo");//前置增强测试user.beforeTest(1);
  • 1
  • 2
  • 3
  • 4

控制台

2016-06-02 15:50:45,127 INFO [com.bc.aop.demo02.MyLogger] - 这是MyLogger类的before方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo02.UserInfo.beforeTest(int)切入点所在目标对象:com.bc.aop.demo02.UserInfo@557caf28代理对象本身:com.bc.aop.demo02.UserInfo@557caf28这是UserInfo类的beforeTest方法!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

结论: 
从控制台输出来看,前置增强会在切入点方法执行之前执行,并可以通过JoinPoint对象获取切入点方法的相关参数,如参数列表、方法签名对象、切入点所在对象及代理对象本身。


  • 后置增强

配置

<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
  • 1

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");UserInfo user = (UserInfo) ac.getBean("userInfo");//后置增强测试user.afterReturningTest(2);
  • 1
  • 2
  • 3
  • 4

控制台

这是UserInfo类的afterReturningTest方法!2016-06-02 15:56:08,334 INFO [com.bc.aop.demo02.MyLogger] - 这是MyLogger类的after-returning方法!切入点方法入参列表:[2]切入点方法签名对象:String com.bc.aop.demo02.UserInfo.afterReturningTest(int)切入点所在目标对象:com.bc.aop.demo02.UserInfo@139982de代理对象本身:com.bc.aop.demo02.UserInfo@139982de切入点方法返回对象:afterReturningTest方法返回值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结论 
从控制台输出看出,后置增强会在切入点方法执行之后,再执行增强方法,因此除了可以像前置增强那样获取切入点方法的相关信息,还可以获取切入点方法的返回值,此外如果切入点方法没有正常执行,如抛出异常,则不会执行后置增强方法。


  • 后置异常增强

配置

<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
  • 1

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");UserInfo user = (UserInfo) ac.getBean("userInfo");try {//后置异常增强测试user.afterThrowingTest(3);
  • 1
  • 2
  • 3
  • 4
  • 5

控制台

这是UserInfo类的afterReturningTest方法!这是UserInfo类的afterThrowingTest方法!2016-06-02 16:00:53,481 INFO [com.bc.aop.demo02.MyLogger] - 这是MyLogger类的after-Throwing方法!切入点方法入参列表:[3]切入点方法签名对象:String com.bc.aop.demo02.UserInfo.afterThrowingTest(int)切入点所在目标对象:com.bc.aop.demo02.UserInfo@385c9627代理对象本身:com.bc.aop.demo02.UserInfo@385c9627异常:java.lang.RuntimeException: afterThrowingTest方法抛出的异常
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结论 
从控制台输出看出,后置异常增强会在切入点方法抛出异常的时候执行异常增强方法,除了可以获取方法相关信息之外也可以获取对应的异常信息。


  • 最终增强

配置

<aop:after method="after" pointcut-ref="pointcut"/>
  • 1

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");UserInfo user = (UserInfo) ac.getBean("userInfo");try {user.afterTest(4);} catch (Exception e) {}
  • 1
  • 2
  • 3
  • 4
  • 5

控制台

这是UserInfo类的afterTest方法!2016-06-02 16:06:15,169 INFO [com.bc.aop.demo02.MyLogger] - 这是MyLogger类的after方法!切入点方法入参列表:[4]切入点方法签名对象:String com.bc.aop.demo02.UserInfo.afterTest(int)切入点所在目标对象:com.bc.aop.demo02.UserInfo@7586beff代理对象本身:com.bc.aop.demo02.UserInfo@7586beff
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

结论 
从控制台和切入点方法可以看出,最终增强无论切入点方法是否正常执行完毕,都会执行增强方法,因此不可以获取返回值或者异常,只可以获取和前置增强相同的信息。


  • 环绕增强

配置

<aop:around method="aroundLogger" pointcut-ref="pointcut"/>
  • 1

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");UserInfo user = (UserInfo) ac.getBean("userInfo");//环绕增强测试user.aroundTest(5);
  • 1
  • 2
  • 3
  • 4
  • 5

控制台

2016-06-02 16:12:00,054 INFO [com.bc.aop.demo02.MyLogger] - 这是MyLogger类的around方法!切入点方法入参列表:[5]切入点方法签名对象:String com.bc.aop.demo02.UserInfo.aroundTest(int)切入点所在目标对象:com.bc.aop.demo02.UserInfo@557caf28代理对象本身:com.bc.aop.demo02.UserInfo@557caf28-------------------------------执行切入点方法这是UserInfo类的aroundTest方法!参数值:5-------------------------------执行切入点方法并改变参数这是UserInfo类的aroundTest方法!参数值:7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结论 
环绕增强比较特殊,如果一个方法配置了环绕增强,那么执行此方法只会执行环绕增强的方法,然后可以在环绕增强的方法中通过JoinPoint或ProceedingJoinPoint(只对环绕增强有效,其余增强使用此对象会报错)获取切入点方法的相关信息,同时,使用ProceedingJoinPoint还可以执行N次切入点方法,也可以改变切入点方法的参数数值(参数数量需要保持相同),因此,使用环绕增强,要想执行切入点方法需要在增强方法内调用ProceedingJoinPoint对象的proceed()方法来执行切入点方法。


三、使用注解方式配置AOP

首先先修改下applicationContext.xml配置文件

<!-- 打开切面注解 --><aop:aspectj-autoproxy proxy-target-class="true"/><!-- 扫描注解包 --><context:component-scan base-package="com.bc"/>
  • 1
  • 2
  • 3
  • 4

然后用上面的MyLogger类来改

@Aspect@Component//必须有这个注解public class MyLogger {    private Logger logger = Logger.getLogger(MyLogger.class);    //定义一个切入点    @Pointcut("execution(public * com.bc.aop..*.*(..))")    public void pointcut() {}    /**前置增强方法*/    @Before("pointcut()")    //@Before("execution(public * com.bc.aop..*.*(..))")可以给方法单独指定切入点    public void beforeLogger(JoinPoint jp) {        logger.info("这是MyLogger类的before方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());    }    /**后置增强方法*/    @AfterReturning(pointcut = "pointcut()", returning = "result")    public void afterReturning(JoinPoint jp, Object result) {        logger.info("这是MyLogger类的after-returning方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("切入点方法返回对象:" + result);    }    /**后置异常增强方法*/    @AfterThrowing(pointcut = "pointcut()", throwing = "e")    public void afterThrowing(JoinPoint jp, Exception e) {        logger.info("这是MyLogger类的after-Throwing方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("异常:" + e);    }    /**最终增强方法*/    @After("pointcut()")    public void after(JoinPoint jp) {        logger.info("这是MyLogger类的after方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());    }    /**环绕增强方法*/    @Around("pointcut()")    public void aroundLogger(ProceedingJoinPoint jp) {        logger.info("这是MyLogger类的around方法!");        System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));        System.out.println("切入点方法签名对象:" + jp.getSignature());        System.out.println("切入点所在目标对象:" + jp.getTarget());        System.out.println("代理对象本身:" + jp.getThis());        System.out.println("-------------------------------");        try {            System.out.println("执行切入点方法");            jp.proceed();            System.out.println("-------------------------------");            System.out.println("执行切入点方法并改变参数");            jp.proceed(new Object[]{7});        } catch (Throwable e) {            e.printStackTrace();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

切入点测试类代码

@Service("person")public class Person {    public String personTest(int i) {        System.out.println("这是Person类的personTest方法,参数值:" + i);        return "personTest方法返回值";    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");Person person = (Person) ac.getBean("person");person.personTest(1);
  • 1
  • 2
  • 3

控制台

2016-06-02 16:48:11,347 INFO [com.bc.aop.demo03.MyLogger] - 这是MyLogger类的around方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo03.Person.personTest(int)切入点所在目标对象:com.bc.aop.demo03.Person@1a4013代理对象本身:com.bc.aop.demo03.Person@1a4013-------------------------------执行切入点方法2016-06-02 16:48:11,367 INFO [com.bc.aop.demo03.MyLogger] - 这是MyLogger类的before方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo03.Person.personTest(int)切入点所在目标对象:com.bc.aop.demo03.Person@1a4013代理对象本身:com.bc.aop.demo03.Person@1a4013这是Person类的personTest方法,参数值:1-------------------------------执行切入点方法并改变参数2016-06-02 16:48:11,371 INFO [com.bc.aop.demo03.MyLogger] - 这是MyLogger类的before方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo03.Person.personTest(int)切入点所在目标对象:com.bc.aop.demo03.Person@1a4013代理对象本身:com.bc.aop.demo03.Person@1a4013这是Person类的personTest方法,参数值:72016-06-02 16:48:11,372 INFO [com.bc.aop.demo03.MyLogger] - 这是MyLogger类的after方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo03.Person.personTest(int)切入点所在目标对象:com.bc.aop.demo03.Person@1a4013代理对象本身:com.bc.aop.demo03.Person@1a40132016-06-02 16:48:11,372 INFO [com.bc.aop.demo03.MyLogger] - 这是MyLogger类的after-returning方法!切入点方法入参列表:[1]切入点方法签名对象:String com.bc.aop.demo03.Person.personTest(int)切入点所在目标对象:com.bc.aop.demo03.Person@1a4013代理对象本身:com.bc.aop.demo03.Person@1a4013切入点方法返回对象:null
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

注解的方式可以大大减少配置代码,但是个人也不是很喜欢这种侵入式配置,不利于后期维护

0 0