aop

Spring-Aop 前置通知、后置通知、环绕通知、异常通知实现

我以Spring In Action 提供的例子进行二次改造,完成我们自己的流程。业务流程很简单,顾客到商店买东西,店员根据顾客的需要给于顾客提供服务。实现方法前插入,方法后插入,环绕,异常四种。

       代码:
          建立一个用户类;
public class Customer {
     private String name = "悠~游!";
     public String getName() {
          return name;
     }
}
三个产品
public class Cheese {
   public String toString(){
         return "奶酪!";
   }
}
public class Pepper {
     public String toString(){
          return "胡椒粉!";
     }
}
public class Squish {
     public String toString(){
          return "果酱!";
     }
}
          建立一个接口;
public interface KwikEMart {
     Squish buySquish(Customer customer) throws KwikEMartException;
     Pepper buyPepper(Customer customer) throws KwikEMartException;
     Cheese buyCheese(Customer customer) throws KwikEMartException;
}

实现这个接口,我们实现三个方法,买奶酪,买胡椒粉,买果酱;
public class ApuKwikEMart implements KwikEMart {
     private boolean cheeseIsEmpty = false;

     private boolean pepperIsEmpty = false;

     private boolean squishIsEmpty = false;

     public Cheese buyCheese(Customer customer) throws NoMoreCheeseException{

          if (cheeseIsEmpty) {
               throw new NoMoreCheeseException();
          }

          Cheese s = new Cheese();
          System.out.println("--我想买:" + s);
          return s;

     }

     public Pepper buyPepper(Customer customer) throws NoMorePepperException{

          if (pepperIsEmpty) {
               throw new NoMorePepperException();
          }

          Pepper s = new Pepper();
          System.out.println("--我想买:" + s);
          return s;

     }

     public Squish buySquish(Customer customer) throws NoMoreSquishException{

          if (squishIsEmpty) {
               throw new NoMoreSquishException();
          }

          Squish s = new Squish();
          System.out.println("--我想买:" + 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;
     }

}
环绕通知的实现,必须实现invoke方法,通过调用invoke.proceed()手工调用对象方法:
public class OnePerCustomerInterceptor implements MethodInterceptor {
    
     private Set customers=new HashSet();

     public Object invoke(MethodInvocation invoke) throws Throwable {
         
          Customer customer=(Customer)invoke.getArguments()[0];
          if(customers.contains(customer)){
               throw new KwikEMartException("One per customer.");
          }
         
          System.out.println("店员:"+customer.getName() + " ,Can I help you ?");
          Object squishee=invoke.proceed(); //手工调用对象方法;
          System.out.println("店员:OK! " + customer.getName() + ".give you! " );
         
          customers.add(squishee);

          return squishee;
     }
}
前置通知的实现;
public class WelcomeAdvice implements MethodBeforeAdvice {

     public void before(Method method, Object[] args, Object target) throws Throwable {
         
          Customer customer = (Customer) args[0];
          System.out.println("店员::Hello " + customer.getName() + " . How are you doing?");

     }
}
public class Customer {
     private String name = "悠~游!";
     public String getName() {
          return name;
     }
}
后置通知实现;
public class ThankYouAdvice implements AfterReturningAdvice {

     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
         
          Customer customer = (Customer) args[0];
          System.out.println("店员:Thank you " + customer.getName() + " . Come again! " );

     }
}
系统异常处理通知实现;
public class KwikEmartExceptionAdvice implements ThrowsAdvice {

     public void afterThrowing(NoMoreSquishException e) {
          System.out.println("系统:NoMoreSquisheesException异常截获了: " + e.getMessage());
     }
     public void afterThrowing(NoMoreCheeseException e) {
          System.out.println("系统:NoMoreCheeseException异常截获了: " + e.getMessage());
     }
     public void afterThrowing(NoMorePepperException e) {
          System.out.println("系统:NoMorePepperException异常截获了: " + e.getMessage());
     }
}
自定义的异常接口;
public class KwikEMartException extends Exception {

     private static final long serialVersionUID = -3962577696326432053L;

