五. Spring-Aop 引入的介紹
下面我們介紹一種通知“引入”,關(guān)于引入,如同它的名字一樣,給對象添加方法和屬性。呵呵,好厲害吧。它是通過CBLIB來動態(tài)生成類的,所以自己用的時候別忘了加載這個包。
代碼:
購物時候放東西的包包;
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"處處可見,因為我們要每個新的對象都有自己引入的狀態(tài),不可能只有一個對象來維持,那個我們肯定是不希望的。
修改我們的RunDemo類,添加一個方法:
/**
* 創(chuàng)建引用通知;
*/
public static void customer() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo/customer.xml");
// 這個類是有CGLIB動態(tài)生成的;
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中調(diào)用這個方法,運行,結(jié)果如下:
悠~游!
1
在這里我要說明一下,關(guān)于引入這個通知的使用我僅僅是看了一眼,具體的例子可能有不恰當(dāng)之處還請高手們指點。
六. Spring-Aop 之 BeanNameAutoProxyCreator
BeanNameAutoProxyCreator 看其名,大概知道其意。根據(jù)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(" 警告:調(diào)用的方法所耗費的時間超過預(yù)警時間(" + thresholdInMillis + " 微秒)");
}
System.out.println(" 測試時間結(jié)束.");
return o;
}
}
這個又是自定義的,呵呵。我們繼承MethodInterceptor這換類,實現(xiàn)我們自己的方法過濾器。具體要實現(xiàn)的功能就是檢驗我們要調(diào)用的方法,和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方法中加入我們的方法:
/**
* 創(chuàng)建beanNameAutoProxyCreator動態(tài)代理;
*/
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();
}
}
運行我們的例子,結(jié)果如下:
開始測試時間.
-- 我想買:奶酪!
測試時間結(jié)束.
看到了么?Spring aop自動尋找Bean的名字為* Target 的類,進(jìn)行方法過濾。呵呵,可能你會說這個有什么用?自己寫不也一樣么?其實如果系統(tǒng)變得龐大的話,自己配置也是十分耗費精力的。
七. Spring-Aop DefaultAdvisorAutoProxyCreator
接下來我們將介紹更加強(qiáng)大的一個代理器: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>
添加下面方法調(diào)用main方法中去:
/**
* 創(chuàng)建defaultAdvisorAutoProxyCreator動態(tài)代理;
*/
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();
}
}
運行,結(jié)果如下:
開始測試時間.
-- 我想買:奶酪!
測試時間結(jié)束.
八. Spring-Aop TargetSources 介紹
1. 可熱交換的目標(biāo)源
可熱交換的目標(biāo)源主要是在你程序運行中切換目標(biāo)對象,而此時調(diào)用者引用的對象也會自動切換。具體的概念你可以參考Spring-Reference關(guān)于它的介紹,我們主要在程序中體會它給我們帶來的改變。
修改我們的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了,同時在代理工廠里面設(shè)置:
//<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) {
// 異常已經(jīng)被截獲了,不信你看控制臺!~;
}
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) {
// 異常已經(jīng)被截獲了,不信你看控制臺!~;
}
}
運行,結(jié)果如下:
店員:悠~游! ,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!
可以看到,我們切換后的對象已經(jīng)被置換了。注意 singleton="false" ,通常情況下需要設(shè)置為false,以保證Spring在必要的時候可以創(chuàng)建一個新的目標(biāo)實例。
2. 支持池的目標(biāo)源
使用支持目標(biāo)池的源提供了一種和無狀態(tài) session Ejb 類似的編程方式,在無狀態(tài)的 Session Ejb 中,維護(hù)了一個相同實例的池,提供從池中獲取可用對象的方法。
這次我們用到了 Commons - pool 這個包,同時在運行時候還需要 commons-collections.jar, 需要把它們加載到環(huán)境變量中。
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中。
代碼:
/**
* 調(diào)用池對象;
*/
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());
}
運行,結(jié)果如下:
-- 我想買:奶酪!
池的大小為:10
同時在我們的控制臺里可以看到這句話:
信息: Creating Commons object pool
你可以得到對象,也可以銷毀對象。但是必須你的目標(biāo)對象實現(xiàn)了 DisposableBean 接口,重寫銷毀的方法。然后通過下面方法調(diào)用:
CommonsPoolTargetSource comPool = (CommonsPoolTargetSource) context.getBean("commonsPool");
try {
comPool.destroyObject(akem);
} catch (Exception e) {
e.printStackTrace();
}
銷毀池則用:
comPool.destroy() ;
九. Spring-Aop 相關(guān)及其他
其他相關(guān)的比較重要的是org.springframework.aop.framework.ProxyFactoryBean 類,幾乎我們上篇都用到了這個類,簡單介紹一下:
屬性
|
描述
|
target
|
代理的目標(biāo)對象
|
proxyInterfaces
|
代理實現(xiàn)的接口
|
interceptorNames
|
在應(yīng)用到的目標(biāo)對象上添加的Advice的名字,可以是攔截器、advisor或者其他通知類型的名字(順序很重要哦)
|
其他還有singleton,proxyTargetClass。
singleton :每次調(diào)用getBean()的時候返回一個新的實例,例如我們使用引入的時候,有狀態(tài)的bean要設(shè)置為false哦。
proxyTargetClass :是否代理目標(biāo)類,而不是實現(xiàn)接口。只能在使用CBLIB的時使用。
proxyTargetClass 重點說一下,什么意思呢?白話說的意思就是:如果你不設(shè)置proxyInterfaces這個,就必須設(shè)置這個方法,并且方法值為True。就是告訴CBLIB你要動態(tài)創(chuàng)建一個代理類來引用我們的目標(biāo)。
在 Spring-Reference 中,提到了事務(wù)代理,我想那個相對于持久化處理時候在了解比較合適。 對于元數(shù)據(jù)的支持,因為我還沒有精力讀到哪里。所以這個暫且擱下,有興趣的讀者可以自己查閱參考。
非常感謝你能讀完正篇文章,將不足的地方通過郵件傳遞給我,我將盡快改正。最后我希望,能夠讓每一個讀過的人都有所獲得,讓我們享受Spring給我們帶來的樂趣吧。
參考資料;
Spring-Reference 中文版 第五章 Spring 之面向方面編程
Spring In Action 中文版 第三章
AOP 入門