深入SpringBoot:自定义EnableAutoConfiguration

前言

上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理。SpringBoot在SpringFramework的基础上集成了Web容器,日志等功能,可以快速的实现Web服务。
先看SpringBoot必要的依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>

spring-boot包含了SpringBoot的核心实现类。
spring-boot-autoconfigure则是根据当前classpath的类来注入不同的实现类。

EnableAutoConfiguration

先看一个基本的例子,这个例子实现了一个简单的web服务。

  1. 引入必要的依赖,开启web服务需要引入对应的EmbedWeb容器。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
  1. 程序入口,这里使用了EnableAutoConfiguration注解。
    @Configuration
    @EnableAutoConfiguration
    public class Bootstrap {
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(Bootstrap.class);
            springApplication.run(args);
        }
        @Controller
        public static class MyController {
            @RequestMapping
            @ResponseBody
            public Map index() {
                Map<String, String> map = new HashMap<String, String>();
                map.put("hello", "world");
                return map;
            }
        }
    }

观察EnableAutoConfiguration可以发现,这里ImportEnableAutoConfigurationImportSelector
Import主要是配合Configuration来使用的,用来导出更多的Configuration类,ConfigurationClassPostProcessor会读取Import的内容来实现具体的逻辑。
EnableAutoConfigurationImportSelector实现了DeferredImportSelector接口,并实现了selectImports方法,用来导出Configuration类。

    String[] selectImports(AnnotationMetadata importingClassMetadata);

导出的类是通过SpringFactoriesLoader.loadFactoryNames()读取了ClassPath下面的META-INF/spring.factories文件,这个文件内容大致如下。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
...
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
...

其中EmbeddedServletContainerAutoConfiguration是实现web服务的主要的配置类。这个类会根据当前存在的类的信息注入必要的EmbeddedServletContainerFactory类。
spring-boot-starter-tomcat引入了tomcat的依赖,所以EmbeddedServletContainerAutoConfiguration发现存在Tomcat.class就会注入TomcatEmbeddedServletContainerFactory来内置web容器。

    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }
    }

自定义EnableAutoConfiguration

自定义EnableAutoConfiguration就比较简单了。完整的代码在Github上了。

  1. 自定义EnableAutoConfiguration,这里ImportMyEnableAutoConfigurationImport
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(MyEnableAutoConfigurationImport.class)
    public @interface MyEnableAutoConfiguration {
    }
  1. 自定义EnableAutoConfigurationImport,注入了ClassLoader,并调用SpringFactoriesLoader.loadFactoryNames()方法,导出Configuration的类。
    public class MyEnableAutoConfigurationImport implements DeferredImportSelector, BeanClassLoaderAware {
        private ClassLoader classLoader;
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            List<String> beanNames = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
            return beanNames.toArray(new String[beanNames.size()]);
        }
    }
  1. 入口类,这里使用了MyEnableAutoConfiguration注解。
    @Configuration
    @MyEnableAutoConfiguration
    public class CustomizeEnableAutoConfigure {
        public static void main(String[] args) {
            SpringApplication application = new SpringApplication(CustomizeEnableAutoConfigure.class);
            application.run(args);
        }
        @Controller
        public static class MyController {
            @RequestMapping
            @ResponseBody
            public Map index() {
                Map<String, String> map = new HashMap<String, String>();
                map.put("hello", "world");
                return map;
            }
        }
    }

结语

这里主要介绍了SpringBootEnableAutoConfiguration的实现,后面会介绍更多关于SpringBoot的内容。

推荐阅读更多精彩内容