spring aop

1.spring aop的相关概念

Aspect切面:横切多个对象。就是横插入系统的功能,例如日志、事务、安全验证等。

Joinpoint连接点:程序执行中某个特定的点,如某个业务方法。Spring AOP只支持在方法调用和异常抛出中插入方面代码。

Advice通知:在切面某个特定的连接点上执行的动作。是编写实际功能代码的地方。

  before:在方法前切入;

  after:在方法后切入,抛出异常时也会切入;

  after-returning:在方法返回后切入,抛出异常不会切入;

  after-throwing:在方法抛出异常时切入;

  Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行;

Pointcut切入点:Jointpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint;定义通知应该织入到哪些连接点上。通常切入点指的是类或方法名。例如某个通知要应用所有以abc开头的方法中,那么所有满足这个规则的方法都是切入点。

Introduction引用:在不修改类代码的前提下,为类添加新的方法和属性。

Taget目标对象:被一个切面或者多个切面所通知的对象。

Aop Proxy代理:aop是通过代理对象来访问目标对象,自然你在代理对象中就可以加入你插入的语句。

Weaving织入:在目标对象中插入方面代码的过程就叫做织入。

2.spring aop的xml配置

//将要作为切面的类

public class MoocAspect {
public void before() {
System.out.println("MoocAspect before.");
}
public void afterReturning() {
System.out.println("MoocAspect afterReturning.");
}
public void afterThrowing() {
System.out.println("MoocAspect afterThrowing.");
}
public void after() {
System.out.println("MoocAspect after.");
}

public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("MoocAspect around 1.");
obj = pjp.proceed();
System.out.println("MoocAspect around 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}

public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) {
System.out.println(bizName + "   " + times);
Object obj = null;
try {
System.out.println("MoocAspect aroundInit 1.");
obj = pjp.proceed();
System.out.println("MoocAspect aroundInit 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}

}


具体的业务逻辑类(目标类)

public class AspectBiz {

public void biz() {
System.out.println("AspectBiz biz.");
// throw new RuntimeException();
}

public void init(String bizName, int times) {
System.out.println("AspectBiz init : " + bizName + "   " + times);
}


}


xml中的配置将我的两个类加入ioc容器,第一个为切面类,第二个为目标类。

<bean id="moocAspect" class="com.imooc.aop.schema.advice.Aspect"></bean>
<bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean>

(1)Aspect切面

<aop:config>
<aop:aspect id="MoocAspect" ref="moocAspect "></aop:aspect>
</aop:config>


(2)切入点Pointcut xml配置

<aop:config>
      <aop:aspect id="MoocAspect" ref="moocAspect ">
             <aop:pointcut id="moocPiontcut" expression="com.imooc.aop.schema.advice.biz.AspectBiz.init() ">
      </aop:aspect>
</aop:config>


下面是附带匹配你的方法(连接点)的方式



(3)advice的xml配置

method:指你要执行的有效代码。pointcut-ref:指定你的切入点

<aop:config>
    <aop:aspect id="moocAspectAOP" ref="moocAspect">
        <aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))" id="moocPiontcut"/> 
        <aop:before method="before" pointcut-ref="moocPiontcut"/> 
        <aop:after-returning method="afterReturning" pointcut-ref="moocPiontcut"/> 
       <aop:after-throwing method="afterThrowing" pointcut-ref="moocPiontcut"/> 
       <aop:after method="after" pointcut-ref="moocPiontcut"/>
       <aop:around method="around" pointcut-ref="moocPiontcut"/> 
       <aop:around method="aroundInit" pointcut="execution(*com.imooc.aop.schema.advice.biz.AspectBiz.init(String, int)) and args(bizName, times)"/>
   </aop:aspect>
</aop:config>


3.spring aop api的介绍以及spring aop代理

(1)spring aop api

注意:以下接口方法中参数中的method表示处于切点的方法,args表示这些方法的参数值(可以在这儿改变参数值),target表示这些方法所在的类的对象实例。

实现前置通知的接口 MethodBeforeAdvice 此接口方法如下

