1. 程式人生 > 其它 >動態代理的兩種實現方式

動態代理的兩種實現方式

Spring學習(五):動態代理的兩種實現方式(全網最容易懂)

前言

    要學習SpringAOP之前,肯定要弄清楚什麼是動態代理,動態代理是怎樣實現的,以及動態代理能解決什麼問題。

一、什麼是動態代理

1、字面意思,代理就是代替別人去做一些事情,如線下店代替工廠去賣電腦、代理工廠做售後工作,線下店就是代理商,從賣給工廠的獲得的錢提取分成就是增強的方法。

2、Java中就是在不改變別別的類,對類做增強處理,如列印日誌、事物的控制,許可權的管理,後續我們都會介紹。
二、兩種實現動態代理的方法
1、基於JDK的動態代理

基於介面的動態代理,用到的類是Proxy的newProxyInstance靜態方法建立,要求被代理物件至少實現一個介面,如果沒有,則不能建立代理物件。
2、基於cglib的動態代理

要匯入cglib第三方庫,使用的類是Enhancer的create靜態方法建立,要求被代理類不能是最終類,即不能用final修飾,如String類。
三、程式碼演示
1、首先建立一個IProduct介面,並建立被代理類,實現這個介面

IProduct

public interface IProduct {
    String sell(Float money);
    void afterSell();
}



Product

public class Product implements IProduct {
    @Override
    public String sell(Float money) {
        System.out.println("代理員交給工廠:"+money);
        return "aaa";
    }
    @Override
    public void afterSell() {
        System.out.println("代理員做售後。。");
    }
}



2、通過JDK來實現動態代理,建立一個消費者Consumer

    這裡我們直接通過匿名內部類來實現,當然不是必須的

Consumer類

public class Consumer {
    public static void main(String[] args) {
        // 建立一個被代理物件
        final Product product = new Product();
        // 建立一個代理物件,並在InvocationHandler的invoke方法裡面,對被代理類的方法做增強
        IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(), product.getClass().getInterfaces(), new InvocationHandler() {
            // 實現具體的增強操作           
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 獲取方法在執行中可能產生的返回值
                Object returnValue = null;
                Float money = (Float) args[0];
                if("sell".equals(method.getName())){
                    // 執行具體的方法
                    returnValue = method.invoke(product, money*0.8F);
                }
                return returnValue;
            }
        });
        System.out.println(proxyProduct.sell(1000F));
    }
}



程式碼分析

1、Proxy.newProxyInstance的三個引數

IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(), product.getClass().getInterfaces(), new InvocationHandler() {
}

    1
    2

    ClassLoader loader獲取被代理類的類載入器。
    Class<?>[] interfaces獲取被代理類的實現介面的陣列。
    InvocationHandler h在invok方法中對方法做增強處理。

2、invoke方法的三個引數

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

    1
    2

    Object proxy 當前代理物件
    Method method 當前方法
    Object[] args方法傳遞的引數

3、通過cglib來實現動態代理,建立一個消費者Consumer

public class Consumer {
    public static void main(final String[] args) {
        // 建立一個被代理物件,這裡要求必須是final
        final Product product = new Product();
        Product proxyProduct =(Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Float money = (Float) objects[0];
                Object returnValue = null;
                if("sell".equals(method.getName())){
                    returnValue = method.invoke(product, 0.8f * money);
                }
                return returnValue;
            }
        });
        System.out.println(proxyProduct.sell(1000f));
    }
}


程式碼分析
1、Enhancer.create的2個引數

Product proxyProduct =(Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
}

    1
    2

    Class type被代理類的class檔案
    Callback callback一個Callback介面,我們通常使用MethodInterceptor介面,繼承了Callback介面

2、intercept方法的引數

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
}

    1
    2

    Method method當前方法
    Object[] objects方法用到的引數陣列

4、測試結果

    代理員交給工廠:800.0
    aaa

代理商收取了200塊提成。
四、結合BeanFactory建立Bean的方式來控制事務

    學完動態代理,可以結合BeanFactory建立Bean的方式來控制事務

1、改造事務分析

