IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 微服务Day3-初探SpringBoot自动装配 -> 正文阅读

[Java知识库]微服务Day3-初探SpringBoot自动装配

一、SpringBoot自动装配

  1. 使用SpringBoot创建项目可以减少很多xml文件的配置工作,甚至不需要写一行xml,就可以直接将整个项目启动,这种“零配置”的做法减轻了开发人员很多的工作量,可以让开发人员专注于业务逻辑的实现。
  2. SpringBoot采用JavaConfig的配置风格,导入组件的方式也由原来的直接配置改为@EnableXXX,这种纯Java代码的配置和导入组件的方式,使代码看上去更加优雅。
  3. SpringBoot的自动装配基于其内部的两种设计策略:开箱即用和约定优先于配置(或者叫约定大于配置)。

二、开箱即用

  1. 概念
    在开发过程中,通过maven项目的pom文件添加相关依赖包,然后通过相应的注解来代替繁琐的XML配置,来管理对象的生命周期。

例如常用注解@Bean、@Configuration和@Autowired等。

  1. 原理
    (1)从pom.xml开始
    SpringBoot的项目都会存在一个父依赖,按住Ctrl+鼠标左键,可以点进去。
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.5.3</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

点进去之后发现里面除了一些插件和配置文件的格式外,还存在一个依赖。

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-dependencies</artifactId>
   <version>2.5.3</version>
</parent>	

这个文件中配置了大量的依赖和相应的版本号,局部信息如下所示。

<properties>
    <activemq.version>5.16.2</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.90</appengine-sdk.version>
    <artemis.version>2.17.0</artemis.version>
    <aspectj.version>1.9.7</aspectj.version>
    <assertj.version>3.19.0</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
    <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
    ...
</properties>

结论1:
spring-boot-dependencies作为父工程,存放了SpringBoot的核心依赖,我们在写或者引入一些依赖的时候,不需要指定版本,正是因为SpringBoot的父依赖已经帮我们维护了一套版本。

在父依赖中也帮我们写好了资源库,不用我们自己再去配置(代码中配置了可以读取的配置文件):

<resources>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <filtering>true</filtering>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>

(2)启动类
启动器是SpringBoot的启动场景,比如我们要使用web相关的,那么就直接引入spring-boot-starter-web,就会帮我们自动导入web环境下所有的必须的依赖。

spring-boot-starter启动类:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.3</version>
</dependency>

该启动器中包含的依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot</artifactId>
  <version>2.2.1.RELEASE</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
  <version>2.2.1.RELEASE</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
  <version>2.2.1.RELEASE</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <version>1.3.5</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>5.2.1.RELEASE</version>
  <scope>compile</scope>
</dependency>
...

其中存放了自动配置相关的依赖、日志相关依赖、还有spring-core等依赖,这些依赖只需要导入一个spring-boot-starter就可以直接将其全部引入,而不需要像以前那样逐个导入。spring-boot-starter-web同样也包含多种依赖(web所需)。SpringBoot会将所有的功能场景都封装成一个一个的启动器,供开发人员使用。不同的启动器包含着不相同的依赖项。

(3)主程序(重要)
SpringBoot项目中,有一个主程序,即程序启动入口,这个程序的最大特点就是其类上放了一个@SpringBootApplication注解,这也正是SpringBoot项目启动的核心。

@SpringBootApplication
public class HelloWorldApplication {
	public static void main(String[] args) {
		SpringApplication.run(HellospringbootApplication.class, args);
	}
}

点击@SpringBootApplication注解,可以知道它是一个组合注解,其中比较重要的是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

@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 {...}
  • @SpringBootConfiguration:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

可以认为@SpringBootConfiguration其实就携带了一个@Configuration注解,代表自己是一个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 {};
}

其中@Import({AutoConfigurationImportSelector.class})比较重要,它帮我们导入了AutoConfigurationSelector,这个类中存在一个方法可以帮助我们获取所有的配置:

/*
	所有的配置都放在configurations中,而这些配置都从getCandiateConfiguration中获取,
	这个方法是用来获取候选的配置
*/
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

getCandiateConfigurations():

// 获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

Q:这个方法可以用来获取所有候选的配置,那么这些候选的配置又是从哪里得到的?
A:实际上它返回了一个List,这个List是由loadFactoryNames()方法返回的,其中传入了一个getSpringFactoriesLoaderFactoryClass(),其主要返回了EnableAutoConfiguration:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

也就是说,它实际返回的就是标注了这个类的所有包,标注了这个类的包就是@SpringBootApplication
另外,在断言中, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."可以确定所有的自动配置类都是定义在spring.factories文件中,从而我,们定位到spring-boot-autoconfigure包下的META-INF/spring.factories文件,其中,auto configure的部分内容如下:

# 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,\
...

以WebMvcAutoConfiguration为例:

@Configuration(
  proxyBeanMethods = false
)
@ConditionalOnWebApplication(
  type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
  public static final String DEFAULT_PREFIX = "";
  public static final String DEFAULT_SUFFIX = "";
  private static final String SERVLET_LOCATION = "/";

  public WebMvcAutoConfiguration() {
  }

  @Bean
  @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
  @ConditionalOnProperty(
      prefix = "spring.mvc.hiddenmethod.filter",
      name = {"enabled"}
  )
  public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
      return new OrderedHiddenHttpMethodFilter();
  }

  @Bean
  @ConditionalOnMissingBean({FormContentFilter.class})
  @ConditionalOnProperty(
      prefix = "spring.mvc.formcontent.filter",
      name = {"enabled"},
      matchIfMissing = true
  )
  public OrderedFormContentFilter formContentFilter() {
      return new OrderedFormContentFilter();
  }
  ...
}