  1. public void before(Method method, Object[] args, Object target)  
  2.             throws Throwable {  }//在此方法内进行日志..的记录

实现方法返回后通知  AfterReturningAdvice,此接口方法如下

  1. public void afterReturning(Object returnValue, Method method,  
  2.             Object[] args, Object target) throws Throwable {  }//在此方法内进行日志..的记录

实现环绕通知  MethodInterceptor,此接口方法如下

  1. public Object invoke(MethodInvocation invocation) throws Throwable { 
  2. invocation.proceed(); //调用被拦截的方法  
  3.  }
  4.           
异常通知ThrowsAdvice,此接口方法如下

  1. public void afterThrowing(Method method, Object[] args, Object target,  
  2.             Throwable exeptionClass) { }//在此方法内进行日志..的记录

(2)spring aop代理和PointcutAdvisorAdvisor

  AOP即Aspect-Oriented Programming,面向方面编程。AOP和OOP类似,也是一种编程模式。但是AOP并不能取代OOP,它只是对OOP的扩展和补充。Spring AOP是基于AOP编程模式的一个框架,它实现了AOP范围内的大多数功能,包括Advice、Pointcut等。

        AOP典型的应用场景:

http://pandonix.iteye.com/blog/336873 写道
1.对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况 
2.监控部分重要函数,若抛出指定的异常,需要以短信或邮件方式通知相关人员 
3.金控部分重要函数的执行时间 

事实上,以上需求没有AOP也能搞定,只是在实现过程中比较郁闷摆了。 

1.需要打印日志的函数分散在各个包中,只能找到所有的函数体,手动添加日志。然而这些日志都是临时的,待问题解决之后应该需要清除打印日志的代码,只能再次手动清除^_^! 

2.类似1的情况,需要捕获异常的地方太多,如果手动添加时想到很可能明天又要手动清除,只能再汗。OK,该需求相对比较固定,属于长期监控的范畴,并不需求临时添加后再清除。然而,客户某天要求,把其中20%的异常改为短信提醒,剩下的80%改用邮件提醒。改之,两天后,客户抱怨短信太多,全部改成邮件提醒... 

3.该需求通常用于监控某些函数的执行时间,用以判断系统执行慢的瓶颈所在。瓶颈被解决之后,烦恼同情况1

       其实说白了就是给代码解耦,如果要将日志的逻辑添加到要插入的函数内,以后要更改或者删除的话,因为违背设计模式的开闭原则,改代码对程序员来说会很累很痛苦。

OOP术语:

方面(Aspect):相当于OOP中的类,就是封装用于横插入系统的功能。例如日志、事务、安全验证等。

通知(Advice):相当于OOP中的方法,是编写实际功能代码的地方。

连接点(Joinpoint):程序执行过程中插入方面的地方。Spring AOP只支持在方法调用和异常抛出中插入方面代码。

切入点(Pointcut):定义通知应该织人到哪些连接点上。通常切入点指的是类或方法名。例如某个通知要应用所有以abc开头的方法中,那么所有满足这个规则的方法都是切入点。

目标(Target):目标类或者目标接口。

代理(Proxy):AOP工作时是通过代理对象来访问目标对象。其实AOP的实现是通过动态代理,离不开代理模式,所以必须要有一个代理对象。

织入(Weaving):在目标对象中插入方面代码的过程就叫做织入。

 

 

示例工程代码(用了很多《Struts 2 + Spring + Hibernate框架技术与项目实战》这本书的第21章的代码,但是也有所改动):

用MyEclipse建立的一个简单的web工程:

