『SpringBoot 源码分析』run() 方法执行流程:(2)刷新应用上下文-准备阶段

『SpringBoot 源码分析』run() 方法执行流程:(2)刷新应用上下文-准备阶段

  • 基于 2.2.9.RELEASE
  • 问题:当方法进行了注释标记之后,springboot 又是怎么注入到容器中并创建类呢?
  1. 首先创建测试主程序
package com.lagou;@SpringBootApplication//标注在类上说明这个类是`SpringBoot`的主配置类
public class SpringBootMytestApplication{public static void main(String[] args) {SpringApplication.run(SpringBootMytestApplication.class, args);}
}
  1. 创建测试 Controller
package com.lagou.controller;@RestController
public class TestController {@RequestMapping("/test")public String test(){System.out.println("源码环境构建成功...");return "源码环境构建成功";}
}

准备阶段

  1. 当准备完成应用上下文环境,以及应用上下文以后,需要为应用上下文做个准备阶段,简单来说其实就是要配置应用上下文,把需要的类装配上
public class SpringApplication {...public ConfigurableApplicationContext run(String... args) {...try {// 将运行时参数封装ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 构造应用上下文环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 处理需要忽略的 BeanconfigureIgnoreBeanInfo(environment);// 打印 bannerBanner printedBanner = printBanner(environment);// 刷新应用上下文前的准备阶段context = createApplicationContext();// 实例化 SpringBootExceptionReporter.class,用来支持报告关于启动的错误exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 1. 刷新应用上下文前的准备阶段prepareContext(context, environment, listeners, applicationArguments, printedBanner);...}catch (Throwable ex) {...}...}
}
  1. 在对应用上下文进行处理时,主要执行了下面几步的装配
  • 把上下文环境设置到应用上下文中
  • 执行容器后置处理
  • 把应用上下文交给 SpringApplication 初始化收集的 org.springframework.context.ApplicationContextInitializer 所有实现类进行初始化工作
  • 利用 org.springframework.boot.context.event.EventPublishingRunListenerorg.springframework.context.ApplicationListener 发布容器准备好事件
public class SpringApplication {...// 完成属性设置 bean对象创建private void  prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 1. 设置容器环境context.setEnvironment(environment);// 2. 执行容器后置处理postProcessApplicationContext(context);// 3. 执行一些初始化器applyInitializers(context); // 4. 向各个监听器发送容器已经准备好的事件listeners.contextPrepared(context);...}
}
  • 执行容器后置处理:其实只是往 BeanFactory 添加了基础的转换器
public class SpringApplication {private BeanNameGenerator beanNameGenerator;private ResourceLoader resourceLoader;private boolean addConversionService = true;...protected void postProcessApplicationContext(ConfigurableApplicationContext context) {if (this.beanNameGenerator != null) {context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,this.beanNameGenerator);}if (this.resourceLoader != null) {if (context instanceof GenericApplicationContext) {((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);}if (context instanceof DefaultResourceLoader) {((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());}}if (this.addConversionService) { // 1. 设置了转换器,例如平时能把整数字符串转换为整形,设置转换器context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());}}
}
  • 执行一些初始化器:就是遍历 org.springframework.context.ApplicationContextInitializer 执行 initialize() 方法
public class SpringApplication {...protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");initializer.initialize(context);}}
}
  • 向各个监听器发送容器已经准备好的事件:就是 org.springframework.boot.context.event.EventPublishingRunListenerorg.springframework.context.ApplicationListener 发布容器准备好事件
public class SpringApplication {...// 1. 当 ApplicationContext 构建完成时,该方法被调用void contextPrepared(ConfigurableApplicationContext context) {for (SpringApplicationRunListener listener : this.listeners) {listener.contextPrepared(context);}}
}
  1. 发布完监听之后,从上下文中获取 IOC 工厂,并设置允许 bean 定义被覆盖参数
public class SpringApplication {...// 完成属性设置 bean 对象创建private void  prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 设置容器环境context.setEnvironment(environment);// 执行容器后置处理postProcessApplicationContext(context);// 执行一些初始化器applyInitializers(context); // 向各个监听器发送容器已经准备好的事件listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 1. 获取 IOC 容器ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 将 main() 函数中的 args 参数封装成单例 Bean,注册进容器beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {// 将 printedBanner 也封装成单例,注册进容器beanFactory.registerSingleton("springBootBanner", printedBanner);}// 2. 设置允许 bean 定义被覆盖参数if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}...}
}
  1. 然后加载启动类,并将启动类注入到容器当中,然后发布容器已加载事件
public class SpringApplication {...// 完成属性设置 bean 对象创建private void  prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 设置容器环境context.setEnvironment(environment);// 执行容器后置处理postProcessApplicationContext(context);// 执行一些初始化器applyInitializers(context); // 向各个监听器发送容器已经准备好的事件listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 获取 IOC 容器ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 将 main() 函数中的 args 参数封装成单例 Bean,注册进容器beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {// 将 printedBanner 也封装成单例,注册进容器beanFactory.registerSingleton("springBootBanner", printedBanner);}// 设置允许 bean 定义被覆盖参数if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) { // 是否需要进行懒加载,这里不是context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// 1. 加载源,这里拿到的是主类,com.lagou.SpringBootMytestApplicationSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");// 2. 加载我们的启动类,将启动类注入容器 重点关注load(context, sources.toArray(new Object[0]));// 取出第一个元素,就是主类,要先实例化主类,灌入容器中// 3. 发布容器已加载事件listeners.contextLoaded(context);}
}
  • 其中将启动类注入到容器当中是比较关键的一步,首先先把 ApplicationContext 转换成 BeanDefinitionRegistry,然后创建 bean 定义加载器
public class SpringApplication {...protected void load(ApplicationContext context, Object[] sources) {if (logger.isDebugEnabled()) {logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));}// 2. 创建 BeanDefinitionLoaderBeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);...}private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {// 1. 将 ApplicationContext 转换成 BeanDefinitionRegistryif (context instanceof BeanDefinitionRegistry) {return (BeanDefinitionRegistry) context;}if (context instanceof AbstractApplicationContext) {return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();}throw new IllegalStateException("Could not locate BeanDefinitionRegistry");}
}
  • 创建 BeanDefinitionLoader 主要是把 BeanDefinitionRegistry 以及主类的 sources 进行赋值初始化
public class SpringApplication {private final Object[] sources;private final AnnotatedBeanDefinitionReader annotatedReader;private final XmlBeanDefinitionReader xmlReader;private BeanDefinitionReader groovyReader;private final ClassPathBeanDefinitionScanner scanner;private ResourceLoader resourceLoader;...protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {// 1. 创建 bean 定义加载器return new BeanDefinitionLoader(registry, sources);}BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {// 其中,sources 就是主类 com.lagou.SpringBootMytestApplicationAssert.notNull(registry, "Registry must not be null");Assert.notEmpty(sources, "Sources must not be empty");this.sources = sources;// 2. 注解形式的 Bean 定义读取器 比如:@Configuration @Bean @Component @Controller @Service等等this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);// 3. XML 形式的 Bean 定义读取器this.xmlReader = new XmlBeanDefinitionReader(registry);if (isGroovyPresent()) {this.groovyReader = new GroovyBeanDefinitionReader(registry);}// 4. 类路径扫描器this.scanner = new ClassPathBeanDefinitionScanner(registry);// 5. 扫描器添加排除过滤器this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));}
}
  1. 创建 BeanDefinitionLoader 成功后,就可以开始执行 load(),这里主要是先把主类注册到 IOC 容器中去
public class SpringApplication {private BeanNameGenerator beanNameGenerator;...protected void load(ApplicationContext context, Object[] sources) {if (logger.isDebugEnabled()) {logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));}// 创建 BeanDefinitionLoaderBeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);if (this.beanNameGenerator != null) {loader.setBeanNameGenerator(this.beanNameGenerator);}if (this.resourceLoader != null) {loader.setResourceLoader(this.resourceLoader);}if (this.environment != null) {loader.setEnvironment(this.environment);}// 1. 执行 load()loader.load();}
}
  • 其中 load() 通过 AnnotatedBeanDefinitionReader 将主类 source 注册进 beanDefinitionMap
class BeanDefinitionLoader {private final AnnotatedBeanDefinitionReader annotatedReader;...int load() {int count = 0;for (Object source : this.sources) {count += load(source);}return count;}// 根据加载不同类型调用不同的方法private int load(Object source) {Assert.notNull(source, "Source must not be null");// 1. 从 Class 加载if (source instanceof Class<?>) {return load((Class<?>) source);}...}private int load(Class<?> source) {// source 就是 class com.lagou.SpringBootMytestApplicationif (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {// Any GroovyLoaders added in beans{} DSL can contribute beans hereGroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);load(loader);}if (isComponent(source)) { // 判断方法有没有标记 Component 注解// 1. 将启动类的 BeanDefinition 注册进 beanDefinitionMapthis.annotatedReader.register(source);return 1;}return 0;}
}
  • 调用 register() 方法时,真正会传到 doRegisterBean() 进行执行。首先会把主类转换成 AnnotatedGenericBeanDefinition,然后获取主类的名称,把名称和 AnnotatedGenericBeanDefinition 封装成 BeanDefinitionHolder 后,注册到上下文中
public class AnnotatedBeanDefinitionReader {...public void register(Class<?>... componentClasses) {for (Class<?> componentClass : componentClasses) {registerBean(componentClass);}}public void registerBean(Class<?> beanClass) {doRegisterBean(beanClass, null, null, null, null);}private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {// 1. 把主类转换成 AnnotatedGenericBeanDefinition,其中 beanClass 为 com.lagou.SpringBootMytestApplicationAnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}abd.setInstanceSupplier(supplier);ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);abd.setScope(scopeMetadata.getScopeName());// 2. 获取类名 springBootMytestApplicationString beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);if (qualifiers != null) {for (Class<? extends Annotation> qualifier : qualifiers) {if (Primary.class == qualifier) {abd.setPrimary(true);}else if (Lazy.class == qualifier) {abd.setLazyInit(true);}else {abd.addQualifier(new AutowireCandidateQualifier(qualifier));}}}if (customizers != null) {for (BeanDefinitionCustomizer customizer : customizers) {customizer.customize(abd);}}// 3. 封装成 BeanDefinitionHolder BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);// 4. 注册到容器中		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);}
}
  • 注册到容器中,简单而言就是调用 BeanDefinitionRegistryBeanDefinitionHolder 的 name 和 AnnotatedGenericBeanDefinition 装配进去
public abstract class BeanDefinitionReaderUtils {...public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {// 1. 获取名称 springBootMytestApplicationString beanName = definitionHolder.getBeanName();// 2. 注册到 IOC 容器中registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());String[] aliases = definitionHolder.getAliases();if (aliases != null) {String[] var4 = aliases;int var5 = aliases.length;for(int var6 = 0; var6 < var5; ++var6) {String alias = var4[var6];registry.registerAlias(beanName, alias);}}}
}
  • 最后会把 name 和 AnnotatedGenericBeanDefinition 存入到 beanDefinitionMap
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {...public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");// 1. 先校验 beanDefinition 是否合法if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition)beanDefinition).validate();} catch (BeanDefinitionValidationException var8) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var8);}}BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {if (!this.isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}if (existingDefinition.getRole() < beanDefinition.getRole()) {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}} else if (!beanDefinition.equals(existingDefinition)) {if (this.logger.isDebugEnabled()) {this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}} else if (this.logger.isTraceEnabled()) {this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");}this.beanDefinitionMap.put(beanName, beanDefinition);} else {if (this.hasBeanCreationStarted()) {synchronized(this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;this.removeManualSingletonName(beanName);}} else {// 2. 再把 beanDefinition 放到 beanDefinitionMap 中this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition == null && !this.containsSingleton(beanName)) {if (this.isConfigurationFrozen()) {this.clearByTypeCache();}} else {this.resetBeanDefinition(beanName);}}	
}
  1. 总结
    在这里插入图片描述

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

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

相关文章

react-grid-layout 实现原理介绍

简介 React-grid-layout 是一个基于 React 的网格布局库&#xff0c;它可以帮助我们轻松地在网格中管理和排列组件。它提供了一个直观的 API&#xff0c;可以通过配置网格的大小、列数和组件的位置来实现复杂的布局&#xff0c;还支持对网格中组件的拖拽和重新排列。 实现 诉…

axios模拟表单提交

axios默认是application/json方式提交&#xff0c;controller接收的时候必须以RequestBody的方式接收&#xff0c;有时候不太方便。如果axios以application/x-www-form-urlencoded方式提交数据&#xff0c;controller接收的时候只要保证名字应对类型正确即可。 前端代码&#…

PMP项目管理主要学习内容是什么?

PMP项目管理是指根据美国项目管理学会(Project Management Institute&#xff0c;简称PMI)制定的项目管理知识体系和方法论进行项目管理的一种认证。PMP主要关注项目的规划、执行和控制等方面的知识和技能。 下面是PMP项目管理《PMBOK指南》第六版的主要学习内容&#xff1a; …

Matlab图像处理-灰度分段线性变换

灰度分段线性变换 如数学涵义的分段一般&#xff0c;分段线性变换就是将图像不同的灰度范围进行不同的线性灰度处理。其表达式可表示如下&#xff1a; 灰度分段线性变换可根据需求突出增强目标区域&#xff0c;而不增强非目标区间&#xff0c;达到特定的显示效果。 示例程序 …

通讯录(C语言)

通讯录 一、基本思路及功能介绍二、功能实现1.基础菜单的实现2.添加联系人信息功能实现3.显示联系人信息功能实现4.删除联系人信息功能实现5.查找联系人信息功能实现6.修改联系人信息功能实现7.排序联系人信息功能实现8.加载和保存联系人信息功能实现 三、源文件展示1.test.c2.…

YOKOGAWA CP461-50处理器模块

数据处理能力&#xff1a; CP461-50 处理器模块具有强大的数据处理能力&#xff0c;用于执行各种控制和数据处理任务。 多通道支持&#xff1a; 该模块通常支持多通道输入和输出&#xff0c;允许与多个传感器和执行器进行通信。 通信接口&#xff1a; CP461-50 处理器模块通常…

每日一题(复制带随机指针的链表)

每日一题&#xff08;复制带随机指针的链表&#xff09; 138. 复制带随机指针的链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 由于每个链表还包含了一个random节点指向了链表中的随机节点&#xff0c;所以并不能直接照搬复制原链表。首先想到的暴力思路是复…

Redis的介绍

Redis的架构介绍如下: 1. 概述 Redis是一个基于内存的高性能NoSQL键值数据库,支持网络访问和持久化特性。 2. 功能架构 Redis提供字符串、哈希、列表、集合、有序集合、位数组等多种数据结构,支持事务、Lua脚本、发布订阅、流水线等功能。 3. 技术架构 Redis使用单线程的…

oracle批量导出字段注释,并且相同字段注释为空的情况取有数据的第一行赋值

SELECT ‘comment on column ‘|| t.table_name||’.’||t.colUMN_NAME||’ is ’ || ‘’‘’ || (CASE WHEN T1.COMMENTS IS NULL THEN (SELECT T2.COMMENTS FROM User_Col_Comments T2 WHERE T1.colUMN_NAMET2.colUMN_NAME AND T2.COMMENTS IS NOT NULL and rownum1) ELSE N…

字节后端社招凉经

一面 1.聊项目。项目方案&#xff0c;技术 2.代码&#xff1a;K个一组翻转链表 3.GC如何排查及解决GC 4.说一下linux命令 grep用法&#xff0c;端口号查询 5.mybatis 注入 6.sql优化 二面 1.聊项目&#xff0c;项目方案&#xff0c;项目技术 2.代码&#xff1a;输入N,输出0-…

嵌入式学习笔记(1)ARM的编程模式和7种工作模式

ARM提供的指令集 ARM态-ARM指令集&#xff08;32-bit&#xff09; Thumb态-Thumb指令集&#xff08;16-bit&#xff09; Thumb2态-Thumb2指令集&#xff08;16 & 32 bit&#xff09; Thumb指令集是对ARM指令集的一个子集重新编码得到的&#xff0c;指令长度为16位。通常在…

Thymeleaf

这就是自动装配的原理 1) .SpringBoot启动会加载大量的自动配置类 2)、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中; 3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中&#xff0c;我们就不需要再手动配置了) 4)、给容器…

【Linux】linux nfs共享存储服务

linux nfs共享存储服务 目录 一、nfs服务 二、nfs优点 三、配置文件 四、共享文件配置过程 五、实验 1.创建共享文件&#xff08;两台终端共享&#xff09; 一、nfs服务 概念&#xff1a;网络上共享文件系统的协议&#xff0c;运行多个服务器之间通过网络共享文件和目…

【C++】内联函数 ( 概念简介 )

文章目录 一、内联行数简介1、内联函数引入 一、内联行数简介 1、内联函数引入 在 C 中 , 定义常量 const int a 10可以替换 C 语言中的 宏定义 #define a 10使用 常量 可以 替代 宏定义 ; 宏定义分为两种 , 一种是 宏常数 , 另外一种是 宏代码片段 ; C 中使用 常量 替代 宏…

渣土车识别监测 渣土车未盖篷布识别抓拍算法

渣土车识别监测 渣土车未盖篷布识别抓拍算法通过yolov7深度学习训练模型框架&#xff0c;渣土车识别监测 渣土车未盖篷布识别抓拍算法在指定区域内实时监测渣土车的进出状况以及对渣土车未盖篷布违规的抓拍和预警。YOLOv7 的策略是使用组卷积来扩展计算块的通道和基数。研究者将…

webRtc 示例

1、使用socket.io进行会话 2、为了方便&#xff0c;参数写死在前端了&#xff0c;前端界面1代码如下&#xff08;由界面1发起视频&#xff09;&#xff1a; <!DOCTYPE html> <html><head><title>Socket.IO chat</title><meta charset"…

数字化新零售平台系统提供商,门店商品信息智慧管理-亿发进销存

传统的批发零售业务模式正面临着市场需求变化的冲击。用户日益注重个性化、便捷性和体验感&#xff0c;新兴的新零售模式迅速崛起&#xff0c;改变了传统的零售格局。如何在保持传统业务的基础上&#xff0c;变革发展&#xff0c;成为了业界亟需解决的问题。 在这一背景下&…

JixiPix Artista Impresso Pro for mac(油画滤镜效果软件)

JixiPix Artista Impresso pro Mac是一款专业的图像编辑软件&#xff0c;专为Mac用户设计。它提供了各种高质量的图像编辑工具&#xff0c;可以帮助您创建令人惊叹的图像。该软件具有直观的用户界面&#xff0c;使您可以轻松地浏览和使用各种工具。 它还支持多种文件格式&…

前端是leyui后端sqlserver和maraDB进行分页

项目场景&#xff1a; 前端是leyui后端sqlserver和maraDB进行分页,两种数据库在后端分页的不同写法 解决方案&#xff1a; 前端: 定义table,表格的格式在接口返回时进行创建,根据id进行绑定 <div class"layui-tab-item layui-show" style"padding-top: 10…

@XmlType,@XmlRootElement,@XmlAttribute的作用与区别

XmlType、XmlRootElement 和 XmlAttribute 都是 Java 标准库中 javax.xml.bind.annotation 包提供的注解&#xff0c;用于在使用 JAXB&#xff08;Java Architecture for XML Binding&#xff09;或其他 XML 绑定技术时&#xff0c;控制 Java 类与 XML 数据之间的映射关系。 它…