SpringBoot源码分析-自动装配-实现原理

文章目录

  • SpringBoot自动装配
    • 前言
    • 介绍
    • 实现原理
      • @SpringBootApplication
      • @EnableAutoConfiguration
      • selectImports方法没有走?
        • DeferredImportSelector
        • 源码分析
        • 设计目的
    • 总结

SpringBoot自动装配

前言

什么是自动装配?用过Spring的应该都知道,虽然后期Spring引入了注解功能,但开启某些特性或者功能的时候,还是不能完全省略xml配置文件。下面这些配置用过Spring的应该都很熟悉,几乎每个项目都有。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd"><context:component-scan base-package="com.xxx" /><mvc:annotation-driven /><!-- 配置视图解析器 --><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/"></property><property name="suffix" value=".jsp"></property></bean><!--  文件模版引擎配置  --><bean id="freemarkerConfiguration" class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean"><property name="templateLoaderPath" value="classpath:tpl/"/><property name="defaultEncoding" value="UTF-8"/></bean><bean id="freemarkEngine" class="com.kedacom.web.freemark.FreemarkEngine"><property name="configuration" ref="freemarkerConfiguration"/></bean><!-- Spring RestTemplate config --><bean id="httpClientFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"><property name="connectTimeout" value="10000"/><property name="readTimeout" value="10000"/></bean><bean id="restTemplate" class="org.springframework.web.client.RestTemplate"><constructor-arg ref="httpClientFactory"/></bean><!-- 还会引入其他方面的配置,例如:数据库,事务,安全,邮件等等 --><import resource="classpath*:/applicationContext-bean.xml"/><import resource="classpath*:/applicationContext-orm.xml"/><import resource="classpath*:/conf/application-security.xml"/><import resource="classpath*:/conf/app-email.xml"/><import resource="classpath*:/applicationContext-webservice.xml"/>
</beans>

而SpringBoot呢?只需要添加相关依赖,无需配置,通过 main 方法即可启动项目。如果要修改默认配置,可以局配置文件 application.propertiesapplication.yml即可对项目进行定制化设置,比如:更换tomcat端口号,配置 JPA 属性等等。

而之所以如此简便就是得益于自动装配机制。

介绍

什么是自动装配?自动装配其实在Spring Framework的后期版本中就实现了。Spring Boot只是在此基础上,使用SPI做了进一步优化。

SPI,全称为 Service Provider Interface,是一种服务发现机制。在JDK的JDBC中就已经使用过。Spring也是模仿JDK设计的。Spring的SPI机制规定:SpringBoot在启动时会扫描第三方 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring容器中,并执行类中定义的各种操作。对于第三方jar 来说,只需要按照SpringBoot定义的标准,就能将自己的功能自动装配进 SpringBoot中。

有了自动装配,在Spring Boot中如果要引入一些新功能,只需要在依赖中引入一个starter和做一些简单配置即可。例如:要在项目中使用redis的话,只需要引入下面的starter。

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

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

spring:redis:host: 127.0.0.1 port: 6379
	@Autowiredprivate RedisTemplate<String,String> redisTemplate;

因此自动装配可以简单理解为:通过starter、注解、配置等方式大大简化了Spring实现某些功能的步骤。

实现原理

@SpringBootApplication

那么SpringBoot是怎么实现自动装配的呢?我们在SpringBoot的启动类中基本都会用到这个注解@SpringBootApplication,我们可以看下他的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

在这个注解的上面几个注解中,其中上面4个是jdk的元注解,后面3个是重点:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。

@ComponentScan:是Spring的注解,用来替换xml中的<context:component-scan />标签的。主要用于扫描被@Component @Service,@Controller注解的 bean的,这个注解默认会扫描启动类所在的包下所有的类。所以不指定basePackage也可以。

@SpringBootConfiguration:这个是Springboot的注解,我们可以把他看成优化版的@Configuration。看了它的源码就知道,它是在@Configuration注解的基础上加了一个@Indexed注解,用于优化Spring启动速度的。

@EnableAutoConfiguration这个注解就是自动装配的核心注解了。接下来,我们来详细分析下这个注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};}

@EnableAutoConfiguration

@EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot的IoC容器中。

注解内部引用了@AutoConfigurationPackage注解以及导入了一个AutoConfigurationImportSelector类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};}

这个AutoConfigurationImportSelector类实现了DeferredImportSelector接口,是ImportSelector的子接口,所以根据@Import注解的实现原理,实际会调用selectImports方法来实现导入。这个方法的返回值中的所有类都会被加入到IOC容器中,我们来看下方法源码:

public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadataAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// 获取所有候选配置类EnableAutoConfigurationAutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

重点关注第9行的getAutoConfigurationEntry方法。这个方法主要负责加载自动配置类。

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//1.判断自动装配开关是否打开if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}//2.获取`EnableAutoConfiguration`注解中的属性AnnotationAttributes attributes = getAttributes(annotationMetadata);//3.加载当前项目类路径下 `META-INF/spring.factories` 文件中声明的配置类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//4.移除掉重复的configurations = removeDuplicates(configurations);//5.应用注解中的排除项Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 6.根据另一个配置文件中的配置,进一步过滤不符合条件的类configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}

接下来详细分析一下这个方法的实现:

  1. 判断自动装配开关是否打开。默认是打开的。spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置。

  2. 获取EnableAutoConfiguration注解中的属性,例如: excludeexcludeName

  3. 加载当前项目类路径下 META-INF/spring.factories 文件中声明的配置类。

    1. 注意:不光是SpringBoot自己的这个配置文件中的类,实际会加载项目中依赖的所有的这个文件。

    2. 如果你的项目引入了Mybatis-plus,可以清楚的看到,它的starter中也是有这个文件的。

    3. Maven: com.baomidou:mybatis-plus-boot-starter:3.5.1

    4. # Auto Configure
      org.springframework.boot.env.EnvironmentPostProcessor=\com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
      
  4. 加载了这么多依赖包中的配置文件,肯定会有重复的。这部就是去重。

  5. 在注解中会指定排除哪个类的,这一步就是应用排除项。例如:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

  6. 根据另一个配置文件中的配置,进一步过滤不符合条件的类。

    1. 通过前面的步骤过滤了不少类,但还剩下很多类需要加载。

    2. 例如:Spring自己的配置文件中就有130多个配置类。难道全部都要加载?

      1. Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.15中的配置。

      2. spring-boot-autoconfigure-2.5.15.jar!\META-INF\spring.factories

      3. # Auto Configure
        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
        org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
        org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
        org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
        org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
        org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
        org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
        org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
        org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
        org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
        org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
        org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
        org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
        org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
        org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
        org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
        org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
        org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
        org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
        org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
        org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
        org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
        org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
        org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
        org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
        org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
        org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
        org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
        org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
        org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
        org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
        org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
        org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
        org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
        org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
        org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
        org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
        org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
        org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
        org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
        org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
        org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
        org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
        org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
        org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
        org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
        org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
        org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
        org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
        org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
        org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
        org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
        org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
        org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration,\
        org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
        org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
        org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
        org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
        org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
        org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
        org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
        org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
        org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
        org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
        org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
        org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
        org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration,\
        org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
        org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
        org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
        org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
        org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
        org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
        org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
        org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
        org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
        org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
        org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
        org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
        
      4. 可以看到这里面其实有很多我们暂时用不到的类。例如:WebSocket相关的,mongodb相关的,ES相关的。等等。

    3. SpringBoot会从另一个配置文件中,加载上述类的加载条件:spring-autoconfigure-metadata.properties

      1. 例如上文提到的ES配置类,在这个文件中,有如下配置:

      2. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration=
        org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration
        org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration.ConditionalOnClass=org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate
        
      3. 其中ConditionalOnClass的意思就是只有当Classpath中存在这个类才会加载。

    4. 有兴趣的童鞋可以了解下,SpringBoot的所有条件注解。

      1. @ConditionalOnBean:当容器里有指定 Bean 的条件下
      2. @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
      3. @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
      4. @ConditionalOnClass:当类路径下有指定类的条件下
      5. @ConditionalOnMissingClass:当类路径下没有指定类的条件下
      6. @ConditionalOnProperty:指定的属性是否有指定的值
      7. @ConditionalOnResource:类路径是否有指定的值
      8. @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
      9. @ConditionalOnJava:基于 Java 版本作为判断条件
      10. @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
      11. @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
      12. @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
  7. 分析结束

selectImports方法没有走?

参考文章:https://zhuanlan.zhihu.com/p/458533586

上面我们在分析自动装配实现原理的时候说过,@EnableAutoConfiguration注解通过 @Import注解导入了一个 ImportSelector接口的实现类 AutoConfigurationImportSelector。按照我们对 @Import 注解的理解,应该会执行 selectImports 接口方法,但调试的时候,执行的情况好像和我们期待的不一样哦,没有走 selectImports()方法中。但是实际确实能进到getAutoConfigurationEntry()方法中。那这是为什么呢?原因就在于AutoConfigurationImportSelector实现的这个接口DeferredImportSelector有点特殊。

DeferredImportSelector
AutoConfigurationImportSelector
DeferredImportSelector
ImportSelector

从上面的类图中可以看到。DeferredImportSelector接口是ImportSelector的子接口,所以他也具备ImportSelector的功能。

如果我们仅仅是实现了DeferredImportSelector接口,重写了selectImports方法,那么selectImports方法是会被执行的。

我们可以用代码测试一下。

public class MyDeferredImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println("MyDeferredImportSelector.selectImports()方法执行...");return new String[0];}
}/*** SpringBoot应用启动类*/
@Import(MyDeferredImportSelector.class)
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}//启动执行效果:
2023-10-10 17:40:04.653  INFO 14304 --- [           main] c.sjj.mashibing.springboot.Application   : No active profile set, falling back to 1 default profile: "default"
MyDeferredImportSelector.selectImports()方法执行...
2023-10-10 17:40:05.257  INFO 14304 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-10-10 17:40:05.262  INFO 14304 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-10-10 17:40:05.263  INFO 14304 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.75]

所以AutoConfigurationImportSelector中应该是做了什么特殊处理导致的没进入selectImports()方法。进一步观察源码会发现,AutoConfigurationImportSelector类内部还覆盖了getImportGroup()方法,同时还返回了它自己内部的一个实现了Group接口的静态内部类的Class对象。然后我们在自己创建的类内部也模仿这覆盖一下这个方法试试。覆盖getImportGroup()方法,同时返回静态内部类。

public class MyDeferredImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println("MyDeferredImportSelector.selectImports()方法执行...");return new String[0];}@Overridepublic Class<? extends Group> getImportGroup() {System.out.println("MyDeferredImportSelector.getImportGroup()方法执行...");return MyDeferredImportSelectorGroup.class;}public static class MyDeferredImportSelectorGroup implements Group{private final List<Entry> imports = new ArrayList<>();@Overridepublic void process(AnnotationMetadata metadata, DeferredImportSelector selector) {System.out.println("MyDeferredImportSelectorGroup.process()");}@Overridepublic Iterable<Entry> selectImports() {System.out.println("MyDeferredImportSelectorGroup.selectImports()");return imports;}}
}

然后运行项目就会发现同样的现象出现了,MyDeferredImportSelector的selectImports方法没有执行。执行的是getImportGroup()方法和MyDeferredImportSelectorGroup中的process()和selectImports()方法。

2023-10-10 17:56:20.711  INFO 16552 --- [           main] c.sjj.mashibing.springboot.Application   : No active profile set, falling back to 1 default profile: "default"
MyDeferredImportSelector.getImportGroup()方法执行...
MyDeferredImportSelectorGroup.process()
MyDeferredImportSelectorGroup.selectImports()
2023-10-10 17:56:21.286  INFO 16552 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

至此我们就可以推导出一个结论:

如果一个类实现了DeferredImportSelector接口,如果它没有实现getImportGroup()接口方法的话(这个接口方法有default实现,所以可以不实现),那就会走入getImportGroup()方法。

如果他实现了getImportGroup()方法,那就不会走自己的selectImports()方法。而是会走入getImportGroup()这个方法返回类型的内部类的实现方法中。

源码分析

