基于XML方式的AOP实现

 基于XML方式的AOP实现

 

在6.1.4节中,我们对面向切面编程AOP技术的实现原理及基本概念进行了深入剖析,本节将延续6.1.4节的话题,进一步探讨基于XML方式的AOP实现。在面向切面编程AOP技术中,切面的提取、通知的编写、切入点的定义、切面与目标对象的AOP装配是几个重要的环节。

所谓切面的提取就是综观Java EE应用,提取其中的所有共有功能(如日志、权限验证、事务管理等),并将其封装到一个类中,这个类就是切面(如日志切面、权限切面、事务切面等),去除了共有功能的业务逻辑代码将变得更加简洁优雅。

所谓通知的编写就是在切面中对共有功能的具体实现,一个通知往往对应的就是切面中的某个方法,然而根据这个通知欲切入到目标方法的位置不同可分为前置通知(在目标方法被调用之前执行)、后置通知(在目标方法被成功调用之后执行)、异常通知(在捕捉到目标方法抛出异常时执行)、最终通知(不管目标方法调用成功与否,均在调用完毕之后执行)、环绕通知(在目标方法被调用的前后均可进行操作)。在基于XML方式的AOP实现中,编写通知时,并不需要指明通知的类型,通知类型的设定是在Spring的XML配置文件中配置设定的。

所谓切入点的定义就是通过编写一个AOP正则表达式,指明欲拦截与插入通知的连接点。由于Spring的连接点仅支持方法级别,因此,本节中所讲的连接点实指目标对象中的某个方法。在定义AOP切入点时,通常使用execution切入点指示符设置连接点正则表达式,具体语法如下:

 
 
  1. execution(modifiers-pattern? ret-type-pattern   
  2. declaring-type-pattern? name-pattern(param-pattern)   
  3. throws-pattern?) 

除了返回值类型模式(ret-type-pattern),名字模式(name-pattern)和参数模式(param-pattern)以外,其他部分都是可选的。访问类型模式(modifiers-pattern)决定了方法的访问类型,如public表示匹配所有公用方法,此模式通常可省略不写,除非有特定的需求。返回值类型模式(ret-type-pattern)决定了方法的返回值类型,当所有返回值类型均予以匹配的话,可直接设置为*,表示匹配任意的返回值类型。类型名称模式(declaring-type-pattern)表示匹配的包或类的名称规则,"包路径.*"表示指定包下的所有类均予以匹配,"包路径..*"则表示指定包及其子包下的所有类均予以匹配,"包路径.类名"表示指定的类予以匹配。名字模式name-pattern匹配的是方法名,*表示匹配所有方法,set*表示所有方法名以set开头的方法。参数模式param-pattern稍微有点复杂:()表示匹配不接受任何参数的方法,(..)表示匹配接受任意数量参数的方法,不论参数有无或多少,(*)表示匹配接受一个任何类型的参数的方法,而(*,String)则表示匹配接受两个参数的方法,第一个参数可以是任意类型,而第二个参数必须是String类型。异常模式(throws-pattern)决定了抛出何种类型的异常,如RuntimeException表示匹配抛出RuntimeException异常的方法,此模式通常可省略不写,除非有特定的需求。

表6-5中列举一些常用的AOP正则表达式以供参考。

表6-5Spring 2.5中常用的AOP正则表达式说明

切入点正则表达式

execution(public * *(..))

匹配所有公有方法

execution(* set*(..))

匹配所有以“set”开头的方法

execution(* test.spring.action.UserAction.*(..))

匹配UserAction类的所有方法

execution(* test.spring.action.*.*(..))

匹配test.spring.action包下所有类的所有方法

execution(* test.spring.action..*.*(..))

匹配test.spring.action包及子包下所有类的所有方法

所谓切面与目标对象的AOP装配就是将编写好的通知和规则好的切入点在Spring配置文件中进行配置,最终让Spring容器按我们的意图实现AOP。

