1 AOP簡介
①簡介
AOP(Aspect Oriented Programming) 面向切面程式設計,是對一類或是所有對象程式設計。
核心:在不增加、改變代碼的基礎上,增加新的功能。
aop程式設計,在架構開發中,應用很多;在實際開發中,用的不是很多。
程式設計階段:面向機器(彙編)->面向過程->面向對象->面向切面
②相關術語
- 切面(aspect):要實作的交叉功能,是系統子產品化的一個切面或領域。如日志記錄。
- 連接配接點:應用程式執行過程中插入切面的地點,可以使方法調用,異常抛出,或是要修改的字段。
- 通知:切面的實際實作,他通知系統新的行為。如在日志通知包含了實作日志功能的代碼,如向日志檔案寫内容。通知在連接配接點插入到應用系統中。
- 切入點:定義了通知應該應用在哪些連接配接點,通知可以應用到AOP架構支援的任何連接配接點。
- 引入:為類添加新的方法和屬性
- 目标對象:被通知的對象。既可以是你編寫的類也可以是第三方類。
- 代理:将通知應用到目标對象後建立的對象,應用系統的其他部分不用為了支援代理對象而改變。
-
織入:将切面應用到目标對象,進而建立一個新的代理對象的過程。織入發生在目标對象生命周期的多個點上:
編譯期:切面在目标對象編譯時織入,這需要一個特殊的編譯器
類裝載期:切面在目标對象被裝載入JVM時織入,這需要一個特殊的類裝載器
運作期:切面在應用系統運作時織入
③AOP實作細節
spring的運作時通知對象;
spring在運作期間建立代理,不需要特殊的編譯器。
spring有兩種代理方式:
1 若目标對象實作了若幹接口
spring使用JDK的java.lang.reflect.Proxy類代理。該類讓spring動态産生一個新類,它實作了所需的接口,織入通知,并且代理對目标對象的所有請求。
2 若目标沒有實作任何接口
spring使用CGLIB庫生成目标對象的子類。使用該方式時需要注意。
- 對接口建立代理優先對類建立代理,因為會産生更加松耦合的系統;對類代理是讓遺留系統或無法實作接口的第三方類庫同樣可以得到通知。這種方式應該是備用方案。
- 标記為final的方法不能夠被通知。spring是為目标類産生子類。任何需要被通知的方法都被複寫,将通知織入。final方法是不允許重寫的。
spring實作了aop聯盟接口。
**spring隻支援方法連接配接點;不提供屬性連接配接點。**spring的觀點是屬性攔截破壞了封裝。面向對象的概念是對象自己處理工作,其他對象隻能通過方法調用得到的結果。
④定義切入點,可以使用正規表達式
<!-- 定義前置通知的切入點 -->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="myMethodBeforeAdvice"/>
<property name="mappedNames">
<list>
<!-- 隻對sayBye方法通知 -->
<value>say*</value>
</list>
</property>
</bean>
2 原理+快速入門案例
開發步驟:
- 定義接口
- 編寫對象(被代理對象==目标對象)
- 編寫通知(前置通知- - -目标方法調用前調用)
- 在beans.xml檔案中配置
- 配置被代理對象
- 配置通知
- 配置代理對象,是ProxyFactoryBean的對象執行個體
- - 代理接口集proxyInterfaces
- - 織入通知interceptorNames
- - 配置被代理對象target
①定義接口
public interface TestServiceInter {
public void sayHello();
}
public interface TestServiceInter2 {
public void sayBye();
}
②編寫對象- - -被代理對象=目标對象
public class Test1Service implements TestServiceInter, TestServiceInter2 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 連接配接點(靜态)
@Override
public void sayHello() {
System.out.println("hi " + name);
}
// 連接配接點(靜态)
@Override
public void sayBye() {
System.out.println("bye " + name);
}
}
③編寫通知
// - - -前置通知- -目标方法調用前調用
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
* method:被調用的方法名
* args:給method傳遞的參數
* target:目标對象
*/
public void before(Method method, Object[] args, Object target)
throws Throwable {
// 切面:該記錄日志功能
// 通知:切面的具體實作
System.out.println("記錄日志 " + method.getName() + " " + target.getClass());
}
}
// - - -後置通知- -目标方法調用後調用
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("關閉資源 " + method.getName() + " " + target.getClass());
}
}
④在beans.xml檔案中配置
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- 配置被代理的對象 -->
<bean id="test1Servece" class="com.test.aop.Test1Service">
<property name="name" value="jiaozl"></property>
</bean>
<!-- 配置前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.test.aop.MyMethodBeforeAdvice"/>
<!-- 配置後置通知 -->
<bean id="myAfterReturningAdvice" class="com.test.aop.MyAfterReturningAdvice"/>
<!-- 配置代理對象
ProxyFactoryBean implements TestServiceInter {
public void sayHello() {};
}
-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口集 -->
<property name="proxyInterfaces">
<list>
<value>com.test.aop.TestServiceInter</value>
<value>com.test.aop.TestServiceInter2</value>
</list>
</property>
<!-- 把通知織入到代理對象 -->
<property name="interceptorNames">
<list>
<!-- 相當于把前置通知(也可以把通知看成攔截器)和代理對象關聯起來 -->
<value>myMethodBeforeAdvice</value>
<value>myAfterReturningAdvice</value>
</list>
</property>
<!-- 配置被代理對象,可以指定 -->
<property name="target" ref="test1Servece"/>
</bean>
</beans>
⑤調用
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/test/aop/beans.xml");
TestServiceInter tsi = (TestServiceInter) ac.getBean("proxyFactoryBean");
// 通知織入後,連接配接點變為-->切入點(動态)
tsi.sayHello();
TestServiceInter2 tsi2 = (TestServiceInter2) tsi;
tsi2.sayBye();
}
3 通知
----------------------------
通知類型 接口 描述
----------------------------
Around MethodInterceptor 攔截對目标方法調用
Before MethodBeforeAdvice 在目标方法調用前調用
After AfterReturningAdvice 在目标方法調用後調用
Throws ThrowsAdvice 當目标方法抛出異常時調用
-----------------------------
①Around通知:
該通知能夠控制目标方法是否真的被調用;
同時也可以控制傳回的對象。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// TODO Auto-generated method stub
System.out.println("調用前....");
Object obj = arg0.proceed(); // 調用的方法
System.out.println("調用後....");
return obj;
}
}
②Throws通知:
該接口是辨別性接口,沒有任何方法,但實作該接口的類必須要有如下形式的方法:
public void afterThrowing(Exception e);
public void afterThrowing(Method method, Object[] os, Object target, Exception e);
第一個方法隻接受一個參數:需要抛出的異常。
第二個方法接受異常、被調用的方法、參數以及目标對象
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception e){
System.out.println("000出大事了 " + e.getMessage());
}
public void afterThrowing(Method method, Object[] os, Object target, Exception e){
System.out.println(method+ " 111出大事了 " + e.getMessage());
}
}
③ 引入通知
以前定義的通知類型是在目标對象的方法被調用的周圍織入。引入通知給目标對象添加新的方法和屬性。
隻需要在xml配置檔案中進行配置:
<?xml version="1.0" encoding="utf-8"?>
<beans>
<!-- 配置被代理的對象 -->
<bean id="test1Servece" class="com.test.aop.Test1Service">
<property name="name" value="jiaozl"></property>
</bean>
<!-- 配置前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.test.aop.MyMethodBeforeAdvice"/>
<!-- 配置後置通知 -->
<bean id="myAfterReturningAdvice" class="com.test.aop.MyAfterReturningAdvice"/>
<bean id="myMethodInterceptor" class="com.test.aop.MyMethodInterceptor"/>
<bean id="myThrowsAdvice" class="com.test.aop.MyThrowsAdvice"/>
<!-- 定義前置通知的切入點 -->
<bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="myMethodBeforeAdvice"/>
<property name="mappedNames">
<list>
<!-- 隻對sayBye方法通知 -->
<value>sayBye</value>
</list>
</property>
</bean>
<!-- 配置代理對象
ProxyFactoryBean implements TestServiceInter {
public void sayHello() {};
}
-->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口集 -->
<property name="proxyInterfaces">
<list>
<value>com.test.aop.TestServiceInter</value>
<value>com.test.aop.TestServiceInter2</value>
</list>
</property>
<!-- 把通知織入到代理對象 -->
<property name="interceptorNames">
<list>
<!-- 使用自定義切入點來控制前置通知 -->
<value>myMethodBeforeAdviceFilter</value>
<value>myAfterReturningAdvice</value>
<value>myMethodInterceptor</value>
<value>myThrowsAdvice</value>
</list>
</property>
<!-- 配置被代理對象,可以指定 -->
<property name="target" ref="test1Servece"/>
</bean>
</beans>