spring動態代理的理解(java)


java動態代理的理解

 代理模式是設計模式的其中一種也是Java相關框架中的重要應用。我也是初學者, 個人見解, 不喜勿噴, 簡單的說就是需要進行功能增強的代理類原本真實對象的被代理類會實現同樣的接口,代理類的字節碼文件是在jvm運行的時候動態生成該類(下面進行的介紹),但是代理類會多去繼承一個Java中的Proxy 類代理類負責為代理類(也就是生成真是對象的java類)預處理信息、增強信息、過濾信息最終把已經增強的轉發給代理類。然而,回想之后,代理類又是誰生成的呢? 因此,還需要一個類去動態的生成代理類,這個類在編寫的時候還需要用到一個Java中的invocationhandler類,這個類是用於增強被代理類中的方法,也就是誰繼承了invocationhandler,誰就要去實現該接口對需要增強的類的方法(該接口中的invoke方法), 並且通過調用代理類生成器的生成代理類方法,就會去調用該實現類的invoke方法, 這是個人自己的理解,所說的動態生成器類就是在這個生成動態代理類的Java類中,不能有別的自己編寫的Java類的引用(可以在該類中看是否有import導入自己編寫的類),萬事俱備, 只欠東風, 那就是搞一個測試, 去看下按照自己的理解是否可行。

1.首先先編寫dao和service的代碼

dao實現部分代碼

public class EmployeeDAOImpl implements IEmployeeDAO {

    public void save(Employee emp) {
        System.out.println("保存員工");
    }

    public void update(Employee emp) {
        System.out.println("修改員工");
    }
}

service實現部分代碼

public class EmployeeServiceImpl implements IEmployeeService {

    private IEmployeeDAO dao;

    public void setDao(IEmployeeDAO dao) {
        this.dao = dao;
    }

    public void save(Employee emp) {
        dao.save(emp);
        System.out.println("保存成功");
    }
}

 2.編寫需要增強被代理對象的方式, 增強其功能, 比如事務增強

//模擬事務管理器:
public class TransactionManager {

    public void begin() {
        System.out.println("開啟事務");
    }

    public void commit() {
        System.out.println("提交事務");
    }

    public void rollback() {
        System.out.println("回滾事務");
    }
}

3.編寫生成代理類的代理類生成器

先介紹下proxy類和invocationhandler類中用到的方法

proxy類:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods. 

Proxy類就是用來動態生成一個代理對象的類,我就叫它為動態代理類生成器吧。它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

這個方法的作用就是獲取到增強版的被代理對象也就是代理類,其接收三個參數,這三個參數所代表的含義:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一個ClassLoader對象,就是定義了由哪個對象的classloader來對要生成的代理對象進行加載,一般使用的是被代理對象的類加載器

interfaces:  一個Interface對象的數組,就是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),
這樣我就能調用這組接口中的方法了, 通俗的講就是被代理類所實現的接口,而代理類也需要這個接口,否則它怎么知道你要用到哪個方法呢 h:  一個InvocationHandler對象,就是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上,也就是需要怎么實現對被代理對象的加強

invocationhandler類:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯到了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。比較抽象, 按照我的理解就是, 你用動態代理類生成器的時候,生成完代理對象了,  但是你僅僅是生成了這個代理對象, 你還需要告訴別人, 你是怎么加強代理對象的呢 。InvocationHandler這個接口的唯一的一個方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

這個方法的作用就是對動態代理類生成的增強版的被代理對象的方法也就是代理類的種方法,如何加強的,其接收三個參數,這三個參數所代表的含義:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  所代理的那個真實對象
method: 所要調用真實對象的某個方法的Method對象
args:  調用真實對象某個方法時接受的參數

重頭戲來了, 在這里我是直接讓TransactionManagerAdvice類去實現了InvocationHandler這個類,這個也就是上面提到的用於實現對代理類中方法的增強, 通過invoke方法去增強。TransactionManagerAdvice既是動態代理類生成器(用到的是proxy類中的newProxyInstance方法), 也是對被代理對象(也就是真實對象)增強的方式(用到的是invocationhandler接口中的invoke方法),其中這兩個成員變量, 我用的是基於xml的DI中的setter方法注入,因此也提供了兩個成員變量的setter方法。

動態代理類生成器如下:

//事務的增強操作
public class TransactionManagerAdvice implements InvocationHandler {

    private Object target;//真實對象(對誰做增強)
    private TransactionManager txManager;//事務管理器(模擬)

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    //創建一個代理對象
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //類加載器,一般跟上真實對象的類加載器
                target.getClass().getInterfaces(), //真實對象所實現的接口(JDK動態代理必須要求真實對象有接口)
                this);//如何做事務增強的對象,誰繼承了invocationhandler,誰就是需要做增強的對象,並且真實對象調用方法會去調用
        //該實現類的invoke方法, 自己的理解
    }

    //如何為真實對象的方法做增強的具體操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().startsWith("get") || method.getName().startsWith("list")) {
            return method.invoke(target, args);//放行
        }
        
        Object ret = proxy;
        txManager.begin();
        try {
            //---------------------------------------------------------------
            ret = method.invoke(target, args);//調用真實對象的方法
            //---------------------------------------------------------------
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
        return ret;
    }
}

