原文地址:https://dzone.com/articles/how-springboot-autoconfiguration-magic-works

In my previous post "Why Spring Boot?", we looked at how to create a Spring Boot application, but you may or may not understand what is going on behind the scenes. You may want to understand the magic behind Spring Boot’s AutoConfiguration.

Before that, you should know about Spring’s @Conditional feature, on which all Spring Boot’s AutoConfiguration magic depends.

Exploring the Power of @Conditional

While developing Spring based applications we may come across a need to register beans conditionally.

For example, you may want to register a DataSource bean pointing to the dev database while running application locally and point to a different production database while running in production.

You can externalize the database connection parameters into the properties file and use the file appropriate for the environment, but you need to change the configuration whenever you need to point to a different environment and build the application.

To address this problem, Spring 3.1 introduced the concept of Profiles. You can register multiple beans of the same type and associate them with one or more profiles. When you run the application you can activate the desired profiles and beans associated with the activated profiles, and only those profiles will be registered.

 
@Configuration
 
public class AppConfig
 
{
 
 @Bean
 
 @Profile("DEV")
 
 public DataSource devDataSource() {
 
 ...
 
 }
 
 
 @Bean
 
 @Profile("PROD")
 
 public DataSource prodDataSource() {
 
 ...
 
 }
 
}
 

Then you can specify the active profile using System Property -Dspring.profiles.active=DEV.

This approach works for simple cases like enable or disable bean registrations based on activated profiles. But if you want to register beans based on some conditional logic then the profiles approach itself is not sufficient.

To provide more flexibility for registering Spring beans conditionally, Spring 4 introduced the concept of @Conditional. By using the @Conditional approach you can register a bean conditionally based on any arbitrary condition.

For example, you may want to register a bean when:

  • A specific class is present in classpath
  • A Spring bean of certain type doesn’t already registered in ApplicationContext
  • A specific file exists on a location
  • A specific property value is configured in a configuration file
  • A specific system property is present/absent

These are just a few examples only and you can have any condition you want.

Let us take a look at how Spring’s @Conditional works.

Suppose we have a UserDAO interface with methods to get data from a data store. We have two implements of UserDAO interface namely JdbcUserDAO which talks to MySQL database andMongoUserDAO which talks to MongoDB.

We may want to enable only one interface of JdbcUserDAO and MongoUserDAO based on a System Property, say dbType.

If the application is started using java -jar myapp.jar -DdbType=MySQL, then we want to enableJdbcUserDAO. Otherwise, if the application is started using java -jar myapp.jar -DdbType=MONGO, we want to enable MongoUserDAO.

Suppose we have a UserDAO bean and a JdbcUserDAO bean. The MongoUserDAO implementation is as follows:

 
public interface UserDAO
 
{
 
 List<String> getAllUserNames();
 
}
 
 
public class JdbcUserDAO implements UserDAO
 
{
 
 @Override
 
 public List<String> getAllUserNames()
 
 {
 
 System.out.println("**** Getting usernames from RDBMS *****");
 
 return Arrays.asList("Siva","Prasad","Reddy");
 
 }
 
}
 
 
public class MongoUserDAO implements UserDAO
 
{
 
 @Override
 
 public List<String> getAllUserNames()
 
 {
 
 System.out.println("**** Getting usernames from MongoDB *****");
 
 return Arrays.asList("Bond","James","Bond");
 
 }
 
}
 

We can implement the Condition MySQLDatabaseTypeCondition to check whether the System Property dbType is "MYSQL" as follows:

 
public class MySQLDatabaseTypeCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
 
 {
 
 String enabledDBType = System.getProperty("dbType");
 
 return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MYSQL"));
 
 }
 
}
 

We can implement the Condition MongoDBDatabaseTypeCondition to check whether the System Property dbType is "MONGODB" as follows:

 
public class MongoDBDatabaseTypeCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
 
 {
 
 String enabledDBType = System.getProperty("dbType");
 
 return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MONGODB"));
 
 }
 
}
 

Now we can configure both JdbcUserDAO and MongoUserDAO beans conditionally using@Conditional as follows:

 
@Configuration
 
public class AppConfig
 
{
 
 @Bean
 
 @Conditional(MySQLDatabaseTypeCondition.class)
 
 public UserDAO jdbcUserDAO(){
 
 return new JdbcUserDAO();
 
 }
 
 
 @Bean
 
