本教程源碼請訪問:tutorial_demo 一、什麼是動態代理 1.1、概念 動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。 特點:位元組碼隨用隨創建,隨用隨載入; 作用:不修改源碼的基礎上對方法增強; 學習目的:為了學習AOP的原理做 ...
本教程源碼請訪問:tutorial_demo
一、什麼是動態代理
1.1、概念
動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。
特點:位元組碼隨用隨創建,隨用隨載入;
作用:不修改源碼的基礎上對方法增強;
學習目的:為了學習AOP的原理做準備。
1.2、實現方式
兩種方式:
- 基於介面的動態代理,JDK官方提供,被代理類最少實現一個介面,如果沒有則不能使用;
- 基於子類的動態代理,第三方cglib庫提供。
我們這篇教程使用基於介面的動態代理方式講解,所有案例都使用這種方式。
1.3、需要明確的幾個概念
目標對象:被增強的對象。
代理對象:需要目標對象,然後在目標對象上添加了增強後的對象。
目標方法:被增強的方法。
代理對象 = 目標對象 + 增強
到現在為止,我們需要知道有一種方式可以在不改變目標對象方法的前提下,對方法進行增強,這個方式就是動態代理。使用它,我們需要提供目標對象和增強生成代理對象。
得到了代理對象就相當於有了一個強化版的目標對象,運行相關方法,除了運行方法本身,增強的內容也會被運行,從而實現了在不改變源碼的前提下,對方法進行增強。
1.4、基於介面的動態代理方式詳解
1.4.1、如何生成代理對象
使用Proxy類中的newProxyInstance方法。
1.4.2、newProxyInstance方法參數詳解
ClassLoader loader
:
類載入器類型,你不用去理睬它,你只需要知道怎麼可以獲得它就可以了,獲取方法:
this.class.getClassLoader();
只要你有一個Class對象就可以獲取到ClassLoader對象。
Class[] interfaces
:
指定newProxyInstance()方法返回的對象要實現哪些介面,因為是數組,可以指定多個介面。
InvocationHandler h
:
三個參數中最重要的一個參數,是一個介面,叫調用處理器。這個介面只有一個方法,即invoke()方法。它是對代理對象所有方法的唯一實現。也就是說,無論你調用代理對象上的哪個方法,其實都是在調用InvocationHandler的invoke()方法。
1.4.3、invoke()方法參數詳解
執行被代理對象的任何介面方法都會經過該方法。
Object proxy
:代理對象,也就是Proxy.newProxyInstance()方法返回的對象,通常我們用不上它。
Method method
:表示當前被調用方法的反射對象,例如m.fun(),那m麽method就是fun()方法的反射對象;
Object[] args
:表示當前被調用方法的參數,當然m.fun()這個調用是沒有參數的,所以args是一個長度為0的數組。
二、動態代理案例
下麵通過一個案例,說明動態代理的用途。
2.1、創建Maven工程並添加坐標
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.codeaction</groupId>
<artifactId>proxy</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.2、創建一個IWaiter介面
package org.codeaction.proxy;
//表示服務員的介面
public interface IWaiter {
//提供服務的方法
void serve();
}
2.3、創建一個IWaiter介面的實現類
package org.codeaction.proxy;
//表示男服務員
public class ManWaiter implements IWaiter {
@Override
public void serve() {
System.out.println("服務...");
}
}
目前存在的問題,我希望讓ManWaiter提供服務的時候(調用serve方法)列印如下信息:
你好...
服務...
再見...
我們可以這樣做:
package org.codeaction.proxy;
//表示男服務員
public class ManWaiter implements IWaiter {
@Override
public void serve() {
System.out.println("你好...");
System.out.println("服務...");
System.out.println("再見...");
}
}
但是這樣我們修改了serve方法,如果將來有其他需求,我們還要再修改serve方法,這顯然很繁瑣,是不可取的,我們可以使用動態代理的方式在不修改源碼的基礎上對serve方法進行增強。
2.4、創建測試類使用動態代理
package org.codeaction.test;
import org.codeaction.proxy.IWaiter;
import org.codeaction.proxy.ManWaiter;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyTest {
@Test
public void TestProxy() {
//目標對象
IWaiter manWaiter = new ManWaiter();
/**
* 三個參數,用來創建代理對象
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = {IWaiter.class};
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
System.out.println("你好...");
resultValue = method.invoke(manWaiter, args);//調用目標對象的目標方法
System.out.println("再見...");
return resultValue;
}
};
//得到代理對象,代理對象就是在目標對象的基礎上進行了增強的對象
IWaiter waiter = (IWaiter) Proxy.newProxyInstance(loader, interfaces, handler);
//前面添加“您好”,後面添加“再見”
waiter.serve();
}
}
運行測試方法,輸出如下:
你好...
服務...
再見...
通過上面的代碼及運行結果我們發現:
- 使用動態代理需要提供:目標對象、三大參數;
- 生成的代理對象是實現了三大參數中第二個參數的所有介面的對象;
- 運行代理對象的方法,就是運行invoke方法;
- 在invoke方法中實現增強。
三、動態代理使用代理工廠實現
上面的案例中,目標對象和增強綁定在了一起,無法自由切換,不靈活,接下來我們創建一個代理工廠來實現動態代理。
3.1、創建前置增強介面
package org.codeaction.proxy;
//前置增強
public interface BeforeAdvice {
void before();
}
3.2、創建後置增強介面
package org.codeaction.proxy;
//後置增強
public interface AfterAdvice {
void after();
}
3.3、創建代理工廠
package org.codeaction.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 這個類用來生成代理對象
* 需要的參數:
* * 目標對象
* * 增強
* 怎麼用?
* 1.創建代理工廠
* 2.給工廠設置三樣東西:
* * 目標對象:setTargetObject(xxx);
* * 前置增強:setBeforeAdvice(該介面的實現)
* * 後置增強:setAfterAdvice(該介面的實現)
* 3.調用createProxy()得到代理對象
* * 執行代理對象方法時:
* > 執行BeforeAdvice的before()
* > 目標對象的目標方法
* > 執行AfterAdvice的after()
*/
public class ProxyFactory {
private Object targetObject;//目標對象
private BeforeAdvice beforeAdvice;//前置增強
private AfterAdvice afterAdvice;//後置增強
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
//用來生成代理對象
public Object createProxyObject() {
//三大參數
ClassLoader classLoader = this.getClass().getClassLoader();
Class[] interfaces = this.targetObject.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
//在調用代理對象的方法時會執行這裡的內容
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
if(beforeAdvice != null) {
//執行前置增強
beforeAdvice.before();
}
//執行目標對象的目標方法
resultValue = method.invoke(targetObject, args);
if(afterAdvice != null) {
//執行後置增強
afterAdvice.after();
}
//返回目標對象的返回值
return resultValue;
}
};
//得到代理對象
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
3.4、在測試類中添加測試方法
@Test
public void testProxyFactory() {
//創建工廠
ProxyFactory factory = new ProxyFactory();
//設置目標對象
factory.setTargetObject(new ManWaiter());
//設置前置增強
factory.setBeforeAdvice(new BeforeAdvice() {
@Override
public void before() {
System.out.println("你好...");
}
});
//設置後置增強
factory.setAfterAdvice(new AfterAdvice() {
@Override
public void after() {
System.out.println("再見...");
}
});
//創建代理對象
IWaiter waiter = (IWaiter) factory.createProxyObject();
//執行代理對象方法
waiter.serve();
}
運行測試方法,輸出如下:
你好...
服務...
再見...
四、使用代理工廠的方式修改上一節的代碼
在上一篇文章我們將純註解方式結合Apache Commons DbUtils實現單表的CRUD操作的代碼修改成了支持事務的版本,每一個Service方法都要開啟事務,提交事務,回滾事務代碼冗餘,如果JdbcUtils中相關方法的方法名修改,那麼Service中每個調用位置都有修改,為瞭解決上面的問題,我們使用動態代理的方式修改上一節的代碼。
4.1、創建前置增強介面
package org.codeaction.proxy;
public interface BeforeAdvice {
void before() throws Exception;
}
4.2、創建後置增強介面
package org.codeaction.proxy;
public interface AfterAdvice {
void after() throws Exception;
}
4.3、創建特殊增強介面
這個是用來進行回滾的,就教他特殊增強吧。
package org.codeaction.proxy;
public interface ActAdvice {
void act() throws Exception;
}
4.4、創建代理工廠
package org.codeaction.proxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class ProxyFactory {
private Object targetObject;
private BeforeAdvice beforeAdvice;
private AfterAdvice afterAdvice;
private ActAdvice actAdvice;
public ActAdvice getActAdvice() {
return actAdvice;
}
public void setActAdvice(ActAdvice actAdvice) {
this.actAdvice = actAdvice;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
public Object createProxyObject() {
ClassLoader classLoader = this.getClass().getClassLoader();
Class[] interfaces = this.targetObject.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
try {
if(beforeAdvice != null) {
beforeAdvice.before();
}
resultValue = method.invoke(targetObject, args);
if(afterAdvice != null) {
afterAdvice.after();
}
} catch (Exception e) {
e.printStackTrace();
if(actAdvice != null) {
actAdvice.act();
}
}
return resultValue;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
4.5、修改Service介面的實現類AccountServiceImpl
去掉所有的和事務相關的代碼,讓Service只關註業務
package org.codeaction.service.impl;
import org.codeaction.dao.IAccountDao;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public List<Account> findAll() throws Exception {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) throws Exception {
return accountDao.findById(id);
}
@Override
public void save(Account account) throws Exception {
accountDao.save(account);
}
@Override
public void update(Account account) throws Exception {
accountDao.update(account);
}
@Override
public void delete(Integer id) throws Exception {
accountDao.delete(id);
}
@Override
public void transfer(Integer srcId, Integer dstId, Float money) throws Exception {
Account src = accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("轉出用戶不存在");
}
if(dst == null) {
throw new RuntimeException("轉入用戶不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("轉出賬戶餘額不足");
}
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
accountDao.update(src);
//int x = 1/0;
accountDao.update(dst);
}
}
4.6、修改主配置類
package org.codeaction.config;
import org.codeaction.proxy.ActAdvice;
import org.codeaction.proxy.AfterAdvice;
import org.codeaction.proxy.BeforeAdvice;
import org.codeaction.proxy.ProxyFactory;
import org.codeaction.service.IAccountService;
import org.codeaction.util.JdbcUtils;
import org.springframework.context.annotation.*;
import java.sql.SQLException;
@Configuration
@ComponentScan(basePackages = "org.codeaction")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
public class MyConfig {
/**
*
* @param factory 代理工廠
* @param accountService 目標對象
* @return
*/
@Bean("proxyAccountService")
public IAccountService createProxyAccountService(ProxyFactory factory, IAccountService accountService) {
factory.setTargetObject(accountService);
factory.setBeforeAdvice(new BeforeAdvice() {
@Override
public void before() throws Exception {
//開啟事務
JdbcUtils.beginTransaction();
}
});
factory.setAfterAdvice(new AfterAdvice() {
@Override
public void after() throws Exception {
//提交事務
JdbcUtils.commitTransaction();
}
});
factory.setActAdvice(new ActAdvice() {
@Override
public void act() throws Exception {
//回滾
JdbcUtils.rollbackTransaction();
}
});
//生成代理對象
return (IAccountService)factory.createProxyObject();
}
}
4.7、修改測試類
package org.codeaction.test;
import org.codeaction.config.MyConfig;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class MyTest {
//註入代理工廠對象
@Autowired
@Qualifier("proxyAccountService")
private IAccountService accountService;
@Test
public void testFindAll() throws Exception {
List<Account> accounts = accountService.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testFindById() throws Exception {
Account account = accountService.findById(3);
System.out.println(account);
}
@Test
public void testSave() throws Exception {
Account account = new Account();
account.setName("abc");
account.setMoney(10000F);
accountService.save(account);
System.out.println(account);
}
@Test
public void testDelete() throws Exception {
accountService.delete(4);
}
@Test
public void testUpdate() throws Exception {
Account account = new Account();
account.setId(5);
account.setName("ab111111111c111");
account.setMoney(10000F);
accountService.update(account);
}
@Test
public void testTrans() throws Exception {
accountService.transfer(1, 2, 10F);
}
}
註意這裡註入的accountServie是代理工廠類的對象,運行測試方法,測試。