静态代理和动态代理

代理分为静态代理动态代理,这里写两个小的 demo,动态代理采用的就是 JDK 代理。

举个例子就是现在一个班上的学生需要交作业,现在由班长代理交作业,那么班长就是代理,学生就是被代理的对象。

静态代理

首先,我们创建一个 Person 接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就可以让班长来代理执行

public interface Person {
    /**
     * 交作业
     */
    void giveTask();
}

Student 类实现 Person 接口,Student 可以具体实施交作业这个行为。

/**
 * 学生代理类
 */
public class Student implements Person {
    private String name;
    
    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交作业");
    }
}

StudentBoss 类,这个类也实现了 Person 接口,但是还另外持有一个学生类对象,那么他可以代理学生类对象执行交作业的行为。

/**
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生行为
 */
public class StudentBoss implements Person {
    /**
     * 被代理的学生
     */
    Student stu;
 
    public StudentBoss(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }

    
    /**
     * 代理交作业,调用被代理学生的交作业的行为
     */
    public void giveTask() {
        // 交作业之前帮你美言几句
        System.out.println(stu.getName() + "真棒!");
        // 交作业
        stu.giveTask();
    }
}

下面测试一下,看代理模式如何使用:

public class StaticProxyTest {
    public static void main(String[] args) {
        // 被代理的学生林浅,他的作业上交有代理对象monitor完成
        Person linqian = new Student("林浅");
 
        // 生成代理对象,并将林浅传给代理对象
        Person monitor = new StudentBoss(linqian);
 
        // 班长代理交作业
        monitor.giveTask();
    }
}

运行结果:

林浅真棒!
林浅交语文作业

这里并没有直接通过林浅(被代理对象)来执行交作业的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

代理模式就是在访问实际对象时引入一定程度的间接性,这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如班长在帮林浅交作业的时候想告诉老师最近林浅的进步很大,就可以轻松的通过代理模式办到,在代理类的交作业之前加入方法即可。

这个优点就可以运用在 Spring 中的 AOP,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法,这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

动态代理

动态代理和静态代理的区别是,静态代理的的代理类是我们自己定义好的,在程序运行之前就已经编译完成,但是动态代理的代理类是在程序运行时创建的。

相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法,我们上面的例子中只有一个代理方法,如果还有很多的代理方法,就太麻烦了,我们来看下动态代理是怎么去实现的。

public interface Person {
    /**
     * 交作业
     */
    void giveTask();
}
/**
 * 学生代理类
 */
public class Student implements Person {
    private String name;
    
    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交作业");
    }
}

创建 StuInvocationHandler 类,实现 InvocationHandler 接口,这个类中持有一个被代理对象的实例 targetInvocationHandler 中有一个 invoke() 方法,所有执行代理对象的方法都会被替换成执行 invoke() 方法。

public class StuInvocationHandler<T> implements InvocationHandler {
    /**
     *invocationHandler持有的被代理对象
     */
    T target;
 
    public StuInvocationHandler(T target) {
        this.target = target;
    }
 
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" + method.getName() + "方法");
        Object result = method.invoke(target, args);
        return result;
    }
}

那么接下来我们就可以具体的创建代理对象了。

/**
 * 代理类
 */
public class ProxyTest {
    public static void main(String[] args) {
 
        // 创建一个实例对象,这个对象是被代理的对象
        Person linqian = new Student("林浅");
 
        // 创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<>(linqian);
 
        // 创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
 
        //代理执行交作业的方法
        stuProxy.giveTask();
    }
}

我们执行代理测试类,首先我们创建了一个需要被代理的学生林浅,将林浅传入 stuHandler 中,我们在创建代理对象 stuProxy 时,将 stuHandler 作为参数,那么所有执行代理对象的方法都会被替换成执行 invoke 方法,也就是说,最后执行的是 StuInvocationHandler 中的 invoke 方法。所以在看到下面的运行结果也就理所当然了。

执行结果:

代理执行giveTask方法
林浅交作业

那么到这里问题就来了,为什么代理对象执行的方法都会通过 InvocationHandler 中的 invoke 方法来执行,带着这个问题,我们需要看一下动态代理的源码,对他进行简单的分析。

上面我们使用 Proxy 类的 newProxyInstance 方法创建了一个动态代理对象,看一下他的源码:

    /**
     * 创建一个代理类的实例,该代理类实现指定的接口,并将方法调用分派到指定的调用处理程序。
     * 
     * @param loader 定义代理类的类加载器。
     * @param interfaces 代理类要实现的接口列表。
     * @param h 分派方法调用的调用处理程序。
     * @return 一个代理实例,具有指定的调用处理程序和由指定的类加载器定义的代理类,该代理类实现了指定的接口。
     * @throws IllegalArgumentException 如果违反了传递给 {@code getProxyClass} 的参数的任何限制,则抛出 {@code IllegalArgumentException}。
     * @throws SecurityException 如果存在安全管理器,并且满足以下任一条件,则抛出安全异常:
     *         <ul>
     *         <li>给定的 {@code loader} 为 {@code null},且调用者的类加载器不为 {@code null},并且调用 {@link SecurityManager#checkPermission} 
     *         用 {@code RuntimePermission("getClassLoader")} 权限拒绝访问;</li>
     *         <li>对于每个代理接口 {@code intf},调用者的类加载器与 {@code intf} 的类加载器不相同或不是其祖先,并且调用 {@link SecurityManager#checkPackageAccess}
     *         拒绝访问 {@code intf};</li>
     *         <li>给定的代理接口之一为非公共接口,并且调用类不在该非公共接口的同一个 {@linkplain Package 运行时包} 中,并且调用 {@link SecurityManager#checkPermission}
     *         用 {@code ReflectPermission("newProxyInPackage.{package name}")} 权限拒绝访问。</li>
     *         </ul>
     * @throws NullPointerException 如果代理接口数组参数或其任何元素为 {@code null},或者调用处理程序 {@code h} 为 {@code null},则抛出 {@code NullPointerException}。
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException {
        Objects.requireNonNull(h); // 确保调用处理程序不为null

        final Class<?>[] intfs = interfaces.clone(); // 克隆接口数组,以防止外部修改影响
        final SecurityManager sm = System.getSecurityManager(); // 获取当前系统中的安全 manager
        if (sm != null) {
            // 检查代理访问权限
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 查找或生成指定的代理类。
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 使用指定的调用处理程序调用代理类的构造函数。
         */
        try {
            if (sm != null) {
                // 检查创建代理实例的权限
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            // 获取代理类的构造函数,并实例化代理类
            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;
                    }
                });
            }
            // 创建代理实例
            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);
        }
    }

