【Spring】GoF 之代理模式

一、代理模式

在 Java 程序中的代理模式的作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为

  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强

  • A 对象无法和 B 对象直接交互时,也可以使用代理模式来解决

代理模式中的三大角色:

  • 目标对象(演员)

  • 代理对象(替身演员)

  • 目标对象和代理对象的公共接口(演员与替身演员相同的行为,可以让观众不知道是替身演员)

如果使用代理模式,对于客户端程序来说,客户端是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象

代理模式在代码实现上,包括两种形式:

  • 静态代理

  • 动态代理

 

二、静态代理

package org.qiu.proxy.service;/*** 订单业务接口* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:09* @since 1.0*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单信息*/void modify();/*** 查看订单详情*/void detail();
}
package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:10* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");}@Overridepublic void modify() {// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");}@Overridepublic void detail() {// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");}
}
public class Test {public static void main(String[] args) {OrderService orderService = new OrderServiceImpl();orderService.generate();orderService.detail();orderService.modify();}
}

运行结果:  

项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?

 

第一种方案

直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:10* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}

运行效果:  

需求可以满足,但显然是违背了OCP开闭原则,这种方案不可取

第二种方案

编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:24* @since 1.0*/
public class OrderServiceImplSub extends OrderServiceImpl{@Overridepublic void generate() {long begin = System.currentTimeMillis();super.generate();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();super.modify();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();super.detail();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}
public class Test {public static void main(String[] args) {OrderService orderService = new OrderServiceImplSub();orderService.generate();orderService.detail();orderService.modify();}
}

运行效果:  

这种方式可以解决,但是存在两个问题:

  • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象

  • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。

这种方案也不可取

第三种方案

使用代理模式(这里采用静态代理)

可以为 OrderService 接口提供一个代理类

package org.qiu.proxy.service;/*** 代理对象* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-16-13:32* @since 1.0*/
public class OrderServiceProxy implements OrderService {// 目标对象// 这里要使用“公共接口”类型,因为公共接口耦合度低private OrderService orderService;// 通过构造方法将目标对象传递给代理对象public OrderServiceProxy(OrderService orderService){this.orderService = orderService;}@Overridepublic void generate() {long begin = System.currentTimeMillis();// 执行目标对象的目标方法orderService.generate();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();orderService.modify();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();orderService.detail();long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));}
}

这种方式的优点:

符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的 

package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.OrderServiceImplSub;
import org.qiu.proxy.service.OrderServiceProxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-16-13:13* @since 1.0*/
public class Test {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderService proxy = new OrderServiceProxy(target);// 调用代理对象的代理方法proxy.generate();proxy.modify();proxy.detail();}
}

运行效果:  

以上就是代理模式中的静态代理,其中:

OrderService 接口是代理类和目标类的共同接口

OrderServiceImpl 是目标类

OrderServiceProxy 是代理类

思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用

 

三、动态代理 

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题

在内存当中动态生成类的技术常见的包括:

  • JDK 动态代理技术:只能代理接口

  • CGLIB 动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比 JDK 动态代理要好(底层有一个小而快的字节码处理框架ASM)

  • Javassist 动态代理技术:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态"AOP"框架。

JDK 动态代理 

package org.qiu.proxy.service;/*** 订单业务接口* @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-08:23* @since 1.0*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单信息*/void modify();/*** 查看订单详情*/void detail();
}
package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-08:23* @since 1.0*/
public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {// 模拟网络延迟try {Thread.sleep(1024);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成!");}@Overridepublic void modify() {// 模拟网络延迟try {Thread.sleep(512);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改!");}@Overridepublic void detail() {// 模拟网络延迟try {Thread.sleep(800);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单详情......");}
}

使用静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类 UserServiceProxy!在动态代理中UserServiceProxy 代理类是可以动态生成的,这个类不需要写。我们直接写客户端程序即可:  

package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-08:22* @since 1.0*/
public class Test {public static void main(String[] args) {OrderService target = new OrderServiceImpl();/*** 参数解析:* 参数一:Classloader loader 类加载器*      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个* 参数二:Class<?>[] interfaces 代理类要实现的接口*      代理类和目标类要实现同一个接口或同一些接口* 参数三:InvocationHandler h 调用处理器对象(是一个接口)*      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)*/OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}
package org.qiu.proxy.service;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:01* @since 1.0*/
public class TimeInvocatioinHandler implements InvocationHandler {private Object target;public TimeInvocatioinHandler(Object target) {this.target = target;}/*** invoke 什么时候被调用?*  当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法被调用** @param proxy     代理对象的引用* @param method    目标对象上的目标方法* @param args      目标方法上的实际参数* @return          若代理对象代用代理方法之后需要返回结果的话,invoke方法必须将目标对象目标方法执行结果继续返回*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long begin = System.currentTimeMillis();// 调用目标对象上的目标方法Object resultValue = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println("耗时(毫秒):" + (end - begin));return null;}
}

上面调用 JDK 自带的方法比较繁琐,这里可以封装一个工具类,方便使用:  

package org.qiu.proxy.util;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.TimeInvocatioinHandler;import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.util* @date 2022-11-20-09:12* @since 1.0*/
public class ProxyUtil {/*** 获取代理对象(JDK动态代理)* @param target* @return*/public static Object newProxyInstance(Object target){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));}}
package org.qiu.proxy.client;import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-08:22* @since 1.0*/
public class Test {public static void main(String[] args) {OrderService target = new OrderServiceImpl();/*** 参数解析:* 参数一:Classloader loader 类加载器*      JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个* 参数二:Class<?>[] interfaces 代理类要实现的接口*      代理类和目标类要实现同一个接口或同一些接口* 参数三:InvocationHandler h 调用处理器对象(是一个接口)*      在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)*//*OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));*/// 工具类封装OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}

 

CGLIB 动态代理

CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。

使用CGLIB,需要引入它的依赖:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

准备一个没有实现接口的类,如下:  

package org.qiu.proxy.service;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:22* @since 1.0*/
public class UserService {public void login(){System.out.println("用户正在登录系统....");}public void logout(){System.out.println("用户正在退出系统....");}
}

使用 CGLIB 在内存中为 UserService 类生成代理类,并创建对象:  

package org.qiu.proxy.client;import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-09:23* @since 1.0*/
public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(方法拦截器对象);// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor

编写MethodInterceptor接口实现类:

package org.qiu.proxy.service;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:24* @since 1.0*/
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return null;}
}

 

MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:

第一个参数:目标对象

第二个参数:目标方法

第三个参数:目标方法调用时的实参

第四个参数:代理方法

在MethodInterceptor的intercept()方法中调用目标以及添加增强:

package org.qiu.proxy.service;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.service* @date 2022-11-20-09:24* @since 1.0*/
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 前增强long begin = System.currentTimeMillis();// 调用目标Object retValue = methodProxy.invokeSuper(target, objects);// 后增强long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 一定要返回return retValue;}
}

回调已经写完了,可以修改客户端程序了:  

package org.qiu.proxy.client;import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;/*** @author 秋玄* @version 1.0* @email qiu_2022@aliyun.com* @project Spring* @package org.qiu.proxy.client* @date 2022-11-20-09:23* @since 1.0*/
public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(new TimerMethodInterceptor());// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();userServiceProxy.logout();}
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:  

  • --add-opens java.base/java.lang=ALL-UNNAMED

  • --add-opens java.base/sun.net.util=ALL-UNNAMED

 

一  叶  知  秋,奥  妙  玄  心

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

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

相关文章

如何在 Python 中使变量不可继承

1. 问题背景 在 Python 中&#xff0c;子类可以继承父类的属性和方法。但是&#xff0c;有时我们希望子类不能继承父类的某些属性或方法。这种情况下&#xff0c;该如何做呢&#xff1f; 2. 解决方案 解决方案一&#xff1a;使用双下划线前缀 Python 中的双下划线前缀用于表…

仓库管理流程详解(附作业流程图)

仓库管理流程在企业的日常运营中至关重要。它不仅是物资流转的核心环节&#xff0c;更关乎着企业的运营效率、成本控制和客户服务水平。一个高效、规范的仓库管理流程能够确保货物从入库到出库的各个环节有序进行&#xff0c;减少资源浪费和时间成本&#xff0c;同时帮助企业实…

Hive Sampling 抽样函数

Hive Sampling 抽样函数 1.random随机抽样 2.数据块抽样 3.分桶表抽样

【静态分析】软件分析课程实验A2-常量传播和Worklist求解器

Tai-e官网&#xff1a; 概述 | Tai-e 参考&#xff1a; https://www.cnblogs.com/gonghr/p/17979609 -------------------------------------------------------- 1 作业导览 为 Java 实现常量传播算法。实现一个通用的 worklist 求解器&#xff0c;并用它来解决一些数据…

超便捷备忘录共享方法 文字文件都可共享

在这个信息爆炸的时代&#xff0c;备忘录已成为我们生活中不可或缺的小助手。它记录着我们的工作计划、待办事项、灵感闪现&#xff0c;甚至是那些温馨的家庭琐事。然而&#xff0c;当我们在不同的设备间穿梭&#xff0c;如何在手机、电脑&#xff0c;甚至是不同品牌的手机之间…

理解机器学习中的类别不平衡问题

大家好&#xff0c;实际世界的数据集通常是杂乱的,当不同类别之间的样本分布不均匀时&#xff0c;就会出现类别不平衡。或者说&#xff0c;某些类别的样本比其他类别多得多。例如&#xff0c;考虑一个信用卡违约数据集&#xff0c;信用卡违约是一个相对较少发生的事件&#xff…

速锐得深入解析吉利几何CAN总线数据通信网络的拓扑层级框架技术

在现代汽车工业中&#xff0c;车辆的电子控制单元&#xff08;ECU&#xff09;之间的通信至关重要。这种通信大多通过控制器局域网络&#xff08;CAN&#xff09;总线实现&#xff0c;它是德国BOSCH公司于20世纪80年代初开发的一种串行数据通信协议。随着技术的不断进步&#x…

读人工智能时代与人类未来笔记01_重塑人类社会秩序

1. AlphaZero 1.1. 2017年年底&#xff0c;由谷歌旗下DeepMind公司开发的人工智能程序AlphaZero击败了当时世界上最强大的国际象棋程序Stockfish 1.1.1. AlphaZero对Stockfish的百场战绩是28胜72平0负&#xff0c;可以说获得了压倒性的胜利 1.1.2. …

安卓手机数据恢复全攻略:从备份到专业软件一网打尽!

随着科技的飞速发展&#xff0c;我们的生活中越来越离不开手机。然而&#xff0c;在使用手机的过程中&#xff0c;我们可能会遇到数据丢失的问题。对于安卓手机用户来说&#xff0c;如何有效地恢复丢失的数据是一个值得探讨的问题。本文将为您介绍安卓手机数据恢复的全攻略&…

西部首个全域直播基地,打造西部直播基地领军形象

天府锋巢直播产业基地作为西部直播产业的领军者&#xff0c;以其前瞻性的战略布局和卓越的服务体系&#xff0c;正加速推动全域直播的快速发展&#xff0c;助力直播产业实现新升级。该基地作为成都规模最大的直播基地&#xff0c;以加快全域直播为核心目标&#xff0c;通过促进…

利用香港多IP服务器优化网站访问速度的关键策略?

利用香港多IP服务器优化网站访问速度的关键策略? 随着数字化时代的不断发展&#xff0c;网站的全球访问速度成为企业吸引用户、提升竞争力的重要因素。特别对于跨国企业而言&#xff0c;如何确保全球用户都能享受到稳定快速的访问体验显得尤为重要。在这一背景下&#xff0c;…

生成ssl证书并配置到nginx

生成ssl证书并配置到nginx 安装证书生成工具 apt-get update apt install software-properties-common add-apt-repository ppa:certbot/certbot apt-get update apt-get install certbot python3-certbot-nginx生成证书 首先在新网上创建一个A链接&#xff0c;域名与服务器做…

考研OSchap4文件管理chap5磁盘管理(部分)

目录 一、整体认知 1.文件的定义 250 2.文件的属性 251 3.文件内部应该如何被组织(逻辑结构) 256 4.文件之间应该如何被组织起来(目录结构) 252 5.OS应该向上提供哪些功能 253 6.文件应该如何存放在外存中(物理结构) 258 7.OS如何管理外存中的空闲块(存储空间的管理) 25…

牛客Java面试题【面试】

牛客Java面试题【面试】 前言推荐牛客Java面试题【面试】第2章 Java笔面试高频考点&解题技巧1. Java基础[2.1 一、java-基础-1](https://www.nowcoder.com/study/live/689/2/1)1.1 为什么Java代码可以实现一次编写、到处运行&#xff1f;1.2 一个Java文件里可以有多个类吗&…

有必要买超声波洗眼镜机吗?力荐四款实力超群超声波清洗机

在日常生活中&#xff0c;眼镜不仅仅是我们视野的延展&#xff0c;像太阳眼镜&#xff0c;也是有着独特的作用。但是&#xff0c;在每天的使用过程中&#xff0c;眼镜片表面难免会有灰尘&#xff0c;污迹&#xff0c;甚至油渍&#xff0c;这些都会对镜片的材质产生一定的损伤&a…

数据结构-树概念基础知识

根结点&#xff1a;非空树中无前驱节点的结点 结点度&#xff1a;结点拥有的子树数或子节点数或后继节点数 树的度&#xff1a;树内各结点的度的最大值 叶子&#xff1a;终端节点&#xff0c;度为0 祖先&#xff1a;从根到该节点所经分支上的所有结点 子孙&#xff1a;以某结点…

Springboot + xxlJob注意事项

1. 部署 就是这个地址: https://gitee.com/xuxueli0323/xxl-job 由于xxl-job的思想是 调度中心负责调度任务,然后有执行器负责接受调度的信息,然后根据调度,执行任务中的具体逻辑 将 xl-job-admin 启动起来,操作xl-job-admin这个文件夹下的配置文件即可: 创建数据库 执行sql…

外面收费888的CR视频批量剪辑软件短视频去重搬运过豆+去水印批量【剪辑脚本+详细教程】

软件功能&#xff1a; 去水印 加水印 裁剪 画中画 去头尾 加头尾 变速 文本 背景音 画面调整 分辨率 虚化 宫格分屏 旋转 帧率 抽帧 动态缩放 码率调整 合并 分割 专场 镜像重置 补针 定帧 音频抽取 视频抽取 字幕 生成gif 静音移除 画面拼接 图转视频等等 不限制次数&#x…

AICloud 分论坛 07-AI原生数据库与RAG【文档管理】

https://github.com/infiniflow/infinityhttps://infiniflow.org/视频观看&#xff1a;https://www.bilibili.com/video/BV16m411y7xW/?spm_id_from333.999.0.0&vd_sourceae7b192be069682aabc96350ba419fc5 简介 为LLM应用程序构建的AI原生数据库&#xff0c;提供令人难…

自查判断海外IP地址的质量,方式有这些!

为了保障海外代理IP的使用感受&#xff0c;在我们购买海外IP地址后&#xff0c;可以对其可靠性和安全性进行自查&#xff0c;避免潜在问题的发生&#xff0c;保障网络体验。 我们可以根据一下方法来进行自查判断&#xff1a; IP黑名单检查&#xff1a;使用IP黑名单检查工具&am…