 @Conditional(MongoDBDatabaseTypeCondition.class)
 
 public UserDAO mongoUserDAO(){
 
 return new MongoUserDAO();
 
 }
 
}
 

If we run the application like java -jar myapp.jar -DdbType=MYSQL then only the JdbcUserDAO bean will be registered.But if you set the System property like -DdbType=MONGODB then only the MongoUserDAO bean will be registered.

Now that we have seen how to conditionally register a bean based on System Property.

Suppose we want to register MongoUserDAO bean only when MongoDB java driver class"com.mongodb.Server" is available on classpath, if not we want to register JdbcUserDAO bean.

To accomplish that we can create Conditions to check the presence or absence of MongoDB driver class "com.mongodb.Server" as follows:

 
public class MongoDriverPresentsCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata)
 
 {
 
 try {
 
 Class.forName("com.mongodb.Server");
 
 return true;
 
 } catch (ClassNotFoundException e) {
 
 return false;
 
 }
 
 }
 
}
 
 
public class MongoDriverNotPresentsCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
 
 {
 
 try {
 
 Class.forName("com.mongodb.Server");
 
 return false;
 
 } catch (ClassNotFoundException e) {
 
 return true;
 
 }
 
 }
 
}
 

We just have seen how to register beans conditionally based on the presence or absence of a class in classpath.

What if we want to register the MongoUserDAO bean only if no other Spring bean of the typeUserDAO is already registered.

We can create a Condition to check if there is any existing bean of a certain type as follows:

 
public class UserDAOBeanNotPresentsCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
 
 {
 
 UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);
 
 return (userDAO == null);
 
 }
 
}
 

What if we want to register MongoUserDAO bean only if property app.dbType=MONGO is set in properties placeholder configuration file?

We can implement that Condition as follows:

 
public class MongoDbTypePropertyCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext,
 
 AnnotatedTypeMetadata metadata)
 
 {
 
 String dbType = conditionContext.getEnvironment()
 
 .getProperty("app.dbType");
 
 return "MONGO".equalsIgnoreCase(dbType);
 
 }
 
}
 

We have just seen how to implement various types of Conditions.But there is even more elegant way to implement Conditions using Annotations. Instead of creating a Condition implementation for both MYSQL and MongoDB, we can create aDatabaseType annotation as follows:

 
@Target({ ElementType.TYPE, ElementType.METHOD })
 
@Retention(RetentionPolicy.RUNTIME)
 
@Conditional(DatabaseTypeCondition.class)
 
public @interface DatabaseType
 
{
 
 String value();
 
}
 

Then we can implement DatabaseTypeCondition to use the DatabaseType value to determine whether to enable or disable bean registration as follows:

 
public class DatabaseTypeCondition implements Condition
 
{
 
 @Override
 
 public boolean matches(ConditionContext conditionContext,
 
 AnnotatedTypeMetadata metadata)
 
 {
 
 Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName());
 
 String type = (String) attributes.get("value");
 
 String enabledDBType = System.getProperty("dbType","MYSQL");
 
 return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));
 
 }
 
}
 

Now we can use the @DatabaseType annotation on our bean definitions as follows:

 
@Configuration
 
@ComponentScan
 
public class AppConfig
 
{
 
 @DatabaseType("MYSQL")
 
 public UserDAO jdbcUserDAO(){
 
 return new JdbcUserDAO();
 
 }
 
 
 @Bean
 
 @DatabaseType("MONGO")
 
 public UserDAO mongoUserDAO(){
 
 return new MongoUserDAO();
 
 }
 
}
 

Here we are getting the metadata from DatabaseType annotation and checking against the System Property dbType value to determine whether to enable or disable the bean registration.

We have seen good number of examples to understand how we can register beans conditionally using@Conditional annotation.

Spring Boot extensively uses @Conditional feature to register beans conditionally based on various criteria.

You can find various Condition implementations that SpringBoot uses inorg.springframework.boot.autoconfigure package of spring-boot-autoconfigure-{version}.jar.

Now that we've come to know about how Spring Boot uses the @Conditional feature to conditionally check whether to register a bean or not, but what exactly triggers the auto-configuration mechanism?

This is what we are going to look at in the next section.

Spring Boot AutoConfiguration

