Spring 源码分析

Spring 源码版本 4.2.8.RELEASE

Bean 生命周期

Spring Bean生命周期

动态代理

代理模式

  • 优点: 在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
  • 缺点: 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

JDK 代理

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

其中的运行时计算生成,这种场景使用最多的是动态代理技术,在java.lang.reflect.Proxy类中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为*$Proxy的代理类的二进制字节流

动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到 JVM 中使用

JDK 动态代理流程

使用 arthas 查看源码

public final class $Proxy0 extends Proxy implements Landlord {private static Method m3;// $Proxy0 类的构造方法// 参数为 invocationHandlerpublic $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {m3 = Class.forName("com.company.proxy.Landlord").getMethod("apartmentToRent", new Class[0]);}public final void apartmentToRent() {this.h.invoke(this, m3, null);return;}
}

我们的代理类实际上是实现了 Landlord 的接口,然后重写了 Landlord 接口中的 apartmentToRent 方法
当外界调用代理类的 apartmentToRent() 方法时,实际上是调用的我们自定义的 new InvocationHandler() 类里面的 invoke 方法

return Proxy.newProxyInstance(ClassLoader,Interfaces,new InvocationHandler() {});
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){// cl = class com.sun.proxy.$Proxy0Class<?> cl = getProxyClass0(loader, intfs);// cons = public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)final Constructor<?> cons = cl.getConstructor(constructorParams);// 根据构造参数实例化对象return cons.newInstance(new Object[]{h});
}

$Proxy0 的构造入参 InvocationHandler 为自定义的 InvocationHandler

  1. 拿到 $Proxy0 的 Class
  2. 根据 Class 拿到其构造方法
  3. 根据构造方法传入参数进行实例化

Cglib 代理

cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充

Spring、Mybatis、Dubbo等都是使用cglib实现动态代理

  • Spring 框架使用CGLIB实现了动态代理、延迟加载、Bean初始化和销毁回调以及字节码增强等功能
  • MyBatis使用CGLIB实现延迟加载和脏数据检查
  • Dubbo使用CGLIB来实现服务接口的动态代理,以便在远程调用时添加额外的功能,如负载均衡、容错处理等

  • 最底层是字节码
  • ASM 是操作字节码的工具
  • cglib 基于 ASM 字节码工具操作字节码(即动态生成代理,对方法进行增强)
  • SpringAOP 基于 cglib 进行封装,实现 cglib 方式的动态代理
cglib 代理流程
public class UserServiceImpl$$EnhancerByCGLIB$$cd9788d extends UserServiceImpl implements Factory {final List findUserList() {// 是否设置了回调MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;if (methodInterceptor == null) {UserServiceImpl$$EnhancerByCGLIB$$cd9788d.CGLIB$BIND_CALLBACKS(this);methodInterceptor = this.CGLIB$CALLBACK_0;}// 设置回调,需要调用 intercept 方法if (methodInterceptor != null) {return (List) methodInterceptor.intercept(this, CGLIB$findUserList$0$Method, CGLIB$emptyArgs, CGLIB$findUserList$0$Proxy);}// 无回调,调用父类的 findUserList 即可return super.findUserList();}final List CGLIB$findUserList$0() {return super.findUserList();}
}

  • 在 JVM 编译期间,我们的 Enhancer 会根据目标类的信息去动态的生成 动态代理类并设置回调
  • 当用户在通过上述的动态代理类执行 findUserList() 方法时,有两个执行选项
    • 若设置了回调接口,则直接调用UserLogProxy 中的 intercept ,然后通过 FastClass 类调用动态代理类,执行CGLIB$findUserList$0 方法,调用父类的 findUserList() 方法
    • 若没有设置回调接口,则直接调用父类的 findUserList() 方法

IOC

Spring 启动流程

obtainFreshBeanFactory

整体简介: 创建容器,并且完成配置文件的加载

refreshBeanFactory:解析我们的 application.xml 文件并生成 BeanDefinition 注册至 DefaultListableBeanFactory 的 beanDefinitionMap 中

refreshBeanFactory 的业务:

  • 通过我们传递的xml 文件的路径,利用 documentLoader 将其封装成 Document 格式
  • 创建 BeanDefinitionDocumentReader 来正式解析 xml 文件并找到文件的 root
  • 根据 root 扫描遍历,对不同配置的标签(import、alias、bean、beans)走不同的逻辑判断
  • 将当前的标签各属性进行组装成 beanDefinition,调用 DefaultListableBeanFactory 进行注册
  • 根据 BeanName 查询该 beanDefinition 是否被注册过,如果被注册过,则直接抛出异常(Spring不允许覆盖)
  • 如果没有注册过,则将 BeanName 与 beanDefinition 注册至 DefaultListableBeanFactory 的 beanDefinitionMap 中
  • 如果该 beanDefinition 含有别名,也要将别名进行注册,至于为什么注册别名

finishBeanFactoryInitialization

整体简介:完成所有非懒加载的单例对象的实例化操作,从此方法开始进行对象的创建,包含了实例化,初始化,循环依赖,AOP等核心逻辑的处理过程,此步骤是最最核心且关键的点,要对其中的细节最够清楚

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 实例化剩下的单例对象beanFactory.preInstantiateSingletons();
}public void preInstantiateSingletons(){// 拿到我们之前存储的所有beanDefinition的名字List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);		// 触发单例bean的初始化,遍历集合的对象for (String beanName : beanNames) {// 如果beanName对应的bean不是FactoryBean,只是普通的bean,通过beanName获取bean实例getBean(beanName);}
}public Object getBean(String name) throws BeansException {// 此方法是实际获取bean的方法,也是触发依赖注入的方法return doGetBean(name, null, null, false);
}protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// 这里需要一步转换,这里的原因我们附录1提到过,这里不再过多讨论String beanName = transformedBeanName(name);// 提前检查单例缓存中是否有手动注册的单例对象,剧透一下(和循环依赖有关联)Object sharedInstance = getSingleton(beanName);// 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}if (mbd.isSingleton()) {// 返回以beanName的(原始)单例对象,如果尚未注册,则使用singletonFactory创建并注册一个对象:sharedInstance = getSingleton(beanName, () -> {try {// 为给定的合并后BeanDefinition(和参数)创建一个bean实例// 这也是我们的核心方法return createBean(beanName, mbd, args);}});
}protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 实际创建bean的调用Object beanInstance = doCreateBean(beanName, mbdToUse, args);
}protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);// 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的beanpopulateBean(beanName, mbd, instanceWrapper);// 执行初始化逻辑exposedObject = initializeBean(beanName, exposedObject, mbd);
}

创建实例的步骤

  • 拿到我们之前注册的 beanDefinitionNames,遍历整个 beanDefinitionNames,每一个 BeanName 生成一个对象
  • 我们需要进行名称转化,防止传入的是一个别名或其他的名称,利用转换后的别名去调用
  • 查询我们的单例缓存中是否已经存在该实例,如果存在直接返回即可
  • 如果不存在,则需要去根据该 beanDefinition 去生成对应的实例

对于生成实例共有三个步骤:

  1. 创建实例
  2. 属性填充
  3. 初始化逻辑
    • 实现 BeanPostProcessor 的前置方法
    • 对象的初始化方法
    • 实现 BeanPostProcessor 的后置方法

AOP

AOP 组件

  • Pointcut:定义切面的匹配点,主要是类和方法
  • Advice:定义切面的行为,即在匹配点执行的操作。
  • Advisor:将 Pointcut 和 Advice 组合成一个对象,表示一个完整的切面
  • Aspect:使用注解或 XML 配置方式定义切面,通常包含多个 Advisor

在Spring AOP中,拦截器链的概念是通过一系列顺序执行的通知(Advice)来实现的。这些通知可以是:

  • 前置通知(Before advice):在方法执行之前执行。
  • 后置通知(After advice):在方法执行之后执行。
  • 环绕通知(Around advice):包围方法的执行,可以决定是否继续执行方法,或者替换方法的返回值。

AOP 核心设计