上文我们是通过现象推断出的结论,那么SpringBoot是在哪里对DeferredImportSelector接口做的特殊处理?又为什么要这么设计呢?

我们可以继续分析源码,这个就需要追溯到@Import注解的实现原理了。

有一个简单办法,可以快速追溯到调用路径,就是使用IDEA在进行debug的时候,在debug窗口是可以看到方法调用堆栈的。

我们可以将断点先设置在getAutoConfigurationEntry()方法中,然后当进入到断点看Debugger窗口,如下图:
在这里插入图片描述

从方法调用堆栈中可以看到@Import注解是在这里被处理:

  1. 首先是启动项目的main方法,然后调用run方法。

  2. 接着会调用AbstractApplicationContext.refresh()进行ioc容器的初始化

  3. 然后进入到refresh的第四个方法invokeBeanFactoryPostProcessors()中,这里会调用所有的BeanFactory后处理器

  4. 接着进入到负责处理@Configuration、@Import等注解的ConfigurationClassPostProcessor类中。

  5. 然后重点关注的代码来了。ConfigurationClassParser.parse()方法。我们来看下这里面的代码:

  6. 	public void parse(Set<BeanDefinitionHolder> configCandidates) {// 循环遍历configCandidatesfor (BeanDefinitionHolder holder : configCandidates) {// 获取BeanDefinitionBeanDefinition bd = holder.getBeanDefinition();// 根据BeanDefinition类型的不同,调用parse不同的重载方法,实际上最终都是调用processConfigurationClass()方法try {if (bd instanceof AnnotatedBeanDefinition) {// 解析注解类型(@Import注解就在这里解析)parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {// 解析有class对象的parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}// 执行找到的DeferredImportSelector// ImportSelector被设计成和@Import注解同样的效果,但是实现了ImportSelector的类可以条件性的决定导入某些配置// DeferredImportSelector的设计是在所有其他的配置类被处理后才进行处理this.deferredImportSelectorHandler.process();}
    
  7. 然后注解类的解析会进入到第一个if分支的parse方法中。最终都是调用processConfigurationClass方法

  8. protected void  processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {// 判断是否跳过解析if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}// 第一次进入的时候,configurationClass的size为0,existingClass肯定为null,在此处处理configuration重复import// 如果同一个配置类被处理两次,两次都属于被import的则合并导入类,返回,如果配置类不是被导入的,则移除旧的使用新的配置类ConfigurationClass existingClass = this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {// 如果要处理的配置类configclass在已经分析处理的配置类记录中已存在,合并两者的importBy属性existingClass.mergeImportedBy(configClass);}return;}else {this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// 处理配置类,由于配置类可能存在父类(若父类的全类名是以java开头的,则除外),所有需要将configClass变成sourceClass去解析,然后返回sourceClass的父类。// 如果此时父类为空,则不会进行while循环去解析,如果父类不为空,则会循环的去解析父类// SourceClass的意义:简单的包装类,目的是为了以统一的方式去处理带有注解的类,不管这些类是如何加载的// 如果无法理解,可以把它当做一个黑盒,不会影响看spring源码的主流程SourceClass sourceClass = asSourceClass(configClass, filter);do {// 解析各种注解sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);}while (sourceClass != null);// 将解析的配置类存储起来,这样回到parse方法时,能取到值this.configurationClasses.put(configClass, configClass);}
    
  9. 接下来就到处理各种配置注解的总方法doProcessConfigurationClass()了

  10. 	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {// @Configuration继承了@Componentif (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes first// 递归处理内部类,因为内部类也是一个配置类,配置类上有@configuration注解,该注解继承@Component,if判断为true,调用processMemberClasses方法,递归解析配置类中的内部类processMemberClasses(configClass, sourceClass, filter);}// Process any @PropertySource annotations// 如果配置类上加了@PropertySource注解,那么就解析加载properties文件,并将属性添加到spring上下文中for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// Process any @ComponentScan annotations// 处理@ComponentScan或者@ComponentScans注解,并将扫描包下的所有bean转换成填充后的ConfigurationClass// 此处就是将自定义的bean加载到IOC容器,因为扫描到的类可能也添加了@ComponentScan和@ComponentScans注解,因此需要进行递归解析Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// The config class is annotated with @ComponentScan -> perform the scan immediately// 解析@ComponentScan和@ComponentScans配置的扫描的包所包含的类// 比如 basePackages = com.mashibing, 那么在这一步会扫描出这个包及子包下的class,然后将其解析成BeanDefinition// (BeanDefinition可以理解为等价于BeanDefinitionHolder)Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if needed// 通过上一步扫描包com.mashibing,有可能扫描出来的bean中可能也添加了ComponentScan或者ComponentScans注解.//所以这里需要循环遍历一次,进行递归(parse),继续解析,直到解析出的类上没有ComponentScan和ComponentScansfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}// 判断是否是一个配置类,并设置full或lite属性if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {// 通过递归方法进行解析parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// Process any @Import annotations// 处理@Import注解processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// Process any @ImportResource annotations// 处理@ImportResource注解,导入spring的配置文件AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methods// 处理加了@Bean注解的方法,将@Bean方法转化为BeanMethod对象,保存再集合中Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfaces// 处理接口的默认方法实现,从jdk8开始,接口中的方法可以有自己的默认实现,因此如果这个接口的方法加了@Bean注解,也需要被解析processInterfaces(configClass, sourceClass);// Process superclass, if any// 解析父类,如果被解析的配置类继承了某个类,那么配置类的父类也会被进行解析if (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;}
    
  11. 可以看到@Import注解是在processImports()方法中处理的。在这之前要先处理@Configuration,@ComponentScan等注解。

  12. 然后我们进入到processImports()方法中。可以看到在处理时分成了3个部分。实现了ImportSelector接口的,实现ImportBeanDefinitionRegistrar接口的。else就是普通类。然后我们直接看本次讨论的重点代码就是ImportSelector接口部分。

  13. // 检验配置类Import引入的类是否是ImportSelector子类
    if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector -> delegate to it to determine imports// 候选类是一个导入选择器->委托来确定是否进行导入Class<?> candidateClass = candidate.loadClass();// 通过反射生成一个ImportSelect对象ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);// 获取选择器的额外过滤器Predicate<String> selectorFilter = selector.getExclusionFilter();if (selectorFilter != null) {exclusionFilter = exclusionFilter.or(selectorFilter);}// 判断引用选择器是否是DeferredImportSelector接口的实例// 如果是则应用选择器将会在所有的配置类都加载完毕后加载if (selector instanceof DeferredImportSelector) {// 将选择器添加到deferredImportSelectorHandler实例中,预留到所有的配置类加载完成后统一处理自动化配置类this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);}else {// 获取引入的类,然后使用递归方式将这些类中同样添加了@Import注解引用的类String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);// 递归处理,被Import进来的类也有可能@Import注解processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);}
    }
    
  14. 如果是DeferredImportSelector实现类会进入到deferredImportSelectorHandler.handle方法中。这个方法只是注册和存储,并不会执行。但如果是非DeferredImportSelector实现类就会直接调用selectImports方法执行。

  15. public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);if (this.deferredImportSelectors == null) {DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();handler.register(holder);handler.processGroupImports();}else {this.deferredImportSelectors.add(holder);}
    }
    
  16. 那DeferredImportSelector的方法会在哪里调用呢?其实就是在我们刚刚分析过的parse方法的最后一行中。大家可以回过头去看步骤6,最后有一行:this.deferredImportSelectorHandler.process();

  17. public void process() {// 此处获取前面存储的deferredImportSelector实例List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;this.deferredImportSelectors = null;try {if (deferredImports != null) {//获取处理器DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);//注册所有的import,关键方法,进入deferredImports.forEach(handler::register);// 核心处理类,此处完成自动配置功能handler.processGroupImports();}}finally {this.deferredImportSelectors = new ArrayList<>();}
    }
    
  18. 先看register方法

  19.     public void register(DeferredImportSelectorHolder deferredImport) {//获取我们重写的getImportGroup方法返回值Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();//如果为空就说明没有重写,使用原来的deferredImport对象DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent((group != null ? group : deferredImport),key -> new DeferredImportSelectorGrouping(createGroup(group)));//到这里放进去的要么是默认的,要么是我们自定义重写的类。grouping.add(deferredImport);this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getConfigurationClass());}
    
  20. 再看processGroupImports方法,重点在getImports()方法。

  21. public void processGroupImports() {for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {Predicate<String> exclusionFilter = grouping.getCandidateFilter();// getImports()方法内部会调用默认的或者我们覆盖过的process方法和selectImports方法grouping.getImports().forEach(entry -> {ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());try {// 配置类中可能会包含@Import注解引入的类,通过此方法将引入的类注入processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),exclusionFilter, false);}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configurationClass.getMetadata().getClassName() + "]", ex);}});}
    }
    
  22. getImports()方法内部调用了process()方法,然后process()调用了getAutoConfigurationEntry()方法

  23. public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)//这里调用了我们设置断点的地方.getAutoConfigurationEntry(annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}
    }
    
  24. 至此,我们就应该知道为什么默认的selectImports没走,而是走的内部类的方法了。

