基于XML方式的AOP实现
在6.1.4节中,我们对面向切面编程AOP技术的实现原理及基本概念进行了深入剖析,本节将延续6.1.4节的话题,进一步探讨基于XML方式的AOP实现。在面向切面编程AOP技术中,切面的提取、通知的编写、切入点的定义、切面与目标对象的AOP装配是几个重要的环节。
所谓切面的提取就是综观Java EE应用,提取其中的所有共有功能(如日志、权限验证、事务管理等),并将其封装到一个类中,这个类就是切面(如日志切面、权限切面、事务切面等),去除了共有功能的业务逻辑代码将变得更加简洁优雅。
所谓通知的编写就是在切面中对共有功能的具体实现,一个通知往往对应的就是切面中的某个方法,然而根据这个通知欲切入到目标方法的位置不同可分为前置通知(在目标方法被调用之前执行)、后置通知(在目标方法被成功调用之后执行)、异常通知(在捕捉到目标方法抛出异常时执行)、最终通知(不管目标方法调用成功与否,均在调用完毕之后执行)、环绕通知(在目标方法被调用的前后均可进行操作)。在基于XML方式的AOP实现中,编写通知时,并不需要指明通知的类型,通知类型的设定是在Spring的XML配置文件中配置设定的。
所谓切入点的定义就是通过编写一个AOP正则表达式,指明欲拦截与插入通知的连接点。由于Spring的连接点仅支持方法级别,因此,本节中所讲的连接点实指目标对象中的某个方法。在定义AOP切入点时,通常使用execution切入点指示符设置连接点正则表达式,具体语法如下:
- execution(modifiers-pattern? ret-type-pattern
- declaring-type-pattern? name-pattern(param-pattern)
- 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参数,通过该参数可以取得目标对象的类名、目标方法名称及目标方法的参数等:
- package test.spring.aop;
- import org.apache.log4j.Logger;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- /** 日志切面 */
- public class LogAspectJ {
- // 取得日志记录器Logger
- public Logger logger = Logger.getLogger(LogAspectJ.class);
- //此方法将用做前置通知
- public void myBeforeAdvice(JoinPoint joinpoint) {
- String classAndMethod = joinpoint.getTarget().getClass().getName()
- +"类的"+joinpoint.getSignature().getName();
- logger.info("前置通知:"+classAndMethod+"方法开始执行!");
- }
- //此方法将用做后置通知
- public void myAfterReturningAdvice(JoinPoint joinpoint) {
- String classAndMethod = joinpoint.getTarget().getClass().getName()
- +"类的"+joinpoint.getSignature().getName();
- logger.info("后置通知:"+classAndMethod+"方法执行正常结束!");
- }
- //此方法将用做异常通知
- public void myAfterThrowingAdvice(JoinPoint joinpoint,Exception e) {
- String classAndMethod = joinpoint.getTarget().getClass().getName()+
- "类的"+joinpoint.getSignature().getName();
- logger.info("异常通知:"+classAndMethod+"方法抛出异常:
- "+e.getMessage());
- }
- //此方法将用做最终通知
- public void myAfterAdvice(JoinPoint joinpoint) {
- String classAndMethod = joinpoint.getTarget().getClass().getName()+
- "类的"+joinpoint.getSignature().getName();
- logger.info("最终通知:"+classAndMethod+"方法执行结束!");
- }
- //此方法将用做环绕通知
- public Object myAroundAdvice(ProceedingJoinPoint pjp)
- throws Throwable {
- long begintime = System.currentTimeMillis();//记下开始时间
- //传递给连接点对象进行接力处理
- Object result=pjp.proceed();
- long endtime = System.currentTimeMillis();//记下结束时间
- String classAndMethod = pjp.getTarget().getClass().getName()+
- "类的"+pjp.getSignature().getName();
- logger.info("环绕通知:"+classAndMethod+"方法执行结束,
- 耗时"+(endtime-begintime)+"毫秒!");
- return result;
- }
- }
接下来编写一个业务控制组件UserAction,我们打算将切面应用到该组件上,对该组件的所有方法均予以拦截并切入所有通知,为了演示异常通知的执行效果,特意在addUser方法的尾部人为抛出一个RuntimeException异常:
- package test.spring.action;
- import test.spring.bean.User;
- import test.spring.service.UserService;
- /** 用户管理业务控制器 */
- public class UserAction {
- //仅声明业务逻辑组件的引用
- UserService service;
- //处理新增用户请求
- public void addUser() {
- String userName="liubin";
- String userPwd="123456";
- service.addUser(userName,userPwd);
- throw new RuntimeException("这是特意抛出的异常信息!");
- }
- //处理删除用户请求
- public void delUser() {
- service.delUser(1);
- }
- //处理装载用户请求
- public void loadUser() {
- User user = service.loadUser(1);
- System.out.println("用户名="+user.getUserName());
- }
- //处理修改用户请求
- public void modiUser() {
- Integer id = 1;
- String userName="liujunyu";
- String userPwd="123456";
- service.modiUser(id, userName, userPwd);
- }
- //提供一条UserService对象的注入通道
- public void setService(UserService service) {
- this.service = service;
- }
- }
日志切面LogAspectJ与业务控制组件UserAction原本是两个互不相干的类,通过在Spring配置文件中进行AOP装配之后,奇妙的事情就会出现了,LogAspectJ中的通知将会巧妙地切入到UserAction中,以实现预期的日志记录功能:
- "1.0" encoding="UTF-8"?>
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
- "dao" class="test.spring.dao.impl.UserDaoImpl"/>
- "service" class="test.spring.service.impl.UserServiceImpl">
- "dao" ref="dao"/>
- "userAction" class="test.spring.action.UserAction">
- "service" ref="service" />
- "logAspectJ" class="test.spring.aop.LogAspectJ"/>
- "logaop" ref="logAspectJ">
- "logpointcut" expression="execution(*
- test.spring.action.UserAction.*(..))"/>
- "logpointcut" method="myBeforeAdvice"/>
- "logpointcut"
- method="myAfterReturningAdvice"/>
- "logpointcut"
- method="myAfterThrowingAdvice"
- throwing="e"/>
- "logpointcut" method="myAfterAdvice"/>
- "logpointcut" method="myAroundAdvice"/>
显然,Spring的AOP配置标签是放置于aop命名空间之下的,因此,事先应该在beans标签中导入AOP命名空间及其配套的schemaLocation。
最后编写UserAction类的Junit测试用例testUserAction:
- package test.spring.junit;
- import org.junit.BeforeClass;
- import org.junit.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.
- ClassPathXmlApplicationContext;
- import test.spring.action.UserAction;
- /** 用户管理业务控制器的测试用例 */
- public class testUserAction {
- static ApplicationContext cxt;
- static UserAction userAction;
- //初始化ApplicationContext容器
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- //使用ClassPathXmlApplicationContext方式初始化ApplicationContext容器
- cxt = new ClassPathXmlApplicationContext("applicationContext.xml");
- //从Bean工厂容器中获取名为"userAction"的UserAction实例
- userAction = (UserAction)cxt.getBean("userAction");
- }
- //测试UserAction的AddUser方法
- @Test
- public void testAddUser() {
- userAction.addUser();
- }
- //测试UserAction的DelUser方法
- @Test
- public void testDelUser() {
- userAction.delUser();
- }
- //测试UserAction的LoadUser方法
- @Test
- public void testLoadUser() {
- userAction.loadUser();
- }
- //测试UserAction的ModiUser方法
- @Test
- public void testModiUser() {
- userAction.modiUser();
- }
- }
测试用例的运行效果如图6-11所示。
![]() |