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方法返回值"; }}
- 使用
<aop:advisor>
配置切入点和前置增强方法
<aop:advisor advice-ref="beforeLogger" pointcut-ref="pointcut"/>
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() } }}
- 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配置
<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@Componentpublic class MyLogger { private Logger logger = Logger.getLogger(MyLogger.class); @Pointcut("execution(public * com.bc.aop..*.*(..))") public void pointcut() {} /**前置增强方法*/ @Before("pointcut()") 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方法返回值"; }}
测试代码
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方法,参数值: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