Spring(一):AOP

包括:
一. AOP 概念
二. AOP 實現方式
     2.1 利用 Proxy 實現 AOP功能
     2.2 利用 CGLib 實現 AOP功能
     2.3 利用 Spring 註解方式 實現 AOP功能
     2.4 利用 Spring XML 文件配置方式實現 AOP功能


一. AOP概念
       AOP也就是面向切面編程。利用AOP,我們可以對方法進行控制,比如說,在方法執行的前後執行某些代碼,如果方法有異常,可以捕捉到異常等。再比如說,以下這些情況就可以使用AOP技術:
  • 對函數調用進行日誌記錄。
  • 監控部分函數,如果拋出異常那麼可以以短信或者郵件通知別人。
  • 監控重要函數的運行時間。
        在實際的工作,我們可能要記錄下用戶的每一個操作,以便日後進行統計分析,從而優化產品。

二. AOP實現方式

2.1  用Proxy類實現AOP功能

        AOP大都可以用在權限系統中,那麼利用Proxy類實現主函數調用Student類的print()方法,打印出”hello world!”。若主函數創建的Student實例有名字,則可以打印,沒有名字,則不可以打印。

       一般來說,我們可以直接在print()方法中加入if 語句來進行判斷,但是這樣一個是代碼量大,一個是不靈活,況且,假如判斷條件需要修改的話,還得找回這個類,不方便對於採用Proxy類方法,主函數 -->代理-->目標對象的方法。這樣,以後不管是修改判斷條件,還是查找等,可以直接在代理類中進行處理。

        對於Proxy類有一個使用前提,就是目標對象必須要實現接口。否則,不能使用這個方法。

過程如下:

1. 創建Student類的接口:

public interface StudentInterface {  
    public void print();  
}  

2. 創建Student類:
public class StudentBean implements StudentInterface{  
    private String name;  
    public StudentBean(){}  
    public StudentBean(String name){this.name = name;}  
    public void print(){  
        System.out.println("Hello World!");  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}  

3. 創建代理工廠類:
public class ProxyFactory implements InvocationHandler {  
    private Object stu;  
    public Object createStudentProxy(Object stu){  
        this.stu = stu;  
        return Proxy.newProxyInstance(stu.getClass().getClassLoader(),  
                stu.getClass().getInterfaces(), this);  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        StudentBean s = (StudentBean)stu;  
        Object object = null;  
        if(s.getName() != null)  
            object = method.invoke(stu, args);  
        else  
            System.out.println("名字爲空,代理類已經攔截!");  
        return object;  
    }  
}  

解釋:

  1. 首先調用代理工廠的createStudentProxy(Object stu)創建StudentBean類的代理類.
  2. 在該方法內,調用Proxy.newProxyInstance()方法創建代理對象。第一個參數是目標對象的類加載器,第二個參數是目標對象實現的接口,第三個參數傳入一個InvocationHandler實例,該參數和回調有關係。
  3. 每當調用目標對象的方法的時候,就會回調該InvocationHandler實例的方法,也就是public Object invoke()方法,我們就可以把限制的條件放在這裏,條件符合的時候,就可以調用method.invoke()方法真正的調用目標對象的方法,否則,則可以在這裏過濾掉不符合條件的調用。

Proxy實現AOP功能總結:

  1. 目標對象必須實現接口。
  2. 調用Proxy.newProxyInstance()方法,返回創建的代理對象。
  3. 由於該方法需要一個實現了InvocationHandler接口的對象,所以我們還要重寫該接口的invoke()方法。
  4. 我們的限制條件就可以放在這個invoke()方法中,當滿足條件,就調用method.invoke()真正的調用目標對象的方法,否則,不做任何事情,直接過濾。


2.2 利用CGlib實現AOP功能

