springAOP之代理模式

springAOP指的是在spring中的AOP,什麼是AOP,相對於java中的面向對象(oop),在面向對象中一些公共的行爲,像日誌記錄,權限驗證等若是都使用面向對象來作,會在每一個業務方法中都寫上重複的代碼,形成代碼的冗餘。而AOP指的是面向切面編程,定義一個切面,用切面去切相應的方法,就能夠織入相關的邏輯。面向切面編程使用代理模式java

1、代理模式

代理模式做爲23種經典設計模式之一,其比較官方的定義爲「爲其餘對象提供一種代理以控制對這個對象的訪問」,簡單點說就是,以前A類本身作一件事,在使用代理以後,A類不直接去作,而是由A類的代理類B來去作。代理類實際上是在以前類的基礎上作了一層封裝。java中有靜態代理、JDK動態代理、CGLib動態代理的方式。靜態代理指的是代理類是在編譯期就存在的,相反動態代理則是在程序運行期動態生成的,spring

2、靜態代理

靜態代理,簡單點來講就是在程序運行以前,代理類和被代理類的關係已經肯定。靜態代理的實現方式通常有如下幾個步驟,首先要定義一個公共的接口,供代理類和被代理類實現,以下編程

package cn.com.staticProxy;
/**
 * * 
*<p>Title: IUserDao</p>
* <p>Description:公共的接口</p>
* <p>Company: </p>
* @author Administrator
* @date 2019年4月24日 下午4:15:12
 */
public interface IUserDao {

    void save();
    void find();
}

而後是代理類和被代理類,先看被代理類,設計模式

package cn.com.staticProxy;

public class UserDao implements IUserDao{
/**
 * 被代理類或者叫代理目標類
 */
    @Override
    public void save() {
        // TODO Auto-generated method stub
        
        System.out.println("模擬保存用戶");
        
    }

    @Override
    public void find() {
        // TODO Auto-generated method stub
        System.out.println("模擬查找用戶");
        
    }

}

從被代理類上能夠看到,被代理類要作的就是save和find操做,若是不使用代理模式,那麼咱們的使用方式則是直接使用,以下ide

package cn.com.staticProxy;
/**
 * * 
*<p>Title: Test2</p>
* <p>Description:直接使用被代理類 </p>
* <p>Company: </p>
* @author Administrator
* @date 2019年4月24日 下午4:18:39
 */
public class Test2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        IUserDao udp=new UserDao();
        udp.save();
        System.out.println("-------------------");
        udp.find();
    }

}

經過測試方法,能夠得出執行方法的結果以下,oop

模擬保存用戶
-------------------
模擬查找用戶

 

下面看代理類,測試

package cn.com.staticProxy;

public class UserDaoProxy implements IUserDao {

    private UserDao ud = new UserDao();

    @Override
    public void save() {
        // TODO Auto-generated method stub

        System.out.println("代理操做,開啓事務");
        ud.save();
        System.out.println("代理操做,關閉事務");
    }

    @Override
    public void find() {
        // TODO Auto-generated method stub
        System.out.println("代理操做,開啓事務");
        ud.find();
        System.out.println("代理操做,關閉事務");
    }

}

能夠看到代理類的實現邏輯是在代理類中持有一個被代理類的實例,經過被代理類實例調用被代理對象的方法,另外在方法以前先後都可加入其它的方法處理邏輯,this

最後,看使用代理類的測試方法,spa

package cn.com.staticProxy;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        IUserDao udp=new UserDaoProxy();
        udp.save();
        System.out.println("-------------------");
        udp.find();
    }

}

返回的執行結果以下,設計

代理操做,開啓事務
模擬保存用戶
代理操做,關閉事務
-------------------
代理操做,開啓事務
模擬查找用戶
代理操做,關閉事務

對比,使用靜態代理和不使用靜態代理,能夠發現使用了代理以後,能夠在被代理方法的執行前或後加入別的代碼,實現諸如權限及日誌的操做。

但,靜態代理也存在必定的問題,若是被代理方法不少,就要爲每一個方法進行代理,增長了代碼維護的成本。有沒有其餘的方式能夠減小代碼的維護,那就是動態代理。

3、JDK動態代理

JDK提供了動態代理的方式,能夠生成代理類,被代理類和接口沿用靜態代理中的IUserDao和UserDao,UserDao爲被代理類,下面看代理類,

package cn.com.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import cn.com.staticProxy.IUserDao;
import cn.com.staticProxy.UserDao;
/**
 * * 
*<p>Title: DynamicProxy</p>
* <p>Description: 動態代理類</p>
* <p>Company: </p>
* @author Administrator
* @date 2019年4月24日 下午4:35:00
 */