 前置通知:

Java代码   收藏代码
  1. package com.aop;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.apache.log4j.Logger;  
  6. import org.springframework.aop.MethodBeforeAdvice;  
  7.   
  8. public class BeforeLogAdvice implements MethodBeforeAdvice {  
  9.     private Logger logger = Logger.getLogger(BeforeLogAdvice.class);  
  10.   
  11.     public void before(Method method, Object[] args, Object target)  
  12.             throws Throwable {  
  13.         String targetClassName = target.getClass().getName();  
  14.         String targetMethodName = method.getName();  
  15.   
  16.         // args[0] = "Juve";//可以改变参数  
  17.   
  18.         String logInfoText = "前置通知:" + targetClassName + "类的"  
  19.                 + targetMethodName + "方法开始执行";  
  20.         logger.info(logInfoText);  
  21.     }  
  22. }  

 前置通知用于将方面代码插入方法之前,也就是说,在方法执行之前,会首先执行前置通知里的代码。包含前置通知代码的类就是方面,这个类需要实现org.springframework.aop.MethodBeforeAdvice接口。该接口中的before方法如上,参数中的method表示处于切点的方法,args表示这些方法的参数值(可以在这儿改变参数值),target表示这些方法所在的类的对象实例。

 

后置通知:

Java代码   收藏代码
  1. package com.aop;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.apache.log4j.Logger;  
  6. import org.springframework.aop.AfterReturningAdvice;  
  7.   
  8. public class AfterLogAdvice implements AfterReturningAdvice {  
  9.   
  10.     private Logger logger = Logger.getLogger(AfterLogAdvice.class);  
  11.   
  12.     public void afterReturning(Object returnValue, Method method,  
  13.             Object[] args, Object target) throws Throwable {  
  14.         // 获取被调用的类名  
  15.         String targetClassName = target.getClass().getName();  
  16.         // 获取被调用的方法名  
  17.         String targetMethodName = method.getName();  
  18.           
  19.         // 日志格式字符串  
  20.         String logInfoText = "后置通知:" + targetClassName + "类的"  
  21.                 + targetMethodName + "方法已经执行";  
  22.         // 将日志信息写入配置的文件中  
  23.         logger.info(logInfoText);  
  24.     }  
  25. }  

后置通知的代码在调用被拦截的方法后调用。注意afterReturning方法的第一参数returnValue表示被拦截的方法的返回值。

 

环绕通知:

Java代码   收藏代码
  1. package com.aop;  
  2.   
  3. import org.aopalliance.intercept.MethodInterceptor;  
  4. import org.aopalliance.intercept.MethodInvocation;  
  5. import org.apache.log4j.Logger;  
  6.   
  7. public class LogAroundAdvice implements MethodInterceptor {  
  8.   
  9.     private Logger logger = Logger.getLogger(LogAroundAdvice.class);  
  10.   
  11.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  12.           
  13.         // 获取被调用的方法名  
  14.         String targetMethodName = invocation.getMethod().getName();  
  15.           
  16.         /* 
  17.         invocation.getArguments()[0] = "Alex";//也可以得到或者改变要执行的方法的参数 
  18.          */  
  19.           
  20.         long beginTime = System.currentTimeMillis();  
  21.         invocation.proceed(); //调用被拦截的方法  
  22.         long endTime = System.currentTimeMillis();  
  23.           
  24.         // 日志格式字符串  
  25.         String logInfoText = "环绕通知:" + targetMethodName + "方法调用前时间" + beginTime  
  26.                 + "毫秒," + "调用后时间" + endTime + "毫秒";  
  27.         // 将日志信息写入配置的文件中  
  28.         logger.info(logInfoText);  
  29.           
  30.         //这儿相当于强行将拦截的方法的返回值设成了null  
  31.         return null;  
  32.         //return invocation.proceed();  
  33.     }  
  34. }  

 环绕通知能力最强,可以在方法调用前执行通知代码,可以决定是否还调用目标方法。也就是说它可以控制被拦截的方法的执行,还可以控制被拦截方法的返回值。

 

异常通知:

Java代码   收藏代码
  1. package com.aop;  
  2.   
  3. import org.apache.log4j.Logger;  
  4. import java.lang.reflect.Method;  
  5. import org.springframework.aop.ThrowsAdvice;  
  6.   
  7. public class ThrowsLogAdvice implements ThrowsAdvice {  
  8.     private Logger logger = Logger.getLogger(ThrowsLogAdvice.class);  
  9.   
  10.     public void afterThrowing(Method method, Object[] args, Object target,  
  11.             Throwable exeptionClass) {  
  12.         // 获取被调用的类名  
  13.         String targetClassName = target.getClass().getName();  
  14.         // 获取被调用的方法名  
  15.         String targetMethodName = method.getName();  
  16.         // 日志格式字符串  
  17.         String logInfoText = "异常通知:执行" + targetClassName + "类的"  
  18.                 + targetMethodName + "方法时发生异常";  
  19.         // 将日志信息写入配置的文件中  
  20.         logger.info(logInfoText);  
  21.     }  
  22.   
  23.     /* 
  24.     public void afterThrowing(IllegalArgumentException e){ 
  25.         System.out.println(e.getMessage()); 
  26.     } 
  27.     */  
  28. }  

如果在调用方法时发生异常,异常通知类可以提供一个机会来处理所发生的异常。org.springframework.aop.ThrowsAdvice接口中afterThrowing有两种重载形式:

Java代码   收藏代码
  1. public void afterThrowing(Throwable e);  
  2. public void afterThrowing(Method method, Object[] args, Object target, Throwable exeptionClass);  

第一种重载形式中,e表示方法抛出的异常类型。如果参数e的类型与异常类型不匹配,afterThrowing将不会被调用。如果这两种重载形式同时存在,并且都匹配抛出的异常类型,那么第一种重载形式优先调用。

 

目标接口IUserService和类UserService:

Java代码   收藏代码
  1. package com.service;  
  2.   
  3. public interface IUserService {  
  4.     public void addUser(String name, int age);  
  5.     public void deleteUser(String name);  
  6. }  

 

Java代码   收藏代码
  1. package com.service;  
  2.   
  3. public class UserService implements IUserService {  
  4.   
  5.     public void addUser(String name, int age) {  
  6.         //省略诸如操作数据库等复杂的逻辑操作  
  7.         System.out.println("add user "+ name +" successfully");  
  8.     }  
  9.   
  10.     public void deleteUser(String name) {  
  11.         //省略诸如操作数据库等复杂的逻辑操作  
  12.         System.out.println("deleted one user named " + name);  
  13.         throw new RuntimeException("这是特意抛出的异常信息!");  
  14.     }  
  15. }  

这个工程中用到了log4j,log4j.properties的配置文件内容就不贴出来了。别忘了在WEB-INF里的logs目录下新建几个log文件。

 

applicationContext.xml文件内容:

Xml代码   收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xmlns:p="http://www.springframework.org/schema/p"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
  7.       
  8.     <bean id="myUserService" class="com.service.UserService"></bean>  
  9.       
  10.     <!-- 定义前置通知 -->  
  11.     <bean id="beforeLogAdvice" class="com.aop.BeforeLogAdvice"></bean>  
  12.     <!-- 定义后置通知 -->  
  13.     <bean id="afterLogAdvice" class="com.aop.AfterLogAdvice"></bean>  
  14.     <!-- 定义异常通知 -->  
  15.     <bean id="throwsLogAdvice" class="com.aop.ThrowsLogAdvice"></bean>  
  16.     <!-- 定义环绕通知 -->  
  17.     <bean id="logAroundAdvice" class="com.aop.LogAroundAdvice"></bean>  
  18.       
  19.     <!-- 定义代理类,名 称为myProxy,将通过myProxy访问业务类中的方法 -->  
  20.     <bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">  
  21.       <property name="proxyInterfaces">  
  22.         <value>com.service.IUserService</value>  
  23.       </property>  
  24.       <property name="interceptorNames">  
  25.         <list>           
  26.          <value>beforeLogAdvice</value>  
  27.          <!-- 织入后置通知 -->  
  28.          <value>afterLogAdvice</value>  
  29.          <!-- 织入异常通知 -->  
  30.          <value>throwsLogAdvice</value>  
  31.          <!-- 织入环绕通知 -->  
  32.          <value>logAroundAdvice</value>  
  33.         </list>  
  34.       </property>  
  35.       <property name="target" ref="myUserService"></property>  
  36.     </bean>  
  37. </beans>  

 这里目标类和几个通知类的bean定义就不说了,重点看一下id为myProxy的ProxyFactoryBean的代理类的定义,它包含三个属性:

