老兄,快来一起过春天!(四)

 AOP:面向切面编程

 

OOP主要是通过对象继承、多态的特点,来进行强大的代码重用。
而AOP是将整体的系统分成各个不同模块,形成不同的切面来进行程序设计。

 

如Struts中数据层的每个方法,就算使用各种ORM框架,如:

...........
boolean backbool = false;
daoManager.startTransaction();
try{
  .......
 daoManager.commitTransaction();
}catch(Exception ex)
{
 ex.printStackTrace();
}
finally{
 daoManager.endTransaction();
}
...........


也不能彻底摆脱事务开始、进行数据操作、结束事务的繁杂代码编写。

而AOP模式可以把每个方法的这些相同代码提取出来,放到一个特定的类中去。而且保证在执行这些原本含有繁杂
代码的方法时,你可以自定义在哪里、在什么时候、用什么状态去调用这个特定的类。这样程序设计将会又一次
得到极大的代码重用,而维护也会更加简单。

 

AOP的几个重要接口记录:
MethodBeforeAdvice:切入到方法执行之前。
MethodInterceptor:切入到方法的具体代码中。
AfterReturningAdvice:切入到方法执行结束之后。
ThrowsAdvice:当AOP监视的业务方法抛出错误,切入到错误方法总拦截错误。

 

其中,MethodInterceptor可切入到方法的具体代码中,因此可完全控制整个方法的走向,甚至改变返回值。

 

 

新建web工程springaop如下结构:

 

在dao包下新建业务服务接口IUserService.java

  1. public interface IUserService { 
  2.     void create(String username,String password);
  3.     void upuser(String username,String password);
  4. }

impl包下有实现类UserServiceImpl.java

  1. package com.springaop.dao.impl;
  2. import com.springaop.dao.IUserService;
  3. import com.springaop.data.UserData;
  4. public class UserServiceImpl implements IUserService {
  5.     
  6.     private UserData userdata;
  7.     public void create(String username, String password) {
  8.         if(userdata == null)
  9.             userdata = new UserData();
  10.         
  11.         if(username.equals(userdata.getMap().get("admin")) 
  12.                 password.equals(userdata.getMap().get("password"))){
  13.             System.out.println("姓名:" + username + "    密码:" + password);
  14.         }
  15.         else{
  16.             throw new RuntimeException("参数错误");
  17.         }
  18.                 
  19.     }
  20.     
  21.     public void upuser(String username,String password){
  22.         System.out.println("执行到:upuser方法");
  23.     }
  24.     public void setUserdata(UserData userdata) {
  25.         this.userdata = userdata;
  26.     }
  27. }

data包下有数据类UserData.java

  1. package com.springaop.data;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.Set;
  5. public class UserData {
  6.     private Map<String,String>map = new HashMap<String, String>();
  7.     
  8.     public UserData(){
  9.         map.put("admin""admin");
  10.         map.put("password""123456789");
  11.     }
  12.     public Map<String, String> getMap() {
  13.         return map;
  14.     }
  15.     public void setMap(Map<String, String> map) {
  16.         this.map = map;
  17.     }
  18. }

main包下有测试类TestMain.java

  1. package com.springaop.main;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. import com.springaop.dao.IUserService;
  5. import com.springaop.dao.impl.UserServiceImpl;
  6. public class TestMain {
  7.     public static void main(String[] arg0){
  8.         ApplicationContext tx = new ClassPathXmlApplicationContext("applicationContext.xml");
  9.         IUserService user = (UserServiceImpl)tx.getBean("userservice");
  10.         try{
  11.             user.create("admin""123456789");
  12.             user.upuser("admin""123456789");
  13.         }catch(Exception e){            
  14.         }
  15.     }
  16. }

将 TestMain.java  作为java Application程序运行,可显示:
姓名:admin    密码:123456789
执行到:upuser方法

这样一切正常,运行成功。

 

以上的测试是在无AOP模式下的程序。现在加入AOP切面:

1:将 UserServiceImpl .java 中的属性 UserData 做为注入bean引入,放弃在类中实例化该类,这样更符合Spring松耦合思想。

      在 ApplicationContext.xml 中加入:   

  1.     <bean id="userservice" class="com.springaop.dao.impl.UserServiceImpl" >
  2.         <property name="userdata">
  3.             <bean class="com.springaop.data.UserData"></bean>
  4.         </property>
  5.     </bean>

