Spring Boot的魔法
Spring Boot是一个基于Spring的框架,遵循“约定胜于配置”的理念,提供一套方便的方法来快速创建可执行的Spring应用。具体来说,以往我们开发一个Java Web端或服务端程序,需要解决两个问题:
- 往往需要做大量的配置:声明大量的bean,并把他们组装到一起
- 需要解决很多依赖版本冲突的问题。特别是当应用依赖到很多组件的时候(Spring MVC, 数据库, Redis等),要解决这些依赖,需要花很多的功夫手工去配置,解决jar包版本冲突。
而如果使用Spring Boot,开发一个应用变得非常简单,因为Spring Boot在以上两个方面给了我们强有力的机制。让我们先看一个例子:
pom.xml:
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
Example.java
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
public class Example {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Example.class, args);
}
}
(完整例子参考这里)
上面的这个例子就完成了一个Web服务器的开发。是不是很震撼?前面说过,Spring Boot在依赖解决和自动配置方面做了很多工作,大大简化了开发人员的工作。今天我们重点看自动配置方面,下一篇文章会再聚焦于依赖解决。我们这里所说的配置,主要是Spring的bean配置。
像Spring一样,Spring Boot大量地使用注解来完成配置工作,自动配置的魔法也主要是通过注解来实现的。其中最重要的一个是@EnableAutoConfiguration注解,这个注解会扫描class path, 根据class path当中的类来判断当前应用依赖于哪些组件,进而自动地创建这些组件所需的Bean。
@EnableXXX注解
这一系列的注解并不是Spring Boot才出现的,它们最早在Spring 3就引入了,当时的想法是允许使用注解来替换XML文件对Spring进行配置。
很多的Spring用户已经知道了@EnableTransactionManagement注解,这个注解启用了声明式的事务管理,@EnableWebMvc这个注解启用Spring MVC,以及@EnableScheduling,这个注解会初始化一个定时器。
实际上,这些注解背后实际上不过是使用@Import注解来做了一个简单的configuration 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#loadFactoryNames。 SpringFactoriesLoader会查找包含META-INF/spring.factories文件的jar包。
当它找到这个jar包,SpringFactoriesLoader就会读取其中的spring.factories文件的内容,在其中找以我们的注解为名字结尾的属性。我们讲的注解是EnableAutoConfiguration,那么对应的属性就是
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
在这个文件里面,我们可以看到Srping Boot的auto-configuration的配置项。我们仔细看下这些配置项,比如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类,显著地依赖于Spring 注解来完成他们的任务,比如:
- @ConditionOnClass 当classpath里面有一个或几个指定的类的时候才激活一个配置
- @EnableConfigurationProperties 自动把一个POJO映射到一个Spring Boot配置文件(默认的是application.properties)里面的属性列表。
- @ConditionalOnMissingBean 当一个bean没有创建的时候,创建一个bean
你还可以使用
@AutoConfigureBefore 或者 @AutoConfigureAfter来调整这些配置类加载的顺序。
属性映射
我们再看看MongoProperties,这是个典型的Spring Boot 属性映射的例子:
@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {
private String host;
private int port = DBPort.PORT;
private String uri = "mongodb://localhost/test";
private String database;
// ... getters/ setters omitted
}
@ConfigurationProperties 这个注解会把每个配置属性和一个POJO的某个属性关联起来。例如,spring.data.mongodb.port这个配置属性会被映射到MongoProperties类的port属性。
如果你是一个Spring Boot用户(打算开发自己的Starter),我强烈建议你使用Spring Boot提供的这个属性映射机制,这会减少属性配置相关的boiler plate代码。
@Conditional注解
Spring Boot的强大能力还来自于Spring 4的一个新特性:@Conditional 注解,这个注解会检查条件,只有这个条件满足的时候,才会enable某些配置。
我们只需要瞄一眼 org.springframework.boot.autoconfigure.condition 就大概可以知道以下这些注解大概可以干些啥了:
- @ConditionalOnBean
- @ConditionalOnClass
- @ConditionalOnExpression
- @ConditionalOnMissingBean
- @ConditionalOnMissingClass
- @ConditionalOnNotWebApplication
- @ConditionalOnResource
- @ConditionalOnWebApplication
让我们选择@ConditionalOnExpression来仔细看下。这个注解允许你用Spring Expression来写一个条件:
@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());
}
}
最后,通过ConditionOutcome.isMatch方法,@Conditional注解会被解成一个布尔类型的值。
ApplicationContextInitializer
spring.factories提供的第二种可能,是定义应用初始化器。应用初始化器使得我们能够在应用启动之前来操控spring里面的applicationContext
具体的做法是,他们会使用 ConfigurableApplicationContext#addApplicationListener 方法来创建一个监听context变更的监听器。
对Spring Boot 自动配置进行调试
Spring Boot官方文档有一些有助于理解auto-configuration是怎么做的资料。
当在debug模式下启动时,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利用了Spring 4提供的功能,使得我们可以创建一个自动化配置的可执行的jar包,开发效率大大增加。
别忘了,就像这个文档所说的,你也可以逐步地把自动化的配置改为自己手动声明的bean。
我最喜欢Spring Boot的地方在于它允许你非常快速地创建一个应用的原型系统,同时还能通过它的代码进行学习。自动化配置背后的实现本身包含了相当不错的代码,可以让你学习到Spring的一招两招。
龙之春说过,Spring Boot让你能够与Spring 团队一起“结对编程”
参考资料
Understanding Spring Boot
geowarin.github.io/understandi…
Developing your first Spring Boot application
docs.spring.io/spring-boot…
Using Spring Boot - Replacing Auto-configuration
docs.spring.io/spring-boot…
Spring Boot Reference Guide
docs.spring.io/spring-boot…
Spring Boot - Common application properties
docs.spring.io/spring-boot…