1、引言

在Java编程中,代理模式是一种常见的设计模式,用于在不修改原始代码的情况下,为对象添加额外的功能。代理模式有两种主要类型:静态代理和动态代理。本文将全面探讨这两种代理模式,包括它们的基本概念、实现方式、应用场景及其优缺点。

2、静态代理

静态代理是在编译时确定代理类的。代理类和目标类都需要实现相同的接口,代理类持有对目标对象的引用,并在调用目标对象的方法前后插入额外的逻辑。

先给大家看一张基础设计代理的原理图

在这里插入图片描述

2.1、静态代理的示例

定义一个接口

public interface ISubject{
    void eat();
}

实际类

public class RealSubject implements ISubject{
    @Override
    public void eat() {
        System.out.println("RealSubject do eat...");
    }
}

代理对象

public class ProxyObject implements ISubject{

    private ISubject iSubject;

    public ProxyObject(ISubject iSubject) {
        this.iSubject = iSubject;
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    @Override
    public void eat() {
        preSomething();
        iSubject.eat();
        afterSomething();
    }
}

工厂类

public class ObjectFactory {

    public static ISubject getInstance(){
        return new ProxyObject(new RealSubject());
    }
    
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testProxy();
    }

    public static void testProxy(){
        ISubject iSubject = ObjectFactory.getInstance();
        iSubject.eat();
    }
}

上面这个是最简单的例子,我们一般在使用的代理模式的时候,我们只关注代理是谁,真实对象是谁即可,如何根据这两个参数(也就是类的全限定名称)进行代理呢?我们可以使用反射,下来我们重构一下工厂类和main方法,示例代码如下:

public class ObjectFactory { 

    public ObjectFactory() {}

    /**
     * 通过类名字符串创建并返回指定类型的实例
     * 
     * @param className 要创建的类的全限定名字符串
     * @return 创建的实例
     * @throws RuntimeException 如果指定的类不存在,或者实例化过程中出现错误,则抛出运行时异常
     */
    public static <T> T getInstance(String className){
        T t = null;
        try {
            // 通过反射机制创建指定类的实例
            t =  (T) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            // 如果类无法被实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果类的构造方法不可访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果类不存在,则抛出运行时异常
            throw new RuntimeException(e);
        }
        return t;
    }

    /**
     * 根据代理名和真实对象名创建并返回一个代理对象实例
     * 
     * @param proxyName 代理类的全限定名,用于动态加载和实例化代理对象
     * @param realName 真实对象类的全限定名,用于获取真实对象的实例
     * @param <T> 泛型标记,表示返回的代理对象类型
     * @return T 返回一个实现了真实对象接口的代理对象实例
     * @throws RuntimeException 如果在实例化代理对象过程中发生错误,则抛出运行时异常
     * 
     * 此方法主要用于在运行时动态创建代理对象,通过反射机制加载并实例化指定的代理类
     * 代理类需要通过构造方法接收真实对象作为参数,以便代理对象能够调用真实对象的方法
     */
    public static <T> T getInstance(String proxyName, String realName){
        // 初始化代理对象为null
        T t = null;
        // 获取真实对象的实例
        T obj = getInstance(realName);
        // 尝试通过反射机制实例化代理对象
        try {
            // 使用代理类名和接口类型来创建代理对象实例
            t = (T) Class.forName(proxyName).getConstructor(obj.getClass().getInterfaces()[0]).newInstance(obj);
        } catch (InstantiationException e) {
            // 如果代理类无法实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果构造方法非法访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            // 如果调用构造方法时目标方法抛出异常,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            // 如果未找到符合要求的构造方法,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果未找到代理类,则抛出运行时异常
            throw new RuntimeException(e);
        }
        // 返回创建的代理对象实例
        return t;
    }
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testReflect();
    }

    public static void testReflect(){
        ISubject iSubject = ObjectFactory.getInstance("com.hy.proxy.ProxyObject", "com.hy.proxy.RealSubject");
        iSubject.eat();
    }
    
}

2.2 静态代理的优缺点

优点:

  1. 代码清晰,易于理解。
  2. 适合于需要明确代理逻辑的场景。

缺点:

  1. 每个代理都需要一个对应的目标类,代码冗余。
  2. 不够灵活,代理类和目标类的关系在编译时确定。

3、动态代理

动态代理是在运行时创建代理对象的,能够在运行时决定具体的代理逻辑。Java的动态代理分为两种主要实现方式:

  • JDK动态代理
  • CGLIB动态代理

在这里插入图片描述

对于动态代理,代理对象可以代理一个类中的多个实现方法,即一个真实类实现多个接口,一个代理类就可以完全代理

3.1、JDK动态代理

JDK动态代理基于反射机制,可以在运行时创建代理对象。要使用JDK动态代理,目标类必须实现一个或多个接口。通过 Proxy 类和 InvocationHandler 接口,可以创建代理对象,并在调用方法时插入额外的逻辑。

