一文总结代理:代理模式、代理服务器

概述

代理在计算机编程领域,是一个很通用的概念,包括:代理设计模式,代理服务器等。

代理类持有具体实现类的实例,将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可以去掉功能服务或者增加额外的功能。

角色

代理模式一般涉及到的角色有:

  • 对象:Client,请求客户端
  • 抽象角色:Subject,声明真实对象和代理对象的共同接口,对应代理接口;
  • 真实角色:RealSubject,代理角色所代表的真实对象,是最终要引用的对象,对应委托类;
  • 代理角色:Proxy,代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装,对应代理类,可以有未公开的方法。

UML类图如下:
在这里插入图片描述
调用顺序示意图:
在这里插入图片描述

分类

代理从类型来分类:

  • 虚代理:虚拟代理,Virtual Proxy,根据需要来创建开销很大的对象,该对象只有在需要时才会被真正创建,即所谓的延迟加载;
  • 远程代理:Remote Proxy,用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以是在本机,也可以在其它机器上,在Java里面最典型的就是RMI技术;
  • copy-on-write代理:在客户端操作时,只有对象确实改变后,才会真的拷贝一个日标对象,算是虚代理的一个分支;
  • 保护代理:Protect Proxy,控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问;
  • Cache代理:为那些昂贵操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果;
  • 防火墙代理:保护对象不被恶意用户访问和操作;
  • 同步代理:使多个用户能够同时访问目标对象而没有冲突;
  • 智能引用代理:Smart Reference Proxy,在访问对象时执行一些附加操作。比如,对指向实际对象的引用计数、第一次引用一个持久对象时,将它装入内存等

另外,根据代理类的生成时间的不同,可分为静态代理和动态代理。

静态代理

代理和被代理对象(目标对象)在代理之前是确定的,都实现相同的接口或者继承相同的抽象类。目标对象作为代理对象的一个属性,具体接口实现中,代理对象可以在调用目标对象相应方法前后加上其他业务处理逻辑。由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态,在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定。

实现静态代理有继承、聚合方法。

优点:业务类只需要关注业务逻辑本身,保证业务类的重用性。

缺点:

  1. 需要大量硬编码
  2. 一个代理类只能代理一个业务类
  3. 如果业务类增加方法时,相应的代理类也要增加方法

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。优势在于可以很方便的对代理类的函数进行统一的处理,添加方法调用次数、添加日志功能等,而不用修改每个代理类的函数。分为JDK动态代理和cglib动态代理。

实现

一般有JDK和cglib等实现方式。

JDK

基于反射,核心API包括:
java.lang.reflect.Proxy,Java动态代理机制生成的所有动态代理类的父类,提供一组静态方法来为一组接口动态地生成代理类及其对象,共4个static方法:

public class Proxy implements java.io.Serializable {// 用于获取指定代理对象所关联的调用处理器public static InvocationHandler getInvocationHandler(Object proxy)// 用于获取关联于指定类装载器和一组接口的动态代理类的类对象public static class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)// 用于判断指定类对象是否是一个动态代理类public static boolean isProxyClass(Class<?> cl)// 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
}

InvocationHandler接口主要用来处理执行逻辑,源码:

public interface InvocationHandler {// obj指代理类,method指被代理的方法,args为该参数的方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

使用JDK动态代理的步骤

  1. 创建被代理的类以及接口;
  2. 创建一个实现接口InvocationHandler,并重写invoke方法的类;
  3. 调用Proxy的静态方法,创建代理类newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
  4. 通过代理调用方法。

实例

代表Subject的接口:

public interface Movable {void move();
}

Car实现Movable接口,是要代理的实际对象,对应RealSubject:

@Slf4j
public class Car implements Movable {@Overridepublic void move() {// 实现开车try {Thread.sleep(new Random().nextInt(1000));System.out.println("汽车行驶中....");} catch (InterruptedException e) {log.error("thread fail", e);}}
}

对应于Proxy的TimeHandler类实现InvocationHandler接口,并在invoke()方法中添加额外的逻辑,用于在代理对象方法调用前后执行:

public class TimeHandler implements InvocationHandler {private final Object target;TimeHandler(Object target) {super();this.target = target;}/*** 参数:* proxy:被代理对象* method:被代理对象的方法* args:方法的参数* res:方法的返回值*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶....");Object res = method.invoke(target, args);long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶....  汽车行驶时间:" + (endTime - startTime) + "毫秒!");return res;}
}

JDK动态代理测试类,也就是上图中的Client类:

public class Test {public static void main(String[] args) {Car car = new Car();InvocationHandler h = new TimeHandler(car);Class<?> cls = car.getClass();/** loader:类加载器* interfaces:实现接口* h:InvocationHandler*/Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);m.move();}
}

