SpringBoot-起步依赖与自动装配原理

简化开发,约定大于配置,开箱即用,提供了各种默认配置来简化项目

1. 起步依赖分析

在起步依赖上,SpringBoot 帮我们管理了各个依赖的版本,使各个依赖不会出现版本冲突;另外,Spring Boot还帮我们打包了各个依赖让不用再像之前使用Spring那样导入一堆的依赖,只要引入起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的用。

pom.xml 配置文件

  • spring-boot-dependencies: 核心依赖在父工程 spring-boot-starter-parent 中,部分坐标的版本、依赖管理、插件管理已经定义好
<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.3.5.RELEASEversion>
    <relativePath/> 
parent>
  • 在spring-boot-dependencies pom文件中,可以看到该文件管理了所有依赖的版本号,解决了我们原有项目中可能存在依赖版本冲突的问题,它来真正管理SpringBoot应用里面的所有依赖版本
<properties>
    <activemq.version>5.15.13activemq.version>
    <antlr2.version>2.7.7antlr2.version>
    <appengine-sdk.version>1.9.82appengine-sdk.version>
    <artemis.version>2.12.0artemis.version>
    <aspectj.version>1.9.6aspectj.version>
    <assertj.version>3.16.1assertj.version>
    <atomikos.version>4.0.6atomikos.version>
    <awaitility.version>4.0.3awaitility.version>
    <bitronix.version>2.1.4bitronix.version>
    <build-helper-maven-plugin.version>3.1.0build-helper-maven-plugin.version>
    ......
properties> 

启动器

  • Spring Boot将所有的功能场景都变成了一个个启动器,使用什么功能,只需要导入相应的启动器依赖
<dependency>
     <groupId>org.springframework.bootgroupId>
     <artifactId>spring-boot-starterartifactId>
dependency> 
  • 比如引用的spring-boot-starter-web 自动导入了所有web依赖的环境
<dependencies>
	 <dependency>
	   <groupId>org.springframework.bootgroupId>
	   <artifactId>spring-boot-starterartifactId>
	   <version>2.3.5.RELEASEversion>
	   <scope>compilescope>
	 dependency>
	 <dependency>
	   <groupId>org.springframework.bootgroupId>
	   <artifactId>spring-boot-starter-jsonartifactId>
	   <version>2.3.5.RELEASEversion>
	   <scope>compilescope>
	 dependency>
	 <dependency>
	   <groupId>org.springframework.bootgroupId>
	   <artifactId>spring-boot-starter-tomcatartifactId>
	   <version>2.3.5.RELEASEversion>
	   <scope>compilescope>
	 dependency>
	 <dependency>
	   <groupId>org.springframeworkgroupId>
	   <artifactId>spring-webartifactId>
	   <version>5.2.10.RELEASEversion>
	   <scope>compilescope>
	 dependency>
	 <dependency>
	   <groupId>org.springframeworkgroupId>
	   <artifactId>spring-webmvcartifactId>
	   <version>5.2.10.RELEASEversion>
	   <scope>compilescope>
	 dependency>
dependencies>

2. 自动配置原理

SpringBoot启动工程的主程序

@SpringBootApplication   //标注这个类是一个SpringBoot应用
public class MainApplication {
   public static void main(String[] args) {
       //  将SpringBoot应用启动
       SpringApplication.run(MainApplication.class, args);
   }
}

SpringBoot要求每一个启动类上都需要加上解 @SpringBootApplication 的注解,该注解表名注解类是SpringBoot的主配置类,该注解内部的源码如下:

@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}
)}
)

其中最重要的两个注解是:@SpringBootConfiguration@EnableAutoConfiguration

2.1 @SpringBootConfiguration

@SpringBootConfiguration : 该注解等同于Configuration, 表明是Spring的一个配置类。

加入@Configuration 注解,表明是一个配置类,相当于原来的声明了多个bean的xml配置文件。@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的

  • @Configuation 等价于
  • @Bean 等价于
  • @ComponentScan 等价于

进入到@Configuration中会发现主要只有一个@Component注解,说明Spring配置类也是Spring的一个组件。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
···
}

2.2 @EnableAutoConfiguration

@EnableAutoConfiguration:开启SpringBoot自动配置功能,

按住Ctrl查看该注解的源码

@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 {};
}

其中,最重要的是@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class}) 这两个注解

1) @AutoConfigurationPackage

下面是@AutoConfigurationPackage 的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

