spring boot提供了一系列的starter pom来简化maven的依赖加载,例如spring-boot-starter-web、spring-boot-starter-test等。
在我们定制自己的starter前先了解一下必须知道的概念。
1、@Enable*注释
@Enable*注释并不是新发明的注释,早在Spring 3框架就引入了这些注释,用这些注释替代XML配置文件。很多Spring开发者都知道@EnableTransactionManagement注释,它能够声明事务管理;@EnableWebMvc注释,它能启用Spring MVC;以及@EnableScheduling注释,它可以初始化一个调度器。 这些注释事实上都是简单的配置,通过@Import注释导入。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EnableAutoConfigurationImportSelector.class,
AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
*/
Class<?>[] exclude() default {};
}
EnableAutoConfigurationImportSelector类使用了Spring Core包的SpringFactoriesLoader类的loadFactoryNamesof()方法。
SpringFactoriesLoader会查询META-INF/spring.factories文件中包含的JAR文件。
当找到spring.factories文件后,SpringFactoriesLoader将查询配置文件命名的属性。在例子中,是org.springframework.boot.autoconfigure.EnableAutoConfiguration。
让我们来看看spring-boot-autoconfigure JAR文件,它真的包含了一个spring.factories文件,内容如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration
在这个文件中,可以看到一系列Spring Boot自动配置的列表。下面我们来看这些配置的细节,以MongoAutoConfiguration为例:
@Configuration
@ConditionalOnClass(Mongo.class)
@EnableConfigurationProperties(MongoProperties.class)
public class MongoAutoConfiguration {
@Autowired
private MongoProperties properties;
private Mongo mongo;
@PreDestroy
public void close() throws UnknownHostException {
if (this.mongo != null) {
this.mongo.close();
}
}
@Bean
@ConditionalOnMissingBean
public Mongo mongo() throws UnknownHostException {
this.mongo = this.properties.createMongoClient();
return this.mongo;
}
}
这个类进行了简单的Spring配置,声明了MongoDB所需典型Bean。
这个类跟其它很多类一样,重度依赖于Spring Boot注释:
- @ConditionOnClass激活一个配置,在类路径中只能存在一到几个这样的类。
- @EnableConfigurationProperties自动映射一个POJO到Spring Boot配置文件(默认是application.properties文件)的属性集。
- @ConditionalOnMissingBean启用一个Bean定义,但必须是这个Bean之前未定义过才有效。
还可以使用@ AutoConfigureBefore注释、@AutoConfigureAfter注释来定义这些配置类的载入顺序。
2、@Conditional注释
Spring Boot的强大之处在于使用了Spring 4框架的新特性:@Conditional注释,此注释使得只有在特定条件满足时才启用一些配置。
在Spring Boot的org.springframework.boot.autoconfigure.condition包中说明了使用@Conditional注释能给我们带来什么,下面对这些注释做一个概述:
- @ConditionalOnBean
- @ConditionalOnClass
- @ConditionalOnExpression
- @ConditionalOnMissingBean
- @ConditionalOnMissingClass
- @ConditionalOnNotWebApplication
- @ConditionalOnResource
- @ConditionalOnWebApplication
以@ConditionalOnExpression注释为例,它允许在Spring的EL表达式中写一个条件。
@Conditional(OnExpressionCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnExpression {
/**
* The SpEL expression to evaluate. Expression should return {@code true} if the
* condition passes or {@code false} if it fails.
*/
String value() default "true";
}
在这个类中,我们想利用@Conditional注释,条件在OnExpressionCondition类中定义:
public class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// ...
// we first get a handle on the EL context via the ConditionContext
boolean result = (Boolean) resolver.evaluate(expression, expressionContext);
// ...
// here we create a message the user will see when debugging
return new ConditionOutcome(result, message.toString());
}
}
在最后,@Conditional通过简单的布尔表达式(即ConditionOutcome方法)来决定。
3、应用程序上下文初始化器
spring.factories还提供了第二种可能性,即定义应用程序的初始化。这使得我们可以在应用程序载入前操纵Spring的应用程序上下文ApplicationContext。
特别是,可以在上下文创建监听器,使用ConfigurableApplicationContext类的addApplicationListener()方法。
AutoConfigurationReportLoggingInitializer监听到系统事件时,比如上下文刷新或应用程序启动故障之类的事件,Spring Boot可以执行一些工作。这有助于我们以调试模式启动应用程序时创建自动配置的报告。
要以调试模式启动应用程序,可以使用-Ddebug标识,或者在application.properties文件这添加属性debug= true。
4、调试Spring Boot自动配置
Spring Boot的官方文档(http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-troubleshoot-auto-configuration)有助于理解自动配置期间发生了什么。当以调试模式运行时,Spring Boot会产生一个报告,如下:
Positive matches:
-----------------
MessageSourceAutoConfiguration
- @ConditionalOnMissingBean (types: org.springframework.context.MessageSource; SearchStrategy: all) found no beans (OnBeanCondition)
JmxAutoConfiguration
- @ConditionalOnClass classes found: org.springframework.jmx.export.MBeanExporter (OnClassCondition)
- SpEL expression on org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration: ${spring.jmx.enabled:true} (OnExpressionCondition)
- @ConditionalOnMissingBean (types: org.springframework.jmx.export.MBeanExporter; SearchStrategy: all) found no beans (OnBeanCondition)
DispatcherServletAutoConfiguration
- found web application StandardServletEnvironment (OnWebApplicationCondition)
- @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)
Negative matches:
-----------------
DataSourceAutoConfiguration
- required @ConditionalOnClass classes not found: org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType (OnClassCondition)
DataSourceTransactionManagerAutoConfiguration
- required @ConditionalOnClass classes not found: org.springframework.jdbc.core.JdbcTemplate,org.springframework.transaction.PlatformTransactionManager (OnClassCondition)
MongoAutoConfiguration
- required @ConditionalOnClass classes not found: com.mongodb.Mongo (OnClassCondition)
FallbackWebSecurityAutoConfiguration
- SpEL expression on org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration: !${security.basic.enabled:true} (OnExpressionCondition)
SecurityAutoConfiguration
- required @ConditionalOnClass classes not found: org.springframework.security.authentication.AuthenticationManager (OnClassCondition)
EmbeddedServletContainerAutoConfiguration.EmbeddedJetty
- required @ConditionalOnClass classes not found: org.eclipse.jetty.server.Server,org.eclipse.jetty.util.Loader (OnClassCondition)
WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#localeResolver
- @ConditionalOnMissingBean (types: org.springframework.web.servlet.LocaleResolver; SearchStrategy: all) found no beans (OnBeanCondition)
- SpEL expression: '${spring.mvc.locale:}' != '' (OnExpressionCondition)
WebSocketAutoConfiguration
- required @ConditionalOnClass classes not found: org.springframework.web.socket.WebSocketHandler,org.apache.tomcat.websocket.server.WsSci (OnClassCondition)
对于每个自动配置,可以看到它启动或失败的原因。
Spring Boot通过配置信息指出:特定配置项被选中的原因、列出匹配到对应类的配置项(positive match)、不包括某个配置项的原因(negative match)。
-------------demo-----------
5、创建starter项目
5.1 创建一个enable注解
package com.aa.cloud.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: EnableDemo
* @Description:
* @author weiyb
* @date 2017年9月20日 下午4:19:19
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableDemo {
}
5.2 服务类
package com.aa.cloud.service;
/**
* @ClassName: UserService
* @Description:
* @author weiyb
* @date 2017年9月20日 下午4:08:51
*/
public class DemoService {
public void demoStarter() {
System.out.println("hello world");
}
}
5.3 自动配置类
package com.aa.cloud.auto.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.aa.cloud.annotation.EnableDemo;
import com.aa.cloud.service.DemoService;
/**
* @ClassName: DemoAutoConfiguration
* @Description:
* @author weiyb
* @date 2017年9月20日 下午4:09:07
*/
@Configuration
@ConditionalOnBean(annotation = EnableDemo.class)//条件判断,只有在使用了@EnableUser时,才会启动
public class DemoAutoConfiguration {
@Bean
public DemoService dbCountRunner() {
return new DemoService();
}
}
5.4 配置META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.aa.cloud.auto.config.DemoAutoConfiguration
6、创建demo starter test项目
6.1 启动类
package com.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.aa.cloud.annotation.EnableDemo;
/**
* @ClassName: ProviderApplication
* @Description:
* @author weiyb
* @date 2017年9月20日 下午4:08:42
*/
@SpringBootApplication
@EnableDemo
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
6.2 测试类
package com.demo.starter.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.aa.cloud.service.DemoService;
import com.cloud.ProviderApplication;
/**
* @ClassName: DemoServiceTest
* @Description:
* @author weiyb
* @date 2017年9月20日 下午4:08:35
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class DemoServiceTest {
@Resource
private DemoService userService;
@Test
public void demoStarter() {
userService.demoStarter();
}
}