3.1.1、JDK动态代理示例代码

接口

public interface ITest {
    void play();
}

public interface ISubject{
    void eat();
}

真实类

public class DynamicRealObject implements ISubject, ITest{
    @Override
    public void eat() {
        System.out.println("DynamicRealObject eat...");
    }

    @Override
    public void play() {
        System.out.println("DynamicRealObject play...");
    }
    
}

动态代理类

public class DynamicProxy implements InvocationHandler {

    private Object target;

    /**
     * 绑定目标对象并生成代理对象
     * 本方法主要用于通过动态代理绑定一个目标对象,并返回该目标对象的代理实例
     * 
     * @param target 目标对象,即需要通过代理的对象
     * @return 返回目标对象的代理实例,该实例可以用于方法拦截和调用
     */
    public Object bind(Object target) {
        // 绑定目标对象到当前代理实例
        this.target = target;
        // 利用Java动态代理机制创建并返回目标对象的代理实例
        // 该代理实例将使用目标对象的类加载器,实现目标对象所实现的所有接口,并将当前代理实例作为调用处理程序
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    /**
     * 该方法通过Java动态代理机制,拦截调用目标方法的请求
     * 在调用目标方法之前和之后执行特定操作,实现环绕通知的功能
     * 这种方式可以用于实现事务管理、日志记录、权限控制等横切关注点
     *
     * @param proxy 代理对象,通常不用直接操作
     * @param method 被调用的目标方法的信息
     * @param args 调用目标方法时传递的参数
     * @return 目标方法的返回值
     * @throws Throwable 目标方法抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法之前执行的操作,如开启事务、日志记录等
        preSomething();
        // 调用目标方法,并传递参数
        Object obj = method.invoke(target, args);
        // 在调用目标方法之后执行的操作,如关闭事务、日志记录等
        afterSomething();
        // 返回目标方法的执行结果
        return obj;
    }
    
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testDynamicProxy();
    }

    public static void testDynamicProxy(){
    	// 结合静态代理的反射,可以这样写
    	// ISubject iSubject = (ISubject) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
    	// ITest iTest = (ITest) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
        ISubject iSubject = (ISubject) new DynamicProxy().bind(new DynamicRealObject());
        iSubject.eat();
        ITest iTest = (ITest) new DynamicProxy().bind(new DynamicRealObject());
        iTest.play();
    }
    
}

3.2、CGLIB动态代理类

CGLIB(Code Generation Library)是一个功能强大的代码生成库,允许在运行时动态创建代理类。与JDK动态代理不同,CGLIB不要求目标类实现接口,而是通过继承目标类来创建代理。

3.2.1 CGLIB动态代理示例代码

目标类

public class CglibProxy {

    public void doSomething(){
        System.out.println("Cglib do something");
    }
    
}

代理类

public class CglibInterceptor implements MethodInterceptor {

    private final Object target;

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

    private void preDoSomething() {
        System.out.println("preDoSomething......");
    }

    private void afterDoSomething() {
        System.out.println("afterDoSomething......");
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        preDoSomething();
        Object obj = method.invoke(target, objects);
        afterDoSomething();
        return obj;
    }
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testCglib();
    }

    /**
     * 使用CGLIB进行动态代理的测试方法
     * 该方法通过CGLIB框架创建一个CglibProxy的子类实例,以实现方法的拦截和调用
     */
    public static void testCglib(){
        // 创建CGLIB增强器实例
        Enhancer enhancer = new Enhancer();
        // 设置代理类的父类为CglibProxy类,这样CGLIB会生成该类的子类
        enhancer.setSuperclass(CglibProxy.class);
        // 设置拦截器,传入CglibProxy实例作为拦截器的关键信息
        enhancer.setCallback(new CglibInterceptor(new CglibProxy()));
        // 通过增强器创建代理类实例
        CglibProxy cglibProxy = (CglibProxy) enhancer.create();
        // 通过代理实例调用目标方法,此调用将被CglibInterceptor拦截并处理
        cglibProxy.doSomething();
    }
}

3.3、动态代理的优缺点

优点:

  1. 灵活性高,可以在运行时创建代理对象。
  2. 不需要修改目标类代码。

缺点:

  1. JDK动态代理只能代理实现了接口的类,不能代理具体类。
  2. CGLIB动态代理在生成代理类时会继承目标类,这可能导致一些问题,如不能代理final类或方法。

3.4 CGLIB和JDK动态代理区别

具体区别如下:

  1. 实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对象在运行时动态创建,通过实现目标类接口的方式来代理目标类。 CGLIB代理则是基于ASM字节码框架实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。

  2. 性能表现: JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广泛,适用于大多数情况下的代理需求。 CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较高,但是它不能代理final类和final方法,以及一些无法生成子类的类。

  3. 应用场景: JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。 CGLIB代理适用于代理类的场景,例如Spring中的AOP切面编程等。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部