更改 UserServiceImpl 代码:

  1. .......
  2. public void create(String username, String password) {
  3.    //删除此段 
  4.    /*if(userdata == null)
  5.       userdata = new UserData();*/
  6.    if(username.equals(userdata.getMap().get("admin")) 
  7. .......

 2:加入分别实现了AOP接口(前置、后置、错误通知)的4个类到程序中:

1):加入Spring Core、Spring AOP包到程序中。

2):在aopservice下,新建4个类:

          UserBeginPrint.java
          实现MethodBeforeAdvice接口,用于在方法执行之前的切入,比如记录日志,检查某些全局变量,开启事务等;

  1. package com.springaop.aopservice;
  2. import java.lang.reflect.Method;
  3. import org.springframework.aop.MethodBeforeAdvice;
  4. public class UserBeginPrint implements MethodBeforeAdvice {
  5. public void before(Method method, Object[] args, Object target)
  6.             throws Throwable {
  7.         System.out.println("方法开始执行");
  8.     }
  9. }

    UserNowPrint.java
    实现MethodInterceptor接口,用于在方法执行时的切入,如对方法传入参数的检查判断、传入值的通用操作等。

  1. package com.springaop.aopservice;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. import org.aopalliance.intercept.MethodInvocation;
  4. public class UserNowPrint implements MethodInterceptor {
  5.    public Object invoke(MethodInvocation method) throws Throwable {
  6.     System.out.println("方法正在执行,参数为:");
  7.     return method.proceed();
  8.    }
  9. }

    UserExitPrint.java
    实现AfterReturningAdvice接口,用于在方法执行后的切入,通常都做方法的收尾擦除工作,如释放某些通用变量,关闭事务,记录日志等。

  1. package com.springaop.aopservice;
  2. import java.lang.reflect.Method;
  3. import org.springframework.aop.AfterReturningAdvice;
  4. public class UserExitPrint implements AfterReturningAdvice {
  5.     public void afterReturning(Object returnValue, Method method,
  6.             Object[] args, Object target) throws Throwable {
  7.         System.out.println("方法执行完成");       
  8.     }
  9. }

    UserErrorPrint.java
    实现ThrowsAdvice接口,用于当业务方法抛出错误后的处理。

  1. package com.springaop.aopservice;
  2. import java.lang.reflect.Method;
  3. import org.springframework.aop.ThrowsAdvice;
  4. public class UserErrorPrint implements ThrowsAdvice {
  5.     public void afterThrowing(Method m,Object[] args,Object target,Throwable subclass){
  6.         System.out.println("你输入的姓名不正确!");
  7.     }
  8.     
  9. }

3:以上4个AOP类搞好以后,打开ApplicationContext.xml。在管理bean声明中加入以上4个类。

  1. <bean id="userbegin" class="com.springaop.aopservice.UserBeginPrint"></bean>
  2.     <bean id="usernow" class="com.springaop.aopservice.UserNowPrint"></bean>
  3.     <bean id="userexit" class="com.springaop.aopservice.UserExitPrint" ></bean>
  4.     <bean id="usererror" class="com.springaop.aopservice.UserErrorPrint" ></bean>

随后进行SpringAOP装配:

  1. <bean id="useraop" class="org.springframework.aop.framework.ProxyFactoryBean">
  2.         <property name="target" ref="userservice" ></property>
  3.         <property name="interceptorNames">
  4.             <list>
  5.                 <value>userbegin</value>
  6.                 <value>usernow</value>
  7.                 <value>userexit</value>
  8.                 <value>usererror</value>
  9.             </list>
  10.         </property>
  11.     </bean>

