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