最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多。

总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了。比如要使用redis,我们直接引入相关的包,将redis连接信息配置下即可使用。本文主要分析下springboot自动化配置的关键。

本文分析核心过程的代码,无关性的代码略过。

1.注解与组合注解

  再说自动化配置前肯定要先说下注解,java5开始引入了注解这么个东西,可以对类,成员方法,成员变量加上标记,而这些标记可以在类加载,编译运行时被读取,基本可以做到无侵入性。同时java也支持组合注解,就是注解之上可以再添加其他注解,例如我们使用springmvc框架时,使用@RestController,即可代替@Controller以及@ResponseBody注解。是因为@RestController就是一个组合注解。其已经组合了两种注解。注解和组合注解在springboot的自动化配置中起了非常大的作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController { @AliasFor(annotation = Controller.class)
String value() default ""; }

2.@SpringBootApplication

  相信用springboot开发的同学对这个注解不会陌生,其是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) })
public @interface SpringBootApplication { @AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {}; }

  我们可以看到其组合了很多注解,而这些注解当然又加载了很多注解,下面是这个注解的注解组合。

  可以看到一个@SpringBootApplication注解后有很多组合的注解。而这些注解在springboot进行自动化配置的时候起了很大的作用。在后面用到的时候这些注解的作用会一一说明

3.从main方法开始

  相信大多数人的springboot项目都是从如下的函数代码开始,那我们就从这个代码开始入手

@SpringBootApplication
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args);
}
}

  跟着代码一路走,会发现其主要分为两步。创建SpringApplication对象,和执行run方法。这儿两步我们分别看

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

4.从创建SpringApplication看spring如何完成自动化可拔插配置

  这是SpringApplication的构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...//省略代码
//设置ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//设置ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); ...//省略代码
}
  我们看setInitializers方法,即查找并设置初始化工具接口ApplicationContextInitializer.class的所有实现类。 我们可以看下spring的查找方法即getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//当前的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//1.获取满足条件的类的全限定名
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//2.根据全限定名进行反射生成实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances; }

  上面的方法主要由两步,第一步是获取系统配置的全限定名,第二步是根据全限定名反射生成实例,这儿我们主要看第一步。接着往下看

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//缓存 防止重复加载
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try {
//1.找到所有 META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories"));
result = new LinkedMultiValueMap<>();
//2.对spring.factories进行迭代
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//3.解析每个spring.factories 并将内容存入result
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
//这儿result中存的则是spring.factories中的东西
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//存入缓存
cache.put(classLoader, result);
return result;
} }

  其实这个方法可以说就揭示了springboot进行自动化配置的一个关键,其会搜寻所有的META-INF/spring.factories。然后将信息以key,values的形式存起来。我们可以看下这种文件长什么样,这儿我们看spring-boot-autoconfigure包下spring.factories文件。由于文件太长我截取部分。

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition # Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
.....................//省略

  这儿可以看到我们之前要查找的ApplicationContextInitializer,在这儿就有对应的两个SharedMetadataReaderFactoryContextInitializer与ConditionEvaluationReportLoggingListener。当然这儿还有很多其他类型的配置类就不一一说明了。springboot会将这些spring.factories文件一一读取,并将其中的信息存储到系统中的cache。既然已经知道了类的全限定名,那我们如果需要加载的话直接反射生成实例即可。我们常见的redis自动配置,或者mongoDB自动配置,在该文件中都可以找到

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...//省略
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\

  相信到了这儿对springboot如何做到自动化配置肯定已经有了一个初步的认识了。springboot提供了一个插件的方式来供第三方主动集成。按照其规则只要我们写出对应的文件内容并放在项目的META-INF/spring.factories中。在springboot项目中引入该依赖,spring就会去自动读取我们的配置。这儿可以举个简单的例子,比如说我们想要实现一个插件,加入了我们这个插件的依赖后springmvc中json转换由jackson换为fastjson(springmvc默认使用jackson)。那我们就可以创建项目。并创建如下类

@Configuration
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
public class WebMvcConfig {
public WebMvcConfig() {
} @Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = fastJsonHttpMessageConverter.getFastJsonConfig();
SerializeFilter[] serializeFilters = fastJsonConfig.getSerializeFilters();
fastJsonConfig.setSerializerFeatures(new SerializerFeature[]{SerializerFeature.PrettyFormat});
List<MediaType> fastMediaTypes = new ArrayList();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
return fastJsonHttpMessageConverter;
} @Bean
public HttpMessageConverters customConverters(FastJsonHttpMessageConverter fastJsonHttpMessageConverter) {
return new HttpMessageConverters(new HttpMessageConverter[]{fastJsonHttpMessageConverter});
} }

  然后在项目的resouce下新建META-INF/spring.factories,并加入上面的配置类信息

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.framework.web.mvc.config.WebMvcConfig

  这样我们的springboot项目如果引入了这个插件并且引入了fastjson相关的依赖。那么系统就会自动进行替换。想下是不是基本做到了无代码侵入,可拔插。这也就是springboot自动发现并配置的关键。

  这个时候当然会有人有疑惑,那我如果引入了这个插件,但是却没引入fastjson的包。那系统不是就会启动报错了吗。能不能做到,我引入了插件所依赖的包才让他生效,否则就不生效呢? spring当然考虑到了这点儿,当然这是后面在分析spring进行配置类解析的时候会讲到的。

  到此其实我们就迈出了spring自动发现配置的关键了,当然了,springboot加载自动配置的原理就是如此但时机并不在这儿,这个后面的文章会讲到。

  

springboot笔记-1.自动化配置的关键的更多相关文章

  1. springboot笔记-2-.核心的上下文以及配置扫描解析(上)

    前言 上一节中说明了springboot是如何做到自动发现配置的,那么本节看下spring如何创建上下文并解析这些配置,加载我们注册到容器管理中的类.上节已经成功的创建了SpringApplicati ...

  2. 【使用篇二】Quartz自动化配置集成(17)

    出处:https://www.jianshu.com/p/49133c107143 定时任务在企业项目比较常用到,几乎所有的项目都会牵扯该功能模块,定时任务一般会处理指定时间点执行某一些业务逻辑.间隔 ...

  3. SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...

  4. SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理

    springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...

  5. 让SpringBoot自动化配置不再神秘

    本文若有任何纰漏.错误,还请不吝指出! 注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory.关于BeanFactory, ...

  6. SpringBoot多重属性文件配置方案笔记

    SpringBoot多重属性文件配置方案笔记 需要重写PropertyPlaceholderConfigurer 同时要忽略DataSourceAutoConfiguration @SpringBoo ...

  7. Spring Boot 探索系列 - 自动化配置篇

    26. Logging Prev  Part IV. Spring Boot features  Next 26. Logging Spring Boot uses Commons Logging f ...

  8. Linux实战教学笔记25:自动化运维工具之ansible (一)

    第二十五节 ansible之文件的批量分发 标签(空格分隔): Linux实战教学笔记-陈思齐 ---本教学笔记是本人学习和工作生涯中的摘记整理而成,此为初稿(尚有诸多不完善之处),为原创作品,允许转 ...

  9. springboot情操陶冶-web配置(八)

    本文关注应用的安全方面,涉及校验以及授权方面,以springboot自带的security板块作为讲解的内容 实例 建议用户可直接路由至博主的先前博客spring security整合cas方案.本文 ...

随机推荐

  1. zabbix4.2配置监控MySQL

    1.在被监控主机安装好MySQL 相关步骤省略. 2.创建监控所需要的MySQL账户(MySQL服务器端) MariaDB [(none)]>grant usage on *.* to zabb ...

  2. Linux MYSQL安装指南

    安装环境:系统是 centos6.5 1.下载 下载地址:http://dev.mysql.com/downloads/mysql/5.6.html#downloads 下载版本:我这里选择的5.6. ...

  3. JAVA 注解教程(一)简单介绍

    相关网址 https://blog.csdn.net/briblue/article/details/73824058 --- 已经整理成笔记 https://blog.csdn.net/bao199 ...

  4. SuperSocket与SuperSocket.ClientEngine实现Protobuf协议

    参考资料说明 SuperSocket文档 http://docs.supersocket.net/ Protobuf语言参考 https://developers.google.com/protoco ...

  5. STL 萃取(Traits)机制剖析

    模板特化 在将萃取机制之前,先要说明模板特化 当有两个模板类,一个是通用泛型模板,一个是特殊类型模板,如果创建一个特殊类型的对象,会优先调用特殊的类型模板类,例如: template <type ...

  6. 【14】N的二进制中1的个数

    class Solution { public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> h = new ...

  7. 在手机浏览器中判断App是否已安装

    从网上搜到之前手机中判断App是否安装可以通过onblur事件+定时器来实现. 但现在要做这个功能时,按网上的说法已经不能实现了.因为现在浏览器中打开App,window不会触发onblur事件. 在 ...

  8. Python记:索引操作示例:将以数指定年,月,日的日期打印出来

    ————————————————————————————————————不要停止奔跑,不要回顾来路,来路无可眷恋,值得期待的只有前方. months=[ 'January', 'February', ...

  9. 计算几何-HPI

    This article is made by Jason-Cow.Welcome to reprint.But please post the article's address.   在线笛卡尔坐 ...

  10. 计算几何-Dot-Vector

    This article is made by Jason-Cow.Welcome to reprint.But please post the article's address. 看了书,然后码 ...