下面我们以一个简单的Java应用SpringXmlAOP具体演示一下基于XML方式的AOP实现,在该实例中省略很多周边代码,大家可参考配套光盘的"源码部分"。

首先定义一个日志切面LogAspectJ,并在该切面中编写用于演示的各种通知,值得注意的是,在定义通知方法时传入了一个连接点JoinPoint参数,通过该参数可以取得目标对象的类名、目标方法名称及目标方法的参数等:

 
 
  1. package test.spring.aop;  
  2. import org.apache.log4j.Logger;  
  3. import org.aspectj.lang.JoinPoint;  
  4. import org.aspectj.lang.ProceedingJoinPoint;  
  5. /** 日志切面 */ 
  6. public class LogAspectJ {  
  7. // 取得日志记录器Logger  
  8. public Logger logger = Logger.getLogger(LogAspectJ.class);  
  9. //此方法将用做前置通知  
  10. public void myBeforeAdvice(JoinPoint joinpoint) {  
  11. String classAndMethod = joinpoint.getTarget().getClass().getName()  
  12. +"类的"+joinpoint.getSignature().getName();  
  13. logger.info("前置通知:"+classAndMethod+"方法开始执行!");  
  14. }  
  15. //此方法将用做后置通知  
  16. public void myAfterReturningAdvice(JoinPoint joinpoint) {  
  17. String classAndMethod = joinpoint.getTarget().getClass().getName()  
  18. +"类的"+joinpoint.getSignature().getName();  
  19. logger.info("后置通知:"+classAndMethod+"方法执行正常结束!");  
  20. }  
  21. //此方法将用做异常通知  
  22.  public void myAfterThrowingAdvice(JoinPoint joinpoint,Exception e) {  
  23. String classAndMethod = joinpoint.getTarget().getClass().getName()+  
  24. "类的"+joinpoint.getSignature().getName();  
  25. logger.info("异常通知:"+classAndMethod+"方法抛出异常:  
  26. "+e.getMessage());  
  27. }  
  28. //此方法将用做最终通知  
  29. public void myAfterAdvice(JoinPoint joinpoint) {  
  30. String classAndMethod = joinpoint.getTarget().getClass().getName()+  
  31. "类的"+joinpoint.getSignature().getName();  
  32. logger.info("最终通知:"+classAndMethod+"方法执行结束!");  
  33. }  
  34. //此方法将用做环绕通知  
  35. public Object myAroundAdvice(ProceedingJoinPoint pjp)   
  36. throws Throwable {  
  37. long begintime = System.currentTimeMillis();//记下开始时间  
  38. //传递给连接点对象进行接力处理  
  39. Object result=pjp.proceed();  
  40. long endtime = System.currentTimeMillis();//记下结束时间  
  41. String classAndMethod = pjp.getTarget().getClass().getName()+  
  42. "类的"+pjp.getSignature().getName();  
  43. logger.info("环绕通知:"+classAndMethod+"方法执行结束,  
  44. 耗时"+(endtime-begintime)+"毫秒!");  
  45. return result;  
  46. }  

接下来编写一个业务控制组件UserAction,我们打算将切面应用到该组件上,对该组件的所有方法均予以拦截并切入所有通知,为了演示异常通知的执行效果,特意在addUser方法的尾部人为抛出一个RuntimeException异常:

 
 
  1. package test.spring.action;  
  2. import test.spring.bean.User;  
  3. import test.spring.service.UserService;  
  4. /** 用户管理业务控制器 */ 
  5. public class UserAction {  
  6. //仅声明业务逻辑组件的引用  
  7. UserService service;  
  8. //处理新增用户请求  
  9. public void addUser() {  
  10. String userName="liubin";  
  11. String userPwd="123456";  
  12. service.addUser(userName,userPwd);  
  13. throw new RuntimeException("这是特意抛出的异常信息!");  
  14. }  
  15. //处理删除用户请求  
  16. public void delUser() {  
  17. service.delUser(1);  
  18. }  
  19. //处理装载用户请求  
  20. public void loadUser() {  
  21. User user = service.loadUser(1);  
  22. System.out.println("用户名="+user.getUserName());  
  23. }  
  24. //处理修改用户请求  
  25. public void modiUser() {  
  26. Integer id = 1;  
  27. String userName="liujunyu";  
  28. String userPwd="123456";  
  29. service.modiUser(id, userName, userPwd);  
  30. }  
  31. //提供一条UserService对象的注入通道  
  32. public void setService(UserService service) {  
  33. this.service = service;  
  34. }  

日志切面LogAspectJ与业务控制组件UserAction原本是两个互不相干的类,通过在Spring配置文件中进行AOP装配之后,奇妙的事情就会出现了,LogAspectJ中的通知将会巧妙地切入到UserAction中,以实现预期的日志记录功能:

 
 
  1. "1.0" encoding="UTF-8"?>  
  2. xmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:aop="http://www.springframework.org/schema/aop" 
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans   
  6. http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  7. http://www.springframework.org/schema/aop   
  8. http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
  9.  
  10. "dao" class="test.spring.dao.impl.UserDaoImpl"/>  
  11.  
  12. "service" class="test.spring.service.impl.UserServiceImpl">  
  13.  
  14. "dao" ref="dao"/>  
  15.  
  16.  
  17. "userAction" class="test.spring.action.UserAction">  
  18.  
  19. "service" ref="service" />  
  20.  
  21.  
  22. "logAspectJ" class="test.spring.aop.LogAspectJ"/>  
  23.  
  24.    
  25.  
  26. "logaop" ref="logAspectJ">    
  27.  
  28. "logpointcut" expression="execution(*
  29.  test.spring.action.UserAction.*(..))"/>  
  30.  
  31. "logpointcut" method="myBeforeAdvice"/>  
  32.  
  33. "logpointcut"   
  34. method="myAfterReturningAdvice"/>  
  35.  
  36. "logpointcut"   
  37. method="myAfterThrowingAdvice"   
  38. throwing="e"/>  
  39.  
  40. "logpointcut" method="myAfterAdvice"/>  
  41.  
  42. "logpointcut" method="myAroundAdvice"/>  
  43.  

显然,Spring的AOP配置标签是放置于aop命名空间之下的,因此,事先应该在beans标签中导入AOP命名空间及其配套的schemaLocation。

最后编写UserAction类的Junit测试用例testUserAction:

 
 
  1. package test.spring.junit;  
  2. import org.junit.BeforeClass;  
  3. import org.junit.Test;  
  4. import org.springframework.context.ApplicationContext;  
  5. import org.springframework.context.support.  
  6. ClassPathXmlApplicationContext;  
  7. import test.spring.action.UserAction;  
  8. /** 用户管理业务控制器的测试用例 */ 
  9. public class testUserAction {  
  10. static ApplicationContext cxt;  
  11. static UserAction userAction;  
  12. //初始化ApplicationContext容器  
  13. @BeforeClass 
  14. public static void setUpBeforeClass() throws Exception {  
  15. //使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器  
  16. cxt = new ClassPathXmlApplicationContext("applicationContext.xml");  
  17. //从Bean工厂容器中获取名为"userAction"的UserAction实例  
  18. userAction = (UserAction)cxt.getBean("userAction");  
  19. }  
  20. //测试UserAction的AddUser方法  
  21. @Test 
  22. public void testAddUser() {  
  23. userAction.addUser();  
  24. }  
  25. //测试UserAction的DelUser方法  
  26. @Test 
  27. public void testDelUser() {  
  28. userAction.delUser();  
  29. }  
  30. //测试UserAction的LoadUser方法  
  31. @Test 
  32. public void testLoadUser() {  
  33. userAction.loadUser();  
  34. }  
  35. //测试UserAction的ModiUser方法  
  36. @Test 
  37. public void testModiUser() {  
  38. userAction.modiUser();  
  39. }  

测试用例的运行效果如图6-11所示。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值