4.編寫測試類

@SpringJUnitConfig
//用於加載配置文件的內容,不加參數默認去找: 測試類名稱-context.xml
public class App { //用於生成增強代理類的代理類生成器 @Autowired private TransactionManagerAdvice advice; //代理對象:com.sun.proxy.$Proxy19 @Test void testSave() throws Exception { //獲取代理對象 IEmployeeService proxy = advice.getProxyObject(); proxy.save(new Employee()); } }

5.配置文件的內容如下(配置文件的名稱: App-context.xml)

<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

  <!--因為
    <bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" />

    <bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" />

    <bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDAO" />
    </bean>

    <!-- 配置一個事務增強的類, 也就是代理類生成器 -->
    <bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice">
        <property name="target" ref="employeeService"/>
        <property name="txManager" ref="transactionManager"/>
    </bean>
</beans>

5.運行保存(save)測試,結果如下:

 

 

 

 

 

 

 

最終大功告成, 測試通過,成功。

個人筆記: 理解延伸

看看動態代理在底層是如何進行操作的

先從動態代理生成的部分開始

第一步: 通過proxy類中的靜態方法newproxyinstance方法,根據傳入的參數,動態生成出代理類的Java文件,只不過他不會出現在編譯時期,而是直接到達運行時期,利用類加載器生成該份字節碼文件而已

第二步: 就是通過代理對象的invoke方法對所用的方法進行增強,因為在前面的TransactionManagerAdvice類中已經定義了如何增強功能, 所以在這個動態生成的代理類中的invoke方法就是多態特性調用自己編寫的invoke方法

第三步: 測試方法

1.首先先借助一個類:這個類的作用是生成代理類的字節碼文件, 並且把這份字節碼文件存放在被代理類的包下面(也就是代理類的字節碼文件), 之前我也是很好奇,為什么代理類還要這樣得到, 這也就是前面也沒提到的, 動態代理是不會生成代理類的Java文件的, 因為動態代理的過程中,我們並沒有實際看到代理類的代碼實現,而且動態代理中被代理對象和代理對象是通過InvocationHandler接口實現類和proxy類中的newproxyinstance方法來完成的代理過程的,其中具體是怎樣操作的,為什么代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行。生成動態代理類的代碼如下

import java.io.FileOutputStream;

import cn.wolfcode.service.impl.EmployeeServiceImpl; import sun.misc.ProxyGenerator; public class DynamicProxyClassGenerator {
    public static void main(String[] args) throws Exception {
        generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy");
    }

    //生成代理類的字節碼文件-->Java反編譯工具-->Java文件
    public static <T>void  generateClassFile(Class<T> targetClass, String proxyName) throws Exception {
        //根據類信息和提供的代理類名稱,生成字節碼  
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
        String path = targetClass.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream out = null;
        //保留到硬盤中  
        out = new FileOutputStream(path + proxyName + ".class");
        out.write(classFile);
        out.close();
    }

}

2.生成字節碼文件之后,找到這份字節碼文件,通過jd-gui反編譯工具生成  .java文件, 下面的就是動態代理的代理類的代碼實現

public final class EmployeeServiceProxy extends Proxy implements IEmployeeService {
    private static final long serialVersionUID = 1L;
    private static Method method_equals;
    private static Method method_toString;
    private static Method method_hashCode;
    private static Method method_update;
    private static Method method_save;

    public EmployeeServiceProxy(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }
    
    static {
        try {
            method_equals = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });
            method_toString = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            method_hashCode = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            
            method_update = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("update",new Class[] { Class.forName("cn.wolfcode.domain.Employee") });
            method_save = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("save",new Class[] { Class.forName("cn.wolfcode.domain.Employee") });
        } catch (Exception e) {
        } 
    }

    public final boolean equals(Object paramObject) {
        try {
            return ((Boolean) this.h.invoke(this, method_equals, new Object[] { paramObject })).booleanValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    

    public final String toString() {
        try {
            return (String) this.h.invoke(this, method_toString, null);
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    public final int hashCode() {
        try {
            return ((Integer) this.h.invoke(this, method_hashCode, null)).intValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    public final void save(Employee paramEmployee) {
        try {
            this.h.invoke(this, method_save, new Object[] { paramEmployee });//多態調用,用的是TransactionManagerAdvice中的invoke方法
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    
    public final void update(Employee paramEmployee) {
        try {
            this.h.invoke(this, method_update, new Object[] { paramEmployee });//多態調用
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
}

上面標紅的就是在上面代理類生成器中的invoke方法

3.最終通過測試方法的代理類生成器獲取代理類對象,然后調用save方法進行測試,成功

總結

我們可以對InvocationHandler的實現類看做一個中介類,理解成它就是代理類對象的中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉為對被代理對象的調用。代理類調用自己方法時,通過自身持有的中介類對象來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理通過中介類實現了具體的代理功能。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

部分參考:https://www.cnblogs.com/gonjan-blog/p/6685611.html

 


免責聲明!

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



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