常言笑的家

Spring, Hibernate, Struts, Ajax, RoR

Spring Aop Step-By-Step 学习笔记(下)

五. Spring-Aop 引入的介绍
 
    下面我们介绍一种通知“引入”,关于引入,如同它的名字一样,给对象添加方法和属性。呵呵,好厉害吧。它是通过CBLIB来动态生成类的,所以自己用的时候别忘了加载这个包。
    
     代码:
     购物时候放东西的包包;
public interface CustomerBag {
     void addBag(Object obj);
 
     void clean();
 
     int getCount();
}
 
我们要给别的对象类添加的Mixin,嘿嘿。
public class CustomerMixin extends DelegatingIntroductionInterceptor implements CustomerBag {
 
     private static final long serialVersionUID = 5296015143432822715L;
    
     private ArrayList bag = new ArrayList();
 
     public void addBag(Object obj) {
          bag.add(obj);
     }
 
     public void clean() {
          bag = new ArrayList();
     }
 
     public ArrayList getBag() {
          return bag;
     }
 
     public int getCount() {
          return bag.size();
     }
 
}
 
     我们的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
<beans>
    
     <bean id="customer" class="demo.Customer" singleton="false" />
 
     <bean id="customerMixin" class="demo.CustomerMixin" singleton="false" />
 
 
     <bean id="customerMixinAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"
          singleton="false">
          <constructor-arg>
               <ref bean="customerMixin" />
          </constructor-arg>
     </bean>
 
     <bean id="customerBean" class="org.springframework.aop.framework.ProxyFactoryBean">
          <property name="proxyTargetClass" value="true" />
          <property name="singleton" value="false" />
          <property name="interceptorNames">
               <list>
                    <value>customerMixinAdvisor</value>
               </list>
          </property>
          <property name="target">
               <ref bean="customer" />
          </property>
     </bean>
 
</beans>
 
可以看到singleton="false"处处可见,因为我们要每个新的对象都有自己引入的状态,不可能只有一个对象来维持,那个我们肯定是不希望的。
修改我们的RunDemo类,添加一个方法:
     /**
      * 创建引用通知;
      */
     public static void customer() {
 
          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/customer.xml");
 
          // 这个类是有CGLIB动态生成的;
          Customer bag = (Customer) context.getBean("customerBean");
          CustomerBag bag2 = (CustomerBag) bag;
          bag2.addBag(new Object());
 
          System.out.println(bag.getName());
          System.out.println(bag2.getCount());
     }
 
          在main中调用这个方法,运行,结果如下:
              
               悠~游!
1
 
          在这里我要说明一下,关于引入这个通知的使用我仅仅是看了一眼,具体的例子可能有不恰当之处还请高手们指点。
 
六. Spring-Aop 之 BeanNameAutoProxyCreator
 
BeanNameAutoProxyCreator 看其名,大概知道其意。根据bean的名字自动匹配拦截代理,让我们看看它能带来什么?
代码:
public class PerformanceThresholdInterceptor implements MethodInterceptor {
 
     private final long thresholdInMillis;
 
     PerformanceThresholdInterceptor(long time) {
          thresholdInMillis = time;
 
     }
 
     public Object invoke(MethodInvocation invocation) throws Throwable {
         
          System.out.println(" 开始测试时间.");
          long t = System.currentTimeMillis();
          Object o = invocation.proceed();
          t = System.currentTimeMillis() - t;
          if (t > thresholdInMillis) {
               System.out.println(" 警告:调用的方法所耗费的时间超过预警时间(" + thresholdInMillis + " 微秒)");
          }
          System.out.println(" 测试时间结束.");
          return o;
     }
}
这个又是自定义的,呵呵。我们继承MethodInterceptor这换类,实现我们自己的方法过滤器。具体要实现的功能就是检验我们要调用的方法,和OnePerCustomerInterceptor这个类一样。
接下来是我们的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
<beans>
     <bean id="kwikEMartTarget" class="demo.ApuKwikEMart"></bean>
    
     <bean id="performanceThresholdInterceptor" class="demo.advice.PerformanceThresholdInterceptor">
          <constructor-arg>
               <value>5000</value>
          </constructor-arg>
     </bean>
 
     <bean id="beanNameAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
          <property name="beanNames">
               <list>
                    <value>*Target</value>
               </list>
          </property>
          <property name="interceptorNames">
               <value>performanceThresholdInterceptor</value>
          </property>
     </bean>
 