这里面放了所有关于WebMvc的配置,如视图解析器、国际化等。

  • ComponentScan

这个注解的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中,详见:https://www.jianshu.com/p/a00d2b43e160

结论2:
当SpringBoot项目启动的时候,会先导入AutoConfigurationImportSelector,这个类会帮我们选择所有的候选配置,我们需要导入的配置都是SpringBoot帮我们写好的一个一个的配置类,这些配置类的位置,存在于META-INF/spring.factories文件中,通过这个文件,Spring可以找到这些配置类的位置,然后去架加载其中的配置。
SpringBoot自动配置加载流程

项目每次启动时并不是把spring.factories文件中所有的配置类都加载,上面WebMvcAutoConfiguration中@ConditionalOnXXX注解,只有其中的所有条件都满足,该类才会生效,才会被加载。

因此,在pom.xml文件中加入starter启动器, SpringBoot自动进行配置,完成开箱即用。

在这里贴一个我认为的比较容易理解的过程:

  • SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  • 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  • 以前我们需要自己配置的东西 , 自动配置类都帮我们解决了
  • 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  • 它将所有需要导入的组件以全类名的方式返回 , 这些组件就会被添加到容器中 ;
  • 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  • 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

三、约定大于配置

  1. 概念
    我们的配置文件(.yml)应该放在哪个目录下,配置文件的命名规范,项目启动时扫描的Bean,组件的默认配置是什么样的等等一系列的东西,都可以被称为约定。
  2. 举例
  • maven目录结构的约定
    file:./config/、file:./、classpath:/config/、classpath:/,约定spring的配置文件目录可以放在/config、/根目录/resource/config/、resource/。
  • SpringBoot默认文件的约定
    SpringBoot默认可以加载application.yml、application.yaml、application.properties三种配置文件,推荐使用前面两种。
  • 项目启动时扫描包范围的约定
    SpringBoot的注解扫描的默认规则是SpringBoot的入口类所在包及其子包。

四、SpringBoot自动配置类如何读取yml配置

  1. yml配置文件中可以配置哪些东西
    SpringBoot总是将所有的配置都用JavaConfig的形式去呈现出来,这样能够使代码更加优雅。
    那么yml中配置的东西,必然是要和这种配置模式去进行联系的,我们在application.yml中配置的东西,通常是一些存在与自动配置类中的属性,那么这些自动配置类,在启动的时候是怎么找到的呢?
    如果你还记得上文的描述,那么你可以很明确地知道:spring.factories!没错,就是它,所以这个问题我们似乎得到了答案——只要存在与spring.factories中的,我们都可以在application.yml中进行配置
    当然,这并不意味着不存在其中的我们就不能配置,这些配置类我们是可以进行自定义的,只要我们写了配置类,我们就可以在yml中配置我们需要的属性值,然后在配置类中直接读取这个配置文件,将其映射到配置类的属性上。那么就牵扯出我们的问题了:配置类是如何去读取yml配置文件中的信息的呢?

核心注解@ConfigurationProperties: 可以将yml文件中写好的值注入到类的属性中。
使用样例:
(1)application.yml文件

object: 
  name: Object
  blogurl: blog.objectspace.cn

(2)配置类

@Component
@ConfigurationProperties(prefix = "object")
public class TestConfig {
    private String name;
    private String blogUrl;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getBlogUrl() {
        return blogUrl;
    }
    public void setBlogUrl(String blogUrl) {
        this.blogUrl = blogUrl;
    }
}

(3)测试类

@SpringBootTest
class SpringbootdemoApplicationTests {
    @Autowired
    TestConfig testConfig;
    @Test
    void contextLoads() {
        System.out.println(testConfig.getName());
        System.out.println(testConfig.getBlogUrl());
    }
}

SpringBoot自动配置类—WebMvcAutoConfigurationAdapter:

@Configuration(
    proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
    private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
    private final Resources resourceProperties;
    private final WebMvcProperties mvcProperties;
    private final ListableBeanFactory beanFactory;
    private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
    private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
    private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
    private final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
    private ServletContext servletContext;

    public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
        this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
        this.mvcProperties = mvcProperties;
        this.beanFactory = beanFactory;
        this.messageConvertersProvider = messageConvertersProvider;
        this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
        this.dispatcherServletPath = dispatcherServletPath;
        this.servletRegistrations = servletRegistrations;
        this.mvcProperties.checkConfiguration();
    }
}

引入了WebMvcProperties.class, ResourceProperties.class, WebProperties.class三个Properties类(旧版本的是两个),并在构造函数中将这两个Properties类注入到类属性中,点击其中一个,查看其内容:
WebMvcProperties:

@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {
	private Locale locale;
	...
}

其中,prefix="spring.mvc"就是application.yml文件中的(date-format是一个需要配置的属性):

spring:
  mvc:
    date-format

在yml中配置的属性,就可以通过@ConfigurationProperties映射到类中属性,然后再通过自动配置类,将这些属性配置到配置类中。

参考:SpringBoot:认认真真梳理以便自动装配原理SpringBoot自动装配原理

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 15:15:56  更:2021-08-17 15:17:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:46:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码