代理

代理

我們拿事務(wù)的實現(xiàn)來舉例。首先,我們模擬一下事務(wù),編寫一個類

public class TransactionManager {
    public void begin() {
        System.out.println("開啟事務(wù)");
    }
    public void commit() {
        System.out.println("提交事務(wù)");
    }
    public void rollback() {
        System.out.println("回滾事務(wù)");
    }
}

沒有代理

沒有代理的事務(wù)實現(xiàn)方式 -裝飾模式

可以在service實現(xiàn)類中開啟事務(wù),代碼類似于如下

public class EmployeeServiceImplWapper implements IEmployeeService {

    private EmployeeServiceImpl service;
    private TransactionManager txManager = new TransactionManager();

    public EmployeeServiceImplWapper(EmployeeServiceImpl service) {
        this.service = service;
    }
    
    @Override
    public void add() {
        try {
            txManager.begin();
            service.add();
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
    }
}

在每次增加一個方法的時候,都要添加事務(wù),顯得比較麻煩?;谪?zé)任分離的原則,service層的責(zé)任就是關(guān)注于邏輯,代碼應(yīng)當(dāng)只關(guān)注于邏輯,只編寫邏輯代碼。

在調(diào)用service方法的時候,使用如下代理

public class App {
    @Test
    public void testExtends() throws Exception {
        EmployeeServiceImpl empSer = new EmployeeServiceImpl();
        IEmployeeService serviceWapper = new EmployeeServiceImplWapper(empSer);
        serviceWapper.add();
    }
}

至此,沒有使用代理的事務(wù)實現(xiàn)方式的完成

這種方式是不安全的。因為調(diào)用者完全可以跳過EmployeeServiceImplWapper類,直接調(diào)用EmployeeServiceImpl的add方法,這樣調(diào)用的方法就沒有事務(wù)了。

代理的事務(wù)實現(xiàn)方式

靜態(tài)代理

@Component
public class EmployeeServiceImplWapper implements IEmployeeService {

    private EmployeeServiceImpl service;
    private TransactionManager txManager = new TransactionManager();
    
    public void setService(EmployeeServiceImpl service) {
        this.service = service;
    }

    @Override
    public void add() {
        try {
            txManager.begin();
            service.add();
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
    }
}

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="ser" class="com.test.EmployeeServiceImplWapper">
        <property name="service">
            <bean class="com.test.service.impl.EmployeeServiceImpl"></bean>
        </property>
    </bean>
</beans>

上述使用了spring的管理工具,service不再暴露在外。

靜態(tài)代理的優(yōu)點:

  • 業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性。

靜態(tài)代理的缺點:

  • 代理對象的某個接口只服務(wù)于某一種類型的對象,也就是說每一個真實對象都得創(chuàng)建一個代理對象。
  • 如果需要代理的方法很多,則要為每一種方法都進(jìn)行代理處理。
  • 如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。

動態(tài)代理

@Component
public class TransactionManagerInvocationHandler implements InvocationHandler {
    @Resource
    private TransactionManager txManager;
    @Resource
    private EmployeeServiceImpl service;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object obj = null;
        try {
            txManager.begin();
            obj = method.invoke(service, args);
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
        return obj;
    }

    public  T getProxy() {
        Object proxy = (IEmployeeService) Proxy.newProxyInstance(Thread
                .currentThread().getContextClassLoader(),
                new Class[] { IEmployeeService.class }, this);
        return (T) proxy;
    }
}

動態(tài)代理類是在程序運行期間由JVM通過反射等機制動態(tài)的生成的,所以不存在代理類的字節(jié)碼文件。代理對象和真實對象的關(guān)系是在程序運行時候才確定的。

接口InvocationHandler的invoke方法簡介:

  • Object proxy --> 生成的代理對象

  • Method method --> 代理對象調(diào)用的方法

  • Object[] args --> 代理對象調(diào)用的方法的參數(shù)

  • return obj --> 調(diào)用方法Method method返回的值

jdk動態(tài)代理總結(jié):

  • JAVA動態(tài)代理是使用java.lang.reflect包中的Proxy類與InvocationHandler接口這兩個來完成的。

  • 要使用JDK動態(tài)代理,必須要定義接口。

  • JDK動態(tài)代理將會攔截所有pubic的方法(因為只能調(diào)用接口中定義的方法),這樣即使在接口中增加了新的方法,不用修改代碼也會被攔截。

  • 如果只想攔截一部分方法,可以在invoke方法中對要執(zhí)行的方法名進(jìn)行判斷。

第三方代理CGLIB

@Component
public class TransactionManagerEnhancerHandler implements InvocationHandler {
    @Resource
    private TransactionManager txManager;
    @Resource
    private EmployeeServiceImpl service;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object obj = null;
        try {
            txManager.begin();
            obj = method.invoke(service, args);
            txManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }
        return obj;
    }

    public  T getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(EmployeeServiceImpl.class);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

}

cglib對指定的目標(biāo)類生成一個子類,并覆蓋其中方法實現(xiàn)增強,但因為采用的是繼承,所以不能對final修飾的類進(jìn)行代理。

CGLIB動態(tài)代理總結(jié):

  • CGLIB可以生成目標(biāo)類的子類,并重寫父類非final修飾符的方法。
  • 要求類不能是final的,要攔截的方法要是非final、非static、非private的。
  • 動態(tài)代理的最小單位是類(所有類中的方法都會被處理)。

spring對代理的選擇

當(dāng)代理的類沒有實現(xiàn)接口的時候,使用的代理方式是CGLIB。如果代理的類有實現(xiàn)的接口的時候,使用的代理方式就是jdk動態(tài)代理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容