代理模式淺析
1.什么是代理模式
什么是代理模式呢码俩,首先舉個簡單的例子够傍,張三(用戶)想要一張演唱會的門票但是自己沒有渠道購買拂苹,這時他的朋友李四(代理人)說他能夠買到演唱會的門票(被代理方法)安聘,張三只需要請李四幫忙購買門票即可,至于李四是怎么買的通過什么方式買的張三并不需要知道瓢棒。代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問浴韭。在某些情況下,一個對象不適合或者不能直接引用另一個對象脯宿,而代理對象可以在客戶端和目標對象之間起到中介的作用念颈。使用代理模式主要有兩個目的:一是保護目標對象窟感,而是增強目標對象蜜自。
代理模式類圖
Subject 是頂層接口,RealSubject 是真實對象(被代理對象)暮蹂,Proxy 是代理對象棠枉,代理對象持有被代理對象的引用,客戶端調(diào)用代理對象方法,同時也調(diào)用被代理對象的方法钦无,但是在代理對象前后增加一些處理。在代碼中索昂,我們想到代理秉宿,就會理解為是代碼增強韵丑,其實就是在原本邏輯前后增加一些邏輯轴合,而調(diào)用者無感知。代理模式屬于結(jié)構(gòu)型模式鸯隅,有靜態(tài)代理和動態(tài)代理蝌以。
2.靜態(tài)代理
什么是靜態(tài)代理虱痕,其實這個動靜區(qū)別是對于程序來說的蛔翅,所謂靜態(tài)也就是在程序運行之前就已經(jīng)存在代理類的字節(jié)碼文件仅讽,代理類和委托類的關系在運行前就已經(jīng)確定了陶缺。下面用代碼寫一個簡單的靜態(tài)代理示例。
public class Order {
private Object orderInfo;
private Long createTime;
private String id;
public Object getOrderInfo() {
return orderInfo;
}
public void setOrderInfo(Object orderInfo) {
this.orderInfo = orderInfo;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
/**
* @author: Winston
* @createTime: 2021/6/15
*/
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao 創(chuàng)建 Order 成功!");
return 1;
}
}
public interface IOrderService {
int createOrder(Order order);
}
public class OrderService implements IOrderService {
private OrderDao orderDao;
public OrderService() {
//如果使用 Spring 應該是自動注入的
//我們?yōu)榱耸褂梅奖憬嗔椋跇?gòu)造方法中將 orderDao 直接初始化了
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService 調(diào)用 orderDao 創(chuàng)建訂單");
return orderDao.insert(order);
}
}
有個IOrderService接口饱岸,里面有個創(chuàng)建訂單的方法createOrder,OrderService實現(xiàn)了IOrderService接口徽千。假設現(xiàn)在在分布式環(huán)境中苫费,需要對數(shù)據(jù)庫進行分庫分表,分庫分表進行完之后使用JAVA操作時双抽,就需要配置多個數(shù)據(jù)源百框,我們通過設置數(shù)據(jù)源路由來動態(tài)切換數(shù)據(jù)源。根據(jù)開閉原則牍汹,已經(jīng)寫好的邏輯不改動铐维,通過代理對象的方式進行修改,我們主要完成的功能是根據(jù)訂單創(chuàng)建時間自動按年進行分庫慎菲。通過ThreadLocal編寫DynamicDataSourceEntry(被代理類)動態(tài)切換數(shù)據(jù)源的類嫁蛇。
/**
* @author: Winston
* @createTime: 2021/6/15
* 動態(tài)切換數(shù)據(jù)源
*/
public class DynamicDataSourceEntry {
// 默認數(shù)據(jù)源
public final static String DEFAULT_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DynamicDataSourceEntry(){}
/**
* 清空數(shù)據(jù)源
*/
public static void clear(){
local.remove();
}
/**
* 獲取當前正在使用的數(shù)據(jù)源名稱
* @return
*/
public static String get(){
return local.get();
}
/**
*還原當前切面的數(shù)據(jù)源
*/
public static void restore(){
local.set(DEFAULT_SOURCE);
}
/**
* 設置已知名字的數(shù)據(jù)源
* @param source
*/
public static void set(String source){
local.set(source);
}
/**
* 根據(jù)年份動態(tài)設置數(shù)據(jù)源
* @param year
*/
public static void set(int year) {
local.set("DB_" + year);
}
}
接下來是編寫代理類實現(xiàn)IOrderService接口,通過這個代理類調(diào)用被代理類同時完成對被代理類的方法調(diào)用钧嘶,在已有功能的基礎上棠众,不將業(yè)務代碼修改,完成數(shù)據(jù)源切換的工作有决。
/**
* @author: Winston
* @createTime: 2021/6/15
*/
public class OrderServiceStaticProxy implements IOrderService {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private IOrderService orderService;
public OrderServiceStaticProxy(IOrderService orderService){
this.orderService = orderService;
}
@Override
public int createOrder(Order order) {
before();
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("靜態(tài)代理類自動分配到【DB_" + dbRouter + "】數(shù)據(jù)源處理數(shù)據(jù)闸拿。");
DynamicDataSourceEntry.set(dbRouter);
orderService.createOrder(order);
after();
return 0;
}
private void before(){
System.out.println("Proxy before method.");
}
private void after(){
System.out.println("Proxy after method.");
}
}
測試代碼
/**
* @author: Winston
* @createTime: 2021/6/15
*/
public class StaticProxyTest {
public static void main(String[] args) throws ParseException {
Order order = new Order();
// Date today = new Date();
// order.setCreateTime(today.getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2017/02/01");
order.setCreateTime(date.getTime());
IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
orderService.createOrder(order);
}
}
運行結(jié)果
Proxy before method.
靜態(tài)代理類自動分配到【DB_2017】數(shù)據(jù)源處理數(shù)據(jù)。
OrderService 調(diào)用 orderDao 創(chuàng)建訂單
OrderDao 創(chuàng)建 Order 成功!
Proxy after method.
靜態(tài)代理小結(jié)
優(yōu)點:不需要修改業(yè)務類的代碼书幕,業(yè)務類只需要關注業(yè)務本身新荤,這是代理共有的優(yōu)點
缺點:
- 代理對象的一個接口只服務于一種類型的對象,如果要代理的方法很多台汇,勢必要為每一種方法都進行代理苛骨,比如要給所有方法都加上打印日志這一操作篱瞎,那么需要對所有方法都要進行代理,因此靜態(tài)代理在程序規(guī)模稍大時就無法勝任了痒芝。
- 如果接口新增一個方法俐筋,除了所有實現(xiàn)類要實現(xiàn)該方法外,所有代理類也要實現(xiàn)該方法严衬,增加了代碼維護的難度澄者。
2.動態(tài)代理
動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)生成的,所以不存在代理類的字節(jié)碼文件请琳。代理類和委托類的關系是在程序運行時確定粱挡。如果以找對象為例,父親給兒子找對象俄精,父親就好比是靜態(tài)代理询筏,只為兒子找對象,不管別人是否要找對象竖慧,而使用動態(tài)代理能夠適應復雜的業(yè)務場景嫌套,媒婆就好比是動態(tài)代理,能夠為任何單身人士尋找對象不僅限于兒子测蘑。同樣我們用代碼寫一個動態(tài)代理的示例.
JDK動態(tài)代理
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public interface Person {
/**
* 相親方法
*/
public void findLove();
}
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public class Girl implements Person{
/**
* 找對象要求
*/
@Override
public void findLove() {
System.out.println("高富帥");
System.out.println("身高180cm");
System.out.println("體重73kg");
System.out.println("家里兩套房");
}
}
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public class JDKMeipo implements InvocationHandler {
/**
* 被代理對象
*/
private Object target;
public Object getInstance(Object target){
this.target = target;
// 獲取class信息
Class clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
/**
* 前置方法
*/
public void before(){
System.out.println("我是媒婆灌危,現(xiàn)在準備開始為你尋找條件合適的相親對象");
}
/**
* 后置方法
*/
public void after(){
System.out.println("條件合適的話就準備嘿嘿嘿");
}
}
測試方法
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public class JdkDynamicProxyTest {
public static void main(String[] args) {
Person person = (Person) new JDKMeipo().getInstance(new Girl());
person.findLove();
}
}
輸出結(jié)果
我是媒婆,現(xiàn)在準備開始為你尋找條件合適的相親對象
高富帥
身高180cm
體重73kg
家里兩套房
條件合適的話就準備嘿嘿嘿
通過結(jié)果我們可以看到碳胳,調(diào)用了前置方法勇蝙、findLove方法、后置方法挨约,但是JDKMeipo中沒有findLove方法味混,person中沒有前置后置方法,那么是哪個類調(diào)用的呢诫惭?
通過debug模式翁锡,我們可以看到是一個全新的類$Proxy0,我們生成這個類的字節(jié)碼文件夕土,同時使用jad將它反編譯看看寫了什么馆衔。
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public class JdkDynamicProxyTest {
public static void main(String[] args) {
Person person = (Person) new JDKMeipo().getInstance(new Girl());
person.findLove();
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
try {
FileOutputStream fileOutputStream = new FileOutputStream("H://$Proxy0.class");
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
import com.gupaoedu.vip.pattern.proxy.service.Person;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy
implements Person
{
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final void findLove()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Person").getMethod("findLove", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
通過看 proxy0繼承了Proxy類同時實現(xiàn)了Person接口,而m3就是Person類的findLove方法篮撑,同時$Proxy0又實現(xiàn)了findLove方法减细,我們看代碼。
public final void findLove()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(thr owable);
}
}
findLove方法調(diào)用了super.h.invoke(this, m3, null);赢笨,而這個h是Proxy中的InvocationHandler接口未蝌,由于JDKMeipo實現(xiàn)了InvocationHandler接口驮吱,因此,super.h.invoke(this, m3, null);實際上調(diào)用的就是JDKMeipo的invoke方法萧吠。
JDK動態(tài)代理小結(jié)
實際上JDK動態(tài)代理是采用字節(jié)重組左冬,重新生成對象來代替原始對象以達到動態(tài)代理的目的。JDK Proxy生成對象的步驟如下以媒婆為示例纸型。
- 拿到被代理對象(Girl)的引用(Person)又碌,并且獲取到它所有的接口(本例中的findLove方法),反射獲取绊袋。(這也是為什么JDK動態(tài)代理需要代理類和被代理類都要事先同一接口的原因)
- JDK Proxy類重新生成一個新的類,這個新的類需要實現(xiàn)被代理類(Girl)的所有實現(xiàn)的所有接口(findLove)
- 動態(tài)生成java代碼铸鹰,把新加的業(yè)務邏輯方法由一定的邏輯代碼去調(diào)用(在代碼中體現(xiàn))
- 編譯新生成的java代碼.class
- 再重新加載到JVM中運行
以上這個過程就叫字節(jié)碼重組癌别。JDK中有一個規(guī)范,在ClassPath下只要是$開頭的class文件一般都是自動生成的蹋笼。
動態(tài)切換數(shù)據(jù)源案例改造
前面動態(tài)數(shù)據(jù)源切換案例我們使用的是使用靜態(tài)代理實現(xiàn)的展姐,我們現(xiàn)在將他改造成動態(tài)代理。
/**
* @Author winston
* @Date 2021/6/20
* 將訂單數(shù)據(jù)源切換案例由靜態(tài)代理改成JDK動態(tài)代理
*/
public class JDKOrderProxy implements InvocationHandler {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
/**
* 被代理對象
*/
private Object proxyObj;
public Object getInstance(Object proxyObj) {
// 賦值被代理對象
this.proxyObj = proxyObj;
// 獲取class信息
Class<?> clazz = proxyObj.getClass();
// 返回對象
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args[0]);
Object result = method.invoke(proxyObj, args);
after();
return result;
}
/**
* 切換數(shù)據(jù)源
*
* @param target 應該是訂單對象
*/
private void before(Object target) {
try {
System.out.println("切換數(shù)據(jù)源");
// 約定優(yōu)于配置剖毯,所以我們約定存在getCreateTime方法圾笨,通過反射得到
Method method = target.getClass().getMethod("getCreateTime");
Long time = (Long) method.invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("靜態(tài)代理類自動分配到【DB_" + dbRouter + "】數(shù)據(jù)源處理數(shù)據(jù)。");
DynamicDataSourceEntry.set(dbRouter);
// orderService.createOrder(order);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 重置數(shù)據(jù)源
*
*/
private void after() {
System.out.println("重置數(shù)據(jù)源");
DynamicDataSourceEntry.restore();
}
}
測試代碼
/**
* @author: Winston
* @createTime: 2021/6/16
*/
public class JdkDynamicProxyOrderTest {
public static void main(String[] args) throws ParseException {
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2018/02/01");
order.setCreateTime(date.getTime());
IOrderService orderService = (IOrderService) new JDKOrderProxy().getInstance(new OrderService());
orderService.createOrder(order);
}
}
CGLIB動態(tài)代理
參考博客:CGLIB動態(tài)代理詳解
CGLIB動態(tài)代理的結(jié)構(gòu)圖如下逊谋,我們可以看出代理類去繼承目標類擂达,每次調(diào)用代理類的方法都會被方法攔截器攔截,在攔截器中才是調(diào)用目標類的該方法的邏輯胶滋。
首先我們通過代碼來感受一下CGLIB動態(tài)代理板鬓,同樣我們以媒婆案例進行改造。
/**
* @author: Winston
* @createTime: 2021/6/22
*/
public class CglibMeipoProxy implements MethodInterceptor {
/**
* 獲取代理對象究恤,這里的參數(shù)是Class類型
* 為啥是class類型俭令,因為這里接收的參數(shù)是父類的class,我們需要繼承這個父類
* 重寫方法生成新的類
* @param clazz
* @return
*/
public Object getInstance(Class clazz) {
//創(chuàng)建Enhancer對象部宿,類似于JDK動態(tài)代理的Proxy類抄腔,下一步就是設置幾個參數(shù)
Enhancer enhancer = new Enhancer();
//設置目標類
enhancer.setSuperclass(clazz);
// 設置攔截器,也就是這個類(CglibMeipoProxy)的intercept方法
enhancer.setCallback(this);
// 生成代理類并返回一個實例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
// 調(diào)用被代理方法理张,注意這里是方法調(diào)用并不是用反射
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
/**
* 前置方法
*/
public void before(){
System.out.println("我是媒婆赫蛇,現(xiàn)在準備開始為你尋找條件合適的相親對象");
}
/**
* 后置方法
*/
public void after(){
System.out.println("條件合適的話就準備嘿嘿嘿");
}
}
測試類
/**
* @author: Winston
* @createTime: 2021/6/22
*/
public class CglibMeipoProxyTest {
public static void main(String[] args) {
//利用 cglib 的代理類可以將內(nèi)存中的 class 文件寫入本地磁盤
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"H://cglib_proxy_class");
Boy boy = (Boy) new CglibMeipoProxy().getInstance(Boy.class);
boy.findLove();
}
}
運行測試類后我們到指定目錄下看一下,發(fā)現(xiàn)生成有三個字節(jié)碼文件涯穷,一個是代理類的FastClass(Boy$$EnhancerByCGLIB$$71710d07$$FastClassByCGLIB$$e2a848ac.class
)棍掐,一個是代理類(Boy$$EnhancerByCGLIB$$71710d07.class
),一個是目標類的FastClass(Boy$$FastClassByCGLIB$$364eb7e9.class
)拷况,這里簡單說一下什么是FastClass作煌,就是給每個方法編號掘殴,通過編號找到方法,這樣可以避免頻繁使用反射導致效率比較低粟誓。
我們通過使用jad工具將代理類的class文件反編譯成java文件奏寨,我們來簡單看一下文件內(nèi)容。我們可以看到對于findLove方法鹰服,在這個代理類中對應會有findLove
和CGLIB$findLove$0
這兩個方法病瞳;其中前者則是我們使用代理類時候調(diào)用的方法,后者是在方法攔截器里面調(diào)用的悲酷,換句話來說當我們代碼調(diào)用代理對象的findLove
方法套菜,然后會到方法攔截器中調(diào)用intercept
方法,該方法內(nèi)則通過proxy.invokeSuper調(diào)用CGLIB$findLove$0
這個方法设易。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: <generated>
package com.gupaoedu.vip.pattern.proxy.service;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.*;
// Referenced classes of package com.gupaoedu.vip.pattern.proxy.service:
// Boy
// 這個代理類是繼承我們的目標類Boy逗柴,并且實現(xiàn)了Factory接口,這個接口就是一些設置回調(diào)函數(shù)和返回實例化對象的方法
public class Boy$$EnhancerByCGLIB$$71710d07 extends Boy
implements Factory
{
static void CGLIB$STATICHOOK1()
{
//注意下面這兩個Method數(shù)組顿肺,用于保存反射獲取的Method對象戏溺,避免每次都用反射去獲取Method對象
Method amethod[];
Method amethod1[];
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
//獲取目標類的字節(jié)碼文件
Class class1 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Boy$$EnhancerByCGLIB$$71710d07");
//代理類的字節(jié)碼文件
Class class2;
//ReflectUtils是一個包裝各種反射操作的工具類,通過這個工具類來獲取各個方法的Method對象屠尊,然后保存到上述的Method數(shù)組中
amethod = ReflectUtils.findMethods(new String[] {
"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"
}, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods());
Method[] _tmp = amethod;
//為目標類的每一個方法都建立索引旷祸,可以想象成記錄下來目標類中所有方法的地址,需要用調(diào)用目標類方法的時候根據(jù)地址就能直接找到該方法
//這就是此處CGLIB$xxxxxx$$Proxy的作用讼昆。托享。。
CGLIB$equals$1$Method = amethod[0];
CGLIB$equals$1$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = amethod[1];
CGLIB$toString$2$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = amethod[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = amethod[3];
CGLIB$clone$4$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
amethod1 = ReflectUtils.findMethods(new String[] {
"findLove", "()V"
}, (class2 = Class.forName("com.gupaoedu.vip.pattern.proxy.service.Boy")).getDeclaredMethods());
Method[] _tmp1 = amethod1;
CGLIB$findLove$0$Method = amethod1[0];
CGLIB$findLove$0$Proxy = MethodProxy.create(class2, class1, "()V", "findLove", "CGLIB$findLove$0");
}
//這個方法就是調(diào)用目標類的的findLove方法
final void CGLIB$findLove$0()
{
super.findLove();
}
//這個方法是我們是我們要調(diào)用的浸赫,在前面的例子中調(diào)用代理對象的findLove方法就會到這個方法中
public final void findLove()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 37;
goto _L3 _L4
_L3:
break MISSING_BLOCK_LABEL_21;
_L4:
break MISSING_BLOCK_LABEL_37;
this;
CGLIB$findLove$0$Method;
CGLIB$emptyArgs;
CGLIB$findLove$0$Proxy;
//這里就是調(diào)用方法攔截器的intecept()方法
intercept();
return;
super.findLove();
return;
}
final boolean CGLIB$equals$1(Object obj)
{
return super.equals(obj);
}
public final boolean equals(Object obj)
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 57;
goto _L3 _L4
_L3:
this;
CGLIB$equals$1$Method;
new Object[] {
obj
};
CGLIB$equals$1$Proxy;
intercept();
JVM INSTR dup ;
JVM INSTR ifnonnull 50;
goto _L5 _L6
_L5:
JVM INSTR pop ;
false;
goto _L7
_L6:
(Boolean);
booleanValue();
_L7:
return;
_L4:
return super.equals(obj);
}
final String CGLIB$toString$2()
{
return super.toString();
}
public final String toString()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 40;
goto _L3 _L4
_L3:
this;
CGLIB$toString$2$Method;
CGLIB$emptyArgs;
CGLIB$toString$2$Proxy;
intercept();
(String);
return;
_L4:
return super.toString();
}
final int CGLIB$hashCode$3()
{
return super.hashCode();
}
public final int hashCode()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 52;
goto _L3 _L4
_L3:
this;
CGLIB$hashCode$3$Method;
CGLIB$emptyArgs;
CGLIB$hashCode$3$Proxy;
intercept();
JVM INSTR dup ;
JVM INSTR ifnonnull 45;
goto _L5 _L6
_L5:
JVM INSTR pop ;
0;
goto _L7
_L6:
(Number);
intValue();
_L7:
return;
_L4:
return super.hashCode();
}
final Object CGLIB$clone$4()
throws CloneNotSupportedException
{
return super.clone();
}
protected final Object clone()
throws CloneNotSupportedException
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 37;
goto _L3 _L4
_L3:
this;
CGLIB$clone$4$Method;
CGLIB$emptyArgs;
CGLIB$clone$4$Proxy;
intercept();
return;
_L4:
return super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature signature)
{
String s = signature.toString();
s;
s.hashCode();
JVM INSTR lookupswitch 5: default 120
// -508378822: 60
// 1192015562: 72
// 1826985398: 84
// 1913648695: 96
// 1984935277: 108;
goto _L1 _L2 _L3 _L4 _L5 _L6
_L2:
"clone()Ljava/lang/Object;";
equals();
JVM INSTR ifeq 121;
goto _L7 _L8
_L8:
break MISSING_BLOCK_LABEL_121;
_L7:
return CGLIB$clone$4$Proxy;
_L3:
"findLove()V";
equals();
JVM INSTR ifeq 121;
goto _L9 _L10
_L10:
break MISSING_BLOCK_LABEL_121;
_L9:
return CGLIB$findLove$0$Proxy;
_L4:
"equals(Ljava/lang/Object;)Z";
equals();
JVM INSTR ifeq 121;
goto _L11 _L12
_L12:
break MISSING_BLOCK_LABEL_121;
_L11:
return CGLIB$equals$1$Proxy;
_L5:
"toString()Ljava/lang/String;";
equals();
JVM INSTR ifeq 121;
goto _L13 _L14
_L14:
break MISSING_BLOCK_LABEL_121;
_L13:
return CGLIB$toString$2$Proxy;
_L6:
"hashCode()I";
equals();
JVM INSTR ifeq 121;
goto _L15 _L16
_L16:
break MISSING_BLOCK_LABEL_121;
_L15:
return CGLIB$hashCode$3$Proxy;
_L1:
JVM INSTR pop ;
return null;
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback acallback[])
{
CGLIB$THREAD_CALLBACKS.set(acallback);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback acallback[])
{
CGLIB$STATIC_CALLBACKS = acallback;
}
//此方法在靜態(tài)代碼塊中被調(diào)用
private static final void CGLIB$BIND_CALLBACKS(Object obj)
{
Boy$$EnhancerByCGLIB$$71710d07 boy$$enhancerbycglib$$71710d07 = (Boy$$EnhancerByCGLIB$$71710d07)obj;
if(boy$$enhancerbycglib$$71710d07.CGLIB$BOUND) goto _L2; else goto _L1
_L1:
Object obj1;
boy$$enhancerbycglib$$71710d07.CGLIB$BOUND = true;
obj1 = CGLIB$THREAD_CALLBACKS.get();
obj1;
if(obj1 != null) goto _L4; else goto _L3
_L3:
JVM INSTR pop ;
CGLIB$STATIC_CALLBACKS;
if(CGLIB$STATIC_CALLBACKS != null) goto _L4; else goto _L5
_L5:
JVM INSTR pop ;
goto _L2
_L4:
(Callback[]);
boy$$enhancerbycglib$$71710d07;
JVM INSTR swap ;
0;
JVM INSTR aaload ;
(MethodInterceptor);
CGLIB$CALLBACK_0;
_L2:
}
public Object newInstance(Callback acallback[])
{
CGLIB$SET_THREAD_CALLBACKS(acallback);
CGLIB$SET_THREAD_CALLBACKS(null);
return new Boy$$EnhancerByCGLIB$$71710d07();
}
public Object newInstance(Callback callback)
{
CGLIB$SET_THREAD_CALLBACKS(new Callback[] {
callback
});
CGLIB$SET_THREAD_CALLBACKS(null);
return new Boy$$EnhancerByCGLIB$$71710d07();
}
public Object newInstance(Class aclass[], Object aobj[], Callback acallback[])
{
CGLIB$SET_THREAD_CALLBACKS(acallback);
JVM INSTR new #2 <Class Boy$$EnhancerByCGLIB$$71710d07>;
JVM INSTR dup ;
aclass;
aclass.length;
JVM INSTR tableswitch 0 0: default 35
// 0 28;
goto _L1 _L2
_L2:
JVM INSTR pop ;
Boy$$EnhancerByCGLIB$$71710d07();
goto _L3
_L1:
JVM INSTR pop ;
throw new IllegalArgumentException("Constructor not found");
_L3:
CGLIB$SET_THREAD_CALLBACKS(null);
return;
}
public Callback getCallback(int i)
{
CGLIB$BIND_CALLBACKS(this);
this;
i;
JVM INSTR tableswitch 0 0: default 30
// 0 24;
goto _L1 _L2
_L2:
CGLIB$CALLBACK_0;
goto _L3
_L1:
JVM INSTR pop ;
null;
_L3:
return;
}
public void setCallback(int i, Callback callback)
{
switch(i)
{
case 0: // '\0'
CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
break;
}
}
public Callback[] getCallbacks()
{
CGLIB$BIND_CALLBACKS(this);
this;
return (new Callback[] {
CGLIB$CALLBACK_0
});
}
public void setCallbacks(Callback acallback[])
{
this;
acallback;
JVM INSTR dup2 ;
0;
JVM INSTR aaload ;
(MethodInterceptor);
CGLIB$CALLBACK_0;
}
//這里有很多的屬性嫌吠,仔細看一下就是一個方法對應兩個,一個是Method類型掺炭,一個是MethodProxy類型
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback CGLIB$STATIC_CALLBACKS[];
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$findLove$0$Method;
private static final MethodProxy CGLIB$findLove$0$Proxy;
private static final Object CGLIB$emptyArgs[];
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
//靜態(tài)代碼塊辫诅,調(diào)用下面靜態(tài)方法,這個靜態(tài)方法大概做的就是獲取目標方法中每個方法的MethodProxy對象
static
{
CGLIB$STATICHOOK1();
}
//無參構(gòu)造器
public Boy$$EnhancerByCGLIB$$71710d07()
{
CGLIB$BIND_CALLBACKS(this);
}
}
FastClass機制分析
為什么要用這種機制呢涧狮?直接用反射多好啊炕矮,但是我們知道反射雖然很好用,但是和直接new對象相比者冤,效率有點慢肤视,于是就有了這種機制,參考這篇博客https://www.cnblogs.com/cruze/p/3865180.html涉枫,有個小例子可以說的非常清楚邢滑;
public class test10 { //這里,tt可以看作目標對象愿汰,fc可以看作是代理對象困后;首先根據(jù)代理對象的getIndex方法獲取目標方法的索引乐纸, //然后再調(diào)用代理對象的invoke方法就可以直接調(diào)用目標類的方法,避免了反射
public static void main(String[] args){
Test tt = new Test();
Test2 fc = new Test2();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null);
}
}
class Test{
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
class Test2{
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
//這個方法對Test類中的方法建立索引
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
}
CGLIB動態(tài)代理小結(jié)
上面我們看了CGLib動態(tài)代理的用法摇予、實際生成的代理類以及FastClass機制汽绢,下面我們就以最前面的那個例子中調(diào)用findLove()方法來看看主要的調(diào)用步驟;
第一步:是經(jīng)過一系列操作實例化出了Enhance對象侧戴,并設置了所需要的參數(shù)然后enhancer.create()成功創(chuàng)建出來了代理對象宁昭,這個就不多說了...
第二步:調(diào)用代理對象的findLove()方法,會進入到方法攔截器的intercept()方法酗宋,在這個方法中會調(diào)用proxy.invokeSuper(obj, args);方法
第三步:invokeSuper中积仗,通過FastClass機制調(diào)用目標類的方法
CGLIB動態(tài)代理和JDK動態(tài)代理比較
- JDK動態(tài)代理是實現(xiàn)了被代理對象的接口,CGLIB動態(tài)代理是繼承了被代理對象
- JDK和CGLIB都是在運行時生成字節(jié)碼蜕猫,JDK是直接生成Class字節(jié)碼斥扛,CGLIB是使用ASM框架寫Class字節(jié)碼,CGLIB代理實現(xiàn)更復雜丹锹,因此生成代理類比JDK效率低
- JDK調(diào)用代理方法是通過反射機制調(diào)用,而CGLIB是通過FastClass機制直接調(diào)用方法芬失,因此CGLIB執(zhí)行效率更高楣黍。
Spring 中的代理選擇的原則
- 當Bean有實現(xiàn)接口時,Spring就會使用JDK的動態(tài)代理
- 當Bean沒有實現(xiàn)接口時棱烂,Spring使用Cglib動態(tài)代理
- Spring可以強制使用Cglib動態(tài)代理租漂,只需要在Spring的配置文件中加如下代碼
<aop:aspectj-autoproxy proxy-target-class="true"/>