public Object proceed() throws Throwable {return super.proceed();
}public Object proceed() {// 从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target的函数,这个函数是通过反射机制完成的if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}// 获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);// 普通拦截器,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}

核心实现思路,递归 + 拦截器链

整个proceed()方法的逻辑是递归的,它按照拦截器链中的顺序,逐个检查并调用拦截器。如果遇到动态方法匹配器,还会进行运行时的匹配检查。如果匹配失败,就会跳过当前拦截器,继续执行链中的下一个拦截器。这种递归调用的方式直到链中的最后一个拦截器或实际的连接点被执行。

事务

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行

MySQL 事务与InnoDB的MVCC实现机制

Spring 事务管理

Spring事务管理的实现步骤大致如下:

  1. 配置事务管理器:根据所使用的数据访问技术(如JDBC、Hibernate等),配置相应的事务管理器。
  2. 配置事务增强(Transaction Advice):创建一个事务增强(一个带有事务语义的Advice),并将其与切入点(Pointcut)关联起来。切入点定义了哪些方法需要事务管理。
  3. 创建代理:Spring AOP会为目标对象创建一个代理,该代理会根据配置的事务属性在方法调用前后添加事务管理逻辑。
  4. 事务的创建和结束:在代理对象的方法被调用时,Spring会根据事务属性创建或加入一个事务,并在方法正常结束或发生异常时提交或回滚事务。
  5. 事务的传播:Spring事务管理支持多种事务传播行为,如支持嵌套事务、独立事务等。
  6. 事务的回滚:Spring允许你定义哪些异常会导致事务回滚,哪些异常不会。
  7. 事务同步:Spring提供了事务同步机制,允许你在事务的开始、结束或回滚时执行一些资源清理工作。
  • 获取事务:TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
  • 提交事务:void commit(TransactionStatus status)
  • 回滚事务:void rollback(TransactionStatus status)

事务 AOP 实现

@EnableTransactionManagement会开启事务配置,TransactionManagementConfigurationSelector选择代理或ASPECTJ,在ProxyTransactionManagementConfiguration注入AutoProxyRegistrar注册AOP处理器及ProxyTransactionManagementConfiguration代理事务配置

本质使用TransactionInterceptor,在AOP的拦截器链里执行事务代理操作

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {@Override@Nullablepublic Object invoke(MethodInvocation invocation) throws Throwable {// 获取我们的代理对象的class属性Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction.../*** 以事务的方式调用目标方法* 在这埋了一个钩子函数 用来回调目标方法的*/return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);}
}@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation){// 获取我们的事务属性源对象TransactionAttributeSource tas = getTransactionAttributeSource();// 通过事务属性源对象获取到当前方法的事务属性信息final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);// 获取我们配置的事务管理器对象final TransactionManager tm = determineTransactionManager(txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// 【重点】创建TransactionInfoTransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);try {// 执行被增强方法,调用具体的处理逻辑【我们实际的方法】retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// 异常回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {//清除事务信息,恢复线程私有的老的事务信息cleanupTransactionInfo(txInfo);}//成功后提交,会进行资源储量,连接释放,恢复挂起事务等操作commitTransactionAfterReturning(txInfo);return retVal;}
}// 创建连接 + 开启事务
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// 获取TransactionStatus事务状态信息status = tm.getTransaction(txAttr);// 根据指定的属性与status准备一个TransactionInfo,return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}// 存在异常时回滚事务
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {// 进行回滚txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}// 调用事务管理器的提交方法
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo){txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}

整体流程

多级缓存

多级缓存能解决属性注入导致的循环依赖问题,不能解决有参构造注入的循环依赖

  • 一级缓存(Singleton Objects):存储已经创建好的单例Bean实例。当一个Bean被成功创建后,它会被存储在这个缓存中,以供后续的请求直接使用,避免重复创建。
  • 二级缓存(Early Singleton Objects):存储早期的Bean引用。在Bean的创建过程中,如果需要引用其他Bean,Spring会尝试从二级缓存中获取。
  • 三级缓存(Singleton Factories):存储Bean工厂对象。在Bean的创建过程中,如果Spring需要为某个Bean创建一个工厂对象(例如,当使用@Bean注解时指定了一个工厂方法),这些工厂对象会被存储在三级缓存中。

源码

查询缓存
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// Step1:查询MyDemo1缓存是否存在Object sharedInstance = getSingleton(beanName);// 如果是单例的beanif (mbd.isSingleton()) {// 直接创建bean即可,注意 getSingleton 方法sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args);});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
}// Step1:从三级缓存中查询 MyDemo1 是否被缓存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 一级缓存查询Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 二级缓存查询singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 三级缓存查询ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}// 这里记住一个操作:在我们创建bean结束之后,会调用 addSingleton 该方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}return singletonObject;
}
解决动态代理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;// 【重点】exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);// 这里会生成动态代理类return wrapIfNecessary(bean, beanName, cacheKey);
}