</beans>
 
在main方法中加入我们的方法:
   /**
    * 创建beanNameAutoProxyCreator动态代理;
    */
   public static void beanNameProxy() {
 
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/beanNameProxy.xml");
 
        KwikEMart akem = (KwikEMart) context.getBean("kwikEMartTarget");
        try {
             akem.buyCheese(new Customer());
        } catch (KwikEMartException e) {
             e.printStackTrace();
        }
   }
 
     运行我们的例子,结果如下:
    
开始测试时间.
-- 我想买:奶酪!
测试时间结束.
    
     看到了么?Spring aop自动寻找Bean的名字为* Target 的类,进行方法过滤。呵呵,可能你会说这个有什么用?自己写不也一样么?其实如果系统变得庞大的话,自己配置也是十分耗费精力的。
 
七. Spring-Aop DefaultAdvisorAutoProxyCreator
 
     接下来我们将介绍更加强大的一个代理器:DefaultAdvisorAutoProxyCreator。
     DefaultAdvisorAutoProxyCreator BeanNameAutoProxyCreator不同的是,DefaultAdvisorAutoProxyCreator只和Advisor 匹配,所以我们写一个Advisor到xml文档中去。
     XML 文档如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
<beans>
 
     <bean id="kwikEMartTarget" class="demo.ApuKwikEMart"></bean>
     <bean id="performanceThresholdInterceptor" class="demo.advice.PerformanceThresholdInterceptor">
          <constructor-arg>
               <value>5000</value>
          </constructor-arg>
     </bean>
 
     <!-- 使用RegexpMethodPointcutAdvisor来匹配切入点完成个一个Advisor; -->
     <bean id="regexpFilterPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
          <property name="pattern">
               <!--    
                    匹配的名字为方法名;
               -->
               <value>.*buy.*</value>
          </property>
          <property name="advice">
               <ref bean="performanceThresholdInterceptor"/>
          </property>
     </bean>
 
     <bean id="defaultAdvisorAutoProxyCreator"
     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
 
</beans>
    
     添加下面方法调用main方法中去:
/**
* 创建defaultAdvisorAutoProxyCreator动态代理;
*/
   public static void defaultAdvisorProxy() {
 
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/defaultAdvisorProxy.xml");
 
        KwikEMart akem = (KwikEMart) context.getBean("kwikEMartTarget");
        try {
             akem.buyCheese(new Customer());
        } catch (KwikEMartException e) {
             e.printStackTrace();
        }
   }
     运行,结果如下:
         
开始测试时间.
-- 我想买:奶酪!
测试时间结束.
 
 
八. Spring-Aop TargetSources 介绍
 
1. 可热交换的目标源
 
可热交换的目标源主要是在你程序运行中切换目标对象,而此时调用者引用的对象也会自动切换。具体的概念你可以参考Spring-Reference关于它的介绍,我们主要在程序中体会它给我们带来的改变。
修改我们的xml成为下面的样子:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
 
<beans>
 
     <bean id="kwikEMartTarget" class="demo.ApuKwikEMart" ></bean>
    
     <bean id="swapApuKwikEMart" class="demo.SwapApuKwikEMart" singleton="false"></bean>
    
     <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" />
 
     <bean id="nameMatchfilterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
          <property name="mappedName">
               <value>buy*</value>
          </property>
     </bean>
 
     <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
          <property name="pointcut">
               <ref bean="nameMatchfilterPointcut" />
          </property>
          <property name="advice">
               <ref bean="onePerCustomerInterceptor" />
          </property>
     </bean>
 
     <bean id="swappable" class="org.springframework.aop.target.HotSwappableTargetSource">
          <constructor-arg><ref local="kwikEMartTarget"/></constructor-arg>
     </bean>
 
     <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
          <property name="proxyInterfaces" value="demo.KwikEMart" />
          <property name="interceptorNames">
               <list>
                    <value>runDemofilterPointcutAdvisor</value>
               </list>
          </property>
          <property name="targetSource">
               <ref bean="swappable" />
          </property>
     </bean>
 
