寫在前面
- 代理模式
代理模式,指的是給目標(biāo)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)目標(biāo)對(duì)象的引用。
為什么要引入這個(gè)代理對(duì)象呢,兩個(gè)目的:
- 通過代理對(duì)象來間接訪問目標(biāo)對(duì)象,這樣能防止直接訪問目標(biāo)對(duì)象帶來的一些不必要的復(fù)雜性。
(什么不必要的麻煩呢?例如如果客戶端需要訪問的對(duì)象在服務(wù)端,客戶端直接訪問需要處理網(wǎng)絡(luò)等一系列復(fù)雜的問題,如果使用代理模式那么客戶端只需要和代理打交道,客戶端不需要知道代理怎么和服務(wù)端交互。) -
通過代理對(duì)象來訪問目標(biāo)對(duì)象,能通過代理對(duì)象對(duì)原有業(yè)務(wù)進(jìn)行增強(qiáng)。
- 代理模式有三種實(shí)現(xiàn)方式:
靜態(tài)代理,Jdk動(dòng)態(tài)代理,Cglib動(dòng)態(tài)代理。
其中,靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別在于代理類的生成時(shí)間不同。
靜態(tài)代理
靜態(tài)代理三大要素:
- 抽象對(duì)象(接口,約定了服務(wù)能夠提供的功能)
- 真實(shí)對(duì)象(實(shí)現(xiàn)類)
- 代理對(duì)象
真實(shí)對(duì)象及代理對(duì)象都必須實(shí)現(xiàn)這個(gè)接口
代理對(duì)象必須包含真實(shí)對(duì)象。(它不提供服務(wù),只是服務(wù)的搬運(yùn)工)
面向?qū)ο笤O(shè)計(jì)開發(fā)的幾個(gè)原則之一:
開閉原則(開放-封閉原則):程序?qū)ν鈹U(kuò)展開放,對(duì)修改關(guān)閉。換句話說,當(dāng)需求發(fā)生變化時(shí),我們可以通過添加新模塊來滿足新需求,而不是通過修改原來的實(shí)現(xiàn)代碼來滿足新需求。
靜態(tài)代理作為代理模式的第一個(gè)實(shí)現(xiàn)版本,違反了開閉原則:
- 讓這個(gè)類的可擴(kuò)展性大打折扣。
- 可維護(hù)性下降,牽一發(fā),動(dòng)全身。
而動(dòng)態(tài)代理,很好的解決了這個(gè)缺點(diǎn)。
動(dòng)態(tài)代理
何謂動(dòng)態(tài)?動(dòng)態(tài)指的是代理對(duì)象不是固定對(duì)象,而是一個(gè)動(dòng)態(tài)對(duì)象。
動(dòng)態(tài)代理沒有源文件,直接在內(nèi)存生成類的字節(jié)碼文件。
- Jdk動(dòng)態(tài)代理
*Tips*
在研究Jdk動(dòng)態(tài)代理之前,先提出幾點(diǎn)疑問:
a. 為什么Jdk動(dòng)態(tài)代理一定要基于接口?
b. 怎么在內(nèi)存生成字節(jié)碼?
c. 生成的字節(jié)碼長(zhǎng)什么樣?
d. 多個(gè)對(duì)象,實(shí)現(xiàn)同一個(gè)接口,若對(duì)這多個(gè)對(duì)象分別進(jìn)行代理,會(huì)生成多個(gè)代理對(duì)象嗎?
e. 多個(gè)對(duì)象,實(shí)現(xiàn)不同接口,若對(duì)這多個(gè)對(duì)象分別進(jìn)行代理,是否會(huì)生成多個(gè)代理對(duì)象呢?
接下來,帶著這幾個(gè)問題來研究Jdk動(dòng)態(tài)代理的底層源碼。
1.1 Jdk動(dòng)態(tài)代理的兩大關(guān)鍵點(diǎn):Proxy類和InvocationHandler接口。
Proxy - 調(diào)度器,生成代理對(duì)象
JdkProxy dynamicProxy=new JdkProxy(hello);
Greeting target1=(Greeting) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), dynamicProxy);
InvocationHandler - 代理對(duì)象到底有什么業(yè)務(wù)能力
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
before();
result=method.invoke(target, args);
after();
return result;
}
public void before(){
System.out.println("[JdkProxy] Come to someone.");
}
public void after(){
System.out.println("[JdkProxy] Back to his own corner");
}
}
1.2 探究Proxy類
Proxy類位于java.lang.reflect包中,追蹤Proxy類的newProxyInstance方法,會(huì)定位到ProxyBuilder的如下方法,截取關(guān)鍵部分:
private static final class ProxyBuilder {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
//......
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
//......
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
其中,generateProxyClass就是在內(nèi)存里直接生成字節(jié)碼的地方!它內(nèi)部調(diào)用了generateClassFile方法(參見附錄),這個(gè)方法就會(huì)根據(jù)Class文件的結(jié)構(gòu),來動(dòng)態(tài)拼接出代理對(duì)象的字節(jié)碼。
而UNSAFE.defineClass,就是直接根據(jù)剛才生成的字節(jié)碼,生成Class對(duì)象。
這個(gè)defineClass方法,最終調(diào)用的是一個(gè)native方法。native方法是用C寫的本地方法,直接調(diào)用操作系統(tǒng)的類庫,來進(jìn)行生成Class對(duì)象。
public native Class<?> defineClass0(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
1.3 看一看動(dòng)態(tài)生成的字節(jié)碼文件
既然已經(jīng)在內(nèi)存中生成了字節(jié)碼文件,那么就把它保存到硬盤上,滿足一下好奇心,看看里面是個(gè)什么玩意兒吧。
在ProxyGenerator中,有這樣一個(gè)屬性,它詢問是否需要將動(dòng)態(tài)代理的對(duì)象存到硬盤。(這里是基于jdk9的demo,如果用的是其它版本的jdk,那么屬性可以會(huì)略有不同)
/** debugging flag for saving generated class files */
private static final boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();
既然如此,那么只要將這個(gè)屬性設(shè)為true,就能直接查看硬盤上的Class文件了。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
果不其然,在項(xiàng)目目錄下,生成了com/sun/proxy子目錄,下面有$Proxy0.class字節(jié)碼文件。這個(gè)類繼承了Proxy類,并且實(shí)現(xiàn)了Greeting接口。
public final class $Proxy0 extends Proxy implements Greeting {
}
//接口代理方法
public final void doGreet()
{
try
{
// invocation handler的 invoke方法在這里被調(diào)用
super.h.invoke(this, m3, null);
return;
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
1.4 關(guān)于InvocationHandler
1.4.1 InvocationHandler是一個(gè)接口。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
它被作為參數(shù)傳給底層方法newProxyInstance(caller, cons, h)。這個(gè)方法里有一行注釋:
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
//......
}
1.4.2 追蹤這個(gè)方法,會(huì)發(fā)現(xiàn)最終調(diào)用了一個(gè)native方法,InvocationHandler被作為參數(shù)傳過去了。
private static native Object newInstance0(Constructor<?> c, Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException;
這個(gè)類實(shí)際做的事情就是:
- 根據(jù)前面生成的類$Proxy0的字節(jié)碼,來實(shí)例化這個(gè)類
- 并且,將invocationHandler傳遞給$Proxy0的父類Proxy的構(gòu)造函數(shù),初始化Proxy的屬性h
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
1.4.3 invoke方法的參數(shù)
- InvocationHandler接口定義了一個(gè)invoke方法,它接收三個(gè)參數(shù)。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
第二個(gè)參數(shù)method,和第三個(gè)參數(shù)args,指的是代理類被調(diào)用的方法和參數(shù)。
而第一個(gè)參數(shù)proxy,很少會(huì)被用到,其實(shí)它指的就是代理對(duì)象。將代理對(duì)象傳遞進(jìn)來有兩個(gè)用處:
可以通過反射獲取代理對(duì)象的信息。
可以將代理對(duì)象返回進(jìn)行連續(xù)調(diào)用。
- 用一個(gè)例子感受一下。(截取部分代碼,完整代碼見附錄)
實(shí)現(xiàn)InvocationHandler的invoke方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();
method.invoke(target, args);
after();
System.out.println("JdkProxy.this is : " + this.getClass());
System.out.println("proxy is : " + proxy.getClass());
return proxy;
}
通過代理類調(diào)用目標(biāo)方法。
Meeting videoMeeting = new VideoMeeting();
dynamicProxy = new JdkProxy(videoMeeting);
Meeting target4 = (Meeting) Proxy.newProxyInstance(videoMeeting.getClass().getClassLoader(),
videoMeeting.getClass().getInterfaces(), dynamicProxy);
target4.haveMeeting().haveMeeting();
執(zhí)行,通過輸出,可以發(fā)現(xiàn)proxy就是動(dòng)態(tài)生成的代理類$Proxy1,而目標(biāo)方法通過連續(xù)調(diào)用,被代理類調(diào)用了兩次。
[JdkProxy] Come to someone.
Meeting by video .
[JdkProxy] Back to his own corner
JdkProxy.this is : class spring.core.aop.jdk.JdkProxy
proxy is : class com.sun.proxy.$Proxy1
[JdkProxy] Come to someone.
Meeting by video .
[JdkProxy] Back to his own corner
JdkProxy.this is : class spring.core.aop.jdk.JdkProxy
proxy is : class com.sun.proxy.$Proxy1
1.5 為什么Jdk動(dòng)態(tài)代理必須基于接口
這是由jdk動(dòng)態(tài)代理的設(shè)計(jì)決定的
通過查看動(dòng)態(tài)代理類的代碼,發(fā)現(xiàn)它繼承了Proxy類。而Java是單繼承,不能同時(shí)繼承兩個(gè)類,所以我們需要和想要代理的類建立聯(lián)系,那就只能通過接口來實(shí)現(xiàn)。那么問題來了,jdk動(dòng)態(tài)代理為什么要設(shè)計(jì)成只允許動(dòng)態(tài)代理接口呢?
在代理模式中,代理類只做一些額外的攔截處理,實(shí)際處理是轉(zhuǎn)發(fā)到原始類做的。
如果允許動(dòng)態(tài)代理一個(gè)類,那么代理對(duì)象也會(huì)繼承類的字段,而這些字段實(shí)際上是沒有使用的,因?yàn)榇韺?duì)象只做轉(zhuǎn)發(fā)處理,對(duì)象的字段存取都是在原始對(duì)象上處理,所以如果代理一個(gè)類,對(duì)內(nèi)存空間是一種浪費(fèi)。
1.6 緩存機(jī)制
調(diào)用過程中發(fā)現(xiàn),對(duì)于不同的類做動(dòng)態(tài)代理,生成的代理對(duì)象,有時(shí)候是同一個(gè)對(duì)象。這是因?yàn)楫?dāng)需要生成代理對(duì)象字節(jié)碼之前,會(huì)先查看緩存,是否已經(jīng)存在同樣classLoader&同樣interface的代理對(duì)象,如果存在,則直接復(fù)用緩存中的class字節(jié)碼。
- Cglib動(dòng)態(tài)代理
(Code Generator Library)
*Tips*
同樣,在研究Cglib動(dòng)態(tài)代理之前,同樣提出幾個(gè)問題:
a. 為什么Cglib動(dòng)態(tài)代理不用基于接口?
b. 怎么在內(nèi)存生成字節(jié)碼?
c. 生成的字節(jié)碼長(zhǎng)什么樣?
2.1 Cglib動(dòng)態(tài)代理的兩大關(guān)鍵點(diǎn)
- Enhancer類
類似于jdk動(dòng)態(tài)代理中的Proxy類,只不過,Enhancer類既能代理普通的類,也能夠代理接口。
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls, this);
}
- MethodInterceptor接口
重寫接口中的intercept方法對(duì)目標(biāo)方法進(jìn)行攔截。
@Override
public Object intercept(Object obj, Method method, Object[] arg,
MethodProxy proxy) throws Throwable {
Object result=null;
try {
before();
result= proxy.invokeSuper(obj, arg);
after();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
2.2 追蹤Enhancer.create()方法
會(huì)發(fā)現(xiàn)Cglib的底層是調(diào)用了ASM開源包,將代理對(duì)象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來進(jìn)行處理。
private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
2.3 關(guān)于MethodInterceptor
方法攔截器
2.4 動(dòng)態(tài)代理對(duì)象的字節(jié)碼文件
public class SayHello$$EnhancerByCGLIB$$4da4ebaf extends SayHello
implements Factory
{
}
2.5 當(dāng)被代理方法或者對(duì)象被final修飾
- 被代理方法被final修飾時(shí),若進(jìn)行代理,則增強(qiáng)無效。
- 被代理對(duì)象被final修飾時(shí),若進(jìn)行代理,則運(yùn)行時(shí)報(bào)錯(cuò),如下。
Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class spring.core.aop.SayHello
- 比較jdk動(dòng)態(tài)代理與cglib動(dòng)態(tài)代理
3.1 各自局限
JDK動(dòng)態(tài)代理:只能代理實(shí)現(xiàn)了接口的類。
Cglib動(dòng)態(tài)代理:由于它的原理是對(duì)指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),采用了繼承,所以不能對(duì)final修飾的類進(jìn)行代理。
3.2 各自優(yōu)勢(shì)
JDK動(dòng)態(tài)代理:最小化依賴關(guān)系,減少依賴意味著簡(jiǎn)化開發(fā)和維護(hù),由于JDK本身的支持,可能比Cglib更可靠。
Cglib動(dòng)態(tài)代理:可以代理沒有接口的類。并且性能相對(duì)更高。
3.3 性能比較(未考證)
jdk8之前,cglib效率更高。
jdk8及之后,jdk效率更高。
附上部分底層代碼及示例代碼
底層方法
generateClassFile
private byte[] generateClassFile() {
/* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/
/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
/*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
/* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
/* ============================================================
* Step 3: Write the final class file.
*/
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
return bout.toByteArray();
}
完整示例代碼-靜態(tài)代理
public interface Greeting {
public void doGreet();
}
public class SayHello implements Greeting {
@Override
public void doGreet() {
System.out.println("Greeting by say 'hello' .");
}
}
public class GreetStaticProxy implements Greeting {
private Greeting hello;//被代理對(duì)象
public GreetStaticProxy(Greeting hello){
this.hello=hello;
}
@Override
public void doGreet() {
before();//執(zhí)行其他操作
this.hello.doGreet();//調(diào)用目標(biāo)方法
after();//執(zhí)行其他操作
}
public void before(){
System.out.println("[StaticProxy] Come to someone.");
}
public void after(){
System.out.println("[StaticProxy] Back to his own corner");
}
}
public class Main {
public static void main(String[] args) {
Greeting hello=new SayHello();
//靜態(tài)代理
GreetStaticProxy staticHelloProxy=new GreetStaticProxy(hello);
staticHelloProxy.doGreet();
System.out.println();
}
}
完整示例代碼-JDK動(dòng)態(tài)代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();
method.invoke(target, args);
after();
System.out.println("JdkProxy.this is : " + this.getClass());
System.out.println("proxy is : " + proxy.getClass());
return proxy;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public void before(){
System.out.println("[JdkProxy] Come to someone.");
}
public void after(){
System.out.println("[JdkProxy] Back to his own corner");
}
}
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Greeting hello=new SayHello();
Greeting shakeHands=new ShakeHands();
//jdk動(dòng)態(tài)代理
JdkProxy dynamicProxy=new JdkProxy(hello);
Greeting target1=(Greeting) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), dynamicProxy);
target1.doGreet();
System.out.println();
}
}
完整示例代碼-CGLIB動(dòng)態(tài)代理
public final class SayHello {
public final void doGreet() {
System.out.println("Greeting by say 'hello' .");
}
}
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
public static CglibProxy proxy=new CglibProxy();
private CglibProxy(){}
public static CglibProxy getInstance(){
return proxy;
}
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object obj, Method method, Object[] arg,
MethodProxy proxy) throws Throwable {
Object result=null;
try {
before();
result= proxy.invokeSuper(obj, arg);
after();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public void before(){
System.out.println("[cglib] Come to someone.");
}
public void after(){
System.out.println("[cglib] Back to his own corner.");
}
}
public class Main {
public static void main(String[] args) {
//cglib代理
SayHello targetProxy=CglibProxy.getInstance().getProxy(SayHello.class);
targetProxy.doGreet();
System.out.println();
CglibProxy.getInstance().getInstance().getProxy(KissHello.class).doGreet();
}
}