设计目的

可能有人会问,Spring设计这个DeferredImportSelector类的目的是什么?通过上面的分析可知DeferredImportSelector是在处理了所有配置注解之后才执行的,而ImportSelector是在处理完@Configuration,@ComponentScan就执行了。SpringBoot设计这个类,应该是为了某些特殊场景下,让DeferredImportSelector执行的时候可以配合后面的注解一起使用。例如:@Bean,@ImportResource,父类注解等等。但实际工作中,我也没遇到过必须要用DeferredImportSelector的场景。

总结

  1. SpringBoot通过@EnableAutoConfiguration开启自动装配
  2. SpringBoot会加载所有Starter中META-INF/spring.factories文件中配置加载配置类
  3. 通过spring-autoconfigure-metadata.properties加载配置类的过滤条件。

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

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

相关文章

MySQL总结练习题

目录 1.准备数据表 2.表之间的关系 3.题目 3.1 取得每个部门最高薪水的人员名称 3.2 哪些人的薪水在部门的平均薪水之上 3.3 取得部门中&#xff08;所有人的&#xff09;平均的薪水等级 3.4 不准用组函数&#xff08;Max &#xff09;&#xff0c;取得最高薪水 3.5 取…

【数据结构】归并排序和计数排序(排序的总结)

目录 一&#xff0c;归并排序的递归 二&#xff0c;归并排序的非递归 三&#xff0c;计数排序 四&#xff0c;排序算法的综合分析 一&#xff0c;归并排序的递归 基本思想&#xff1a; 归并采用的是分治思想&#xff0c;是分治法的一个经典的运用。该算法先将原数据进行拆…

BUUCTF SimpleRev

分析 该文件为64位的ELF文件&#xff0c;运行在linux平台 使用IDA64打开 进入Decry函数 输入flag和成功的提示 看看如何才能成功拿到flag 这里比较text和str2&#xff0c;text是源代码就有的 那么str2应该就是我们输入的内容 先分析text的内容是什么 进入join函数 该函数…

SpringBoot项目整合MybatisPlus持久层框架+Druid数据库连接池

前言 之前搭建SpringBoot项目工程&#xff0c;所使用的持久层框架不是Mybatis就是JPA&#xff0c;还没试过整合MybatisPlus框架并使用&#xff0c;原来也如此简单。在此简单记录一下在SpringBoot项目中&#xff0c;整合MybatisPlus持久层框架、Druid数据库连接池的过程。 一、…

Eclipse iceoryx(千字自传)

1 在固定时间内实现无任何限制的数据传输 在汽车automotive、机器人robotics和游戏gaming等领域,必须在系统的不同部分之间传输大量数据。使用Linux等操作系统时,必须使用进程间通信(IPC)机制传输数据。Eclipse iceoryx是一种中间件,它使用零拷贝Zero-Copy、共享内存Share…

【OSPF宣告——network命令与多区域配置实验案例】

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;喜欢编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

win10电脑插入耳机,右边耳机声音比左边小很多

最近使用笔记本看视频&#xff0c;发现插入耳机&#xff08;插入式和头戴式&#xff09;后&#xff0c;右边耳机声音比左边耳机声音小很多很多&#xff0c;几乎是一边很清晰&#xff0c;另一边什么都听不到。 将耳机插到别人电脑上测试耳机正常&#xff0c;那就是电脑的问题。试…

自然语言处理(NLP)的开发框架

自然语言处理&#xff08;NLP&#xff09;领域有许多开源的框架和库&#xff0c;用于处理文本数据和构建NLP应用程序。以下是一些常见的NLP开源框架及其特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合…

Outlook屏蔽Jira AI提醒

前言&#xff1a;最近不知道为什么jira上的ai小助手抽风&#xff0c;一周发个几千封邮件…导致我现在都不想在邮箱里面跟找垃圾一样找消息了。实在忍无可忍&#xff0c;决定屏蔽AI小助手&#xff0c;方法很简单&#xff0c;follow me~~ 第一步&#xff1a;双击打开电脑版Outloo…

springboot家乡特色推荐系统springboot28

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

leetCode 1143.最长公共子序列 动态规划 + 滚动数组

1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串…

Oracle Database Express Edition (XE)配置与部署

获取下载安装包 https://www.oracle.com/cn/database/technologies/xe-downloads.htmlhttps://yum.oracle.com/repo/OracleLinux/OL7/latest/x86_64/index.html安装.rpm安装包 cd /usr/local/src wget https://download.oracle.com/otn-pub/otn_software/db-express/oracle-d…

Linux下kibana的安装与配置

1. 环境配置 确保Linux服务器上已安装Java 8或更高版本。可以通过运行 java -version 来验证Java的版本。 下载Kibana 7.17.11的压缩文件&#xff0c;可以从Kibana 7.17.11下载 上传服务器&#xff0c;并解压Kibana压缩文件。 2. Kibana配置 编辑Kibana的配置文件 config/k…

Idea下面git的使用:变基、合并、优选、还原提交、重置、回滚、补丁

多分支和分支切换 变基和合并 变基是把本项目的所有提交都列出来按顺序一个个提交到目标分支上去 而合并是把两个分支合并起来&#xff0c;但是旧的分支还是可以启动其他分支&#xff0c;在旧的分支上继续开发 master: A -- B -- C -- M/ feature: D -- Emaster: A -…

粘性文本整页滚动效果

效果展示 CSS 知识点 background 相关属性综合运用position 属性的 sticky 值运用scroll-behavior 属性运用scroll-snap-type 属性运用scroll-snap-align 属性运用 整体页面效果实现 <div class"container"><!-- 第一屏 --><div class"sec&qu…

JAVA编程题-求矩阵螺旋值

螺旋类 package entity; /*** 打印数组螺旋值类*/ public class Spiral { // 数组行private int row; // 数组列private int col; // 行列数private int size; // 当前行索引private int rowIndex; // 当前列索引private int colIndex; // 行开始索引private int rowStart; //…

Docker---cgroups资源限制

目录 一、cpu资源控制 1、 设置cpu使用率上限 2、设置cpu资源占用比&#xff08;设置多个容器时才有效&#xff09; 3、设置容器绑定指定的CPU 三、内存资源控制 四、磁盘IO配额控制 1、限制Block IO 2、限制bps和iops进行限制 一、cpu资源控制 cgroups是一个非常强大的…

web基础以及http协议

web基础&#xff0c;http协议 域名&#xff1a;www.88886.co DNS解析 静态页面 动态页面 DNS域名&#xff1a; 网络上的通信都是基于IP通信模式&#xff1a;TCP/IP TCP建立连接和断开连接&#xff0c;都是要双方进行确认的 建立连接&#xff1a;三次握手 断开连接&#x…

Spring源码解析(十二):TransactionInterceptor事务拦截器

Spring源码系列文章 Spring源码解析(一)&#xff1a;环境搭建 Spring源码解析(二)&#xff1a;bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三)&#xff1a;bean容器的刷新 Spring源码解析(四)&#xff1a;单例bean的创建流程 Spring源码解析(五)&…

dockerfile lnmp 搭建wordpress、docker-compose搭建wordpress

-----------------安装 Docker--------------------------- 目前 Docker 只能支持 64 位系统。systemctl stop firewalld.service setenforce 0#安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2 --------------------------------------------------…