Java动态代理

Java动态代理

Tans 1,258 2022-03-21

动态代理

一. 基于JDK的动态代理

动态代理使用了java.lang.reflect包下的代理组件,本质上是在JVM运行期间创建某个接口的实例对象,注意:使用JDK代理的对象必须实现某个接口,主要的核心类有以下两个:

  • java.lang.reflect.Proxy :Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类
  • java.lang.reflect.InvocationHandler : 是代理实例的调用处理程序实现的接口。每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用被编码并分派到其调用处理程序的调用方法。

可以看出,其底层原理就是通过反射在运行时创建代理类的实例,此实例对象包含一个调用处理程序,那么在被代理方法调用的时候,就转去调用handler中的相应方法。

具体操作流程如下:

//1.创建 接口 和 被代理方法声明
public interface UserDao {
    void get();
}

/*-----------------------------------*/
//2.创建 实现类
public class UserDaoImpl implements UserDao{
    @Override
    public void get() {
        System.out.println("本源方法");
    }
}

/*-----------------------------------*/
//3. 创建 Handler 指派器,也就是用来代理方法的处理器
class UserDaoProxy implements InvocationHandler{
    private Object object; //被代理的对象实例
    public UserDaoProxy(Object object) {
        this.object = object;
    }

     // 当被代理方法执行的时候,转到这里实现新方法。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行之前....");
        Object res = method.invoke(object, args);
        System.out.println("方法执行之后");
        return res;
    }
}

/*-----------------------------------*/
//4. 测试类
public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces = {UserDao.class};
        UserDaoImpl um = new UserDaoImpl();
         //通过Proxy创建了一个代理对象。
         // newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
         //loader:类加载器 interfaces:代理对象所实现的接口  h:上文中提到的调用处理器
        UserDao userDao  = (UserDao)Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(), interfaces,
                new UserDaoProxy(um));
        userDao.get();
    }
}


//在JVM生成的代理类大致是这样的:
public class UserDaoDynamicProxy implements UserDao {
    InvocationHandler handler;
    public UserDaoDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
     //代理之后的get方法
    public void get() {
        handler.invoke( //转去执行处理器中的invoke方法
           this,
           UserDao.class.getMethod("get"),
           null);
    }
}

二. 基于Cglib代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
  • 与JDK提供的动态代理不同之处在于被代理的对象无需实现接口

1. 引入坐标

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

2.编写被代理类(这里复用上文的UserDao)

public interface UserDao {
    void get();
}
public class UserDaoImpl implements UserDao{
    @Override
    public void get() {
        System.out.println("本源方法");
    }
}

3. 创建MethodInterceptor实现类

class ProxyFactoryByCglib implements MethodInterceptor{
    private Object object;

    ProxyFactoryByCglib(Object object){
        this.object = object;
    }

     //创建获取代理实例的方法
    public Object getProxyInstance(){
        Enhancer enhancer = new Enhancer(); //Enhance就是最终返回的代理类对象
        enhancer.setSuperclass(object.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

     
     //代理的具体处理器,和jdk代理中InvocationHandler的invoke方法有点类似。
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法调用之前");
        method.invoke(object, objects);
        System.out.println("方法调用之后");
        return null;
    }
}

4.编写测试类

public class JDKProxyByCglib {

    public static void main(String[] args) {
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao proxyInstance = (UserDaoImpl)new ProxyFactoryByCglib(userDao).getProxyInstance();
        proxyInstance.get();
    }
}

5.输出结果

方法调用之前
本源方法
方法调用之后

总的来说,CGLIB通过生成子类来重写非final方法来代理。需要注意的是:如果被代理方法是final修饰的,那么此方法不可被代理。

总结

一般来说,可以使用JDK动态代理方法来创建代理。对于没有接口或性能因素的情况,CGLIB是一个很好的选择。CGLIB比使用Java反射的JDK动态代理方法更快。