前言

开篇学习了 SpringBoot Helloworld 程序的编写,上手非常的快。现在来了解 SpringBoot 的自动配置特性以及 Configration 注解。

自动配置特性

上篇日志通过导入 spring-boot-web-starter ,它就会自动配置好 SpringMVC 以及相关的常用的组件,当然也包括我们常见的字符编码问题以及文件上传等等,这些都是默认配置好了的。但是只有在导入相关的 starter 的时候才会去加载相关的配置文件。

我们可以做一个测试,来查看 Spring MVC 相关的是如何自动配置的

package top.bestguo.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

@org.springframework.boot.autoconfigure.SpringBootApplication
public class SpringBootApplication {

    public static void main(String[] args) {

        // Spring IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringBootApplication.class, args);
        // 获取全部的 Bean 名字
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }

    }

}

运行结果比较长。因为配置的加载项还是蛮多的。可以在你的控制台输出时。按下 Ctrl + F 搜索 Spring 整合 SpringMVC 相关的关键字,肯定能够找到相关的关键字。也就意味着这些都是自动配置了的:

  • 前端控制器:DispathchServlet
  • 视图解析器:ViewResolver
  • 静态资源管理器:servlethandler
  • 文件上传模块:Multipart
  • 编码过滤模块:characterEncodingFilter

……

自动包扫描

SpringBoot 默认有一个自动包的扫描,无需自己配置。那不用配置它是怎么扫描的呢?它是这么规定的,就是只要 SpringBoot 的启动类在哪个目录下,那么和这个启动类的同级目录以及它的子目录,它都会去扫描。

请看下面的项目结构

E:.
└─top
    └─bestguo
        └─helloworld
            | SpringBootApplication.java
            │
            └─controller
                    | HelloController.java
                    | DemoController.java

SpringBoot 下有一个同级的文件夹 controller,那么这个 controller 下的所有类都会被扫描到。前提是这些类中必须有标记 @Controller、@Service、@Repository 等这种可被 Spring 识别到的注解。

如果是这种目录结构,SpringBoot 主程序能够扫描到 WorldController 吗?显然这是扫描不到的。

E:.
└─top
    └─bestguo
        │ WorldController.java
        │
        └─helloworld
            │ SpringBootApplication.java
            │
            └─controller
                   | HelloController.java
                   | DemoController.java

WorldController.java 代码如下

package top.bestguo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/world")
public class WorldController {

    @RequestMapping("/springboot2")
    @ResponseBody
    public String hello() {
        return "你好, SpringBoot2!";
    }

}

我们可以访问一下这个页面 http://localhost:8080/world/springboot2 ,发现报错了。

报错了就意味着,SpringBoot 主程序是扫描不到它的父文件夹里的内容的。如果要想然 SpringBoot 去让他直接访问父文件夹是行不通的,只能通过重新配置包扫描路径。

包扫描路径可以自己手动的来指定!

包扫描的路径设置成 “top.bestguo” 它就从这个位置开始扫描,扫描其子目录和孙目录即可,只需要在 SpringBootApplication 注解中传入一个参数,这个参数就是包扫描路径,即

@SpringBootApplication(scanBasePackages = “top.bestguo”)

package top.bestguo.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

@org.springframework.boot.autoconfigure.SpringBootApplication(scanBasePackages = "top.bestguo")
public class SpringBootApplication {
    public static void main(String[] args) {
        // Spring IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringBootApplication.class, args);
        // 获取全部的 Bean 名字
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

然后我们重新访问一下看看,发现页面是可以正常访问了。

默认值

SpringBoot 中,这些配置文件中的绝大多数都有默认的设置。你可以使用默认的设置,也可以通过 application.properties 来对默认配置的值进行修改。

比如我们修改内嵌 Tomcat 的端口号,通过提示可以看到默认是 8080。我们可以将其设置成 8081。

还有上传文件大小,默认是 1MB ,我们可以设置成 100 MB

以上的这些配置都是在 application.properties 文件中进行配置的

server.port=8081
spring.servlet.multipart.max-file-size=100MB

默认配置最终都是映射到 MultipartProperties,配置文件的值最终会绑定到每个类上,这个类会在 Spring 容器中创建对象。

spring-boot-autoconfigure

SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面,在这个包下面我们可以看到很多。比如 jdbc、aop、jackson、web 等等自动配置类。

自动配置原理

在 SpringBoot 的主程序中,有一个 SpringBootApplication 注解,这个注解用于标记 SpringBoot 的主程序的入口。有它,才能够正常的启动该程序。

SpringBootApplication 是一个合成的注解,这个注解包含着三个注解,它们分别是 SpringBootConfigurationEnableAutoConfigurationComponentScan 三个注解。

SpringBootConfiguration 注解

其中 SpringBootConfiguration 这个注解,它本质上就是一个 Configuration 注解。也就是说,SpringBoot 的主程序类它也是一个配置类。

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

ComponentScan 注解

就是普通的包扫描注解,指定哪一个扫描的包路径。

EnableAutoConfiguration 注解

该注解是 SpringBootConfiguration 注解中非常重要的一个注解,通过它就可以找到 SpringBoot 的主程序类是在哪个包路径下,以及……

该注解的代码如下

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    //主体省略......
}

其中 AutoConfigurationPackage 注解就是找到 SpringBoot 的主程序类是在哪个包路径下。Import 注解也说过,它也是将组件注册到容器中。

Import 注解的详细使用可以看看 “SpringBoot注解的学习” 这篇日志

它需要导入的 AutoConfigurationImportSelector 这个类的目的是加载那些自动配置类,也就是那些 xxxxxAutoConfiguration.java。

AutoConfigurationPackage 注解

该注解的代码如下

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    //主体省略......
}

其中,Register 类就是把包下面的一系列组件给导入进来,它并不是仅仅导入一个

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    /**
     * 一系列组件导入的方法
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }

}

通过该注解的元信息获取到主程序所在的包路径,然后再调用 register 方法,将一系列的包给导入进来。这也就能够解释。启动类的同级目录以及它的子目录下的组件给导入进来。

AutoConfigurationImportSelector

未完待续……