public class DynamicProxy implements InvocationHandler{
    //被代理類的實例
    private IUserDao iud=null;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        Object result=null;
        System.out.println("開始JDK動態代理");
        method.invoke(iud, args);
        System.out.println("結束JDK動態代理");
        return result;
    }
    //構造方法
    public DynamicProxy(IUserDao iud){
        this.iud=iud;
    }
    
}

須要實現JDK中的InvocationHandler接口,實現其中的invoke方法,在此方法中經過反射的方式調用被代理類的方法,能夠在方法執行前或後進行別的處理,下面看測試代碼

package cn.com.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import cn.com.staticProxy.IUserDao;
import cn.com.staticProxy.UserDao;

public class Test2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        UserDao ud=new UserDao();
        
        DynamicProxy dp=new DynamicProxy(ud);
        
        //生成代理對象
        IUserDao iud=(IUserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), dp);
        iud.save();
        System.out.println("--------------");
        iud.find();
        
    }

    
}

在測試代碼中經過Proxy類的newProxyInstance方法,生成代理類的實例iud,須要三個參數,第一個爲被代理類的類加載器,第二個爲被代理類實現的接口,第三個則爲invocationHandler的實現類,這樣就生成了代理對象,而後經過代理對象調用方法執行結果以下,

開始JDK動態代理
模擬保存用戶
結束JDK動態代理
--------------
開始JDK動態代理
模擬查找用戶
結束JDK動態代理

另外,因爲第三個參數爲一個接口,還可使用匿名內部類的方式進行書寫,其實現代碼以下,

package cn.com.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import cn.com.staticProxy.IUserDao;
import cn.com.staticProxy.UserDao;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        //被代理類的實例
        UserDao ud = new UserDao();

        IUserDao iud = (IUserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // TODO Auto-generated method stub

                        Object result = null;
                        if ("find".equals(method.getName())) {
                            result = method.invoke(ud, args);
                        } else {
                            System.out.println("開始JDK代理");
                            result = method.invoke(ud, args);
                            System.out.println("結束JDK代理");
                        }

                        return result;
                    }
                });
        //使用代理類調用方法
        iud.find();
        System.out.println("----------------");
        iud.save();
    }

}

執行結果以下,

模擬查找用戶
----------------
開始JDK代理
模擬保存用戶
結束JDK代理

從上面能夠看出代理類是由Proxy這個類經過newProxyInstance方法動態生成的,生成對象後使用「實例調用方法」的方式進行方法調用,那麼代理類的被代理類的關係只有在執行這行代碼的時候纔會生成,所以成爲動態代理。

JDK的動態代理也存在不足,即被代理類必需要有實現的接口,如沒有接口則沒法使用JDK動態代理(從newProxyInstance方法的第二個參數可得知,必須傳入被代理類的實現接口,那麼須要使用CGLib動態代理。

4、CGLib動態代理

CGLib動態代理是一個第三方實現的動態代理類庫,不要求被代理類必須實現接口,它採用的是繼承被代理類,使用其子類的方式,彌補了被代理類沒有接口的不足,

要使用CGLib必需要引入第三方類庫,我這裏使用的類庫以下,

要使用CGLib代理須要實現其MethodInterceptor接口,

package cn.com.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        
        System.out.println("開始CGLib動態代理");
        Object object=proxy.invokeSuper(obj, args);
        System.out.println("結束CGLib動態代理");
        return object;
    }

}

其,測試代碼以下,

package cn.com.cglib;

import cn.com.staticProxy.UserDao;
import net.sf.cglib.proxy.Enhancer;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(UserDao.class);
        enhancer.setCallback(new MyMethodInterceptor());
        //生成代理類
        UserDao ud=(UserDao) enhancer.create();
        ud.save();
System.out.println("----------------"); ud.find(); } }

能夠看出使用Enhancer生成代理類,須要設置被代理類,也就是父類(從這裏能夠看出是使用繼承,生成的子類),設置回調方法。

開始CGLib動態代理
模擬保存用戶
結束CGLib動態代理
--------------------
開始CGLib動態代理
模擬查找用戶
結束CGLib動態代理

在設置回調enhancer.setCallback的時候須要傳入MethodInterceptor的實例,這裏可使用匿名內部類的方式,可參照上面的。

5、總結

對靜態代理、JDK動態代理、CGLib動態代理作一個總結,靜態代理的維護成本比較高,有一個被代理類就須要建立一個代理類,並且須要實現相同的接口。JDK動態代理模式和CGLib動態代理的區別是JDK動態代理須要被代理類實現接口,而CGLib則是生成被代理類的子類,要求被代理類不能是final的,由於final類沒法被繼承。

下次會基於springAOP梳理相關內容。

 

有不正之處歡迎指正,感謝!

相關文章
相關標籤/搜索