设计模式——代理模式(Proxy)

1.什么是代理模式

**代理模式是通过一个代理对象控制对原对象的访问,在不修改原对象的前提下,增强或者扩展其功能。**代理对象作为中间层,可以在调用目标对象前后插入额外的逻辑(如日志、权限验证、缓存等等)。

2.为什么需要代理模式

  • 保护目标对象:隐藏目标对象细节,限制直接访问目标对象;
  • 功能增强:无侵入式地实现对目标对象的功能扩展;
  • 解耦:将核心业务逻辑非功能性需求分离(如日志);

3.代理模式的分类

代理分为静态代理、动态代理。而动态代理可以分为JDK代理以及cglib代理。其中cglib代理又经常被单独提及,因为其在实现上无需涉及到接口实现。

类型 特点 适用场景
静态代理 手动编写代理类,代理类与目标类实现同一接口 简单场景,代理类数量少时使用,需要代理的类数量太多,代理工作就变得多量而且低效了,代码冗余
动态代理 运行时动态生成代理类,无需手动编写代理类代码 复杂场景,需代理多个类或方法
JDK代理 基于接口实现,要求目标类必须实现接口 Spring默认对接口的代理
CGLIB代理 基于继承实现,可代理无接口的类 代理普通类,或目标类无接口时使用

3.1静态代理

java 复制代码
// 目标接口
interface UserService {
    void save();
}

// 目标类
class UserServiceImpl implements UserService {
    @Override
    public void save() { System.out.println("保存用户"); }
}

// 静态代理类
class UserServiceProxy implements UserService {
    private UserService target = new UserServiceImpl();
    
    @Override
    public void save() {
        //经典场景就是通过这种增强方式实现被调用类中方法的日志增强
        System.out.println("前置增强,比如输出日志");
        target.save(); // 调用目标方法
        System.out.println("后置增强");
    }
}

静态代理是既可以代理有接口的实现类(上述)、也可以代理没有实现接口的类的(通过即成的方式)。

3.2JDK动态代理

java 复制代码
public class JdkProxy implements InvocationHandler {
    private Object target;
    
    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK前置增强");
        Object result = method.invoke(target, args);
        System.out.println("JDK后置增强");
        return result;
    }
}

使用

java 复制代码
public interface ServiceDao {
    void save();
}

public class ServiceDaoImpl implements ServiceDao {
    @Override
    public void save() {
        System.out.println("数据保存成功");
    }
}



//Main.java
public class Main {
    public static void main(String[] args) {
        ServiceDao target = new ServiceDaoImpl();
        JdkProxy proxyFactory = new JdkProxy();
        //这一步强转的类型是接口,并不是具体的实现类!!!之后再调用接口的方法
        ServiceDao proxy = (ServiceDao) proxyFactory.getProxy(target);
        proxy.save(); // 正常执行代理逻辑
    }
}

JDK动态代理生成的代理对象本质上是接口的实现类 ,而非目标类(如ServiceDaoImpl)的子类。所以JDK代理只能够针对至少实现了一个接口的类进行代理,如果目标类没有实现任何接口,JDK代理无法为其生成代理对象,这时候需要使用CGLIB代理,后者通过继承目标类来生成子类代理。

如果遇上多个接口实现的类,不同接口中有相同方法签名的方法怎么办?

可以在InvocationHandler.invoke中通过Method对象区分

java 复制代码
public class UserServiceImpl implements UserService, Loggable, Serializable {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }

    @Override
    public void log(String message) {
        System.out.println("日志记录:" + message);
    }
}

//如何区分不同接口相同的方法名,进行不同处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getDeclaringClass() == UserService.class) {
        System.out.println("处理UserService方法");
    } else if (method.getDeclaringClass() == Loggable.class) {
        System.out.println("处理Loggable方法");
    }
    return method.invoke(target, args);
}

JDK动态代理只能增强接口中声明的方法。即使目标类有自己独有的非接口方法,这些方法也不会被代理,因此调用这些方法时不会触发`InvocationHandler`的`invoke`方法。用户需要明确,只有接口中定义的方法会被代理拦截和增强

总结:DK动态代理天然支持多接口代理,一个代理对象即可覆盖所有接口。同时其局限性,即必须基于接口,只能增强接口方法,而无法处理类自身的方法,这也是为什么CGLIB代理在需要代理普通类时被推荐使用的原因。

3.3 cglib代理

那么针对没有实现接口的类,我们可以通过cglib代理(子类代理)进行代理实现。

对比点 JDK代理 CGLIB代理
实现方式 基于接口 基于继承
依赖条件 目标类必须实现接口 目标类不能是final类,方法不能是final
性能 生成代理快,但反射调用慢 生成代理慢,但方法调用快(FastClass机制)
代理对象类型 实现目标接口的代理类 目标类的子类

通过继承的方式实现代理,可以想象final类是无法继承,final方法也是无法继承的,故此这一类的类和方法都无法通过cglib代理。

java 复制代码
public class CglibProxy implements MethodInterceptor {
    public Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz); // 设置父类
        enhancer.setCallback(this);    // 设置回调
        return enhancer.create();      // 生成代理对象
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB前置增强");
        Object result = proxy.invokeSuper(obj, args); // 调用父类方法
        System.out.println("CGLIB后置增强");
        return result;
    }
}

//Main.java
public class Main {
    public static void main(String[] args) {
        CGLIBProxy proxy = new CGLIBProxy();
        UserService proxy1 = (UserService) proxy.getProxy(UserServiceImpl.class);
        proxy1.save();
    }
}

//输出
CGLIB前置增强
保存用户
CGLIB后置增强

总结:掌握代理模式的核心在于理解"控制访问"和"功能增强"的本质,区分静态代理与动态代理的适用场景,并能在实际开发中灵活选择JDK代理或CGLIB代理。通过代理模式,可以实现代码解耦、功能扩展,同时提升系统的可维护性。

参考文献:

代理模式 | 菜鸟教程

......

相关推荐
黄明基3 小时前
设计模式学习:2、状态模式实现订单状态流转
设计模式·.net·dotnet core
JuicyActiveGilbert4 小时前
【C++设计模式】第一篇:单例模式(Singleton)
c++·单例模式·设计模式
小马爱打代码4 小时前
设计模式详解(单例模式)
java·单例模式·设计模式
web安全工具库4 小时前
深入理解设计模式中的单例模式(Singleton Pattern)
单例模式·设计模式
码熔burning5 小时前
(二 十 三)趣学设计模式 之 解释器模式!
java·设计模式·解释器模式
Nita.6 小时前
设计模式|策略模式 Strategy Pattern 详解
设计模式·c#·策略模式
攻城狮7号6 小时前
【第15节】C++设计模式(行为模式)-State(状态)模式
c++·设计模式·状态模式
JuicyActiveGilbert7 小时前
【C++设计模式】第五篇:原型模式(Prototype)
c++·设计模式·原型模式
画个逗号给明天"7 小时前
C++设计模式之单例模式
单例模式·设计模式