代理模式
首先回顧:
反射–05–動态代理:https://blog.csdn.net/weixin_48052161/article/details/116862418
Spring–03–AOP簡介:https://blog.csdn.net/weixin_48052161/article/details/108651671
定義:
代理模式有兩個英文名字:Proxy Pattern 和 Surrogate Pattern。代理模式的定義為:為其他對象提供一種代理以控制對這個對象的通路
- 代理對象在用戶端和目标對象之間起到中介作用。
- 代理模式屬于結構型設計模式,屬于GOF23種設計模式之一
- 代理模式可以分為靜态代理和動态代理兩種類型,而動态代理中又分為JDK動态代理和CGLIB代理兩種。
結構:
靜态代理:
特點:代理類和被代理類在編譯期間,就确定下來了
接口
interface ClothFactory{
void produceCloth();
}
被代理類
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工廠生産一批運動服");
}
}
代理類
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理類對象進行執行個體化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工廠做一些準備工作");
factory.produceCloth();
System.out.println("代理工廠做一些後續的收尾工作");
}
}
測試
public class StaticProxyTest {
public static void main(String[] args) {
//建立被代理類的對象
ClothFactory nike = new NikeClothFactory();
//建立代理類的對象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
動态代理:
要想實作動态代理,需要解決的問題?
問題一:如何根據加載到記憶體中的被代理類,動态的建立一個代理類及其對象。
問題二:當通過代理類的對象調用方法a時,如何動态的去調用被代理類中的同名方法a。
- 問題一 :通過bean工廠ProxyFactory
- 問題二:通過反射擷取代理對象,的方法對象 Method
jdk提供的相關ApI
- (package java.lang.reflect)
動态代理案例 1 :JDK代理
接口:
interface Human{
String getBelief();
void eat(String food);
}
被代理類
//被代理類
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜歡吃" + food);
}
}
擴充方法:
class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}
bean代理工廠
調用此方法,傳回一個代理類的對象。解決問題
class ProxyFactory{
//調用此方法,傳回一個代理類的對象。解決問題一
public static Object getProxyInstance(Object obj){//obj:被代理類的對象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
動态代理實際上是: 多态+反射的應用
1.obj.getClass().getClassLoader(), ==>為了擷取Class對象 進行反射
2.obj.getClass().getInterfaces() ==> 根據需要實作的接口資訊,在代碼中動态建立 該Proxy類的位元組碼;
多态+反射 ==>代理對象能夠動态的去調用被代理類中的同名方法。
調用處理器 InvocationHandler
1. 當我們通過代理類的對象,調用方法a時,就會自動的調用如下的方法:invoke()
2. 将被代理類要執行的方法a的功能就聲明在invoke()中
- 實作InvocationHandler接口,重寫invoke()方法
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理類的對象進行指派
public void bind(Object obj){
this.obj = obj;
}
//當我們通過代理類的對象,調用方法a時,就會自動的調用如下的方法:invoke()
//将被代理類要執行的方法a的功能就聲明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即為代理類對象調用的方法,此方法也就作為了被代理類對象要調用的方法
//obj:被代理類的對象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的傳回值就作為目前類中的invoke()的傳回值。
return returnValue;
}
}
調用處理器 類比 AOP當中的切面對象
測試類:
- 通過代理工廠建立代理類對象
- 通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理類的對象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//當通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("火鍋");
}
}
解析:
- 讓代理類顯示出來
System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
System.getProperties().put(“jdk.proxy.ProxyGenerator.saveGeneratedFiles”,“true”);
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
// System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//proxyInstance:代理類的對象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//當通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("火鍋");
}
}
$Proxy0
package com.sun.proxy;
import Proxy.Human;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Human {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void eat(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String getBelief() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("Proxy.Human").getMethod("eat", Class.forName("java.lang.String"));
m3 = Class.forName("Proxy.Human").getMethod("getBelief");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
構造方法
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
- 把執行器InvocationHandler傳入進來,令屬性h指向它
static靜态代碼塊
- 通過發射擷取方法對象,并指派給代理類屬性
代理類屬性
代理類方法
- 實際是調用執行器中的重寫後的invoke()方法
是以當通過代理類的對象調用方法a時,能夠動态的去調用被代理類中的同名方法a。并且還能實作功能的擴充
為何調用代理類的方法就會自動進入InvocationHandler 的 invoke()方法呢?
- 其實是因為在動态代理類的定義中,構造函數是含參的構造,參數就是我們invocationHandler執行個體,而每一個被代理接口的方法都會在代理類中生成一個對應的實作方法,并在實作方法中最終調用invocationHandler的invoke方法,這就解釋了為何執行代理類的方法會自動進入到我們自定義的invocationHandler的invoke方法中,然後在我們的invoke方法中再利用jdk反射的方式去調用真正的被代理類的業務方法,而且還可以在方法的前後去加一些我們自定義的邏輯。比如切面程式設計AOP等。
為什麼JDK的動态代理必須有接口?
- 由于java的單繼承,動态生成的代理類已經繼承了Proxy類的,就不能再繼承其他的類,是以隻能靠實作被代理類的接口的形式,故JDK的動态代理必須有接口
動态代理案例 2 :CGLIB代理
區分JDK代理:
- 假如目标對象(被代理對象)實作接口,則底層可以采用JDK動态代理機制為目标對象建立代理對象(目标類和代理類會實作共同接口)。
- 假如目标對象(被代理對象)沒有實作接口,則底層可以采用CGLIB代理機制為目标對象建立代理對象(預設建立的代理類會繼承目标對象類型)。
依賴:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
案例示範
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Random;
public class cglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Tank.class);
enhancer.setCallback(new TimeMethodInterceptor());
Tank tank = (Tank)enhancer.create();
tank.move();
}
}
class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(o.getClass().getSuperclass().getName());
System.out.println("before");
Object result = null;
result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
class Tank {
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
JDK動态代理VSCglib動态代理
1.從實作方式上說
-
JDK動态代理
利用攔截器(攔截器必須實作InvocationHanlder)加上反射機制生成一個實作代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
-
CGLIB動态代理
利用ASM開源包,對代理對象類的class檔案加載進來,通過修改其位元組碼生成子類來處理。
2.何時使用JDK或者CGLIB?
- 如果目标對象實作了接口,預設情況下會采用JDK的動态代理實作AOP。
- 如果目标對象實作了接口,可以強制使用CGLIB實作AOP。
- 如果目标對象沒有實作了接口,必須采用CGLIB庫,Spring會自動在JDK動态代理和CGLIB之間轉換。
JDK代理是不需要第三方庫支援,隻需要JDK環境就可以進行代理CGLib必須依賴于CGLib的類庫,但是它需要類來實作任何接口代理的是指定的類生成一個子類,覆寫其中的方法,是一種繼承。
3. JDK動态代理和CGLIB位元組碼生成的差別?
- JDK動态代理隻能對實作了接口的類生成代理,而不能針對類。
- CGLIB是針對類實作代理,主要是對指定的類生成一個子類,覆寫其中的方法,并覆寫其中方法實作增強,但是因為采用的是繼承,是以該類或方法最好不要聲明成final,對于final類或方法,是無法繼承的。
CGLIB對于final類,是無法建立代理對象的。
如果想建立 ,得用ASM架構去操作位元組碼檔案
因為CGLIB是通過繼承目标類來重寫其方法來實作的,故而如果是final和private方法則無法被重寫,也就是無法被代理。
4.CGlib比JDK快?
- 使用CGLib實作動态代理,CGLib底層采用ASM位元組碼生成架構,使用位元組碼技術生成代理類,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進行代理,因為CGLib原理是動态生成被代理類的子類。
- 在jdk6、jdk7、jdk8逐漸對JDK動态代理優化之後,在調用次數較少的情況下,JDK代理效率高于CGLIB代理效率,隻有當進行大量調用的時候,jdk6和jdk7比CGLIB代理效率低一點,但是到jdk8的時候,jdk代理效率高于CGLIB代理,
總之,每一次jdk版本更新,jdk代理效率都得到提升,而CGLIB代理消息确有點跟不上步伐。
5.Spring如何選擇用JDK還是CGLIB?
- 當Bean實作接口時,Spring就會用JDK的動态代理。
- 當Bean沒有實作接口時,Spring使用CGlib是實作。
- 可以強制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
動态代理案例 3 :AOP 應用原理分析
Spring–03–AOP簡介
Spring AOP底層基于代理機制實作功能擴充:
- 假如目标對象(被代理對象)實作接口,則底層可以采用JDK動态代理機制為目标對象建立代理對象(目标類和代理類會實作共同接口)。
- 假如目标對象(被代理對象)沒有實作接口,則底層可以采用CGLIB代理機制為目标對象建立代理對象(預設建立的代理類會繼承目标對象類型)。
建立日志切面類對象 案例:
- 将此日志切面類作為核心業務增強(一個橫切面對象)類,用于輸出業務執行時長,其關鍵代碼如下:
package com.cy.pj.common.aspect;
@Aspect
@Slf4j
@Component
public class SysLogAspect {
@Pointcut("bean(sysUserServiceImpl)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint jp) throws Throwable{
try {
log.info("start:{}"+System.currentTimeMillis());
Object result=jp.proceed();//最終會調用目标方法
log.info("after:{}"+System.currentTimeMillis());
return result;
}catch(Throwable e) {
log.error("after:{}",e.getMessage());
throw e;
}
}
}
解析:
- 用戶端向服務端發起一個請求,伺服器端的Controller來處理這個請求.
-
Controller處理請求,假如需要對這個請求做一定的業務處理,他可能要調用業務層對象. controller調用xxxService接口.的時候,實際運作時,要給這個接口注入一個實作類的對象.
(最開始的路線是,Controller去通路我們的xxxService接口,xxxService去通路具體的實作類xxxServiceImpl,實作類去通路具體的dao)
- Controller運作時去通路我們的xxxService接口,service通路代理對象,(Controller是耦合于我們的service接口,其實注入的是我們代理對象,代理對象預設優先級高于目标對象,是以Controller運作時通過接口的引用通路代理對象)
- 代理對象通路我們的切面,
- 切面再調用我們的目标對象xxxServiceImpl,的方法,(jp.proceed())目标對象再通路我們的dao進行業務處理.
ASM
定義:
ASM是一種通用Java位元組碼操作和分析架構。它可以用于修改現有的class檔案或動态生成class檔案。
- ASM 是一個 Java 位元組碼操控架構。它能夠以二進制形式修改已有類或者動态生成類。
- ASM 可以直接産生二進制 class 檔案,也可以在類被加載入 Java 虛拟機之前動态改變類行為。
- ASM 從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類。
應用:
- ASM 已經被廣泛應用于一系列 Java 項目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。
- Hibernate 和 Spring也通過 cglib,另一個更高層一些的自動代碼生成工具使用了 ASM。
- JDK代理 和 CGLIB代理也使用了ASM架構
優勢:
- ASM 能夠通過改造既有類,直接生成需要的代碼。增強的代碼是寫死在新生成的類檔案内部的,沒有反射帶來性能上的付出。同時,ASM 與 Proxy 程式設計不同,不需要為增強代碼而新定義一個接口,生成的代碼可以覆寫原來的類,或者是原始類的子類。它是一個普通的 Java 類而不是 proxy 類,甚至可以在應用程式的類架構中擁有自己的位置,派生自己的子類。
- 相比于其他流行的 Java 位元組碼操縱工具,ASM 更小更快。ASM 具有類似于 BCEL 或者 SERP 的功能,而隻有 33k 大小,而後者分别有 350k 和 150k。同時,同樣類轉換的負載,如果 ASM 是 60% 的話,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。
代理模式優缺點:
優點:
- 代理模式能将代理對象與真實被調用的目标對象分離。
- 一定程度上降低了系統的耦合度,擴充性好。
- 可以起到保護目标對象的作用。
- 可以對目标對象的功能增強。
缺點:
- 代理模式會造成系統設計中類的數量增加。
- 在用戶端和目标對象增加一個代理對象,會造成請求處理速度變慢。