文章目录
上一节,通过分析源码知道了SpringBoot的自动装配原理,SpringBoot给我们做好了很多默认配置,但是我们难免要去更改一些配置,如数据库的连接信息、端口号信息等。那么如何去更改,SpringBoot底层是如何对我们的更改进行识别的呢?
这一篇博文以“更改数据库连接信息”为例演示更改方式,再以“静态资源路径”分析一下SpringBoot底层是如何识别配置的
更改默认配置
回顾Spring配置方式
先回顾一下在Spring中,我们是怎样去注入数据的。
xml方式
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring_study_db"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
</bean>
Java类配置方式
jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_study_db
jdbc.username=root
jdbc.password=admin
配置类
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dateSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
Debug启动,结果如下:
@RestController
public class StartController {
@Autowired
private DataSource dataSource;
@RequestMapping("/start")
public String start(){
return "helloword springboot!!";
}
}
相比之下,可以发现配置类的配置方式可读性强,容易理解。只需要我们明白相应的注解含义并且会使用即可。
但是这样还是显得不是那么的优雅,SpringBoot既然是Spring的再封装,那么他提供了两种配置方式,一种比一种优雅。
SpringBoot注入方式一
使用application.properties配置文件,信息和上面的jdbc.properties一样,只是换了名字而已。因为SpringBoot回去读取以application开头的properties文件和yaml文件。
认识yml
application.yml,是SpringBoot特有的一种配置方式,配置简单灵活
jdbc:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/spring_study_db
username: root
password: admin
编写JdbcProperties 类,使用@ConfigurationProperties(prefix = "jdbc")
注解并加上前缀作为区分。
@ConfigurationProperties(prefix = "jdbc")
@Data
public class JdbcProperties {
private String driverClassName;
private String url;
private String userName;
private String password;
}
JdbcConfig 类,使用@EnableConfigurationProperties(JdbcProperties.class)
注解,引入JdbcProperties 类的信息,这样就可以JdbcConfig类中的任何位置直接注入(构造函数注入,或者@Autowrited
,或者@Bean
)了
@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
// @Autowired
// private JdbcProperties jdbcProperties;
// public JdbcConfig(JdbcProperties jdbcProperties){
// this.jdbcProperties=jdbcProperties;
//}
@Bean
public DataSource dateSource(JdbcProperties jdbcProperties){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName());
druidDataSource.setUrl(jdbcProperties.getUrl());
druidDataSource.setUsername(jdbcProperties.getUserName());
druidDataSource.setPassword(jdbcProperties.getPassword());
return druidDataSource;
}
}
Debug启动,测试结果如下:
这种方式,已经挺优雅了,而且可以重用,在任何位置都可以进行注入,但是必定是多写了一个类,还是有点麻烦。
SpringBoot注入方式二
不需要JdbcProperties 类。直接使用JdbcConfig类,@ConfigurationProperties(prefix = "jdbc")
作用在方法上即可。
@Configuration
public class JdbcConfig {
@Bean
@ConfigurationProperties(prefix = "jdbc")
public DataSource dateSource(){
return new DruidDataSource();
}
}
当SpringBoot扫描到这个bean时,发现了@ConfigurationProperties(prefix = "jdbc")
这个注解,就会根据application.properties文件中前缀为jdbc开头的配置,去要返回的类中找有没有对应的set方法
的属性,如果有就会自动注入。
结果如下:
总结:
SpringBoot方式一注入,适用于很多地方要使用这个配置,哪里用,哪里就注入。
方式二,则就是这个地方的DataSource使用,不需要把配置记录下来(JdbcProperties类就起到了记录作用)。
看看源码
知道了如何去改变默认配置,那么现在去看看源码,以我们熟悉的WebMvcAutoConfiguration
类为例,看看他的内部是怎样给我们配置的,同时也就知道如何去改变这些默认的配置了。
WebMvcAutoConfiguration 类中有一个WebMvcAutoConfigurationAdapter
内部类,这个类标注了@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
,根据字面意思他是启动MVC自动配置,这个内部类中有ResourceProperties、WebMvcProperties
两个对象,而且在构造方法中也传入了这两个对象,类似与我们上面的自定义的JdbcProperties类。哈哈!是不是似乎有点明白了。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
/**
* Configuration equivalent to {@code @EnableWebMvc}.
*/
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
// on the classpath
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
……
}
}
WebMvcProperties
这里只针对性的摘取部分源码进行分析。我们发现WebMvcProperties 类确实是定义了很多属性,这些属性的前缀全部被定义为@ConfigurationProperties(prefix = "spring.mvc")
,而且都提供了set方法。和我们上面SpringBoot注入方式一是一模一样的。
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
private Locale locale;
private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
private String dateFormat;
private boolean dispatchTraceRequest = false;
private boolean dispatchOptionsRequest = true;
private boolean ignoreDefaultModelOnRedirect = true;
private boolean publishRequestHandledEvents = true;
private boolean throwExceptionIfNoHandlerFound = false;
private boolean logResolvedException = false;
private String staticPathPattern = "/**";
private final Async async = new Async();
private final Servlet servlet = new Servlet();
private final View view = new View();
private final Contentnegotiation contentnegotiation = new Contentnegotiation();
private final Pathmatch pathmatch = new Pathmatch();
public static class View {
private String prefix;
private String suffix;
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
}
这样在application.yaml中,就会有很多的提示,而这些提示就是我们这里定义的属性,我们要想去改变他,只需要在这里去修改就好了。例如我们修改端口号为8081。
再啰嗦一下,其实这三者存在这样的关系:
- XXXXXAutoConfiguration帮我们自动装配,然后从XXXXProperties中取得默认配置,而XXXXProperties则和配置文件(application.properties,application.yml)可以绑定,我们就能修改配置文件使用自定义配置了。**
ResourceProperties
静态资源的导入,其实也是靠这种方式的。
直接上图分析吧 。好像更清晰啊。
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}
所以这就是我们的静态资源一般只写在"classpath:/resources/", “classpath:/static/”, “classpath:/public/” 路径下的原因。