Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1029
  • 博文数量: 1
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2019-09-28 19:25
文章分类
文章存档

2019年(1)

我的朋友
最近访客

分类: Java

2019-09-28 19:27:12

原文地址:spring aop 配置 作者:catlovemouse


1、我理解的Spring AOP
AOP其实就是划分出来了一个切面,然后在这个切面里面进行了一些增强,最后产生一个增加了新功能的代理对象,注意,是代理对象,这是Spring AOP实现的基础。这个对象只不过比原始对象(Bean)多了一些功能而已,比如Bean预处理,Bean后处理,异常处理等。

2、简单的Demo理解Spring AOP
首先定义一个AOP代理工厂AOPProxyFactory,这个工厂产生增强后的代理对象。
Java代码
  1. public class AOPProxyFactory{   
  2.   public static Object createProxy(Object target,MethodBeforeAdvice mbAdvice){  
  3.   return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
  4.   target.getClass().getInterfaces(),  
  5.   new InvocationHandler(){//拦截器  
  6.     public Object invoke()(Object proxy,Method m,Object[] args) throws Throwable{  
  7.       mbAdvice.before(m,args,target);//方法执行前增强  
  8.       return m.invoke(target,args);//方法调用  
  9.     }  
  10.   });  
  11. }  

然后定义before增强
Java代码
  1. MethodBeforeAdvice mbAdvice =new MethodBeforeAdvice(){  
  2.   public before(Method m,Object[] args,Object target){  
  3.        do something;  
  4.   }  
  5. };  

最后使用代理工厂产生一个业务Bean的代理
Java代码
  1. UserserviceImpl target=new UserserviceImpl();//待增强的原始业务bean,在Spring中并非new出来,而是使用IOC注入Userservice接口中的  
  2. Userservice us= (Userservice)AOPProxyFactory.create(target,mbAdvice);  
  3. us.login(username,password);//us即为代理对象,此方法执行时,会首先执行before()  

原理就是这么简单:InvocationHandler--MethodBeforeAdvice--invoke
这里是在方法嗲调用前增强,当然也可以在方法调用后增强、方法抛出异常时增强。

3、在Spring中实现的AOP
上面的例子是我的一个demo,以说明Spring的AOP实现原理。实际上Spring有一个 org.springframework.aop.framework.ProxyFactoryBean接口,可以注入target(欲增强的对象)、 interceptorNames(拦截器的一个list)属性,从而实现AOP。

示例中的MethodBeforeAdvice可以对代理对象的任何方法进行增强,也就是说,Advice只实现了功能的扩展,而没有拦截器的功能(事实上是拦截了所有的方法),而实际中并不需要这样,下面介绍针对特定方法的增强。

4、只拦截特定的方法
使用Spring的PointCutAdvisor。一个Advisor定义了一个Pointcut和一个Advice,满足Poincut(指定了哪些方法需要增强),则执行相应的Advice(定义了增强的功能)。

有两个常用的实现类:
NameMacthMethodPointCutAdvisor、RegexpMethodPointCutAdvisor
前者需要注入mappedName和advice属性,后者需要注入pattern和advice属性。
mappedName指明了要拦截的方法名,pattern按照正则表达式的方法指明了要拦截的方法名,advice定义了一个增强(需要自己实 现MethodBeforeAdvice、MethodAfterAdvice、ThrowsAdvice、MethodInterceptor接口之 一)。然后在ProxyFactoryBean的拦截器(interceptorNames)中注入这个PointCutAdvisor即可。
Xml代码
  1. <bean id="loginBeforeAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
  2.    <property name="pattern" value=".*UserService\.login"/>  
  3.    <property name="advice">  
  4.       <bean class="com.xxx.LoginBeforeAdvice"/>  
  5.    property>  
  6. bean>  


最后将PointCutAdvisor和(或)Advice装配到ProxyFactoryBean中即可。
ProxyFactoryBean中可同时注入Advisor和Advice,只不过二者的拦截不一样而已。
Xml代码
  1. <bean id="userService" class="org.spring.aop.framework.ProxyFactoryBean">  
  2.    <property name="interceptorNames">  
  3.       <list>  
  4.          <value>loginBeforeAdvisorvalue>  
  5.          <value>loginThrowsAdvicevalue>  
  6.          ...  
  7.       list>  
  8.    property>  
  9.    <property name="target" ref="targetService"/>  
  10. bean>  


注意:一个ProxyFactoryBean只能指定一个代理目标,不是很方便,第2节将介绍自动代理,方便配置。

