Spring Boot - 1. 自动配置
谈及SpringBoot之前,先来聊一聊**微服务,**那么什么是微服务呢?
当然,关于 微服务
,它并不存在公认的单一定义;
**微服务
**是一种专注于构建基于能力(业务功能)、可独立部署的小型服务的架构风格。
关于微服务的更细致分析,大家可以去看一下 Martin Fowler 关于微服务的论文
论文的 原文地址:http://martinfowler.com/articles/microservices.html
- 微服务的划分方法更倾向于围绕业务功能对服务结构进行划分、拆解
- 使用微服务所构建的各个应用的目标都是尽可能实现**“高内聚和低耦合”**
- 他们拥有自己的领域逻辑
- 更多的是 接收请求、处理逻辑、返回响应
- 这些应用通过使用简单的REST风格的协议来进行编排
- 而不不是诸如:WS-Choreography 、BEPL 或者集中式工具进行编排。
快速构建微服器原型
在 Spring Boot之前, 我们通常使用 Spring MVC 来构建一个微服务, 并使用 JPA 来连接到数据库
具体的操作 应该有如下:
-
确定要是用 SpringMVC 、JPA 、ORM框架;
-
设置Spring 上下文并将所有不同的层装配在一起;
-
使用Spring MVC 设置 Web层 (包括 SpringMVC 的配置) :
- 为DispatcherServlet、处理程序、解析器、视图解析器等配置 相应的bean
-
数据层配置 orm 框架 :
- SessionFactory、数据源等配置相应的bean
-
确定如何存储应用程序并实现此目标;
-
确定如何进行单元测试;
-
确定事务管理策略,并实现;
-
确定如何实施安全性,并实现;
-
设置日志记录框架;
-
确定如何在生产环境中监视应用程序,并实现;
-
确定并实现度量管理系统,一提供有关应用程序的统计数据;
-
确定如何将应用程序部署到Web或者应用程序服务器,并实现。
步骤何其多!!!
那么恰巧, SpringBoot 正致力于解决这些问题;
开箱即用
- 使用基于Spring 的项目 快速构建微服务
- 采用固定的设置,基于常见用法 做出默认假设,并提供配置选项来处理偏离默认设置的情况;
- 开箱既提供一系列非功能性特性
- 不生成代码,并避免大量的使用XML配置
SpringBoot 自动装配 原理解析
当你创建一个SpringBoot项目时,它自动创建一个类 Appliation
,我通常管他就叫做 启动类 ,SpringBoot 自动装配的关键在于他的启动类,下面就来仔细的看一下在这个启动类中都有哪些内容:
一、 启动类 Application
通过 main( )方法,启动程序
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
可以看到 Application
类中就只有一个主函数 main( )
方法、也用到了一个 org.springframework.boot.SpringApplication
类的 静态方法 run( )
;
既然,找到了些许苗头,不妨让我们点开看看 这个静态的run( )
方法的究竟;
二、类 SpringApplication
的静态 run( )
方法
org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
通过查看run方法,可以获得以下信息:
- run 是静态方法,可用于运行{@link SpringApplication} 指定源使用默认设置。
- 参数primarySource 是 加载的主源
- 接收一个Spring容器配置类 (也就是通过java代码,对Spring容器进行配置)
- 参数args 是 应用程序参数
- 通常是从主函数 main( )方法中将命令行参数args传递给SpringApplication类
- 作用就是: 在使用 java命令启动该应用时,可以通过命令行的参数对Spring应用做一些配置
- 返回值 是 运行的这的ApplicationContext {@link ApplicationContext}
SpringBootApplication类课用于从java main方法中 引导和启动 SpringBoot应用程序,通常执行以下步骤
- 创建 Spring ApplicationContext 实例对象
- 启用相关功能,接收命令行参数,并将他们公布为Spring 属性
- 根据配置加载所有的 Spring bean
那么翻到这里 你是不是 也没有找到 自动装配 的原理呢 ,是的 我也没有, 那不妨换一个方向!!!
在 启动类 Application
的上方 还有一个标注着的注解 @SpringBootApplication
那就让我们一起来看看 这个注解 有起到了什么样的作用了吧.
三、注解 @SpringBootApplication
(开启了Spring组件扫描、Spring Boot 自动配置)
org.springframework.boot.autoconfigure.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 {
}
实际上它是一个复合注解,除了元注解之外,进入到注解当中,你肯定也能和我一样的 看到 如下的几个注解,那么这些个注解都起到了什么作用呢??
- 元注解:
- @Target: 表示该注解的作用目标;
@Target(ElementType.TYPE)
——接口、类、枚举、注解
@Target(ElementType.FIELD)
——字段、枚举的常量
@Target(ElementType.METHOD)
——方法
@Target(ElementType.PARAMETER)
——方法参数
@Target(ElementType.CONSTRUCTOR)
——构造函数
@Target(ElementType.LOCAL_VARIABLE)
——局部变量
@Target(ElementType.ANNOTATION_TYPE)
——注解
@Target(ElementType.PACKAGE)
——包- @Retention : 就是指定Annotation的保留策略属性。
@Retention(RetentionPolicy.SOURCE)
——注解只会存在源代码中,将会被编译器丢弃- 注释将由编译器记录在类文件中,但不必在运行时由 VM保留。这是默认的行为
@Retention(RetentionPolicy.CLASS)
——注解将会被编译器记录到class文件中,但是在加载到 VM 的时候会被抛弃(同时,这也是默认的策略)@Retention(RetentionPolicy.RUNTIME)
——注解不单会被保留到class文件阶段,而且也在运行时被VM保留、因此可以通过反射方式读取它们。- 核心注解:
- @ComponentScan
- @SpringBootConfiguration
- @EnableAutoConfiguration
1、注解 @ComponentScan
启动组件扫描
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
在这其中:
关于类和方法的Annotation在发生情况下是不出现在javadoc中。
- 定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。
basePackages与value**(互为对方的别名): 作用一致;**
- 用于指定包的路径,进行扫描
- 同时,该属性为一个数组,因此可以指定多个包路径;
- 并且,该属性不赋值时,默认扫描被改注解修饰类所在的包及其子包下的组件。
basePackageClasses: 用于指定某个类的包的路径进行扫描
nameGenerator: bean的名称的生成器
useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测 (默认开启)
includeFilters: 包含的过滤条件
- FilterType.ANNOTATION:按照注解过滤
- FilterType.ASSIGNABLE_TYPE:按照给定的类型
- FilterType.ASPECTJ:使用ASPECTJ表达式
- FilterType.REGEX:正则
- FilterType.CUSTOM:自定义规则
excludeFilters: 排除的过滤条件,用法和includeFilters一样
**这就很明显了,注解 @ComponentScan
启动了组件扫描,开发的组件以及bean定义 能被自动扫描发现,并注入到Spring应用程序上下文,例如控制层添加的@Controller 等一些注解 都是可以被扫描到的 **
2、注解 @SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
从上述源码得出,@SpringBootConfiguration
注解内部有一个核心注解@Configuration,而此注解是Spring提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。
3、注解 @EnableAutoConfiguration
自动载入应用程序所需的所有Bean
org.springframework.boot.autoconfigure.EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
- @Import :用于做导入 | 导入一个或者多个的组件或者类< 通常是 @Configuration注解 标注的配置类。>
- AutoConfigurationImportSelector: 自动配置导入选择器
通过@Import(AutoConfigurationImportSelector.class)
,借助AutoConfigurationImportSelector
,@EnableAutoConfiguration
可以帮助SpringBoot应用将所有符合条件的@Configuration
配置都加载到当前SpringBoot创建并使用的IoC容器。接下来就要仔细的扒一扒 这个选择器了.
3.1 选择器 AutoConfigurationImportSelector
类
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
AutoConfigurationImportSelector
方法selectImports
中, 根据导入配置类的注释元数据, 选择并返回要导入的配置(类)的名称。
- 在方法中 通过 调用 本类中受保护的 getAutoConfigurationEntry 方法 获取 自动配置实体
AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata))
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
AutoConfigurationEntry
方法 根据导入的 标注有@Configuration
注解的配置类 的 注解元数据 获取相应的自动配置实体
- 在方法中 通过本类中受保护的 getCandidateConfigurations 方法 获取配置项完全限定名 列表
List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes 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;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
默认情况下,getCandidateConfigurations方法 将使用 SpringFactoriesLoader 类中的getSpringFactoriesLoaderFactoryClass()来加载候选对象,并返回应该考虑的自动配置类名。
- 那么这些候选对象是些什么东西呢??
- 也就是 getSpringFactoriesLoaderFactoryClass()方法的 返回值 EnableAutoConfiguration.class 了(也就是 标注着@EnableAutoConfiguration的配置类了)
通过 调用 Spring 框架的 工具类 SpringFactoriesLoader中的 静态方法 loadFactoryNames
使用给定的类加载器从指定的位置 中加载给定类型的工厂实现的全限定类名。
List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 ?
classLoader.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()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
- 通过三目 去获取一个资源
- Enumeration urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));- 并将所获取的资源 通过遍历封装为 Properties对象
- 这个获取资源的 路径就是 “META-INF/spring.factories” 这就是 自动装配的 核心的配置 工厂文件了
- 在这个文件中 存在着 所有 可以自动配置组件的 配置类的完全限定名
四、META-INF/spring.factories
文件
找到 spring-boot-autoconfigure-2.3.2.RELEASE.jar!/META-INF/spring.factories 去这里边 瞧个究竟 !!
我们拿 Redis
自动配置 的 自动配置类 RedisAutoConfiguration
来进一步的分析,到底是如何做到自动的装配的
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
1、注解 @Configuration(proxyBeanMethods = false)
- 这不就明摆着 告诉你 他就是一个 配置类嘛 !!!
proxyBeanMethods
= flase 不会被代理,- 如果为true会被
CGLIB
代理;- 如果只是普通配置类的话建议设置为 flase ,这样能提升性能;
2、 注解 @ConditionalOnClass(RedisOperations.class)
条件类
- 几个常用的 条件注解
Conditions 作用描述 (判断是否满足条件) @ConditionalOnBean 判断是否存在某个指定的bean @ConditionalOnMissingBean 判断是否不存在某个bean @ConditionalOnClass 当前类路径classpath上是否存在指定的类 @ConditionalOnMissingClass 当前类路径classpath上是否不存在指定的类 @ConditionalOnResource 当前classpath是否存在某个资源文件 @ConditionalOnProperty 当前jvm是否包含某个系统属性为某个值 @ConditionalOnWebApplication 当前spring context是否是web应用程序
- 它们的作用是按照一定的条件进行判断,满足条件给容器注册bean。
- 首先进入眼睛的,铁定是那些个报错出现红线的地方了, 那这是为什么呢???
- 找不到他, 为什么找不到呢!!!
- 因为, 在依赖管理 pom.xml文件中 我们并没有加入Redis 相关的 starter 启动器
- 稍后, 待我们添加上
redis
对应的启动器之后,再次回来 看看他的状态, 暂时先看下一个注解
3、注解 @EnableConfigurationProperties(RedisProperties.class)
该注解的注释解释是这样的 :
- 为带有@ConfigurationProperties注解的Bean提供有效的支持。这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。
里面参数
RedisOperations.class
@ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private int database = 0; private String url; private String host = "localhost"; private String password; private int port = 6379; // setter / getter // ...... }
- @ConfigurationProperties(prefix = “spring.redis”)
- 注解
@ConfigurationProperties
prefix 绑定着核心配置文件中 以spring.redis
为前缀的配置项 ;- 在Boot 的 核心配置文件
application.yml/properties
中配置过 redis 是不是对类RedisProperties
中的 属性特别的眼熟, 没错,他们是一一对应的, 在配置文件中可以配置的东西就是这一系列 Spring Boot 提供的XXXProperties
类中的属性.
4、再看一下添加stater 启动器之后的情况;
条件判断成立,
五、总结
SpringBoot 自动配置原理( 注解 @EnableAutoConfiguration
) 的工作原理:
-
自动配置的关键
-
@EnableAutoConfiguration
注解; -
META-INF/spring.factories
中 包含与之对应的类的限定名; -
依赖管理
pom.xml
的 中的starter
启动器;
-
-
自动配置的入口 :
-
启动类
Application
上注解@SpringBootApplication
中包含的注解@EnableAutoConfiguration
-
@EnableAutoConfiguration
通过@Import
注解,借助AutoConfigurationImportSelector
自动配置导入选择器,调用SpringFactoriesLoader
的loadFactoryNames
方法,从classpath
中寻找所有的META-INF/spring.factories
配置文件;- 借助
AutoConfigurationImportSelector
将所有复合条件的@Configuration 配置都加载到 IoC容器当中 - 当然这所有的自动配置类, 并不是都生效的, 根据类上标注的
@ConditionalXXX
注解, 进行条件判断,条件成立之后,进行自动配置Bean注入SpringContext(IoC);
- 借助
通过以上的这些原理, 无论何时在 Spring Boot 项目中添加新依赖项(启动器),Spring Boot 自动配置 都会尝试根据依赖项自动装配
bean
-