当前位置: 首页 > news >正文

Java 代理模式

代理模式简介

代理模式简单来说就是使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后可以增加一些自定义操作。

代理模式有静态代理动态代理两种实现方式。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦增加新的方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)

从JVM层面来说,静态代理在编译时将接口、实现类、代理类这些都变成了一个个实际的class文件
静态代理实现步骤:

  • 定义一个接口及其实现类
  • 创建一个代理类同样实现这个接口
  • 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法,这样的话,就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

实现

  • 定义发送短信的接口
public interface SmsService {String send(String message);
}
  • 实现发送短信的接口
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}
  • 创建代理类并且同样实现发送短信的接口
public class SmsProxy implements SmsService {private final SmsService smsService;public SmsProxy(SmsService smsService) {this.smsService = smsService;}@Overridepublic String send(String message) {//调用方法之前,我们可以添加自己的操作System.out.println("before method send()");smsService.send(message);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method send()");return null;}
}
  • 实际使用
public class Main {public static void main(String[] args) {SmsService smsService = new SmsServiceImpl();SmsProxy smsProxy = new SmsProxy(smsService);smsProxy.send("java");}
}

动态代理

相比于静态代理而言,动态代理更加灵活,不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,可以直接代理实现类(CGLIB动态代理机制)。

从JVM角度来说,动态代理是在运行时动态生成类字节码,并加载到JVM中的

说到动态代理,Spring AOP、RPC框架应该是两个不得不提的,他们的实现都依赖了动态代理。

就Java而言,动态代理的实现方式有多种,最主要的如JDK动态代理、CGLIB动态代理

JDK动态代理

介绍

在JDK动态代理机制中,InvocationHandler接口和Proxy类是核心。

Proxy类中使用频率最高的方法是newProxyInstance(),这个方法主要用来生成一个代理对象。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException{......
}

这个方法一共有三个参数:

  • loader:类加载器,用于加载代理对象
  • interfaces:被代理类实现的一些接口
  • h:实现了InvocationHandler接口的对象。

要实现动态代理的话,还必须实现InvocationHandler来自定义处理逻辑。当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

public interface InvocationHandler {/*** 当你使用代理对象调用方法的时候实际会调用到这个方法*/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

invoke()方法三个参数

  • proxy:动态生成的代理类
  • method:与代理类对象调用的方法相对应
  • args: 当前method方法的参数

当通过Proxy类的newProxyInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler接口的类的invoke()方法。可以在invoke()方法中自定义处理逻辑。

使用步骤及代码示例

1. 定义一个接口及其实现类

  • 定义接口
public interface SmsService {String send(String message);
}
  • 定义实现类
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}

2.自定义InvocationHandler并重写invoke方法,在invoke方法中调用原生方法病自定义一些处理逻辑

mport java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** @author shuang.kou* @createTime 2020年05月11日 11:23:00*/
public class DebugInvocationHandler implements InvocationHandler {/*** 代理类中的真实对象*/private final Object target;public DebugInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {//调用方法之前,我们可以添加自己的操作System.out.println("before method " + method.getName());Object result = method.invoke(target, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return result;}
}

3. 通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象

public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 目标类的类加载器target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个new DebugInvocationHandler(target)   // 代理对象对应的自定义InvocationHandler );}
}

4. 实际使用

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java")

CGLIB动态代理

JDK动态代理有一个最致命的问题就是其只能代理实现了接口的类
为了解决这个问题,我们可以用CGLIB动态代理机制来避免。

CGLIB是一个基于ASM的字节码生成库,他允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。很多知名的框架都使用到了CGLIB。如Spring中的AOP模块:如果目标对象实现了接口,则默认采用JDK动态代理,否则采用CGLIB动态代理。

在CGLIB动态代理机制中MethodInterceptor接口和Enhancer类是核心。

需要自定义MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法。

public interface MethodInterceptor extends Callback{// 拦截被代理类中的方法public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
  • obj: 被代理的对象
  • method:被拦截的方法
  • args: 方法入参
  • proxy:用于调用原始方法

你可以通过Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor中的intercept方法。

CGLIB动态代理的使用
  • 定义一个类
  • 自定义MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理的方法,如JDK动态代理中的invoke方法。
  • proxy:用于调用原始方法

代码示例

