淺析spring中的AOP(面向切面編程)


* 它用來生成代理對象
 * 它需要所有的參數
 * * 目標對象
 * * 增強
 * @author cxf
 */
/**
 * 1. 創建代理工廠
 * 2. 給工廠設置三樣東西:
 *   * 目標對象:setTargetObject(xxx);
 *   * 前置增強:setBeforeAdvice(該接口的實現)
 *   * 後置增強:setAfterAdvice(該接口的實現)
 * 3. 調用createProxy()得到代理對象
 *   * 執行代理對象方法時:
 *   > 執行BeforeAdvice的before()
 *   > 目標對象的目標方法
 *   > 執行AfterAdvice的after()
 * @author cxf
 *
 */
public class ProxyFactory {
private Object targetObject;//目標對象
private BeforeAdvice beforeAdvice;//前置增強
private AfterAdvice afterAdvice;//後置增強


/**
* 用來生成代理對象
* @return
*/
public Object createProxy() {
/*
* 1. 給出三大參數
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = targetObject.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* 在調用代理對象的方法時會執行這裏的內容
*/
// 執行前置增強
if(beforeAdvice != null) {
beforeAdvice.before();
}

Object result = method.invoke(targetObject, args);//執行目標對象的目標方法
// 執行後置增強
if(afterAdvice != null) {
afterAdvice.after();
}

// 返回目標對象的返回值
return result;
}
};
/*
* 2. 得到代理對象
*/
Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h);
return proxyObject;
}


public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}



// 服務員接口
public interface Waiter {
// 服務
public void serve();

public void shouQian();
}


// 服務員實現類

public class ManWaiter implements Waiter {
public void serve() {
System.out.println("服務中...");
}

public void shouQian() {
System.out.println("收錢!");
}
}


// 前置增強接口

public interface BeforeAdvice {
public void before();
}


// 後置增強接口

public interface AfterAdvice {
public void after();
}

/*
 * 目標是讓目標對象和增強都可以切換!
 */
public class Demo3 {
@Test
public void fun1() {
ProxyFactory factory = new ProxyFactory();//創建工廠
factory.setTargetObject(new ManWaiter());//設置目標對象
factory.setBeforeAdvice(new BeforeAdvice() {//設置前置增強
public void before() {
System.out.println("您好!");
}
});

factory.setAfterAdvice(new AfterAdvice() {//設置後置增強
public void after() {
System.out.println("再見!");
}
});

Waiter waiter = (Waiter)factory.createProxy();
waiter.shouQian();
waiter.serve();
}




1、AOP 是什麼?

都知道面向對象無非是封裝、繼承、多態,根據某一個類,實例化一個對象,然後操作這一個對象。

AOP  按我自己的理解就是,面向多個對象,或是面向N個對象。

比如我們在service中,可能要對所有的DML操作添加一個transaction,如果說service很少的話,我們可以直接在service中添加一個transaction,那麼如果再開發中,有幾十個甚至幾百個service都需要給他們添加一個transaction,難道我們就寫上幾十遍、上百遍的transaction。當然不是,在spring中,提供了一個AOP,面向切面的編程,說白了就是面向N個對象的編程,就是將這所有的service中的共同部分提取出來,比如將所有的transaction提取出來,寫成一個通知,然後織入(注意別寫錯字),織入action的代理對象之中,這個actionproxy會先執行織入的通知,然後執行其他。

2、首先明白幾個概念(概念可以先略過,還是先後面代碼,再來明白吧),

代理模式:代理模式的英文叫做ProxySurrogate,中文都可譯爲”代理“,所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用

Aspect(切面):是通知和切入點的結合,通知和切入點共同定義了關於切面的全部內容---它的功能、在何時和何地完成其功能

joinpoint(連接點):所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點.

Pointcut(切入點):所謂切入點是指我們要對哪些joinpoint進行攔截的定義.

   通知定義了切面的”什麼”和”何時”,切入點就定義了”何地”.

Advice(通知):所謂通知是指攔截到joinpoint之後所要做的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)

Target(目標對象):代理的目標對象

Weaving(織入):是指把切面應用到目標對象來創建新的代理對象的過程.切面在指定的連接點織入到目標對象

Introduction(引入):在不修改類代碼的前提下, Introduction可以在運行期爲類動態地添加一些方法或Field.


spring在運行期創建代理,不需要特殊的編譯器.

spring有兩種代理方式:

1.若目標對象實現了若干接口,spring使用JDKJava.lang.reflect.Proxy類代理。

2.若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。

   使用該方式時需要注意:

          1.對接口創建代理優於對類創建代理,因爲會產生更加鬆耦合的系統。

             對類代理是讓遺留系統或無法實現接口的第三方類庫同樣可以得到通知,

            這種方式應該是備用方案。

          2.標記爲final的方法不能夠被通知。spring是爲目標類產生子類。任何需要

             被通知的方法都被複寫,將通知織入。final方法是不允許重寫的。

   spring只支持方法連接點:不提供屬性接入點,spring的觀點是屬性攔截破壞了

  封裝。面向對象的概念是對象自己處理工作,其他對象只能通過方法調用的得到的

  結果。



3、概念可以先略過。下面寫個簡單的,來描述下(前置通知)。

比如實現一個功能,就是在所有請求到達action之前爲這個請求添加一個日誌(先導入spring的必備的jar包),下面的5段代碼放在同一個包中即可運行,直接用Java application運行就行

