springboot mybatis starter 自动配置的原理
用过springboot的同学都是知道,springboot配置简单,更提供了大量的starter方便集成各个组件。每个组件基本只要依赖了jar包,基本不需要或者需要少量的配置信息就可以直接使用了,那么在这简单的starter背后的原理是什么呢?
原理
统一的命名规范 XXX-spring-boot-starter (非必须)
你如果去看每个starter的的名字,比如mybatis的starter名字如下:
1 2 3 4 5
| <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>
|
你会发现每个starter的artifactId
都是以spring-boot-starter
结尾,这个是普遍认同的一个规范,不是必须按照这个名字来,也可以自定义名字,原因下面介绍。
统一的配置文件 spring.factories
必须在classpath下的META-INF文件夹下创建一个spring.factories,本质是一个Properties,所以得按照key-value的形式进行配置。自动配置的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration
。我们看下mybatis的配置:
1 2 3
| # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
|
当然你也可以使用其他的一些key,可以参考:spring-boot-autoconfigure 的spring.factories文件内容
这里解释一下为什么jar包的artifactId
名字不是必须按照规范来,让我们看下源码的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
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 factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
|
源码中使用了ClassLoader的getResources方法,该方法传入一个文件相对路径也就是META-INF/spring.factories
,会返回代码中所有找到的路径,这个跟jar包的名字没有任何关系。
注解 Configuration
Configuration 是springboot提供的,表示这是一个配置类,其实它的作用跟@Bean
是一样的,都是让spring实例化该类。
同时springboot还额外提供了一下注解用来实现自动配置。
@ConditionalOnClass
表示存在某些类的时候,才进行初始化。
表示必须先初始化某个类
表示初始化完成之后加载某个类
@ConditionalOnMissingBean
可以跟@Bean配合使用,避免多次初始化,来看一个具体的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Bean public A getA() { return new A(); }
@Bean @ConditionalOnMissingBean public A getA2() { return new A(); }
static class A { public A() { System.out.println("=====init A"); } }
|
输出如下,只会打印一次
现在我们调整上面的代码继续看输出的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Bean @ConditionalOnMissingBean public A getA() { return new A(); }
@Bean public A getA2() { return new A(); }
static class A { public A() { System.out.println("=====init A"); } }
|
输出如下,打印两次
打印两次的原因是我们在getA2方法上没有添加ConditionalOnMissingBean注解,所以不会判断是否已经存在A的实例