        還是實現主函數調用Student類的print()方法,打印出”hello world!”。若主函數創建的Student實例有名字,則可以打印,沒有名字,則不可以打印。但是這個時候Student類不再實現接口,不像利用 Proxy那樣的實現接口。這時候使用第三方框架CGLib。

具體實現:

1. 引入CGlib的jar包。

2. 創建StudentBean類:

public class StudentBean {  
    private String name;  
    public StudentBean(){}  
    public StudentBean(String name){this.name = name;}  
    public void print(){  
        System.out.println(name +" hello world!");  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}  

3. 創建CGLib代理類:
public class CGlibProxyFactory implements MethodInterceptor{  
    private Object object;  
    public Object createStudent(Object object){  
        this.object = object;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(object.getClass());  
        enhancer.setCallback(this);  
        return enhancer.create();  
    }  
    public Object getObject() {  
        return object;  
    }  
    public void setObject(Object object) {  
        this.object = object;  
    }  
    @Override  
    public Object intercept(Object proxy, Method method, Object[] args,  
            MethodProxy methodProxy) throws Throwable {  
        StudentBean stu = (StudentBean)object;  
        Object result = null;  
        if(stu.getName() != null)  
            result = methodProxy.invoke(object, args);  
        else  
            System.out.println("方法已經被攔截...");  
        return result;  
    }  
}  

        在這一步中,我們使用一個Enhancer類來創建代理對象,不再使用Proxy。使用Enhancer類,需要爲其實例指定一個父類,也就是我們 的目標對象。這樣,我們新創建出來的對象就是目標對象的子類,有目標對象的一樣。除此之外,還要指定一個回調函數,這個函數就和Proxy的 invoke()類似。

        總體來說,使用CGlib的方法和使用Proxy的方法差不多,只是Proxy創建出來的代理對象和目標對象都實現了同一個接口。而CGlib的方法則是直接繼承了目標對象。

4. 創建主類

public class Main {  
    public static void main(String[] args) {  
        StudentBean stu1 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean()));  
        StudentBean stu2 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean("whc")));  
        stu1.print();  
        stu2.print();  
    }  
}  


2.3 利用spring註解方式實現AOP功能

前提:介紹spring註解方法的話先介紹一些概念:

        在上面的例子中,我們可以在proxy方式中重寫invoke方法,也可以在cglib方式中重寫intercept方法,並且在 裏面調用invoke方法來實際調用目標方法。

        那麼我們可以在這個invoke()方法的前前後後加入我們的一些方法,比如在調用實際方法前,調用一個我 們自定義的方法,在調用實際方法後,也調用我們的一個自定義方法,那麼這些就分別叫做前置通知,後置通知,除此之外,還有例外通知,最終通知,整個這個重 寫方法,就叫做環繞通知。整個這個處理類,就叫做切面。(Ps:不太清楚的先搞清楚AOP的一些概念)

1. 引入spring的jar包:

有aspectjweaver-1.6.11.M2.jar,cglib.nodep-2.1.3.jar,spring.jar,aspectjrt.jar,common-annotations.jar,commons-logging-1.1.1.jar包。

2. 配置aop命名空間:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">  
    <aop:aspectj-autoproxy/>  
    <bean id = "stuInterceptor" class = "com.springaop.test.StuInterceptor"></bean>  
    <bean id = "stu" class = "com.springaop.test.Student"></bean>  
</beans>  

3.創建目標對象類:
public class Student {  
    public String print(String name){  
        System.out.println("print() method:" + name);  
        return "hello";  
    }  
}  

4.創建切面:
@Aspect  
public class StuInterceptor {  
    /** 
     * 打印方法AOP 
     */  
    @Pointcut("execution(* com.springaop.test.Student.print(..))")  
    // @Pointcut("execution(* com.springaop.test.Student.*(..))")  
    public void printMethod(){}  
      
    @Before("printMethod()")  
    public void printBeforeAdvice(){  
        System.out.println("printBeforeAdvice()!");  
    }  
      
    @AfterReturning(pointcut="printMethod()",returning="flag")  
    public void printAfterAdvice(String flag){  
        System.out.println("printAfterAdvice()! " + flag);  
    }  
    @After("printMethod()")  
    public void finallyAdvice(){  
        System.out.println("finallyAdvice()!");  
    }  
      