     String retValue = "KwikEMartException 异常!";

     public KwikEMartException(String name) {
          retValue = name;
     }
     public KwikEMartException() {

     }
     public String getMessage() {
          return retValue;
     }

}
没有更多的奶酪异常;
public class NoMoreCheeseException extends KwikEMartException {
     private static final long serialVersionUID = -3961123496322432053L;

     String retValue = "NoMoreCheeseException 异常!";

     public NoMoreCheeseException() {
          super();
     }

     public NoMoreCheeseException(String name) {
          super(name);
     }

     public String getMessage() {

          return retValue;
     }
}
没有更多的胡椒粉异常;
public class NoMorePepperException extends KwikEMartException {
     private static final long serialVersionUID = -3961234696322432053L;

     String retValue = "NoMorePepperException 异常!";

     public NoMorePepperException() {
          super();
     }

     public NoMorePepperException(String name) {
          super(name);
     }

     public String getMessage() {

          return retValue;
     }
}
没有更多的果酱异常;
public class NoMoreSquishException extends KwikEMartException {
     private static final long serialVersionUID = -3121234696322432053L;

     String retValue = "NoMoreSquishException 异常!";

     public NoMoreSquishException() {
          super();
     }

     public NoMoreSquishException(String name) {
          super(name);
     }

     public String getMessage() {

          return retValue;
     }
}
运行实例类;
         
public class RunDemo {

               public static void kwikEMart() {

               ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/kwikemart.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) {
                         //异常已经被截获了,不信你看控制台!~;
                    }
               }

               public static void main(String[] args) {
                    kwikEMart();
               }
}
     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">
          <!--
               把这里注释去掉的话,程序调用的时候测试异常通知;
               <property name="cheeseIsEmpty">
                    <value>true</value>
               </property>
               <property name="pepperIsEmpty">
                    <value>true</value>
               </property>
               <property name="squishIsEmpty">
                    <value>true</value>
               </property>
          -->
     </bean>

     <!-- 方法调用前通知 -->
     <bean id="welcomeAdvice" class="demo.advice.WelcomeAdvice" />
     <!-- 方法调用后通知 -->
     <bean id="thankYouAdvice" class="demo.advice.ThankYouAdvice" />
     <!-- 环绕调用通知 -->
     <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" />
     <!-- 异常调用通知 -->
     <bean id="kwikEmartExceptionAdvice" class="demo.advice.KwikEmartExceptionAdvice" />
    
     <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
          <property name="proxyInterfaces" value="demo.KwikEMart" />
          <property name="interceptorNames">
               <list>
                   
                         <value>welcomeAdvice</value>
                         <value>thankYouAdvice</value>
                         <value>onePerCustomerInterceptor</value>
                         <value>kwikEmartExceptionAdvice</value>
                   
               </list>
          </property>
          <property name="target">
               <ref bean="kwikEMartTarget" />
          </property>
     </bean>

</beans>
     这个例子东西很多,不过每个类的代码都不大。如果你对org.springframework.aop.framework.ProxyFactoryBean不是很了解的话可以看我下篇尾处的介绍。读清楚之后,我们运行RunDemo 类,查看控制台结果,如下:

店员::Hello 悠~游! . How are you doing?
店员:悠~游! ,Can I help you ?
--我想买:果酱!
店员:OK! 悠~游!.give you!
店员:Thank you 悠~游! . Come again!
店员::Hello 悠~游! . How are you doing?
店员:悠~游! ,Can I help you ?
--我想买:胡椒粉!
店员:OK! 悠~游!.give you!
店员:Thank you 悠~游! . Come again!
店员::Hello 悠~游! . How are you doing?
店员:悠~游! ,Can I help you ?
--我想买:奶酪!
店员:OK! 悠~游!.give you!
店员:Thank you 悠~游! . Come again!

    我们将kwikEMartTarget里面的注释去掉,测试异常实现,如下:
    
店员::Hello 悠~游! . How are you doing?
店员:悠~游! ,Can I help you ?
系统:NoMoreSquisheesException异常截获了: NoMoreSquishException 异常!
     好好理解一下,我就不废话了,我们进行下一节。

 

