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
-
在invok方法中對方法做增強處理。InvocationHandler h
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檔案Class type
-
一個Callback接口,我們通常使用Callback callback
接口,繼承了Callback接口MethodInterceptor
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);
}
可以看到代理類實作了事務,當代碼報錯,資料正常復原了。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLwEjM2ITM1QTMwIjMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
五、總結
1、JDK動态代理,自帶的,友善使用,但是要要求必須實作接口,有一定的限制。
2、cglib,需要導入第三方jar包,使用的時候沒有什麼限制。
3、SpringAOP以上2種方法都用到了。
4、學完動态代理,可以結合BeanFactory建立Bean的方式來控制事務。