SpringBoot憑藉"約定大於配置"的理念,已經成爲最流行的web開發框架,因此有必須對其進行深刻的瞭解;本文經過整合Mybatis類來分析SpringBoot提供的自動配置(AutoConfigure)功能,在此以前首先看一個整合Mybatis的實例。java
提供SpringBoot整合Mybatis的實例,經過Mybatis實現簡單的增刪改查功能;mysql
CREATE TABLE `role` ( `note` varchar(255) CHARACTER SET utf8 DEFAULT NULL, `role_name` varchar(255) DEFAULT NULL, `id` bigint(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8
提供建立role表相關的sql,對錶進行增刪改查操做;git
主要是mybatis-spring-boot-starter和使用的mysql驅動:github
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency>
提供鏈接mysql相關的信息:url,驅動,用戶名,密碼;web
spring.datasource.url=jdbc:mysql://localhost/mybatis spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
分別提供表對應的bean類和操做數據庫的dao類;spring
public class Role { private long id; private String roleName; private String note; //省略get/set方法 }
@Mapper public interface RoleDao { @Select("SELECT id,role_name as roleName,note FROM role WHERE id = #{id}") Role findRoleById(@Param("id") long id); }
public interface RoleService { public Role findRoleById(long roleId); } @Service public class RoleServiceImpl implements RoleService { @Autowired private RoleDao roleDao; @Override public Role findRoleById(long roleId) { return roleDao.findRoleById(roleId); } }
@RestController public class RoleController { @Autowired private RoleService roleService; @RequestMapping("/role") public String getRole(long id) { return roleService.findRoleById(id).toString(); } }
啓動服務,進行簡單的測試:http://localhost:8888/role?id=111
結果以下:sql
Role [id=111, roleName=zhaohui, note=hello]
如上實例中,咱們使用了不多的配置,就經過mybatis實現了操做數據庫;正常使用mybatis須要的SqlSessionFactory和SqlSession沒有看到被實例化,同時mybatis依賴的數據源也沒有看到被引用,那SpringBoot是如何幫咱們自動配置的,下面重點分析一下;數據庫
要想使用自動配置功能,SpringBoot提供了註解@EnableAutoConfiguration,固然不須要咱們配置由於在@SpringBootApplication註解中默認以及啓用了;apache
@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註解自己也有註解@EnableAutoConfiguration:瀏覽器
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { //...省略... }
在註解@EnableAutoConfiguration中重點看一下@Import註解中使用的AutoConfigurationImportSelector類,此類是自動註解的核心類,會有條件的加載咱們默認指定的配置類;這裏有兩個概念一個是有條件,一個是配置類,分別簡單介紹一下:配置類能夠簡單理解就是相關組件對接SpringBoot的對接類,此類能夠作一些初始化的工做;有條件表示並非有配置類就能被對接上,是有條件的,SpringBoot默認提供了大量配置類,但並非全部配置類都能被加載初始化的,是有條件的,好比mybatis在沒有數據源的狀況下,沒有mybatis基礎包的狀況下是不能被對接的;下面首先看一下SpringBoot提供的哪些條件類;
SpringBoot提供了不少條件類,能夠在配置中上配置註解條件類,相關條件類能夠在spring-boot-autoconfigure包下的org.springframework.boot.autoconfigure.condition下找到,主要包含以下:
以上是註解類,註解自己沒有功能,只是提供標記的功能,具體功能在@Conditional中指定的,好比ConditionalOnBean註解以下所示:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { //...省略... }
相關功能的實現就在OnBeanCondition類中,一樣其餘註解類的實現類也在包org.springframework.boot.autoconfigure.condition下找到;
Springboot應用啓動過程當中使用ConfigurationClassParser分析配置類,此類中有一個processImports方法,此方法用來處理@Import註解,在@EnableAutoConfiguration註解存在@Import註解,這時候會實例化註解中的AutoConfigurationImportSelector,在其內部有一個AutoConfigurationGroup內部類,內部類有兩個核心方法分別是:process和selectImports;
@Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } }
此方法主要獲取通過條件過濾以後可用的自動配置類,主要調用AutoConfigurationImportSelector中的getAutoConfigurationEntry完成的:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
首先獲取了全部備選的自動配置類,而後刪除了重複和被排除的類,最後經過條件進行篩選出可用的配置類,下面分別看一下,首先看一下如何獲取全部備選的配置類:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
經過SpringFactoriesLoader獲取類路徑下META-INF/spring.factories文件中key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置類,能夠看一下spring-boot-autoconfigure.jar中的spring.factories內容:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ 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,\ //...如下省略...
固然這裏只是截取了其中一個類路徑jar下的部分配置,獲取全部配置類以後進行去重,去被排除的類,而後進行條件過濾,下面重點看一下:
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; candidates[i] = null; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); }
此方法大體就是首先獲取配置的AutoConfigurationImportFilter ,而後對以前獲取的全部配置類進行過濾,最後返回過濾以後的配置類;AutoConfigurationImportFilter一樣也是經過SpringFactoriesLoader類進行加載類路徑下META-INF/spring.factories,只不過當前的key是:org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,能夠看一下SpringBoot默認配置的filter:
# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
能夠看到Filter其實就是上文介紹的條件類,這裏默認了OnBeanCondition,OnClassCondition以及OnWebApplicationCondition,已這裏使用的Mybatis爲例看一下MybatisAutoConfiguration的註解:
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration implements InitializingBean { //...如下省略... }
能夠看到其中有用到@ConditionalOnClass,表示必須提供SqlSessionFactory和SqlSessionFactoryBean類的狀況下才加載此配置類,而整兩個是正式Mybatis基礎包中提供的;有了基礎包還不行,還須要DataSource,並且DataSource必須在MybatisAutoConfiguration實例化以前初始化好,SpringBoot是如何實現,繼續看另一個核心方法selectImports():
@Override public Iterable<Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } Set<String> allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); Set<String> processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); processedConfigurations.removeAll(allExclusions); return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); } private List<String> sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata) .getInPriorityOrder(configurations); }
首先是對被排除類的一個過濾,而後接下來重點看一下對配置類進行排序的一個方法,具體操做在類AutoConfigurationSorter中進行的,具體方法爲getInPriorityOrder():
public List<String> getInPriorityOrder(Collection<String> classNames) { AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory, this.autoConfigurationMetadata, classNames); List<String> orderedClassNames = new ArrayList<>(classNames); // Initially sort alphabetically Collections.sort(orderedClassNames); // Then sort by order orderedClassNames.sort((o1, o2) -> { int i1 = classes.get(o1).getOrder(); int i2 = classes.get(o2).getOrder(); return Integer.compare(i1, i2); }); // Then respect @AutoConfigureBefore @AutoConfigureAfter orderedClassNames = sortByAnnotation(classes, orderedClassNames); return orderedClassNames; }
首先使用order進行排序,而後使用@AutoConfigureBefore和@AutoConfigureAfter就行排序;order其實就是經過註解@AutoConfigureOrder進行排序的,值是一個整數,結構相似以下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureBefore和@AutoConfigureAfter字面意思也很好理解,指定在其餘配置類以前和以後,因此能夠看到在MybatisAutoConfiguration中有以下配置:
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
表示在DataSourceAutoConfiguration配置類加載以後纔會加載Mybatis配置類,這樣就解決了依賴關係;還有上文提到的Mybatis操做數據庫依賴的SqlSessionFactory和SqlSession,都在MybatisAutoConfiguration進行了初始化操做;SpringBoot自己其實以及提供了大量經常使用組件的自動配置類,咱們只須要提供知足的特定條件,SpringBoot自動會幫我加載初始化等操做,可是確定也有自定義配置類的需求,下面用一個簡單的實例來看看如何自定義一個自動配置類;
接下來咱們用很簡單的實例來看一下自定義的流程,一個格式化大寫消息的實例;
<groupId>com.format</groupId> <artifactId>format-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>format-spring-boot-starter</name> <url>http://maven.apache.org</url> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Spring 官方 Starter一般命名爲spring-boot-starter-{name}如 spring-boot-starter-web,Spring官方建議非官方Starter命名應遵循{name}-spring-boot-starter的格式;
@ConfigurationProperties("format.service") public class FormatServiceProperties { private String type; //...get/set省略... } public class FormatService { private String type; public FormatService(String type) { this.type = type; } public String wrap(String word) { if(type.equalsIgnoreCase("Upper")){//大寫 return word.toUpperCase(); }else if(type.equalsIgnoreCase("Lower")){//小寫 return word.toLowerCase(); } return word; } }
屬性類提供了type參數能夠在application.properties中配置,可配置值包括:upper,lower;
@Configuration @ConditionalOnClass(FormatService.class) @EnableConfigurationProperties(FormatServiceProperties.class) public class FormatAutoConfigure { @Autowired private FormatServiceProperties properties; @Bean @ConditionalOnMissingBean FormatService formatService() { return new FormatService(properties.getType()); } }
這個就是自定義的自動配置類,SpringBoot啓動的時候會根據條件自動初始化;最後在resources/META-INF/下建立spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.format.FormatAutoConfigure
在其餘SpringBoot中能夠引入上面建立的項目,引入方式也很簡單:
<dependency> <groupId>com.format</groupId> <artifactId>format-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
同時在application.properties配置格式化類型:
format.service.type=upper
啓動應用,瀏覽器訪問http://localhost:8888/format?word=hello,結果爲:HELLO
本文從使用SpringBoot整合Mybatis開始,而後提出使用中產生的疑問,進而經過分析源碼的方式來理解SpringBoot的自動配置機制,最後自定義了一個自動配置類來看看具體如何使用;SpringBoot經過自動配置的方式幫助開發者減小了很大的工做量,達到開箱即用的效果;可是另外一方面若是出現問題須要調試可能不是那麼好定位。
https://github.com/ksfzhaohui...