天天看点

【设计模式】代理模式代理模式

代理模式

为某个对象提供一种代理,以控制其他对象对这个对象的访问。属于结构型模式。

某些情况下,一个对象A不适合或者不能引用、直接访问某个对象B,而代理对象可以在客户端A和目标对象B之间起到中介作用

【设计模式】代理模式代理模式

代理模式主要有三个重要角色:

  1. 抽象角色(subject): 声明真实subject和代理subject共同的接口方法
  2. Real Subject : 被代理的真实对象
  3. 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动态代理的执行效率更高