-
SpringBoot学习笔记(二)
本文主要浅析SpringBoot:
(如需深入理解,还需要从实践和学习中获得)
1.pom.xml
(查看其)父依赖
<!--父依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点进去发现,其还有一个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.3</version>
</parent>
这里才是整整管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
2.启动器
springboot-boot-starter-xxxx:就是SpringBoot的场景启动器。比如:
<!--web场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SpringBoot将所有的功能场景都抽取出来,做成一个个启动器,项目中需要什么就可依照需求来定制,开箱即用。
3.主启动类
(默认如下:)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
当然,默认的启动器,我们也可以更改,只需要在自定义的启动器类上加上@SpringBootApplication
注解,再删掉默认的即可。
4.部分注解解析:
@SpringBootApplication
标注在某个类上,说明这个类是SpringBoot的主配置类;
@EnableAutoConfiguration
:开启自动配置功能。源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
在@Import(AutoConfigurationImportSelector.class)
中,AutoConfigurationImportSelector
:表示自动配置导入选择器。根据源码可以找到,它有一个获得候选配置的方法getCandidateConfigurations
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//省略部分代码
//获得候选的配置
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;
}
1.查看上面源码中的getSpringFactoriesLoaderFactoryClass()
方法,得知它的返回值是启动自动导入配置文件的注解类;EnableAutoConfiguration
2.查看上面源码,在getCandidateConfigurations()方法内调用:SpringFactoriesLoader类的静态方法。我们进入SpringFactoriesLoader类loadFactoryNames() 方法,它里面又调用了loadSpringFactories 方法。
loadSpringFactories
源码:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//去获取一个资源 "META-INF/spring.factories"(多次发现spring.factories文件,可以全局搜索它)
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//将读取的资源遍历,封装成一个properties
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[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
@AutoConfigurationPackage
:自动配置包。源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
在@Import(AutoConfigurationPackages.Registrar.class)
中的,@Import
:是Spring底层注解,给容器中导入一个组件。Registrar.class
作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器。
5.spring.factories
1.根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在。
2.可以通过SpringApplication的类加载器获取
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
System.out.println(SpringApplication.class.getClassLoader().getResource("META-INF/spring.factories"));
}
}
所以,自动配置的真正实现是从classpath中搜索所有的META-INF/spring.factories配置文件,并将其中对应的org.springframework.boot.autofonfigure.包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。
6.SpringApplication.run()方法
该方法主要分为两部分:1.SpringApplication的实例化,2.run方法的执行
7.SpringApplication
这个类主要做了一下四件事情:
- 推断应用的类型是普通的项目还是Web项目;
- 查找并加载所有可用初始化器,设置到initializers属性中;
- 找出所有应用程序的监听器,设置到listeners属性中;
-
推断并设置main方法的定义类,找到运行的主类;
查看其构造器,源码:
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//省略部分代码。deduce:演绎,推断,推演
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
8.run方法流程分析
9.浅析SpringBoot的自动配置原理
SpringBoot官方文档中有大量的配置,我们无法全部记住。
下面以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:
源码:
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//和SpringBoot的配置文件映射
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean//判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
首先,分析HttpEncodingAutoConfiguration类上的注解:
@Configuration(proxyBeanMethods = false)
:表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@EnableConfigurationProperties(ServerProperties.class)
:表示启用指定类的ConfigurationProperties功能;查看ServerProperties:
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
:Spring底层@Conditional注解,根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效。这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效。
@ConditionalOnClass(CharacterEncodingFilter.class)
:判断当前项目有没有这个类CharacterEncodingFilter(SpringMVC中它可以是解决乱码问题的过滤器)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
:表示判断配置文件中是否存在某个配置:server.servlet.encoding.enabled;如果不存在,判断也是成立的
。即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;(matchIfMissing:match匹配、If如果、Missing缺失)
我们去配置文件里面试试前缀,看提示:
这就是自动装配的原理!
10.了解:@Conditional
Spring4.0 介绍了一个新的注解@Conditional,它的逻辑语义可以作为"If…then…else…"来对bean的注册起作用。
Conditional是由SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下:
SpringBoot 模块大量的使用@Conditional 注释,我们可以将Spring的@Conditional注解用于以下场景:
- 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
- 可以作为元注解,用于自动编写构造性注解;
- 作为方法级别的注解,作用在任何@Bean方法上。
例子:
//实现 Condition 接口
class ConditionTest implements Condition {
//根据Condition接口中的 matches 方法进行判断 ,如果 matches 为true 则注册Bean , 为false 则不注册Bean
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
// 业务逻辑 ,不同的逻辑做不同的判断,根据逻辑判断是否需要注册bean
return false ;
}
}
//使用 Conditional 注解
@Conditional(ConditionTest.class)
class Test1 {
public Test1() {
System.out.println( "AbcTest");
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Test1.class);
}
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
这也说明了,那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
如何哪些自动配置类生效?
我们可以通过启用debug=true
属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。
控制台输出(部分截图):
说明:
- Positive matches:自动配置类启用的:正匹配
- Negative matches:没有启动,没有匹配成功的自动配置类:负匹配
- Unconditional classes: 没有条件的类