javascript
注解不自动导包_玩转SpringBoot2.X:SpringBoot自动配置原理大揭秘
我們在使用SpringBoot的時候,是不是覺得特方便,根本不需要我們?nèi)ヅ渲檬裁炊丝谔?#xff0c;應用名稱,又比如我們再整合redis的時候,其實也不需要我們?nèi)ブ付ǘ丝谔?#xff0c;IP,都會有默認的。是不是特方便。那么SpringBoot到底是怎么實現(xiàn)自動配置的呢。我們這里來分析下。
一、什么是自動配置
要了解SpringBoot的自動配置,我們得先理解啥是自動配置,字面上理解就是配置是自動化的,不需要我們額外的編配置文件,全部都有默認的配置。若是我們的項目都用SpringBoot提供的默認配置,那么理論上來說我們可以實現(xiàn)完全的零配置開發(fā)?!白詣印钡谋憩F(xiàn)形式就是我們只需要引我們想用功能的包,相關(guān)的配置我們完全不用管。誤區(qū):我剛開始以為自動配置的意思是自動繼集成了了各種redis、mybatis插件,然后我就一直弄不懂為什么我明明不需要這些插件都還要給我集成。其實我是鉆牛角尖,只是自動幫我們初始化默認配置文件,其實是配置類而已。相信這個誤區(qū)只有我這種愛鉆牛角尖的人才會多想。
二、SpringBoot加載自定義配置
我明要學習SpringBoot的自動化配置,那么我們來先學習一下SpringBoot是怎么加載自定義的配置的。如下新建一個最簡單的SpringBoot項目,當然是maven的啦。可以參考我的博文二、快速入門-Hello SpringBoot2.0,這里我們就建立兩個類,一個是配置類Config,一個是啟動類App,還要建立一個配置文件application.yml,項目目錄如下:
我們這里要實現(xiàn)的就是,把配置文件定義的key值賦值給Config。
1、Config
@ConfigurationProperties(prefix="config")public class Config { private String username="隨筆博客"; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; }}@ConfigurationProperties注解可以注入在application.properties配置文件中的屬性。
2、App
@SpringBootApplication@RestControllerpublic class App { @Autowired private Config config; @RequestMapping("/hello") public String hello() { System.out.println(config.getUsername()); return "hello"; } public static void main(String[] args) { SpringApplication.run(App.class, args); }}3、application.yml
config: username: suibibk4、在App這個類里注入Config類對象
有如下幾種方法。
A、在Config上使用注解@Component
@Component@ConfigurationProperties(prefix="config")public class Config {B、在Config上使用注解@Configuration
@Configuration@ConfigurationProperties(prefix="config")public class Config {C、在App中用@Bean
@Beanpublic Config config() { return new Config();}D、在App上加入如下注解
@EnableConfigurationProperties(Config.class)public class App {@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的類生效。
5、總結(jié)
上面四種方法都可以實現(xiàn)讀取配置文件的信息,并且這里默認有一個值“隨筆博客”,到這里,是不是覺得可能SpringBoot也是用這種方式定義了超級多自動配置類?
三、SpringBoot啟動流程自動配置流程分析
由上面我們可以知道,也許SpringBoot也是在啟動的時候自動把很多組件默認的配置加載進容器中了,這樣子我們再加入某一組件比如redis后就可以不需要指定配置(如果沒有個性化的話)。我們來一起跟蹤下吧。
1、啟動類App
@SpringBootApplicationpublic class App {我們的啟動類就只用一個注解@SpringBootApplication就可以啟動我們的SpringBoot應用了。那么我們跟蹤這個注解進去。
2、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 {這里我們主要要看兩個注解,一個是@SpringBootConfiguration,跟蹤進去如下
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {我們看到了@Configuration注解,這個注解顧名思義,表明這是一個配置類,也就是說,我們的App也是一個配置類,這也就解釋了為什么上面我們再App上可以用如下方法注入bean
@Beanpublic Config config() { return new Config();}其實上面@SpringBootConfiguration跟自動配置沒有啥關(guān)系,主要我們要看@EnableAutoConfiguration注解,跟蹤進去。
3、EnableAutoConfiguration
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {順便提一起,下面四個是元注解
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited這里我們先看@AutoConfigurationPackage注解
Storing auto-configuration packages for reference later (e.g. by JPA entity scanner).保存自動配置類以供之后的使用,比如給JPA entity掃描器用來掃描開發(fā)人員通過注解@Entity定義的entity類。也就是說,這個類跟@ComponentScan是不同的,@ComponentScan注解默認就會裝配標識了@Controller,@Service,@Repository,@Component注解的類到spring容器中,而@AutoConfigurationPackage注解主要掃描的是@Entity這種注解。
然后我們最主要的是看@Import(AutoConfigurationImportSelector.class)這個注解,@Import注解支持導入普通的java類,并將其聲明成一個bean,所以相當于是將AutoConfigurationImportSelector類實例化為bean加入容器中。我們跟蹤進去。
4、AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {我們這里可以看DeferredImportSelector類,跟蹤進去
public interface DeferredImportSelector extends ImportSelector {再跟蹤進去ImportSelector
public interface ImportSelector { /** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */ String[] selectImports(AnnotationMetadata importingClassMetadata);}在SpringBoot中,若是用@Import注解導入的實現(xiàn)了ImportSelector接口的類,會在啟動的時候自動調(diào)用selectImports方法。這個我們其實可以測試下,寫一個類比如ConfigData,實現(xiàn)ImportSelector接口,然后在App啟動類上加入如下注解:
@Import(ConfigData.class)你會發(fā)現(xiàn),在啟動App后會自動調(diào)用ConfigData的selectImports方法。那么在這里我們看一下同樣是實現(xiàn)了ImportSelector接口,當然是通過實現(xiàn)DeferredImportSelector接口來實現(xiàn)的AutoConfigurationImportSelector類的selectImports方法,該方法也會在App啟動的時候被調(diào)用。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); } catch (IOException ex) { throw new IllegalStateException(ex); } }這里我們主要看如下語句
List configurations = getCandidateConfigurations(annotationMetadata, attributes);因為這個語句的字面意思就是讀取配置,所以我們跟蹤進入這個方法中去。也是屬于這個類的方法(廢話)。
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List 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.loadFactoryNames方法
public static List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }當然繼續(xù)跟蹤進入loadSpringFactories方法
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) return result; try { Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry, ?> entry : properties.entrySet()) { List factoryClassNames = Arrays.asList( StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); result.addAll((String) entry.getKey(), factoryClassNames); } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }這里看到加載了資源文件FACTORIES_RESOURCE_LOCATION,對應的文件是
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";META-INF/spring.factories,也就是加載在全部META-INF下面的spring.fatories文件。當然這里我們直接去查看自動配置包下面的這個文件,如下圖位置:
打開可以看到如下內(nèi)容
可以看到注釋#Auto Configure這里對應的所有Class就是在SpringBoot啟動的時候?qū)崿F(xiàn)自動配置的類。我們可以舉個例子看一下是怎么實現(xiàn)自動配置的我們舉個例子
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration5、RedisAutoConfiguration
@Configuration@ConditionalOnClass(RedisOperations.class)@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate redisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean(StringRedisTemplate.class) public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }}可以很明顯的看到上面有注解@EnableConfigurationProperties(RedisProperties.class),這不就跟我們一開始說的一樣么,RedisProperties會自動實例化bean加入到IOC容器中。此時我們redis想用,就直接用啦,不需要我們寫配置文件。跟蹤RedisProperties進去可看到
@ConfigurationProperties(prefix = "spring.redis")public class RedisProperties { /** * Database index used by the connection factory. */ private int database = 0; /** * Connection URL. Overrides host, port, and password. User is ignored. Example: * redis://user:password@example.com:6379 */ private String url; /** * Redis server host. */ private String host = "localhost"; /** * Login password of the redis server. */ private String password; /** * Redis server port. */ private int port = 6379;很明顯跟我們一開始加載自定義配置是一樣的,我們會有個默認值,比如host為localhost,端口號為6379,當然我們也可以在application.yml配置文件中覆蓋,前綴為spring.redis。
好了,到這里我們就應該知道SpringBoot是如何實現(xiàn)自動化配置的了。就是在META-INF/spring.factories的這個文件中,配置好所有需要配置的類,比如redis,elasticsearch,freemarker,jdbc等等我們經(jīng)常要集成的組件的配置類,然后在容器啟動的時候就全部這些配置類注入到IOC容器中,有組件想使用的話就直接從容器中讀取對應的配置,完全可以做到零配置,也可以做到自定義配置來覆蓋默認配置。
順便提一下@ConditionalOnMissingBean注解,我們先看如下說明。
@ConditionalOnBean:當容器里有指定的bean的條件下。
@ConditionalOnMissingBean:當容器里不存在指定bean的條件下。
@ConditionalOnClass:當類路徑下有指定類的條件下。
@ConditionalOnMissingClass:當類路徑下不存在指定類的條件下。
@ConditionalOnProperty:指定的屬性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表當xxx.xxx為enable時條件的布爾值為true,如果沒有設(shè)置的情況下也為true。
也就是上面RedisAutoConfiguration下的代碼的作用是:
@Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate redisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }黨容器中沒有redisTemplate對象的時候,就這里實例化一個bean放入到容器中。
6、那自動配配置類如何實例化的呢?
也許有細心的兄弟,會覺得代碼里selectImports不是只是加載了配置的類名么,那么像RedisAutoConfiguration這些配置類什么時候?qū)嵗哪?#xff0c;其實這得還是歸功于ImportSelector接口的厲害了,實現(xiàn)這個接口的類,在@Import導入后,會自動調(diào)用selectImports方法,然后這個方法返回的類名數(shù)組都會自動實例化。也就是這樣,那些自動化配置類全部實例化了沒然后對應的配置文件類也全部自動實例化了,太厲害了,拜服。ImportSelector接口的具體原理看來以后得好好研究。當然這里也可以寫一個很簡單的例子證明下ImportSelector。如下
public class User { public void test() { System.out.println("隨筆博客"); }}public class DataConfig implements ImportSelector{ public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{User.class.getName()}; }}然后在App中使用@Import
@SpringBootApplication@RestController@Import(DataConfig.class)public class App { @Autowired private User user; @RequestMapping("/hello") public String hello() { user.test(); return "hello"; } public static void main(String[] args) { SpringApplication.run(App.class, args); }}啟動訪問http://localhost:8080/hello, 可以看到控制臺打印隨筆博客。哈哈哈 到這里基本上搞明白了。
總結(jié)
通過上面分析,相信大家對SpringBoot自動化配置應該很清楚了,無非就是在啟動的時候把各常用組件的配置文件類實例化到IOC容器中。我們根據(jù)上面的代碼跟蹤可以了解,SpringBoot的實現(xiàn)方式真的是太精妙了,不得不佩服,受益良多。
總結(jié)
以上是生活随笔為你收集整理的注解不自动导包_玩转SpringBoot2.X:SpringBoot自动配置原理大揭秘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: stm32的语音识别_免费开源基于STM
- 下一篇: 怎么把cad做的图分享给别人_在线协同文