代理可以分為靜態(tài)代理、動(dòng)態(tài)代理翔冀,動(dòng)態(tài)代理又可以分為 jvm的動(dòng)態(tài)代理 和 cglib的動(dòng)態(tài)代理。像spring框架的AOP的底層就使用了動(dòng)態(tài)代理的技術(shù)。
代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式;即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.
這里使用到編程中的一個(gè)思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴(kuò)展該方法
舉個(gè)例子來說明代理的作用:假設(shè)我們想邀請(qǐng)一位明星,那么并不是直接連接明星,而是聯(lián)系明星的經(jīng)紀(jì)人,來達(dá)到同樣的目的.明星就是一個(gè)目標(biāo)對(duì)象,他只要負(fù)責(zé)活動(dòng)中的節(jié)目,而其他瑣碎的事情就交給他的代理人(經(jīng)紀(jì)人)來解決.這就是代理思想在現(xiàn)實(shí)中的一個(gè)例子
用圖表示如下:
代理模式的關(guān)鍵點(diǎn)是:代理對(duì)象與目標(biāo)對(duì)象.代理對(duì)象是對(duì)目標(biāo)對(duì)象的擴(kuò)展,并會(huì)調(diào)用目標(biāo)對(duì)象
一蚁署、靜態(tài)代理
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類.
下面舉個(gè)案例來解釋:
模擬保存動(dòng)作,定義一個(gè)保存動(dòng)作的接口:IUserDao.java,然后目標(biāo)對(duì)象實(shí)現(xiàn)這個(gè)接口的方法UserDao.java,此時(shí)如果使用靜態(tài)代理方式,就需要在代理對(duì)象(UserDaoProxy.java)中也實(shí)現(xiàn)IUserDao接口.調(diào)用的時(shí)候通過調(diào)用代理對(duì)象的方法來調(diào)用目標(biāo)對(duì)象.
需要注意的是,代理對(duì)象與目標(biāo)對(duì)象要實(shí)現(xiàn)相同的接口,然后通過調(diào)用相同的方法來調(diào)用目標(biāo)對(duì)象的方法
代碼示例:
接口:IUserDao.java
/**
* 接口
*/
public interface IUserDao {
void save();
}
目標(biāo)對(duì)象:UserDao.java
/**
* 接口實(shí)現(xiàn)
* 目標(biāo)對(duì)象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
}
}
代理對(duì)象:UserDaoProxy.java
/**
* 代理對(duì)象,靜態(tài)代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目標(biāo)對(duì)象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("開始事務(wù)...");
target.save();//執(zhí)行目標(biāo)對(duì)象的方法
System.out.println("提交事務(wù)...");
}
}
測(cè)試類:App.java
/**
* 測(cè)試類
*/
public class App {
public static void main(String[] args) {
//目標(biāo)對(duì)象
UserDao target = new UserDao();
//代理對(duì)象,把目標(biāo)對(duì)象傳給代理對(duì)象,建立代理關(guān)系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//執(zhí)行的是代理的方法
}
}
靜態(tài)代理總結(jié):
1.可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展.
2.缺點(diǎn):因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多.同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).
如何解決靜態(tài)代理中的缺點(diǎn)呢?答案是可以使用動(dòng)態(tài)代理方式
二、動(dòng)態(tài)代理
2.1蚂四、JDK接口動(dòng)態(tài)代理
2.1.1 動(dòng)態(tài)代理特點(diǎn)
1.代理對(duì)象,不需要實(shí)現(xiàn)接口
2.代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
3.動(dòng)態(tài)代理也叫做:JDK代理,接口代理
JDK中生成代理對(duì)象的API
代理類所在包:java.lang.reflect.Proxy
JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個(gè)參數(shù),完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態(tài)方法,且接收的三個(gè)參數(shù)依次為:
- ClassLoader loader,:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces,:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
- InvocationHandler h:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入
2.1.2 代碼示例
接口類IUserDao.java以及接口實(shí)現(xiàn)類,目標(biāo)對(duì)象UserDao是一樣的,沒有做修改.在這個(gè)基礎(chǔ)上,增加一個(gè)代理工廠類(ProxyFactory.java),將代理類寫在這個(gè)地方,然后在測(cè)試類(需要使用到代理的代碼)中先建立目標(biāo)對(duì)象和代理對(duì)象的聯(lián)系,然后代用代理對(duì)象的中同名方法
代理工廠類:ProxyFactory.java
/**
* 創(chuàng)建動(dòng)態(tài)代理對(duì)象
* 動(dòng)態(tài)代理不需要實(shí)現(xiàn)接口,但是需要指定接口類型
*/
public class ProxyFactory{
//維護(hù)一個(gè)目標(biāo)對(duì)象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//給目標(biāo)對(duì)象生成代理對(duì)象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始事務(wù)2");
//執(zhí)行目標(biāo)對(duì)象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務(wù)2");
return returnValue;
}
}
);
}
}
測(cè)試類:App.java
/**
* 測(cè)試類
*/
public class App {
public static void main(String[] args) {
// 目標(biāo)對(duì)象
IUserDao target = new UserDao();
// 【原始的類型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 給目標(biāo)對(duì)象光戈,創(chuàng)建代理對(duì)象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 內(nèi)存中動(dòng)態(tài)生成的代理對(duì)象
System.out.println(proxy.getClass());
// 執(zhí)行方法 【代理對(duì)象】
proxy.save();
}
}
總結(jié):
代理對(duì)象不需要實(shí)現(xiàn)接口(需實(shí)現(xiàn)InvocationHandler接口),但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理。
2.1.3 原理分析
首先是定義一個(gè)Person接口:
/**
* 創(chuàng)建Person接口
* @author Gonjan
*/
public interface Person {
//上交班費(fèi)
void giveMoney();
}
創(chuàng)建需要被代理的實(shí)際類:
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假設(shè)數(shù)錢花了一秒時(shí)間
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "上交班費(fèi)50元");
}
}
再定義一個(gè)檢測(cè)方法執(zhí)行時(shí)間的工具類遂赠,在任何方法執(zhí)行前先調(diào)用start方法久妆,執(zhí)行后調(diào)用finsh方法,就可以計(jì)算出該方法的運(yùn)行時(shí)間跷睦,這也是一個(gè)最簡(jiǎn)單的方法執(zhí)行時(shí)間檢測(cè)工具筷弦。
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
//結(jié)束時(shí)打印耗時(shí)
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗時(shí)" + (finishTime - tl.get()) + "ms");
}
}
創(chuàng)建StuInvocationHandler類,實(shí)現(xiàn)InvocationHandler接口抑诸,這個(gè)類中持有一個(gè)被代理對(duì)象的實(shí)例target烂琴。InvocationHandler中有一個(gè)invoke方法,所有執(zhí)行代理對(duì)象的方法都會(huì)被替換成執(zhí)行invoke方法哼鬓。
再再invoke方法中執(zhí)行被代理對(duì)象target的相應(yīng)方法监右。當(dāng)然,在代理過程中异希,我們?cè)谡嬲龍?zhí)行被代理對(duì)象的方法前加入自己其他處理健盒。這也是Spring中的AOP實(shí)現(xiàn)的主要原理,這里還涉及到一個(gè)很重要的關(guān)于java反射方面的基礎(chǔ)知識(shí)称簿。
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理對(duì)象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表動(dòng)態(tài)代理對(duì)象
* method:代表正在執(zhí)行的方法
* args:代表調(diào)用目標(biāo)方法時(shí)傳入的實(shí)參
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執(zhí)行" +method.getName() + "方法");
*/
//代理過程中插入監(jiān)測(cè)方法,計(jì)算該方法耗時(shí)
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
做完上面的工作后扣癣,我們就可以具體來創(chuàng)建動(dòng)態(tài)代理對(duì)象了,上面簡(jiǎn)單介紹了如何創(chuàng)建動(dòng)態(tài)代理對(duì)象憨降,我們使用簡(jiǎn)化的方式創(chuàng)建動(dòng)態(tài)代理對(duì)象:
public class ProxyTest {
public static void main(String[] args) {
//創(chuàng)建一個(gè)實(shí)例對(duì)象父虑,這個(gè)對(duì)象是被代理的對(duì)象
Person zhangsan = new Student("張三");
//創(chuàng)建一個(gè)與代理對(duì)象相關(guān)聯(lián)的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//創(chuàng)建一個(gè)代理對(duì)象stuProxy來代理zhangsan,代理對(duì)象的每個(gè)執(zhí)行方法都會(huì)替換執(zhí)行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler)授药;
//代理執(zhí)行上交班費(fèi)的方法
stuProxy.giveMoney();
}
}
我們執(zhí)行這個(gè)ProxyTest類士嚎,先想一下呜魄,我們創(chuàng)建了一個(gè)需要被代理的學(xué)生張三,將zhangsan對(duì)象傳給了stuHandler中莱衩,我們?cè)趧?chuàng)建代理對(duì)象stuProxy時(shí)爵嗅,將stuHandler作為參數(shù)了的,上面也有說到所有執(zhí)行代理對(duì)象的方法都會(huì)被替換成執(zhí)行invoke方法笨蚁,也就是說睹晒,最后執(zhí)行的是StuInvocationHandler中的invoke方法。所以在看到下面的運(yùn)行結(jié)果也就理所當(dāng)然了括细。
運(yùn)行結(jié)果:
代理執(zhí)行g(shù)iveMoney方法
張三上交班費(fèi)50元
giveMoney方法耗時(shí)1001ms
上面說到伪很,動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類中的方法奋单。是因?yàn)樗斜淮韴?zhí)行的方法锉试,都是通過在InvocationHandler中的invoke方法調(diào)用的,所以我們只要在invoke方法中統(tǒng)一處理辱匿,就可以對(duì)所有被代理的方法進(jìn)行相同的操作了键痛。例如,這里的方法計(jì)時(shí)匾七,所有的被代理對(duì)象執(zhí)行的方法都會(huì)被計(jì)時(shí)絮短,然而我只做了很少的代碼量。
動(dòng)態(tài)代理的過程昨忆,代理對(duì)象和被代理對(duì)象的關(guān)系不像靜態(tài)代理那樣一目了然丁频,清晰明了。因?yàn)閯?dòng)態(tài)代理的過程中邑贴,我們并沒有實(shí)際看到代理類席里,也沒有很清晰地的看到代理類的具體樣子,而且動(dòng)態(tài)代理中被代理對(duì)象和代理對(duì)象是通過InvocationHandler來完成的代理過程的拢驾,其中具體是怎樣操作的奖磁,為什么代理對(duì)象執(zhí)行的方法都會(huì)通過InvocationHandler中的invoke方法來執(zhí)行。帶著這些問題繁疤,我們就需要對(duì)java動(dòng)態(tài)代理的源碼進(jìn)行簡(jiǎn)要的分析咖为,弄清楚其中緣由。
**1.Java 動(dòng)態(tài)代理創(chuàng)建出來的動(dòng)態(tài)代理類 **
上面我們利用Proxy類的newProxyInstance方法創(chuàng)建了一個(gè)動(dòng)態(tài)代理對(duì)象稠腊,查看該方法的源碼躁染,發(fā)現(xiàn)它只是封裝了創(chuàng)建動(dòng)態(tài)代理類的步驟(紅色標(biāo)準(zhǔn)部分):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
其實(shí),我們最應(yīng)該關(guān)注的是 Class
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理類class文件寫入成功");
} catch (Exception e) {
System.out.println("寫文件錯(cuò)誤");
}
對(duì)這個(gè)class文件進(jìn)行反編譯架忌,我們看看jdk為我們生成了什么樣的內(nèi)容:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*注意這里是生成代理類的構(gòu)造方法吞彤,方法參數(shù)為InvocationHandler類型,看到這,是不是就有點(diǎn)明白
*為何代理對(duì)象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法饰恕,而InvocationHandler又持有一個(gè)
*被代理對(duì)象的實(shí)例挠羔,不禁會(huì)想難道是....? 沒錯(cuò)埋嵌,就是你想的那樣褥赊。
*
*super(paramInvocationHandler),是調(diào)用父類Proxy的構(gòu)造方法莉恼。
*父類持有:protected InvocationHandler h;
*Proxy構(gòu)造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//這個(gè)靜態(tài)塊本來是在最后的,我把它拿到前面來速那,方便描述
static
{
try
{
//看看這兒靜態(tài)塊兒里面有什么俐银,是不是找到了giveMoney方法。請(qǐng)記住giveMoney通過反射得到的名字m3端仰,其他的先不管
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
*
*這里調(diào)用代理對(duì)象的giveMoney方法捶惜,直接就調(diào)用了InvocationHandler中的invoke方法,并把m3傳了進(jìn)去荔烧。
*this.h.invoke(this, m3, null);這里簡(jiǎn)單吱七,明了。
*來鹤竭,再想想踊餐,代理對(duì)象持有一個(gè)InvocationHandler對(duì)象,InvocationHandler對(duì)象持有一個(gè)被代理的對(duì)象臀稚,
*再聯(lián)系到InvacationHandler中的invoke方法吝岭。嗯,就是這樣吧寺。
*/
public final void giveMoney()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意窜管,這里為了節(jié)省篇幅,省去了toString稚机,hashCode幕帆、equals方法的內(nèi)容。原理和giveMoney方法一毛一樣赖条。
}
jdk為我們的生成了一個(gè)叫$Proxy0(這個(gè)名字后面的0是編號(hào)失乾,有多個(gè)代理類會(huì)一次遞增)的代理類,這個(gè)類文件時(shí)放在內(nèi)存中的谋币,我們?cè)趧?chuàng)建代理對(duì)象時(shí)仗扬,就是通過反射獲得這個(gè)類的構(gòu)造方法,然后創(chuàng)建的代理實(shí)例蕾额。通過對(duì)這個(gè)生成的代理類源碼的查看早芭,我們很容易能看出,動(dòng)態(tài)代理實(shí)現(xiàn)的具體過程诅蝶。
我們可以對(duì)InvocationHandler看做一個(gè)中介類退个,中介類持有一個(gè)被代理對(duì)象募壕,在invoke方法中調(diào)用了被代理對(duì)象的相應(yīng)方法。通過聚合方式持有被代理對(duì)象的引用语盈,把外部對(duì)invoke的調(diào)用最終都轉(zhuǎn)為對(duì)被代理對(duì)象的調(diào)用舱馅。
代理類調(diào)用自己方法時(shí),通過自身持有的中介類對(duì)象來調(diào)用中介類對(duì)象的invoke方法刀荒,從而達(dá)到代理執(zhí)行被代理對(duì)象的方法代嗤。也就是說,動(dòng)態(tài)代理通過中介類實(shí)現(xiàn)了具體的代理功能缠借。
生成的代理類:$Proxy0 extends Proxy implements Person干毅,我們看到代理類繼承了Proxy類,所以也就決定了java動(dòng)態(tài)代理只能對(duì)接口進(jìn)行代理泼返,Java的繼承機(jī)制注定了這些動(dòng)態(tài)代理類們無法實(shí)現(xiàn)對(duì)class的動(dòng)態(tài)代理硝逢。
上面的動(dòng)態(tài)代理的例子,其實(shí)就是AOP的一個(gè)簡(jiǎn)單實(shí)現(xiàn)了绅喉,在目標(biāo)對(duì)象的方法執(zhí)行之前和執(zhí)行之后進(jìn)行了處理渠鸽,對(duì)方法耗時(shí)統(tǒng)計(jì)。Spring的AOP實(shí)現(xiàn)其實(shí)也是用了Proxy和InvocationHandler這兩個(gè)東西的柴罐。
2.2 Cglib動(dòng)態(tài)代理
2.2.1 Cglib動(dòng)態(tài)代理特點(diǎn)
上面的靜態(tài)代理和動(dòng)態(tài)代理模式都是要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類的方式類實(shí)現(xiàn)代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在內(nèi)存中構(gòu)建一個(gè)子類對(duì)象從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象功能的擴(kuò)展.
- JDK的動(dòng)態(tài)代理有一個(gè)限制,就是使用動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口,如果想代理沒有實(shí)現(xiàn)接口的類,就可以使用Cglib實(shí)現(xiàn).
- Cglib是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展java類與實(shí)現(xiàn)java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
- Cglib包的底層是通過使用一個(gè)小而塊的字節(jié)碼處理框架ASM來轉(zhuǎn)換字節(jié)碼并生成新的類.不鼓勵(lì)直接使用ASM,因?yàn)樗竽惚仨殞?duì)JVM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉.
Cglib子類代理實(shí)現(xiàn)方法:
- 1.需要引入cglib的jar文件,但是Spring的核心包中已經(jīng)包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
- 2.引入功能包后,就可以在內(nèi)存中動(dòng)態(tài)構(gòu)建子類
- 3.代理的類不能為final,否則報(bào)錯(cuò)
- 4.目標(biāo)對(duì)象的方法如果為final/static,那么就不會(huì)被攔截,即不會(huì)執(zhí)行目標(biāo)對(duì)象額外的業(yè)務(wù)方法.
2.2.2 代碼示例
目標(biāo)對(duì)象類:UserDao.java
/**
* 目標(biāo)對(duì)象,沒有實(shí)現(xiàn)任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
}
}
Cglib代理工廠:ProxyFactory.java
/**
* Cglib子類代理工廠
* 對(duì)UserDao在內(nèi)存中動(dòng)態(tài)構(gòu)建一個(gè)子類對(duì)象
*/
public class ProxyFactory implements MethodInterceptor{
//維護(hù)目標(biāo)對(duì)象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//給目標(biāo)對(duì)象創(chuàng)建一個(gè)代理對(duì)象
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設(shè)置父類
en.setSuperclass(target.getClass());
//3.設(shè)置回調(diào)函數(shù)
en.setCallback(this);
//4.創(chuàng)建子類(代理對(duì)象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("開始事務(wù)...");
//執(zhí)行目標(biāo)對(duì)象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務(wù)...");
return returnValue;
}
}
測(cè)試類:
/**
* 測(cè)試類
*/
public class App {
@Test
public void test(){
//目標(biāo)對(duì)象
UserDao target = new UserDao();
//代理對(duì)象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//執(zhí)行代理對(duì)象的方法
proxy.save();
}
}
在Spring的AOP編程中:
如果加入容器的目標(biāo)對(duì)象有實(shí)現(xiàn)接口,用JDK代理
如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,用Cglib代理
2.2.3 Cglib代理原理分析
如果我們了解了CGLIB創(chuàng)建代理類的原理徽缚,那么其局限性也就一目了然。我們現(xiàn)在做個(gè)實(shí)驗(yàn)丽蝎,將UserDao類加上final修飾符猎拨,使其不可被繼承:
再次執(zhí)行測(cè)試代碼,這次就報(bào)錯(cuò)了: Cannot subclass final class XXXX屠阻。
所以通過CGLIB成功創(chuàng)建的動(dòng)態(tài)代理红省,實(shí)際是被代理類的一個(gè)子類。那么如果被代理類被標(biāo)記成final国觉,也就無法通過CGLIB去創(chuàng)建動(dòng)態(tài)代理吧恃。
3、通過編譯期提供的API動(dòng)態(tài)創(chuàng)建代理類
假設(shè)我們確實(shí)需要給一個(gè)既是final麻诀,又未實(shí)現(xiàn)任何接口的ProductOwner類創(chuàng)建動(dòng)態(tài)代碼痕寓。除了InvocationHandler和CGLIB外,我們還有最后一招:
我直接把一個(gè)代理類的源代碼用字符串拼出來蝇闭,然后基于這個(gè)字符串調(diào)用JDK的Compiler(編譯期)API呻率,動(dòng)態(tài)的創(chuàng)建一個(gè)新的.java文件,然后動(dòng)態(tài)編譯這個(gè).java文件呻引,這樣也能得到一個(gè)新的代理類礼仗。
package proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class ProductOwnerSourceCodeProxy {
public static void main(String[] arg) throws Exception {
Class<?> c = getProxyClass();
Constructor<?>[] constructor = c.getConstructors();
Object POProxy = constructor[0].newInstance("Ross");
Method defineBackLog = c.getDeclaredMethod("defineBackLog");
defineBackLog.invoke(POProxy);
}
private static String getSourceCode() {
String src = "package proxy;\n\n"
+ "public final class ProductOwnerSCProxy {\n"
+ "\tprivate String name;\n\n"
+ "\tpublic ProductOwnerSCProxy(String name){\n"
+ "\t\tthis.name = name;\n" + "\t}\n\n"
+ "\t\tpublic void defineBackLog(){\n"
+ "\t\tSystem.out.println(\"PO writes some document before defining BackLog\");"
+ "\t\tSystem.out.println(\"PO: \" + name + \" defines Backlog.\");}}\n";
return src;
}
private static String createJavaFile(String sourceCode) {
String fileName = "C:\\Users\\i042416\\git\\JavaTwoPlusTwoEquals5\\src\\proxy\\ProductOwnerSCProxy.java";
File javaFile = new File(fileName);
Writer writer;
try {
writer = new FileWriter(javaFile);
writer.write(sourceCode);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return fileName;
}
private static void compile(String fileName) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> iter = sjfm.getJavaFileObjects(fileName);
CompilationTask ct = compiler.getTask(null, sjfm, null, null, null, iter);
ct.call();
sjfm.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Class<?> loadClass() {
URL[] urls;
String path = "file:\\C:\\Users\\i042416\\git\\JavaTwoPlusTwoEquals5\\src\\";
Class<?> c = null;
try {
urls = new URL[] { (new URL(path)) };
URLClassLoader ul = new URLClassLoader(urls);
c = ul.loadClass("proxy.ProductOwnerSCProxy");
ul.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return c;
}
private static Class<?> getProxyClass() {
String sourceCode = getSourceCode();
String javaFile = createJavaFile(sourceCode);
compile(javaFile);
return loadClass();
}
}
參考
Java代理設(shè)計(jì)模式(Proxy)的四種具體實(shí)現(xiàn)
java 代理 理解原理及實(shí)現(xiàn)
Java的三種代理模式