其中,@AutoConfigurationPackage 导入了一个 AutoConfigurationPackages.Registrar.class 组件,下面是AutoConfigurationPackages.Registrar 的源码,

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
   Registrar() {}
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
   }
   public Set<Object> determineImports(AnnotationMetadata metadata) {
       return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
   }
}

在 Registrar 类中,最重要的是 registerBeanDefinitions() 方法,在该方法中打上断点,进行debug,可以发现 @SpringBootApplication 配置的全限定类名。因此 register() 方法会扫描主配置类所在包及其子包下的组件,并加载注册到 Spring 的 IOC 容器中。

SpringBoot-起步依赖与自动装配原理_第1张图片

2) @Import(AutoConfigurationImportSelector.class)

@Import 注解可以给Spring导入一些组件,这里导入的是一个选择器 AutoConfigurationImportSelector,该类实现了 DeferredImportSelector 接口,DeferredImportSelector接口继承自 ImportSelector 接口。

Spring 容器会实例化 AutoConfigurationImportSelector 类,并且调用其中的 selectImports() 方法, 该类的源码如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
}

selectImports方法将所有需要导入的组件以全限定类名的方式返回,并将这些组件添加到容器中, 通过Debug的方式可以看到,该方法会给容器中导入非常多的自动配置类(xxxAutoConfiguration),也就是给容器中导入某个场景需要的所有组件,并配置好这些组件。通过这些自动配置类,免去了我们手动编写配置文件加载注入组件的工作。

SpringBoot-起步依赖与自动装配原理_第2张图片

那再加载这些配置类之前是如何获得这些类名的呢,主要是通过 getCandidateConfigurations() 方法,下面是 getCandidateConfigurations() 的源码。

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;
}

继续点击进入 SpringFactoriesLoader.loadFactoryNames() 方法中

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
       String factoryTypeName = factoryType.getName();
       return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

最后进入 loadSpringFactories() 方法中,可以发现SpringBoot 通过 SpringFactoriesLoader 加载机制,扫描 classpath 下的 META-INF/spring.factories 文件 ,获取需要需要装配的配置类,自动配置类就此生效,免去了我们自己手动配置的工作。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
}

spring.factories是自动配置的核心文件,其中的数据是以key=value的形式存储,loadFactoryNames方法通过key获取多个value,也就是配置类。

SpringBoot-起步依赖与自动装配原理_第3张图片

SpringBoot-起步依赖与自动装配原理_第4张图片

  • 通过@Import(AutoConfigurationImportSelector.class)实现配置类导入,调用selectImports方法批量加载自动配置类
  • 通过SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要装配的配置类
  • 通过筛选移除不需要或不符合条件的配置类,完成自动装配。

选取 HttpEncodingAutoConfiguration 配置类为例,分析SpringBoot自动装配原理

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
    private final Encoding properties;
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
        this.properties = properties.getServlet().getEncoding();
    }
    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
        return filter;
    }
    ....
}
  • @Configuration: 表示该类为注解类

  • @EnableConfigurationProperties({ServerProperties.class}): 使使用 @ConfigurationProperties 注解的类生效。任何被@EnableConfigurationProperties应用到的@ConfigurationProperties类都会被Environment属性配置,并注入到IOC容器中.

    • 如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。也就是说 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。

    • 点入 ServerProperties 中会发现该类使用了@ConfigurationProperties注解将配置文件中的属性与ServerProperties类中的字段值绑定,并注入IOC容器中。

      @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
      public class ServerProperties {
      	...
      }
      
  • @ConditionalOnWebApplication(type = Type.SERVLET): Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;该注解判断当前应用是否是web应用,如果是,当前配置类生效 ;

  • @ConditionalOnClass({CharacterEncodingFilter.class}): 判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;

  • @ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true):判断配置文件中是否存在某个配置 spring.http.encoding.enabled;matchlfMissing表示我们配置文件中没有spring.http.encoding.enabled=true,也是默认生效的;

因此可以得到:

  • @EnableConfigurationProperties(xxxProperties.class)注解使配置属性类xxxProperties被创建并由配置文件初始化后放入IOC容器中;
  • 配置类xxxProperties通过@ConfigurationProperties(prefix = “xxx”)注解,从配置文件中获取值并注入到配置类中, 所以我们可以在application.properties或application.yml配置文件中自定义一些相应的属性。

总结:

  • SpringBoot启动会加载大量的自动配置类
  • 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  • 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
  • 容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
    • xxxxAutoConfigurartion:自动配置类;给容器中添加组件
    • xxxxProperties:封装配置文件中相关属性;

