javascript
SpringBoot AutoConfiguration魔术如何工作?
在我以前的文章中, 為什么選擇SpringBoot? 我們已經(jīng)研究了如何創(chuàng)建SpringBoot應(yīng)用程序。 但是您可能會(huì)也可能不會(huì)了解幕后發(fā)生的事情。 您可能想了解SpringBoot自動(dòng)配置背后的魔力。
但是在此之前,您應(yīng)該了解Spring的@Conditional功能,所有SpringBoot的AutoConfiguration魔術(shù)賴以其基礎(chǔ)。
探索@Conditional的力量
在開發(fā)基于Spring的應(yīng)用程序時(shí),我們可能會(huì)遇到有條件地注冊(cè)bean的需求。
例如,您可能想在本地運(yùn)行應(yīng)用程序時(shí)注冊(cè)一個(gè)指向DEV數(shù)據(jù)庫(kù)的DataSource bean,而在生產(chǎn)環(huán)境中運(yùn)行時(shí)則指向一個(gè)不同的PRODUCTION數(shù)據(jù)庫(kù)。
您可以將數(shù)據(jù)庫(kù)連接參數(shù)外部化為屬性文件,并使用適合于環(huán)境的文件。 但是,每當(dāng)需要指向其他環(huán)境并構(gòu)建應(yīng)用程序時(shí),都需要更改配置。
為了解決這個(gè)問(wèn)題,Spring 3.1引入了Profiles的概念。 您可以注冊(cè)多個(gè)相同類型的bean,并將它們與一個(gè)或多個(gè)概要文件關(guān)聯(lián)。 當(dāng)您運(yùn)行該應(yīng)用程序時(shí),您可以激活所需的概要文件,并且僅與激活的概要文件關(guān)聯(lián)的bean將被注冊(cè)。
@Configuration public class AppConfig {@Bean@Profile("DEV")public DataSource devDataSource() {...}@Bean@Profile("PROD")public DataSource prodDataSource() {...} }然后,您可以使用系統(tǒng)屬性-Dspring.profiles.active = DEV指定活動(dòng)配置文件
這種方法適用于簡(jiǎn)單的情況,例如基于激活的配置文件啟用或禁用bean注冊(cè)。 但是,如果您要基于某些條件邏輯來(lái)注冊(cè)bean,那么Profiles方法本身是不夠的。
為了為有條件地注冊(cè)Spring Bean提供更大的靈活性,Spring 4引入了@Conditional的概念。 通過(guò)使用@Conditional方法,您可以根據(jù)任意條件有條件地注冊(cè)bean。
例如,在以下情況下,您可能要注冊(cè)一個(gè)bean:
- 一個(gè)特定的類存在于類路徑中
- 某種類型的Spring bean尚未在ApplicationContext中注冊(cè)
- 特定文件存在于某個(gè)位置
- 在配置文件中配置特定的屬性值
- 存在/不存在特定的系統(tǒng)屬性
這些僅是幾個(gè)示例,您可以具有所需的任何條件。
讓我們看一下Spring的@Conditional的工作原理。
假設(shè)我們有一個(gè)UserDAO接口,其中包含從數(shù)據(jù)存儲(chǔ)中獲取數(shù)據(jù)的方法。 我們UserDAO的接口即JdbcUserDAO兩種工具進(jìn)行對(duì)話的MySQL數(shù)據(jù)庫(kù)和MongoUserDAO進(jìn)行對(duì)話的MongoDB的 。
我們可能只想基于系統(tǒng)屬性dbType啟用JdbcUserDAO和MongoUserDAO之一。
如果使用java -jar myapp.jar -DdbType = MySQL啟動(dòng)應(yīng)用程序,則我們要啟用JdbcUserDAO ;否則,如果使用java -jar myapp.jar -DdbType = MONGO啟動(dòng)應(yīng)用程序, 則要啟用MongoUserDAO 。
假設(shè)我們有UserDAO接口和JdbcUserDAO , MongoUserDAO實(shí)現(xiàn)如下:
public interface UserDAO {List<String> getAllUserNames(); }public class JdbcUserDAO implements UserDAO {@Overridepublic List<String> getAllUserNames(){System.out.println("**** Getting usernames from RDBMS *****");return Arrays.asList("Siva","Prasad","Reddy");} }public class MongoUserDAO implements UserDAO {@Overridepublic List<String> getAllUserNames(){System.out.println("**** Getting usernames from MongoDB *****");return Arrays.asList("Bond","James","Bond");} }我們可以實(shí)現(xiàn)條件MySQLDatabaseTypeCondition來(lái)檢查系統(tǒng)屬性dbType是否為“ MYSQL” ,如下所示:
public class MySQLDatabaseTypeCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata){String enabledDBType = System.getProperty("dbType");return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MYSQL"));} }我們可以實(shí)現(xiàn)條件MongoDBDatabaseTypeCondition來(lái)檢查系統(tǒng)屬性dbType是否為“ MONGODB ”,如下所示:
public class MongoDBDatabaseTypeCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata){String enabledDBType = System.getProperty("dbType");return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MONGODB"));} }現(xiàn)在,我們可以使用@Conditional有條件地配置JdbcUserDAO和MongoUserDAO Bean,如下所示:
@Configuration public class AppConfig {@Bean@Conditional(MySQLDatabaseTypeCondition.class)public UserDAO jdbcUserDAO(){return new JdbcUserDAO();}@Bean@Conditional(MongoDBDatabaseTypeCondition.class)public UserDAO mongoUserDAO(){return new MongoUserDAO();} }如果我們像java -jar myapp.jar -DdbType = MYSQL那樣運(yùn)行應(yīng)用程序,則僅JdbcUserDAO bean將被注冊(cè)。 但是,如果您-DdbType = MONGODB,則僅MongoUserDAO Bean將被注冊(cè)。
現(xiàn)在,我們已經(jīng)看到了如何基于系統(tǒng)屬性有條件地注冊(cè)bean。
假設(shè)我們希望,注冊(cè)MongoUserDAO豆只有當(dāng)MongoDB的 Java驅(qū)動(dòng)程序類“com.mongodb.Server”可在類路徑中,如果不是我們想注冊(cè)JdbcUserDAO豆。
為此,我們可以創(chuàng)建條件來(lái)檢查MongoDB驅(qū)動(dòng)程序類“ com.mongodb.Server”的存在與否,如下所示:
public class MongoDriverPresentsCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata){try {Class.forName("com.mongodb.Server");return true;} catch (ClassNotFoundException e) {return false;}} }public class MongoDriverNotPresentsCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata){try {Class.forName("com.mongodb.Server");return false;} catch (ClassNotFoundException e) {return true;}} }我們剛剛看到了如何根據(jù)類路徑中是否存在類來(lái)有條件地注冊(cè)bean。
如果僅在尚未注冊(cè)其他類型為UserDAO的 Spring Bean的情況下才想注冊(cè)MongoUserDAO Bean,該怎么辦?
我們可以創(chuàng)建一個(gè)條件來(lái)檢查是否存在某種特定類型的現(xiàn)有bean,如下所示:
public class UserDAOBeanNotPresentsCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata){UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);return (userDAO == null);} }如果僅在屬性占位符配置文件中設(shè)置屬性app.dbType = MONGO時(shí)才想注冊(cè)MongoUserDAO bean,該怎么辦 ?
我們可以如下實(shí)現(xiàn)該條件:
public class MongoDbTypePropertyCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata){String dbType = conditionContext.getEnvironment().getProperty("app.dbType");return "MONGO".equalsIgnoreCase(dbType);} }我們剛剛看到了如何實(shí)現(xiàn)各種類型的條件。 但是,還有使用注釋來(lái)實(shí)現(xiàn)條件的更優(yōu)雅的方法。 除了為MYSQL和MongoDB創(chuàng)建Condition實(shí)現(xiàn)之外,我們還可以創(chuàng)建一個(gè)DatabaseType注釋,如下所示:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(DatabaseTypeCondition.class) public @interface DatabaseType {String value(); }然后,我們可以實(shí)現(xiàn)DatabaseTypeCondition以使用DatabaseType值來(lái)確定是啟用還是禁用bean注冊(cè),如下所示:
public class DatabaseTypeCondition implements Condition {@Overridepublic 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));} }現(xiàn)在,我們可以在bean定義上使用@DatabaseType批注,如下所示:
@Configuration @ComponentScan public class AppConfig {@DatabaseType("MYSQL")public UserDAO jdbcUserDAO(){return new JdbcUserDAO();}@Bean@DatabaseType("MONGO")public UserDAO mongoUserDAO(){return new MongoUserDAO();} }在這里,我們從DatabaseType批注中獲取元數(shù)據(jù),并對(duì)照System Property dbType值進(jìn)行檢查,以確定是啟用還是禁用Bean注冊(cè)。
我們已經(jīng)看到了很多示例,以了解如何使用@Conditional批注有條件地注冊(cè)bean。
SpringBoot廣泛使用@Conditional功能根據(jù)各種條件有條件地注冊(cè)bean。
您可以在spring-boot-autoconfigure- {version} .jar的 org.springframework.boot.autoconfigure包中找到SpringBoot使用的各種Condition實(shí)現(xiàn)。
現(xiàn)在,我們了解了SpringBoot如何使用@Conditional功能有條件地檢查是否注冊(cè)Bean。 但是究竟是什么觸發(fā)了自動(dòng)配置機(jī)制呢?
這就是我們將在下一部分中討論的內(nèi)容。
SpringBoot自動(dòng)配置
SpringBoot自動(dòng)配置魔術(shù)的關(guān)鍵是@EnableAutoConfiguration批注。 通常,我們使用@SpringBootApplication注釋應(yīng)用程序入口點(diǎn)類,或者如果要自定義默認(rèn)值,則可以使用以下注釋:
@Configuration @EnableAutoConfiguration @ComponentScan public class Application {}@EnableAutoConfiguration批注通過(guò)掃描類路徑組件并注冊(cè)與各種條件匹配的bean來(lái)啟用Spring ApplicationContext的自動(dòng)配置。
SpringBoot在spring-boot-autoconfigure- {version} .jar中提供了各種AutoConfiguration類,這些類負(fù)責(zé)注冊(cè)各種組件。
通常, AutoConfiguration類使用@Configuration注釋,以將其標(biāo)記為Spring配置類,并使用@EnableConfigurationProperties注釋,以綁定定制屬性和一個(gè)或多個(gè)條件Bean注冊(cè)方法。
例如,考慮org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration類。
@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 {@Beanpublic DataSourceInitializer dataSourceInitializer() {return new DataSourceInitializer();}}@Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })protected static class NonEmbeddedConfiguration {@Autowiredprivate 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 {@Beanpublic Object dataSourceMBean(DataSource dataSource) {........}}...... }此處, DataSourceAutoConfiguration帶有@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})注釋,這意味著僅當(dāng)在類路徑上有DataSource.class和EmbeddedDatabaseType.class類可用時(shí),才會(huì)考慮在DataSourceAutoConfiguration中對(duì)bean進(jìn)行自動(dòng)配置。
該類還帶有@EnableConfigurationProperties(DataSourceProperties.class)批注,該啟用了自動(dòng)將application.properties中的屬性綁定到DataSourceProperties類的屬性的功能。
@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 }使用此配置,所有以spring.datasource。*開頭的屬性都將自動(dòng)綁定到DataSourceProperties對(duì)象。
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您還可以看到一些內(nèi)部類和bean定義方法,這些內(nèi)部類和bean定義方法用SpringBoot的條件注釋(例如@ ConditionalOnMissingBean,@ ConditionalOnClass和@ConditionalOnProperty等)進(jìn)行注釋。
僅當(dāng)這些條件匹配時(shí),這些Bean定義才會(huì)在ApplicationContext中注冊(cè)。
您還可以在spring-boot-autoconfigure- {version} .jar中探索許多其他AutoConfiguration類,例如
- 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.JacksonAutoConfiguration等。
我希望您現(xiàn)在通過(guò)使用各種AutoConfiration類以及@Conditional功能來(lái)了解SpringBoot自動(dòng)配置的工作方式。
翻譯自: https://www.javacodegeeks.com/2016/03/springboot-autoconfiguration-magic-works.html
總結(jié)
以上是生活随笔為你收集整理的SpringBoot AutoConfiguration魔术如何工作?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: dns服务器的配置与管理
- 下一篇: 与Spring和Maven签订合约优先S