上一节介绍的都是使用ProxyFactoryBean实现代理对象的创建,本节介绍使用自动代理实现。通过自动代理,可以实现自动为多个目标Bean实 现AOP代理、避免客户端直接访问目标Bean(即getBean返回的都是Bean的代理对象)。spring的自动代理是通过 BeanPostProcessor实现的,容器载入xml配置后会修改bean为代理Bean,而id不变。
ApplicationContext可以直接检测到定义在容器中的BeanPostProcessor,BeanFactory需要手动添加。
有2种常用的BeanPostProcessor:
1.BeanNameAutoProxyCreator
2.DefaultAdvisorAutoProxyCreator

1、BeanNameAutoProxyCreator
Xml代码
  1. <bean id="loginBeforeAdvisor" .../>  
  2. <bean id="loginThrowsAdvisor" .../>  
  3. <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
  4.           
  5.         <property name="beanNames" value="*Service">  
  6.         property>  
  7.         <property name="interceptorNames">  
  8.             <list>  
  9.                 <value>loginBeforeAdvisorvalue>  
  10.                 <value>loginThrowsAdvisorvalue>  
  11.             list>  
  12.         property>  
  13. bean>  

使用通配符*可以为所有以Service结尾的目标Bean实现AOP代理。
2、DefaultAdvisorAutoProxyCreator
Xml代码
  1. <bean id="loginBeforeAdvisor" .../>  
  2. <bean id="loginThrowsAdvisor" .../>  
  3. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>  


在1,2节里面我们已经大致了解了AOP的工作原理,以及Spring下AOP的配置与实现,BeanNameAutoProxyCreator,DefaultAdvisorAutoProxyCreator已经部分简化了AOP配置,然而还是很繁琐:
首先要编写xxxAdvice类(需要自己实现MethodBeforeAdvice、MethodAfterAdvice、 ThrowsAdvice、MethodInterceptor接口之一),然后还要在xml中配置Advisor,还要在Advisor中注入 Advice,最后还要将Advisor加入ProxyFactoryBean、BeanNameAutoProxyCreator或者 DefaultAdvisorAutoProxyCreator中。

而Spring2.x以后引入了@AspectJ注解,可以通过Java5的注解以AspectJ的语法在Spring中配置AOP了,使用@AspectJ后,这一切都将变得异常简单:
只需要定义一个Aspect类,在Aspect中声明Advice(可以同时声明多个),然后在xml中配置这个Aspect,最后添加一行就可以搞定。
这种方式更加灵活、简单。
下面是一个Demo:
Java代码
  1. //定义一个切面  
  2. @Aspect  
  3. public class LoggerAspect{  
  4.    @Before("excution(* Service.login(username,..))")  
  5.    public void logBefore(String username){  
  6.       System.out.println("[Logger] User " + username + "try to login");  
  7.    }  
  8. }  

然后在Spring的xml文件中配置:
Xml代码
  1.   
  2. <bean id="loggerAspect" class="com.xxx.LoggerAspect"/>  
  3.   
  4. <bean id="userService" .../>  
  5.   
  6. <aop:aspectj-autoproxy/>  

如果想添加更多的Advice,只需要在LoggerAspect类中声明一个类似logBefore的方法即可,而不是像之前的那样需要再重新编写一个Advice类。例如:
Java代码
  1. @AfterReturning("excution(* Service.login(username,..))")  
  2. public void logSuc(String username){  
  3.    System.out.println("[Logger] User " + username + "login successfully");  
  4. }  

1,2节里面还介绍过一个“环绕通知”,也就是在方法的前后异常时都进行了增强,AspectJ 的@Around注解同样可以实现:
Java代码
  1. @Aspect  
  2. public class SecurityAspect{  
  3.    @Around("excution(* Service.login(username,..))")  
  4.    public Object securityCheck(ProceedingJionPoint pjp) throws Throwable{  
  5.       //调用之前进行用户检查  
  6.       String username=(String)pjp.getArgs()[0];  
  7.       if(!"zhangsan".equals(username)){  
  8.          //对除zhangsan之外的用户开放  
  9.          return pjp.proceed();  
  10.       }  
  11.       //抛出异常,禁止zhangsan登陆  
  12.       throw new RuntimeException("Reject login!");  
  13.    }  
  14. }  



