SpringBoot系列-2 自动装配

背景:

Spring提供了IOC机制,基于此我们可以通过XML或者注解配置,将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC,比较繁琐。
基于"约定优于配置"原则的自动装配机制为该问题提供了一个解决方案。

不同SpringBoot版本细节部分存在差异,本文基于SpringBoot的2.3.2.RELEASE版本进行说明

1.自动装配机制

SpringBoot在启动时通过SPI机制扫描所有JAR包下的spring.factories文件,将文件中EnableAutoConfiguration包含的配置类全部加载到容器中。
根据各个配置类的条件确定是否进行装载,条件包括:容器中有无指定Bean,类路径中有无指定Class对象等。配置类内部Bean的定义也可通过条件确定是否进行装载。
Spring在spring-boot-autoconfigure包中为三方件定义了很多配置类,并提供了对应的starter依赖;用户只需通过引入对应的starter依赖即可完成对应三方件的组装。

以redis为例:

[1] 在pom.xml中添加redis对应的starter:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

[2] 在spring配置文件中添加对redis的配置:

spring:redis:host: localhostport: 6379timeout: 3000database: 0

[3] 测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class RedisComponentTest {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Testpublic void testRedis() {Assert.assertEquals("testValue", redisTemplate.opsForValue().get("testKey"));}
}

Note:在redis中添加"testKey" -> "testValue"后,该测试用例就可以运行成功。
原因分析:
在spring-boot-autoconfigure的spring.factories文件中有如下定义:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...

进入RedisAutoConfiguration配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {@Bean@ConditionalOnMissingBean(name = "redisTemplate")public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//...}@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//...}
}

RedisAutoConfiguration自动配置类的装配条件是@ConditionalOnClass(RedisOperations.class), 即类路径中包含RedisOperations.class. RedisOperations定义在spring-data-redis包中,而依赖的spring-boot-starter-data-redis包含了对spring-data-redis的依赖。

另外,在[SpringBoot系列-1 启动流程]中的也提到过使用jetty代替tomcat的方式:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId><version>2.6.4</version>
</dependency>

即类路径中删除了对Tomcat的默认依赖,添加了对Jetty的依赖;在自动配置类 ServletWebServerFactoryConfiguration中因找不到Tomcat.class对象而不会装配Tomcat相关组件,因引入了jetty的starter而装配Jetty容器。

2.自定义starter

除了SpringBoot自定义的starter外,也有第三方自定义的starter, 如常见的mybatis:

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version>
</dependency>

用户也可基于SpringBoot提供的自动装配机制自定义starter,从而可以从多个项目中抽出重复的逻辑,以减少不必要的重复操作。本章通过一个完整的案例进行说明。

2.1 准备pom文件:

<groupId>com.demo</groupId>
// [标注1]
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0.0</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>2.7.5</version></dependency>
</dependencies>

Note 1: springboot官方的starter依赖基本是pom, 用于关联需要的依赖项。而用户或者第三方自定义时,starter需要包含:配置类、依赖项、spring.factories文件。另外,命名时需要遵循命名规范:springboot定义的形式如spring-boot-starter-xxx, 用户自定义的形式如xxx-spring-boot-starter.

只需引入spring-boot-autoconfigure依赖即可,因为spring-boot-autoconfigure依赖了spring-boot,而spring-boot依赖了spring.

2.2 定义属性配置类

属性配置类用于提供用户自定义能力:

@ConfigurationProperties("demo.configure")
public class DemoProperties {private String userName;private String password;// getter和setter方法
}

用户可以在spring.yml等配置文件中通过"demo.configure"对DemoProperties的userName和password属性进行配置。

2.3 定义服务类

服务类包含了该组件的核心逻辑:

public class DemoService {private final DemoProperties demoProperties;public DemoService(DemoProperties demoProperties) {this.demoProperties = demoProperties;}public Boolean check(String name, String password) {if (name == null || password == null) {return false;}return name.equals(demoProperties.getUserName()) && password.equals(demoProperties.getPassword());}
}

此时提供了一个服务方法,校验用户名和密码。

2.4 自动装配类

@Configuration
//导入属性配置类
@EnableConfigurationProperties(DemoProperties.class)
@ConditionalOnClass(DemoService.class)
public class DemoAutoConfiguration {@Beanpublic DemoService demoService(DemoProperties demoProperties) {return new DemoService(demoProperties);}
}

