Java代理

Posted on By xqw

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

JDK动态代理

  1. 被代理的类必须实现接口,代理的方法是接口的方法
  2. 使用Proxy的静态方法:newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理类
  3. InvocationHandler 的 invoke方法就是代理类最后执行的方法

示例

interface Human {
    void info();

    void fly();
}

// 被代理类
static class SuperMan implements Human {
    public void info() {
        System.out.println("我是超人");
    }
    public void fly() {
        System.out.println("I can fly!");
    }
}

static class MyInvocationHandler implements InvocationHandler {

    // 被代理类对象的声明
    Object obj;

    // 动态的创建一个代理类的对象
    public Object bind(Object obj){
        this.obj = obj;
        //生成一个动态类
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("代理前操作。。。");
        Object returnVal = method.invoke(obj, args);
        System.out.println("代理后操作。。。");

        return returnVal;
    }
}

//调用
public static void main(String[] args) {
    //创建一个被代理类的对象
    SuperMan man = new SuperMan();
    MyInvocationHandler handler = new MyInvocationHandler();
    //返回一个代理类的对象
    Object obj = handler.bind(man);
    System.out.println(obj.getClass());
    //代理实例 强转
    Human hu = (Human)obj;
    //通过代理类的对象调用重写的抽象方法
    hu.info();

    System.out.println();

    hu.fly();
}

Proxy.newProxyInstance 源码

先getProxyClass0生成代理类 放回Class,然后通过反射创建出一个实例

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //最关键一步,获取代理类(Human$Proxy0)的Class,使用native方法生成字节码
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //通过反射获取构造方法:public Human$Proxy0(InvocationHandler ih){}
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //相当于 new Human$Proxy0(h)
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

生成的代理类代码

通过newProxyInstance生成的类近似类:

public final class Human$Proxy0 extends Proxy implements Human {
    private static Method info;
    private static Method fly;

    InvocationHandler ih;
    public Human$Proxy0(InvocationHandler paramInvocationHandler) {
        ih = paramInvocationHandler;
    }   
    
    @Override
    public void info() {
        ih.invke(this, info, null);
    }
    
    @Override
    public void fly() {
        ih.invke(this, fly, null);
    }
}

Java中动态代理使用与原理详解

CGLIB代理

JDK代理必须实现接口,CGLIB不需要,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成final。

示例

// 被代理类,未实现接口
static class SuperMan {
    public void info() {
        System.out.println("我是超人");
    }
    public void fly() {
        System.out.println("I can fly!");
    }
}

static class CGLibProxy implements MethodInterceptor {
    // CGLib需要代理的目标对象
    private Object targetObject;

    public Object bind(Object obj) {
        this.targetObject = obj;
        Enhancer enhancer = new Enhancer();
        // 设置父类--可以是类或者接口
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        Object proxyObj = enhancer.create();
        // 返回代理对象
        return proxyObj;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        Object obj = null;
        System.out.println("代理前操作。。。");

        // 执行目标目标对象方法
        obj = method.invoke(targetObject, args);

        System.out.println("代理后操作。。。");
        return obj;
    }
}

public static void main(String[] args) {
    SuperMan man = new SuperMan();//创建一个被代理类的对象

    // 添加如下代码,获取代理类源文件
    String path = CGLibProxy.class.getResource(".").getPath();
    System.out.println(path);
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);

    CGLibProxy cgLibProxy = new CGLibProxy();
    Object obj = cgLibProxy.bind(man);//返回一个代理类的对象
    System.out.println(obj.getClass());
    //class com.web.test.SuperMan$$EnhancerByCGLIB$$3be74240
    SuperMan hu = (SuperMan)obj;
    hu.info();//通过代理类的对象调用重写的抽象方法

    System.out.println();

    hu.fly();
}

生成的代理类代码

//继承自SuperMan
public class CglibProxy$SuperMan$$EnhancerByCGLIB$$7c617847 extends SuperMan implements Factory {
    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$info$0$Method;
    private static final MethodProxy CGLIB$info$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$fly$1$Method;
    private static final MethodProxy CGLIB$fly$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.proxy.CglibProxy$SuperMan$$EnhancerByCGLIB$$7c617847");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$2$Method = var10000[0];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[1];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[2];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[3];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        var10000 = ReflectUtils.findMethods(new String[]{"info", "()V", "fly", "()V"}, (var1 = Class.forName("com.proxy.CglibProxy$SuperMan")).getDeclaredMethods());
        CGLIB$info$0$Method = var10000[0];
        CGLIB$info$0$Proxy = MethodProxy.create(var1, var0, "()V", "info", "CGLIB$info$0");
        CGLIB$fly$1$Method = var10000[1];
        CGLIB$fly$1$Proxy = MethodProxy.create(var1, var0, "()V", "fly", "CGLIB$fly$1");
    }

    final void CGLIB$info$0() {
        super.info();
    }

    public final void info() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        //使用代理接口Factory的intercept方法
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
        } else {
            super.info();
        }
    }

//
}

Spring AOP

  • Spring中动态代理的实现 Spring代理实际上是对JDK代理和CGLIB代理做了一层封装,并且引入了AOP概念:Aspect、advice、joinpoint等等,同时引入了AspectJ中的一些注解@pointCut,@after,@before等等。Spring Aop严格的来说都是动态代理。

  • Spring在选择用JDK还是CGLiB的依据:
    当Bean实现接口时,Spring就会用JDK的动态代理
    当Bean没有实现接口时,Spring使用CGlib是实现

  • 强制使用CGLIB实现AOP spring.aop.proxy-target-class=true 或者 @EnableAspectJAutoProxy(proxyTargetClass = true)

  • 性能上区别 JDK1.6 1.7下,jdk代理创建比CGLiB快,执行方法比CGLiB慢
    1.8 jdk代理更快
    Spring AOP中JDK和CGLib动态代理哪个更快?