proxyInterface:代理所实现的接口。Spring AOP无法截获未在该属性指定的接口中的方法。

interceptorNames:用于拦截方法的拦截器名,通知类是其中一种类型,也可以是Advisor。

target:目标类。要注意的是一个代理只能有一个target。

 

主测试类:

Java代码   收藏代码
  1. package com.test;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.service.IUserService;  
  7.   
  8. public class MainTest {  
  9.     public static void main(String[] args) {  
  10.         ApplicationContext context = new ClassPathXmlApplicationContext(  
  11.                 "applicationContext.xml");  
  12.         IUserService userService = (IUserService) context.getBean("myProxy");  
  13.   
  14.         userService.addUser("ton"56);  
  15.         userService.deleteUser("ton");  
  16.     }  
  17. }  

 可以看到,这儿应用的bean是myProxy,而不是myUserService。运行结果如下:

Txt代码   收藏代码
  1. [INFO ] [22:08:13] com.aop.BeforeLogAdvice - 前置通知:com.service.UserService类的addUser方法开始执行  
  2. add user ton successfully  
  3. [INFO ] [22:08:13] com.aop.LogAroundAdvice - 环绕通知:addUser方法调用前时间1382969293109毫秒,调用后时间1382969293109毫秒  
  4. [INFO ] [22:08:13] com.aop.AfterLogAdvice - 后置通知:com.service.UserService类的addUser方法已经执行  
  5. [INFO ] [22:08:13] com.aop.BeforeLogAdvice - 前置通知:com.service.UserService类的deleteUser方法开始执行  
  6. deleted one user named ton  
  7. [INFO ] [22:08:13] com.aop.ThrowsLogAdvice - 异常通知:执行com.service.UserService类的deleteUser方法时发生异常  
  8. Exception in thread "main" java.lang.RuntimeException: 这是特意抛出的异常信息!  

 

 

/

注:还是这个工程,有没有办法让在取service的bean时可以不用代理呢,让AOP的通知在服务调用方毫不知情的下就进行织入呢。答案是使用BeanNameAutoProxyCreator。在applicationContext.xml声明如下bean:

Xml代码   收藏代码
  1. <bean id="myServiceAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
  2.         <property name="interceptorNames">  
  3.             <list>  
  4.                 <value>logAroundAdvice</value>  
  5.             </list>  
  6.         </property>  
  7.         <property name="beanNames">  
  8.             <value>*Service</value>  
  9.         </property>  
  10.     </bean>  

 这个BeanNameAutoProxyCreator的bean中指明上下文中所有调用以Service结尾的服务类都会被拦截,执行logAroundAdvice的invoke方法。同时它会自动生成Service的代理,这样在使用的时候就可以直接取服务类的bean,而不用再像上面那样还用取代理类的bean。

Java代码   收藏代码
  1. package com.test;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.service.IUserService;  
  7.   
  8. public class MainTest {  
  9.     public static void main(String[] args) {  
  10.         ApplicationContext context = new ClassPathXmlApplicationContext(  
  11.                 "applicationContext.xml");  
  12.         IUserService userService = (IUserService) context.getBean("myUserService");  
  13.   
  14.         userService.addUser("ton"56);  
  15.         userService.deleteUser("ton");  
  16.     }  
  17. }  

具体的例子以及详情查看   http://tonl.iteye.com/blog/1966075







  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒悟wjn

打赏可获取源码

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值