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

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>
        
        
        <aop:pointcut expression="execution(public * com.bc.aop..*.*(..))" id="pointcut"/>

    aop:config>

一、通过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方法!");
    }

}
  • 用来测试的切入点类
/**测试业务类*/
public class UserBiz {

    public String addUser(String uname, String pwd) {
        System.out.println("这是UserBiz类的addUser方法!");
        return "UserBiz类的addUser方法返回值";
    }
}
  • 使用配置切入点和前置增强方法
ref="beforeLogger" pointcut-ref="pointcut"/>
  • 相关实体bean配置略

  • 测试代码

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

这里写图片描述

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

二、通过配置实现增强

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

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

切入点类代码

/**用于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方法返回值";
    }
}

applicationContext.xml配置


<aop:config>
    
    <aop:aspect ref="myLogger">
        
    aop:aspect>
aop:config>
  • 前置增强

配置

method="beforeLogger" pointcut-ref="pointcut"/>

测试代码

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

控制台

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方法!

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


  • 后置增强

配置

method="afterReturning" pointcut-ref="pointcut" returning="result"/>

测试代码

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

控制台

这是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方法返回值

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


  • 后置异常增强

配置

method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>

测试代码

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

控制台

这是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方法抛出的异常

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


  • 最终增强

配置

method="after" pointcut-ref="pointcut"/>

测试代码

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

控制台

这是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

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


  • 环绕增强

配置

method="aroundLogger" pointcut-ref="pointcut"/>

测试代码

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo user = (UserInfo) ac.getBean("userInfo");

//环绕增强测试
user.aroundTest(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

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


三、使用注解方式配置AOP

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


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

<context:component-scan base-package="com.bc"/>

然后用上面的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();
        }
    }
}

切入点测试类代码

@Service("person")
public class Person {

    public String personTest(int i) {
        System.out.println("这是Person类的personTest方法,参数值:" + i);
        return "personTest方法返回值";
    }
}

测试代码

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

控制台

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方法,参数值:7
2016-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@1a4013
2016-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

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

你可能感兴趣的:(Spring-AOP配置切入点方式及配置各种类型增强)