springboot笔记-1.自动化配置的关键

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

总所周知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加载自动配置的原理就是如此但时机并不在这儿,这个后面的文章会讲到。

  

 

posted @ 2020-02-27 14:44  雨落寒沙  阅读(485)  评论(0编辑  收藏  举报