springAOP之代理模式


springAOP指的是在spring中的AOP,什么是AOP,相對於java中的面向對象(oop),在面向對象中一些公共的行為,像日志記錄,權限驗證等如果都使用面向對象來做,會在每個業務方法中都寫上重復的代碼,造成代碼的冗余。而AOP指的是面向切面編程,定義一個切面,用切面去切相應的方法,就可以織入相關的邏輯。面向切面編程使用代理模式

一、代理模式

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

二、靜態代理

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

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操作,如果不使用代理模式,那么我們的使用方式則是直接使用,如下

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();
    }

}

通過測試方法,可以得出執行方法的結果如下,

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

 

下面看代理類,

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("代理操作,關閉事務");
    }

}

可以看到代理類的實現邏輯是在代理類中持有一個被代理類的實例,通過被代理類實例調用被代理對象的方法,另外在方法之前前后均可加入其它的方法處理邏輯,

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

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();
    }

}

返回的執行結果如下,

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

對比,使用靜態代理和不使用靜態代理,可以發現使用了代理之后,可以在被代理方法的執行前或后加入別的代碼,實現諸如權限及日志的操作。

但,靜態代理也存在一定的問題,如果被代理方法很多,就要為每個方法進行代理,增加了代碼維護的成本。有沒有其他的方式可以減少代碼的維護,那就是動態代理。

三、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動態代理。

四、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的實例,這里可以使用匿名內部類的方式,可參照上面的。

五、總結

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

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

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM