专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java代理模式详解:静态代理与动态代理的原理、实现与最佳实践

代理模式:为另一个对象提供一个替身或占位符,以便于控制对这个对象的访问

静态代理:

就是将一些在方法中重复的功能提取出来,通过一个专门的类去封装,在具体类中需要的时候就用那个专门的类的对象去调用

动态代理:

原理和静态代理差不多,只是用了一个反射的接口,去调用一些方法方便进行动态调度

动态代理的步骤就是:

先把代理类实例化,再实例化被代理类,将被代理类的对象传到代理类的方法中,因为是Object类型,所以需要转型再输出那个方法

img_1

img_2

静态代理

抽象角色:一般会使用接口或抽象类

package com.carl.agent;
/**
 * @Version 1.0.0
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 租房
 */
public interface RentHouse {
    /**
     * 租房流程
     */
    void rent();
}

真实角色:被代理的角色

package com.carl.agent;
/**
 * @Version 1.0.0
 *
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 房东--要租房
 */
public class Landlord implements RentHouse {
    @Override
    public void rent() {
        System.out.println("三室一厅,独厨独卫,家具齐全!价格面议");
    }
}

代理角色:代理真实角色,一般会做一些附属操作

package com.carl.agent;
/**
 * @Version 1.0.0
 *
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 静态代理
 */
public class StaticProxy {
    private RentHouse rentHouse;
    public StaticProxy(){
    }
    public StaticProxy(RentHouse rentHouse){
        this.rentHouse = rentHouse;
    }
    public void rent(){
        rentHouse.rent();
        System.out.println("中介费用为房价的一半!");
    }
}

使用者:调用代理角色

@Test
public void testStaticAgent(){
    //租房直接找房东
    RentHouse rentHouse=new Landlord();
    //办理租房流程
    rentHouse.rent();
    //找中介租房子
    StaticProxy staticProxy=new StaticProxy(rentHouse);
    //中介给你办理租房流程
    staticProxy.rent();
}

代理的好处

  • 可以使真实角色的操作更加纯粹,不用关注一些公共的业务
  • 公共的业务交给代理角色,实现业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量增加,开发效率变低

例如:

UserService作为抽象角色,UserServiceImpl作为一个真实角色,需要每次访问该业务类中的方式的时候,都要做权限判断,判断权限是否满足要求

这个时候使用代理角色,调用方法时,都统一进行权限管理,这样权限判断的业务和查询User表的业务就分离了,互不影响。当UserServiceImpl增加一些业务,也可以通过代理,增加权限管理的业务,统一集中管理

动态代理

动态代理和静态代理的角色一致,只是通过反射机制,让代理变成了动态

动态代理的两大类【Spring的AOP同时支持JDK和CGLib动态代理,具体使用哪种代理取决于目标对象的实现与配置】

  • 基于接口的动态代理(JDK动态代理==SpringBoot 2.x默认)
  • 基于类的动态代理(CGlib动态代理==SpringBoot 3.x默认)

JDK动态代理

通过java.lang.reflect.Proxy类生成代理对象,要求被代理类必须实现至少一个接口。代理对象会实现与原类相同的接口。

// 接口
public interface PaymentService {
    void pay(int amount);
}
// 实现类
public class AlipayService implements PaymentService {
    @Override
    public void pay(int amount) {
        System.out.println("支付宝支付: " + amount + "元");
    }
}
//-----以上都是正常接口实现------
//-----以下则是实现动态代理的地方 ------
// InvocationHandler实现
public class SecurityHandler implements InvocationHandler {
    private final Object target;
    public SecurityHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[安全校验] 开始验证身份...");
        Object result = method.invoke(target, args);
        System.out.println("[安全校验] 交易完成记录日志");
        return result;
    }
}
// 使用代理
public class JdkProxyDemo {
    public static void main(String[] args) {
        PaymentService realService = new AlipayService();
        PaymentService proxy = (PaymentService) Proxy.newProxyInstance(
                PaymentService.class.getClassLoader(),
                new Class[]{PaymentService.class},
                new SecurityHandler(realService)
        );
        proxy.pay(100); // 代理方法调用
    }
}

执行结果

[安全校验] 开始验证身份...
支付宝支付: 100元
[安全校验] 交易完成记录日志

Proxy类

Proxy提供了创建动态代理类和实例的静态方法(newProxyInstance())

常用方法

  • getInvocationHandler(Object proxy):返回指定代理实例的调用处理程序
  • getProxyClass(ClassLoader loader,类<?>…interfaces):给出类加载器和接口的代理类
  • isProxyClass():如果当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成代理类时,返回true
  • newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvocationHandler h):返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序

JDK提供了两种方式获取动态代理类的实例:

InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler); 
/**
 * 获取代理
 * @return {@link Object }
 */
public Object getProxy() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    /*
     * 第一个参数:this.getClass().getClassLoader(),使用handler对象的classLoader加载代理对象
     * 第二个参数:handler.getClass().getInterfaces(),为代理类提供真实对象的接口,便于调用接口中的所有方法
     * 第三个参数:this,InvocationHandler对象
     */
    return Proxy.newProxyInstance(this.getClass().getClassLoader(), handler.getClass().getInterfaces(),this);
}

InvocationHandler类

InvocationHandler类是被代理实例调用处理程序的接口【代理逻辑处理器,实现invoke()方法增强逻辑】

Object invoke(Object proxy, Method method, Object[] args):通过反射获取被代理类的示例

  • proxy:代理类代理的真实对象
  • method:通过反射获取到的需要调用某个对象的方法
  • args:指代代理对象方法的传递参数

在invoke对象中,调用被代理对象的处理程序,并可以进行增强,具体实现如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = method.invoke(handler, args);
    return result;
}

实现步骤

抽象被代理角色

package com.carl.proxy.dynamic;
/**
 * @Version 1.0.0
 *
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 租房
 */
public interface RentHouse {
    /**
     * 租金
     */
    void rent();
}

真实被代理角色

package com.carl.proxy.dynamic;
/**
 * @Version 1.0.0
 *
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 房东--要租房
 */
public class Landlord implements RentHouse {
    @Override
    public void rent() {
        System.out.println("三室一厅,独厨独卫,家具齐全!价格面议");
    }
}

代理角色

package com.carl.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @Version 1.0.0
 * @see InvocationHandler
 * @author carl蔡先生
 * @Date 2022/10/03
 * @Description 动态代理--必须实现InvocationHandler接口
 */
public class DynamicProxy implements InvocationHandler {
        /**
         * 被代理对象
         */
        private RentHouse rentHouse;
        public void setRentHouse(RentHouse rentHouse) {
                this.rentHouse = rentHouse;
        }
        /**
         * 被代理对象的处理程序
         * @param proxy 代理对象
         * @param method 方法
         * @param args 参数
         * @throws Throwable 抛出最高异常
         * @return {@link Object }
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //前后增加操作
                lookHouse();
                Object result = method.invoke(rentHouse, args);
                fare();
                return result;
        }
        /**
         * 获取代理类
         * @return {@link Object }
         */
        public Object getProxy(){
                return Proxy.newProxyInstance(this.getClass().getClassLoader(), rentHouse.getClass().getInterfaces(),this);
        }
        public void lookHouse(){
                System.out.println("中介带看房源!");
        }
        public void fare(){
                System.out.println("中介收中介费!");
        }
}
package com.carl.proxy.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @Version 1.0.0
 * @see InvocationHandler
 * @author carl蔡先生
 * @Date 2022/10/04
 * @Description 通用代理调用处理程序
 */
public class ProxyInvocationHandler implements InvocationHandler {
    /**
     * @see Object
     * 被代理对象
     */
    private Object handler;
    public ProxyInvocationHandler() {
    }
    public ProxyInvocationHandler(Object handler) {
        this.handler = handler;
    }
    /**
     * 获取代理
     * @return {@link Object }
     */
    public Object getProxy() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        /*
         * 第一个参数:this.getClass().getClassLoader(),使用handler对象的classLoader加载代理对象
         * 第二个参数:handler.getClass().getInterfaces(),为代理类提供真实对象的接口,便于调用接口中的所有方法
         * 第三个参数:this,InvocationHandler对象
         */
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), handler.getClass().getInterfaces(),this);
    }
    /**
     * @param proxy 代理类
     * @param method 方法
     * @param args 参数
     * @throws Throwable 抛出异常
     * @return {@link Object }
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(handler, args);
        return result;
    }
}

CGLib动态代理

通过操作字节码生成被代理类的子类,不需要实现接口。需要引入第三方库CGLib

1、 引入maven依赖

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

2、 实现被代理类

// 被代理类(无需实现接口)
    public class WechatPayService {
        public void pay(int amount) {
            System.out.println("微信支付: " + amount + "元");
        }
    }

3、 实现MethodInterceptor接口

// MethodInterceptor实现
    public class LogInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, 
                               MethodProxy proxy) throws Throwable {
            System.out.println("[日志] 方法调用: " + method.getName());
            Object result = proxy.invokeSuper(obj, args); // 调用父类方法
            System.out.println("[日志] 方法执行完成");
            return result;
        }
    }

测试

// 使用代理
public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(WechatPayService.class); // 设置父类
        enhancer.setCallback(new LogInterceptor());     // 设置回调
        WechatPayService proxy = (WechatPayService) enhancer.create();
        proxy.pay(200); // 代理方法调用
    }
}

执行结果:

[日志] 方法调用: pay
微信支付: 200元
[日志] 方法执行完成

注意事项:

  • CGLib无法代理final修饰的最终类
  • CGlib需要默认构造函数【空参构造器】
  • 匿名类隐式依赖外部类实例,导致CGLib无法生成代理
new ByteBuddy()
      .subclass(type)
      .method(any()).intercept(MethodDelegation.to(new Object() {
         @RuntimeType
         public Object intercept(@SuperCall Callable<?> c, 
                                 @Origin Method m, 
                                 @AllArguments Object[] a) throws Exception {
           // implement your interception logic
         }
       }).make();
  • 指定构造器参数。enhancer.create(new Class<?>[] {type.getEnclosingClass()}, new Object[] {null})传递 null 作为外部类实例(需确保逻辑安全)

    该用其他字节码库【如:ByteBuddy】

  • CGLib存在首次生成代理类时加载过慢问题【需要操作字节码,可以在启动阶段提前加载代理类】

  • MethodProxy.invokeSuper()代替Method.invoke()。可以减少反射开销。

总结

针对一些开发场景,如何选择使用JDK动态代理还是CGLib动态代理?

1、 优先考虑JDK动态代理,确保符合面向接口编程原则
2、 实在是无需使用接口的使用CGLib
3、 高频调用的方法建议使用CGLib【性能要求高的场景】
4、 注意CGLib的类初始化问题

未经允许不得转载:搜云库技术团队 » Java代理模式详解:静态代理与动态代理的原理、实现与最佳实践

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们