Java中的两种动态代理

620 阅读4分钟

动态代理和静态代理的区别就是在是现阶段不用关心代理类,代理类不是程序员编写的,而是在运行时动态生成的,动态代理的实现方式通常有两种:JDK动态代理CGLIB代理

JDK动态代理

是Java自带的代理方式

原理:

是在运行时动态生成一个代理类,这个代理对象一定和原有对象实现同一个接口,这样就可以有相同的行为,所以当使用JDK动态代理时,原有对象和代理对象必须满足这个条件

CGLIB代理

Code Generation Library是一个开源项目,是一个强大的、高性能的、高质量的Code生成类库,它可以在运行器拓展Java类和实现Java接口

原理:CGLIB通过动态生成一个子类,该子类继承被代理类,重写被代理类的所有不是final修饰的方法,并在子类中采用方法拦截的技术拦截父类的所有方法的掉用

两种代理方式的使用教程

JDK动态代理的使用

声明一个接口Drink:

public interface Drink {
    void drink();
}

创建一个实现该接口的类:

public class Milk implements Drink {
    @Override
    public  void drink() {
        System.out.println("drink milk");
    }
}

JDK动态代理处理器:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
​
public class CommonInvocationHandler implements InvocationHandler {
    // 被代理的对象
    private Object proxied;
​
    public CommonInvocationHandler(Object proxied) {
        this.proxied = proxied;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用被代理对象的方法前做一些事情
        System.out.println("before doing something");
        // 调用被代理对象的方法
        Object result = method.invoke(proxied, args);
        // 在调用被代理对象的方法后做一些事情
        System.out.println("after doing something");
        return result;
    }
}

使用JDK动态代理:

import java.lang.reflect.Proxy;
​
public class Main {
    public static void main(String[] args) {
        Milk milk = new Milk();
        Drink proxy = (Drink) Proxy.newProxyInstance(milk.getClass().getClassLoader(), milk.getClass().getInterfaces(), new CommonInvocationHandler(new Milk()));
        proxy.drink();
    }
}

输出结果:

before doing something
drink milk
after doing something

CGLIB代理的使用

导入CGLIB依赖:

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

创建被代理类:

public class Producer {
    public void spanMoney(double monty) {
        System.out.println("花费: " + monty + "元");
    }
}

使用CGLIB代理:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
public class Consumer {
    public static void main(String[] args) {
        // new一个被代理对象,因为代理类执行被代理类的方法时,需要用到被代理类的对象
        final Producer producer = new Producer();
        Producer producerProxy = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println(methodProxy);
                Double money = (Double) objects[0];
                return method.invoke(producer, money * 0.8);
            }
        });
​
        producerProxy.spanMoney(1000);
    }
}

输出结果:

org.springframework.cglib.proxy.MethodProxy@39a054a5
花费: 800.0

JDK动态代理和CGLIB的区别

  • JDK动态代理只能对接口进行代理,不能对普通的类进行代理,这是因为JDK动态代理生成的代理类,其父类是Proxy,且Java不支持类的多继承
  • CGLIB能够代理接口和普通类,但是被代理的类不能被final修饰,且接口中的方法不能使用final修饰
  • JDK动态代理使用Java反射来实现,在生成类上更高效
  • CGLIB使用ASM框架直接对字节码进行修改,使用了FastClass的特性,在某些情况下,类的方法执行会比较高效

动态代理的一些限制:

√表示可以代理,×表示不可代理

privatestaticfinal
JDK动态代理××
CGLIB×××

为什么CGLIB不能代理private、final、static方法?

因为CGLIB代理类是通过继承被代理类来实现的,所以:

  1. 如果方法被private修饰,那么子类是无法调用超类的私有方法的;
  2. 如果被static修饰,从技术上的角度来说是可以实现的,但是被排除了,因为从程序设计角度来说,静态方法是属于类的,而不是属于某个对象的

为什么JDK动态代理不能代理private、static方法,而可以代理final方法?

在JDK动态代理中,代理类和被代理类需要实现共同的接口,来拥有相同的行为,那么private、static方法都是无法修饰接口方法的,自然不行

final修饰的是实现了接口类的子类的方法,不影响代理的调用,所以是可以的

Spring中两者的使用场景

  1. 如果目标对象实现了接口,默认情况下会采用JDK动态代理来实现AOP,可以强制使用CGLIB来实现AOP

    强制使用CGLIB实现AOP的方式:

    1. 添加CGLIB库

    2. 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>,或者在SpringBoot的application中配置:

      spring:
        aop:
          proxy-target-class: true
      

      该值默认为false,表示使用JDK动态代理,手动设置为true,表示使用CGLIB代理,但即使默认为false,当目标没有实现接口时,Spring会自动使用CGLIB代理

  2. 如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

参考资料

动态代理jdk和cglib区别、注意事项(private,static,final)、spring aop原理

Cglib和jdk动态代理的区别