使用Proxy.newProxyInstance()方法创建代理对象,指定Movable接口作为代理对象类型,并将TimeHandler对象作为代理对象的InvocationHandler。

缺点:

  1. 仍有硬编码
  2. 需要在对象初始化时,使用特定的方式进行初始化

源码分析

基于JDK-22,以Proxy.newProxyInstance()方法为切入点:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {Objects.requireNonNull(h);@SuppressWarnings("removal")final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();// 查找或生成指定的代理类及其构造函数Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);return newProxyInstance(caller, cons, h);
}

基于源码的注释,进一步查看getProxyConstructor代码:

private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) {// optimization for single interfaceif (interfaces.length == 1) {Class<?> intf = interfaces[0];if (caller != null) {checkProxyAccess(caller, loader, intf);}return proxyCache.sub(intf).computeIfAbsent(loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build());} else {// interfaces clonedfinal Class<?>[] intfsArray = interfaces.clone();if (caller != null) {checkProxyAccess(caller, loader, intfsArray);}final List<Class<?>> intfs = Arrays.asList(intfsArray);return proxyCache.sub(intfs).computeIfAbsent(loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build());}
}

核心方法是构建ProxyBuilder实例,ProxyBuilder是java.lang.reflect.Proxy的静态内部类,利用构造器模式:

ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {// 在模块系统完全初始化之前不支持代理if (!VM.isModuleSystemInited()) {throw new InternalError("Proxy is not supported until module system is fully initialized");}// 接口数不得超过65535个if (interfaces.size() > 65535) {throw new IllegalArgumentException("interface limit exceeded: " interfaces.size());}Set<Class<?>> refTypes = referencedTypes(loader, interfaces);// IAE if violates any restrictions specified in newProxyInstancevalidateProxyInterfaces(loader, interfaces, refTypes);this.interfaces = interfaces;this.context = proxyClassContext(loader, interfaces, refTypes);assert getLoader(context.module()) == loader;
}

核心方法之一,referencedTypes检查是否为static方法:

private static Set<Class<?>> referencedTypes(ClassLoader loader, List<Class<?>> interfaces) {var types = new HashSet<Class<?>>();for (var intf : interfaces) {for (Method m : intf.getMethods()) {// 不能为static方法if (!Modifier.isStatic(m.getModifiers())) {addElementType(types, m.getReturnType());addElementTypes(types, m.getSharedParameterTypes());addElementTypes(types, m.getSharedExceptionTypes());}}}return types;
}

validateProxyInterfaces方法检查是否是接口,

private static void validateProxyInterfaces(ClassLoader loader, List<Class<?>> interfaces, Set<Class<?>> refTypes) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size());for (Class<?> intf : interfaces) {// 检查是否为接口if (!intf.isInterface()) {throw new IllegalArgumentException(intf.getName() + " is not an interface");}// 检查是否为隐藏类:JDK15引入新特性if (intf.isHidden()) {throw new IllegalArgumentException(intf.getName() + " is a hidden interface");}// 检查是否是密封类:JDK17引入新特性if (intf.isSealed()) {throw new IllegalArgumentException(intf.getName() + " is a sealed interface");}// 验证类加载器是否将此接口的名称解析为同一个Class对象,下同ensureVisible(loader, intf);// 检查是否已经生成过此接口的代理类if (interfaceSet.put(intf, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + intf.getName());}}for (Class<?> type : refTypes) {ensureVisible(loader, type);}
}

为什么JDK动态代理只能代理接口

面试时常见的问题之一,参考上面的源码分析,实际上还可以补充回答:JDK动态代理对密封类,隐藏类不生效,即不能代理密封类,隐藏类。

cglib

Code Generation Library,一款高性能Code生成类库的开源组件,可在运行期扩展Java类与实现Java接口,很多其他开源组件都在使用cglib

net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调类型,经常被基于代理的AOP用来实现拦截方法的调用,接口只定义一个方法:

public interface MethodInterceptor extends Callback {// object是代理对像,method是拦截方法,args是方法参数。原来的方法可通过使用Method对象的一般反射调用,或使用MethodProxy对象调用,后者更快Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}	

实例

创建代理类:

public class CglibProxy implements MethodInterceptor {private final Enhancer enhancer = new Enhancer();Object getProxy(Class<?> clazz) {// 设置创建子类的类enhancer.setSuperclass(clazz);enhancer.setCallback(this);return enhancer.create();}/*** 拦截所有目标类方法的调用*/@Overridepublic Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("日志开始...");// 代理类调用父类的方法Object res = proxy.invokeSuper(obj, args);System.out.println("日志结束...");return res;}
}

测试类:

public class Client {public static void main(String[] args) {CglibProxy proxy = new CglibProxy();Train t = (Train) proxy.getProxy(Train.class);t.move();}static class Train {void move() {System.out.println("火车行驶中...");}}
}

源码分析

基于cglib最新版3.3.0来分析,从enhancer.create()入手,调用createHelper方法:

private Object createHelper() {// 校验callbackTypes、filter是否为空,及相应处理策略preValidate();Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,ReflectUtils.getNames(interfaces),filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),callbackTypes,useFactory,interceptDuringConstruction,serialVersionUID);this.currentKey = key;Object result = super.create(key);return result;
}

核心方法是create:

protected Object create(Object key) {try {ClassLoader loader = getClassLoader();// 查询缓存Map<ClassLoader, ClassLoaderData> cache = CACHE;ClassLoaderData data = cache.get(loader);// DCLif (data == null) {synchronized (AbstractClassGenerator.class) {cache = CACHE;data = cache.get(loader);if (data == null) {// 构建缓存Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);data = new ClassLoaderData(loader);newCache.put(loader, data);CACHE = newCache;}}}this.key = key;Object obj = data.get(this, getUseCache());if (obj instanceof Class) {// 用于向后兼容return firstInstance((Class) obj);}// 真正创建代理对象return nextInstance(obj);} catch (RuntimeException e) {throw e;} catch (Error e) {throw e;} catch (Exception e) {throw new CodeGenerationException(e);}
}

不管是firstInstance还是nextInstance,最后都是调用ReflectUtils.newInstance方法:

public static Object newInstance(final Constructor cstruct, final Object[] args) {boolean flag = cstruct.isAccessible();try {if (!flag) {cstruct.setAccessible(true);}// 使用JDKreturn cstruct.newInstance(args);} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {throw new CodeGenerationException(e);} finally {if (!flag) {cstruct.setAccessible(flag);}}
}

最后使用JDK源码Constructor.newInstance(args);,因此cglib不能对声明为final的方法进行代理,因为cglib原理是动态生成被代理类的子类。

区别

主要区别:

  • JDK:利用拦截器(实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;
  • cglib:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

具体来说:
JDK动态代理只能针对实现接口的类生成代理(实例化一个类)。此时代理对象和目标对象实现相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。

cglib是针对类实现代理,主要是对指定的目标类生成一个子类(没有实例化一个类),覆盖其中的方法,通过方法拦截技术拦截所有父类方法的调用。

使用区别:

  • JDK不能用于非接口类、隐藏类、(未经允许扩展的)密封类(的子类)
  • cglib不能用于final方法、隐藏类、同上

Spring AOP

Spring AOP基于JDK Proxy和cglib来生成代理对象,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认策略是如果目标类是接口,则使用JDK动态代理,如果目标对象没有实现接口,则默认会采用cglib代理。

代理模式将继承模式和关联模式结合在一起使用,是两者的综合体,不过这个综合体的作用倒不是解决对象注入的问题,而是为具体操作对象找到一个保姆或者是秘书,对外代表具体的实例对象,实例对象的入口和出口都是通过这个二号首长,具体的实例对象是一号首长,一号首长是要干大事的,所以一些事务性,重复性的工作例如泡茶,安排车子,这样的工作是不用劳烦一号首长的大驾,而是二号首长帮忙解决的,这就是AOP的思想。AOP解决程序开发里事务性,和核心业务无关的问题,但这些问题对于业务场景的实现是很有必要的,在实际开发里AOP也是节省代码的一种方式。

AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,大中型应用都会涉及到的持久化、事务、安全、日志和调试等。

实现AOP的技术,主要分为两大类:

  • 动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
  • 静态织入的方式,引入特定的语法创建Aspect,从而使得编译器可以在编译期间织入有关Aspect代码。

拓展

代理服务器

一般有正向代理、反向代理。

正向代理

一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。如通过Chrome的SwitchSharp访问外网。

反向代理

反向代理:以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。这个服务器没有保存任何网页的真实数据,所有的静态网页或者CGI程序,都保存在内部的Web服务器上。因此对反向代理服务器的攻击并不会使得网页信息遭到破坏,这样就增强Web服务器的安全性。

反向代理经常和CDN一起工作,其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置反向代理节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决Internet网络拥挤的状况,提高用户访问网站的响应速度。

区别

正向代理解决的是客户端访问互联网的问题,客户端知道目标的;
反向代理解决的是互联网收到客户端请求,如何把请求转到内网服务器的问题,不知道目标,代理的是服务端。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

测试分类篇

按测试对象划分 这里可以分为界面测试, 可靠性测试, 容错率测试, 文档测试, 兼容性测试, 安装卸载测试, 安全测试, 性能测试, 内存泄露测试. 界面测试 界面测试&#xff08;简称UI测试)&#xff0c;指按照界面的需求&#xff08;一般是UI设计稿&#xff09;和界面的设计规则…

DOS攻击实验

实验背景 Dos 攻击是指故意的攻击网络协议实现的缺陷或直接通过野蛮手段&#xff0c;残忍地耗尽被攻击对象的资源&#xff0c;目的是让目标计算机或网络无法提供正常的服务或资源访问&#xff0c;使目标系统服务系统停止响应甚至崩溃。 实验设备 一个网络 net:cloud0 一台模…

基于微信小程序+SpringBoot+Vue的儿童预防接种预约系统(带1w+文档)

基于微信小程序SpringBootVue的儿童预防接种预约系统(带1w文档) 基于微信小程序SpringBootVue的儿童预防接种预约系统(带1w文档) 开发合适的儿童预防接种预约微信小程序&#xff0c;可以方便管理人员对儿童预防接种预约微信小程序的管理&#xff0c;提高信息管理工作效率及查询…

24暑假算法刷题 | Day22 | LeetCode 77. 组合,216. 组合总和 III,17. 电话号码的字母组合

目录 77. 组合题目描述题解 216. 组合总和 III题目描述题解 17. 电话号码的字母组合题目描述题解 77. 组合 点此跳转题目链接 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输…

移动UI:排行榜单页面如何设计,从这五点入手,附示例。

移动UI的排行榜单页面设计需要考虑以下几个方面&#xff1a; 1. 页面布局&#xff1a; 排行榜单页面的布局应该清晰明了&#xff0c;可以采用列表的形式展示排行榜内容&#xff0c;同时考虑到移动设备的屏幕大小&#xff0c;应该设计合理的滚动和分页机制&#xff0c;确保用户…

贪心算法.

哈夫曼树 哈夫曼树&#xff08;Huffman Tree&#xff09;&#xff0c;又称为霍夫曼树或最优二叉树&#xff0c;是一种带权路径长度最短的二叉树&#xff0c;常用于数据压缩。 定义&#xff1a;给定N个权值作为N个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若该树…

普乐蛙VR航天航空体验馆知识走廊VR体验带你登陆月球

VR航天航空设备是近年来随着虚拟现实&#xff08;VR&#xff09;技术的快速发展而兴起的一种新型设备&#xff0c;它结合了航天航空领域的专业知识与VR技术的沉浸式体验&#xff0c;为用户提供了前所未有的航天航空体验。以下是对VR航天航空设备的详细介绍&#xff1a; 一、设备…

UGUI优化篇--UGUI合批

UGUI合批 UGUI合批规则概述UGUI性能查看工具合批部分的特殊例子一个白色image、蓝色image覆盖了Text&#xff0c;白色image和Text哪个先渲染 Mask合批Mask为什么会产生两个drawcallMask为什么不能合批Mask注意要点 RectMask2D为什么RecMask2D比Mask性能更好主要代码RectMask2D注…

Golang | Leetcode Golang题解之第295题数据流的中位数

题目&#xff1a; 题解&#xff1a; type MedianFinder struct {nums *redblacktree.Treetotal intleft, right iterator }func Constructor() MedianFinder {return MedianFinder{nums: redblacktree.NewWithIntComparator()} }func (mf *MedianFinder) AddNum(…

MySQL中多表查询之外连接

首先先来介绍一下我做的两个表&#xff0c;然后再用他们两个举例说明。 -- 创建教师表 create table teachers( id_t int primary key auto_increment, -- 老师编号 name_t varchar(5) -- 姓名 ); -- 创建学生表 create table students( id_s int primary key auto_increment,…

数据结构——单链表OJ题(下)

目录 一、链表的回文结构 思路一&#xff1a;数组法 &#xff08;1&#xff09;注意 &#xff08;2&#xff09;解题 思路二&#xff1a;反转链表法 &#xff08;1&#xff09; 注意 &#xff08;2&#xff09;解题 二、相交链表 &#xff08;1&#xff09;思路&#…

优化算法:1.遗传算法(GA)及Python实现

一、定义 遗传算法就像是在模拟“优胜劣汰”的进化过程&#xff0c;通过选择最优秀的个体&#xff0c;交配产生下一代&#xff0c;并引入一定的变异&#xff0c;逐步优化解决问题。 二、具体步骤 初始化种群(Initialization)&#xff1a; 假设你要找到一个迷宫的最佳出口路径。…

CTF-NSSCTF[GKCTF 2021]

[GKCTF 2021]easycms 考察&#xff1a; 用扫描工具扫描目录&#xff0c;扫描到后台登录界面/admin.php 题目提示了密码是五位弱口令&#xff0c;试了试弱口令admin和12345直接成功了 任意文件下载 点击设计-->主题然后随便选择一个主题&#xff0c;点击自定义&#xff0…

故障诊断 | 基于Transformer故障诊断分类预测(Matlab)

文章目录 预测效果文章概述程序设计参考资料预测效果 文章概述 Transformer故障诊断/分类预测 | 基于Transformer故障诊断分类预测(Matlab) Transformer 模型本质上都是预训练语言模型,大都采用自监督学习 (Self-supervised learning) 的方式在大量生语料上进行训练,也就是…

CTF之网站被黑

简单看一下网页和源码没发现什么明显漏洞 那就扫描一下目录 发现了/shell.php文件&#xff0c;访问一下&#xff0c;发现是一个后台管理登录页面 别无他法只能爆破喽&#xff0c;爆破后发现密码是hack flag{25891d9e9d377f006eda3ca7d4c34c4d}

@JSONField(format = “yyyyMMddHH“)的作用和使用

JySellerItqrdDataDO对象中的字段为&#xff1a; private Date crdat; 2.数据库中的相应字段为&#xff1a; crdat datetime DEFAULT NULL COMMENT 创建时间,2. 打印出的结果为&#xff1a; “crdat”:“2024072718” 年月日时分秒 3. 可以调整format的格式 4. 这样就把Date类…

RedHat8安装Oracle19C

RedHat8安装Oracle19C 1、 更新yum源 更新yum源为阿里云镜像源&#xff1a; # 进入源目录 cd /etc/yum.repos.d/ # 删除 redhat 默认源 rm redhat.repo # 下载阿里云的centos7源 curl -O http://mirrors.aliyun.com/repo/Centos-8.repo # 替换 Centos-8.repo 中的 $releasev…

初学Mybatis之 Lombok 篇

idea 安装 Lombok 插件&#xff1a; File->Settings->Plugins->搜索 lombok 下载 在项目中导入 lombok 的 jar 包&#xff1a; <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.…

C语言程序设计之数学函数篇

程序设计之数学函数 问题1_1代码1_1结果1_1 问题1_2代码1_2结果1 _2 问题1_3代码1_3结果1_3 问题1_1 函数 f u n fun fun 的功能是计算&#xff1a; s ln ⁡ ( 1 ) ln ⁡ ( 2 ) ln ⁡ ( 3 ) ⋯ ln ⁡ ( n ) s\sqrt{\ln(1)\ \ \ln(2)\ \ \ln(3)\ \ \cdots \ \ \ln(n)\ } …

ReentrantReadWriteLock详解

目录 ReentrantReadWriteLock详解1、ReentrantReadWriteLock简介2、ReentrantReadWriteLock类继承结构和类属性3、ReentrantReadWriteLock的读写锁原理分析4、ReentrantReadWriteLock.WriteLock类的核心方法详解非公平写锁的获取非公平写锁的释放公平写锁的获取公平写锁的释放 …