天天看點

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

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() {
}
           
  • ClassLoader loader

    擷取被代理類的類加載器。
  • Class<?>[] interfaces

    擷取被代理類的實作接口的數組。
  • InvocationHandler h

    在invok方法中對方法做增強處理。

2、invoke方法的三個參數

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
           
  • 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() {
}
           
  • Class type

    被代理類的class檔案
  • Callback callback

    一個Callback接口,我們通常使用

    MethodInterceptor

    接口,繼承了Callback接口

2、intercept方法的參數

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
}
           
  • 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();
        }
    }
}
           

現在我們隻留一行代碼

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

可以看到代理類實作了事務,當代碼報錯,資料正常復原了。

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

五、總結

1、JDK動态代理,自帶的,友善使用,但是要要求必須實作接口,有一定的限制。

2、cglib,需要導入第三方jar包,使用的時候沒有什麼限制。

3、SpringAOP以上2種方法都用到了。

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