二级缓存主要用于解决已经实例化但尚未初始化的Bean之间的循环依赖问题。而三级缓存则用于更复杂的情况,比如需要提前暴露Bean的引用(通过代理对象或工厂对象)来解决循环依赖。

三级缓存目的:属性注入的阶段在执行初始方法(AOP)之前,缓存池中的半实例化对象不是代理对象


参考资料:

  1. 图片转自爱敲代码的小黄 Spring系列

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Apifox 「定时任务」操作指南,解锁自动化测试的新利器

定时任务是按照预设时间自动执行的任务&#xff0c;它可以有效解决一些常见问题&#xff0c;比如频繁执行的回归测试和大规模的接口测试&#xff0c;这些任务需要在固定时间点或间隔周期内自动运行&#xff0c;以确保软件的持续集成和持续交付过程中的稳定性和可靠性。通过使用…

Windows下 批量重命名文件【bat实现】-两个小问题

Windows下 批量重命名文件【bat实现】_bat批量重命名文件ren-CSDN博客 上面是原来的教程&#xff0c;我遇到了两个小问题&#xff0c;问题及解决如下&#xff1a; ①dir/b>rename.csv : 无法将“dir/b>rename.csv”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。…

【每天学个新注解】Day 2 Lombok注解简解(一)—@Data、@Build、@Value

Data 相当于同时使用了 Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode 1、如何使用 需要同时使用Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode注解一个Bean的时候。 2、代码示例 例&#xff1a; Data public cla…

CCC SPAKE2+流程解析

1、SPAKE2流程及SCP03通道介绍 SPAKE2流程发生在CCC车主配对过程中的Phase2。 SPAKE2流程为车辆和手机之间的数据交换建立了一个安全通道SCP03。 那这个SCP03通道是干啥的&#xff1f; 我们可以先简单的理解为&#xff1a;建立安全通道前&#xff0c;车辆和手机之间交互的AP…

剖解反转链表

剖解反转链表 思路&#xff1a; 1.若链表为空或者只存在一个节点&#xff0c;就无需反转&#xff0c;直接返回head 2.若存在多个节点 首先将head.next给到cur&#xff0c;并将head.next置为null 剩余的节点就利用头插法&#xff0c;反转链表 class Solution {public ListNo…

基于SpringBoot+Vue+MySQL的特色旅游网站系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着旅游业的蓬勃发展&#xff0c;人们对旅游体验的需求日益多样化与个性化。传统的旅游信息查询与预订方式已难以满足现代游客的需求。因此&#xff0c;我们开发了这款基于SpringBootVueMySQL的特色旅游网站系统。该系统旨在通…

MySQL高阶1917-Leetcodify好友推荐

目录 题目 准备数据 分析数据 总结 题目 为 Leetcodify 用户推荐好友。我们将符合下列条件的用户 x 推荐给用户 y &#xff1a; 用户 x 和 y 不是好友&#xff0c;且用户 x 和 y 在同一天收听了相同的三首或更多不同歌曲。 注意&#xff0c;好友推荐是单向的&#xff0c…

web前端字段大小写下划线转换工具

文章目录 前言一、如何使用&#xff1f;二、相关代码总结 前言 程序员在敲代码的过程中都要命名一些字段&#xff0c;但是Java语言对字段的命名规范和sql命名规范不一样&#xff0c;如下图所示&#xff0c;这种机械性的转换工作很劳神费力&#xff0c;为了省点劲写了一个web小…

