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

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

这里写目录标题

    • 前言
    • 温馨提示
    • 手把手带你解析 @MapperScan 源码
    • 手把手带你解析 @MapperScan 源码细节剖析
    • 工厂模式+Jdk 代理手撕脚手架,复刻 BeanDefinitionRegistryPostProcessor
    • 手撕 FactoryBean
    • 代理 Mapper 在 Spring 源码中的生成流程
    • 手撕 MapperProxyFactory
    • 手撕增强逻辑 InvocationHandler
    • 源码级别解读 Mapper 要被设计成接口的原因
    • 自定义 Executor 实现,框架 Dao 层
    • 手写基础架构效果演示
    • 总结

前言

最近在码云搜 Es 的开源项目学学技术,无意间搜到 Easy-Es 这么一个项目,里面的用法和 Mybatis-Plus 一模一样,当时心想我擦,这个人是直接悟透了 Mybatis-Plus 吗,虽然老早前看过源码。之前大概看了一下,就是对 Mapper 对象进行代理,植入了一些自定义逻辑而已,没仔细看过实现细节,现在网上居然有人直接又造了一个轮子,直呼 666,于是乎深入看了 Mybatis-Plus 是如何生成 Mapper 代理对象的全部源码,并且一比一复刻出来了。

温馨提示

阅读以下文章了解前置知识对理解本文更有帮助

  1. 深入jdk动态代理源码解析
  2. 模拟jdk动态代理(完整版)
  3. Factorybean与BeanFactory的区别
  4. 手把手debug自动装配源码、顺带弄懂了@Import等相关的源码(全文3w字、超详细)
  5. spring 源码解析(配图文讲解)顺带搞懂了循环依赖、aop底层实现

手把手带你解析 @MapperScan 源码

废话不多说直接步入正题,我们在使用 Mybatis 的时候要要设置 @MapperScan 扫描对应的 Mapper 接口,一步步点进去
在这里插入图片描述
在这里插入图片描述
发现其实就是注册了 MapperScannerConfigurer 这个 Bean ,都是些常用套路。然后发现 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor、InitializingBean。

  1. BeanDefinitionRegistryPostProcessor:Spring 为我们提供的扩展点,让程序员可以自己干预 Bean 的生成

  2. InitializingBean:在 Bean 填充属性(populateBean)完成后会调用

在这里插入图片描述
直接看重写了 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法就行,看下图可以看到就是利用 ClassPathMapperScanner (路径扫描器)去扫描指定包下面的类,然后生成对应的 BeanDefinition 注册到 BeanDefinitionMap 中,然后 Spring 会将 BeanDefinitionMap 中的所有 BeanDefinition 生成 Bean 放到 Spring 单例池里面提供给程序员使用。
在这里插入图片描述
然后来到 scan 源码,cmd+art+b 查看 doScan 实现类,点进第二个,第一个是 Spring 实现的,而我们看的是 Mybatis 的源码这里大家要注意一下!

在这里插入图片描述
然后你会发现扫描完 basePackages 下的类生成对应的 BeanDefinition ,后还会去处理一下这些 BeanDefinition,click 进去。
在这里插入图片描述
发现得到的所有 Mapper 的 BeanDefinition 的 BeanClass 都被替换成了mapperFactoryBeanClass (工厂 bean)

在这里插入图片描述
在这里插入图片描述
到这里我大概就明白了,所有的 Mapper BeanDefinition 统一设置为 MapperFactoryBean 类型,最终生成的 Bean 本质 Class 是 MapperFactoryBean 但是名字依然是原来的名字,然后通过代理工厂统一生成代理对象(这也是很多开源框架的常用套路)。接下来验证一下我的猜想。看一下 MapperFactoryBean 构造实现了 FactoryBean 。
在这里插入图片描述
当我们的项目中使用了如下代码时,拿到的 Bean 其实是在紧挨上图一中的 getObject 方法中创建的。

@Autowired
UserMapper userMapper;

然后进入 getMapper 方法里面。看到确实是通过 MapperProxyFactory (代理工厂)生成的代理对象 Mapper。

在这里插入图片描述

看到这你是不是觉得源码也不过如此,对于整个简单的流程虽然走完了,但是作为一个要进行开发整个轮子的开发者来说,还远远不够。还需要了解更多细节

  1. 如何将指定包路径下的所有类生成 BeanDefinition ?。
  2. MapperProxyFactory 如何初始化,并且 MapperProxyFactory 如何根据感知生产什么类型的代理对象等

手把手带你解析 @MapperScan 源码细节剖析

这部分的文章读者可选择自行跳过,

knownMappers 中的数据什么时候初始化的?

回到 MapperFactoryBean 类中可以看到 checkDaoConfig 方法左侧有一个这个小图标,说明就是抽象接口的实现类,一般为了简化操作很多框架包括我也喜欢利用抽象接口封装逻辑

在这里插入图片描述
点击来到了上层的实现类,发现还被包裹了一层逻辑接着点向上的那个图标
在这里插入图片描述
来到最顶层的 checkDaoConfig 发现原来 MapperFactoryBean 居然实现了 InitializingBean 接口,当 MapperFactoryBean 属性填充完成以后,进行调用 afterPropertiesSet 方法,触发我们的 checkDaoConfig 方法调用。

在这里插入图片描述

最终会发现在进行 addMapper 的时候会以 key:mapperInterface ,value:MapperProxyFactory 的键值对放到 knownMappers 里面,而 mapperInterface 其实就UserMapper 的 Class。
在这里插入图片描述
在这里插入图片描述

Mapper 是使用什么代理创建的?

答:看一下 MapperProxyFactory 源码得知是用的 Jdk 代理,直接代理接口

在这里插入图片描述

如何动态的批量创建、修改 Bean ?

答:通过实现 Spring 提供的扩展接口 BeanDefinitionRegistryPostProcessor 动态注册、修改 BeanDefinition 即可。

如何实现动态的将一个普通 Bean 改成工厂 Bean ?

答:通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下

genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass());   
genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);

源码中的 mapperInterface 是什么东西?

答:Mapper 对象的 Class。举个例子当项目中用到了

@Autowired
UserMapper userMapper;

此时的 mapperInterface 就是 UserMapper.Class

为什么 Mapper 的代理对象能转换成目标对象?

了解 Jdk 动态代理的都知道,代理对象不能转换成目标对象,只能装换成目标对象的接口实现类或者 Proxy 对象,原因就是如下,可以看到代理对象和目标对象半毛钱关系都没有。

代理对象 extends proxy implments 目标对象实现接口

那为什么 UserMapper 的代理对象但是还能用 UserMapper 接收呢?项目中应该这样使用才对啊!!

@Autowired
Proxy userMapper;

工厂模式+Jdk 代理手撕脚手架,复刻 BeanDefinitionRegistryPostProcessor

里面的逻辑主要就是扫描指定包下面的类,生成对应的 BeanDefinition,然后自定义一个我们自己的后置处理器,将所有 BeanDefinition 替换成工厂 Bean。读者可自行封装对应的后置处理器,方便其他使用者进行扩展。整个流程对标 ClassPathMapperScanner 源码中的 doScan 逻辑。

/*** 扫描哪些包是 mapper,并统一设置类型为 BaseFactoryBean*/
@Slf4j
@Component
public class RegistryPostProcessorConfig implements BeanDefinitionRegistryPostProcessor {private Class<? extends BaseFactoryBean> mapperFactoryBeanClass = BaseFactoryBean.class;@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {//扫描指定路径下的 BeanDefinitionSet<BeanDefinitionHolder> beanDefinitions = scan();if (beanDefinitions.size() >= 1) {//后置处理器:全部替换成工厂 BeanfactoryBeanDefinitionPostProcess(beanDefinitions);}//注册 BeanDefinitionregister(beanDefinitions,registry);log.info("自定义 Mapper 扫描注册完成");}/*** 扫描指定包下面的类,包装成一个个的 BeanDefinitionHolder,我这里就简单写写直接指定了*/public Set<BeanDefinitionHolder> scan() {HashSet<BeanDefinitionHolder> beanDefinitions = new HashSet<>();GenericBeanDefinition scanBeanDefinition = new GenericBeanDefinition();scanBeanDefinition.setBeanClassName("userMapper");scanBeanDefinition.setBeanClass(UserMapper.class);GenericBeanDefinition scanBeanDefinition2 = new GenericBeanDefinition();scanBeanDefinition2.setBeanClassName("studentMapper");scanBeanDefinition2.setBeanClass(StudentMapper.class);beanDefinitions.add(new BeanDefinitionHolder("userMapper",scanBeanDefinition));beanDefinitions.add(new BeanDefinitionHolder("studentMapper",scanBeanDefinition2));return beanDefinitions;}public void factoryBeanDefinitionPostProcess(Set<BeanDefinitionHolder> beanDefinitions) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);genericBeanDefinition.setLazyInit(false);/*** 设置 bean 创建的构造 class,必须设置不然 bean 无法被创建*/genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition()).getBeanClass());genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);}}public void register(Set<BeanDefinitionHolder> beanDefinitions, BeanDefinitionRegistry registry) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {/*** BeanDefinition 重置了 BeanClass 为 BaseFactoryBean 后,对应的 BeanClassName 会自动变成 com.zzh.service2.structure.factory.bean.BaseFactoryBean* 造成所有的 Mapper 接口的 BeanDefinition 的 BeanClassName 都是 com.zzh.service2.structure.factory.bean.BaseFactoryBean 导致注册报错!!!* 因此自定义包装 BeanDefinitionHolder 对象,设置原始 BeanName* 例如:BeanDefinitionHolder(key->userMapper,value->BeanDefinition)* BeanDefinitionHolder(key->studentMapper,value->BeanDefinition)*/registry.registerBeanDefinition(beanDefinitionHolder.getBeanName(), beanDefinitionHolder.getBeanDefinition());}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}}

手撕 FactoryBean

实现 FactoryBean 接口,同时设置成泛型,让任何类型的 Mapper 接口都是转换成此 FactoryBean,当 Spring 进行属性填充完成之后,进行初始化 Bean 的时候会调用 InitializingBean 接口里面的方法,此时我们将 UserMapper.Class 放到一个临时容器中,等 BaseFactoryBean.getObject 方法被调用的时候,再去容器里面拿到 UserMapper.Class 进行 Jdk 代理创建代理对象。

@Data
public class BaseFactoryBean<T> implements FactoryBean<T>, InitializingBean {/*** > 如何实现动态的将一个普通 Bean 改成工厂 Bean ?* 通过设置 BeanDefinition 的 BeanClass、ConstructorArgumentValues 替换成工厂 Bean 的 Class 即可。关键代码如下* ```javascript* genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(((GenericBeanDefinition) beanDefinition).getBeanClass());* genericBeanDefinition.setBeanClass(mapperFactoryBeanClass);* ```*/public BaseFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}private Class<T> mapperInterface;/*** 通过 MapperProxyFactory 工厂统一生产代理对象*/@Overridepublic T getObject() throws Exception {return (T) BaseMapperRegistry.getMapper(mapperInterface).newInstance(DaoTemplateFactory.getInstance().getDaoTemplate());}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic void afterPropertiesSet() {BaseMapperRegistry.addMapper(this.mapperInterface);}@Overridepublic boolean isSingleton() {return true;}
}

补充一嘴 Spring 中的源码逻辑,BeanDefinitionMap 中所有的 BeanDefinition 都会走
CreateBean 的流程,先是调用 createBeanInstance 方法创建一个实例对象,然后调用 populateBean 方法为实例对象填充属性,接着才是调用 InitializingBean 里面的方法。可以看到此时的 mapperInterface 是 UserMapper.Class

在这里插入图片描述

代理 Mapper 在 Spring 源码中的生成流程

当创建好 UserMapper 这个 Bean 的时候,会调用 getObjectForBeanInstance 方法获取其实例,发现 UserMapper 是个工厂 Bean,于是乎调用 getObject 方法,走我们的 Jdk 创建代理对象的逻辑,最终放到 Ioc 容器里面的是我们自己创建的代理对象!
在这里插入图片描述

然后顺着栈帧来到 getObject,到此整个流程结束!

在这里插入图片描述

手撕 MapperProxyFactory

根据目标对象的 Class 生成代理对象,同时 InvocationHandler 里面织入我们手写的 DaoTemplate,用来与数据库进行交互。
亮点代码只有一行:由于目标对象自身是一个 Mapper 接口,参数二实现类的接口用的是自己本身 new Class[]{mapperInterface} 这样生成的代理对象就可以转换成目标对象了。

T proxyInstance = (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);
@Slf4j
public class MapperProxyFactory<T> {/*** 被代理对象 Class*/@Getterprivate final Class<T> mapperInterface;private ConcurrentHashMap methodCaches = new ConcurrentHashMap<Object, Object>();public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public T newInstance(MapperProxyInvoke<T> baseMapperProxy) {T proxyInstance = (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, baseMapperProxy);log.info("proxyInstance instanceof BaseMapper:{}", proxyInstance instanceof BaseMapper);log.info("proxyInstance instanceof BaseMapper:{}", proxyInstance instanceof UserMapper);return proxyInstance;}/*** Mybatis-Plus 封装了 SqlSession 对象操作 db,我这里也简单封装一个 DaoTemplate 做做样子* @param daoTemplate* @return*/public T newInstance(DaoTemplate daoTemplate) {MapperProxyInvoke<T> baseMapperProxy = new MapperProxyInvoke<T>(daoTemplate, mapperInterface, methodCaches);return newInstance(baseMapperProxy);}/*** 为啥jdk生成的代理对象居然不支持类型转换为目标对象?* https://blog.csdn.net/qq_42875345/article/details/115413716*/public static void main(String[] args) {test1();test2();}/*** 强制代理对象实现 UserMapper 接口,从而实现 jdk生成的代理对象支持转换为目标对象!!!!!!!* 关键代码:new Class[]{UserMapper.class}* 这也是为什么 Mapper 要设计成接口的原因!!!!!!!* 代理对象结构:代理对象 extends proxy implments UserMapper*/static void test1() {Mapper proxyInstance = (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.err.println("代理前置输出");return null;}});System.err.println(proxyInstance instanceof UserMapper); //trueSystem.err.println(proxyInstance instanceof BaseMapper);System.err.println(proxyInstance instanceof Mapper);}/*** 普通 Jdk 代理对象只实现目标对象的实现接口* 代理对象结构:代理对象 extends proxy implments 目标对象实现接口*/static void test2() {Mapper proxyInstance = (Mapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), UserMapper.class.getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.err.println("代理前置输出");return null;}});System.err.println(proxyInstance instanceof UserMapper); //falseSystem.err.println(proxyInstance instanceof BaseMapper);System.err.println(proxyInstance instanceof Mapper);}}

手撕增强逻辑 InvocationHandler

实现了 InvocationHandler 接口,每当代理 Mapper 中的方法被调用的时候,都会执行 invoke 中的逻辑。里面分默认方法(被 default 修饰的方法)与 db 查询的方法

/*** 代理 Mapper 增强逻辑*/
@Slf4j
public class MapperProxyInvoke<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final DaoTemplate daoTemplate;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;public MapperProxyInvoke(DaoTemplate daoTemplate, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {this.daoTemplate = daoTemplate;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}/**** @param proxy 生成的代理对象* @param method 被调用的目标对象方法* @param args 被调用的目标对象方法中的参数* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info("代理对象前置输出!!!!");try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {/*** Mapper 自带的默认方法走这调用(userMapper.say())*/return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}/*** (userMapper.seleteById(1))走这调用* 此处需要 Method 与 daoTemplate 中的方法名称、参数做匹配然后调用 daoTemplate 中的方法* 源码中也是这么干的,我懒这里直接硬编码匹配 seleteById 了*/return daoTemplate.seleteById(1);
//        mybatis 源码中还做了方法缓存加快处理速度
//        final MapperMethod mapperMethod = cachedMapperMethod(method);
//        return mapperMethod.execute(sqlSession, args);}private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class<?> declaringClass = method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);}
}

源码级别解读 Mapper 要被设计成接口的原因

这里也贴一下 UserMapper 的代码吧,也解释一下 Mapper 为什么要采用接口的形式

/*** UserMapper 只能是接口,如果 UserMapper 为类,生成的代理对象不能转换为 UserMapper* 只能转换为 proxy、或者 BaseMapper ,原因:代理对象 extends proxy implments BaseMapper* 但是我们需要 @Autowire UserMapper 这样使用。需要代理对象为 UserMapper 类型,因此 UserMapper 只能是接口* 让生成的代理对象 extends proxy implments UserMapper*/
public interface UserMapper extends BaseMapper<User> {default String say() {return "UserMapper say";}
}

自定义 Executor 实现,框架 Dao 层

代理对象会根据被调用的方法匹配 DaoTemplate 中的方法进行执行,在这里面可以自行封装类似于 Mybatis 二级缓存,多级 Executor ,动态数据源切换的逻辑,工程量巨大,我这里只提供思路,简单查个库给大家演示一下设计原理。到此所有组件全部开发完成。

/*** 封装原始的 jdbc 逻辑,可扩展组件:多级缓存查询、多级 Executor 查询、数据库连接池切换等等*/
public class DaoTemplate {String driver = "com.mysql.cj.jdbc.Driver";String url = "url";String username = "root";String password = "pwd";public Connection getConnection() throws SQLException, ClassNotFoundException {Class.forName(driver);return DriverManager.getConnection(url, username, password);}//随便写写了,直接拼接public User seleteById(Integer id) throws SQLException, ClassNotFoundException {String sql = "select * from user where id = " + id;ResultSet resultSet = getConnection().createStatement().executeQuery(sql);resultSet.next();return new User().setId(resultSet.getInt("id")).setName(resultSet.getString("name"));}}

手写基础架构效果演示

基础依赖包如下
在这里插入图片描述
可以看到使用我们手撕的 UserMapper 可以成功的查到 db 中的数据
在这里插入图片描述

总结

本文基于源码分析了 Mybatis 中代理 Mapper 创建的详细流程,基于理解一比一手撕复刻了出来,期间遇到的问题都总结在注释里面了。有人说会这个有啥用,会这个你可以将所有和数据库打交道的技术,都封装成类似于 Mybatis-Plus 的框架造福全宇宙!!!让技术不在复杂让小学生都会写代码,你就是明日之星!!!!

相关文章:

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架&#xff0c;复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增…...

【C++11算法】iota算法

文章目录 前言一、iota函数1.1 iota是什么&#xff1f;1.2 函数原型1.3 参数和返回值1.4 示例代码1.5 示例代码21.6 示例代码3 总结 前言 C标准库提供了丰富的算法&#xff0c;其中之一就是iota算法。iota算法用于填充一个区间&#xff0c;以递增的方式给每个元素赋予一个值。…...

付费加密音乐格式转换Mp3、Flac工具

一、工具介绍 这是一款免费的将付费加密音乐等多种格式转换Mp3 Flac工具,现在大部分云音乐公司,比如QQ音乐、酷我音乐、酷狗音乐、网易云音乐、虾米音乐(RIP🙏)等,都推出了自己专属的云音乐格式,这些格式一般只能在制定的播放器里播放,其它的播放软件并不支持,在很多情…...

React前端开发架构:构建现代响应式用户界面

在当今的Web应用开发中&#xff0c;React已经成为最受欢迎的前端框架之一。它的出色性能、灵活性和组件化开发模式&#xff0c;使得它成为构建现代响应式用户界面的理想选择。在这篇文章中&#xff0c;我们将探讨React前端开发架构的核心概念和最佳实践&#xff0c;以帮助您构建…...

Azure Bastion的简单使用

什么是Azure Bastion Azure Bastion 是一个提供安全远程连接到 Azure 虚拟机&#xff08;VM&#xff09;的服务。传统上&#xff0c;访问 VM 需要使用公共 IP 或者设立 VPN 连接&#xff0c;这可能存在一些安全风险。Azure Bastion 提供了一种更安全的方式&#xff0c;它是一个…...

深入理解高并发编程 - 深度解析ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor 并实现了 ScheduledExecutorService 接口&#xff0c;这使得它可以同时充当线程池和定时任务调度器。 构造方法 public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, …...

Android---- 一个完整的小项目(消防app)

前言&#xff1a; 针对不同群体的需求&#xff0c;想着应该拓展写方向。医疗app很受大家喜欢&#xff0c;就打算顺手写个消防app&#xff0c;里面基础框架还是挺简洁 规整的。登陆注册和本地数据库写的便于大家理解。是广大学子的毕设首选啊&#xff01; 此app主要为了传递 消防…...

XXX程序 详细说明

用于记录理解PC程序的程序逻辑 1、程序的作用 根据原作者的说明&#xff08;文件说明.txt&#xff09;&#xff0c;该程序 (PC.py) 的主要作用是提取某一个文件夹中的某个设备 (通过config中的信息看出来是Ag_T_8) 产生的日志文件&#xff0c;然后提取其中某些需要的数据&…...

perl下载与安装教程【工具使用】

Perl是一个高阶程式语言&#xff0c;由 Larry Wall和其他许多人所写&#xff0c;融合了许多语言的特性。它主要是由无所不在的 C语言&#xff0c;其次由 sed、awk&#xff0c;UNIX shell 和至少十数种其他的工具和语言所演化而来。Perl对 process、档案&#xff0c;和文字有很强…...

Chrome谷歌浏览器修改输入框自动填充样式

Chrome谷歌浏览器修改输入框自动填充样式 背景字体 背景 input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #fff inset !important; }字体 input:-internal-autofill-selected {-webkit-text-fill-color: #000 !important; }...

Azure CLI 进行磁盘加密

什么是磁盘加密 磁盘加密是指在Azure中对虚拟机的磁盘进行加密保护的一种机制。它使用Azure Key Vault来保护磁盘上的数据&#xff0c;以防止未经授权的访问和数据泄露。使用磁盘加密&#xff0c;可以保护磁盘上的数据以满足安全和合规性要求。 参考文档&#xff1a;https://l…...

Java“牵手”根据关键词搜索(分类搜索)速卖通商品列表页面数据获取方法,速卖通API实现批量商品数据抓取示例

速卖通商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取速卖通商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问速卖通商城的网页来获取商品详情信息。以下是两种常用方法的介…...

商城-学习整理-高级-消息队列(十七)

目录 一、RabbitMQ简介(消息中间件)1、RabbitMQ简介&#xff1a;2、核心概念1、Message2、Publisher3、Exchange4、Queue5、Binding6、Connection7、Channel8、Consumer9、Virtual Host10、Broker 二、一些概念1、异步处理2、应用解耦3、流量控制5、概述 三、Docker安装RabbitM…...

Android Camere开发入门(1):初识Camera

Android Camere开发入门(1):初识Camera 初步了解 在Android开发中,相机(Camera)是一个常见而重要的功能模块。它允许我们通过设备的摄像头捕捉照片和录制视频,为我们的应用程序增加图像处理和视觉交互的能力。 随着Android系统的不断发展和更新,相机功能也不断改进和增…...

hive表的全关联full join用法

背景&#xff1a;实际开发中需要用到全关联的用法&#xff0c;之前没遇到过&#xff0c;现在记录一下。需求是找到两张表的并集。 全关联的解释如下&#xff1b; 下面建两张表进行测试 test_a表的数据如下 test_b表的数据如下&#xff1b; 写第一个full join 的SQL进行查询…...

PMP串讲

&#xff01;5种冲突解决策略 &#xff01;敏捷3355。 &#xff1f;PMP项目管理132种工具技术合集&#xff1a; 参考2&#xff1a;项目管理的132种工具 - 水之座 ?质量管理,有多少种图&#xff1a; ?风险管理,有多少种图&#xff1a; --参考&#xff1a;PMP相关的十八种…...

最长回文子序列——力扣516

动态规划 int longestPalindromeSubseq(string s){int n=s.length();vector<vector<int>>...

从零实现深度学习框架——Transformer从菜鸟到高手(二)

引言 &#x1f4a1;本文为&#x1f517;[从零实现深度学习框架]系列文章内部限免文章&#xff0c;更多限免文章见 &#x1f517;专栏目录。 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;系列文章会基于纯Python和NumPy从零创建自己的类PyTorch深度学习框…...

docker监控平台FAST OS DOCKER --1

感觉这个是目前好用的中文平台&#xff0c;暂为v1吧 拉取镜像 docker pull wangbinxingkong/fast运行镜像 docker run --name fastos --restart always -p 18091:8081 -p 18092:8082 -e TZ"Asia/Shanghai" -d -v /var/run/docker.sock:/var/run/docker.sock -v /e…...

SpringBoot2.0集成WebSocket

<!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency> 新建配置类 import org.springframework.boot.autoconfigure.condition.Cond…...

Video2X:用AI魔法让老旧视频重获新生

Video2X&#xff1a;用AI魔法让老旧视频重获新生 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/video2x 你是否曾…...

终极指南:用ncmdump彻底解决网易云音乐NCM格式限制

终极指南&#xff1a;用ncmdump彻底解决网易云音乐NCM格式限制 【免费下载链接】ncmdump ncmdump - 网易云音乐NCM转换 项目地址: https://gitcode.com/gh_mirrors/ncmdu/ncmdump 在数字音乐时代&#xff0c;格式兼容性已成为音乐爱好者面临的核心挑战。当你从网易云音乐…...

告别远程桌面‘失忆症’:一招锁定xrdp端口,让你的XFCE会话永不丢失

告别远程桌面‘失忆症’&#xff1a;一招锁定xrdp端口&#xff0c;让你的XFCE会话永不丢失 远程办公和跨平台协作已成为现代开发者的日常&#xff0c;但当你正沉浸于代码世界时&#xff0c;突然的网络波动或客户端切换却让整个工作环境"人间蒸发"——这种经历恐怕每…...

在「唯」与「阿」之间安放计算之道,老子这句话给 SAP HANA 开发的一层提醒

「唯之与阿,相去几何?美之与恶,相去若何?人之所畏,不可不畏。荒兮,其未央哉!」放在 SAP HANA 开发里看,不是把古文硬贴到技术上,而是在提醒我们,很多工程判断看起来差别很小,落到系统运行里却会拉开很大的距离。一个 JOIN 写在 application server,还是下推到 data…...

OpenClaw数据包工厂:从非结构化业务信息到可审查工作包的AI自动化实践

1. 项目概述&#xff1a;从混乱业务输入到可审查工作包的转变如果你是一名创业者、服务运营商或者任何需要处理大量非结构化业务信息的人&#xff0c;那么“信息过载”和“行动泄漏”这两个词你一定不陌生。每天&#xff0c;会议录音、客户邮件、CRM导出数据、表单提交像潮水一…...

kagent:把 Agent 当 Pod 来管,赌的是 Agent 的最终归宿是 K8s

我们写过用 kubectl apply -f deployment.yaml 起一个 Pod&#xff0c;写过用 Service 把它暴露出来&#xff0c;写过用 Operator 监听 CRD 自动调和状态。Solo.io 那群人 2025 年初做了一个看起来很自然、但没人提早做出来的事&#xff1a;把同一套思路平移到 AI Agent 上——…...

HolmesGPT 值不值得跟?把 AI SRE 的七强格局摊开看

CNCF Sandbox 在 2025-10 收了一个项目叫 HolmesGPT&#xff0c;定位是"开源 SRE Agent"。看着像下一个值得跟的风口——但同样进了 Sandbox 的 k8sgpt 已经 7,746 星&#xff0c;比它早一年&#xff1b;新来的 kagent 背靠 Solo.io&#xff0c;2,716 星只用了一年就…...

Go语言CLI工具服务化:基于JSON-RPC的进程间通信与自动化集成

1. 项目概述与核心价值最近在折腾一些自动化流程和跨平台脚本时&#xff0c;遇到了一个挺有意思的需求&#xff1a;如何让一个用Go语言写的、功能强大的命令行工具&#xff0c;能够被其他语言&#xff08;比如Python、Node.js&#xff09;或者更上层的应用&#xff08;比如Web界…...

ANSYS Maxwell 静电仿真避坑指南:模型设置、求解失败与结果解读的5个常见问题

ANSYS Maxwell 静电仿真避坑指南&#xff1a;模型设置、求解失败与结果解读的5个常见问题 当你第一次成功运行ANSYS Maxwell的静电仿真时&#xff0c;那种成就感是真实的。但很快你会发现&#xff0c;能跑通仿真和得到可信结果之间&#xff0c;隔着无数个深夜调试的坑。这篇文章…...

OpenClaw工作空间管理工具:自动化文件治理与优化实践

1. 项目概述&#xff1a;一个专为OpenClaw设计的本地化工作空间管理工具如果你和我一样&#xff0c;深度使用过OpenClaw这套开源AI智能体框架&#xff0c;那你一定对那几个核心的Markdown配置文件又爱又恨。AGENTS.md里定义着你的数字员工&#xff0c;SOUL.md是它们的“灵魂”与…...