官方对Spring Boot的介绍是这么说的:
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".
We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.
Features
Create stand-alone Spring applications
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
Provide opinionated 'starter' dependencies to simplify your build configuration
Automatically configure Spring and 3rd party libraries whenever possible
Provide production-ready features such as metrics, health checks and externalized configuration
Absolutely no code generation and no requirement for XML configuration
大概意思就是Spring Boot集成一些第三方库,基于"约定优于配置 "的原则,简化了各种配置并通过starter引入,同时提供了一些检查功能,使用户可以快速开发并部署Spring应用。这些Spring本身和第三方库提供的功能,都是由“AutoConfigure”相关的功能实现的,可以说Spring Boot=SpringFramework+AutoConfigure。Spring已经默认提供了大部分常用库的支持,我们仍然可以自己开发starter,让用户可以更快更简单的引入其他第三方或自己开发的功能。
Quick start
创建依赖模块
创建一个maven项目:spring-boot-starter-foo-api,模拟一个第三方库
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-custom-starter</artifactId> <groupId>org.dfg.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-starter-foo-api</artifactId> </project>
创建一个类,模拟库API
public class Account { private String number; //getter、setter public String toString() { return String.format("Account:{number=%s}", number); } }
创建autoconfigue模块
创建一个maven项目:spring-boot-starter-foo-autoconfigue,用于配置依赖并加入Spring
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-custom-starter</artifactId> <groupId>org.dfg.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-starter-foo-autoconfigue</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.dfg.demo</groupId> <artifactId>spring-boot-starter-foo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
创建配置类
@Configuration public class FooAutoConfiguration { @Bean(name = "account") @ConditionalOnMissingBean public Account getAccount() { System.out.println("create account"); return new Account("011"); } }
到这一步就是通常情况下Spring基友注解API配置的用法,但是Spring Boot Starter的目标是做到无配置、自动发现、自动配置。这就需要下一步,在resources目录新建文件:META-INF\spring.factories,并输入:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.dfg.demo.springboot.FooAutoConfiguration
创建starter模块
创建一个maven项目:spring-boot-starter-foo-starter,作为starter依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-custom-starter</artifactId> <groupId>org.dfg.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-starter-foo-starter</artifactId> <dependencies> <dependency> <groupId>org.dfg.demo</groupId> <artifactId>spring-boot-starter-foo-autoconfigue</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
在resources目录下创建文件:META-INF\spring.provides,并输入:
provides: spring-boot-starter-foo-autoconfigue,spring-boot-starter-foo-api
创建应用模块
创建一个maven项目:spring-boot-starter-foo-example,模拟需求依赖的应用
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-custom-starter</artifactId> <groupId>org.dfg.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-starter-foo-example</artifactId> <dependencies> <dependency> <groupId>org.dfg.demo</groupId> <artifactId>spring-boot-starter-foo-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> </project>
创建测试类:
@SpringBootApplication public class AccountApplication { } @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = AccountApplication.class) public class AccountTest { @Autowired ApplicationContext context; @Test public void accountTest() throws Exception { Assert.assertTrue(context.containsBean("account")); Account account = context.getBean(Account.class); System.out.println(account); Assert.assertEquals(account.getNumber(), "011"); } }
执行单元测试,程序打印:
create account Account:{number=011}
可见,应用只需引入starter依赖,无需任何其他操作,就能使用其提供的功能,做到了简单无配置。
自定义配置
零配置固然方便,但是实际应用中必然需要自定义配置,Spring提供了允许自定义配置的功能。
在spring-boot-starter-foo-api模块中加入一个类模拟API:
public class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } //getter、setter @Override public String toString() { return String.format("User:{name=%s, age=%s}", name, age); } }
在spring-boot-starter-foo-autoconfigue模块中加入配置参数映射类:
@ConfigurationProperties(prefix = "foo") public class FooProperties implements InitializingBean { private User config; public User getConfig() { return config; } public void setConfig(User config) { this.config = config; } @Override public void afterPropertiesSet() { if (config == null) { config = new User(); config.name = "tony"; config.age = 18; } } public static class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
修改自动配置类:
@Configuration @EnableConfigurationProperties(FooProperties.class) public class FooAutoConfiguration { @Bean(name = "user") @ConditionalOnMissingBean public User getUserByProperty(FooProperties properties) { System.out.println("create user"); String name = properties.getConfig().getName(); int age = properties.getConfig().getAge(); User user = new User(name, age); return user; } }
在spring-boot-starter-foo-example模块中的resources目录下创建配置文件application.yaml:
foo: config: name: abc age: 9
创建测试类:
@SpringBootApplication public class AccountApplication { } @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = {AccountApplication.class}) public class UserTest { @Autowired ApplicationContext context; @Test public void userTest() throws Exception { Assert.assertTrue(context.containsBean("user")); User user = context.getBean(User.class); System.out.println(user); Assert.assertEquals(user.getName(), "abc"); Assert.assertEquals(user.getAge(), 9); } }
执行单元测试,程序打印:
create user User:{name=abc, age=9}
至此一个引入依赖即可使用,并支持配置的starter就完成了。
相关注解
-
@Configuration,表示这是一个配置类,Spring容器会在启动时从这个类获取bean。
-
@EnableConfigurationProperties,启用@ConfigurationProperties,可以直接指定一个。
-
@ConfigurationProperties,将外部属性绑定到类,并在定义Bean的方法中使用。
-
@ConditionalOnClass/@ConditionalOnMissingClass,组件(配置类或者创建bean的方法)只有在环境变量里存在/不存在时才注册。
-
@ConditionalOnBean/@ConditionalOnMissingBean,只有当指定bean存在/不存在时满足匹配,当放在创建bean方法上时,默认取这个方法返回类型。由于只能取到已创建的bean,所以需要下一个注解配合。
-
@AutoConfigureBefore/@AutoConfigureAfter,指定的EnableAutoConfiguration必须在指定的自动配置类之前/之后执行。
-
@ConditionalOnSingleCandidate,同@ConditionalOnBean,但是要求只有一个
关于spring.factories
AutoConfiguration类唯一一次被用到就是在META-INF\spring.factories文件里,spring就是通过这个文件找到要加载的配置类的。依赖于spring本身的加载机制,类似于SPI(Service Provider Interface)。通过SpringFactoriesLoader这个类读取配置文件,其中key是接口或抽象类的全限定名,value是用逗号分隔的实现类名。
package org.springframework.core.io.support; public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { ... 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); } } }
当Spring启动后,会执行ConfigurableApplicationContext.refresh()初始化容器,其中invokeBeanFactoryPostProcessors()会执行所有BeanFactoryPostProcessor,其中有个ConfigurationClassPostProcessor用于处理@Configuration类。其中processConfigBeanDefinitions()创建ConfigurationClassParser解析配置类,直到ConfigurationClassParser.processImports()中查找配置类所有。由于@SpringBootApplication有注解@EnableAutoConfiguration,而@EnableAutoConfiguration又有注解@Import(AutoConfigurationImportSelector.class),会注册一个DeferredImportSelector处理器。然后执行这些处理器,在DeferredImportSelectorGrouping.getImports()中调用AutoConfigurationImportSelector.process()。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //处理过滤 ... return new AutoConfigurationEntry(configurations, exclusions); } protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 查找EnableAutoConfiguration.class对应的Configuration类名 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware { @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { // 查找要自动配置的配置类 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } ... } }
ConfigurationClassParser通过迭代查找所有的配置类,并最终registerBeanDefinition()注册为BeanDefinition()。
关于starter模块
关于spring-boot-starter-foo-starter这个模块,里面只提供META-INF\spring.provides一个文件,而spring并未读取这个文件。事实上这个文件和这个模块并不是必需的,官方的说法是这个文件的用处是帮助STS(和支持这个功能的插件)提供内容自动完成建议。
所以实际应用中,可以直接引用spring-boot-starter-foo-autoconfigue这个模块。
相关代码:
https://github.com/dingfugui/spring-notes/tree/master/spring-boot-custom-starter