動態代理的兩種實現方式


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的方式來控制事務。


免責聲明!

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



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