添加了@ConditionalOnClass(DemoService.class)表示当DemoService.class在类路径中时,该自动装配类才会生效。

2.5 spring.factories文件

在resources目录下新增META-INF/spring.factories文件,指定自动配置类:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.caltta.demo.DemoAutoConfiguration

2.6 使用方式

将上述的starter项目install到仓库后,在其他项目中可通过如下方式引入:

<dependency><groupId>com.caltta</groupId><artifactId>demo-spring-boot-starter</artifactId><version>1.0.0</version>
</dependency>

在application.yml文件中配置:

demo:configure:userName: rootpassword: Root.123

测试用例:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class DemoComponentTest {@Autowiredprivate DemoService demoService;@Testpublic void testDemoService() {Assert.assertTrue(demoService.check("root", "Root.123"));}
}

测试用例可正常运行。

3.原理

3.1 @SpringBootApplication注解

@SpringBootApplication注解是由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan注解组成的复合注解:
(1) @SpringBootConfiguration本质上是一个@Configuration注解;
(2) @ComponentScan定义了包扫描路径;
(3) @EnableAutoConfiguration开启自动装配。

@SpringBootApplication注解中定义了几个属性:
(1) scanBasePackages/scanBasePackageClasses桥接给@ComponentScan,用于确定扫描包路径,默认我注解类所在路径;
(2) exclude/excludeName桥接给@EnableAutoConfiguration,用于排除自动装配的类;
(3) proxyBeanMethods桥接给@Configuration注解,用于确定代理类型。

3.2 @EnableAutoConfiguration注解

@EnableAutoConfiguration由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)组成:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {Class<?>[] exclude() default {};String[] excludeName() default {};
}

其中,@AutoConfigurationPackage注解用于向IOC添加一个BasePackages类型的Bean对象,属性默认为注解所在类的包名。
@Import(AutoConfigurationImportSelector.class)用于向容器导入AutoConfigurationImportSelector对象,该部分是整个装配机制的关键。

3.3 AutoConfigurationImportSelector

AutoConfigurationImportSelector是DeferredImportSelector接口的实现类,更是ImportSelector接口的实现类。
selectImports方法如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {// 判断是否开启自动装配if (!isEnabled(annotationMetadata)) {return {};}// 获取&&返回需要装配的类型列表AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

Note: 在ConfigurationClassPostProcessor处理@Import注解时,对于DeferredImportSelector类型调用的是getAutoConfigurationEntry方法。

上述逻辑住就要包含两个方法:
isEnabled方法表示是否开启自动装配,逻辑如下:

protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass() == AutoConfigurationImportSelector.class) {return getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true);}return true;
}

Note:以"spring.boot.enableautoconfiguration"为key从环境变量中取值,如果为false则关闭自动装配,其他情况(空或者false)开启。如:在application.yml中配置“spring.boot.enableautoconfiguration”值为false即可关闭。
getAutoConfigurationEntry方法用于获取待装配的类:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 获取注解属性,即@EnableAutoConfiguration的exclude和excludeNameAnnotationAttributes attributes = getAttributes(annotationMetadata);// 根据SPI机制从spring.factories中加载EnableAutoConfiguration的值List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 去重,因为spring.factories文件加载自多个jar包-可能有重复configurations = removeDuplicates(configurations);// 根据@EnableAutoConfiguration的exclude和excludeName进行排除Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 对初步排除的结果进行再次过滤configurations = getConfigurationClassFilter().filter(configurations);// 发送事件&&返回结果fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}

getExclusions方法获取需要排除的装配类:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {Set<String> excluded = new LinkedHashSet<>();excluded.addAll(asList(attributes, "exclude"));excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));// 从环境变量中"spring.autoconfigure.exclude"指定的类型数组excluded.addAll(getExcludeAutoConfigurationsProperty());return excluded;
}

Note: 排除自动装配可通过@EnableAutoConfiguration的exclude和excludeName属性,也可通过在application.yml中设置"spring.autoconfigure.exclude"值来进行排除。

getConfigurationClassFilter().filter(configurations)方法对候选的自动装配类进行再一次过滤。
getConfigurationClassFilter()获取配置自动配置过滤器的主要逻辑如下:

private ConfigurationClassFilter getConfigurationClassFilter() {//...List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);//...
}protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

Note-1: 获取过滤器:
从spring.factories文件中获取AutoConfigurationImportFilter对应的值。spring-boot-autoconfigure包中的spring.factories文件中有如下定义:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

