【设计模式】代理模式

一、什么是代理模式

概念:

  • 代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。

分类:

  1. 静态代理:在编译时就已经确定了代理对象和真实对象的关系,代理对象和真实对象实现相同的接口或继承相同的父类。在代理对象中,对真实对象的方法进行封装,在方法调用前后可以添加额外的逻辑。
  2. 动态代理:在运行时生成代理对象,不需要事先确定代理对象和真实对象的关系。常用的动态代理技术有JDK动态代理和CGLIB动态代理。
    • JDK动态代理:通过使用Java的反射机制,动态生成代理对象。代理对象必须实现至少一个接口,通过Proxy类和InvocationHandler接口实现代理对象的创建和方法调用的处理。
    • CGLIB动态代理:通过继承的方式,动态生成代理对象。代理对象无需实现接口,通过Enhancer类和MethodInterceptor接口实现代理对象的创建和方法调用的处理。

代理模式的角色组成:

  1. 抽象主题(Subject):抽象主题定义了真实主题和代理共同的接口,它是客户端和代理对象之间的约束。

  2. 真实主题(Real Subject):真实主题是实际执行业务逻辑的对象,它实现了抽象主题的接口。

  3. 代理(Proxy):代理是对真实主题的访问控制和管理对象。代理实现了抽象主题的接口,并在其内部持有一个真实主题的引用。代理对象可以在访问真实主题之前进行一些额外的处理,例如权限验证、缓存等。

代理模式中的客户端通过与代理对象进行交互,而代理对象负责将请求转发给真实主题对象。客户端无需直接与真实主题对象打交道,而是通过代理对象来进行间接访问。这样做的好处是可以在代理对象中添加一些额外的功能,同时也可以对真实主题对象进行访问控制,提高了系统的灵活性和安全性。

二、代理模式的Java实现示例

1、静态代理的实现

首先定义一个接口 Subject,包含一个方法 request()

java 复制代码
public interface Subject {
    void request();
}

然后创建一个真实的对象 RealSubject,实现 Subject 接口:

java 复制代码
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真实的对象处理请求");
    }
}

接着创建一个代理对象 ProxySubject,同样实现 Subject 接口,并持有一个真实对象的引用:

java 复制代码
public class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("代理对象处理请求之前的操作");
        realSubject.request();
        System.out.println("代理对象处理请求之后的操作");
    }
}

最后,可以通过以下方式使用代理对象:

java 复制代码
public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

运行上述代码可以得到以下输出:

代理对象处理请求之前的操作
真实的对象处理请求
代理对象处理请求之后的操作

在这个示例中,ProxySubject 是代理对象,通过持有一个 RealSubject 对象的引用,来代理真实对象的方法调用。在代理对象的 request() 方法中,可以在真实对象的方法调用前后添加一些额外的操作,从而实现了静态代理。

2、jDK动态代理的实现

在Java中,动态代理可以使用 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口来实现。下面是一个简单的动态代理的示例代码:

首先定义一个接口 Subject,包含一个方法 request()

java 复制代码
public interface Subject {
    void request();
}

然后创建一个真实的对象 RealSubject,实现 Subject 接口:

java 复制代码
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真实的对象处理请求");
    }
}

接着创建一个实现 InvocationHandler 接口的代理处理器 ProxyHandler

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxyHandler implements InvocationHandler {
    private Object target;

    public ProxyHandler(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;
    }
}

最后,可以通过以下方式使用动态代理对象:

java 复制代码
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxyHandler proxyHandler = new ProxyHandler(realSubject);
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                RealSubject.class.getInterfaces(),
                proxyHandler);
        proxySubject.request();
    }
}

运行上述代码可以得到以下输出:

代理对象处理请求之前的操作
真实的对象处理请求
代理对象处理请求之后的操作

在这个示例中,ProxyHandler 是代理处理器,通过实现 InvocationHandler 接口,可以在代理对象的方法调用前后添加一些额外的操作。在 main() 方法中,使用 Proxy 类的 newProxyInstance() 方法动态创建一个代理对象,并将代理处理器传入。然后通过代理对象调用 request() 方法,实际上是调用了代理处理器的 invoke() 方法,在该方法中可以添加额外的操作,并通过反射调用真实对象的实际方法。

注意,动态代理要求目标类必须实现一个接口,因为动态代理是基于接口来创建代理对象的。

3、CGLIB动态代理的实现

CGLIB(Code Generation Library)是一个用于生成Java类字节码的开源库,它可以在运行时动态生成一个指定类的子类作为代理类。CGLIB动态代理主要用于代理那些没有实现接口的类。

下面是CGLIB动态代理的一个简单示例:

首先,我们需要引入cglib库的依赖,例如使用Maven:

xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

然后,创建一个目标类 UserDao

java 复制代码
public class UserDao {
    public void save() {
        System.out.println("保存用户信息");
    }
}

接下来,创建一个代理类 CglibProxy

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    private Object target;

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

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("代理前的操作");
        Object result = proxy.invoke(target, args);
        System.out.println("代理后的操作");
        return result;
    }
}

最后,我们可以使用代理类来生成代理对象并调用目标类的方法:

java 复制代码
public class Main {
    public static void main(String[] args) {
        UserDao userDao = new UserDao();
        CglibProxy cglibProxy = new CglibProxy(userDao);
        UserDao proxy = (UserDao) cglibProxy.getProxy();
        proxy.save();
    }
}