</beans>
 
代码:
切换后的对象
public class SwapApuKwikEMart implements KwikEMart {
     private boolean cheeseIsEmpty = false;
 
     private boolean pepperIsEmpty = false;
 
     private boolean squishIsEmpty = false;
 
     public Cheese buyCheese(Customer customer) throws KwikEMartException {
 
          if (cheeseIsEmpty) {
               throw new NoMoreCheeseException();
          }
 
          Cheese s = new Cheese();
          System.out.println("-- 我不是ApuKwikEMart,我想买:" + s);
          return s;
 
     }
 
     public Pepper buyPepper(Customer customer) throws KwikEMartException {
 
          if (pepperIsEmpty) {
               throw new NoMorePepperException();
          }
 
          Pepper s = new Pepper();
          System.out.println("-- 我不是ApuKwikEMart,我想买:" + s);
          return s;
 
     }
 
     public Squish buySquish(Customer customer) throws KwikEMartException {
 
          if (squishIsEmpty) {
               throw new NoMoreSquishException();
          }
 
          Squish s = new Squish();
          System.out.println("-- 我不是ApuKwikEMart,我想买:" + s);
          return s;
 
     }
 
     public void setCheeseIsEmpty(boolean cheeseIsEmpty) {
          this.cheeseIsEmpty = cheeseIsEmpty;
     }
 
     public void setPepperIsEmpty(boolean pepperIsEmpty) {
          this.pepperIsEmpty = pepperIsEmpty;
     }
 
     public void setSquishIsEmpty(boolean squishIsEmpty) {
          this.squishIsEmpty = squishIsEmpty;
     }
 
}
添加下面代码的引用到我们的main中。
 /**
  * 热源切换;
  */
 public static void swapKwikEMart() {
 
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/swapKwikmart.xml");
 
      // 如果你想通过类来引用这个的话,就要用到CGLIB.jar了,同时在代理工厂里面设置:
      //<property name="proxyTargetClass" value="true" />
      KwikEMart akem = (KwikEMart) context.getBean("kwikEMart");
     
      try {
           akem.buySquish(new Customer());
           akem.buyPepper(new Customer());
           akem.buyCheese(new Customer());
      } catch (KwikEMartException e) {
           // 异常已经被截获了,不信你看控制台!~;
      }
     
      HotSwappableTargetSource swap = (HotSwappableTargetSource) context.getBean("swappable");
      swap.swap(context.getBean("swapApuKwikEMart"));
     
      try {
           akem.buySquish(new Customer());
           akem.buyPepper(new Customer());
           akem.buyCheese(new Customer());
      } catch (KwikEMartException e) {
           // 异常已经被截获了,不信你看控制台!~;
      }
 }
 
运行,结果如下:
 
店员:悠~游! ,Can I help you ?
-- 我想买:果酱!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
-- 我想买:胡椒粉!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
-- 我想买:奶酪!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
-- 我不是ApuKwikEMart,我想买:果酱!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
-- 我不是ApuKwikEMart,我想买:胡椒粉!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
-- 我不是ApuKwikEMart,我想买:奶酪!
                            店员:OK! 悠~游!.give you!
 
可以看到,我们切换后的对象已经被置换了。注意 singleton="false" ,通常情况下需要设置为false,以保证Spring在必要的时候可以创建一个新的目标实例。
 
2. 支持池的目标源
使用支持目标池的源提供了一种和无状态 session Ejb 类似的编程方式,在无状态的 Session Ejb 中,维护了一个相同实例的池,提供从池中获取可用对象的方法。
这次我们用到了 Commons pool 这个包,同时在运行时候还需要 commons-collections.jar, 需要把它们加载到环境变量中。
xml 文件如下:
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
 