4:更改测试类TestMain.java 代码,把原本转换为UserServiceImpl的代码换为IUserService,把getBean的对象换为AOP代理Bean:useraop

  1. ........
  2. ApplicationContext tx = new ClassPathXmlApplicationContext("applicationContext.xml");
  3. IUserService user = (IUserService)tx.getBean("useraop");
  4. try{
  5.     user.create("admin""123456789");
  6. ........

   完成装配之后,同样运行TestMain,一切顺利的话,可以看到运行结果为:

            方法开始执行
           方法正在执行,参数为:
           姓名:admin    密码:123456789
           方法执行完成
           方法开始执行
           方法正在执行,参数为:
           执行到:upuser方法
           方法执行完成

 

   这段 AOP 装配XML意为:

           <property name="target" ref="userservice" ></property>

          target属性指需要SpringAOP切面去监控的类 或 方法。指定了ref 后,AOP切面只能作用于ref指定的类 或方法

 

     <property name="interceptorNames">
           <list>
               <value>userbegin</value>
               <value>usernow</value>
               <value>userexit</value>
                <value>usererror</value>
          </list>
    </property>

     interceptorNames 属性指需要AOP切面去调用的AOP接口实现类(Advice:增强)。调用的顺序是按照list中value的顺序来进行的,如:把usernow、userexit提前,

               <value>usernow</value>
               <value>userexit</value>
               <value>userbegin</value>
               <value>usererror</value>

 

   可以看到运行结果也随之改变

      方法正在执行,参数为:
      方法开始执行
      姓名:admin    密码:123456789
      方法执行完成
      方法正在执行,参数为:
      方法开始执行
      执行到:upuser方法
      方法执行完成

 

     list也可以不必set全部4个AOP接口,可以按照自己的需要set需要的接口,如只有usernow一个,或有2个、3个等。

      <bean id="useraop" class="org.springframework.aop.framework.ProxyFactoryBean">

     ProxyFactoryBean 是Spring制定的AOP代理bean,具有 target 目标对象和 interceptorNames 声明注入的各种接口对象的工厂Bean,他返回包含target指定类的上层接口,如果这个指定类实现了多重接口,他将返回所有的上层接口,而业务类可根据需要转换接口,也就是TestMain中的 IUserService user = (IUserService)tx.getBean("useraop"); 如业务类需要指定ProxyFactoryBean的返回接口,可在其属性中加上:

      <property name="interfaces" value="com.springaop.dao.IUserService"></property>   value需要填写指定实现接口类的全路径。

 

     这次装配运行中,可以看到TestMain测试类调用了target代理指定类的两个方法 create 、upuser,而装配没有指定AOP代理具体代理监控哪个方法,因此他默认把所有调用到target代理指定类的操作都代理监控起来了。结果打印了两次开始、正在执行、结束,下面的装配需要实现具体指定target监控代理的方法。不至于IUserService的方法全都被代理。

 

     打开ApplicationContext.xml,增加管理Bean如下:

  1. <bean id="useraopAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" >
  2.     <property name="mappedNames">
  3.         <list>
  4.                 <value>create</value>
  5.             </list>
  6.         </property>
  7.         <property name="advice" ref="userbegin">                
  8.         </property>     
  9.     </bean> 

     NameMatchMethodPointcutAdvisor 类是spring AOP 最基本的 PointcutAdvisor (切入增强),它是Spring中静态Pointcut的实例,这段话是Spring 手册上的译文,没有深入到Spring 的AOP 底层。不知道具体何意,只是觉得这个类是AOP 切入的基类?是Spring初始化时的静态切入类的具体实例。

     属性mappedNames指定具体监控 target  代理类的哪个方法,如有多个方法需要监控,可在list中set具体方法名,更可以用正则将符合条件的方法监控起来。

     <value>*create*</value>

     <value>*upuser*</value>

 

加入useraopAdvisor注入bean后,更改useraop注入bean的interceptorNames 属性的list。

    <value>userbegin</value>
    <value>usernow</value>
    <value>userexit</value>
    <value>usererror</value>

更改为:

    <value>useraopAdvisor</value>

 

最后运行TestMain,可以发现结果为:

 

方法正在执行,参数为:
姓名:admin    密码:123456789
执行到:upuser方法

 

 

 


target代理监控的UserServiceImpl类只有creat处于监控范围之内 useraopAdvisor  — mappedNames — mappedNames  —  list — value — create。

 

 

 

用了一天的时间看完SpringAOP,忽然感觉这东西好像 Filter ,和过滤器一样针对某个方法、某个类、某个文件夹进行过滤,不知道他的底层实现是怎么样的。另外,在做 NameMatchMethodPointcutAdvisor  实现时没有看到资料 可以在一个bean设置中代理监控N个Advisor ,试过诸如:

<property name="advice">

    <list>

         <ref="userbegin" />

        <ref="usernow" />

   </list>

</property>

 

但还是报错了,后来还是声明了4个NameMatchMethodPointcutAdvisor  ,每个advisor 监控一个advice,最后再用useraop装配起来才成功。但感觉这样应该不是最好实现方法,如果哪位高手看到这个疑问,又知道怎么做,请留言或站内联系我,呵呵,小生先谢过了!
另外Spring AOP 诸多术语如Advice(增强)、pointcut(切入点)、AOP Proxy(AOP 代理)等都按自己理解,觉得更符合自己理解的词代替了,如增强 - 监控、切入点 - 切入、AOP Proxy -代理监控,希望随着对Sring了解渐渐深入,有一天会完全理解这些术语的含义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值