深入理解设计模式-结构型之代理模式

Spring中的静态和动态代理
Spring动态代理详解
AOP编程实现原理

这里对动态代理实现进行补充。

静态代理模式和动态代理模式区别

静态代理模式和动态代理模式都属于设计模式中的代理模式,它们用于在不改变原始对象的情况下控制对对象的访问。然而,它们在实现方式和适用场景上有一些区别。

静态代理模式:

  • 在静态代理模式中,代理类是在编译时期就已经存在的,代理类和原始类都是事先定义好的。代理类负责将对原始对象的方法调用委托给原始对象(代理类负责将客户端的调用转发给原始对象,但在转发之前和之后,它可以执行一些额外的逻辑实现对原始对象行为的控制和增强。这就是代理模式的核心思想。),同时可以在调用前后添加一些额外的逻辑。

优点:

  • 编译时生成,性能相对较高。
  • 可以在代理类中加入额外的逻辑,如日志记录、权限控制等。

缺点:

  • 静态代理需要为每个被代理的类创建一个代理类,导致类的数量增加。

动态代理模式:

  • 动态代理模式是在运行时生成代理类的方式。Java中的动态代理是通过反射机制在运行时创建代理类,无需为每个被代理的类都创建一个代理类。
  • 动态代理主要涉及两个重要接口:InvocationHandler 和 Proxy。
  • InvocationHandler 是一个接口,代理类实现这个接口来实现自定义的代理逻辑。
  • Proxy 是一个类,提供了创建动态代理对象的静态方法。动态代理适用于需要在运行时为多个类生成代理的情况,如 AOP(面向切面编程)等。

优点:

  • 动态代理减少了代理类的数量,可以为多个类生成代理。更加灵活,可以在运行时动态添加和修改代理逻辑。

缺点:

  • 由于是在运行时生成代理类,相对于静态代理,性能略有下降。

区别总结:

  • 静态代理是在编译时期就已经存在的代理类,而动态代理是在运行时生成的代理类。
  • 静态代理需要为每个被代理的类创建一个代理类,动态代理可以为多个类生成代理。
  • 静态代理性能相对较高,动态代理更加灵活。
  • 动态代理适用于需要动态地添加、修改代理逻辑的情况,例如 AOP。

1、基于 JDK 的动态代理实现步骤

基于 JDK 的动态代理需要使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。我们依旧使用缓存代理的案例来实现,具体步骤如下:
(1)定义一个接口,声明需要代理的方法:

public interface DataQuery {
    String query(String queryKey);
    String queryAll(String queryKey);
}

(2)创建一个被代理类,实现这个接口,并在其中定义实现方法:

public class DatabaseDataQuery implements DataQuery {
    @Override
    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "result";
   }
   
    @Override
    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
   }
}

(3)创建一个代理类,实现 InvocationHandler 接口,并在其中定义一个被代理类的对象作为属性。

public class CacheInvocationHandler implements InvocationHandler {
    private HashMap<String,String> cache = new LinkedHashMap<>(256);
    private DataQuery databaseDataQuery;
    public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {
        this.databaseDataQuery = databaseDataQuery;
   }
    public CacheInvocationHandler() {
        this.databaseDataQuery = new DatabaseDataQuery();
   }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws 

Throwable {
        // 1、判断是哪一个方法

        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回

            result = cache.get(args[0].toString());
            if(result != null){
            System.out.println("数据从缓存重获取。");
                return result;
           }
            // 3、未命中,查数据库(需要代理实例)

            result = (String) method.invoke(databaseDataQuery, args);
            // 4、如果查询到了,进行呢缓存

            cache.put(args[0].toString(),result);
            return result;
       }
        // 当其他的方法被调用,不希望被干预,直接调用原生的方法

        return method.invoke(databaseDataQuery,args);
   }
}

在代理类中,我们实现了 InvocationHandler 接口,并在其中定义了一个被代理类的对象作为属性。在 invoke 方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。

(4)在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Proxy.newProxyInstance 方法生成代理对象。

public class Main {
    public static void main(String[] args) {
        // jdk提供的代理实现,主要是使用Proxy类来完成

        // 1、classLoader:被代理类的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 2、代理类需要实现的接口数组
        Class[] interfaces = new Class[]{DataQuery.class};
        // 3、InvocationHandler
        InvocationHandler invocationHandler = new CacheInvocationHandler();
        //代理对象
        DataQuery dataQuery = (DataQuery)Proxy.newProxyInstance(
                classLoader, interfaces, invocationHandler
       );
        // 事实上调用query方法的使用,他是调用了invoke
        String result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key2");
        System.out.println(result);
        System.out.println("++++++++++++++++++++++++++++++++++++");
        // 事实上调用queryAll方法的使用,他是调用了invoke
        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key2");
        System.out.println(result);
        System.out.println("--------------------");
   }
}

在这个示例中,我们使用 Proxy.newProxyInstance 方法生成代理对象,并将代理对象转换成 **DataQuery 接口类型,以便调用其代理的方法。**在代理对象调用方法时,会调用 CacheInvocationHandler 类中的 invoke 方法,实现对被代理对象的方法的增强。

2、基于 CGLIB 的动态代理实现步骤

基于 CGLIB 的动态代理需要使用 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口。具体步骤如下:
(1)创建一个被代理类,定义需要被代理的方法。

public class DatabaseDataQuery {
    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢

        System.out.println("正在从数据库查询数据");
        return "result";
   }
    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
   }
}

(2)创建一个方法拦截器类,实现 MethodInterceptor 接口,并在其中定义一个被代理类的对象作为属性。

