前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

1、Java动态代理作用

主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法)

因为在 InvocationHandlerinvoke方法中,通过反射,可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。

2、动态代理

2.1、JDK动态代理

2.1.1、代码实现

2.1.1.1、目标接口
public interface BuyHouse {

    void buyHosue();

}

2.1.1.2、目标接口实现类
public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }
}

2.1.1.3、动态处理器 (实现的是 InvocationHandler
public class DynamicProxyHandler implements InvocationHandler {

    private Object object;

    public DynamicProxyHandler(final Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("买房前准备");
        Object result = method.invoke(object, args);
        System.out.println("买房后装修");
        return result;
    }
}

2.1.2、测试

2.1.1.4、测试
2.1.1.4.1、第一个参数 指定当前目标对象使用的类加载器,获取加载器的
2.1.1.4.2、第二个参数 指定目标对象实现的接口的类型,使用泛型方式确认类型, 这里的参数就是接口列表
2.1.1.4.3、第三个参数 指定动态处理器
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        
BuyHouse proxyBuyHouse = (BuyHouse) 
    Proxy.newProxyInstance(BuyHouse.class.getClassLoader(),
    buyHouse.getClass().getInterfaces(), 
    handler);

public class DynamicProxyTest {

    public static void main(String[] args) throws IOException {
        BuyHouse buyHouse = new BuyHouseImpl();
        System.out.println("buyHouse " + buyHouse.getClass().getName());

        DynamicProxyHandler handler = new DynamicProxyHandler(buyHouse);
        // 1、第一个参数 指定当前目标对象使用的类加载器
        // 2、第二个参数 指定目标对象实现的接口的类型 接口列表
        // 3、第三个参数 指定动态处理器,
        BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(
                BuyHouse.class.getClassLoader(),
                buyHouse.getClass().getInterfaces(),
                handler);

        System.out.println("proxyBuyHouse :" + proxyBuyHouse.getClass().getName());
        proxyBuyHouse.buyHosue();


        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", buyHouse.getClass().getInterfaces());
        String  code = IOUtils.toString(bytes, "utf-8");

        FileOutputStream fileOutputStream = new FileOutputStream("D:/study/HealerJean.github.io/_posts/1_设计模式/hlj-design-pattern/porxy.class");
        IOUtils.write(bytes, fileOutputStream);
    }

}


buyHouse com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouseImpl
proxyBuyHouse com.sun.proxy.$Proxy0
买房前准备
我要买房
买房后装修


2.1.3、生成的代理类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse;
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 BuyHouse {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    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 int countAdd(int var1) throws  {
        try {
            return (Integer)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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);
        }
    }

    public final void buyHosue() throws  {
        try {
            super.h.invoke(this, m3, (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("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("countAdd", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("buyHosue");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

2.1.4、总结

jdk动态代理是由java内部的反射机制来实现的 ,使用动态代理的对象必须实现一个或多个接口,jdk动态代理只能对接口进行代理,那么为什么呢

Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的$Proxy0.class文件发现:

class类定义为:

public final class $Proxy0 extends Proxy implements Interface {

	public $Proxy0(InvocationHandler paramInvocationHandler) { 
		super(paramInvocationHandler); 
}

$Proxy0 继承了需要被代理的类,由于java的单继承动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口

2.2、gclib动态代理

2.2.1、代码实现

2.2.1.1、目标接口
public interface BuyHouse {

    void buyHosue();

}

2.2.1.2、目标实现类
public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }
}

2.2.1.3、CglibProxy
public class CglibProxy implements MethodInterceptor {

    @SuppressWarnings("unchecked")
    public <T> T getInstance(Object target, Class<T> clazz) {
        //字节码加强器:用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //代理的目标对象
        enhancer.setSuperclass(target.getClass());
        //回调类,在代理类方法调用时会回调Callback类的intercept方法
        enhancer.setCallback(this);

        //创建代理类
        Object result = enhancer.create();
        System.out.println(result.getClass().getName());
        return (T)result;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //调用目标类(父类)的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("proxy"+proxy);
        return result;
    }


    // CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,
    // 但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象, 因为无需频繁创建对象,用CGLIB合适,
    // 反之使用JDK方式要更为合适一些。
    // 同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

}

2.2.2、测试

public class CglibProxyTest {

    public static void main(String[] args){
        BuyHouse buyHouse = new BuyHouseImpl();
        CglibProxy cglibProxy = new CglibProxy();
        BuyHouseImpl buyHouseCglibProxy = cglibProxy.getInstance(buyHouse,BuyHouseImpl.class);
        
        buyHouseCglibProxy.buyHosue();
    }
}

2.2.2、总结

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类(被代理的类)的方法,实现AOP面向切面编程呢。

JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。 Cglib 不能对 final 类以及 final 方法进行代理。因为他是继承被代理类的子类,如果被代理类使用了final就不能被继承

对接口进行代理的cglib最后生成的源码是实现了该接口和Factory接口

对实现类进行代理的cglib最后生成的源码是继承了实现类并实现了Factory接口

3、动态代理总结

3.1、jdk代理和CGLIB代理的区别

1、JDK 和 CGLib动态代理性能对比-教科书上的描述,记得有但是


1CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少有研究表明大概要高10倍

2但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多有研究表明大概有8倍的差距

3因此对于singleton的代理对象或者具有实例池单例的代理因为无需频繁的创建代理对象所以比较适合采用CGLib动态代理反正则比较适用JDK动态代理

在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,但是JDK动态代理和CGLib动态代理的适用场景还是不一样的哈!

JDK动态代理只能对实现了接口的类生成代理而不能针对类

CGLIB是针对类实现代理主要是对指定的类生成一个子类覆盖其中的方法最后生成的源码是实现了该接口和Factory接口 也可以对接口进行代理最后生成的源码是继承了实现类并实现了Factory接口

3.2、Spring在选择用JDK还是CGLiB的依据

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);


如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

ContactAuthor