代理模式
为某个对象提供一种代理,以控制其他对象对这个对象的访问。属于结构型模式。
某些情况下,一个对象A不适合或者不能引用、直接访问某个对象B,而代理对象可以在客户端A和目标对象B之间起到中介作用
代理模式主要有三个重要角色:
- 抽象角色(subject): 声明真实subject和代理subject共同的接口方法
- Real Subject : 被代理的真实对象
- Proxy: 代理对象, 持有real subject的引用
静态代理
代理对象持有真实对象的引用
public class Proxy implements ISubject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
public void before() {
System.out.println("before real subject call");
}
@Override
public void request() {
before();
realSubject.request();
after();
}
public void after() {
System.out.println("after real subject call");
}
}
动态代理
JDK动态代理
public interface IPerson {
void rentRoom();
}
public class RoomProxy implements InvocationHandler {
private IPerson person;
public IPerson getInstance(IPerson person) {
this.person = person;
Class<?> clazz = person.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public void before() {
System.out.println("收到房子需求,物色房源....");
}
public void after() {
System.out.println("签合同,成交.....");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.person, args);
after();
return result;
}
}
JDK动态代理实现
JDK动态代理采用字节重组,重新生成对象来代替原始对象,以达到动态代理的目的
jdk动态代理生成对象的步骤如下:
1.获取被代理对象的引用,通过反射获取被代理对象的所有接口
2.JDK动态代理类重新生成一个新的类A,A要实现被代理类实现的所有接口。
3.动态生成Java代码
4.将新生成的Java代码编译为class文件
5.将编译好的代理类的class文件加载到JVM中运行
通过如下代码得到一个代理类的class文件,然后通过jad反编译得到$Proxy.jad
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{IPerson.class});
FileOutputStream fos = new FileOutputStream("D://$Proxy.class");
fos.write(bytes);
fos.flush();
fos.close();
可以看到新生成的 $Proxy 继承了Proxy类,并且实现了IPerson接口,重写了接口的rentRoom方法(这里的invocationHandler就是我们定义的RoomProxy实例)。
还在静态代码块中通过反射获取了被代理对象的所有方法,并保存了对应方法的引用,用于在重写的方法中通过反射调用被代理对象的方法
注意下: 从下述的源码中可以看到,所有重写后的方法都是通过h.invoke调用的,也就是都会执行我们定义在RoomProxy中的前置和后置逻辑
public final class $Proxy extends Proxy
implements IPerson
{
public $Proxy(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 rentRoom()
{
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.cpp.proxy.jdk.IPerson").getMethod("rentRoom", 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());
}
}
}
CGLib动态代理
和JDK动态代理不同,CGLib动态代理的目标对象不需要实现任何接口,它是通过动态继承目标对象来实现动态代理的
public class Renter {
public void rentRoom() {
System.out.println("我要租一个房子");
}
}
public class RoomProxy implements MethodInterceptor {
public Object getInstance(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("收到请求,帮你找房子");
}
private void after() {
System.out.println("签订合同,交中介费");
}
}
CGLib动态代理实现原理
public static void main(String[] args) {
//将内存中的class类写到磁盘,然后通过jad反编译
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://cglib_proxy//");
Renter renter = (Renter) new RoomProxy().getInstance(Renter.class);
renter.rentRoom();
}
可以看到生成了三个class文件
通过反编译的文件可以看出Renter$$EnhancerByCGLIB$$56be5228
是生成的代理类
1.生成的代理类继承了Renter类
2.代理类持有MethodInterceptor 对象
3.动态代理类会重写父类 Renter的非 final、private 方法;也会构建自己的cglib 方法,对应方法名为CGLIB+$父类方法名$
4.对于自己的cglib 方法:通过super.方法名,直接调用父类;对于重写的方法:它会调用拦截器中的 intercept()
方法
5.methodProxy.invokeSuper()
方法会调用动态代理类中的 cglib 方法;methodProxy.invoke()
方法会调用动态代理类中的重写方法,所以如果我们在RoomProxy
拦截器里调用methodProxy.invoke()
就会出现死循环
public class Renter$$EnhancerByCGLIB$$56be5228 extends Renter
implements Factory
{
private MethodInterceptor CGLIB$CALLBACK_0;
static void CGLIB$STATICHOOK1()
{
CGLIB$rentRoom$0$Proxy = MethodProxy.create(classloader, (CGLIB$rentRoom$0$Method = Class.forName("com.cpp.proxy.cglib.Renter").getDeclaredMethod("rentRoom", new Class[0])).getDeclaringClass(), class1, "()V", "rentRoom", "CGLIB$rentRoom$0");
//...
return;
}
//methodProxy.invokeSuper() 方法会调用
final void CGLIB$rentRoom$0()
{
super.rentRoom();
}
//methodProxy.invoke()方法会调用
public final void rentRoom()
{
//...
this;
CGLIB$rentRoom$0$Method;
CGLIB$emptyArgs;
CGLIB$rentRoom$0$Proxy;
//调用拦截器的intercept()方法
intercept();
return;
super.rentRoom();
return;
}
//...
}
整个调用路径为:
代理对象调用rentRoom()
-> 调用重写的rentRoom()
方法 -> 调用拦截器的intercept()
方法 -> 调用methodProxy.invokeSuper()
-> 调用CGLIB$rentRoom$0()
-> 调用父类(被代理对象)的rentRoom()
方法
那么其他两个class分别是什么呢?
CGLib共生成3个类,分别为代理类,代理类的FastClass,目标类的FastClass,它采用了FastClass的机制来实现对被拦截方法的调用。
FastClass
机制就是对一个类的方法建立索引,然后通过索引来直接调用相应的方法, 而不需要通过反射来调用,其调用效率比JDK动态代理通过反射调用效率高
FastClass的初始化是通过init()方法实现的,初始化时机为调用methodProxy.invokeSuper()
或methodProxy.invoke()
方法,在methodProxy.invokeSuper()
调用的时候会生成FastClassInfo(第一次生成后会放入到缓存中)
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
//初始化FastClassInfo
this.init();
FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
CreateInfo ci = this.createInfo;
FastClassInfo fci = new FastClassInfo();
//ci.c1=目标类的class fci.f1=目标类的FastClass(从缓存获取,如果没有则生成新的FastClass并放入缓存中)
fci.f1 = helper(ci, ci.c1);
//ci.c2=CGLib生成的代理类的class fci.f2=代理类的FastClass
fci.f2 = helper(ci, ci.c2);
//方法在目标类的FastClass中的索引(这里只是一个接口,具体的实现在CGLib里生成的子类里)
fci.i1 = fci.f1.getIndex(this.sig1);
//方法在代理类的FastClass中的索引
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
看下getIndex()
的具体实现
public int getIndex(Signature signature)
{
String s = signature.toString();
s;
s.hashCode();
JVM INSTR lookupswitch 10: default 204
goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11
_L2:
"getClass()Ljava/lang/Class;";
equals();
JVM INSTR ifeq 205;
goto _L12 _L13
_L13:
break MISSING_BLOCK_LABEL_205;
_L12:
return 7;
//...
"rentRoom()V";
equals();
JVM INSTR ifeq 205;
goto _L18 _L19
_L19:
break MISSING_BLOCK_LABEL_205;
_L18:
return 0;
//...
JVM INSTR pop ;
return -1;
}
//通过index直接定位进行方法调用
public Object invoke(int i, Object obj, Object aobj[])
throws InvocationTargetException
{
(Renter)obj;
i;
JVM INSTR tableswitch 0 9: default 152
goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11
_L2:
rentRoom();
return null;
_L3:
((Number)aobj[0]).longValue();
((Number)aobj[1]).intValue();
//...
JVM INSTR new #76 <Class InvocationTargetException>;
JVM INSTR dup_x1 ;
JVM INSTR swap ;
InvocationTargetException();
throw ;
_L1:
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
我们可以模仿FastClass的机制写一个demo,通过索引来进行方法调用
public class Renter {
public void rentRoom() {
System.out.println("我要租一个房子");
}
public String toString() {
return "Renter String";
}
}
public class RenterFastClass {
public int getIndex(String s, Class[] classes) {
int hashCode = s.hashCode();
switch (hashCode) {
case 1963324341:
return 1;
case 1774939245:
return 2;
}
return -1;
}
public Object invoke(int i, Object o, Object[] objects) {
Renter renter = (Renter) o;
switch (i) {
case 1:
renter.rentRoom();
return null;
case 2:
return renter.toString();
}
return null;
}
}
public static void main(String[] args) {
RenterFastClass fs = new RenterFastClass();
Renter renter = new Renter();
int index = fs.getIndex("rentRoom()",new Class[]{Renter.class});
fs.invoke(index, renter, null);
int index2 = fs.getIndex("toString()",new Class[]{Renter.class});
System.out.println(fs.invoke(index2, renter, null));
}
执行结果如下:
总结:
- CGLib动态代理继承了被代理的类;JDK动态代理实现了被代理类的接口
- JDK动态代理和CGLib动态代理都是在运行时生成字节码文件,JDK动态代理直接写Class字节码,CGLib是用过ASM框架写字节码,CGLib生成代理类字节码文件的效率更低
- CGLib使用了FastClass机制来调用方法,比通过反射调用的JDK动态代理的执行效率更高