-
2008-06-19 22:58:08版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。 http://robert.blog.51cto.com/374512/83040一、基本概念:(1)目标对象(target)就是被代理的对象,也就是具体的业务逻辑。比如OrderService(2)切面 (Aspect)advisor把advice和pointcut组装起来的东西就是切面(3)连接点 (Jointpoint)切面可以插入的地点,主要有方法、属性(4)切入点 (Pointcut)指定哪些连接点可以应用切面/通知(5)通知(Advice)(6)advisor ---->他其实就是一个装配器,负责把advice和poincut关联起来切面的具体实现二、基本使用1.代理1)ProxyFactoryBeanSpring的AOP实现是基于代理的,而在Spring里创建一个AOP代理的基本方法就是使用org.springframework.aop.framework.ProxyFactoryBeanProxyFactoryBean的各个属性,参照Spring参考手册:7.5.2. JavaBean属性这里的配置重点是目标与拦截器。实际创建的代理类型,有两种:代理接口和代理类。更详细的解释需要参照Spring参考手册:7.5.3. 基于JDK和CGLIB的代理。下面我们看一下具体的配置。
(1)代理接口(JDK动态代理):< bean id ="person"
class ="org.springframework.aop.framework.ProxyFactoryBean" >
<!-- 需要被代理的接口 -->
< property name ="proxyInterfaces" >
< value >com.mycompany.Person </ value >
</ property >
<!-- 目标对象 -->
< property name ="target" >
< ref local ="personTarget" />
</ property >
<!-- 需要被应用的通知或拦截器,这里的顺序是很重要的 -->
< property name ="interceptorNames" >
< list >
< value >myAdvisor </ value >
< value >debugInterceptor </ value >
</ list >
</ property >
</ bean >
目标对象也可以用匿名内部bean的方式使用:< bean id ="person" class ="org.springframework.aop.framework.ProxyFactor Bean" >
< property name ="proxyInterfaces" > < value >com.mycompany.Person </ value > </ property >
<!-- Use inner bean, not local reference to target -->
< property name ="target" >
< bean class ="com.mycompany.PersonImpl" >
< property name ="name" > < value >Tony </ value > </ property >
< property name ="age" > < value >51 </ value > </ property >
</ bean >
</ property >
< property name ="interceptorNames" >
< list >
< value >myAdvisor </ value >
< value >debugInterceptor </ value >
</ list >
</ property >
</ bean >(2)代理类(CGLIB代理):
目标类不存在接口,只能使用CGLIB代理,去掉proxyInterfaces属性设置proxyTargetClass属性设为true或者忽略这个属性目标类即使存在接口,也强制使用CGLIB代理:设置proxyTargetClass属性设为true,甚至proxyInterfaces属性被设置的情况下仍然将实际使用基于CGLIB的代理。
“全局”advisor通过在一个拦截器名后添加一个星号,所有bean名字与星号之前部分相匹配的Advice都将被加入到advisor链中。< bean id ="proxy" class ="org.springframework.aop.framework.ProxyFactoryBean" >
< property name ="target" ref ="service" />
< property name ="interceptorNames" >
< list >
< value >globa * </ value >
</ list >
</ property >
</ bean >
< bean id ="global_debug" class ="org.springframework.aop.interceptor.DebugInterceptor" />
< bean id ="global_performance" class ="org.springframework.aop.interceptor.PerformanceMonitorInterceptor" />
2)TransactionProxyFactoryBean使用TransactionProxyFactoryBean时,我们通常都会用简化代理的方式进行配置。你也许需要许多相似的代理定义,特别是定义事务性代理的时候。使用父子bean定义,以及内部bean定义,可以让代理定义大大得到极大的简化。请参照Spring参考手册:7.6. 简化代理定义
2.Advisor预先了解的概念:Advisor=Advice+Pointcut,所以这里的配置重点是如何把Advice和Pointcut集成到一起。除了Introduction Advice(和DefaultIntroductionAdvisor一起使用),任何advisor都可以和任何Advice一起工作。具体的配置示例如下:1)NameMatchMethodPointcutAdvisor(内部使用NameMatchMethodPointcut)< bean id ="helloAdvisor"
class ="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" >
< property name ="mappedName" value ="*Newbie" />
< property name ="advice" ref ="logBeforeAdvice" />
</ bean >
2)RegexpMethodPointcutAdvisor在背后,如果使用J2SE 1.4或者以上版本,Spring将使用JdkRegexpMethodPointcut,在之前版本的虚拟机上,Spring将退回去使用Perl5RegexpMethodPointcut。配置示例:< bean id ="regExpAdvisor"
class ="org.springframework.aop.support.RegexpMethodPointcutAdvisor" >
< property name ="pattern" value =".*Newbie" />
< property name ="advice" ref ="logBeforeAdvice" />
</ bean >
3)DefaultPointcutAdvisor(使用外部注入的Pointcut)< bean id ="defaultAdvisor"
class ="org.springframework.aop.support.DefaultPointcutAdvisor" >
< constructor-arg ref ="adviceBean" />
< constructor-arg ref ="poingcutBean" />
</ bean >或< bean id ="defaultAdvisor"
class ="org.springframework.aop.support.DefaultPointcutAdvisor" >
< property name ="advice" ref ="adviceBean" > </ property >
< property name ="pointcut" ref ="pointcutBean" > </ property >
</ bean >3.Advice这里的重点是编程实现相应的接口。具体参照Spring参考手册,摘要如下:Around Advice--实现MethodInterceptor接口--参照Spring参考手册:7.3.2.1. 拦截around通知Before Advice --实现BeforeAdvice接口 --参照Spring参考手册:7.3.2.2. 前置通知Throw Advice --实现ThrowsAdvice接口 --参照Spring参考手册:7.3.2.3. 异常通知After Returning Advice--实现AfterReturningAdvice接口--参照Spring参考手册:7.3.2.4. 后置通知Introduction Advice --实现IntroductionInterceptor接口,并使用DefaultIntroductionAdvisor织入Advice或者,继承DelegatingIntroductionInterceptor,并使用DefaultIntroductionAdvisor织入Advice。Spring 把引入通知(introduction advice)作为一种特殊的拦截通知进行处理。--参照Spring参考手册:7.3.2.5. 引入通知
4.切入点PointCut孤立的PointCut没有什么用处,必须结合Advisor一起使用,PointCut和DefaultPointcutAdvisor的结合使用,可以参照: http://www.easyjf.com/bbs.ejf?cmd=appShow&id=45547521)静态切入点(1)正则表达式切入点包括Perl5RegexpMethodPointcut和JdkRegexpMethodPointcut两者都是StaticMethodMatcherPointcut的子类, 不限制类,只限制方法Perl5RegexpMethodPointcut依赖Jakarta ORO进行正则表达式匹配,需要把 jakarta-oro-xx.jar 文件放到 classpath 上。JdkRegexpMethodPointcut需要在 JDK1.4 及以上的环境运行,不需要额外的库他们均有2个属性:(1) pattern或patterns:前者表示单个正则表达式,后置表示多个正则表达式,支持<list>配置;(2) ExcludedPattern或ExcludedPatterns:前者表示排除某个字符串,后者表示排除一组字符串,支持<list>配置;配置示例:< bean id ="pointcutBean"
class ="org.springframework.aop.support. JdkRegexpMethodPointcut" >
< property name ="pattern" >
< value >.*business.* </ value >
</ property >
< property name ="ExcludedPattern" >
< value >business2 </ value >
</ property >
</ bean >或< bean id ="pointcutBean"
class ="org.springframework.aop.support. Perl5RegexpMethodPointcut" >
< property name ="pattern" >
< value >.*business.* </ value >
</ property >
< property name ="ExcludedPattern" >
< value >business2 </ value >
</ property >
</ bean >
(2)NameMatchMethodPointcutStaticMethodMatcherPointcut的子类, 不限制类,只限制方法NameMatchMethodPointcut只有一个属性mappedName或者mappedNames,前者表示映射单个字符串,后者表示映射一组字符串,支持<list>配置< bean id ="pointcutBean" class ="org.springframework.aop.support.NameMatchMethodPointcut" >
< property name ="mappedNames" >
< list >
< value >business* </ value >
</ list >
</ property >
</ bean >
(3)ExpressionPointcut接口不是StaticMethodMatcherPointcut的子类, 可以既限制类,又限制方法在 Spring2 中,在 Pointcut 的基础上,引入了一个 ExpressionPointcut 接口用来通过切入点表达语言来描述切入点。有了 ExpressionPointcut,我们可以使用下面更加简单的方式来描述切入点,如 execution(* Component.business*(..))表示执行所有 Component 的业务方法(此处为 business 打头的方法)。Spring2 提供了一个 ExpressionPointcut 的实现,即 AspectJExpressionPointcut,该类的使用很简单,只需要做如下配置即可:< bean id ="pointcutBean"
class ="org.springframework.aop.aspectj.AspectJExpressionPointcut" >
< property name ="expression"
value ="execution(void spring.chapter3.proxy.Component.business*(..))" >
</ property >
</ bean >
属性驱动的切入点一个重要的静态切入点是元数据驱动(metadata-driven)切入点。这使用元数据参数:特别是源代码级别的元数据。2)动态切入点控制流切入点(ControlFlowPointcut)-- 既限制类,又限制方法如果有这样的需求:我们对一个方法进行切入通知,但只有这个方法在 一个特定方法中被调用的时候执行通知,我们可以使用ControlFlowPointCut流程切入点
当BeanOne的方法被 Test类的runfoo方法调用时,织入通知。BeanOne.java
package study.springAop;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class BeanOne {
private Logger logger =
Logger.getLogger( this.getClass().getName());
public void foo(){
logger.log(Level.INFO, "executing==>foo-one");
}
public void bar(){
logger.log(Level.INFO, "executing==>bar-one");
}
}
SimpleAdvise.javapackage study.springAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SimpleAdvise
implements MethodInterceptor {
private Logger logger =
Logger.getLogger( this.getClass().getName());
public Object invoke(MethodInvocation invocation) throws Throwable {
logger.log(Level.INFO, "before>>>");
Object retVal=invocation.proceed();
logger.log(Level.INFO, "after<<<");
return retVal;
}
}
Test.javapackage study.springAop;
public class Test {
BeanOne beanOne = null;
public void setBeanOne(BeanOne beanOne) {
this.beanOne = beanOne;
}
public void runfoo() {
beanOne.foo();
beanOne.bar();
}
public void runbar() {
beanOne.bar();
beanOne.foo();
}
public void runfoo(BeanOne pBeanOne) {
pBeanOne.foo();
pBeanOne.bar();
}
public void runbar(BeanOne pBeanOne) {
pBeanOne.bar();
pBeanOne.foo();
}
}
beans-config.xml<? xml version ="1.0" encoding ="UTF-8" >
< beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" >
< bean id ="myTest" class ="study.springAop.Test" >
< property name ="beanOne" ref ="proxyOne" > </ property >
</ bean >
<!-- 配置DefaultPointcutAdvisor-->
< bean id ="myFlowControlPointcut"
class ="org.springframework.aop.support.ControlFlowPointcut" >
< constructor-arg value ="study.springAop.Test" />
< constructor-arg value ="runfoo" />
</ bean >
< bean id ="myAdvise" class ="study.springAop.SimpleAdvise" />
< bean id ="myAdvisor"
class ="org.springframework.aop.support.DefaultPointcutAdvisor" >
< property name ="advice" ref ="myAdvise" />
< property name ="pointcut" ref ="myFlowControlPointcut" />
</ bean >
<!-- 配置代理-->
< bean id ="beanOne" class ="study.springAop.BeanOne" />
< bean id ="proxyOne"
class ="org.springframework.aop.framework.ProxyFactoryBean" >
<!-- 目标类没有实现任何接口,一个基于CGLIB的代理将被创建 -->
< property name ="target" ref ="beanOne" />
< property name ="interceptorNames" >
< list >
< value >myAdvisor </ value >
</ list >
</ property >
</ bean >
</ beans >
log4j.propertieslog4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] %-13c{1}:%L - %m%n
package study.springAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class SpringAOPDemo {
private static Logger logger =
Logger.getLogger(SpringAOPDemo. class.getName());
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans-config.xml");
Test myTest = (Test) context.getBean( "myTest");
BeanOne proxyOne = (BeanOne) context.getBean( "proxyOne");
logger.log(Level.INFO, "/n the flowing doesn't work...");
// 直接调用
logger.log(Level.INFO, "不通过Test类调用BeanOne的方法...");
proxyOne.foo();
proxyOne.bar();
// 通过runbar方法调用
logger.log(Level.INFO, "通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runbar(proxyOne);
logger.log(Level.INFO, "通过Test类的runbar()方法调用BeanOne的方法...");
myTest.runbar();
logger.log(Level.INFO, "/n the flowing works...");
// 通过runfoo方法调用
logger.log(Level.INFO, "通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...");
myTest.runfoo(proxyOne);
logger.log(Level.INFO, "通过Test类的runfoo()方法调用BeanOne的方法...");
myTest.runfoo();
}
}
INFO [main] SpringAOPDemo:18 -
the flowing doesn't work...
INFO [main] SpringAOPDemo:20 - 不通过Test类调用BeanOne的方法...
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SpringAOPDemo:24 - 通过Test类的runbar(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:26 - 通过Test类的runbar()方法调用BeanOne的方法...
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SpringAOPDemo:28 -
the flowing works...
INFO [main] SpringAOPDemo:30 - 通过Test类的runfoo(BeanOne pBeanOne)方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SpringAOPDemo:32 - 通过Test类的runfoo()方法调用BeanOne的方法...
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :10 - executing==>foo-one
INFO [main] SimpleAdvise :15 - after<<<
INFO [main] SimpleAdvise :13 - before>>>
INFO [main] BeanOne :13 - executing==>bar-one
INFO [main] SimpleAdvise :15 - after<<<
3)AspectJ切入点表达式语言从2.0开始,Spring中使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut。而且,使用AspectJ切入点表达式通常会更简单一些。格式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)更详细的例子参考:6.2.3.4. 示例三、高级使用1.自动代理1)BeanNameAutoProxyCreator为名字匹配字符串或者通配符的bean自动创建AOP代理。主要目的是把相同的配置一致地应用到多个对象,并且使用最少量的配置。< bean class ="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
< property name ="beanNames" > < value >jdk*,onlyJdk </ value > </ property >
< property name ="interceptorNames" >
< list >
< value >myInterceptor </ value >
</ list >
</ property >
</ bean >
2)DefaultAdvisorAutoProxyCreator
这个类的奇妙之处在于它实现了 BeanPostProcessor 接口。当 ApplicationContext 读入所有 Bean 的配置信息后,DefaultAdvisorAutoProxyCreator 将扫描上下文,寻找所有的 Advisor 。它将这些 Advisor 应用到所有符合 Advisor 切入点的 Bean 中。这个代理创建器只能与 Advisor 配合使用 。通常自动代理的好处是它让调用者或者被依赖对象不能得到一个没有通知过的对象。
在下面例子中,在ApplicationContext上调用getBean("businessObject1")将返回一个AOP代理,而不是目标业务对象。< bean class ="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
< bean class ="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor" >
< property name ="transactionInterceptor" ref ="transactionInterceptor" />
</ bean >
< bean id ="customAdvisor" class ="com.mycompany.MyAdvisor" />
< bean id ="businessObject1" class ="com.mycompany.BusinessObject1" >
<!-- Properties omitted -->
</ bean >
< bean id ="businessObject2" class ="com.mycompany.BusinessObject2" />
DefaultAdvisorAutoProxyCreator支持过滤(通过使用一个命名约定让只有特定的advisor被评估,允许在同一个工厂里使用多个不同配置的AdvisorAutoProxyCreator)和排序。关于过滤的说明:It's possible to filter out advisors - for example, to use multiple post processors of this type in the same factory - by setting the usePrefix property to true, in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's bean name followed by a dot (like "aapc.") will be used. This default prefix can be changed from the bean name by setting the advisorBeanNamePrefix property. The separator (.) will also be used in this case.
3)AbstractAdvisorAutoProxyCreator
如果在某些情况下框架提供的DefaultAdvisorAutoProxyCreator不能满足你的需要,你可以通过继承AbstractAdvisorAutoProxyCreator这个类(DefaultAdvisorAutoProxyCreator的父类)来创建你自己的自动代理创建器。4)使用元数据驱动的自动代理参照:JPetStore示例应用程序的/attributes 目录2.使用TargetSourcesProxyFactoryBean的targetSource属性,缺省的实现是对于每次调用代理将返回相同的目标。如果我们想对目标实现 池化(pooling),热切换(hot swappable)等功能时,就需要配置ProxyFactoryBean的targetSource属性了。< bean id ="businessObject" class ="org.springframework.aop.framework.ProxyFactoryBean" >
< property name ="targetSource" ref ="配置为适当的TargetSource" />
< property name ="interceptorNames" value ="myInterceptor" />
</ bean >
如果我们想热切换一个AOP代理的目标-- 配置为HotSwappableTargetSource< bean id ="initialTarget" class ="mycompany.OldTarget" />
< bean id ="swapper" class ="org.springframework.aop.target.HotSwappableTargetSource" >
< constructor-arg ref ="initialTarget" />
</ bean >
如果我们想池化一个AOP代理的目标-- 配置为CommonsPoolTargetSource< bean id ="businessObjectTarget" class ="com.mycompany.MyBusinessObject" scope ="prototype" >
... properties omitted
</ bean >
< bean id ="poolTargetSource" class ="org.springframework.aop.target.CommonsPoolTargetSource" >
< property name ="targetBeanName" value ="businessObjectTarget" />
< property name ="maxSize" value ="25" />
</ bean >
如果我们想为每个进来的请求(即每个线程)创建一个对象-- 配置为ThreadLocalTargetSource< bean id ="threadlocalTargetSource" class ="org.springframework.aop.target.ThreadLocalTargetSource" >
< property name ="targetBeanName" value ="businessObjectTarget" />
</ bean >