pdf怎么编辑修改内容?试试这四款工具!

作为一个经常探索各种办公软件的人&#xff0c;今天我打算和大家聊聊一个我们工作中经常会遇到的问题——编辑PDF文件。我们都知道&#xff0c;PDF文件以其格式稳定、不易被篡改而受到青睐&#xff0c;但这也意味着一旦需要修改内容&#xff0c;就变得相当棘手。不过&#xff0…

设计模式之类结构模式例题

答案&#xff1a;B A 知识点&#xff1a; 设计模式中类结构模式分别是&#xff1a;工厂方法模式&#xff0c;适配器模式&#xff0c;模板方法模式&#xff0c;解释器模式 记忆方法&#xff1a;公司里有个模特小姐姐&#xff0c;公是工厂模式&#xff0c;司是适配器模式&…

Mapper代理

文章目录 Mapper代理路径一定要写对Mapper代理方式加载配置文件 &#xff08;包扫描的方式&#xff09; Mapper代理 好像就是能 包名.方法。 反正就是防止硬编码&#xff0c;更灵活&#xff0c;更适用。 路径一定要写对 Mapper代理方式加载配置文件 &#xff08;包扫描的方式…

fiddler抓包08_抓Android手机请求

课程大纲 手机抓包&#xff0c;电脑端的设置和IOS端相同&#xff0c;设置一次即可&#xff0c;无需重复设置。 前提&#xff1a;电脑和手机连接同一个局域网 土小帽电脑和手机都连了自己的无线网“tuxiaomao”。 Step1. 电脑端设置 ① 打开Fiddler - 开启抓包&#xff08;F12…

Windows下如何定时执行自定义任务

目录 一.前言二.设置定时自动执行自定义任务 一.前言 本文环境是Windows11系统。 有时候我们希望能够在Windows下定时自动执行自定义任务&#xff0c;比如检测数据库服务的状态。那在Windows下怎么定时自动执行自定义任务&#xff0c;这篇文章介绍一种方法。 二.设置定时自动…

计算机毕业设计推荐-基于python的电子图书阅读推荐平台【源码+文档+讲解】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的电子图书阅读推…

吴津雨银洁刘雅雯获得国际超模大赛四川总决赛网络组三甲

9月8日众人期盼已久的都江堰杯2024国际超模大赛四川总决赛在三遗之城都江堰落下帷幕。国际超模大赛已经举办第12个年头&#xff0c;每年为时尚界、模特界输送无数的优秀时尚模特人才&#xff0c;让世界超模中出现更多的中国面孔。大赛在全球已经布局多个国家及地区&#xff0c;…

项目第七弹:消费者管理模块

项目第七弹&#xff1a;消费者管理模块 一、为何要有这个模块&#xff1f;二、消费者是否需要持久化&#xff1f;三、怎么设计&#xff1f;1.如何抽象描述&#xff1f;1.回想一下基于生产消费模型的线程池2.如何组织3.消息处理与确认问题的解决4.自动确认标志5.消费者代码 2.队…

【计算机组成原理】主存储器深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

DETR论文翻译与理解

DETR&#xff08;Detection with transformer&#xff09; DETR&#xff1a;End to End Object Detection with Transformer 论文链接&#xff1a;2005.12872 (arxiv.org) 参考视频&#xff1a;https://www.bilibili.com/video/BV1GB4y1X72R/?spm_id_from333.788&vd_…

JBoss反序列化漏洞CVE-2017-12149

1.环境搭建 cd vulhub-master/jboss/CVE-2017-12149 docker-compose up -d 2.访问漏洞地址 3.漏洞验证 http://47.121.211.205:8080/invoker/readonly 返回500说明漏洞存在 4.使用漏洞进行利用 直接执行命令

人脸识别换装技术实现记录-1

最近,研究了下人脸识别换装,确定了技术方案和技术路线,并最终实现了想要达成的效果,现将制作过程中遇到的问题以及实现的过程记录下来,以便回顾总结的同时,也和其他想实现人脸识别换装的同学分享下经验,避免踩坑。 本项目主要是在Android系统上实现人脸换装的效…