The key to the Spring Boot’s auto-configuration magic is @EnableAutoConfiguration annotation. Typically we annotate our Application entry point class with either @SpringBootApplication or if we want to customize the defaults we can use the following annotations:

 
@Configuration
 
@EnableAutoConfiguration
 
@ComponentScan
 
public class Application
 
{
 
 
}
 

The @EnableAutoConfiguration annotation enables the auto-configuration of SpringApplicationContext by scanning the classpath components and registers the beans that are matching various Conditions.

SpringBoot provides various AutoConfiguration classes in spring-boot-autoconfigure-{version}.jar, which are responsible for registering various components.

Typically AutoConfiguration classes are annotated with @Configuration to mark it as a Spring configuration class and annotated with @EnableConfigurationProperties to bind the customization properties and one or more Conditional bean registration methods.

For example, consider the org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration class.

 
@Configuration
 
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
 
@EnableConfigurationProperties(DataSourceProperties.class)
 
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
 
public class DataSourceAutoConfiguration 
 
{
 
 ...
 
 ...
 
 @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class)
 
 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
 
 @Import(EmbeddedDataSourceConfiguration.class)
 
 protected static class EmbeddedConfiguration {
 
 
 }
 
 
 @Configuration
 
 @ConditionalOnMissingBean(DataSourceInitializer.class)
 
 protected static class DataSourceInitializerConfiguration {
 
 @Bean
 
 public DataSourceInitializer dataSourceInitializer() {
 
 return new DataSourceInitializer();
 
 }
 
 }
 
 
 @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)
 
 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
 
 protected static class NonEmbeddedConfiguration {
 
 @Autowired
 
 private DataSourceProperties properties;
 
 
 @Bean
 
 @ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
 
 public DataSource dataSource() {
 
 DataSourceBuilder factory = DataSourceBuilder
 
 .create(this.properties.getClassLoader())
 
 .driverClassName(this.properties.getDriverClassName())
 
 .url(this.properties.getUrl()).username(this.properties.getUsername())
 
 .password(this.properties.getPassword());
 
 if (this.properties.getType() != null) {
 
 factory.type(this.properties.getType());
 
 }
 
 return factory.build();
 
 }
 
 }
 
 ...
 
 ...
 
 @Configuration
 
 @ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled")
 
 @ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy")
 
 @Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
 
 @ConditionalOnMissingBean(name = "dataSourceMBean")
 
 protected static class TomcatDataSourceJmxConfiguration {
 
 @Bean
 
 public Object dataSourceMBean(DataSource dataSource) {
 
 ....
 
 ....
 
 }
 
 }
 
 ...
 
 ...
 
}
 

Here, DataSourceAutoConfiguration is annotated with @ConditionalOnClass({ DataSource.class,EmbeddedDatabaseType.class }) which means that the AutoConfiguration of beans within DataSourceAutoConfiguration will be considered only if the DataSource.class and EmbeddedDatabaseType.class classes are available on classpath.

The class is also annotated with@EnableConfigurationProperties(DataSourceProperties.class) which enables binding the properties in application.properties to the properties of DataSourceProperties class automatically.

 
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
 
public class DataSourceProperties implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {
 
 
 public static final String PREFIX = "spring.datasource";
 
 ...
 
 ...
 
 private String driverClassName;
 
 private String url;
 
 private String username;
 
 private String password;
 
 ...
 
 //setters and getters
 
}
 

With this configuration all the properties that starts with spring.datasource.* will be automatically binds to DataSourceProperties object.

 
spring.datasource.url=jdbc:mysql://localhost:3306/test
 
spring.datasource.username=root
 
spring.datasource.password=secret
 
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
 

You can also see some inner classes and bean definition methods that are annotated with SpringBoot’s Conditional annotations such as @ConditionalOnMissingBean, @ConditionalOnClass and @ConditionalOnProperty etc.

These bean definitions will be registered in ApplicationContext only if those conditions are matched.

You can also explore many other AutoConfiguration classes in spring-boot-autoconfigure-{version}.jar such as:

  • org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
  • org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
  • org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
  • org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationetc etc.

I hope now you have an understanding of how Spring Boot auto-configuration works by using various AutoConfiration classes along with @Conditional features.

