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的主配置类;
image

@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
image
2.查看上面源码,在getCandidateConfigurations()方法内调用:SpringFactoriesLoader类的静态方法。我们进入SpringFactoriesLoader类loadFactoryNames() 方法,它里面又调用了loadSpringFactories 方法。
image

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 , 看到了很多自动配置的文件;这就是自动配置根源所在。
image
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"));
	}
}

image
所以,自动配置的真正实现是从classpath中搜索所有的META-INF/spring.factories配置文件,并将其中对应的org.springframework.boot.autofonfigure.包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。

6.SpringApplication.run()方法

image
该方法主要分为两部分: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方法流程分析

image

9.浅析SpringBoot的自动配置原理

SpringBoot官方文档中有大量的配置,我们无法全部记住。
image

下面以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:
image

@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET):Spring底层@Conditional注解,根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效。这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效。
@ConditionalOnClass(CharacterEncodingFilter.class):判断当前项目有没有这个类CharacterEncodingFilter(SpringMVC中它可以是解决乱码问题的过滤器)
image

@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true):表示判断配置文件中是否存在某个配置:server.servlet.encoding.enabled;如果不存在,判断也是成立的
。即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;(matchIfMissing:match匹配、If如果、Missing缺失)

我们去配置文件里面试试前缀,看提示:

image

这就是自动装配的原理!

10.了解:@Conditional

Spring4.0 介绍了一个新的注解@Conditional,它的逻辑语义可以作为"If…then…else…"来对bean的注册起作用。
Conditional是由SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下:
image

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属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。
image

控制台输出(部分截图):
image

说明:

  • Positive matches:自动配置类启用的:正匹配
  • Negative matches:没有启动,没有匹配成功的自动配置类:负匹配
  • Unconditional classes: 没有条件的类
posted @ 2021-03-25 13:28  江河湖泊  阅读(169)  评论(0编辑  收藏  举报