一:spring mvc controller层aop
aop类
@Component @Aspect public class PermissionUtil { private Logger Log = LoggerFactory.getLogger(PermissionUtil.class); @Around("execution(public * com.chinacache.controller.*.*(..))") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Log.info("start aop ..."); Object retVal = pjp.proceed(); Log.info("end aop ..."); return retVal; } }
将会拦截所有controller包下的方法
转自:http://pandonix.iteye.com/blog/336873/
此前对于AOP的使用仅限于声明式事务,除此之外在实际开发中也没有遇到过与之相关的问题。最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP 来解决。一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容。本文是权当本人的自己AOP学习笔记,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智。
- 对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况
- 监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员
- 金控部分重要函数的执行时间
事实上,以上需求没有AOP也能搞定,只是在实现过程中比较郁闷摆了。
- 需要打印日志的函数分散在各个包中,只能找到所有的函数体,手动添加日志。然而这些日志都是临时的,待问题解决之后应该需要清除打印日志的代码,只能再次手动清除^_^!
- 类 似1的情况,需要捕获异常的地方太多,如果手动添加时想到很可能明天又要手动清除,只能再汗。OK,该需求相对比较固定,属于长期监控的范畴,并不需求临 时添加后再清除。然而,客户某天要求,把其中20%的异常改为短信提醒,剩下的80%改用邮件提醒。改之,两天后,客户抱怨短信太多,全部改成邮件提 醒...
- 该需求通常用于监控某些函数的执行时间,用以判断系统执行慢的瓶颈所在。瓶颈被解决之后,烦恼同情况1
终于下定决心,采用AOP来解决!代码如下:
切面类TestAspect
- package com.spring.aop;
- /**
- * 切面
- *
- */
- public class TestAspect {
- public void doAfter(JoinPoint jp) {
- System.out.println("log Ending method: "
- + jp.getTarget().getClass().getName() + "."
- + jp.getSignature().getName());
- }
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
- long time = System.currentTimeMillis();
- Object retVal = pjp.proceed();
- time = System.currentTimeMillis() - time;
- System.out.println("process time: " + time + " ms");
- return retVal;
- }
- public void doBefore(JoinPoint jp) {
- System.out.println("log Begining method: "
- + jp.getTarget().getClass().getName() + "."
- + jp.getSignature().getName());
- }
- public void doThrowing(JoinPoint jp, Throwable ex) {
- System.out.println("method " + jp.getTarget().getClass().getName()
- + "." + jp.getSignature().getName() + " throw exception");
- System.out.println(ex.getMessage());
- }
- private void sendEx(String ex) {
- //TODO 发送短信或邮件提醒
- }
- }
- package com.spring.service;
- /**
- * 接口A
- */
- public interface AService {
- public void fooA(String _msg);
- public void barA();
- }
- package com.spring.service;
- /**
- *接口A的实现类
- */
- public class AServiceImpl implements AService {
- public void barA() {
- System.out.println("AServiceImpl.barA()");
- }
- public void fooA(String _msg) {
- System.out.println("AServiceImpl.fooA(msg:"+_msg+")");
- }
- }
- package com.spring.service;
- /**
- * Service类B
- */
- public class BServiceImpl {
- public void barB(String _msg, int _type) {
- System.out.println("BServiceImpl.barB(msg:"+_msg+" type:"+_type+")");
- if(_type == 1)
- throw new IllegalArgumentException("测试异常");
- }
- public void fooB() {
- System.out.println("BServiceImpl.fooB()");
- }
- }
ApplicationContext
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
- default-autowire="autodetect">
- <aop:config>
- <aop:aspect id="TestAspect" ref="aspectBean">
- <!--配置com.spring.service包下所有类或接口的所有方法-->
- <aop:pointcut id="businessService"
- expression="execution(* com.spring.service.*.*(..))" />
- <aop:before pointcut-ref="businessService" method="doBefore"/>
- <aop:after pointcut-ref="businessService" method="doAfter"/>
- <aop:around pointcut-ref="businessService" method="doAround"/>
- <aop:after-throwing pointcut-ref="businessService" method="doThrowing" throwing="ex"/>
- </aop:aspect>
- </aop:config>
- <bean id="aspectBean" class="com.spring.aop.TestAspect" />
- <bean id="aService" class="com.spring.service.AServiceImpl"></bean>
- <bean id="bService" class="com.spring.service.BServiceImpl"></bean>
- </beans>
测试类AOPTest
- public class AOPTest extends AbstractDependencyInjectionSpringContextTests {
- private AService aService;
- private BServiceImpl bService;
- protected String[] getConfigLocations() {
- String[] configs = new String[] { "/applicationContext.xml"};
- return configs;
- }
- /**
- * 测试正常调用
- */
- public void testCall()
- {
- System.out.println("SpringTest JUnit test");
- aService.fooA("JUnit test fooA");
- aService.barA();
- bService.fooB();
- bService.barB("JUnit test barB",0);
- }
- /**
- * 测试After-Throwing
- */
- public void testThrow()
- {
- try {
- bService.barB("JUnit call barB",1);
- } catch (IllegalArgumentException e) {
- }
- }
- public void setAService(AService service) {
- aService = service;
- }
- public void setBService(BServiceImpl service) {
- bService = service;
- }
- }
运行结果如下:
- log Begining method: com.spring.service.AServiceImpl.fooA
- AServiceImpl.fooA(msg:JUnit test fooA)
- log Ending method: com.spring.service.AServiceImpl.fooA
- process time: 0 ms
- log Begining method: com.spring.service.AServiceImpl.barA
- AServiceImpl.barA()
- log Ending method: com.spring.service.AServiceImpl.barA
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.fooB
- BServiceImpl.fooB()
- log Ending method: com.spring.service.BServiceImpl.fooB
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.barB
- BServiceImpl.barB(msg:JUnit test barB type:0)
- log Ending method: com.spring.service.BServiceImpl.barB
- process time: 0 ms
- log Begining method: com.spring.service.BServiceImpl.barB
- BServiceImpl.barB(msg:JUnit call barB type:1)
- log Ending method: com.spring.service.BServiceImpl.barB
- method com.spring.service.BServiceImpl.barB throw exception
- 测试异常
《Spring参考手册》中定义了以下几个AOP的重要概念,结合以上代码分析如下:
- 切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
- 连接点(Joinpoint) :程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。
- 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect
- 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定
- 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
- AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将
<aop:config>
的proxy-target-class
属性设为true
通知(Advice)类型
- 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法
- 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行
- 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
- 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,TestAspect中的doAround方法。
- 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。 ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,TestAspect中的doThrowing方法。
切入点表达式
- 通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
- execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
- 通知参数
可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下
- <aop:config>
- <aop:aspect id="TestAspect" ref="aspectBean">
- <aop:pointcut id="businessService"
- expression="execution(* com.spring.service.*.*(String,..)) and args(msg,..)" />
- <aop:after pointcut-ref="businessService" method="doAfter"/>
- </aop:aspect>
- </aop:config>
TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(* com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:
- public void doAfter(JoinPoint jp,String msg)
- 访问当前的连接点
任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint
类型。JoinPoint
接口提供了一系列有用的方法, 比如 getArgs()
(返回方法参数)、getThis()
(返回代理对象)、getTarget()
(返回目标)、getSignature()
(返回正在被通知的方法相关信息)和 toString()
(打印出正在被通知的方法的有用信息。
http://blog.csdn.net/csh624366188/article/details/7651702
首先我们来看一下官方文档所给我们的关于AOP的一些概念性词语的解释:
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于Aspect注解方式来实现。通俗点说就是我们加入的切面类(比如log类),可以这么理解。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。通俗的说就是加入切点的那个点
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
通知类型:
前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
spring AOP的实现
在spring2.5中,常用的AOP实现方式有两种。第一种是基于xml配置文件方式的实现,第二种是基于注解方式的实现。接下来,以具体的示例来讲解这两种方式的使用。下面我们要用到的实例是一个注册,就有用户名和密码,我们利用AOP来实现在用户注册的时候实现在保存数据之前和之后或者是抛出异常时,在这些情况下都给他加上日志。在这里我们只讲解AOP,所以我只把关键代码贴出来,不相干的就不贴了。
首先我们来看一下业务逻辑service层:
- /**
- * RegisterService的实现类
- * @author 曹胜欢 */
- public class RegisterServiceImpl implements RegisterService {
- private RegisterDao registerDao;
- public RegisterServiceImpl() {}
- /** 带参数的构造方法 */
- public RegisterServiceImpl(RegisterDao registerDao){
- this.registerDao =registerDao;
- }
- public void save(String loginname, String password) {
- registerDao.save(loginname, password);
- throw new RuntimeException("故意抛出一个异常。。。。");
- }
- /** set方法 */
- public void setRegisterDao(RegisterDao registerDao) {
- this.registerDao = registerDao;
- }}
对于业务系统来说,RegisterServiceImpl类就是目标实现类,它的业务方法,如save()方法的前后或代码会出现异常的地方都是AOP的连接点。
下面是日志服务类的代码:
- /**
- * 日志切面类
- * @author 曹胜欢
- */
- public class LogAspect {
- //任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
- public void before(JoinPoint call) {
- //获取目标对象对应的类名
- String className = call.getTarget().getClass().getName();
- //获取目标对象上正在执行的方法名
- String methodName = call.getSignature().getName();
- System.out.println("前置通知:" + className + "类的" + methodName + "方法开始了");
- }
- public void afterReturn() {
- System.out.println("后置通知:方法正常结束了");
- }
- public void after(){
- System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的");
- }
- public void afterThrowing() {
- System.out.println("异常抛出后通知:方法执行时出异常了");
- }
- //用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
- public Object doAround(ProceedingJoinPoint call) throws Throwable {
- Object result = null;
- this.before(call);//相当于前置通知
- try {
- result = call.proceed();
- this.afterReturn(); //相当于后置通知
- } catch (Throwable e) {
- this.afterThrowing(); //相当于异常抛出后通知
- throw e;
- }finally{
- this.after(); //相当于最终通知
- }
- return result;
- }
- }
这个类属于业务服务类,如果用AOP的术语来说,它就是一个切面类,它定义了许多通知。Before()、afterReturn()、after()和afterThrowing()这些方法都是通知。
下面我们就来看具体配置,首先来看一下:
<1>.基于xml配置文件的AOP实现:这种方式在实现AOP时,有4个步骤。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- 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>
- <bean id="registerDaoImpl" class="com.zxf.dao.RegisterDaoImpl"/>
- <bean id="registerService" class="com.zxf.service.RegisterServiceImpl">
- <property name=" registerDaoImpl " ref=" RegisterDaoImpl "/>
- </bean>
- <!-- 日志切面类 -->
- <bean id="logAspectBean" class="com.zxf.aspect.LogAspect"/>
- <!-- 第1步: AOP的配置 -->
- <aop:config>
- <!-- 第2步:配置一个切面 -->
- <aop:aspect id="logAspect" ref="logAspectBean">
- <!-- 第3步:定义切入点,指定切入点表达式 -->
- <aop:pointcut id="allMethod"
- expression="execution(* com.zxf.service.*.*(..))"/>
- <!-- 第4步:应用前置通知 -->
- <aop:before method="before" pointcut-ref="allMethod" />
- <!-- 第4步:应用后置通知 -->
- <aop:after-returning method="afterReturn" pointcut-ref="allMethod"/>
- <!-- 第4步:应用最终通知 -->
- <aop:after method="after" pointcut-ref="allMethod"/>
- <!-- 第4步:应用抛出异常后通知 -->
- <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/>
- <!-- 第4步:应用环绕通知 -->
- <!--
- <aop:around method="doAround" pointcut-ref="allMethod" />
- -->
- </aop:aspect>
- </aop:config>
- </beans>
上述配置针对切入点应用了前置、后置、最终,以及抛出异常后通知。这样在测试执行RegisterServiceImpl类的save()方法时,控制台会有如下结果输出:
前置通知:com.zxf.service.RegisterServiceImpl类的save方法开始了。
针对MySQL的RegisterDao实现中的save()方法。
后置通知:方法正常结束了。
最终通知:不管方法有没有正常执行完成,一定会返回的。
下面我们在来看一下第二种配置方式:
<2>基于注解的AOP的实现
首先创建一个用来作为切面的类LogAnnotationAspect,同时把这个类配置在spring的配置文件中。
在spring2.0以后引入了JDK5.0的注解Annotation的支持,提供了对AspectJ基于注解的切面的支持,从而 更进一步地简化AOP的配置。具体的步骤有两步。
Spring的配置文件是如下的配置:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- 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>
- <bean id="registerDao" class="com.zxf.dao.RegisterDaoImpl"/>
- <bean id="registerService" class="com.zxf.service.RegisterServiceImpl">
- <property name="registerDao" ref="registerDao"/>
- </bean>
- <!-- 把切面类交由Spring容器来管理 -->
- <bean id="logAspectBean" class="com.zxf.aspect.LogAnnotationAspect"/>
- <!-- 启用spring对AspectJ注解的支持 -->
- <aop:aspectj-autoproxy/>
- </beans>
这是那个切面的类LogAnnotationAspect
- /**
- * 日志切面类
- */
- @Aspect //定义切面类
- public class LogAnnotationAspect {
- @SuppressWarnings("unused")
- //定义切入点,提供一个方法,这个方法的名字就是改切入点的id
- @Pointcut("execution(* com.zxf.service.*.*(..))")
- private void allMethod(){}
- //针对指定的切入点表达式选择的切入点应用前置通知
- @Before("execution(* com. zxf.service.*.*(..))")
- public void before(JoinPoint call) {
- String className = call.getTarget().getClass().getName();
- String methodName = call.getSignature().getName();
- System.out.println("【注解-前置通知】:" + className + "类的"
- + methodName + "方法开始了");
- }
- //访问命名切入点来应用后置通知
- @AfterReturning("allMethod()")
- public void afterReturn() {
- System.out.println("【注解-后置通知】:方法正常结束了");
- }
- //应用最终通知
- @After("allMethod()")
- public void after(){
- System.out.println("【注解-最终通知】:不管方法有没有正常执行完成,"
- + "一定会返回的");
- }
- //应用异常抛出后通知
- @AfterThrowing("allMethod()")
- public void afterThrowing() {
- System.out.println("【注解-异常抛出后通知】:方法执行时出异常了");
- }
- //应用周围通知
- //@Around("allMethod()")
- public Object doAround(ProceedingJoinPoint call) throws Throwable{
- Object result = null;
- this.before(call);//相当于前置通知
- try {
- result = call.proceed();
- this.afterReturn(); //相当于后置通知
- } catch (Throwable e) {
- this.afterThrowing(); //相当于异常抛出后通知
- throw e;
- }finally{
- this.after(); //相当于最终通知
- }
- return result;
- }
- }
备注:输出结果和前面的一样。
http://blog.csdn.net/a906998248/article/details/7514969
一、什么是 AOP。
AOP(Aspect Orient Programming),也就是面向切面编程。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程。
二、AOP 的作用。
常常通过 AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。
三、AOP 的实现原理。
如图:AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
四、Spring 中对 AOP 的支持
Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。Spring 默认使用 Java 动态代理来创建 AOP 代理, 这样就可以为任何接口实例创建代理了。当需要代理的类不是代理接口的时候, Spring 自动会切换为使用 CGLIB 代理,也可强制使用 CGLIB。
AOP 编程其实是很简单的事情。纵观 AOP 编程, 其中需要程序员参与的只有三个部分:
- 定义普通业务组件。
- 定义切入点,一个切入点可能横切多个业务组件。
- 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。
所以进行 AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法。
五、Spring 中 AOP 的实现。
Spring 有如下两种选择来定义切入点和增强处理。
- 基于 Annotation 的“零配置”方式:使用@Aspect、@Pointcut等 Annotation 来标注切入点和增强处理。
- 基于 XML 配置文件的管理方式:使用 Spring 配置文件来定义切入点和增强点。
1、基于 Annotation 的“零配置”方式。
(1)、首先启用 Spring 对 @AspectJ 切面配置的支持。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <!-- 启动对@AspectJ注解的支持 -->
- <aop:aspectj-autoproxy/>
- </beans>
如果不打算使用 Spring 的 XML Schema 配置方式,则应该在 Spring 配置文件中增加如下片段来启用@AspectJ 支持。
- <!-- 启用@AspectJ 支持 -->
- <bean class="org.springframeword.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
(2)、定义切面 Bean。
当启动了@AspectJ 支持后,只要在 Spring 容器中配置一个带@Aspect 注释的 Bean, Spring 将会自动识别该 Bean 并作为切面处理。
- // 使用@Aspect 定义一个切面类
- @Aspect
- public class LogAspect {
- // 定义该类的其他内容
- ...
- }
(3)、定义 Before 增强处理。
- // 定义一个切面
- @Aspect
- public class BeforeAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @Before("execution(* com.wicresoft.app.service.impl.*.*(..))")
- public void authorith(){
- System.out.println("模拟进行权限检查。");
- }
- }
上面使用@Before Annotation 时,直接指定了切入点表达式,指定匹配 com.wicresoft.app.service.impl包下所有类的所有方法执行作为切入点。
关于这个表达式的规则如下图。
(4)、定义 AfterReturning 增强处理。
- // 定义一个切面
- @Aspect
- public class AfterReturningAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @AfterReturning(returning="rvt", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
- public void log(Object rvt) {
- System.out.println("模拟目标方法返回值:" + rvt);
- System.out.println("模拟记录日志功能...");
- }
- }
(5)、定义 AfterThrowing 增强处理。
- // 定义一个切面
- @Aspect
- public class AfterThrowingAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @AfterThrowing(throwing="ex", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
- public void doRecoverActions(Throwable ex) {
- System.out.println("目标方法中抛出的异常:" + ex);
- System.out.println("模拟抛出异常后的增强处理...");
- }
- }
(6)、定义 After 增强处理。
After 增强处理与AfterReturning 增强处理有点相似,但也有区别:
- AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入。
- After 增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入。
- // 定义一个切面
- @Aspect
- public class AfterAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @After("execution(* com.wicresoft.app.service.impl.*.*(..))")
- public void release() {
- System.out.println("模拟方法结束后的释放资源...");
- }
- }
(7)、Around 增强处理
Around 增强处理近似等于 Before 增强处理和 AfterReturning 增强处理的总和。它可改变执行目标方法的参数值,也可改变目标方法之后的返回值。
- // 定义一个切面
- @Aspect
- public class AroundAdviceTest {
- // 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点
- @Around("execution(* com.wicresoft.app.service.impl.*.*(..))")
- public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable {
- System.out.println("执行目标方法之前,模拟开始事物...");
- // 执行目标方法,并保存目标方法执行后的返回值
- Object rvt = jp.proceed(new String[]{"被改变的参数"});
- System.out.println("执行目标方法之前,模拟结束事物...");
- return rvt + "新增的内容";
- }
- }
(8)、访问目标方法的参数。
访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为 JoinPoint 类型,当该增强处理方法被调用时,该 JoinPoint 参数就代表了织入增强处理的连接点。JoinPoint 里包含了如下几个常用方法。
- Object[] getArgs(): 返回执行目标方法时的参数。
- Signature getSignature(): 返回被增强的方法的相关信息。
- Object getTarget(): 返回被织入增强处理的目标对象。
- Object getThis(): 返回 AOP 框架为目标对象生成的代理对象。
提示:当时使用 Around 处理时,我们需要将第一个参数定义为 ProceedingJoinPoint 类型,该类型是 JoinPoint 类型的子类。
(9)、定义切入点。
所谓切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。
Spring 切入点定义包含两个部分:
- 一个切入点表达式。
- 一个包含名字和任意参数的方法签名。
- // 使用@Pointcut Annotation 时指定切入点表达式
- @pointcut("execution * transfer(..)")
- // 使用一个返回值为void,方法体为空的方法来命名切入点
- private void anyOldTransfer(){}
- // 使用上面定义的切入点
- @AfterReturning(pointcut="anyOldTransfer()", returning="reVal")
- public void writeLog(String msg, Object reVal){
- ...
- }
2、基于 XML 配置文件的管理方式。
- 不配置切入点
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <aop:config>
- <!-- 将 fourAdviceBean 转换成切面 Bean, 切面 Bean 的新名称为:fourAdviceAspect,指定该切面的优先级为2 -->
- <aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">
- <!-- 定义个After增强处理,直接指定切入点表达式,以切面 Bean 中的 Release() 方法作为增强处理方法 -->
- <aop:after pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" />
- <!-- 定义个Before增强处理,直接指定切入点表达式,以切面 Bean 中的 authority() 方法作为增强处理方法 -->
- <aop:before pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="authority" />
- <!-- 定义个AfterReturning增强处理,直接指定切入点表达式,以切面 Bean 中的 log() 方法作为增强处理方法 -->
- <aop:after-returning pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="log" />
- <!-- 定义个Around增强处理,直接指定切入点表达式,以切面 Bean 中的 processTx() 方法作为增强处理方法 -->
- <aop:around pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="processTx" />
- </aop:aspect>
- </aop:config>
- <!-- 省略各个Bean 的配置 -->
- <!-- ... -->
- </beans>
- 配置切入点
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">
- <aop:config>
- <!-- 定义一个切入点,myPointcut,直接知道它对应的切入点表达式 -->
- <aop:pointcut id="myPointcut" expression="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" />
- <aop:aspect id="afterThrowingAdviceAspect" ref="afterThrowingAdviceBean" order="1">
- <!-- 使用上面定于切入点定义增强处理 -->
- <!-- 定义一个AfterThrowing 增强处理,指定切入点以切面 Bean 中的 doRecovertyActions() 方法作为增强处理方法 -->
- <aop:after-throwing pointcut-ref="myPointcut" method="doRecovertyActions" throwing="ex" />
- </aop:aspect>
- </aop:config>
- <!-- 省略各个Bean 的配置 -->
- <!-- ... -->
- </beans>
参考:
《轻量级 Java EE 企业应用实战(第三版)》 李刚