Spring
中的
AOP
创建时间:
2007年1月27日
星期六
零雨其蒙原创,转载请注明
一、概述
(一)基本概念
1
、什么是AOP?
面向方面编程。所谓方面即是指日志、权限、异常处理、事务处理等。
2
、AOP的3个关键概念
(
1
)切入点(
Pointcut
):
Pointcut
是
Join Point
的集合,
Join Point
就是需要注入
Adivce
的位置,也就是需要插入日志输出代码、事务处理代码等“方面”(
Aspect
,也就是
AOP
中的
A
)代码的地方。
比如我现在要写一个存钱的方法:
saving
()
通常情况下我就得在这个
saving
()方法前后写些事务代码
如:
logger.log(Level.INFO,”start”);
Saving();
logger.log(Level.INFO,”end”);
对于事务代码而言,
saving
()方法的前后就都是
Join Point
了。在
Spring
中它对应
config.xml
中设定的方法,这个方法就是类(
class
)中需要进行某方面处理的方法(
method
)。
(
2
)通知(
Advice
):
就是指
Join Point
对应的代码(方法)。比如日志输出这个方面,指的就是日志输出的代码或方法了。在
Spring
中,它对应类(
class
)。
(
3
)
Advisor
:
是
Poincut
和
Advice
的配置器,它包括
Pointcut
和
Advice
,是将
Advice
注入程序中
Pointcut
位置的代码。在
Sping
中,它对应
config.xml
中的配置段
<bean id=logAdvisor class=”org.springframework.aop.support.RegexpMethodPointcutAdvisor”>
。
<bean id=”logAdvisor” class=”org.springframework.aop.support.RegexpMetho
dPointcutAdvisor”>
//advice
属性对应的当然就是输出日志的类,也就是
①
对应的那个bean
<property name=”advice”>
<ref bean=”log”/>
</property>
//patterns
属性指出指定要代理的方法,使用的是正则表达式
<property name=”patterns”>
<value>.*doAuditing.*</value>
</property>
</bean>
(二)框架图
创建代理的两种方法
|
ProxyFactoryBean
|
动态代理
|
|
Spring4
种
Advice
|
Interception Around
|
Before
|
After Returning
|
Throw
|
|
两种代理方式
|
Java
动态代理
|
CGLIB
代理
|
|
(三)何时使用什么
1
、创建代理的两种方法中首选动态代理。
第
1
种:针对某个类进行配置。可以指定某个类中所有方法都调用方面,也可以指定某个类中的某个方法,此时由于用到正则表达式,于是需要引入
jakarta-oro-2.0.8.jar
包。
第
2
种:针对某个方法进行配置。
2
、Spring4种Advice:
第
1
种:在需要调用方面的方法前后都调用处理方面的代码
第
2
种:在需要调用方面的方法之前调用处理方面的代码
第
3
种:在需要调用方面的方法之后都调用处理方面的代码
第
4
种:在需要调用方面的方法发生异常时调用处理方面的代码
3
、两种代理方式首选第1种。
第
1
种:面向接口,必须先定义接口,这是好的习惯,应该提倡
第
2
种:当没有接口的时候,可以使用这种方法。需引入
cglib-nodep-2.1_3,jar
包。
二、详细
(一)、创建AOP代理的两种方法:
1
、用ProxyFactoryBean创建AOP代理
(需要指明代理目标类)
(
1
)代理目标类的所有方法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
//
下面
interceptorNames
属性(property)中的value值就是这个bean的id,其主要对应的是写入日志的那个类,也就是Spring AOP概念中的Advice(通知)。
①
<bean id="log" class="com.gc.action.LogAround"/>
//
要输出日志的那个类(因为这种方法必须要指明代理目标类)
②
<bean id="timeBook" class="com.gc.action. timeBook "/>
//
下面开始定义代理类,也就是ProxyFactoryBean,这是Spring自带的类,这也是Spring AOP中的Advisor
<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean
”>
//
第一个属性,是指明要代理的类的接口,因为这个例子中使用的是Java动态代理机制来实现AOP的,因此必须指明接口
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>
//
这个属性是指明代理目标(target)类,也就是
②
定义的那个类
<property name=”target”>
<ref bean=”timeBook”/>
</property>
//
这个属性是用来指明插入哪个Advice,此处使用list,应该表示这个类不只是可以调用这一个log类
<property name=”interceptorNames”>
<list>
<value>log</value>
//
这个值(log)对应
①
中定义的那个id为log的bean
</list>
</property>
</bean>
</beans>
(
2
)代理目标类的指定方法
<?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="log" class="com.gc.action.LogAround"/>
<bean id="timeBook" class="com.gc.action. timeBook "/>
//
在上一段配置文件中添加了下面这个bean,用来指明要输出日志的指定方法(上一个例子是所有方法都输出日志)
<bean id=”logAdvisor” class=”org.springframework.aop.support.RegexpMetho
dPointcutAdvisor”>
//advice
属性对应的当然就是输出日志的类,也就是
①
对应的那个bean
<property name=”advice”>
<ref bean=”log”/>
</property>
//patterns
属性指出指定要代理的方法,使用的是正则表达式
<property name=”patterns”>
<value>.*doAuditing.*</value>
</property>
</bean>
<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean
”>
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name=”target”>
<ref bean=”timeBook”/>
</property>
<property name=”interceptorNames”>
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
2
、用DefaultAdvisorAutoProxyCreator创建自动代理
(好处:不用指明代理目标类,如果一个大项目中有很多类也不必一个一个设置
AOP
代理)
<?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="log" class="com.gc.action.Log
Aop
"/>
<bean id="timeBook" class="com.gc.action. timeBook "/>
//
使用DefaultAdvisorAutoProxyCreator(红色代码)替代ProxyFactoryBean(绿色代码),因为绿色代码的作用是为具体的类(即所谓代理目标类)设置advice。
<bean id=”autoProxyCreator” class=”org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator”>
<bean id=”logAdvisor
” class=”org.springframework.aop.support.RegexpMetho
dPointcutAdvisor”>
<property name=”advice”>
<ref bean=”log”/>
</property>
<property name=”patterns”>
<value>.*doAuditing.*</value>
</property>
</bean>
/*<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean”>
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name=”target”>
<ref bean=”timeBook”/>
</property>
<property name=”interceptorNames”>
<list>
<value>log</value>
</list>
</property>
</bean>*/
</beans>
3
、总结
实际上,
DefaultAdvisorAutoProxyCreator
和
ProxyFactoryBean
就是两种代理类,前者是自动的将
Advisor
和目标类联系起来,后者是通过指定的方式,将目标类和
Advisor
组合起来。
而
advisor
,对应的就是
org.springframework.aop.support.RegexpMethodPointcutAdvisor
,通过正则表达式来匹配类中的方法(设定
Pointcut
)。
(二)Spring四种通知(Advice)形式
1
、Interception Around通知
(
1
)负责输出日志的类
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class LogAround implements MethodInterceptor{
private Logger logger = Logger.getLogger(this.getClass().getName());
public Objectinvoke(MethodInvocation methodInvocation) throws Throwable {
logger.log(Level.INFO, methodInvocation.getArguments()[0] + "
开始审核数据....");
try {
Object result = methodInvocation.proceed();
return result;
}
finally {
logger.log(Level.INFO, methodInvocation.getArguments()[0] + "
审核数据结束....");
}
}
}
(
2
)配置文件
<?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="log" class="com.gc.action.Log
Aop
"/>
<bean id="timeBook" class="com.gc.action. timeBook "/>
<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean”>
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name=”target”>
<ref bean=”timeBook”/>
</property>
<property name=”interceptorNames”>
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
2
、Before通知
(
1
)负责输出日志的类
import java.lang.reflect.Method;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;
public class LogBefore implements MethodBeforeAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void before(Method method, Object[] args, Object target) throws Throwable {
logger.log(Level.INFO, args[0] + "
开始审核数据
....");
}
}
(
2
)配置文件
与第
1
种方法相同。
3
、After Returning通知
(
1
)负责输出日志的类
import java.lang.reflect.Method;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.aop.AfterReturningAdvice;
public class LogAfterReturning implements AfterReturningAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterReturning(Method method, Object[] args, Object target) throws Throwable {
logger.log(Level.INFO, args[0] + "
开始审核数据
....");
}
}
(
2
)配置文件
与第
1
种方法相同。
4
、Throw通知
(
1
)负责输出日志的类
import java.lang.reflect.Method;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.springframework.aop.ThrowsAdvice;
public class LogThrow implements ThrowsAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterThrowing(Method method, Object[] args, Object target,Throwable subclass) throws Throwable {
logger.log(Level.INFO, args[0] + "
开始审核数据
....");
}
}
(
2
)配置文件
与第
1
种方法相同。
5
、测试程序
(
1
)使用自动代理
public class TestHelloWorld {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("timeBook");
timeBookProxy.doAuditing("
张三
");
(
2
)不使用自动代理
public class TestHelloWorld {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("logProxy");
timeBookProxy.doAuditing("
张三
");
使用自动代理,则直接调用该类的名字(
timeBook
),否则调用相应的代理
bean
(
logProxy
)。因为第
1
种方法中根本就没有对应的代理
bean
,只有一个
Spring
的自动代理类
(三)Spring两种代理方式
1
、Java动态代理
(
1
)配制文件(不使用自动代理)
<?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="log" class="com.gc.action.Log
Aop
"/>
<bean id="timeBook" class="com.gc.action. timeBook "/>
<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean”>
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>
<property name=”target”>
<ref bean=”timeBook”/>
</property>
<property name=”interceptorNames”>
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
(
2
)测试程序
public class TestHelloWorld {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("logProxy");
timeBookProxy.doAuditing("
张三
");
2
、CGLIB代理
(
1
)配制文件(不使用自动代理)
<?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="log" class="com.gc.action.Log
Aop
"/>
<bean id="timeBook" class="com.gc.action. timeBook "/>
<bean id=”logProxy” class=”org.springframework.aop.framework.ProxyFactor
yBean”>
//
增加如下属性,就表示使用的是CGLIB代理(对目标类直接代理)
<property name=”proxyTargetClass”>
<value>true</value>
</property>
/*
然后去掉下面的属性,也就是说此种方法不需要面向接口,或不需要指出接口
<property name=”proxyInterfaces”>
<value>com.gc.impl.TimeBookInterface</value>
</property>*/
<property name=”target”>
<ref bean=”timeBook”/>
</property>
<property name=”interceptorNames”>
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
(
2
)测试程序
public class TestHelloWorld {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ApplicationContext actx=new FileSystemXmlApplicationContext("config.xml");
TimeBookInterface timeBookProxy = (TimeBookInterface)actx.getBean("logProxy");
timeBookProxy.doAuditing("
张三
");