<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
spring.redis.host=192.168.8.74spring.redis.password=123456spring.redis.database=0
package com.example.springbootvipjtdemo.redisdemo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;/** * @author Eclipse_2019 * @create 2022/6/9 14:36 */@RestController@RequestMapping("/redis")public class RedisController { @Autowired private RedisTemplate redisTemplate; @GetMapping("/save") public String save(@RequestParam String key,@RequestParam String value){ redisTemplate.opsForValue().set(key,value); return "添加成功"; } @GetMapping("/get") public String get(@RequestParam String key){ String value = (String)redisTemplate.opsForValue().get(key); return value; }}
通過上面的案例,我們就能看出來,RedisTemplate這個類的bean對象,我們并沒有通過XML的方式也沒有通過注解的方式注入到IoC容器中去,但是我們就是可以通過@Autowired注解自動從容器里面拿到相應的Bean對象,再去進行屬性注入。
那這是怎么做到的呢?接下來我們來分析一下自動裝配的原理,等我們弄明白了原理,自然而然你們就懂了RedisTemplate的bean對象怎么來的。
@Target(ElementType.TYPE) // 注解的適用范圍,其中TYPE用于描述類、接口(包括包注解類型)或enum聲明@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三個生命周期)@Documented // 表明這個注解應該被javadoc記錄@Inherited // 子類可以繼承該注解@SpringBootConfiguration // 繼承了Configuration,表示當前是注解類@EnableAutoConfiguration // 開啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助@ComponentScan(excludeFilters = { // 掃描路徑設置@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {...}
在其中比較重要的有三個注解,分別是:
ComponentScan的功能其實就是自動掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義;并將這些bean定義加載到IoC容器中。
我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。
注:所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages。
此注解顧名思義是可以自動配置,所以應該是springboot中最為重要的注解。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)//【重點注解】public @interface EnableAutoConfiguration {...}
其中最重要的兩個注解:
當然還有其中比較重要的一個類就是:AutoConfigurationImportSelector.class。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {}
通過@Import(AutoConfigurationPackages.Registrar.class)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {register(registry, new PackageImport(metadata).getPackageName());}……}
注冊當前啟動類的根package;注冊org.springframework.boot.autoconfigure.AutoConfigurationPackages的BeanDefinition。
AutoConfigurationPackage注解的作用是將添加該注解的類所在的package作為自動配置package 進行管理。
可以通過 AutoConfigurationPackages 工具類獲取自動配置package列表。當通過注解@SpringBootApplication標注啟動類時,已經為啟動類添加了@AutoConfigurationPackage注解。路徑為 @SpringBootApplication -> @EnableAutoConfiguration -> @AutoConfigurationPackage。也就是說當SpringBoot應用啟動時默認會將啟動類所在的package作為自動配置的package。
如我們創建了一個sbia-demo的應用,下面包含一個啟動模塊demo-bootstrap,啟動類時Bootstrap,它添加了@SpringBootApplication注解,我們通過測試用例可以看到自動配置package為com.tm.sbia.demo.boot。
可以從圖中看出AutoConfigurationImportSelector實現了 DeferredImportSelector 從 ImportSelector繼承的方法:selectImports。
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); 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 StringUtils.toStringArray(configurations);}
第9行List configurations =getCandidateConfigurations(annotationMetadata,`attributes);其實是去加載各個組件jar下的 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。
如果獲取到類信息,spring可以通過類加載器將類加載到jvm中,現在我們已經通過spring-boot的starter依賴方式依賴了我們需要的組件,那么這些組件的類信息在select方法中就可以被獲取到。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; }
其返回一個自動配置類的類名列表,方法調用了loadFactoryNames方法,查看該方法。
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}
自動配置器會跟根據傳入的factoryClass.getName()到項目系統路徑下所有的spring.factories文件中找到相應的key,從而加載里面的類。
這個外部文件,有很多自動配置的類。如下:
其中,最關鍵的要屬@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件(spring.factories)的bean定義(如Java Config@Configuration配置)都加載到當前SpringBoot創建并使用的IoC容器。
其實SpringFactoriesLoader的底層原理就是借鑒于JDK的SPI機制,所以,在將SpringFactoriesLoader之前,我們現在發散一下SPI機制。
SPI ,全稱為 Service Provider Interface,是一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。這一機制為很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。我們先通過一個很簡單的例子來看下它是怎么用的。
首先,我們需要定義一個接口,SPIService。
package com.example.springbootvipjtdemo.spidemo;/** * @author Eclipse_2019 * @create 2022/6/8 17:55 */public interface SPIService { void doSomething();}
然后,定義兩個實現類,沒別的意思,只輸入一句話。
package com.example.springbootvipjtdemo.spidemo;/** * @author Eclipse_2019 * @create 2022/6/8 17:56 */public class SpiImpl1 implements SPIService{ @Override public void doSomething() { System.out.println("第一個實現類干活。。。"); }}----------------------我是乖巧的分割線----------------------package com.example.springbootvipjtdemo.spidemo;/** * @author Eclipse_2019 * @create 2022/6/8 17:56 */public class SpiImpl2 implements SPIService{ @Override public void doSomething() { System.out.println("第二個實現類干活。。。"); }}
最后呢,要在ClassPath路徑下配置添加一個文件。文件名字是接口的全限定類名,內容是實現類的全限定類名,多個實現類用換行符分隔。
文件路徑如下:
上一步已經找到了MySQL中的com.mysql.jdbc.Driver全限定類名,當調用next方法時,就會創建這個類的實例。它就完成了一件事,向DriverManager注冊自身的實例。
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { //注冊 //調用DriverManager類的注冊方法 //往registeredDrivers集合中加入實例 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { // Required for Class.forName().newInstance() }}
在DriverManager.getConnection()方法就是創建連接的地方,它通過循環已注冊的數據庫驅動程序,調用其connect方法,獲取連接并返回。
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { //registeredDrivers中就包含com.mysql.cj.jdbc.Driver實例 for(DriverInfo aDriver : registeredDrivers) { if(isDriverAllowed(aDriver.driver, callerCL)) { try { //調用connect方法創建連接 Connection con = aDriver.driver.connect(url, info); if (con != null) { return (con); } }catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } }}
既然我們知道JDBC是這樣創建數據庫連接的,我們能不能再擴展一下呢?如果我們自己也創建一個java.sql.Driver文件,自定義實現類MyDriver,那么,在獲取連接的前后就可以動態修改一些信息。
還是先在項目ClassPath下創建文件,文件內容為自定義驅動類com.viewscenessupervisor.spi.MyDriver我們的MyDriver實現類,繼承自MySQL中的NonRegisteringDriver,還要實現java.sql.Driver接口。這樣,在調用connect方法的時候,就會調用到此類,但實際創建的過程還靠MySQL完成。
package com.viewscenessupervisor.spipublic class MyDriver extends NonRegisteringDriver implements Driver{ static { try { java.sql.DriverManager.registerDriver(new MyDriver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public MyDriver()throws SQLException {} public Connection connect(String url, Properties info) throws SQLException { System.out.println("準備創建數據庫連接.url:"+url); System.out.println("JDBC配置信息:"+info); info.setProperty("user", "root"); Connection connection = super.connect(url, info); System.out.println("數據庫連接創建完成!"+connection.toString()); return connection; }}--------------------輸出結果---------------------準備創建數據庫連接.url:jdbc:mysql:///consult?serverTimezone=UTCJDBC配置信息:{user=root, password=root}數據庫連接創建完成!com.mysql.cj.jdbc.ConnectionImpl@7cf10a6f
借助于Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!
SpringFactoriesLoader屬于Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,加載工廠類。
SpringFactoriesLoader為Spring工廠加載器,該對象提供了loadFactoryNames方法,入參為factoryClass和classLoader即需要傳入工廠類名稱和對應的類加載器,方法會根據指定的classLoader,加載該類加器搜索路徑下的指定文件,即spring.factories文件。
傳入的工廠類為接口,而文件中對應的類則是接口的實現類,或最終作為實現類。
public abstract class SpringFactoriesLoader {//... public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { ... } public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { .... }}
配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應的一組@Configuration類
上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內容,可以很好地說明問題。
(重點)所以,@EnableAutoConfiguration自動配置的魔法其實就變成了:
從classpath中搜尋所有的META-INF/spring.factories配置文件,并將其中
org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的配置項通過反射(Java Refletion)實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個并加載到IoC容器。
本文鏈接:http://www.www897cc.com/showinfo-26-11764-0.html玩轉SpringBoot—自動裝配解決Bean的復雜配置
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 徹底搞懂Spring的Bean加載