public class CacheMethodInterceptor implements MethodInterceptor {
    private HashMap<String,String> cache = new LinkedHashMap<>(256);
    private DatabaseDataQuery databaseDataQuery;
    public CacheMethodInterceptor() {
        this.databaseDataQuery = new DatabaseDataQuery();
   }
    @Override

    public Object intercept(Object obj, Method method, Object[] args, 

MethodProxy proxy) throws Throwable {
        // 1、判断是哪一个方法

        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回

            result = cache.get(args[0].toString());
            if(result != null){
                System.out.println("数据从缓存重获取。");
                return result;
           }
            // 3、未命中,查数据库(需要代理实例)

            result = (String) method.invoke(databaseDataQuery, args);
            // 4、如果查询到了,进行呢缓存

            cache.put(args[0].toString(),result);
            return result;
       }
        return method.invoke(databaseDataQuery,args);
   }
}

在这个代理类中,我们实现了 MethodInterceptor 接口,并在其中定义了一个被代理类的对象作为属性。在 intercept 方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。
(3)在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create 方法生成代理对象。

public class Main {
    public static void main(String[] args) {
        // cglib通过Enhancer
        Enhancer enhancer = new Enhancer();
        // 设置他的父类(被代理类)
        enhancer.setSuperclass(DatabaseDataQuery.class);
        // 设置一个方法拦截器,用来拦截方法
        enhancer.setCallback(new CacheMethodInterceptor());
        // 创建代理类
        DatabaseDataQuery databaseDataQuery = 
(DatabaseDataQuery)enhancer.create();
        databaseDataQuery.query("key1");
        databaseDataQuery.query("key1");
        databaseDataQuery.query("key2");
   }
}

在这个示例中,我们使用 Enhancer.create 方法生成代理对象,并将代理对象转换成 RealSubject 类型,以便调用 request 方法。在代理对象调用 request 方法时,会调用 DynamicProxy 类中的 intercept 方法,实现对被代理对象的增强。

在实际应用中,基于 CGLIB 的动态代理可以代理任意类,但是生成的代理类比较重量级。如果被代理类是一个接口,建议使用基于 JDK 的动态代理来实现,这也是spring的做法;如果被代理类没有实现接口或者需要代理的方法是 final 方法,建议使用基于 CGLIB 的动态代理来实现。

动态代理的应用场景

动态代理是一种代理模式,它在运行时动态生成代理对象,而无需提前创建具体的代理类。动态代理在 Java 中通常使用java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。以下是一些动态代理的

使用场景:

  1. 日志记录: 使用动态代理可以在方法调用前后自动添加日志记录,从而跟踪方法的执行过程。这样可以方便地监控系统运行情况,诊断问题,而无需修改实际类的代码。

  2. 性能监控: 动态代理可用于测量方法执行的时间,以评估性能。在方法调用前后记录时间戳,然后计算时间差,就可以得到方法执行所需的时间。

  3. 事务管理: 在数据库操作中,动态代理可用于自动管理事务。在方法调用前开始一个事务,在方法成功执行后提交事务,如果发生异常,则回滚事务。这样可以确保数据的一致性和完整性。

  4. 权限验证: 使用动态代理可以在方法调用前进行权限验证,确保只有具有适当权限的用户才能访问受保护的资源。这可以提高系统的安全性。

  5. 缓存: 动态代理可用于实现方法结果的缓存。在方法调用前检查缓存,如果缓存中有结果,则直接返回,否则执行方法并将结果存入缓存。这可以提高程序的执行效率。

  6. 负载均衡与故障转移: 在分布式系统中,动态代理可以用于实现负载均衡和故障转移。代理对象根据某种策略(如轮询、随机等)选择一个可用的服务实例,并将请求转发给它。如果服务实例发生故障,代理对象可以自动选择另一个可用的实例。

  7. API 速率限制: 使用动态代理,可以在方法调用前检查 API 请求速率是否超过预设的限制。如果超过限制,可以拒绝请求或将请求延迟一段时间后再执行。

  8. 数据验证: 在方法调用前,动态代理可以用于验证传入的参数是否符合预期的规则和约束。这有助于确保数据的有效性和一致性。

  9. 重试机制: 当方法调用失败时(例如,因为网络问题、服务不可用等原因),动态代理可以实现自动重试的机制。代理对象可以在一定的时间间隔内尝试重新执行方法,直到成功或达到最大重试次数。

  10. 懒加载与资源管理: 动态代理可以用于实现资源的懒加载和管理。例如,代理对象可以在第一次访问资源时才创建和初始化它。此外,代理对象还可以在资源不再需要时自动释放它,以减少内存占用和提高性能。

  11. 跨语言和跨平台调用: 动态代理可以实现跨语言和跨平台的对象调用。例如,一个 Java 客户端可以使用动态代理调用一个基于 Python 的服务。在这种情况下,代理对象会负责处理跨语言通信的细节,如序列化、反序列化和网络传输。

  12. AOP(面向切面编程): 动态代理是实现 AOP 的一种方式。AOP 允许在程序运行时动态地插入和修改横切关注点(如日志记录、性能监控等),而无需修改实际代码。动态代理可以轻松地实现 AOP,以提高代码的可维护性和可重用性。

这些仅仅是动态代理的一些应用场景。动态代理是一种非常灵活的技术,可以用于解决许多不同类型的问题。在实际项目中,根据具体需求,可以将动态代理与其他设计模式结合使用。事实上,我们如果不是编写中间件产品,绝大部分的场景都是在spring环境下实现的,我们理解了代理的本是就是增强,他是aop的一种实现方式,工作中绝大部分的场景都是使用aop来实现的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值