手写 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 代理对象的全部源码,并且一比一复刻出来了。
温馨提示
阅读以下文章了解前置知识对理解本文更有帮助
- 深入jdk动态代理源码解析
- 模拟jdk动态代理(完整版)
- Factorybean与BeanFactory的区别
- 手把手debug自动装配源码、顺带弄懂了@Import等相关的源码(全文3w字、超详细)
- spring 源码解析(配图文讲解)顺带搞懂了循环依赖、aop底层实现
手把手带你解析 @MapperScan 源码
废话不多说直接步入正题,我们在使用 Mybatis 的时候要要设置 @MapperScan 扫描对应的 Mapper 接口,一步步点进去
发现其实就是注册了 MapperScannerConfigurer 这个 Bean ,都是些常用套路。然后发现 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor、InitializingBean。
-
BeanDefinitionRegistryPostProcessor:Spring 为我们提供的扩展点,让程序员可以自己干预 Bean 的生成
-
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。
看到这你是不是觉得源码也不过如此,对于整个简单的流程虽然走完了,但是作为一个要进行开发整个轮子的开发者来说,还远远不够。还需要了解更多细节
- 如何将指定包路径下的所有类生成 BeanDefinition ?。
- 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 代理手撕脚手架,复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增…...
【C++11算法】iota算法
文章目录 前言一、iota函数1.1 iota是什么?1.2 函数原型1.3 参数和返回值1.4 示例代码1.5 示例代码21.6 示例代码3 总结 前言 C标准库提供了丰富的算法,其中之一就是iota算法。iota算法用于填充一个区间,以递增的方式给每个元素赋予一个值。…...
付费加密音乐格式转换Mp3、Flac工具
一、工具介绍 这是一款免费的将付费加密音乐等多种格式转换Mp3 Flac工具,现在大部分云音乐公司,比如QQ音乐、酷我音乐、酷狗音乐、网易云音乐、虾米音乐(RIP🙏)等,都推出了自己专属的云音乐格式,这些格式一般只能在制定的播放器里播放,其它的播放软件并不支持,在很多情…...

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

Azure Bastion的简单使用
什么是Azure Bastion Azure Bastion 是一个提供安全远程连接到 Azure 虚拟机(VM)的服务。传统上,访问 VM 需要使用公共 IP 或者设立 VPN 连接,这可能存在一些安全风险。Azure Bastion 提供了一种更安全的方式,它是一个…...
深入理解高并发编程 - 深度解析ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor 并实现了 ScheduledExecutorService 接口,这使得它可以同时充当线程池和定时任务调度器。 构造方法 public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, …...

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

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

perl下载与安装教程【工具使用】
Perl是一个高阶程式语言,由 Larry Wall和其他许多人所写,融合了许多语言的特性。它主要是由无所不在的 C语言,其次由 sed、awk,UNIX shell 和至少十数种其他的工具和语言所演化而来。Perl对 process、档案,和文字有很强…...
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来保护磁盘上的数据,以防止未经授权的访问和数据泄露。使用磁盘加密,可以保护磁盘上的数据以满足安全和合规性要求。 参考文档:https://l…...
Java“牵手”根据关键词搜索(分类搜索)速卖通商品列表页面数据获取方法,速卖通API实现批量商品数据抓取示例
速卖通商城是一个网上购物平台,售卖各类商品,包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取速卖通商品列表和商品详情页面数据,您可以通过开放平台的接口或者直接访问速卖通商城的网页来获取商品详情信息。以下是两种常用方法的介…...

商城-学习整理-高级-消息队列(十七)
目录 一、RabbitMQ简介(消息中间件)1、RabbitMQ简介: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用法
背景:实际开发中需要用到全关联的用法,之前没遇到过,现在记录一下。需求是找到两张表的并集。 全关联的解释如下; 下面建两张表进行测试 test_a表的数据如下 test_b表的数据如下; 写第一个full join 的SQL进行查询…...
PMP串讲
!5种冲突解决策略 !敏捷3355。 ?PMP项目管理132种工具技术合集: 参考2:项目管理的132种工具 - 水之座 ?质量管理,有多少种图: ?风险管理,有多少种图: --参考:PMP相关的十八种…...

最长回文子序列——力扣516
动态规划 int longestPalindromeSubseq(string s){int n=s.length();vector<vector<int>>...

从零实现深度学习框架——Transformer从菜鸟到高手(二)
引言 💡本文为🔗[从零实现深度学习框架]系列文章内部限免文章,更多限免文章见 🔗专栏目录。 本着“凡我不能创造的,我就不能理解”的思想,系列文章会基于纯Python和NumPy从零创建自己的类PyTorch深度学习框…...
docker监控平台FAST OS DOCKER --1
感觉这个是目前好用的中文平台,暂为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…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...