介绍完AspectJ的注解式AOP后,我们发现这样做还是有重复的代码:excution(* Service.login(username,..))
其实这就是一个Pointcut,我们可以在Logger类中声明一个方法为一个Pointcut,然后在后面的Advice中重用这个Pointcut:
Java代码
  1. @Aspect  
  2. public class LoggerAspect{  
  3.    //声明切入点  
  4.    @Pointcut("excution(* Service.login(username,..))")  
  5.    public void login(){};  
  6.   
  7.    @Before(value="login()")  
  8.    public void logBefore(String username){  
  9.       System.out.println("[Logger] User " + username + "try to login");  
  10.    }  
  11.    @AfterReturing(value="login()")  
  12.    public void logSuc(String username){  
  13.       System.out.println("[Logger] User " + username + "login successfully");  
  14.    }  
  15.    @AfterThrowing(pointcut="login()",throwing="e")  
  16.    public void logFailure(RuntimeException e){  
  17.       System.out.println("[Logger] Exception:" + e.getMessage());  
  18.    }  
  19. }  

这里简单介绍一下AspectJ的Pointcut,在Spring中使用最多的是excution切入点,匹配方法的执行,完整的表达式如下:
excution([修饰符] 返回类型 [声明类型] 方法名称 (参数类型) [异常类型])
其中返回类型、方法名称、参数类型是必须的,其他为可选的。
最常用的匹配模式是“*”,用来表示任意的返回类型或匹配部分命名模式。
“()”匹配一个无参数的方法。
“(..)”匹配一个含有任意个参数的方法。
“(String,*)”匹配含有两个参数的方法,首个参数类型为String。
“(String,..)”匹配至少含有一个参数的方法,首个参数类型为String。

匹配任意的public方法:
excution(public * *(..))

匹配任何以set开头的方法:
excution(* set*(..))

匹配Service接口中的方法:
excution(* *Service.*(..))

匹配会抛出IO异常的方法:
excution(* *(..) throws java.io.IOException)

一般情况下,若需要使用Spring AOP,最好使用AspectJ注解方式,这样代码更简单,配置更少,易于维护。

在第三节里面,完满讲了使用@AspectJ注解实现Spring AOP,它需要运行在Java5以上的版本中,对于Java1.4之前的版本,我们也想使用Spring AOP,那么怎么办呢?
一种是像1,2节里面讲的那样,定义Advice实现MethodBeforeAdvice、MethodAfterAdvice、 ThrowsAdvice、MethodInterceptor接口之一,然后包装在Advisor中,最后使用BeanPostProcessor(如 BeanNameAutoProxyCreator、DefaultAdvisorAutoProxyCreator)创建业务Bean的代理对象。显然 这种方式很繁琐。
第二种选择是使用Spring的命名空间,这是在2.0版本以后引入的。它可以运行在Java1.4基础上。本节主要介绍使用基于xml配置实现Spring AOP。
1、定义业务Bean
Java代码
  1. package spring.aop;  
  2. public class UserService {  
  3.     public void getUser(int id){  
  4.         System.out.println("the user id: "+id);  
  5.     }  
  6. }  

2、定义切面
Java代码
  1. public class UserAspect {  
  2.     //业务方法执行前会执行此操作  
  3.     public void before() {  
  4.         System.out.println("before advice");  
  5.     }  
  6.     //业务方法正常执行结束后会执行此操作  
  7.     public void afterReturn() {  
  8.         System.out.println("after-returning advice");  
  9.     }  
  10.     //相当于finally,无论业务方法是否产生异常,执行后都会执行此操作  
  11.     public void after() {  
  12.         System.out.println("after advice");  
  13.     }  
  14. }  

这里的切面就是一个简单的POJO,不需要实现任何接口,不需要继承任何类。里面的方法就是一个Advice(包括before、after、after-throwing、after-return、around五种类型),而Pointcut在xml配置。
3、配置xml
Xml代码
  1. <bean id="userService" class="spring.aop.UserService"/>  
  2. <bean id="aspect" class="spring.aop.UserAspect"/>  
  3. <aop:config>  
  4.     <aop:pointcut id="pointcut" expression="execution(* spring.aop.UserService.* (..))"/>  
  5.     <aop:aspect ref="aspect">  
  6.         <aop:before pointcut-ref="pointcut" method="before"/>  
  7.         <aop:after-returning pointcut-ref="pointcut" method="afterReturn" >  
  8.         <aop:after pointcut-ref="pointcut" method="after">  
  9.     aop:aspect>  
  10. aop:config>  


测试代码:
Java代码
  1. ApplicationContext con=new ClassPathXmlApplicationContext("applicationContext.xml");  
  2. UserService us=(UserService)con.getBean("userService");  
  3. us.getUser(12);  

输出结果:
Java代码
  1. before advice  
  2. the user id: 12  
  3. after-returning advice  
  4. after advice  