  1. 添加依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
  1. 实现一个短信发送的类
package github.javaguide.dynamicProxy.cglibDynamicProxy;public class AliSmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}
  1. 自定义MethodInterceptor(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*** 自定义MethodInterceptor*/
public class DebugMethodInterceptor implements MethodInterceptor {/*** @param o           被代理的对象(需要增强的对象)* @param method      被拦截的方法(需要增强的方法)* @param args        方法入参* @param methodProxy 用于调用原始方法*/@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//调用方法之前,我们可以添加自己的操作System.out.println("before method " + method.getName());Object object = methodProxy.invokeSuper(o, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return object;}
}
  1. 获取代理类
import net.sf.cglib.proxy.Enhancer;public class CglibProxyFactory {public static Object getProxy(Class<?> clazz) {// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(clazz.getClassLoader());// 设置被代理类enhancer.setSuperclass(clazz);// 设置方法拦截器enhancer.setCallback(new DebugMethodInterceptor());// 创建代理类return enhancer.create();}
}
  1. 实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

JDK动态代理和CGLIB动态代理对比

  1. JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。另外,CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法。

  2. 就二者的效率来说,大部分情况是JDK动态代理更优秀。

静态代理和动态代理对比

  1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且不需要对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的。
  2. JVM层面:静态代理在编译时就将接口、实现类、代理类都变成了一个个实际的class文件,而动态代理是在运行时动态生成类字节码,并加载到JVM中的。

相关文章:

  • C语言---------深入理解指针
  • react:handleEdit={() => handleEdit(user)} 和 handleEdit={handleEdit(user)}有啥区别
  • MFC socket编程-服务端和客户端流程
  • Vue43-单文件组件
  • 22.1 正则表达式-定义正则表达式、正则语法
  • k8s学习--Traffic Shifting 流量接入
  • 【归并排序】| 详解归并排序核心代码之合并两个有序数组 力扣88
  • ubuntu18.04 配置 mid360并测试fast_lio
  • C++ 算法教程
  • 深入理解指针(四)
  • 计算机图形学入门11:图形管线与着色器
  • fs模块(一)
  • Day07-06_13【CT】LeetCode手撕—1. 两数之和
  • Vue主要使用-03
  • mysql之数据聚合
  • JS 中的深拷贝与浅拷贝
  • django开发-定时任务的使用
  • Java 最常见的 200+ 面试题:面试必备
  • Java基本数据类型之Number
  • Spring-boot 启动时碰到的错误
  • 阿里云购买磁盘后挂载
  • 多线程事务回滚
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 容器服务kubernetes弹性伸缩高级用法
  • 软件开发学习的5大技巧,你知道吗?
  • 深度学习在携程攻略社区的应用
  • 微信开源mars源码分析1—上层samples分析
  • 微信小程序实战练习(仿五洲到家微信版)
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • "无招胜有招"nbsp;史上最全的互…
  • (02)Hive SQL编译成MapReduce任务的过程
  • (多级缓存)缓存同步
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (一)Kafka 安全之使用 SASL 进行身份验证 —— JAAS 配置、SASL 配置
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (一)基于IDEA的JAVA基础10
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET Reactor简单使用教程
  • .NET Standard 的管理策略
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • .NET开发者必备的11款免费工具
  • .NET企业级应用架构设计系列之结尾篇
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • /etc/motd and /etc/issue
  • @ 代码随想录算法训练营第8周(C语言)|Day53(动态规划)
  • [ 云计算 | AWS ] AI 编程助手新势力 Amazon CodeWhisperer:优势功能及实用技巧
  • [.net]官方水晶报表的使用以演示下载
  • [AIGC 大数据基础]hive浅谈
  • [Android Pro] AndroidX重构和映射
  • [C#][opencvsharp]opencvsharp sift和surf特征点匹配