Starter机制本身实现基于SPI,很多框架都使用了java的SPI机制,如java.sql.Driver的SPI实现(mysql驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架,Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的 Bean 根据环境( 条件)进行自动配置。使用者只需要依赖相应功能的 Starter,无需做过多的配置和依赖,SpringBoot 就能自动扫描并加载相应的模块。SpringBoot 存在很多开箱即用的 Starter 依赖,使得我们在开发业务代码时能够非常方便的、不需要过多关注框架的配置,而只需要关注业务即可。要熟悉Starter机制,首先需要了解@SpringBootApplication注解
@SpringBootApplication注解
@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 {};
}
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
主要包含:@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class)
@Import注解:把多个分来的容器配置合并在一个配置中,xml 形式下有一个<import resource/> 形式的注解,在JavaConfig 中所表达的意义是一样的。
@Import(AutoConfigurationImportSelector.class)中AutoConfigurationImportSelector的类图
实现了ImportSelector接口,重写了selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //加载 "META-INF/spring-autoconfigure-metadata.properties"文件,里面都是加载bean的conditon AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); //下面代码具体分析 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //具体分析 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); //记载配置文件 META-INF/spring.factories中EnableAutoConfiguration.class为key的value集合 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去重和根据条件Condition加载bean到Ioc容器中
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
spring-autoconfigure-metadata.properties中的小部分内容
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,org.springframework.data.cassandra.core.ReactiveCassandraTemplate,reactor.core.publisher.Flux
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
ConditionalOnClass代表必须存在后面的class才会加载,autoConfigureAfter代表后面bean实例化后再实例化当前bean,还有其他条件,这里只是部分,目的就是根据条件加载bean。
META-INF/spring.factories中EnableAutoConfiguration.class为key的value集合
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
AutoConfigurationPackages.Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//实现动态注册IOC register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
直接用这三个注解也可以启动 springboot 应用,只是每次配置三个注解比较繁琐,所以直接用一个复合注解更方便些。
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.8.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.36</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.0.4.RELEASE</version> </dependency> </dependencies>
定义接口JsonFormat,用来把java bean解析成String
public interface JsonFormat<T> { String parse (T t) throws JsonProcessingException; }
两个实现类,一个基于FastJson,一个基于Jackson
public class fastJsonFormat<T> implements JsonFormat<T> { @Override public String parse(T o) { String s = JSON.toJSONString(o); return s; } }
public class JacksonFormat<T> implements JsonFormat<T> { @Override public String parse(T t) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); String s = objectMapper.writeValueAsString(t); return s; } }
动态配置属性类 FormatConfigProperties
@ConfigurationProperties(prefix = "json.format", ignoreUnknownFields = true) public class FormatConfigProperties { public FormatConfigProperties() { } public FormatConfigProperties(String name1, String name2, String name3) { this.name1 = name1;this.name2 = name2;this.name3 = name3; } private String name1; private String name2; private String name3; public String getName1() { return name1; } public void setName1(String name1) { this.name1 = name1; } public String getName2() { return name2; } public void setName2(String name2) { this.name2 = name2; } public String getName3() { return name3; } public void setName3(String name3) { this.name3 = name3; } }
实例化两种 JsonFormat
@Configuration public class JsonFormatConfiguration { @ConditionalOnClass(name ="com.alibaba.fastjson.JSON") @Bean @Primary public JsonFormat stringFormat(){ return new fastJsonFormat(); } @ConditionalOnClass(name = "com.fasterxml.jackson.databind") @Bean public JsonFormat jsonFormat(){ return new JacksonFormat(); } }
实现动态注入的类JsonFormatAutoConfiguration
@EnableConfigurationProperties(FormatConfigProperties.class) @Import({JsonFormatConfiguration.class}) @Configuration public class JsonFormatAutoConfiguration { @Bean public JsonFormatTemplate jsonFormatTemplate(JsonFormat jsonFormat,FormatConfigProperties formatConfigProperties) { return new JsonFormatTemplate(jsonFormat,formatConfigProperties); } }
最重要的一步,也是最后一步,在当前项目的resources目录下新建 META-INF/spring.factories 文件里面内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.json.config.JsonFormatAutoConfiguration
基于springboot自带的@EnableAutoConfiguration注解,就用将需要的bean注入Ioc容器中,实现动态注入bean,然后把当前项目打成jar包,需要用到此功能的项目直接pom引入即可。
@RunWith(SpringRunner.class) @SpringBootTest public class DemoApplicationTests { @Autowired private JsonFormatTemplate jsonFormatTemplate; @Test public void contextLoads() throws JsonProcessingException { Person person = new Person("meixi", "阿根廷", 18); System.out.println( jsonFormatTemplate.parse(person)); } }
上面的starter demo本身没有实际意义,真实项目中按照这个方式定制Starter即可。