引言

学习了前面的静态代理,我们知道了它的一些缺点,该如何改进它呢,当然是使用本篇的重点,通过让代理类动态生成,也就是动态代理.

为什么类可以动态生成,这涉及到类的加载机制,这里不讲……

实现动态代理的方向

有两种方式:

  1. 通过实现接口的方式 -> JDK动态代理
  2. 通过继承类的方式 -> CGLIB动态代理

JDK动态代理

JDK动态的实现

  1. 动态代理是由java.lang.reflect.Proxy反射类提供实例创建的调派,由Proxy.newProxyInstance()方法创建对应的实例对象.
  2. 通过java.lang.reflect.InvocationHandler接口中的invoke()方法进行调用,增强,转发实现业务.

我们接着之前的场景,我们在通过代购帮我们购买了好多次物品以后,代购现在越来越有钱了,代购的商品种类也更加丰富,他便自己开了一家代购公司D,这个时候我们如果想要去通过代购去买一件商品,就不在会是每一次都是同一个人去接待我们,可能是D公司的小刘美女业务员、也可能是小王帅哥业务员。即:动态代理就是,同一个服务由不同的实例去帮我们实现。

代码示例

A抽象对象

public interface ASellService {
    void sale(String name);
}

A真实对象(商家A)

public class AMerchantServiceImplA implements ASellService {
    @Override
    public void sale(String name) {
        System.out.println(name + "购买了A商品");
    }
}

B抽象对象

public interface BSellService {
    void sale(String name);
}

B真实对象(商家B)

public class BMerchantServiceImpl implements BSellService {
    @Override
    public void sale(String name) {
        System.out.println(name + "购买了B商品");
    }
}

JDK动态代理对象(代理公司)

public class DCompany implements InvocationHandler {
    // 代理对象需要包含真实对象,为提升真实对象的复用性,则使用Object接受
    private Object objFactory;

    public Object getObjFactory(){
        return objFactory;
    }

    public void setObjFactory(Object objFactory){
        this.objFactory = objFactory;
    }

    // 使用JDK代理类获取代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(objFactory.getClass().getClassLoader(), objFactory.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 进行业务增强
        System.out.println("JDK动态代理对业务进行了增强处理");
        // 通过反射调用方法本身
        Object invoke = method.invoke(objFactory, args);
        System.out.println("JDK动态代理对业务进行了增强处理结束");
        return invoke;
    }
}

测试类

public class DynamicProxyTest {
    public static void main(String[] args) {
        // A商品卖家
        ASellService A = new AMerchantServiceImplA();
        // B商品卖家
        BSellService B = new BMerchantServiceImpl();

        // 购买者
        String customerName = "张三";
        // 代购公司
        DCompany dCompany = new DCompany();
        dCompany.setObjFactory(A);
        ASellService AProxyInstance = (ASellService) dCompany.getProxyInstance();

        System.out.println("业务员"+AProxyInstance.getClass()+"接待"+customerName);
        AProxyInstance.sale(customerName);

        System.out.println("--------------------------------------------------------------");

        dCompany.setObjFactory(B);
        BSellService BProxyInstance = (BSellService) dCompany.getProxyInstance();
        System.out.println("业务员"+BProxyInstance.getClass()+"接待"+customerName);
        BProxyInstance.sale(customerName);
    }
}

结果:

image-20210719161840640

我们可以清晰的看到JDK动态代理执行后打印的结果,以及Proxy动态调派帮我们由不同的代理对象$Proxy X实例化真实对象。

保存代理类

借助工具类,保存代理类,

(通过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理类)

System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
public class ProxyUtils {
    /**
     * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
     * params: clazz 需要生成动态代理类的类
     * proxyName: 为动态生成的代理类的名称
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        // 根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            //保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然后在代码最后一行加入代码

// 保存JDK动态代理生成的代理类,类名保存为 asd
ProxyUtils.generateClassFile(BMerchantServiceImpl.class, "asd");

代码如下:

public final class asd extends Proxy implements BSellService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public asd(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        // 省略...
    }

    public final String toString() throws  {
        // 省略...
    }

    public final void select() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        // 省略...
    }

    public final void update() 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"));
            m3 = Class.forName("com.company.dynamicAgent.BSellService").getMethod("sale", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从上述代码可知:

  • asd继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 由于 asd继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstanceDCompany对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
image-20210719170527735

CGLIB动态代理

先引入Maven包

<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.3.0</version>
</dependency>

代码示例

编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调

public class LogInterceptor implements MethodInterceptor {

    private Object target;//维护一个目标对象
    public LogInterceptor(Object target) {
        this.target = target;
    }

    //为目标对象生成代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }

    /**
     * @param object 表示要进行增强的对象
     * @param method 表示拦截的方法
     * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
     * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     * @return 执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);   // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

编写一个UserDao类,它没有接口,只有两个方法,select() update()

public class UserDao {
    public void select() {
        System.out.println("UserDao 查询 selectById");
    }
    public void update() {
        System.out.println("UserDao 更新 update");
    }
}

测试

public class CglibTest {
    public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();
        System.out.println(target.getClass());

        //代理对象
        UserDao proxy = (UserDao) new LogInterceptor(target).getProxyInstance();
        System.out.println(proxy.getClass());

        //执行代理对象方法
        proxy.select();
        proxy.update();
    }
}

结果

log start time [Fri Dec 21 00:06:40 CST 2018] 
UserDao 查询 selectById
log end time [Fri Dec 21 00:06:40 CST 2018] 
log start time [Fri Dec 21 00:06:40 CST 2018] 
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018] 

CGLIB创建动态代理类的模式:

  1. 查找目标类上所有非final的public类型的方法定义;
  2. 将这些方法的定义转换为字节码;
  3. 将组成的字节码转换成相对应的代理的class对象
  4. 实现MethodInterceptor接口,用来处理对代理类上所有方法的请求

JDK动态代理和CGLIB动态代理

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能

面试题

描述动态代理的几种实现方式?分别说出相应的优缺点.

代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。

静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

  • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点:不同的接口要有不同的代理类实现,会很冗余

JDK 动态代理

  • 为了解决静态代理中,生成大量的代理类造成的冗余;
  • JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
  • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
  • jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
  • 优点:解决了静态代理中冗余的代理实现类问题。
  • 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理

  • 由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
  • CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
  • 实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
  • 但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
  • 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
  • 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
  • 缺点:技术实现相对难理解些。

CGlib 对接口实现代理?

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;

/**
 * 创建代理类的工厂 该类要实现 MethodInterceptor 接口。
 * 该类中完成三样工作:
 * (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象
 * (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类
 * (3)定义回调接口方法。对目标类的增强这在这里完成
 */
public class CGLibFactory implements MethodInterceptor {
    // 声明目标类的成员变量
    private UserService target;

    public CGLibFactory(UserService target) {
        this.target = target;
    }
    // 定义代理的生成方法,用于创建代理对象
    public UserService myCGLibCreator() {
        Enhancer enhancer = new Enhancer();
        // 为代理对象设置父类,即指定目标类
        enhancer.setSuperclass(UserService.class);
        /**
         * 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,
         * 是因为MethodIntecepter接口继承自Callback,是其子接口
         */
        enhancer.setCallback(this);
        return (UserService) enhancer.create();// create用以生成CGLib代理对象
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start invoke " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("end invoke " + method.getName());
        return result;
    }
}

参考

文章1:https://www.cnblogs.com/staticking/p/13628307.html

文章2:https://www.cnblogs.com/whirly/p/10154887.html

文章3:https://www.jianshu.com/p/9bcac608c714

文章4:https://segmentfault.com/a/1190000011291179