Spring 源码分析

news2024/11/15 11:00:53

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 类的构造方法
    // 参数为 invocationHandler
    public $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.$Proxy0
    Class<?> 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的属性,则会递归初始化依赖的bean
    populateBean(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
	@Nullable
	public 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)) {
        // 【重点】创建TransactionInfo
		  TransactionInfo 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);
    
    // 如果是单例的bean
    if (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.coloradmin.cn/o/2156994.html

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

相关文章

HarmonyOS鸿蒙开发实战(5.0)悬浮窗拖拽和吸附动画实践

鸿蒙HarmonyOS NEXT开发实战往期文章必看&#xff08;持续更新......&#xff09; HarmonyOS NEXT应用开发性能实践总结 HarmonyOS NEXT应用开发案例实践总结合集 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

spring揭秘22-springmvc01-概述

文章目录 【README】【1】MVC模式实现web应用架构【1.1】MVC模式【1.2】单个集中式控制器实现【1.2.1】引入2层控制器 【2】springmvc概述【2.1】一级控制器&#xff1a;DispatcherServlet【2.1.1】HandlerMapping处理器映射【2.1.2】springmvc二级控制器Controller【2.1.3】Vi…

【自动驾驶】控制算法(十)深度解析车辆纵向控制 | Carsim 油门刹车标定表的制作

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

7款国内AI搜索引擎大全网站

与传统搜索引擎相比&#xff0c;AI搜索引擎利用先进的自然语言处理、机器学习和深度学习技术&#xff0c;提供更加精准和个性化的搜索服务。小编就来和大家分享国内免费的AI搜索引擎网站&#xff0c;方便大家体验使用。 AI搜索引擎网站大全&#xff1a;https://www.bgrdh.com/f…

java日志框架之Log4j

文章目录 一、Log4j简介二、Log4j组件介绍1、Loggers (日志记录器)2、Appenders&#xff08;输出控制器&#xff09;3、Layout&#xff08;日志格式化器&#xff09; 三、Log4j快速入门四、Log4j自定义配置文件输出日志1、输出到控制台2、输出到文件3、输出到数据库 五、Log4j自…

ESP32无线WiFi蓝牙SOC,设备物联网通信方案,启明云端乐鑫代理商

在当今数字化时代&#xff0c;物联网(IoT)正迅速成为连接我们生活各个方面的无形纽带&#xff0c;越来越多的日常物品被赋予了智能功能&#xff0c;从灯泡到插座&#xff0c;从门锁到家电设备&#xff0c;这些设备正在改变我们与家庭环境的互动方式。 随着智能产品的普及&…

*C++:string

一.STL简介 1.STL STL(standard template libaray- 标准模板库 ) &#xff1a; 是 C 标准库的重要组成部分 &#xff0c;不仅是一个可复用的组件库&#xff0c;而且 是一个包罗数据结构与算法的软件框架 。 2.STL六大组件 二.标准库里的string类 标准string库网址&#xff1…

【AI算法岗面试八股面经【超全整理】——NLP】

AI算法岗面试八股面经【超全整理】 概率论【AI算法岗面试八股面经【超全整理】——概率论】信息论【AI算法岗面试八股面经【超全整理】——信息论】机器学习【AI算法岗面试八股面经【超全整理】——机器学习】深度学习【AI算法岗面试八股面经【超全整理】——深度学习】NLP【A…

Crack道路裂缝检测数据集——目标检测数据集

【Crack道路裂缝检测数据集】共3684张。 目标检测数据集&#xff0c;标注文件为YOLO适用的txt格式。已划分为训练、验证集。 图片分辨率&#xff1a;224*224 类别&#xff1a;crack Crack道路裂缝检测数据集 数据集描述 该数据集是一个专门用于训练和评估基于YOLO&#xff0…

[笔记]某变频器,功能列表及参数表

产品代号&#xff1a;INVT GOODDRIVE&#xff0c;这家公司我的产品我似乎在特检院看到过&#xff1f;或者在某个地铁建设工地看到过。是深圳的。 1.产品功能点&#xff1a; 变频锥形电机控制、抱闸转矩验证&#xff1f;抱闸反馈零位检测行程限位超载防护轻载升速&#xff08;…

机器学习课程学习周报十三

机器学习课程学习周报十三 文章目录 机器学习课程学习周报十三摘要Abstract一、机器学习部分1. 文生图模型概述2. Stable Diffusion概述3. ControlNet概述4. 概率论复习&#xff08;二&#xff09; 总结 摘要 本周的学习内容涵盖了文生图模型、Stable Diffusion、ControlNet以…

从零开始讲DDR(5)——读懂Datasheet

对于开发人员来说&#xff0c;需要根据实际场景和使用的需要&#xff0c;使用不同厂家&#xff0c;不同型号的DDR&#xff0c;虽然原理上大同小异&#xff0c;但是还是有一些细节上的需要注意的地方&#xff0c;接触一个新的DDR芯片&#xff0c;首先就是需要找到对应的datashee…

Mybatis 返回 Map 对象

一、场景介绍 假设有如下一张学生表&#xff1a; CREATE TABLE student (id int NOT NULL AUTO_INCREMENT COMMENT 主键,name varchar(100) NOT NULL COMMENT 姓名,gender varchar(10) NOT NULL COMMENT 性别,grade int NOT NULL COMMENT 年级,PRIMARY KEY (id) ) ENGINEInnoD…

LeetCode讲解篇之238. 除自身以外数组的乘积

文章目录 题目描述题解思路题解代码 题目描述 题解思路 对于该题&#xff0c;我们可以先使用一个循环记录所有非零元素的乘积结果和非零元素的个数 如果非零元素个数为0&#xff0c;则非零元素的乘积除以数组对应位置的数字就是除自身以外的数组的乘积如果非零元素个数为1&am…

新质农业——水肥一体化技术

橙蜂智能公司致力于提供先进的人工智能和物联网解决方案&#xff0c;帮助企业优化运营并实现技术潜能。公司主要服务包括AI数字人、AI翻译、埃域知识库、大模型服务等。其核心价值观为创新、客户至上、质量、合作和可持续发展。 橙蜂智农的智慧农业产品涵盖了多方面的功能&…

【人工智能学习】8_人工智能其他通用技术

知识图谱 在看影视剧或小说时&#xff0c;若其中的人物很多、人物关系复杂&#xff0c;我们一般会用画人物关系图谱来辅助理解人物关系。那什么是知识图谱呢&#xff1f; 知识是人类对信息进行处理之后的认识和理解&#xff1b;对数据和信息的凝练、总结后的成果。 将信息转…

MySQL-排名函数ROW_NUMBER(),RANK(),DENSE_RANK()函数的异同

MySQL-排名函数ROW_NUMBER()&#xff0c;RANK()&#xff0c;DENSE_RANK()函数的异同 前言 假设有如下表结构与数据&#xff0c;class_id表示班级&#xff0c;需求&#xff1a;现在要按照班级分组&#xff0c;每个班级的学生进行年龄从小到大排序 一、ROW_NUMBER()函数 ROW_NUM…

YOLO航拍车辆和行人识别

YOLO航拍车辆和行人识别 图片数量9695&#xff0c;标注为xml和txt格式&#xff1b; class&#xff1a;car&#xff0c;pedestrian&#xff0c;truck&#xff0c;bus 用于yolo&#xff0c;Python&#xff0c;目标检测&#xff0c;机器学习&#xff0c;人工智能&#xff0c;深度学…

软件测试分类篇(下)

目录 一、按照测试阶段分类 1. 单元测试 2. 集成测试 3. 系统测试 3.1 冒烟测试 3.2 回归测试 4. 验收测试 二、按照是否手工测试分类 1. 手工测试 2. 自动化测试 3. 手工测试和自动化测试的优缺点 三、按照实施组织分类 1. α测试(Alpha Testing) 2. β测试(Beta…

图像放大效果示例【JavaScript】

实现效果&#xff1a; 当鼠标悬停在小图&#xff08;缩略图&#xff09;上时&#xff0c;大图&#xff08;预览图&#xff09;会随之更新为相应的小图&#xff0c;并高亮当前悬浮的小图的父元素。 代码&#xff1a; 1. HTML部分 <!DOCTYPE html> <html lang"z…