How Spring Boot Autoconfiguration Magic Works--转的更多相关文章

  1. 了解 Spring Boot AutoConfiguration

    原文:http://sivalabs.in/2016/03/how-springboot-autoconfiguration-magic/ 作者:Siva 译者:http://oopsguy.com ...

  2. Spring Boot AutoConfiguration注解@ConditionalXXXX之前生今世

    1.注解@Conditional的定义 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHO ...

  3. Spring boot 官网学习笔记 - Auto-configuration(@SpringBootApplication、@EnableAutoConfiguration、@Configuration)

    Spring Boot auto-configuration attempts to automatically configure your Spring application based on ...

  4. Spring Boot文档阅读

    原因之初 最初习惯百度各种博客教程,然后跟着操作,因为觉得跟着别人走过的路走可以少走很多弯路,省时间.然而,很多博客的内容并不够完整,甚至错误,看多了的博客甚至有千篇一律的感觉.此外,博客毕竟是记载博 ...

  5. Spring Boot Reference Guide

    Spring Boot Reference Guide Authors Phillip Webb, Dave Syer, Josh Long, Stéphane Nicoll, Rob Winch,  ...

  6. Spring Boot文档

    本文来自于springboot官方文档 地址:https://docs.spring.io/spring-boot/docs/current/reference/html/ Spring Boot参考 ...

  7. Spring boot 内存优化

    转自:https://dzone.com/articles/spring-boot-memory-performance It has sometimes been suggested that Sp ...

  8. Spring Boot Memory Performance

    The Performance Zone is brought to you in partnership with New Relic. Quickly learn how to use Docke ...

  9. Spring Boot面试题

    Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家. 问题一 Spring Boot.Spring MVC 和 Spring 有什么区别 ...

随机推荐

  1. android wifi Direct Audio TX/RX延迟分析

    1 Direct Audio TX代码流程 1.1 从Host到FW 1.1.1 代码流程 htc.c::HifLayerRecvCallback//从HIF_USB_CONTEXT获取数据中断,具体 ...

  2. C++ 非阻塞套接字的使用 (3)

    异步非阻塞套接字避免了死循环的接收问题,但是软件用起来体验还是很差.究其原因,软件在指令的发送.接收上, 采取了一种不合理的方式:在指令的发送后,立刻调用接收函数,等待回令. 若是采用同步阻塞套接字, ...

  3. NFS挂载操作指南

    NFS 全称 network file system,其功能是实现将某台服务器的某个目录下资源共享给其他服务器.被共享的服务器作为nfs服务端,需要开启和配置nfs server服务.共享他人资源的服 ...

  4. linux安装oracle 11g rac

    安装oracle 11gR2 RAC 一.网络规划及安装虚拟主机 主机名 主机版本 Ip rac1.localdomain Redhat 6.5 RAC节点1 192.168.100.11 rac2. ...

  5. MyEclipse 常用操作

    1.使用JREBEL插件包实现myeclipse修改类文件后无须重启 在Myeclipse中的window-preferences(搜索tomcat)->然后到tomcatx.x下的-jdk中配 ...

  6. Java设计模式10:观察者模式

    观察者模式 观察者模式也叫作发布-订阅模式,也就是事件监听机制.观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态上发生变化时,会通知所有观察者对象,使他 ...

  7. DBCP连接池使用问题

    问题现象: 启动应用,访问无压力,一切正常,一段时间过后,应用访问异常. 问题分析: 1.web容器线程爆满,拒绝服务.由于应用并发量大,线程响应时间长增加,线程池连接数逐步递增直到爆满,导致应用拒绝 ...

  8. Java提高篇(三五)-----Java集合细节(一):请为集合指定初始容量

    集合是我们在Java编程中使用非常广泛的,它就像大海,海纳百川,像万能容器,盛装万物,而且这个大海,万能容器还可以无限变大(如果条件允许).当这个海.容器的量变得非常大的时候,它的初始容量就会显得很重 ...

  9. 【案例分享】SQL job服务连接不到存储引擎

    在SQL Server agent的日志中发现有大量错误的信息,都是关于作业执行失败的日志 从日志的内容来看,是作业执行过程中无法连接到存储引擎, 可奇怪的是,存储引擎就是本机,而且已经正常启动了,截 ...

  10. Redis教程(十一):虚拟内存介绍:

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/138.html 一.简介: 和大多NoSQL数据库一样,Redis同样遵循 ...