【学习】Spring 面向切向编程(AOP)

1、业务需求: 因为最近在研究审批流的实现,所以在每次save或update的时候,就要走审批流的处理,在每个service中加入审批流的处理方法,显然不大合适,所以由此想到用AOP切面技术来实现。

2、本文环境是struts2 + spring 2.5

3、就像其它的流行技术一样,有注解和XML两种实现方式,aop的实现,也可以通过这两种方式实现,本文用XML的方式开发。

4、先来看看AOP的XML配置:
<!-- 配置日志切面 -->
<bean id="diaryAspect" class="com.testPro.aop.DiaryAspect"></bean>
<aop:config>
	<!-- 声明一个切面 -->
	<aop:aspect id="myDiaryAspect" ref="diaryAspect">
		<aop:pointcut id="Operation"
				expression="(execution(* com.testPro.*.service.*.save*(..))) or (execution(* com.weboa.*.service.*.update*(..)))" />
		<aop:before pointcut-ref="Operation" method="beforeMethod" />
		<aop:after pointcut-ref="Operation" method="afterMethod" />
		<aop:after-returning pointcut-ref="Operation" returning="result" method="afterReturning" />
		<aop:after-throwing pointcut-ref="Operation" method="throwException" />
	</aop:aspect>
</aop:config>

解释:
4.1 首先是方法切点函数: expression,可用通配符进行方法的配置,语法如下:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
这么说很抽象,举几个实例:
a、execution(* com.baobaotao.*(..)):匹配com.baobaotao包下所有类的所有方法;
b、execution(* com..*.*DAO.find*(..)):匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。
关于切点函数的通配,网上有很多总结,这里不再举例。

补充:切点函数支持复合运算符,如我上面的XML配置,就是匹配所有以save或update开头的方法名。

4.2 Aspectj中的增强类型:基本类型有6个。

类型 解释 成员
Before 增置增强 value,argNames
AfterReturning 后置增强 value,pointcut,returning,artNames
Around 环绕增强 value,argName
AfterThrowing 抛出增强 value,pointcut,throwing,argNames
After Final增强,不管是抛出异常或者是正常退出,该增强都会执行 value,argName
DeclareParents 引介增强 value,defaultImpl


4.3 我上面的XML中定义了before,after,afterreturning和afterthrowing的增强(当然直接在切面的实现类里用ANNOTATION配置也是可以的。)
即标签:<aop:before/><aop:after/><aop:after-returning/><aop:after-throwing />
其中属性pointcut-ref是指定义的切面bean的ID,method是指具体定义的切面实体类中的方法。标签中<aop:after-returning/>的returning是指方法返回值。具体和实现类中的参数对应。

5、Aspect实现类(核心)
package com.testPro.aop;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.struts2.ServletActionContext;
import org.aspectj.lang.JoinPoint;

import com.opensymphony.xwork2.ActionContext;

/**
 * 日志切面类
 */
public class DiaryAspect {
	// 方法调用前执行
	public void beforeMethod(JoinPoint point) {
		Object[] args = point.getArgs();
		for(Object arg : args){
			System.out.println("当前连接点方法运行时的入参列表:" + arg);
		}
		System.out.println("当前执行方法:"+ point.getSignature().getName());
		System.out.println("获取连接点所在的目标对象:"+ point.getTarget().getClass().getName());
		System.out.println("获取代理本身:" + point.getThis().getClass().getName());
	}

	// 方法调用结束后执行
	public void afterMethod(JoinPoint point) {
		System.out.println("final增强");
	}
	
	public void afterReturning(JoinPoint point,int result){  
		//得到方法名
		String className = point.getTarget().getClass().getName();
		
		//得到SESSION
		HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);  
		HttpSession session = request.getSession();
		int userid = Integer.parseInt(session.getAttribute("userid").toString());
		
		System.out.println("后置增强类:目标对象方法返回值:"
				+ result);
    }

	// 抛出异常后执行
	public void throwException(JoinPoint point) {
		System.out.println("抛出异常后执行" );
	}
}

解释:
5.1 因为我实现的是审批流的切面,所以重点业务都放在了后置增强类中。我一开始的时候是放在after中来实现的,后来因为需要用到目标对象方法的返回值,所以就重点写了afterreturning方法。

5.2 关于AOP中的session,因为我用的是STRUTS2的框架,所以用上下文ActionContext直接得到了session。

5.3 连接点信息类:JoinPoint。它包含了很多的信息,包括能得到连接点方法运行时的入参列表,方法签名对象,连接点所在的目标对象,代理对象本身等。
java.lang.Object[] getArgs() 获取连接点方法运行时的入参列表
Signature getSignature() 获取连接点的方法签名对象
java.lang.Object getTarget() 获取连接点所在的目标对象
java.lang.Object getThis() 获取代理对象本身

关于以上四点,我在类TestImpl的saveTest方法中测试,在beforeMethod方法中输出:
当前连接点方法运行时的入参列表:19
当前连接点方法运行时的入参列表:1
当前执行方法:save
获取连接点所在的目标对象:com.testPro.temp.service.impl.TestImpl
获取代理本身:com.testPro.temp.service.impl.TestImpl$$EnhancerByCGLIB$$c7579c95

-----------------------------------------------
【值得注意的地方】
我在类TestImpl的saveTest方法中实现对POJO类Test的insert操作,然后本来的想法是在切面中获取saveTest相当参数,进行反射后,在具体的审批流中对Test实体类进行再次的查询和修改。

但这一思路并没有成功。 主要原因是在service层的save方法中save的Test实体类,在做切面的时候,并没有实际的插入到数据库中,所以我在用select语句去数据库查找时,得不到该信息。主要也是因为事务没有在service的save方法后进行提交,在切面中再生成一个,而是用了同一个事务。即:REQUIRED(如果有事务,那么加入事务,没有的话新创建一个)。

后来觉得切面中也没必要再对save中的实体进行再操作,所以将实体类的操作统一放到service中实现,切面中专心对审批流进行操作。这样的流程就走通了。


-----------------------------------------------
【参考】
http://blog.csdn.net/sin90lzc/article/details/7486145
http://aopalliance.sourceforge.net
《Spring 3.x企业应用开发实战》第七章 基于@AspectJ和Schema的AOP



猜你喜欢

转载自angelbill3.iteye.com/blog/2083348