五. 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方法中加入我们的方法: 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方法中去: 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 入門 其他: 全文源码及word教程下载:点击下载