    @Around("printMethod() && args(name)")  
    public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{  
        Object result = null;  
        if(name.equals("whc"))  
            pjp.proceed();  
        else  
            System.out.println("print()方法以及被攔截...");  
        return result;  
    }  
}  

解釋:

  1. 聲明該類是切面,在類前使用@Aspect
  2. 聲明切入點:
    @Pointcut("execution(* com.springaop.test.Student.print(..))")
    
    // @Pointcut("execution(*com.springaop.test.Student.*(..))")

  3. 兩種方式都可以。其中第一個*表示返回值可以爲任意類型,com.springaop.test.Student.print表示指定的方法。com.springaop.test.Student.*表示該類中的任意方法。(..)表示任意參數。
  4. 前置通知:@Before("printMethod()"),裏面的"printMethod()"即之前定義的切入點方法。
  5. 後置通知:@AfterReturning(pointcut="printMethod()",returning="flag")可以得到方法的返回值,放在flag中,flag要和後置通知的方法參數對應。
  6. 最終通知:@After("printMethod()")
  7. 環繞通知:@Around("printMethod() && args(name)"),可以傳入參數。一般我們的權限條件就在這裏寫。如下環繞通知:
    public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{}

    必須傳入一個ProceedingJoinPoint類型的參數,符合條件則可以直接調用pjp.proceed();就可以調 用目標對象的方法。如果不符合條件,則不調用pjp.proceed();
  8. 把切面配置在XMl文件中:如上配置。
  9. 在主函數中,創建Spring容器,即通過getBean方式得到實例對象,調用其方法即可。
    public class Main {  
        public static void main(String[] args) {  
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
            Student stu = (Student)ctx.getBean("stu");  
            stu.print("whc");  
        }  
    }  

輸出結果:

printBeforeAdvice()!
print() method:whc
printAfterAdvice()! hello
finallyAdvice()!

2.4 利用springXML文件配置方式實現AOP功能
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">  
    <aop:aspectj-autoproxy/>  
    <bean id = "stu" class = "com.springaop.test.Student"></bean>  
    <bean id = "interceptor" class = "com.springaop.test.StuInterceptor"></bean>  
      
    <aop:config>  
        <aop:aspect id = "stuInterceptor" ref = "interceptor">  
            <aop:pointcut  id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>  
            <aop:before pointcut-ref="mycut" method="printBeforeAdvice" />  
            <aop:after-returning pointcut-ref="mycut" method="printAfterAdvice"/>  
            <aop:after pointcut-ref="mycut" method="finallyAdvice"/>  
            <aop:around pointcut="mycut" method="printAroundAdvice"/>  
        </aop:aspect>  
    </aop:config>  
      
</beans>  
  • 首先可以在切面的類把註釋去掉,因爲我們不再使用註釋的方法。
  • 然後再上面的配置文件中,我們定義了<aop:config>,進一步在裏面配置了切面,切入點,前置,後置通知等等一系列的東西。

       這裏有一個問題就是如何傳入參數的問題,比如我要爲環繞方法傳入一個name參數。那麼,該如何在這裏配置?一個解決辦法就是在上述配置中不要如下代碼:

<aop:pointcut  id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>  
       每一行改爲:
<aop:before pointcut="execution(* com.springaop.test.Student.print(..))" method="printBeforeAdvice" />  
<aop:around pointcut="execution(* com.springaop.test.Student.print(..)) and args(name)" method="printAroundAdvice" arg-names(name)/


總結:
  1. AOP概念:面向切面編程;能夠在方法的前後執行某些代碼,達到控制的目的。
  2. 實現方法有幾種。1. Proxy 類方式(需要目標對象實現某一接口。目的就是 代理類也實現該接口,使得代理類能夠完全代理該對象) 2. CGlib 方式(此時的代理方式爲代理類繼承代理對象,以此達到代理類和代理對象一致的目的) 3. Spring 註解方式 4. Spring XML 文件配置的方式。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章