这么多的自动配置为什么有的没有生效,需要导入对应的start才能生效呢?

@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
SpringBoot-起步依赖与自动装配原理_第5张图片
我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

结果:

  • Positive matches:(自动配置类启用的:正匹配)
  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
  • Unconditional classes: (没有条件的类)

学习主要参考了狂神说的博客教程:狂神说SSM及SpringBoot系列文章

3. SpringBoot配置文件

3.1 配置文件

SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用
application.properties 或者 application.yml (application.yaml) 进行配置, SpringBoot默认会从Resources目录下加载配置文件。

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的, 只能使用以下两种方式:

  • application.properties

    语法结构 :key = value

  • application.yml / application.yaml

    语法结构 :key:空格 value

配置文件的作用: 修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

3.2 application.yml配置文件

YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。

YML文件的扩展名可以使用.yml或者.yaml

语法

  • 对象、Map(键值对)

    #对象、Map格式
    k: 
        v1:
        v2:
    
    student:
        name: guolipa
        age: 3
    

    或者转换成行内写法

    student: {name: qinjiang,age: 3}
    
  • 数组( List、set )

    pets:
      - cat
      - dog
      - pig
    

    行内写法

    pets: [cat,dog,pig]
    

3.3 注入配置文件

yaml 文件更强大的地方在于,他可以在实体类中直接注入配置文件中的匹配值,或者说可以将配置文件与配置类的相应的属性进行映射

1)使用注解@Value映射

以通过@Value注解将配置文件中的值映射到一个Spring管理的Bean的字段上

或者,application.yml配置如下:

person:
	name: zhangsan
	age: 18

实体Bean的代码如下

@Controller
public class QuickStartController {
	@Value("${person.name}")
	private String name;
	@Value("${person.age}")
	private Integer age;
	...
}

默认从全局配置文件中获取字段值, 我们也可以自定义某个配置文件,通过注解 @PropertySource 将配置文件与 实体类绑定

自定义配置文件 user.properties

user.name=kuangshen
user.age=18
user.sex=男

我们在User类上使用@Value来进行注入

@Component //注册bean
@PropertySource(value = "classpath:user.properties") //绑定配置类
public class User {
    //直接使用@value
    @Value("${user.name}") //从配置文件中取值
    private String name;
    @Value("#{9*2}")  // #{SPEL} Spring表达式
    private int age;
    @Value("男")  // 字面量
    private String sex;
}

2) 使用注解@ConfigurationProperties映射

通过注解@ConfigurationProperties(prefix="配置文件中的key的前缀")可以将配置文件中的配置自动与实体进行映

可以将实体Bean改为:

@Controller
@ConfigurationProperties(prefix = "person")
public class QuickStartController {
	private String name;
	private Integer age;
	...
}

注解

@SpringBootApplication

@Configuration

@ConfigurationProperties

@Bean

@RestController

Spring4之后新加入的注解,原来返回json需要@ResponseBody和@Controller配合。

即@RestController是@ResponseBody和@Controller的组合注解。

@Mapper 和 @MapperScan

@CookieValue @RequestParam

@GetMapping

Model

@ControllerAdvice & @ExceptionHandler

它这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:

  • 全局异常处理
  • 全局数据绑定
  • 全局数据预处理

灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用

全局异常处理

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");
        return mv;
    }
}

在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。

@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。

WebMvcConfigurer

WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;

public interface WebMvcConfigurer {
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }

    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }

    default void addFormatters(FormatterRegistry registry) {
    }

    default void addInterceptors(InterceptorRegistry registry) {
    }
	/*
     * 配置拦截器
     */
    default void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    default void addCorsMappings(CorsRegistry registry) {
    }
	
	/* 视图跳转控制器 */
    default void addViewControllers(ViewControllerRegistry registry) {
    }
	/* 配置视图解析器 */
    default void configureViewResolvers(ViewResolverRegistry registry) {
    }

    default void addArgumentResolvers(List resolvers) {
    }

    default void addReturnValueHandlers(List handlers) {
    }

    default void configureMessageConverters(List> converters) {
    }

    default void extendMessageConverters(List> converters) {
    }

    default void configureHandlerExceptionResolvers(List resolvers) {
    }

    default void extendHandlerExceptionResolvers(List resolvers) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

你可能感兴趣的:(SpringBoot,spring,boot)