持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
注:不同版本的SpringBoot可能有差异,这里是以SpringBoot-2.5.6版本为基础讲的,2.7版本的自动配置文件已迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。
注解原理定位
每一个SpringBoot应用的启动入口都会有个@SpringBootApplication的注解,而SpringBoot完成自动配置的原理就在该注解中。
从@SpringBootApplication注解中进入可以看到@EnableAutoConfiguration 的注解。
@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 {
}
再从里面看,发现该注解引入了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类中有个读取META-INF/spring.factories文件的方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 加载META-INF/spring.factories中的配置
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
在spring-boot-autoconfigure-x.jar包下META-INF中也有个spring.factories文件,而前面的选择器就是读取这个位置的。里面配置了多个springboot自动配置类。
我们以 DispatcherServletAutoConfiguration这个前端控制器的配置为例。
在源码中可以发现,DispatcherServlet需要在满足Conditional的条件下才会创建该Bean,同时DispatcherServlet依赖于WebMvcProperties这个属性对象。
从WebMvcProperties类中可以看出它的属性是由前缀spring.mvc去配置的。也即我们可以在SpringBoot的配置文件 application.properties 中用这个前缀去配置SpringMvc的属性。
如 spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss 配置日期格式。
从前面可以看出,如果要实现SpringBoot的自动配置,那么需要以下几个条件:
- 属性配置文件(如WebMvcProperties)
- 服务;(如DispatcherServlet)
- 服务的自动配置类;(如DispatcherServletAutoConfiguration)
- 有着META-INF/spring.factories文件,同时在里面有着配置指向该服务的自动配置类;
自定义自动配置的服务
知道自动配置的原理后,可以尝试自定义自动配置的服务。这里的场景是定义输出字符串的服务
0. 准备工作
新建maven项目,引入SpringBoot自动配置的依赖
<groupId>org.example</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootStartHello</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
1. 配置属性文件
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties {
private static final String DEFAULT_MSG = "world";
private String msg = DEFAULT_MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2. 设计服务
public class HelloService {
private String msg;
public String sayHello() {
return "Hello" + msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3. 自动配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello",value = "enabled",matchIfMissing = true)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService() {
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
4. 新建META-INF/spring.factories
配置指向该服务的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.example.HelloServiceAutoConfiguration
5. 打包到本地仓库
idea 执行 Maven的install生命周期。
6. 新建一个新的SpringBoot项目
- pom中引入刚打包的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 本地仓库下的包 -->
<dependency>
<groupId>org.example</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
- 配置启动类,引入服务
@RestController
@SpringBootApplication
public class SpringbootDemoApplication {
@Autowired
private HelloService helloService;
@RequestMapping("/")
public String index() {
return helloService.sayHello();
}
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
- 启动项目,访问路径 在spring.factories中加上 debug=true 可以看到哪些自动配置类被加载了,哪些没有。
可以发现自定义的自动配置类被加载,同时HelloService也被自动注入。
访问路径,可以得到Helloworld,说明HelloService被自动注入了,不需要额外去声明注入。且因为没在配置文件中设置属性,这里显示的是默认msg。
在spring.factories中修改属性,hello.msg=zhangsan。重新启动项目,发现请求结果变了,说明属性可以被正确修改读取。
最后
可以发现使用自动配置后,我们不需要显示的去声明HelloService这个服务,只需要直接引入即可。如果是普通的Spring项目,那么当我们需要引入外部服务的时候,一般都要在xml文件中定义 或者 在@Configuration标注下的类中返回对象到@Bean注解的方法中。
例如SpringMvc中的视图解析器需要这样配置。
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>
又或者使用Java配置
@EnableWebMvc
@Configuration
@ComponentScan(value = "com.example.controller")
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 视图解析器的bean配置
* @return
*/
@Bean
public ViewResolver viewResolver() {
System.out.println("viewResolver init");
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
/**
* 如果需要在jsp中使用jstl标签的话,需要加上这个视图,且要用这个引用,否则会报找不到方法的500错误
* 项目中使用JSTL,SpringMVC会把视图由InternalView转换为JstlView。
* 若使用Jstl的fmt标签,需要在SpringMVC的配置文件中配置国际化资源文件。
* 需要引入 jstl.jar和standard.jar
*/
// resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
resolver.setPrefix("/WEB-INF/page/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
}
通过查看spring-boot-autoconfigure-2.5.6.jar包下META-INF/spring.factories文件,可以知道SpringBoot可以帮我们自动配置的服务,这样就省去较多的配置工作了。同时,当针对服务的属性有调整时,通过 application.properties 配置文件就可以实现对服务参数的调整。可以说方便太多