SpringBoot之所以可以达到如此精简的配置,主要原因就是SpringBoot大量的自动配置!!!
SpringBoot应用从启动类的main方法中启动,加载SpringBoot主配置类(依赖@SpringBootApplication注解),主配置类(@SpringBootApplication):
@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 {
。。。
}
开启自动配置功能(依赖**@EnableAutoConfiguration**注解):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
。。。
}
用选择器(@Import(AutoConfigurationImportSelector.class))获取候选配置,给容器导入一些组件:
查看AutoConfigurationImportSelector类的**public String[] selectImports(AnnotationMetadata annotationMetadata)**方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
通过selectImports方法间接调用**getCandidateConfigurations(annotationMetadata, attributes)**方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader())**方法去扫描所有jar包类路径下的:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF /spring.factories";
把扫描到的properties文件的内容包装成properties对象:
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 factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
从包装好的properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把它们添加到容器中去。
☆上面这五个步骤大致讲述了SpringBoot自动配置的原理,可能还是比较抽象不易理解,接下来用一个例子再次具体分析理解☆
spring.factories中每一个XXXAutoConfiguration类都是容器中的一个组件,都加入到容器中,用他们来做自动配置。每一个XXX自动配置类都可以进行自动配置功能,举个简单的例子(HttpEncodingAutoConfiguration):
首先找到HttpEncodingAutoConfiguration类:
//表明这是一个配置类,像编写配置文件一样,也可以向容器中添加组件
@Configuration
//启动指定类的ConfigurationProperties功能,
//将配置文件中对应的值和HttpProperties绑定起来,并将HttpProperties加入Ioc容器
@EnableConfigurationProperties(HttpProperties.class)
//Spring底层的@Conditional注解,根据不同的条件,如果满足指定的条件
//整个配置类里面的配置就会生效;判断当前应用是否是web应用,如果是,则配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判断当前项目有没有CharacterEncodingFilter这个类
//SpringMvc中CharacterEncodingFilter这个类一般是配置Web.xml中解决乱码的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)
//判断配置文件中是否存在某个配置spring.http.encoding.enabled
//matchIfMissing = true如果不存在,判断也成立,
//即使配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他的值已经和配置文件映射了
private final HttpProperties.Encoding properties;
//只有一个有参构构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean//给容器添加一个组件,这些组件的某些值需要从Properties中获取
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
等等方法。。。
}
根据当前不同的条件判断,决定这个配置类是否生效:一旦这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的properties类中获取的,properties类里面的每一个属性又是和配置文件绑定的。
来看一看HttpProperties类:
@ConfigurationProperties(prefix = "spring.http")//从配置文件中获取指定的值和bean的属性进行绑定
public class HttpProperties {
private boolean logRequestDetails;
private final Encoding encoding = new Encoding();
//getter/setter/is等等方法。。。
public static class Encoding {//这里有一个Encoding静态内部类
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
//getter/setter/is等等方法。。。
}
}
这时在我们的application.properties文件中加一条配置:
spring.http.encoding.charset=utf-8
ctrl左键这个Key,发现居然跳到了上面HttpProperties类的Encoding静态内部类的setCharset(Charset charset)方法!!这下子恍然大悟了!当然这只是大量组件中较为简单的一个,但是每个组件的自动配置逻辑大同小异,只有掌握了SpringBoot的这一精髓,才能更好对其他的细节进行深入理解!
用户很多时候不可避免的进行自定义配置,SpringBoot除了自动配置,理所当然的支持用户的自定义配置!
SpringBoot的自定义配置主要有两种:1.使用配置文件进行外部属性配置。2.用配置类进行配置。
接下来对两种配制方法展开说明:
SpringBoot中比较常见且推荐的是**.properties和.yml**两种配置文件。
想知道配置文件中所有可配属性,可以在SpringBoot官方说明文档中找到 Common application properties——SpringBoot2.1.8
当然如果你已经理解了自动配置的原理,大可不必因为这点小事去查询文档了
YAML是JSON的一个超集,可以将外部配置以层次结构形式存储起来。当项目的类路径中用SnakeYAML库(spring-boot-starter中已经被包含)时,SpringApplication类将自动支持YAML作为properties的替代。所以在优先级上YAML>properties。
YAML的数据格式和JSON很像,都是树状结构都是K-V格式,并通过“:”进行赋值。
properties文件中以".“进行分割的,在yml文件中用”:“进行分割的。yml文件的每个”:"后面一定都要加一个空格,否则文件会报错。
可以在编写代码时在属性上使用@Value($(key))来取值并对被注解的属性赋值,也可以在类上使用@ConfigurationProperties(prefix=“xxx”)注解取出所有以xxx为前缀的key所对应的值来对被注解类的属性进行匹配并赋值。
功能 | @ConfigurationProperties | @Value |
---|---|---|
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
无论是yml文件还是properties文件,这两个注解都能获取到值。
如果只是某个业务逻辑中需要获取一下配置文件中的某个值,建议使用@Value。
如果是编写一个JavaBean和配置文件进行映射,建议直接用@ConfigurationProperties。
上面介绍的两个注解都是默认从默认全局配置文件(application.properties或application.yml)中读取值,如果需要加载指定配置文件中的值,则需要使用**@PropertySesource(value = {“classpath:xxx.propertiex”},…)**来指定需要加载的一个或多个配置文件。
在使用springboot的时候一般是极少需要添加配置文件的(application.properties除外),但是在实际应用中也会存在不得不添加配置文件的情况,例如集成其他框架或者需要配置一些中间件等,在这种情况下,我们就需要引入我们自定义的xml配置文件了。在SpringBoot中依然支持xml文件的配置方式。但是需要在启动类上加**@ImportResource(locations = {“classpath:xxx.xml”},…)**注解来指定一个或多个xml文件的位置从而对框架或中间件进行配置。
SpringBoot2.0之后建议自定义配置类继承WebMvcConfigurationSupport,在自定义配置类上加上**@Component**注解就可以自动扫描到配置类并加载了。
WebMvcConfigurationSupport这个类中提供了很多很多方法大多数方法都是顾名思义:处理器异常解析器、添加拦截器等等。。。很多很便利的方法。
@Component
public class Configuration extends WebMvcConfigurationSupport {
//添加自定义拦截器
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**").excludePathPatterns("/login","/toLogin");
super.addInterceptors(registry);
}
//自定义视图解析器,使用这个配置类之后properties文件中配置的视图解析器就失效了
@Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
}
可以对配置类中的方法进行注解,将方法的返回值添加到容器中,容器中这个组件默认的id就是方法名。
@Bean
public UserService userService01(){//容器中就会加入一个UserService类型的组件,id是userService01
return new UserService();
}