Spring學習(四):事務的學習之銀行轉賬案例
原來的事務控制我們是寫在Service層,現在我們要把重複程式碼抽取出來,統一交給代理物件去管理事務。
原Service程式碼

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    TransactionManager transactionManager;
    @Autowired
    IAccountDao accountDao;
    @Autowired
    private ConnectionUtils connectionUtils;
    
    @Override
    public void updateAccount(Account account) {
        try {
            transactionManager.beginTransaction();
            accountDao.updateAccount(account);
            int a = 1/0; // 模擬業務層出錯
            transactionManager.commitTransaction();
        }catch (Exception e){
            transactionManager.rollbackTransaction();
            e.printStackTrace();
        }finally {
            transactionManager.release();
        }
    }
}



現在我們只留一行程式碼

accountDao.updateAccount(account);

    1

2、程式碼編寫思路分析

    建立一個BeanFactory,裡面注入一個AccountService。
    在get方法中返回一個代理物件。
    選擇一種動態代理的實現方法,編寫代理詳細實現程式碼。
    配置bean.xml配置檔案

3、程式碼的實現

BeanFactory類

public class BeanFactory {

    @Autowired
    /**
     * 由於配置檔案有2個AccountService實現類的bean配置,所以要指定beanId才可以自動注入
     * proxyAccountService、accountService
     */
    @Qualifier("accountService")
    private IAccountService iAccountService;

    @Autowired
    TransactionManager transactionManager;

    // 通過JDK動態代理實現
    public IAccountService getAccountService() {
        IAccountService proxyIaccountService = (IAccountService) Proxy.newProxyInstance(iAccountService.getClass().getClassLoader(), iAccountService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnValue = null;
                try {
                    transactionManager.beginTransaction();
                    System.out.println("開啟事務。。。");
                    System.out.println("執行【"+method.getName()+"】方法。。。");
                    returnValue = method.invoke(iAccountService, args);
                    System.out.println(5/0);
                    transactionManager.commitTransaction();
                    System.out.println("COMMIT事務。。。");
                }catch (Exception e){
                    System.out.println("ROLLBACK事務。。。");
                    transactionManager.rollbackTransaction();
                    e.printStackTrace();
                }finally {
                    transactionManager.release();
                }
                return returnValue;
            }
        });
        return proxyIaccountService;
    }

    // 通過Cglib動態代理實現
    public IAccountService getAccountServiceByCglib() {
        IAccountService proxyAccountServiceByCglib = (IAccountService) Enhancer.create(IAccountService.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                try {
                    transactionManager.beginTransaction();
                    System.out.println("開啟事務。。。");
                    System.out.println("執行【"+method.getName()+"】方法。。。");
                    returnValue = method.invoke(iAccountService, objects);
                    System.out.println(5/0);
                    transactionManager.commitTransaction();
                    System.out.println("COMMIT事務。。。");
                }catch (Exception e){
                    System.out.println("ROLLBACK事務。。。");
                    transactionManager.rollbackTransaction();
                    e.printStackTrace();
                }finally {
                    transactionManager.release();
                }
                return returnValue;            }
        });
        return proxyAccountServiceByCglib;
    }
    
    public void setIAccountService(IAccountService iAccountService) {
        this.iAccountService = iAccountService;
    }
}


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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--找到對應的XML頭,和開啟包掃描-->
    <context:component-scan base-package="com"/>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置資料來源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
        <property name="user" value="root"/>
        <property name="password" value="123456" />
    </bean>

    <bean id="connectionUtils" class="com.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="transationManager" class="com.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils" />
    </bean>

    <!-- 配置BeanFactory類,用工廠建立我們的代理AccountService -->
    <bean id="beanFactory" class="com.utils.BeanFactory"></bean>
    <!-- 通過JDK動態代理實現 -->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
    <!-- 通過Cglib動態代理實現 -->
    <bean id="proxyAccountServiceByCglib" factory-bean="beanFactory" factory-method="getAccountServiceByCglib"></bean>

</beans>


測試類

public void testFindAccountAll(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService = (IAccountService) context.getBean("proxyAccountService");
        Account account = new Account();
        account.setId(1);
        account.setMoney(500D);
        account.setName("aaa");
        accountService.updateAccount(account);
}



可以看到代理類實現了事務,當代碼報錯,資料正常回滾了。
在這裡插入圖片描述
五、總結

1、JDK動態代理,自帶的,方便使用,但是要要求必須實現介面,有一定的約束。
2、cglib,需要匯入第三方jar包,使用的時候沒有什麼約束。
3、SpringAOP以上2種方法都用到了。
4、學完動態代理,可以結合BeanFactory建立Bean的方式來控制事務。