4、环绕通知
Java代码
  1. public Object around(ProceedingJoinPoint pjp) throws Throwable{  
  2.     try{  
  3.         System.out.println("around advice: before");  
  4.         Object obj=pjp.proceed();//必须调用此方法,否则后续处理终断  
  5.         System.out.println("around advice: after-returning");  
  6.         return obj;  
  7.     }catch(Exception e){  
  8.         throw e;  
  9.     }  
  10. }  

对应的xml配置:
Xml代码
  1. <aop:aspect ref="aspect">  
  2.     <aop:around pointcut-ref="pointcut" method="around"/>  
  3. aop:aspect>  

环绕通知使我们有机会在方法执行的前后都作出相应的处理,功能最为强大,但必须包含一个处理连接点参数ProceedingJoinPoint pjp,并在与处理(before)之后调用pjp.proceed(),忘记次调用会带来莫名其妙的结果,所以能使用其他Advice进行处理的,尽量 使用其他更简单的Advice。

5、异常
Java代码
  1. public void exception(Exception e) {  
  2.     //记录异常  
  3.     System.out.println("exception ["+e+"]");  
  4. }  

这里参数Exception e是必须的。在xml配置中也必须指明此参数:throwing="e"
对应的xml配置:
Xml代码
  1. <aop:aspect ref="aspect">  
  2.     <aop:after-throwing pointcut-ref="pointcut" method="exception" throwing="e"/>  
  3. aop:aspect>  

6、使用带参数的Advice
上面的例子中除了around和after-throwing含有参数,且around中的参数不需要xml中配置,其他的Advice都是无参数的,要想使用带有自定义参数的Advice,怎么办呢?此时就需要重新配置Pointcut了:
例子,一个带参数的before Advice:
定义业务Bean:
Java代码
  1. public class UserService {  
  2.     public void login(User user){  
  3.         System.out.println("user id: "+u.getId());  
  4.     }  
  5. }  

定义before Advice:
Java代码
  1. public void before(User user) {  
  2.     System.out.println("user "+u.getName()+"try to login...");  
  3. }  

在xml中配置Pointcut:
Xml代码
  1. <aop:config>  
  2.     <aop:pointcut id="pointcut" expression="execution(* spring.aop.UserService.* (User)) and args(u)"/>  
  3.     <aop:aspect ref="aspect">  
  4.         <aop:before pointcut-ref="pointcut" method="before" arg-names="u"/>  
  5.     aop:aspect>  
  6. aop:config>  

关键在于这一行:expression="execution(* spring.aop.UserService.* (User)) and args(u)",指明了切入点为spring.aop.UserService的任何方法,并且此方法含有一个类型的User的参数,参数名为u(可以与业务Bean中的参数名不一样,实际上是它的一个别名),中arg-names就是引用的这个别名(可以与Advice中的参数名不一样)。

注意:
其他Advice不能共享此Pointcut,除非Advice中的参数与此Pointcut中的参数一致。



有人会问,如果Advice中只使用业务Bean方法的部分参数,该如何做呢?
答案是:依然利用Pointcut配置。
Demo:
定义业务Bean:
Java代码
  1. public class UserService {  
  2.     public void login(String name,String psw){  
  3.         System.out.println("login...");  
  4.     }  
  5. }  

定义before Advice:
Java代码
  1. public void before(String n) {  
  2.     System.out.println("user "+n+" try to login...");  
  3. }  

在xml中配置Pointcut:
Xml代码
  1. <aop:config>  
  2.     <aop:pointcut id="pointcut" expression="execution(* spring.aop.UserService.* (String,..)) and args(n,..)"/>  
  3.     <aop:aspect ref="aspect">  
  4.         <aop:before pointcut-ref="pointcut" method="before" arg-names="n"/>  
  5.     aop:aspect>  
  6. aop:config>  

(String,..)声明切入点至少含有一个String类型的参数,显然可以匹配UserService中的login(String name,String psw);
args(n,..)声明给login(String name,String psw)的第一个参数起了个别名“n”传递给Advice,如果中arg-names不是“n”,将抛出异常。

7、Advice的顺序
1)一般情况下,before、after-throwing、after的执行顺序是一定的,即:
before-->after-throwing-->after
而before与around中的proceed()方法调用之前的处理则是按照谁配在前谁先处理的原则,after与around中的proceed()方法调用之后的处理也是如此;
当异常抛出时,after-returning操作不会被处理,而after-throwing、after依次被处理。
2)如果是基于注解的方式,在测试中发现是按照如下顺序执行增强的:
Java代码
  1. before advice  
  2. around advice: before  
  3. login...  
  4. around advice: after-returning  
  5. after-returning advice  
  6. after advice 
阅读(143) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~