运行程序,输出结果为:

代理前的操作
保存用户信息
代理后的操作

可以看到,CGLIB动态代理通过创建目标类的子类,实现了对目标类方法的代理,同时在代理前后进行了一些额外的操作。

三、动态带来和静态代理的区别

动态代理和静态代理都是实现代理模式的方式,它们之间的主要区别在于代理对象的生成时机和方式。

  1. 代理对象的生成时机:

    • 静态代理需要在编译时期就创建代理对象,即在程序运行之前就已经存在代理类的字节码文件。代理类一般通过手工编写代码来创建。
    • 动态代理是在运行时期动态生成代理对象。代理类是根据目标类和代理处理器的信息,在程序运行时动态生成的。
  2. 代理对象的生成方式:

    • 静态代理需要为每个目标类手动编写一个代理类,将目标类的方法委托给代理类来进行处理。
    • 动态代理通过使用 Proxy 类和 InvocationHandler 接口,在运行时动态生成代理对象。代理对象在运行时动态地实现了目标接口,并将方法的调用委托给代理处理器。
  3. 灵活性和扩展性:

    • 动态代理相比静态代理更加灵活,可以代理任意实现了接口的目标类,无需为每个目标类都编写一个代理类。
    • 静态代理相比动态代理的扩展性较差,每次新增或修改目标类时,都需要手动修改代理类。

总体来说,动态代理相比静态代理具有更高的灵活性和扩展性,但代理对象的生成过程相对复杂一些。静态代理则相对简单,代理对象的生成在编译时期就已经完成。选择使用哪种代理方式取决于具体的需求和场景。

四、代理模式的应用场景

  1. 远程代理:用于在不同的地址空间中访问远程对象。

    例如,调用远程服务器上的WebService接口时,可以使用代理模式来隐藏网络通信的细节。

  2. 虚拟代理:用于延迟加载对象,提高系统性能。

    例如,当需要加载大量图片时,可以使用虚拟代理来在显示图片时才真正加载和显示,而不是一次性加载所有图片。

  3. 安全代理:用于控制对对象的访问权限。

    例如,对于某些敏感操作或数据,可以使用安全代理来进行权限验证,确保只有授权用户才能访问。

  4. 缓存代理:用于缓存对象的结果,避免重复执行开销较大的操作。

    例如,对于频繁访问数据库的操作,可以使用缓存代理来缓存查询结果,减少对数据库的访问。

  5. 日志记录代理:用于在调用对象方法前后进行日志记录,以便进行调试或性能分析。

    例如,可以使用日志记录代理来记录每个方法的调用时间和参数。

  6. 计数代理:用于对对象的方法调用进行计数,可以用于统计方法的调用次数等。

    例如,可以使用计数代理来统计某个方法被调用的次数,以便进行性能分析或优化。

总的来说,代理模式适用于需要在不改变客户端代码的情况下增加额外功能或控制对对象的访问的场景。它可以提供更加灵活、安全和高效的访问方式。

五、SpringBoot中代理模式的实现

在Spring Boot中,代理模式的实现主要通过Spring框架提供的AOP(面向切面编程)功能实现。Spring AOP基于动态代理技术,可以在运行时生成代理对象,并在代理对象的方法执行前后插入额外的逻辑。

以下是使用Spring Boot实现代理模式的示例代码:

  1. 定义一个接口(被代理对象):
java 复制代码
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}
  1. 创建一个实现该接口的目标对象:
java 复制代码
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户:" + username);
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户:" + username);
    }
}
  1. 创建一个切面类,实现对目标对象方法的增强逻辑:
java 复制代码
@Component
@Aspect
public class LogAspect {
    @Before("execution(* com.example.UserService.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("调用方法:" + methodName);
    }
  
    @AfterReturning("execution(* com.example.UserService.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法调用结束:" + methodName);
    }
}
  1. 在Spring Boot的配置类中启用AOP功能:
java 复制代码
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // 其他配置...
}
  1. 通过Spring的依赖注入,使用代理对象调用方法:
java 复制代码
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping("/users")
    public void addUser(@RequestParam String username) {
        userService.addUser(username);
    }
    
    @DeleteMapping("/users/{username}")
    public void deleteUser(@PathVariable String username) {
        userService.deleteUser(username);
    }
}

通过以上步骤,可以实现在调用UserService接口的方法前后自动插入日志记录的功能,而不需要显式在每个方法中添加日志代码。这就是使用Spring Boot实现代理模式的方式之一。

相关推荐
Str_Null8 分钟前
Seatunnel运行时报错Caused by: java.lang.NoClassDefFoundError: com/mysql/cj/MysqlType
java·seatunnel
麻花201321 分钟前
WPF里面的C1FlexGrid表格控件添加RadioButton单选
java·服务器·前端
理想不理想v35 分钟前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
请叫我青哥38 分钟前
第五十二条:谨慎使用重载
java·spring
疯一样的码农1 小时前
Apache Maven简介
java·maven·apache
小安同学iter1 小时前
Java进阶五 -IO流
java·开发语言·intellij-idea
尽兴-1 小时前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
书埋不住我2 小时前
java第三章
java·开发语言·servlet
boy快快长大2 小时前
将大模型生成数据存入Excel,并用增量的方式存入Excel
java·数据库·excel
孟秋与你2 小时前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式