简化开发,约定大于配置,开箱即用,提供了各种默认配置来简化项目
在起步依赖上,SpringBoot 帮我们管理了各个依赖的版本,使各个依赖不会出现版本冲突;另外,Spring Boot还帮我们打包了各个依赖让不用再像之前使用Spring那样导入一堆的依赖,只要引入起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的用。
spring-boot-dependencies
: 核心依赖在父工程 spring-boot-starter-parent
中,部分坐标的版本、依赖管理、插件管理已经定义好<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.5.RELEASEversion>
<relativePath/>
parent>
<properties>
<activemq.version>5.15.13activemq.version>
<antlr2.version>2.7.7antlr2.version>
<appengine-sdk.version>1.9.82appengine-sdk.version>
<artemis.version>2.12.0artemis.version>
<aspectj.version>1.9.6aspectj.version>
<assertj.version>3.16.1assertj.version>
<atomikos.version>4.0.6atomikos.version>
<awaitility.version>4.0.3awaitility.version>
<bitronix.version>2.1.4bitronix.version>
<build-helper-maven-plugin.version>3.1.0build-helper-maven-plugin.version>
......
properties>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
spring-boot-starter-web
自动导入了所有web依赖的环境<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.3.5.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.3.5.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<version>2.3.5.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.10.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.10.RELEASEversion>
<scope>compilescope>
dependency>
dependencies>
SpringBoot启动工程的主程序
@SpringBootApplication //标注这个类是一个SpringBoot应用
public class MainApplication {
public static void main(String[] args) {
// 将SpringBoot应用启动
SpringApplication.run(MainApplication.class, args);
}
}
SpringBoot要求每一个启动类上都需要加上解 @SpringBootApplication
的注解,该注解表名注解类是SpringBoot的主配置类,该注解内部的源码如下:
@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}
)}
)
其中最重要的两个注解是:@SpringBootConfiguration
和 @EnableAutoConfiguration
@SpringBootConfiguration
: 该注解等同于Configuration
, 表明是Spring的一个配置类。
加入@Configuration
注解,表明是一个配置类,相当于原来的声明了多个bean的xml配置文件。@Configuration
标注在类上,相当于把该类作为spring的xml配置文件中的
。
@Configuation
等价于
@Bean
等价于
@ComponentScan
等价于
进入到@Configuration
中会发现主要只有一个@Component
注解,说明Spring配置类也是Spring的一个组件。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
···
}
@EnableAutoConfiguration
:开启SpringBoot自动配置功能,
按住Ctrl查看该注解的源码
@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})
这两个注解
@AutoConfigurationPackage
下面是@AutoConfigurationPackage
的源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
其中,@AutoConfigurationPackage
导入了一个 AutoConfigurationPackages.Registrar.class
组件,下面是AutoConfigurationPackages.Registrar
的源码,
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
在 Registrar 类中,最重要的是 registerBeanDefinitions()
方法,在该方法中打上断点,进行debug,可以发现 @SpringBootApplication
配置的全限定类名。因此 register()
方法会扫描主配置类所在包及其子包下的组件,并加载注册到 Spring 的 IOC 容器中。
@Import(AutoConfigurationImportSelector.class)
@Import
注解可以给Spring导入一些组件,这里导入的是一个选择器 AutoConfigurationImportSelector
,该类实现了 DeferredImportSelector
接口,DeferredImportSelector
接口继承自 ImportSelector
接口。
Spring 容器会实例化 AutoConfigurationImportSelector
类,并且调用其中的 selectImports()
方法, 该类的源码如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
}
selectImports
方法将所有需要导入的组件以全限定类名的方式返回,并将这些组件添加到容器中, 通过Debug的方式可以看到,该方法会给容器中导入非常多的自动配置类(xxxAutoConfiguration
),也就是给容器中导入某个场景需要的所有组件,并配置好这些组件。通过这些自动配置类,免去了我们手动编写配置文件加载注入组件的工作。
那再加载这些配置类之前是如何获得这些类名的呢,主要是通过 getCandidateConfigurations()
方法,下面是 getCandidateConfigurations() 的源码。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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()
方法中
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
最后进入 loadSpringFactories()
方法中,可以发现SpringBoot 通过 SpringFactoriesLoader 加载机制,扫描 classpath 下的 META-INF/spring.factories 文件 ,获取需要需要装配的配置类,自动配置类就此生效,免去了我们自己手动配置的工作。
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 factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
spring.factories是自动配置的核心文件,其中的数据是以key=value的形式存储,loadFactoryNames方法通过key获取多个value,也就是配置类。
选取 HttpEncodingAutoConfiguration 配置类为例,分析SpringBoot自动装配原理
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
return filter;
}
....
}
@Configuration
: 表示该类为注解类
@EnableConfigurationProperties({ServerProperties.class})
: 使使用 @ConfigurationProperties 注解的类生效。任何被@EnableConfigurationProperties应用到的@ConfigurationProperties类都会被Environment属性配置,并注入到IOC容器中.
如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。也就是说 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。
点入 ServerProperties 中会发现该类使用了@ConfigurationProperties注解将配置文件中的属性与ServerProperties类中的字段值绑定,并注入IOC容器中。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
...
}
@ConditionalOnWebApplication(type = Type.SERVLET)
: Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;该注解判断当前应用是否是web应用,如果是,当前配置类生效 ;
@ConditionalOnClass({CharacterEncodingFilter.class})
: 判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)
:判断配置文件中是否存在某个配置 spring.http.encoding.enabled;matchlfMissing表示我们配置文件中没有spring.http.encoding.enabled=true,也是默认生效的;
因此可以得到:
总结:
这么多的自动配置为什么有的没有生效,需要导入对应的start才能生效呢?
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
结果:
学习主要参考了狂神说的博客教程:狂神说SSM及SpringBoot系列文章
SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用
application.properties 或者 application.yml (application.yaml) 进行配置, SpringBoot默认会从Resources目录下加载配置文件。
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的, 只能使用以下两种方式:
application.properties
语法结构 :key = value
application.yml / application.yaml
语法结构 :key:空格 value
配置文件的作用: 修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。
YML文件的扩展名可以使用.yml或者.yaml
语法
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
student:
name: guolipa
age: 3
或者转换成行内写法
student: {name: qinjiang,age: 3}
数组( List、set )
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
yaml 文件更强大的地方在于,他可以在实体类中直接注入配置文件中的匹配值,或者说可以将配置文件与配置类的相应的属性进行映射
以通过@Value注解将配置文件中的值映射到一个Spring管理的Bean的字段上
或者,application.yml配置如下:
person:
name: zhangsan
age: 18
实体Bean的代码如下
@Controller
public class QuickStartController {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private Integer age;
...
}
默认从全局配置文件中获取字段值, 我们也可以自定义某个配置文件,通过注解 @PropertySource
将配置文件与 实体类绑定
自定义配置文件 user.properties
user.name=kuangshen
user.age=18
user.sex=男
我们在User类上使用@Value来进行注入
@Component //注册bean
@PropertySource(value = "classpath:user.properties") //绑定配置类
public class User {
//直接使用@value
@Value("${user.name}") //从配置文件中取值
private String name;
@Value("#{9*2}") // #{SPEL} Spring表达式
private int age;
@Value("男") // 字面量
private String sex;
}
通过注解@ConfigurationProperties(prefix="配置文件中的key的前缀")
可以将配置文件中的配置自动与实体进行映
射
可以将实体Bean改为:
@Controller
@ConfigurationProperties(prefix = "person")
public class QuickStartController {
private String name;
private Integer age;
...
}
Spring4之后新加入的注解,原来返回json需要@ResponseBody和@Controller配合。
即@RestController是@ResponseBody和@Controller的组合注解。
Model
它这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用
全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler
注解用来指明异常的处理类型,即如果这里指定为 NullpointerException
,则数组越界异常就不会进到这个方法中来。
WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
/*
* 配置拦截器
*/
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
/* 视图跳转控制器 */
default void addViewControllers(ViewControllerRegistry registry) {
}
/* 配置视图解析器 */
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List resolvers) {
}
default void addReturnValueHandlers(List handlers) {
}
default void configureMessageConverters(List> converters) {
}
default void extendMessageConverters(List> converters) {
}
default void configureHandlerExceptionResolvers(List resolvers) {
}
default void extendHandlerExceptionResolvers(List resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}