然后,我们需要重点关注 Class<?> cl = getProxyClass0(loader, intfs) 这句代码,这里产生了代理类,这个类就是动态代理的关键,由于是动态生成的类文件,我们将这个类文件打印到文件中。

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
String path = "/Users/mapei/Desktop/okay/65707.class";

try {
    FileOutputStream fos = new FileOutputStream(path);
    fos.write(classFile);
    fos.flush();
    System.out.println("代理类class文件写入成功");
} catch (Exception e) {
    System.out.println("写文件错误");
}

对这个 class 文件进行反编译,我们看看 jdk 为我们生成了什么样的内容:

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
  
    /**
     * 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
     * 为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
     * 被代理对象的实例,就可以去调用真正的对象实例。
    */
    public $Proxy0(InvocationHandler paramInvocationHandler) throws {
        super(paramInvocationHandler);
    }
  
    // 这个静态块本来是在最后的,我把它拿到前面来,方便描述
    static {
        try {
            // 看看这儿静态块儿里面的住 giveTask 通过反射得到的名字 m3,其他的先不管
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
 
    /**
     * 
     * 这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
     * this.h.invoke(this, m3, null);我们可以对将InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
    */
    public final void giveTask() throws {
        try {
          this.h.invoke(this, m3, null);
          return;
        } catch (Error|RuntimeException localError) {
          throw localError;
        } catch (Throwable localThrowable) {
          throw new UndeclaredThrowableException(localThrowable);
        }
    }
}

JDK 动态代理和 CGLIB 动态代理

两者的主要区别

代理方式

  • JDK 动态代理:基于接口实现,通过 java.lang.reflect.Proxy 动态生成代理类。

  • CGLIB 动态代理:基于类继承,通过字节码技术生成目标类的子类,来实现对目标方法的代理。

使用场景

  • JDK 动态代理:推荐用于代理接口的场景,适合代理的类实现了接口。

  • CGLIB 动态代理:适合没有接口的类,或需要代理具体类中的非接口方法的场景。由于基于继承实现,不能代理 final 类和 final 方法。

实现原理

JDK 动态代理

JDK 动态代理基于 Java 的反射机制,由 java.lang.reflect.Proxy 类和 InvocationHandler 接口实现。代理类在运行时生成,并调用 InvocationHandlerinvoke() 方法。

核心类和接口

  • ProxyProxy 是 Java 动态代理的核心类,所有代理对象都是该类的子类。

  • InvocationHandler 接口InvocationHandler 定义了 invoke() 方法,该方法在代理实例上调用方法时触发,并在方法前后插入增强逻辑。

实现示例

interface Service {
  void performAction();
}

class ServiceImpl implements Service {
  public void performAction() {
      System.out.println("Executing performAction");
  }
}

class ServiceInvocationHandler implements InvocationHandler {
  private Object target;

  public ServiceInvocationHandler(Object target) {
      this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("Before method execution");
      Object result = method.invoke(target, args);
      System.out.println("After method execution");
      return result;
  }
}

public class JDKProxyExample {
  public static void main(String[] args) {
      Service service = new ServiceImpl();
      Service proxy = (Service) Proxy.newProxyInstance(
              service.getClass().getClassLoader(),
              service.getClass().getInterfaces(),
              new ServiceInvocationHandler(service)
      );
      proxy.performAction();
  }
}

CGLIB 动态代理

CGLIB(Code Generation Library)通过继承目标类创建代理类,并重写方法插入切面逻辑。CGLIB 生成的代理类是目标类的子类,通过重写方法来实现代理功能。

核心类和接口

  • Enhancer:CGLIB 的核心类,用于创建代理对象。Enhancer 类允许设置代理类的父类和方法拦截器。

  • MethodInterceptor 接口MethodInterceptor 定义了 intercept() 方法,允许在方法调用前后插入增强逻辑。

实现示例

class Service {
  public void performAction() {
      System.out.println("Executing performAction");
  }
}

class ServiceMethodInterceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
      System.out.println("Before method execution");
      Object result = proxy.invokeSuper(obj, args);
      System.out.println("After method execution");
      return result;
  }
}

public class CGLIBProxyExample {
  public static void main(String[] args) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(Service.class);
      enhancer.setCallback(new ServiceMethodInterceptor());
      Service proxy = (Service) enhancer.create();
      proxy.performAction();
  }
}