您的位置:首页 > 编程语言 > Java开发

Spring Aop Step-By-Step 学习笔记

2008-11-21 14:13 579 查看
最近由于工作需要,要求掌握关于Spring 方面的东西。所以花了两个星期的时间来学习Spring的基本知识,主要包括 Ioc和Aop两方面。
本文为笔者的Spring 在Aop方面的学习笔记,主要结合了Spring In Action 第三章和 Spring-Reference 第五章为学习向导。根据自己的理解和书中的实例来一步一步完成对于在Spring 中Aop 方面的编程。其中基础部分Ioc需要读者自己参考资料了解,本文将不做描述。
说明:我将尽量缩短程序长度,在程序部分将减少注释说明,重点要读者自己根据上下文和程序结果理解体会,具体api信息请读者自己参考Spring-api文档和相关资料。
一. 准备工作:

1. 开发环境:
l 适合人群:
要了解Spring Ioc,对Spring- Aop 可以不了解或者仅仅熟悉Aop概念,未参与Spring Aop开发实战的初学者。同时也希望高手对于本文的不足或理解错误之处给予指点,谢谢。
l 开发环境:
JDK 1.4_2
l 开发工具:
Eclipse 3.12 (未采用任何插件,主要是为初学者熟悉和理解xml文档的配置)
l 所需组件:
Spring-Framework-1.2.8
下载地址:
http://prdownloads.sourceforge.net/springframework/spring-framework-1.2.8-with-dependencies.zip?use_mirror=ufpr

2. 建立工程:
首先用Eclpse 建立一个普通java项目,导入jar文件到编译环境中,如下:
a) Spring.jar 为Spring 的核心jar文件,必须;
b) Commons-loggin.jar 日志文件,必须;
c) Cglib.jar 动态代理文件,不是必须(本文需要);
d) Jak-oro.jar 使用Perl和Awk 正则表达式进行文本解析工具,不是必须(本文需要);
建立工程如下:



好了,下来我们开始我们的Spring-aop之旅;

二. Spring -Aop入门

AOP全名Aspect-oriented programming。Spring framework是很有前途的AOP技术。作为一种非侵略性的,轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是像往常一样编程。
关键性概念:
1) Advice 是代码的具体实现,例如一个实现日志记录的代码。
2) Pointcut 是在将Advice 插入到程序的条件。
3) advisor是把pointcut和advice的组合在一起装配器。

图例:
你的程序可能如上,现在要在三个流程上同时加入日志控制和权限控制,如下:



你的程序可能如上,现在要在三个流程上同时加入日志控制和权限控制,如下:




其中拿日志为例,日志控制和流程之间的穿插点处叫做连接点(Joinpoint),而Advice就是我们日志处理的具体代码,Pointcut就是定义一个规则,对三个和业务有关的连接点进行过滤和匹配(例如我们对于业务1不做日志处理)。Advisor就是将符合的规则的剩下的两个连接点和具体的日志记录代码组合在一起。

三. 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-Reference 中文版第五章 Spring之面向方面编程
http://www.jactiongroup.net/reference/html/

Spring AOP中文教程 http://spring.jactiongroup.net/viewtopic.php?t=478

Spring In Action 中文版 第三章

AOP入門

其他:
全文源码及word教程下载:点击下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: