zoukankan      html  css  js  c++  java
  • 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加载自动配置的原理就是如此但时机并不在这儿,这个后面的文章会讲到。

      

  • 相关阅读:
    02
    01
    Redis、Mongo
    Django
    Django
    Django
    Django
    7.2
    Django
    contenttypes
  • 原文地址:https://www.cnblogs.com/hetutu-5238/p/12371188.html
Copyright © 2011-2022 走看看