详解JAVA *
作者:奉强的个人博客 发布时间:2023-11-24 22:52:04
文档更新说明
2018年09月24日 v1.0 初稿
代理在生活中很常见,比如说婚介网站,其实就是找对象的代理;还有社保代理、人事代理;还有找黄牛抢票,其实也是一种代理;而这些代理,在JAVA中也是有对应实现的。
1、为什么要 *
* 的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。
关键点:
不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强
2、举个栗子
我们用一个很简单的例子来说明:Hello
类,有一个introduction
方法。
现在我们的需求就是不修改Hello
类的introduction
方法,在introduction
之前先sayHello
,在introduction
之后再sayGoodBye
3、实现方式
JAVA中,实现 * 有两种方式,一种是JDK提供的,一种是第三方库CgLib提供的。特点如下:
JDK * :被代理的目标类需要实现接口CgLib
方式:可以对任意类实现 *
3.1、JDK *
JDK * 需要实现接口,然后通过对接口方法的增强来实现 *
所以要使用JDK * 的话,我们首先要创建一个接口,并且被代理的方法要在这个接口里面
3.1.1、创建一个接口
我们创建一个接口如下:
Personal.java
public interface Personal {
/**
* 被代理的方法
*/
void introduction();
}
3.1.2、实现接口
创建接口实现类,并且完成introduction
方法
PersonalImpl.java
public class PersonalImpl implements Personal {
@Override
public void introduction() {
System.out.println("我是程序员!");
}
}
3.1.3、创建代理类
JDK代理的关键就是这个代理类了,需要实现InvocationHandler
在代理类中,所有方法的调用都好分发到invoke
方法中。我们在invoke
方法完成对方法的增强即可
JDKProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
* 目标对象
*/
private T target;
/**
* 构造函数传入目标对象
*
* @param target 目标对象
*/
public JDKProxyFactory(T target) {
this.target = target;
}
/**
* 获取代理对象
*
* @return 获取代理
*/
public T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@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;
}
}
就这样,JDK * 的代码就完成了,接下来写一份测试代码
3.1.4、编写测试代码
为了方便测试,我们编写一个test
方法
同时为了查看class
文件,还添加了一个generatorClass
方法,这个方法可以将 * 生成的.class
输出到文件
ProxyTest.java
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
public void testJdkProxy() {
// 生成目标对象
Personal personal = new PersonalImpl();
// 获取代理对象
JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
Personal proxy = proxyFactory.getProxy();
// 将proxy的class字节码输出到文件
generatorClass(proxy);
// 调用代理对象
proxy.introduction();
}
/**
* 将对象的class字节码输出到文件
*
* @param proxy 代理类
*/
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
3.1.5、查看运行结果
可以看到,运行test
方法之后,控制台打印出如下:
大家好!
我是程序员!
再见!
我们在introduction
方法前和后都成功增加了功能,让这个程序员的自我介绍瞬间变得更加有礼貌了。
3.1.6、探探 * 的秘密
* 的代码并不多,那么JDK
底层是怎么帮我们实现的呢?
在测试的时候我们将动态生成的代理类的class字节码输出到了文件,我们可以反编译看看。
结果有点长,就不全部贴出来了,不过我们可以看到,里面有一个introduction
方法如下:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
public final void introduction() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
原来,生成的代理对象里面,引用了我们的InvocationHandler
,然后在将introduction
方法里面调用了InvocationHandler
的introduction
,而InvocationHandler
是由我们编写的代理类,在这里我们增加了sayHello
和sayGoodBye
操作,然后还调用了原对象的introduction
方法,就这样完成了 * 。
3.2、CgLib *
CgLib动态
3.2.1、创建被代理对象
由于CgLib
不需要实现接口,所以我们不需要创建接口文件了(当然,你要有接口也没有问题)
直接创建目标类,实现introduction
方法
PersonalImpl.java
public class PersonalImpl {
public void introduction() {
System.out.println("我是程序员!");
}
}
3.2.2、创建代理类
同样,我们也需要创建代理类,并且在这里实现增强的逻辑,这次我们不是实现InvocationHandler
接口了,而是实现CgLib
提供的接口MethodInterceptor
,都是类似的,MethodInterceptor
中,全部方法调用都会交给intercept
处理,我们在intercept
添加处理逻辑即可。
CgLibProxyFactory.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 CgLibProxyFactory<T> implements MethodInterceptor {
/**
* 获取代理对象
*
* @param tClass 被代理的目标对象
* @return 代理对象
*/
public T getProxyByCgLib(Class<T> tClass) {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置需要增强的类的类对象
enhancer.setSuperclass(tClass);
// 设置回调函数
enhancer.setCallback(this);
// 获取增强之后的代理对象
return (T) enhancer.create();
}
/**
* 代理类方法调用回调
*
* @param obj 这是代理对象,也就是[目标对象]的子类
* @param method [目标对象]的方法
* @param args 参数
* @param proxy 代理对象的方法
* @return 返回结果,返回给调用者
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("大家好!");
Object result = proxy.invokeSuper(obj, args);
System.out.println("再见!");
return result;
}
}
3.2.3、编写测试代码
在刚才的测试方法中,我们添加一个cglib
的测试方法:
@Test
public void testCgLibProxy() {
// 生成被代理的目标对象
PersonalImpl personal = new PersonalImpl();
// 获取代理类
CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// 将proxy的class字节码输出到文件
generatorClass(proxy);
// 调用代理对象
proxy.introduction();
}
3.2.4、查看运行结果
运行测试用例,可以看到跟JDK
的实现一样的效果
大家好!
我是程序员!
再见!
3.2.5、探探 * 的秘密
跟JDK
的测试一样,我们也来看看生成的class
文件
public final void introduction() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以发现,与JDK
的 * 并没有区别。
4、如何选择
既然有两种实现方式,那么到底应该怎么选择呢?
就两个原则:
目标类有接口实现的,JDK
和CgLib
都可以选择,你开心就好
目标类没有实现任何接口,那只能用CgLib
了
5、后记
其实在第一次看到 * 的时候,我就想不明白,我们都把目标类new出来了,为什么还要将目标类丢给代理类呢?为什么不直接调用目标类对应的方法呢?
后来才发现,原来我没搞清楚 * 的使用场景,场景很清晰,就是:
不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强
关键是增强,代理类里面我们是可以添加很多处理逻辑的,从而实现增强效果。就像黄牛抢票比我们厉害些一样。
以上所述是小编给大家介绍的JAVA * 详解整合,希望对大家有所帮助。
来源:https://www.fengqiangboy.com/15377761043880.html


猜你喜欢
- 本文实例讲述了C#反射应用。分享给大家供大家参考。具体如下:通过反射实现多系统数据库的配置通过定义接口,反射实例化配置的节点的值配置App.
- 前言这篇博客介绍Java环境的配置,主要是安装JDK,以及path、JAVA_hOME、CLASSPAT的配置,还会介绍配置这些的原因。一.
- 什么是显式转换Explicit Conversion就是在将一种类型转换成另外一种类型时,需要额外的代码来完成这种转换。int n = 1;
- 前言笔者上次用C#写.Net代码差不多还是10多年以前,由于当时Java已经颇具王者风范,Net几乎被打得溃不成军。因此当时笔者对于这个.N
- 前言最近想体验下最新版本的SpringBoot,逛了下官网,发现SpringBoot目前最新版本已经是2.6.4了,版本更新确实够快的。之前
- Android 添加系统设置属性的实现及步骤Android源码开发中,常常要用到一些全局标志或者说变量,这时候我们可以给android系统添
- 最近有一个java实验,要求用java使用数据库,于是本人新手小白,在idea上卡了好半天希望看到这个博客的人能解决问题,跳过一些坑首先,我
- 1 泰勒级数介绍近期工作中需要使用matlab建模,期间做案例的时候有个方程:sin(x)=0,要求不使用现有api进行求解,然后有点懵,不
- 解释:二叉树的深度:从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。二叉树的宽度:二叉树的每一层中
- 本文实例为大家分享了C语言实现扫雷游戏的具体代码,供大家参考,具体内容如下此次扫雷中我们以9乘以9的表格为例,后期可自动调动主要思路:建立两
- 本文实例讲述了Android编程设计模式之状态模式。分享给大家供大家参考,具体如下:一、介绍状态模式中的行为是由状态来决定的,不同的状态下有
- 我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通
- 前言本文主要是将最近工作中遇到的一个问题进行总结分享,主要介绍的是如何让WebView中H5页面全屏播放视频。关于这个问题,做一下简单分析,
- 一、前言最近在回顾数据结构与算法,有部分的算法题用到了栈的思想,说起栈又不得不说链表了。数组和链表都是线性存储结构的基础,栈和队列都是线性存
- JVM内存模型/内存空间Java虚拟机JVM运行起来,就会给内存划分空间,这块空间成为运行时数据区。运行时数据区主要划分为以下 6
- 1.定义指向非法的内存地址指针叫作野指针(Wild Pointer),也叫悬挂指针(Dangling Pointer),意为无法正常使用的指
- 1、使用 ctrl+F12打开类中所有方法的界面2、然后直接键盘中输入方法名称或者方法包含的字母,会自动模糊匹配相关方法名补充:idea快速
- 前言最近在学习使用 React Native开发,iOS搞完,开始适配安卓,由于木有接触过安卓,所以碰到了很多问题,第一个问题,安卓的返回键
- IDEA时跳转到.class的解决项目背景:jdk1.8软件环境:IDEA问题:两个不同的项目,在A项目中写了一个实体类。B项目中引用。我想
- Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。Java.io 包中的流支持很多种格式,比如:基