💗推荐阅读文章💗
- 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》
- 🌺MySQL系列🌺👉2️⃣《MySQL系列教程》
- 🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》
- 🌻SSM框架系列🌻👉4️⃣《SSM框架系列教程》
🎉本博客知识点收录于🎉👉🚀《SpringBoot系列教程》🚀—>✈️01【SpringBoot快速入门、yml语法、自动配置、整合框架】✈️
文章目录
- 四、SpringBoot原理
- 4.1 起步依赖原理
- 4.1.1 版本锁定
- 4.1.2 起步依赖
- 4.2 自动配置原理
- 4.2.1 @SpringBootApplication源码
- 4.2.2 @SpringBootConfiguration源码
- 4.2.3 @EnableAutoConfiguration源码
- 1)Registrar导入器
- 2)AutoConfigurationImportSelector导入器
- 4.2.4 [@ComponentScan ](/ComponentScan )
- 1)AutoConfigurationExcludeFilter
- 2)TypeExcludeFilter
- 3)自定义排除器
- 4.3 自动配置类
- 4.3.1 自动配置类说明
- 4.3.2 @Conditional派生注解
- 4.3.3 自定义自动配置类
- 4.4 属性配置类
- 4.4.1 属性配置类说明
- 4.4.2 自定义属性配置类
- 4.5 自定义场景启动器
- 4.5.1 场景启动器包含内容
- 4.5.2 搭建自动配置工程
- 4.5.2.1 依赖
- 4.5.2.2 自动配置类
- 4.5.2.3 属性配置类
- 4.5.2.4 具体配置的类
- 4.5.2.5 spring.factories
- 4.5.3 搭建测试工程
- 4.5.3.1 依赖
- 4.5.2.2 Controller
- 4.5.2.3 application.yml
- 4.5.2.4 引导类
四、SpringBoot原理
4.1 起步依赖原理
4.1.1 版本锁定
SpringBoot项目都是继承于spring-boot-starter-parent
工程的,我们点进去看看这个工程中配置了什么?
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.0.1.RELEASE</version><relativePath>../../spring-boot-dependencies</relativePath>
</parent>...
<properties><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><resource.delimiter>@</resource.delimiter><maven.compiler.source>${java.version}</maven.compiler.source><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.target>${java.version}</maven.compiler.target>
</properties><pluginManagement><plugins><plugin><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-plugin</artifactId><version>${kotlin.version}</version></plugin>...</plugins>
</pluginManagement>
我们发现spring-boot-starter-parent
工程是继承于spring-boot-dependencies
工程,在spring-boot-starter-parent
工程中锁定了部分插件使用的版本:
我们继续点进spring-boot-dependencies
工程:
<properties><activemq.version>5.15.3</activemq.version><antlr2.version>2.7.7</antlr2.version><appengine-sdk.version>1.9.63</appengine-sdk.version><artemis.version>2.4.0</artemis.version><aspectj.version>1.8.13</aspectj.version><assertj.version>3.9.1</assertj.version><atomikos.version>4.0.6</atomikos.version><bitronix.version>2.1.4</bitronix.version>.....
</properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot</artifactId><version>2.0.1.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId><version>2.0.1.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test-autoconfigure</artifactId><version>2.0.1.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId><version>2.0.1.RELEASE</version></dependency>....</dependencies>
</dependencyManagement>
从上面的spring-boot-starter-dependencies的pom.xml中我们可以发现,一部分坐标的版本、依赖管理、插件管理已经定义好,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了(解决了版本冲突问题)。
这些版本的搭配经过了SpringBoot官方团队的测试,我们在使用过程中,采用SpringBoot搭配的版本匹配,就避免了版本冲突、不稳定等因素;
4.1.2 起步依赖
在继承的父工程中我们并没有看到依赖的引入(只是版本的锁定),真正的依赖是在我们引入场景启动器时引入的,我们点击spring-boot-starter-web
场景启动器查看:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.0.1.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.0.1.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.0.1.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.9.Final</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.0.5.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.0.5.RELEASE</version><scope>compile</scope></dependency>
</dependencies>
我们发现在spring-boot-starter-web
依赖中引入了大量web场景需要的依赖,如Spring-web、Spring-webmvc、json转换、tomcat等,SpringBoot中的场景启动器(starter)对场景的依赖进行"打包",这样以后我们的项目只需要引入对应场景的starter即可;
4.2 自动配置原理
我们在前面说到过,SpringBoot所有的配置基于约定理念,并不是不需要配置,而是SpringBoot在项目启动时帮我们配置了,所有的配置基于约定的参数已经配置好了;那么SpringBoot是如何做到"自动配置"的呢?
我们点击标注在引导类上的@SpringBootApplication注解:
@SpringBootApplication // 标注这是一个引导类
public class HelloApplication {public static void main(String[] args) {SpringApplication.run(HelloApplication.class);}
}
4.2.1 @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 {...}
- 1)@Target({ElementType.TYPE}):元注解,标注这个注解只能在类上使用
- 2)@Retention(RetentionPolicy.RUNTIME):元注解,标注这个注解的生命周期是RUNTIME,可以在运行时解析到这个注解
- 3)@Documented :元注解,表明这个注解应该被 javadoc工具记录
- 4)**@Inherited **** **:元注解,注解在类上面时,子类会自动继承此注解
4.2.2 @SpringBootConfiguration源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
上面三个都是元注解
**@Configuration **** **:Spring提供的注解,表示这是一个配置类
也就是说标注了@SpringBootApplication、@SpringBootConfiguration等注解的类都可以当做一个配置类使用
4.2.3 @EnableAutoConfiguration源码
@EnableAutoConfiguration是SpringBoot自动配置的核心;
@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)Registrar导入器
- 1)@AutoConfigurationPackage:在该注解上标注了@Import注解,导入了一个Registrar导入器,开启包的自动导入配置,自动导入标注了该注解的类的同级(包括子级、孙级)包下的所有的Bean;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
往容器中注册了一个Registrar
,查看Registrar:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 配置包注册(注册标注了AutoConfigurationPackage注解的类所在的全路径下面的所有类)register(registry, new PackageImport(metadata).getPackageName());}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}}
2)AutoConfigurationImportSelector导入器
在@EnableAutoConfiguration还标注了一个@Import(AutoConfigurationImportSelector.class),导入了一个AutoConfigurationImportSelector类;
查看继承体系:
发现AutoConfigurationImportSelector
实现接口ImportSelector
,在导入时,一定会调用**selectImports**
进行Bean配置;
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AnnotationAttributes attributes = getAttributes(annotationMetadata);// 给容器中导入一批组件(xxxAutoConfiguration)List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 过滤掉一些不生效的组件configurations = filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);// 其他全部导入到IOC容器给中return StringUtils.toStringArray(configurations);
}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;
}
configurations值:
- SpringFactoriesLoader源码:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {Enumeration<URL> urls = (classLoader != null ?// META-INF/spring.factoriesclassLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));result.addAll((String) entry.getKey(), factoryClassNames);}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}
}
4.2.4 @ComponentScan
@SpringBootApplication注解上除了标注了上面两个注解外,还标注了一个@ComponentScan注解,其内容如下:
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
添加了两个排除过滤器,分别是TypeExcludeFilter
和AutoConfigurationExcludeFilter
;两个排除过滤器都继承与TypeFilter
接口,并且是一个函数式接口;IOC容器启动时,会将被扫描的Bean的元数据信息传递到该match,由该方法的返回值来决定是否要排除这个Bean;
@FunctionalInterface
public interface TypeFilter {/*** 根据match方法的返回值来决定当前Bean是否要注册到IOC容器* @param metadataReader: 这个注解标注的目标类的元数据读取器* @param metadataReaderFactory: 获取元数据读取器的工厂*/boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException;}
1)AutoConfigurationExcludeFilter
AutoConfigurationExcludeFilter
:
package org.springframework.boot.autoconfigure;import java.io.IOException;
import java.util.List;import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;/*** A {@link TypeFilter} implementation that matches registered auto-configuration classes.** @author Stephane Nicoll* @since 1.5.0*/
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {private ClassLoader beanClassLoader;private volatile List<String> autoConfigurations;@Overridepublic void setBeanClassLoader(ClassLoader beanClassLoader) {this.beanClassLoader = beanClassLoader;}// 作用: 当前这个类不能是一个配置类 并且 也不能是一个自动配置类(不能写在META-INF/spring.factroies文件中)@Overridepublic boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);}// 当前这个类上是否标注了Configuration注解private boolean isConfiguration(MetadataReader metadataReader) {return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());}// 当前这个类是否是一个自动配置类private boolean isAutoConfiguration(MetadataReader metadataReader) {return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());}protected List<String> getAutoConfigurations() {if (this.autoConfigurations == null) {this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);}return this.autoConfigurations;}
}
说明:因为自动配置类是SpringBoot规定的加载规则,默认规则为加载每个jar包下的**/META-INF/spring.factories**
文件中的**org.springframework.boot.autoconfigure.EnableAutoConfiguration**
指定的自动配置类;
如果我们自己想要自定义自动配置类让其生效,有两种方法:
- 1)在类上只标注一个
@Configuration
注解,没有写在/META-INF/spring.factories
文件中,不会被排除,但也被默认的规则扫描到了(Registrar导入器); - 2)在类上只标注一个
@Configuration
注解,写在/META-INF/spring.factories
文件中,被排除(但被SpringBoot扫描到了),这是SpringBoot推荐的写法,标准的自动配置类
Tips:自动配置类和普通的配置类的作用都是一模一样的,只不过自动配置类一般用于加载某些场景,而且是写在
/META-INF/spring.factories
中的;
2)TypeExcludeFilter
TypeExcludeFilter
:
package org.springframework.boot.context;import java.io.IOException;
import java.util.Collection;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}// 作用: 获取IOC容器中,所有有关于TypeExcludeFilter的后代类,然后执行这些后代类的match方法@Overridepublic boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {if (this.beanFactory instanceof ListableBeanFactory&& getClass() == TypeExcludeFilter.class) {// 从IOC容器中获取有关于TypeExcludeFilter的所有Bean(包括后代类)Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();for (TypeExcludeFilter delegate : delegates) {// 调用TypeExcludeFilter后代类的所有match方法if (delegate.match(metadataReader, metadataReaderFactory)) {return true;}}}return false;}@Overridepublic boolean equals(Object obj) {throw new IllegalStateException("TypeExcludeFilter " + getClass() + " has not implemented equals");}@Overridepublic int hashCode() {throw new IllegalStateException("TypeExcludeFilter " + getClass() + " has not implemented hashCode");}
}
TypeExcludeFilter
过滤器是SpringBoot提供给我们用的排除过滤器,我们可以根据一些条件来决定是否要过滤某些Bean;
3)自定义排除器
- 自定义排除器:
package com.dfbz;import com.dfbz.controller.HelloController;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;import java.io.IOException;/*** @author lscl* @version 1.0* @intro:*/
//@Component // 不能用这种方式注册到IOC容器,因为排除器在容器扫描时生效,这个时候连都还没加载
public class MyTypeExcludeFilter extends TypeExcludeFilter {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {// 排除HelloControllerreturn metadataReader.getClassMetadata().getClassName().equals(HelloController.class.getName());}
}
排除器(MyTypeExcludeFilter)需要注册到IOC容器中才会生效,但不能使用**@Component **** 注册,因为排除器是在扫描组件时生效,这个时候可能连排除器上标注的@Componen注解还未扫描; **
- 编写初始化器:
package com.dfbz;import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;/*** @author lscl* @version 1.0* @intro:*/
public class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {// 往IOC容器中注册一个单例Bean(在IOC容器初始化后,@ComponentScan组件扫描之前执行)applicationContext.getBeanFactory().registerSingleton("myTypeExcludeFilter",new MyTypeExcludeFilter());}
}
- 让初始化器生效:
在resources
目录下编写META-INF/spring.factories
,内容如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.dfbz.MyApplicationContextInitializer
4.3 自动配置类
4.3.1 自动配置类说明
通过刚刚的自动配置原理我们发现,SpringBoot在启动时,就加载了非常多的一些配置类(xxxAutoConfiguration),这些配置类中配置了非常多的信息,包括根据条件导入一些Bean、配置一些属性、绑定一些配置;
我们之所以能够启动SpringBoot环境就配置好了大量的环境(SpringMVC环境/MyBatis环境/JPA环境等)都是因为这些自动配置类在SpringBoot启动时帮我们在IOC容器里注册了大量的Bean;
Tips:配置类的地址在
**META-INF/spring.factories**
文件中**org.springframework.boot.autoconfigure.EnableAutoConfiguration**
key所指定的内容;
以DispatcherServletAutoConfiguration
配置类举例:
// 配置顺序(数字越小,越优先加载,负数也可以)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 标注这是一个配置类
@Configuration// 如果是web环境才配置此类
@ConditionalOnWebApplication(type = Type.SERVLET)// 如果系统中有DispatcherServlet类才配置此类(导入了这个依赖就配置这个类)
@ConditionalOnClass(DispatcherServlet.class)// 在ServletWebServerFactoryAutoConfiguration类加载之后再加载此类
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
// 开启属性配置
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";// 标注这是一个配置类@Configuration// 满足DefaultDispatcherServletCondition的matches方法时配置该类@Conditional(DefaultDispatcherServletCondition.class)// 容器中有ServletRegistration类时配置该类@ConditionalOnClass(ServletRegistration.class)// 开启配置属性@EnableConfigurationProperties(WebMvcProperties.class)protected static class DispatcherServletConfiguration {private final WebMvcProperties webMvcProperties;public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {this.webMvcProperties = webMvcProperties;}// 往IOC容器中配置一个DispatcherServlet类@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet() {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());return dispatcherServlet;}@Bean@ConditionalOnBean(MultipartResolver.class)@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)public MultipartResolver multipartResolver(MultipartResolver resolver) {// Detect if the user has created a MultipartResolver but named it incorrectlyreturn resolver;}}@Configuration@Conditional(DispatcherServletRegistrationCondition.class)@ConditionalOnClass(ServletRegistration.class)@EnableConfigurationProperties(WebMvcProperties.class)@Import(DispatcherServletConfiguration.class)protected static class DispatcherServletRegistrationConfiguration {private final ServerProperties serverProperties;private final WebMvcProperties webMvcProperties;private final MultipartConfigElement multipartConfig;public DispatcherServletRegistrationConfiguration(ServerProperties serverProperties, WebMvcProperties webMvcProperties,ObjectProvider<MultipartConfigElement> multipartConfigProvider) {this.serverProperties = serverProperties;this.webMvcProperties = webMvcProperties;this.multipartConfig = multipartConfigProvider.getIfAvailable();}@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcherServlet,this.serverProperties.getServlet().getServletMapping());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;}}
}
4.3.2 @Conditional派生注解
我们已经知道了自动配置类的加载规则,只要配置在META-INF/spring.factories
文件的org.springframework.boot.autoconfigure.EnableAutoConfiguration
key中的全类名,该配置类即可被加载,但有些时候我们并不希望这些配置类能够被立即加载,而是需要符合某些条件时这些配置类才会被加载;
SpringBoot内置了非常多的条件判断注解,这些注解可以帮助我们在符合条件的清空下,该配置类/方法才会生效;这些注解我们统一称为派生注解;为@ConditionalXxx
;
- @Conditional表格:
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
4.3.3 自定义自动配置类
- 自动配置类的加载说明:
- 1)必须是一个配置类
- 2)必须配置在
META-INF/spring.factories
中的org.springframework.boot.autoconfigure.EnableAutoConfiguration
key中; - 3)可能还会包含一些派生注解(@ConditionalXxx)
package com.dfbz.config;import com.dfbz.controller.HelloController;
import com.dfbz.entity.City;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author lscl* @version 1.0* @intro:*/
@Configuration
@ConditionalOnJava(JavaVersion.EIGHT) // Java版本必须是1.8
@ConditionalOnBean(HelloController.class) // IOC容器中必须存在HelloController这个对象
@ConditionalOnResource(resources = "application.properties") // 类路径下必须存在application.properties
public class MyAutoConfiguration {@Beanpublic City city() {return new City();}
}
4.4 属性配置类
4.4.1 属性配置类说明
自动配置类中通常会绑定(通过EnableConfigurationProperties开启)一些属性配置类(xxxProperties.class),这些属性配置类通过**@ConfigurationProperties**注解标识;
在自动配置类中根据条件配置了大量的Bean,而这些Bean上面大都开启(@EnableConfigurationProperties)了属性配置类(xxxProperties.class),这些属性配置类是SpringBoot"基于约定配置"的保障;
查看ServerProperties:
- @ConfigurationProperties:与application.yml/properties配置文件进行绑定;
// 与配置文件进行绑定
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {private Integer port;private InetAddress address;@NestedConfigurationPropertyprivate final ErrorProperties error = new ErrorProperties();private Boolean useForwardHeaders;private String serverHeader;}
4.4.2 自定义属性配置类
- 自定义属性配置类:
package com.dfbz.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** @author lscl* @version 1.0* @intro:*/
@Data // 提供get/set/toString...
@AllArgsConstructor // 有参构造
@NoArgsConstructor // 无参构造@Component // 必须要放入IOC容器才能使用@ConfigurationProperties注解
@ConfigurationProperties(prefix = "com.dfbz.book") // 绑定的前缀为: com.dfbz.book
public class Book {private String name;private Double price;
}
- application.yml:
com:dfbz:book:name: 《Java入门到精通》price: 38.8
- DemoController:
package com.dfbz.controller;import com.dfbz.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;/*** @author lscl* @version 1.0* @intro:*/
@RestController
public class DemoController {@Autowiredprivate Book book;@GetMapping("/getBook")@ResponseBodypublic Book getBook() {return book;}
}
访问:http://localhost:8080/getBook:
也可以使用@EnableConfigurationProperties
注解来指定要开启某个类上面的属性配置;这样这个类就不需要被放入IOC容器也依旧能与配置文件进行绑定;
package com.dfbz;import com.dfbz.entity.Book;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;/*** @author lscl* @version 1.0* @intro:*/
@SpringBootApplication
@EnableConfigurationProperties(Book.class) // 开启Book类的属性配置功能
public class HelloApplication {public static void main(String[] args) {SpringApplication.run(HelloApplication.class);}
}
注释Book类中的@Component
注解;
重启服务器,再次访问:http://localhost:8080/getBook发现配置依旧可以;
4.5 自定义场景启动器
4.5.1 场景启动器包含内容
1)自动配置类;
xxxAutoConfiguration
2)开启属性配置,并绑定属性配置类;
xxxProperties
3)加入到META-INF/spring.factories中;
4.5.2 搭建自动配置工程
自动配置场景启动器名称规范:{name}-springboot-starter-autoconfigurer
4.5.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.dfbz</groupId><artifactId>02-mystarter-springboot-starter-autoconfigurer</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><!--springboot的启动器,包含所有starter的基本配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies></project>
4.5.2.2 自动配置类
package com.mystarter.autoconfig;import com.mystarter.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author lscl* @version 1.0* @intro: 自动配置类*/
// 标注这是一个配置类
@Configuration
// 如果是web环境才配置此类
@ConditionalOnWebApplication
// 开启配置属性
@EnableConfigurationProperties(MyStarterProperties.class)
public class MyStarterAutoConfiguration {@Beanpublic HelloService helloService(){return new HelloService();}
}
4.5.2.3 属性配置类
package com.mystarter.properties;import org.springframework.boot.context.properties.ConfigurationProperties;/*** @author lscl* @version 1.0* @intro: 属性配置类,绑定配置文件,指定前缀*/
@ConfigurationProperties(prefix = "mystarter")
public class MyStarterProperties {private String text;public String getText() {return text;}public void setText(String text) {this.text = text;}
}
4.5.2.4 具体配置的类
package com.mystarter.service;import org.springframework.beans.factory.annotation.Value;/*** @author lscl* @version 1.0* @intro:*/
public class HelloService {@Autowiredprivate MyStarterProperties properties;public String sayHello() {return properties.getText();}
}
4.5.2.5 spring.factories
在/MATA-INF/
目录下新建spring.factories
文件;
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.mystarter.autoconfig.MyStarterAutoConfiguration
4.5.3 搭建测试工程
4.5.3.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>03-MyStarterTest</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version></parent><dependencies><!--导入我们自定义的starter--><dependency><groupId>com.dfbz</groupId><artifactId>02-mystarter-springboot-starter-autoconfigurer</artifactId><version>1.0-SNAPSHOT</version></dependency><!--web场景的starter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies></project>
4.5.2.2 Controller
package com.dfbz.controller;import com.mystarter.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;/*** @author lscl* @version 1.0* @intro:*/
@Controller
public class HelloController {@Autowiredprivate HelloService helloService;@GetMapping("/hello")@ResponseBodypublic String hello() {return helloService.sayHello();}
}
4.5.2.3 application.yml
mystarter:text: hello~
4.5.2.4 引导类
package com.dfbz;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author lscl* @version 1.0* @intro:*/
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class);}
}