默认情况下(无用户自定义&&三方件引入),只有OnBeanCondition、OnClassCondition、OnWebApplicationCondition三个过滤器。该过滤器与自动装配的元数据配合实现快速排除不必要的自动配置类,加快容器启动速度。

Note-2: 构造ConfigurationClassFilter

new ConfigurationClassFilter(this.beanClassLoader, filters)方法构造时,传入了过滤器,同时从类路径加载了元数据:

ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {// 加载"META-INF/spring-autoconfigure-metadata.properties"文件内容this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);this.filters = filters;
}

Note-3: 执行过滤

List<String> filter(List<String> configurations) {String[] candidates = StringUtils.toStringArray(configurations);boolean skipped = false;for (AutoConfigurationImportFilter filter : this.filters) {boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);for (int i = 0; i < match.length; i++) {if (!match[i]) {candidates[i] = null;skipped = true;}}}if (!skipped) {return configurations;}List<String> result = new ArrayList<>(candidates.length);for (String candidate : candidates) {if (candidate != null) {result.add(candidate);}}return result;
}

逻辑较为清晰:对每个候选的自动配置类都进行三个过滤器的过滤操作(调用过滤器的match方法),只有三个过滤器都返回true才会保留;否则会被标记为false,然后排除。skipped用于优化流程,没有匹配失败情况,可快速退出。

遍历过滤器调用filter.match(candidates, this.autoConfigurationMetadata)方法,以OnClassCondition为例进行说明。

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// 省略日志...ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);boolean[] match = new boolean[outcomes.length];for (int i = 0; i < outcomes.length; i++) {match[i] = (outcomes[i] == null || outcomes[i].isMatch());}return match;
}

入参中: autoConfigurationClasses表示候选的自动装配类列表,autoConfigurationMetadata表示加载的自动配置元数据。
getOutcomes方法根据autoConfigurationMetadata对每个候选的自动装配类生成一个匹配结果,结果为空或者true表示匹配,继续看getOutcomes方法实现细节:

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);} else {OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());return outcomesResolver.resolveOutcomes();}
}

根据处理器个数进行优化,确定是否折成两半分别进行,本质还是调用了StandardOutcomesResolver的resolveOutcomes方法:

public ConditionOutcome[] resolveOutcomes() {return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {ConditionOutcome[] outcomes = new ConditionOutcome[end - start];for (int i = start; i < end; i++) {String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {// 从元数据中获取ConditionalOnClass为key尾缀的值String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");if (candidates != null) {outcomes[i - start] = getOutcome(candidates);}}}return outcomes;
}

Note:
在spring-boot-autoconfigure包中定义的spring-autoconfigure-metadata.properties文件有如下定义:

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=\
org.springframework.data.redis.core.RedisOperations

表示此阶段会根据类路径中是否存在RedisOperations类确定是否排除自动配置类RedisAutoConfiguration。
继续跟踪getOutcome(candidates)方法进入:

private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {// 会根据类加载机制是否排除异常,确定类是否存在if (ClassNameFilter.MISSING.matches(className, classLoader)) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class").items(Style.QUOTE, className));}return null;
}

ClassNameFilter.MISSING的matches方法实现如下:

public boolean matches(String className, ClassLoader classLoader) {return !isPresent(className, classLoader);
}
static boolean isPresent(String className, ClassLoader classLoader) {if (classLoader == null) {classLoader = ClassUtils.getDefaultClassLoader();}try {// 使用类加载器加载classNameresolve(className, classLoader);return true;} catch (Throwable ex) {return false;}
}

isPresent方法通过类加载器去类路径中加载,加载成功则返回true,否则返回false.
上述为OnClassCondition过滤机制。

4.整体流程

对于一个SpringBoot项目,已经知道了自动装配机制的实现原理;现在再结合@Configuration注解分章节梳理一下Bean的注入IOC的流程。
这部分需要读者对Spring启动流程ConfigurationClassPostProcessorSpringBoot启动流程有比较清晰的理解,可参考:Spring系列-11 @Configuration注解原理 和 SpringBoot系列-1启动流程和 Spring系列-1 启动流程.

4.1 主配置类注入阶段

为表述方便,使用SpringBoot系列-1启动流程中案例进行介绍,如下所示:

@SpringBootApplication
// 标注[1]
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

SpringBoot系列-1启动流程的章节-2.2中介绍:在SpringApplication对象的run方法中,刷新Spring容器前的准备阶段中通过BeanDefinitionLoader将主配置类导入IOC中,即此时主配置类DemoApplication已被导入IOC容器。

4.2 获取配置类

配置类值被@Configuration注解的Bean。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor接口的实现类,更是BeanFactoryPostProcessor接口实现类,因此在容器刷新阶段会通过invokeBeanFactoryPostProcessors方法调用其勾子方法(时机比注入非懒加载靠前)。
调用勾子逻辑进入ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// ⚠️第一阶段:List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// ...// 	⚠️第二阶段...
}

processConfigBeanDefinitions方法可以分为两个阶段:
(1) 第一阶段:从IOC容器中获取配置类;
(2) 第二阶段:解析配置类获取Bean对象,并讲所有的Bean对象注入到IOC中.

实际上,此时获取的configCandidates获取的就是 章节-4.1 主配置类注入阶段 中注入IOC的DemoApplication.

4.3 解析配置类&&启动自动装配

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// ⚠️第一阶段...// ⚠️第二阶段:ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {parser.parse(candidates);parser.validate();// ...Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);this.reader.loadBeanDefinitions(configClasses);// ...}while (!candidates.isEmpty());// ...
}

该阶段可以分为两个步骤:解析出所有的Bean、注册解析得到的Bean。核心逻辑在于前者,配置类解析依赖于解析器ConfigurationClassParser,存在递归逻辑,用图解表示如下:
在这里插入图片描述
DemoApplication类上注解了@SpringBootApplication,继而间接注解了@Import(AutoConfigurationImportSelector.class),在4.3 解析配置类阶段会通过ImportSelect逻辑导入AutoConfigurationImportSelector类,从而启动自动装配过程。

Note: 上图中的条件过滤用于处理注解在自动配置类中添加的@Conditional注解。

4.4 条件注解

条件注解的解析和判断在ConditionEvaluator类的shouldSkip中方法进行,读者可自行阅读。

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

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

相关文章

Unity中全局光照GI的总结

文章目录 前言一、在编写Shader时&#xff0c;有一些隐蔽的Bug不会直接报错&#xff0c;我们需要编译一下让它显示出来&#xff0c;方便修改我们选择我们的Shader&#xff0c;点击编译并且展示编译后的Shader后的内容&#xff0c;隐蔽的Bug就会暴露出来了。 二、我们大概回顾一…

Python 潮流周刊#26:requests3 的现状

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 你好&#xff0c;我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容&#xff0c;大部分为英文。本周刊开源&#xff0c;欢迎投稿[1]。另有电报频道[2]作为副刊&#xff0c;补充发布更加丰富的资讯。 &#…

每天一点python——day66

#每天一点Python——66 #字符串的分隔 #如图&#xff1a; #方法①split()从左开始分隔&#xff0c;默认空格为分割字符&#xff0c;返回值是一个列表 shello world jisuanji#首先创建一个字符串 list1s.split() print(list1)#输出结果是&#xff1a;[hello, world, jisuanji]注…

No179.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

python实现炒股自动化,个人账户无门槛量化交易的开始

本篇作为系列教程的引子&#xff0c;对股票量化程序化自动交易感兴趣的朋友可以关注我&#xff0c;现在只是个粗略计划&#xff0c;后续会根据需要重新调整&#xff0c;并陆续添加内容。 股票量化程序化自动交易接口 很多人在找股票个人账户实现程序化自动交易的接口&#xff0…

【笔记】结合P02项目——maven继承与聚合

maven的继承关系 P02项目大概是这个样子&#xff0c;下图展示的是其父工程 父工程配置了parent依赖springb-boot-starter-parent&#xff0c;子工程配置其parant为父工程 子工程引用common子工程 maven的版本锁定 管理子工程的版本号问题 父工程添加dependencyManageMent…

【Docker】Docker 网络

引言 Docker是一个开源的应用容器引擎&#xff0c;它允许开发者将应用及其依赖打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器或Windows机器上&#xff0c;也可以实现虚拟化。Docker的主要优势之一是其网络功能&#xff0c;而网络功能的核心就是网络驱动…

剖析WPF模板机制的内部实现

剖析WPF模板机制的内部实现 众所周知&#xff0c;在WPF框架中&#xff0c;Visual类是可以提供渲染&#xff08;render&#xff09;支持的最顶层的类&#xff0c;所有可视化元素&#xff08;包括UIElement、FrameworkElment、Control等&#xff09;都直接或间接继承自Visual类。…

Maya 2024 for Mac(3D建模软件)

Maya 2024是一款三维计算机图形软件&#xff0c;具有强大的建模、动画、渲染、特效等功能&#xff0c;广泛应用于影视、游戏、广告等行业。以下是Maya 2024软件的主要功能介绍&#xff1a; 建模&#xff1a;Maya 2024具有强大的建模工具&#xff0c;包括多边形建模、曲面建模、…

Leetcode100120. 找出强数对的最大异或值 I

Every day a Leetcode 题目来源&#xff1a;100120. 找出强数对的最大异或值 I 解法1&#xff1a;模拟 枚举 2 遍数组 nums 的元素&#xff0c;更新最大异或值。 代码&#xff1a; /** lc appleetcode.cn id100120 langcpp** [100120] 找出强数对的最大异或值 I*/// lc c…

【大模型】大语言模型语料下载

文章目录 概述Hugging Faceobs操作git-lfs例子RedPajama-Data-1TSlimPajama-627B/git clone续传 数据格式参考资料 概述 大模型训练中语料是非常重要的&#xff0c;目前公网上有各种各样的语料可以供下载&#xff0c;但是不可能每个用户、每次训练任务都通过公网去拉取语料&am…

MYSQL操作详解

一)计算机的基本结构 但是实际上&#xff0c;更多的是这种情况: 二)MYSQL中的数据类型: 一)数值类型: 数据类型内存大小(字节)说明bit(M)M指定位数,默认为1单个二进制位值&#xff0c;或者为0或者为1&#xff0c;主要用于开/关标志tinyint1字节1个字节的整数值&#xff0c;支持…

快速走进通信世界 --- 基础知识扫盲

想不到吧&#xff0c;家人们&#xff0c;博主好久没来更新文章了&#xff0c;而且这次更新的是关于通信工程的文章。博主确实以前一直更新关于编程的文章&#xff0c;只不过最近在学习一些新的知识&#xff0c;以后有机会了我还是会继续更新一些编程技术文章的。不过每一门技术…

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测(SE注意力机制)

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09; 目录 时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描述 1.MATLA…

数字政府!3DCAT实时云渲染助推上海湾区数字孪生平台

数字孪生&#xff0c;是一种利用物理模型、传感器数据、运行历史等信息&#xff0c;在虚拟空间中构建实体对象或系统的精确映射&#xff0c;从而实现对其全生命周期的仿真、优化和管理的技术。数字孪生可以应用于各个领域&#xff0c;如工业制造、智慧城市、医疗健康、教育培训…

idea使用git删除本地提交(未推送)

1、找到reset head 2、打开弹窗&#xff0c;在HEAD后面输入^ 结果为HEAD^ 注释&#xff1a; Reset Type 有三种&#xff1a; Mixed&#xff08;默认方式&#xff09;&#xff0c;保留本地源码&#xff0c;回退 commit 和 index 信息&#xff0c;最常用的方式Soft 回退到某个版本…

Android选项卡TabHost

选项卡主要由TabHost(标签&#xff0c;主人)&#xff0c;TabWidget(微件)和FrameLayout3个组件组成&#xff0c;用于实现一个多标签页的用户界面。 1. TabHost在XML文件中添加&#xff1a; XML布局文件中添加选项卡时必须使用系统id来为各组件指定id属性。 <TabHostandro…

1366 - Incorrect string value: ‘\xE5\xB9\xBF\xE5\x85\xB0...‘ for column编码错误

1366 - Incorrect string value: ‘\xE5\xB9\xBF\xE5\x85\xB0…’ for column ‘campus_name’ at row 1 > 查询时间: 0s 原因是数据库创建的时候使用的默认编码latin1&#xff0c;导致表和字段的编码格式都是这种编码&#xff0c;显然这种编码不支持中文。 自己修改了数据库…

MySQL表的增删改查(进阶)

目录 数据库约束 约束的定义 约束类型 null约束 unique:唯一约束 default:默认值约束 primary key:主键约束(重要) foreign key:外键约束(描述两个表之间的关联) 表的设计 一般思路 三大范式 一对一 一对多 ​编辑 多对多 ​编辑 新增 查询 聚合查询 聚合函…