* com.springsource.NET.sf.cglib-2.2.0.jar
cglib代理
* com.springsource.org.aopalliance-1.0.0.jar
* com.springsource.org.aspectj.tools-1.6.6.RELEASE.jar
* org.springframework.aop-3.0.2.RELEASE.jar
spring的面向切面編程,提供AOP(面向切面編程)實現
* org.springframework.aspects-3.0.2.RELEASE.jar
spring提供對AspectJ框架的整合


定義接口

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. TestInterface1  

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.niit.spring.aop;  
  2.   
  3. /** 
  4.  * @author Emine_wang 
  5.  */  
  6. public interface TestInterface1 {  
  7.     public void sayHello();  
  8.     public void sayh();  
  9.   
  10. }  

實現:
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. TestInterface1Impl   

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.niit.spring.aop;  
  2.   
  3. /** 
  4.  * @author Emine_wang 
  5.  */  
  6. public class TestInterface1Impl implements TestInterface1 {  
  7.   
  8.     @Override  
  9.     public void sayHello() {  
  10.         // TODO Auto-generated method stub  
  11.           
  12.         System.out.println("Hello Emine");  
  13.   
  14.     }  
  15.     public void sayh(){  
  16.           
  17.         System.out.println("Hello Wang");  
  18.     }  
  19. }  


通知(就是概念中的Advice):

MyMethodBeforeAdvice.java

別忘了繼承org.springframework.aop.MethodBeforeAdvice;需要導包


[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.niit.spring.aop;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.springframework.aop.MethodBeforeAdvice;  
  6.   
  7. /** 
  8.  * @author Emine_wang 
  9.  *  編寫通知,advice 
  10.  */  
  11. public class MyMethodBeforeAdvice implements MethodBeforeAdvice{  
  12.     @Override  
  13.     public void before(Method method, Object[] args, Object object)  
  14.             throws Throwable {  
  15.         /** 
  16.          * method指表示被調用的方法 
  17.          * args給這個method方法傳遞的參數 
  18.          * object目標對象 
  19.          * 其實是利用反射機制得到方法中的名字、參數等信息 
  20.          *  
  21.          */  
  22.         System.out.println("記錄日誌"+method.getName());  
  23.     }  
  24.       
  25. }  


配值spring的配置文件beans.xml

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.     http://www.springframework.org/schema/context  
  8.     http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
  9.       
  10.     <!-- 配置目標對象(被代理對象) -->  
  11.     <bean id="TestInterface1Impl" class="com.niit.spring.aop.TestInterface1Impl">  
  12.     </bean>  
  13.       
  14.     <!-- 配置前置通知 -->  
  15.     <bean id="MyMethodBeforeAdvice" class="com.niit.spring.aop.MyMethodBeforeAdvice"></bean>  
  16.       
  17.     <!-- 配置代理對象 -->  
  18.       
  19.     <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">   
  20.       
  21.         <!-- 配置代理的接口集 -->  
  22.         <property name="proxyInterfaces">  
  23.             <list>  
  24.                 <value>com.niit.spring.aop.TestInterface1</value>  
  25.             </list>  
  26.         </property>  
  27.           
  28.         <!-- 把通知織入到代理對象 -->  
  29.           
  30.         <property name="interceptorNames">  
  31.         <!-- 相當於把前置通知 MyMethodBeforeAdvice和代理對象關聯起來,我們也可以把通知看成是攔截器  
  32.         struts2核心攔截器  
  33.         -->  
  34.             <value>MyMethodBeforeAdvice</value>  
  35.         </property>  
  36.           
  37.         <!-- 配置被代理對象,沒有被代理對象,那麼context.getBean("proxyFactoryBean")爲空 -->  
  38.           
  39.         <property name="target" ref="TestInterface1Impl"></property>  
  40.           
  41.       
  42.      </bean>  
  43.       
  44.       
  45.       
  46.       
  47.       
  48.       
  49. </beans>  

測試:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.niit.spring.aop;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. import com.niit.spring.bean.User;  
  7.   
  8. public class TestAop {  
  9.   
  10.     /** 
  11.      * @param Emine_Wang 
  12.      */  
  13.     public static void main(String[] args) {  
  14.         // TODO Auto-generated method stub  
  15.           
  16.         ApplicationContext  context = new ClassPathXmlApplicationContext("com/niit/spring/aop/beans.xml");//加載beans配置文件  
  17.           
  18. //      TestInterface1Impl testInterface1Impl =  (TestInterface1Impl) context.getBean("TestInterface1Impl");//這樣寫相當於沒有用到代理對象,當然不會執行通知了  
  19. //      testInterface1Impl.sayHello();  
  20.         TestInterface1 testInterface1 = (TestInterface1)context.getBean("proxyFactoryBean");//反射生成代理對象  
  21. //      System.out.println(testInterface1.getClass());  
  22.           
  23.         testInterface1.sayHello();  
  24.         testInterface1.sayh();  
  25.     }  
  26.   
  27. }  

結果:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).  
  2. log4j:WARN Please initialize the log4j system properly.  
  3. 記錄日誌sayHello  
  4. Hello Emine  
  5. 記錄日誌sayh  
  6. Hello Wang  

我們會發現,我並沒有在TestInterface1Impl中的sayHello()和sayh()方法中打印”記錄日誌“,卻發現這裏打印了。說明,通知advice已經被執行,是由代理對象幫我們執行。

發佈了164 篇原創文章 · 獲贊 26 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章