四. Spring-Aop 之Pointcut+advice+Advisor 实现

我们修改我们的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="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" />

     <!-- 使用NameMatchMethodPointcut -->
     <bean id="nameMatchfilterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
          <property name="mappedName">
               <!--
                    buy.* 以buy开头的方法;
               -->
               <value>buy*</value>
          </property>
     </bean>

     <!-- 使用Perl5RegexpMethodPointcut -->
     <bean id="regexpFilterPointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
          <property name="pattern">
               <!--
                    .*buy.+ 以buy开头的方法;
                    .*buyS.+ 以buyS开头的方法;
               -->
               <value>.*suy.+</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="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
          <property name="proxyInterfaces" value="demo.KwikEMart" />
          <property name="interceptorNames">
               <list>
                    <value>runDemofilterPointcutAdvisor</value>
               </list>
          </property>
          <property name="target">
               <ref bean="kwikEMartTarget" />
          </property>
     </bean>

</beans>

运行,结果如下:
店员:悠~游! ,Can I help you ?
--我想买:果酱!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
--我想买:胡椒粉!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
--我想买:奶酪!
        店员:OK! 悠~游!.give you!

在这里简单说明一下xml文档:nameMatchfilterPointcut和regexpFilterPointcut 是我们自己定义好规则的Pointcut。nameMatchfilterPointcut 根据mappedName来设置过滤规则, regexpFilterPointcut则是用pattern来设置过滤规则。runDemofilterPointcutAdvisor则将我们的Pointcut和advice组合在一起。
读者可以自己修改runDemofilterPointcutAdvisor的pointcut来切换不同的Pointcut。如果需要RegexpMethodPointcut 的子类的实现,必须要oro包支持。(注:RegexpMethodPointcut有俩个子类的实现,JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut)。
     但是,如果我们想让我们的Advisor同时实现多个Pointcut+advice怎么办呢?利用Spring In Action里面的实例,我们来自己实现我们的Pointcut。

    代码:
 

public class MyUnionPointcut implements Pointcut {
    
     private Pointcut delegate;

     public ClassFilter getClassFilter() {
          return getDelegate().getClassFilter();
     }

     private Pointcut getDelegate() {
          if (delegate == null) {
               throw new AopConfigException("No pointcuts have been configured.");
          }
          return delegate;
     }

     public MethodMatcher getMethodMatcher() {
          return getDelegate().getMethodMatcher();
     }

     public void setPointcuts(List pointcuts) {

          if (pointcuts == null || pointcuts.size() == 0) {
               throw new AopConfigException("Must have at least one Pointcut.");
          }
          delegate = (Pointcut) pointcuts.get(0);

          for (int i = 1; i < pointcuts.size(); i++) {
               Pointcut pointcut = (Pointcut) pointcuts.get(i);
               delegate = Pointcuts.union(delegate, pointcut);
          }

     }
}

其实就是继承Pointcut类,实现getMethodMatcher()方法即可,接下来看我们把那两个Pointcut组合成一个,当其中一个Pointcut满足的时候就返回true,调用我们的Advice。

在xml中,加入下面代码:
 

     <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
          <property name="pointcuts">
               <list>
               <!-- 这里说明一下:动态切入点和静态切入点集合在一起的时候好像有问题? -->
                    <ref bean="nameMatchfilterPointcut" />
                    <ref bean="regexpFilterPointcut" />
               </list>
          </property>
     </bean>
修改runDemofilterPointcutAdvisor的pointcut来加入我们组合好的Pointcut。

     <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
          <property name="pointcut">
               <ref bean=”myUnionPointcut" />
          </property>
          <property name="advice">
               <ref bean="onePerCustomerInterceptor" />
          </property>
     </bean>

