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("
張三
");
?
?