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

原创 2016年06月01日 16:39:59

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>

一、通过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方法返回值";
    }
}
  • 使用<aop:advisor>配置切入点和前置增强方法
<aop:advisor advice-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());
  • 执行结果

这里写图片描述

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

二、通过<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();
        }
    }
}

切入点类代码

/**用于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配置

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

配置

<aop:before 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对象获取切入点方法的相关参数,如参数列表、方法签名对象、切入点所在对象及代理对象本身。


  • 后置增强

配置

<aop:after-returning 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方法返回值

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


  • 后置异常增强

配置

<aop:after-throwing 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方法抛出的异常

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


  • 最终增强

配置

<aop:after 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

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


  • 环绕增强

配置

<aop:around 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配置切入点方式及配置各种类型增强

AOP(Aspect-Oriented Programming):面向切面编程  是一种通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的技术 AOP相关jar包: ...
  • FiangAsDre
  • FiangAsDre
  • 2016年10月21日 15:09
  • 501

spring中AOP中配置切面和切入点(15)

2016/1/18 10:46:211.配置Spring所有的切面和通知器都必须放在一个内(可以配置包含多个元素),每一个可以包含pointcut,advisor和aspect元素 (它们必须按照这个...
  • kang82651204
  • kang82651204
  • 2016年01月18日 13:08
  • 4533

Spring切入点表达式常用写法

以下文档来自Spring中文开发指南2.5文档,由满江红开源组织翻译:   Spring AOP 用户可能会经常使用 execution切入点指示符。执行表达式的格式如下: execution(m...
  • remote_roamer
  • remote_roamer
  • 2016年10月09日 11:57
  • 776

spring 事务和数据回滚和事件切入点定义

今天遇到了一个spring事务不会回滚的问题,搞了一天晚上在群里面得到大神的帮助才得以解决,不多说了先贴配置文件吧 ...
  • qq_22929803
  • qq_22929803
  • 2015年10月30日 20:23
  • 2187

Spring中的AOP(六)——定义切入点和切入点指示符

定义切入点     在前文(点击查看)中使用到的AdviceTest类中同一个切点(即* com.abc.service.*.advice*(..)匹配的连接点)却重复定义了多次,这显然不符合软件设...
  • qq_27966627
  • qq_27966627
  • 2016年03月09日 15:47
  • 1152

Spring切入点表达式

execution 其格式为: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(p...
  • scgaliguodong123_
  • scgaliguodong123_
  • 2015年11月11日 16:59
  • 1037

Spring 之AOP AspectJ切入点语法详解(最全面、最详细。)

6.5  AspectJ切入点语法详解 6.5.1  Spring AOP支持的AspectJ切入点指示符        切入点指示符用来指示切入点表达式目的,,在Spring AOP...
  • zhengchao1991
  • zhengchao1991
  • 2016年11月29日 12:14
  • 10745

Spring 之AOP AspectJ切入点语法详解

三6.5  AspectJ切入点语法详解 6.5.1  Spring AOP支持的AspectJ切入点指示符        切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前...
  • Happy_wu
  • Happy_wu
  • 2016年07月27日 10:51
  • 1991

Spring切入点表达式常用写法

Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合. ar...
  • u013895412
  • u013895412
  • 2017年02月17日 10:27
  • 625

spring学习笔记(23)基于tx/aop配置切面增强事务

在上一篇文章中,我们使用了声明式事务来配置事务,使事务配置从service逻辑处理中解耦出来。但它还存在一些缺点: 1. 我们只针对方法名的特定进行拦截,但无法利用方法签名的其它信息定位,如修饰符、...
  • qwe6112071
  • qwe6112071
  • 2016年03月26日 01:34
  • 3453
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Spring-AOP配置切入点方式及配置各种类型增强
举报原因:
原因补充:

(最多只允许输入30个字)