同时在运行前,读者可以自己修改两个pointcut的匹配方法,来匹配更多的可选项。同时可以参考的一个类是org.springframework.aop.support.UnionPointcut,它实现两个pointcut的联合。如果读者想实现更加灵活的匹配,需要自己来定义自己的pointcut。(如第一个pointcut交叉第二个pointcut,联合第三个pointcut),交叉的工具类为ComposablePointcut。
运行的结果请读者自己试验。这个时候您可能在想,这些pointcut都是Spring自己的实现,我们能否自己来定义我们自己规则的pointcut呢?当然可以!~
代码:
/**
*
* 自己定义的静态切入点
* 满足条件是:当传入的数字大于1块钱的时候才可以;
*
* @author 悠~游
* @since 2006-06-22
* @link www.uusam.com
*/
public class MyPointcut extends StaticMethodMatcherPointcut implements Serializable {

     private static final long serialVersionUID = -101281038294508751L;

     private int money = 0;

     /**
      * 实现方法,业务逻辑为:根据输入的钱数和动作(必须是以buy开头的方法),来确定是否有钱购买商品,买完之后去掉商品的价格;
      */
     public boolean matches(Method method, Class targetClass) {
          if (method.getName().indexOf("buyCheese") == 0 && money >= 100) {
               money -= 100;
               return true;
          } else if (method.getName().indexOf("buyPepper") == 0 && money >= 5) {
               money -= 5;
               return true;
          } else if (method.getName().indexOf("buySquish") == 0 && money >= 10) {
               money -= 10;
               return true;
          }
          System.out.println("门卫:你要买的东西太贵,你的钱 "+money+" 太少!~ ,取消服务!");
          return false;
     }

     public void setMoney(int money) {
          this.money = money;
     }

}

这个就是我们自己定义的静态Pointcut,主要实现自己的matches方法。看看如何加入到我们的XML文档中:

     <!-- 使用自定义的切入点 -->
     <bean id="myPointcut" class="demo.pointcut.MyPointcut">
          <property name="money">
               <value>100</value>
          </property>
     </bean>

很简单不是么?我们定义一个数字,就是用户的money,进入商店时候兜里的钱^_^。同样修改我们的myUnionPointcut里面的pointcuts,加入我们的pointcut。

    
<bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
          <property name="pointcuts">
               <list>
                    <!—上面两个要设置成不通过哦,或者索性就去掉先。 -->
                    <ref bean="nameMatchfilterPointcut" />
                    <ref bean="regexpFilterPointcut" />
<ref bean="myPointcut" />
               </list>
          </property>
     </bean>
    
当上面两个Pointcut定义的规则不通过的时候,程序开始校验我们的myPointcut。运行,结果如下:
店员:悠~游! ,Can I help you ?
--我想买:果酱!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
--我想买:胡椒粉!
店员:OK! 悠~游!.give you!
门卫:你要买的东西太贵,你的钱 85 太少!~ , 取消服务!
--我想买:奶酪!//服务员没了...
    
     好了,是不是我们想要的结果呢?呵呵。
     同时,Spring 提供动态Pointcut。关于动态的说明我就不在熬述了,我们这里只关心具体Spring带给我们的具体实现方法,具体应用请读者自己斟酌使用。
    

     <!-- 定制动态接入点,来判断当前线程堆栈中是否有demo.RunDemo这个类; -->
     <bean id="runDemoPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
          <constructor-arg>
               <value>demo.RunDemo</value>
          </constructor-arg>
     </bean>
     修改下面的引用我们的动态Pointcut;
<bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
          <property name="pointcuts">
<list>
     <ref bean=" runDemoPointcut " />
         </list>
    </property>
</bean>

     运行,结果如下:
店员:悠~游! ,Can I help you ?
--我想买:果酱!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
--我想买:胡椒粉!
店员:OK! 悠~游!.give you!
店员:悠~游! ,Can I help you ?
--我想买:奶酪!
店员:OK! 悠~游!.give you!
    
     动态切入点是根据当前堆栈信息进行方法匹配的一种规则,读者可以自己修改demo.RunDemo,如java.lang.Integer,来看看结果。

--我想买:果酱!
--我想买:胡椒粉!
--我想买:奶酪!
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,AOP,xml,应用服务器,bean)