<beans>
 
     <bean id="kwikEMartTarget" class="demo.ApuKwikEMart" singleton="false"></bean>
 
     <bean id="commonsPool" class="org.springframework.aop.target.CommonsPoolTargetSource">
          <property name="targetBeanName">
               <value>kwikEMartTarget</value>
          </property>
          <property name="maxSize">
               <value>10</value>
          </property>
     </bean>
 
     <bean id="methodInvokingFactoryBeanAdvisor"
          class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
          <property name="targetObject">
               <ref bean="commonsPool" />
          </property>
          <property name="targetMethod">
               <value>getPoolingConfigMixin</value>
          </property>
 
     </bean>
     <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
          <property name="proxyInterfaces" value="demo.KwikEMart" />
          <property name="interceptorNames">
               <list>
                    <value>methodInvokingFactoryBeanAdvisor</value>
               </list>
          </property>
          <property name="targetSource">
               <ref bean="commonsPool" />
          </property>
     </bean>
 
</beans>
 
同时,我们还需要 添加下面代码的引用到我们的main中。
代码:
 
       /**
        * 调用池对象;
        */
       public static void getCommonPoolObject() {
 
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/kwikemart.xml");
 
              KwikEMart akem = (KwikEMart) context.getBean("kwikEMart");
              try {
                     akem.buyCheese(new Customer());
              } catch (KwikEMartException e) {
                     e.printStackTrace();
              }
             
              PoolingConfig pool=(PoolingConfig)akem;
              System.out.println(" 池的大小为: "+pool.getMaxSize());
       }
 
运行,结果如下:
 
-- 我想买:奶酪!
池的大小为:10
 
同时在我们的控制台里可以看到这句话:
信息: Creating Commons object pool
你可以得到对象,也可以销毁对象。但是必须你的目标对象实现了 DisposableBean 接口,重写销毁的方法。然后通过下面方法调用:
 
CommonsPoolTargetSource comPool = (CommonsPoolTargetSource) context.getBean("commonsPool");
 
try {
       comPool.destroyObject(akem);
} catch (Exception e) {
       e.printStackTrace();
              }
销毁池则用:
comPool.destroy()
             
 
九. Spring-Aop 相关及其他
 
其他相关的比较重要的是org.springframework.aop.framework.ProxyFactoryBean 类,几乎我们上篇都用到了这个类,简单介绍一下:
 

ProxyFactoryBean

属性
描述
target
代理的目标对象
proxyInterfaces
代理实现的接口
interceptorNames
在应用到的目标对象上添加的Advice的名字,可以是拦截器、advisor或者其他通知类型的名字(顺序很重要哦)
 
其他还有singleton,proxyTargetClass。
singleton :每次调用getBean()的时候返回一个新的实例,例如我们使用引入的时候,有状态的bean要设置为false哦。
     proxyTargetClass :是否代理目标类,而不是实现接口。只能在使用CBLIB的时使用。
    
proxyTargetClass 重点说一下,什么意思呢?白话说的意思就是:如果你不设置proxyInterfaces这个,就必须设置这个方法,并且方法值为True。就是告诉CBLIB你要动态创建一个代理类来引用我们的目标。
         
Spring-Reference 中,提到了事务代理,我想那个相对于持久化处理时候在了解比较合适。 对于元数据的支持,因为我还没有精力读到哪里。所以这个暂且搁下,有兴趣的读者可以自己查阅参考。
 
非常感谢你能读完正篇文章,将不足的地方通过邮件传递给我,我将尽快改正。最后我希望,能够让每一个读过的人都有所获得,让我们享受Spring给我们带来的乐趣吧。
 
   
   
    参考资料;
               Spring-Reference 中文版 第五章 Spring 之面向方面编程  
                                                                     http://www.jactiongroup.net/reference/html/
        
Spring AOP 中文教程                                http://spring.jactiongroup.net/viewtopic.php?t=478
    
Spring In Action 中文版 第三章
    
AOP 入門

posted on 2006-12-19 13:17 常言笑 阅读(719) 评论(0)  编辑  收藏 所属分类: JAVA/J2EE


只有注册用户登录后才能发表评论。


网站导航:
 

My Links

Blog Stats

常用链接

留言簿(5)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