在 《Spring Boot Hello World》 中介绍了一个简单的spring boot例子,体验了spring boot中的诸多特性,其中的自动配置特性极大的简化了程序开发中的工作(不用写一行XML)。本文我们就来看一下spring boot是如何做到 自动配置 的。
首先阐明,spring boot的自动配置是基于spring framework提供的特性实现的,所以在本文中,我们先介绍spring framework的相关特性,在了解了这些基础知识后,我们再来看spring boot的自动配置是如何实现的。
在以往使用spring framework进行程序开发时,相信大家也只是使用XML搭配注解的方式对spring容器进行配置,例如在XML文件中使用 <context:component-scan base-package="**"/>
指定spring需要扫描package的根路径。
除了使用XML对spring进行配置,还可以使用Java代码执行完全相同的配置。下面我们详细看一下如何使用Java代码对spring容器进行配置,详细内容可参考 这里 。
使用Java代码进行spring配置,有两个核心注解 @Configuration
和 @Bean
:
@Configuration public class AppConfig { @Bean public SampleService sampleService() { return new SampleServiceImpl(); } }
@Bean
注解用于修饰方法,方法的返回值会作为一个bean装载到spring容器中。bean的id就是方法的名字。
@Configuration
注解用于修饰一个类,它表明这个类的作用是用来对spring容器进行配置的。
上面的Java代码相当于下面的XML配置:
<beans> <bean id="sampleService" class="com.**.SampleServiceImpl"/> </beans>
使用AnnotationConfigApplicationContext类构建一个spring容器,从容器中取出对应的bean的测试代码如下:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); SampleService myService = ctx.getBean("sampleService" ,SampleService.class); myService.doService(); }
使用 @ComponentScan
注解指定需要扫描package的根路径:
@Configuration @ComponentScan(basePackages = "com.**.service.impl") public class AppConfig { }
上面的Java代码相当于下面的XML配置:
<beans> <context:component-scan base-package="com.**.service.impl"/> </beans>
此外,AnnotationConfigApplicationContext类还提供了scan方法用于指定要扫描的包路径。我们可以删除AppConfig类上的 @ComponentScan
注解,在构造spring容器时使用下面代码:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.**.service.impl"); ctx.refresh(); SampleService myService = ctx.getBean("sampleService" ,SampleService.class); myService.doService(); }
将所有的spring配置全部放在同一个类中肯定是不合适的,这会导致那个配置类非常复杂。通常会创建多个配置类,再借助@Import将多个配置类组合成一个。@Import的功能类似于XML中的 <import/>
。
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
上面的代码分别创建了两个配置类ConfigA和ConfigB,它们分别定义了a和b两个Bean。在ConfigB上使用 @Import
注解导入ConfigA的配置,此时应用代码如果加载ConfigB的配置,就自动也加载了ConfigA的配置。如下代码所示:
public static void main(String[] args) { // 只加载ConfigB一个配置类,但同时也包含了ConfigA的配置 ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); System.out.println(a); System.out.println(b); }
@Import还可以同时导入多个配置类。当有多个配置类需要同时导入时,示意代码如下:
@Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } }
@Conditional注解根据某一个条件是否成立来判断是否构建Bean。借助Condition接口可以表示一个特定条件。例如下面代码实现了一个条件,当然这个条件始终成立:
public class SampleCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // 如果条件成立返回true, 反之返回false return true; } }
有了表达条件的类SampleCondition,接下来我们就可以通过@Conditional注解对创建bean的函数进行配置:
请输入代码@Configuration public class ConditionConfig { // 只有当满足SampleCondition指定的条件时,参会构造id时sampleBean这个bean。当然这里的条件始终成立 @Conditional(SampleCondition.class) @Bean public SampleBean sampleBean() { return new SampleBean(); } }
由于SampleCondition的matches方法返回true,表示创建bean的条件成立,所以sampleBean会被创建。如果matches返回false,sampleBean就不会被构建。
在spring boot中,根据这个原理提供了很多@ConditionOnXXX的注解,这些注解都在包org.springframework.boot.autoconfigure.condition下面。例如比较常见的@ConditionalOnClass注解,这个注解的判断逻辑是只有指定的某个类在classpath上存在时,判断条件才成立。@ConditionalOnClass的具体代码如下:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { Class<?>[] value() default {}; String[] name() default {}; }
@ConditionalOnClass具体的判断逻辑可参看OnClassCondition类。
介绍完前面这些基础的知识后,我们来看Spring Boot是如何实现自动装配的。
《Spring Boot官方文档第14章》 推荐在程序的main class上使用注解@SpringBootApplication对Spring应用进行自动配置,我们就从分析这个注解开始。下面是@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 { // ...
@SpringBootApplication是一个组合注解,主要由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解构成。
@SpringBootConfiguration表明被标注的类是提供了Spring Boot应用的配置,其实这个注解与@Configuration注解的功能类似。
@ComponentScan指定需要扫描package的路径,@SpringBootApplication也提供了相应属性,指定需要扫描哪些package或不扫描哪些package。《Spring Boot官方文档》建议将应用的main class放置于整个工程的根路径,并用@SpringBootApplication注解修饰main class,这样整个项目的子package就都会被自动扫描包含。建议的工程结构如下所示,其中Application就是应用的main class。
com +- example +- myapplication +- Application.java | +- customer | +- Customer.java | +- CustomerController.java | +- CustomerService.java | +- CustomerRepository.java | +- order +- Order.java +- OrderController.java +- OrderService.java +- OrderRepository.java
@EnableAutoConfiguration是这里最重要的注解,它实现了对Spring Boot应用自动装配的功能。@EnableAutoConfiguration是利用 SpringFactoriesLoader 机制加载自动装配配置的,它的配置数据在META-INF/spring.factories中,我们打开spring-boot-autoconfigure jar中的该文件,发现EnableAutoConfiguration对应着N多 * AutoConfiguration配置类,我们截取几个重要的配置类如下(已经删除了很多):
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,/ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,/ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,/ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,/ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,/ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,/ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,/ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,/ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,/ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,/ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,/ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,/ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,/ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,/ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,/ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,/ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,/ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,/ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,/ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,/ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,/ org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,/ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,/ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,/ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,/ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,/ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,/ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,/ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,/ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,/ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,/ org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,/ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,/ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,/ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,/ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,/ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,/ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,/ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,/ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,/ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,/ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,/ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,/ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,/ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,/ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,/ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,/ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,/ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,/ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,/ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,/ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,/ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,/ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,/ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,/ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,/ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,/ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,/ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,/ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,/
可以看到Spring Boot已经写好了N多 * AutoConfiguration类,有Spring Framework的、Web的、redis的、JDBC的等等。
我们从其中选择HttpEncodingAutoConfiguration这个类来看下它是如何实现自动配置的:
@Configuration @EnableConfigurationProperties(HttpProperties.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(CharacterEncodingFilter.class) @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { private final HttpProperties.Encoding properties; public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; }
上面代码表示,只有在满足如下条件时,才会注入characterEncodingFilter这个bean: