Java * 四种实现方式详解
作者:源点+ 发布时间:2022-03-12 02:16:38
代理模式也是一种非常常见的设计模式。了解Spring框架的都知道,Spring AOP 使用的就是 * 模式。今天就来系统的重温一遍代理模式。
在现实生活中代理是随处可见的,当事人因某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而需要一个职业人员来完成一些专业的操作, 也可能由于当事人没有时间处理事务,而聘用代理人出面。而在软件设计中,使用代理模式的地方也很多,由于安全原因,屏蔽客户端直接访问真实对象, 或者为了提升系统性能,使用代理模式实现延迟加载,还有就是AOP,对委托类的功能进行增强等。
一、代理模式的结构
代理模式的主要参与者有4个,如下图所示:
角色 | 作用 |
---|---|
Subject | 主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法 |
RealSubject | 委托类,真实主题,真正实现业务逻辑的类 |
Proxy | 代理类,代理和封装委托类 |
Client | 客户端,使用代理类和主题接口完成业务逻辑 |
loading="lazy" alt="" />角色作用Subject主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法RealSubject委托类,真实主题,真正实现业务逻辑的类Proxy代理类,代理和封装委托类Client客户端,使用代理类和主题接口完成业务逻辑
二、代理模式的实现
代理模式一般分为静态代理和 * 两种:
静态代理,顾名思义,就是提前创建好代理类文件并在程序运行前已经编译成字节码。
* ,是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入到ClassLoader中。
了解了两种代理模式大概区别后,接下来就以一个短信发送功能增强的示例来详细阐述两种代理的实现方式。
1、静态代理实现
第一步,定义主题接口,该接口只有一个send
方法:
public interface ISender {
public boolean send();
}
第二步,定义主题真正实现类:
public class SmsSender implements ISender {
public boolean send() {
System.out.println("sending msg");
return true;
}
}
第三步,创建代理类,封装实现类:
public class ProxySender implements ISender {
private ISender sender;
public ProxySender(ISender sender){
this.sender = sender;
}
public boolean send() {
System.out.println("处理前");
boolean result = sender.send();
System.out.println("处理后");
return result;
}
}
第四步,客户端调用:
@Test
public void testStaticProxy(){
ISender sender = new ProxySender(new SmsSender());
boolean result = sender.send();
System.out.println("输出结果:" + result);
}
以上就实现了一个简单的静态代理,很明显,静态代理需要为每个真实主题定义一个形式上完全一样的封装类,
如果真实主题方法有所修改,那代理类也需要跟着修改,不利于系统的维护。
2、 * 实现
与静态代理相比, * 有更多优势, * 不仅不需要定义代理类,甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
目前 * 类的生成方法有很多,有JDK自带的 * 、CGLIB、Javassist和ASM库等。
JDK * :内置在JDK中,不需要引入第三方jar,使用简单,但功能比较弱。
CGLIB/Javassist:这两个都是高级的字节码生成库,总体性能比JDK * 好,且功能强大。
ASM:低级字节码生成工具,近乎使用bytecode编码,对开发人员要求最高。当然性能也是最好(相比前几种也不是很大的提升,这里不做具体介绍)。
以下实例依然以SmsSender
和ISender
作为被代理对象和接口进行试验。
1) JDK *
JDK的 * 需要实现一个处理方法调用的Handler,用于实现代理方法的内部逻辑,实现InvocationHandler
接口。
public class JdkProxyHandler implements InvocationHandler {
private Object target;
public JdkProxyHandler(Object target){
this.target = target;
}
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;
}
}
客户端调用:
@Test
public void testJdkProxy(){
ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{ISender.class},
new JdkProxyHandler(new SmsSender()));
boolean result = sender.send();
System.out.println("代理对象:" + sender.getClass().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:com.sun.proxy.$Proxy4
输出结果:true
这样实现一个简单的AOP就完成了,我们看到代理类的类型是com.sun.proxy.$Proxy4
。那JDK是如何创建代理类?
首先从Proxy.newProxyInstance入手,来研究JDK是如何生成代理类:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
该方法有3个参数:
loader:用哪个类加载器去加载代理对象,生成目标对象的代理需要确保其类加载器相同,所以需要将目标对象的类加载器作为参数传递。
interfaces:代理类需实现的接口列表,JDK * 技术需要代理类和目标对象都继承自同一接口,所以需要将目标对象的接口作为参数传递。
h:调用处理器,调用实现了InvocationHandler类的一个回调方法,对目标对象的增强逻辑在这个实现类中。
具体代码如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//1.检查
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//获取代理类类型
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//通过反射创建代理对象
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
总结:具体代码细节就不在这里深究,但可以明显的看出,JDK的 * 底层是通过Java反射机制实现的,并且需要目标对象继承自一个接口才能生成它的代理类。
2) CGLIB(Code Generation Library) *
使用CGLIB * 前需要引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
和JDK代理不同,CGLib * 技术不需要目标对象实现自一个接口,只需要实现一个处理代理逻辑的切入类,并实现MethodInterceptor
接口。
定义真实主题实现类:
public class BdSender {
public boolean send() {
System.out.println("sending msg");
return true;
}
}
代理类逻辑处理类:
public class CglibProxyInterceptor implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
/**
* 获取代理类
* @param clazz
* @return
*/
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("处理前");
Object result = methodProxy.invokeSuper(object,args);
System.out.println("处理后");
return result;
}
}
客户端调用:
@Test
public void testCglibProxy(){
BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + sender.getClass().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34
输出结果:true
总结CgLib的特点:
使用CGLib实现 * ,完全不受代理类必须实现接口的限制
CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高
CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类
3)Javassist *
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
使用avassist * 前需要引入依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
使用Javassist生成 * 可以有以下两种方式:
代理工厂创建:需要实现
MethodHandler
用于代理逻辑处理,实现与CGLib非常类似动态代码创建:可通过Java代码生成字节码,这种方式创建的 * 非常灵活,甚至可以在运行时生成业务逻辑
代理工厂创建 — 代理逻辑处理类
public class JavassistProxyHandler implements MethodHandler {
private ProxyFactory proxyFactory = new ProxyFactory();
/**
* 获取代理对象
* @param clazz 被代理类
* @return
* @throws Exception
*/
public Object getProxy(Class clazz) throws Exception {
proxyFactory.setSuperclass(clazz);
Class<?> factoryClass = proxyFactory.createClass();
Object proxy = factoryClass.newInstance();
((ProxyObject)proxy).setHandler(this);
return proxy;
}
public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
System.out.println("处理前");
Object result = method1.invoke(object,args);
System.out.println("处理后");
return result;
}
}
客户端调用:
@Test
public void testJavassistProxy() throws Exception {
BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + sender.getClass().getName());
System.out.println("输出结果:" + result);
}
输出结果
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender_$$_jvstbce_0
输出结果:true
动态代码创建 — 代理逻辑处理类:
public static Object getProxy(Class clazz) throws Exception {
ClassPool mPool = ClassPool.getDefault();
CtClass c0 = mPool.get(clazz.getName());
//定义代理类名称
CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy");
//添加父类继承
mCtc.setSuperclass(c0);
//添加类的字段信息
CtField field = new CtField(c0, "real", mCtc);
field.setModifiers(AccessFlag.PRIVATE);
mCtc.addField(field);
//添加构造函数
CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表构造函数的第1个参数
mCtc.addConstructor(constructor);
//添加方法
CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send");
CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc);
newMethod.setBody("{" +
"System.out.println(\"处理前\");" +
"boolean result = $0.real.send();" +
"System.out.println(\"处理后\");" +
"return result;}");
mCtc.addMethod(newMethod);
//生成动态类
return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance());
}
客户端调用:
@Test
public void testJavassisBytecodetProxy() throws Exception {
BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + sender.getClass().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender$$BytecodeProxy
输出结果:true
Javassist被用于struts2和hibernate中,都用来做动态字节码修改使用。一般开发中不会用到,但在封装框架时比较有用。
以上介绍了静态代理和 * 创建的几种方法与优缺点介绍,希望可以帮到大家。
来源:https://www.cnblogs.com/zs-yuandian/p/14653197.html


猜你喜欢
- 组件在容器(比如Jframe)中的位置和大小是由布局管理器来决定的。所有的容器都会使用一个布局管理器,通过它来自动进行组件的布局管理。种类j
- 图像的旋转需要调用 Graphics2D 类的rota
- Spring Cloud Feign简介 Spring Cloud Feign也是一个基础工具类,它整合了Spring Cloud Ribb
- 作为一个初级GIS程序员,关于封装那些宏观的概念暂且不提,编程经常面对的就是“字段,属性,方法”,这也是面向对象的基本概念之一。1.字段通常
- using System; using System.Collections; using System.Text; using Syste
- 问题 @Cacheable注解不支持配置过期时间,所有需要通过配置CacheManneg来配置默认的过期时间和针对每个类或者是方法进行缓存
- File存储(内部存储)一旦程序在设备安装后,data/data/包名/ 即为内部存储空间,对外保密。Context提供了2个方法来打开输入
- 1:HttpHelper.javapublic class HttpHelper { //1:标准的Ja
- 在进行java编程的时候,我们可以生成可运行的jar文件,但是鉴于平台的不同,我们可能需要将jar文件转化为exe格式。今天,小编就用一款叫
- 本文实例讲述了Android 开发使用PopupWindow实现弹出警告框的复用类。分享给大家供大家参考,具体如下:Android开发中相信
- 本文实例讲述了Java基于享元模式实现五子棋游戏功能。分享给大家供大家参考,具体如下:一、模式定义享元模式,以共享的方式高效地支持大量的细粒
- 前段时间,同事在代码中KW扫描的时候出现这样一条:上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作
- 最近在学习springboot,session这个点一直困扰了我好久,今天把这些天踩的坑分享出来吧,希望能帮助更多的人。一、pom.xml配
- 本文实例讲述了Android7.0开发实现Launcher3去掉应用抽屉的方法。分享给大家供大家参考,具体如下:年初做过一个项目,有一个需求
- 布局中listview要覆盖标题栏 int mTouchSlop = ViewConfiguration.get(this).getScal
- forword跳转页面的三种方式:1.使用serlvet/** * 使用forward跳转,传递基本类型参数到页面  
- 前言RefreshIndicator是Flutter里常见的下拉刷新组件,使用是比较方便的。但由于产品兄弟对其固定的刷新样式很是不满,而且代
- 说实话,关于Android中对短信的一些相关操作是一个比较入门的东西。那我现在还要来写这一篇博客的原因只是因为现在开发中有相关内容,而又想将
- 本文实例讲述了C#实现获取鼠标句柄的方法,分享给大家供大家参考。具体实现方法如下:一、调用user32.dll(1)引用using Syst
- 在开源中国看到的操作ini文件的,写的还不看,留着以后用using System;using System.IO;using System.