4.1.5.springboot核心特性及設計思想【上】
時長:55min
5.1.springboot注解驅動
5.1.1.spring4.X版本注解驅動
主要是注解裝飾功能的一個完善。提供條件注解裝配,即@Conditional注解使用。
5.1.1.1.什么是條件化裝配?
其實,它是對是否進行bean裝配的一個條件限制,如果條件返回true,則允許裝配。否則,不允許。
下面通過示例代碼來,說明條件裝配的應用。
1.定義一個配置類
首先,創建一個springboot項目。然后創建配置類:
package com.wf.demo.springbootdemo.condition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @ClassName SpringConfiguration * @Description 統一的配置類 * @Author wf * @Date 2020/7/6 18:09 * @Version 1.0 */ @Configuration public class SpringConfiguration { /** * 在某個環境下不裝載 * 或不滿足某個條件不裝載 * 或者已經裝載過了,不要重復裝載 * @return */ @Conditional(MyCondition.class) @Bean public DemoService demoService(){ return new DemoService(); } }
說明:
配置類中,通過@Bean進行bean裝配,然后加上@Conditional注解,就能實現條件裝配。
@Conditional注解,傳參為Condition接口子類Class對象,可以傳參多個子類對象,多個對象之間是且的關系。
2.定義Condition子類
package com.wf.demo.springbootdemo.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @ClassName MyCondition * @Description 實現條件子類 * @Author wf * @Date 2020/7/6 18:14 * @Version 1.0 */ public class MyCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //TODO 這里可以寫對應的判斷邏輯 return false; } }
說明:
子類中實現match方法,進行條件判斷,如果返回false,則不允許bean裝配。
3.bean定義
package com.wf.demo.springbootdemo.condition; /** * @ClassName DemoService * @Description service bean類 * @Author wf * @Date 2020/7/6 18:10 * @Version 1.0 */ public class DemoService { }
4.測試bean實例獲取
package com.wf.demo.springbootdemo.condition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * @ClassName ConditionMain * @Description 測試類 * @Author wf * @Date 2020/7/6 18:17 * @Version 1.0 */ public class ConditionMain { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class); DemoService bean = context.getBean(DemoService.class); System.out.println(bean); } }
測試結果如下:
結果拋出異常,因為條件返回false,不允許裝配。
下面修改match方法,返回true,如下所示:
再次運行測試,成功裝配,如下所示:
5.1.2.spring5.X版本注解驅動
引入@Indexed注解,是用來提升性能的。當項目文件目錄很多時,掃描@Component,@Service。。。這些注解時,
就會很耗時,而@Indexed注解,可以提升注解掃描的性能。
總結:
spring的注解驅動的發展,是為了bean裝配更加簡單。
下面通過springboot中整合redis來說明,注解驅動的簡便性。
5.1.2.1.springboot整合redis示例
1.pom.xml中引入redis依賴
<!--整合redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
版本由springboot-parent來管理。
2.redis服務安裝
在生產中會有專門的redis服務安裝redis服務。而學習環境下,一般是在虛擬機上安裝redis應用服務器。
3.controller中引用redis代碼
package com.wf.demo.springbootdemo.web; import com.wf.demo.springbootdemo.dao.pojo.User; import com.wf.demo.springbootdemo.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName HelloController * @Description 測試controller * @Author wf * @Date 2020/7/6 11:27 * @Version 1.0 */ @RestController public class HelloController { @Autowired private RedisTemplate<String,String> redisTemplate; //配置文件注入 // @Value("Mic") // private String name; @Autowired private IUserService userService; @GetMapping("hello") public String test(){ User user = new User(); user.setId(18); user.setName("MIc"); userService.insert(user); return "success"; } @GetMapping("/say") public String say(){ return redisTemplate.opsForValue().get("name"); } }
說明:
springboot已經為了我們使用redis封裝了客戶端工具類RedisTemplate。
只有使用它的api即可。
4.配置redis連接參數
spring.redis.host=192.168.216.128
#redis.port=6379默認
5.運行項目,測試
只需要啟動main,瀏覽器端訪問controller接口即可。
說明:
我們可以看到,springboot整合redis是如此之簡單。
這里RedisTemplate實例,能夠直接引用,說明spring IOC容器中已經完成bean的裝配。
但是,我們是沒有做這部分工作的,而是由springboot進行了裝配。
【1】回顧spring4.X中bean裝配方式
》xml配置
》@Configuration注解裝配
》@Enable裝配
現在,springboot提供了自動裝配的功能。
5.2.springboot自動裝配
自動裝配的原理是什么?是如何實現的呢?
5.2.1.spring動態Bean的裝配方案
主要有兩種:
ImportSelector:
Registator
所謂動態裝載,即根據上下文,或某些條件去裝載一些配置類。
下面通過代碼示例,來說明springboot實現批量掃描配置類的原理。
所謂批量掃描,就是一次性掃描所有jar包中的配置類。示例代碼中以分包的形式,來模擬不同jar包的掃描。
5.2.1.1.實現ImportSelector的方式
1.在demo2包下創建redis配置類
package com.wf.demo.springbootdemo.demo2; /** * @ClassName RedisConfiguration * @Description redis配置類 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedisConfiguration { @Bean public MyRedisTemplate myRedisTemplate(){ return new MyRedisTemplate(); } } package com.wf.demo.springbootdemo.demo2; /** * @ClassName MyRedisTemplate * @Description 自定義redis模板類 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ public class MyRedisTemplate { }
2.在demo3包下創建Mybatis配置類
package com.wf.demo.springbootdemo.demo3; /** * @ClassName MySqlSessionFactory * @Description bean * @Author wf * @Date 2020/7/7 9:44 * @Version 1.0 */ public class MySqlSessionFactory { } package com.wf.demo.springbootdemo.demo3; /** * @ClassName MybatisConfiguration * @Description redis配置類 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ import com.wf.demo.springbootdemo.demo2.MyRedisTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisConfiguration { @Bean MySqlSessionFactory mySqlSessionFactory(){ return new MySqlSessionFactory(); } }
3.scan包下進行springboot批量掃描處理
【1】定義一個Selector子類實現
package com.wf.demo.springbootdemo.scan; import com.wf.demo.springbootdemo.demo2.RedisConfiguration; import com.wf.demo.springbootdemo.demo3.MybatisConfiguration; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; /** * @ClassName MyImportSelector * @Description 子類實現 * @Author wf * @Date 2020/7/6 19:30 * @Version 1.0 */ public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{MybatisConfiguration.class.getName(), RedisConfiguration.class.getName()}; } }
說明:
實現子類中返回了多個配置類的全路徑類名。【即可獲取配置類的位置】
【2】定義Enable注解,來掃描Selector實現子類
package com.wf.demo.springbootdemo.scan; import org.springframework.context.annotation.Import; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class) public @interface EnableConfiguration { }
【3】springboot啟動入口main中bean獲取測試
package com.wf.demo.springbootdemo; import com.wf.demo.example.demo2.MyRedisTemplate; import com.wf.demo.example.demo3.MySqlSessionFactory; import com.wf.demo.example.scan.EnableConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; //@EnableConfiguration @SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(ca.getBean(MyRedisTemplate.class)); System.out.println(ca.getBean(MySqlSessionFactory.class)); } }
說明:
這里先注釋掉@EnableConfiguration注解,測試結果,肯定會報錯。如下所示:
然后,放開注解。bean注入成功,如下所示:
再次運行測試,結果如下:
總結:
為什么加上@EnableConfiguration注解,就能掃描到所有的配置類了呢?
因為這個注解里面,使用@Import注解,告知了spring所有配置類的位置所在。
其實是ImportSelector接口實現子類中,重寫selectImports方法,告訴了spring要掃描的配置類的位置在哪里。
4.selectImports方法的實現
上面的實現中,傳遞的是配置類的全路徑類名,其實也可以直接傳遞要裝配bean的全路徑類名。
方法實現修改示例如下:
然后,啟動main,測試結果如下:
思考:
現在,已經基本弄清楚,批量掃描配置類的底層原理了。那么,就需要進行功能擴展,現在,只是通過傳參兩個類名,
然后就可以掃描兩個配置類了。
如果有很多的第三方組件需要批量掃描,總不能一個個傳參類名吧,這么做顯然是不合理的。
所以,在selectImports方法中,一定可以通過某種機制去完成指定路徑的配置類的掃描。
這里的某種機制,也體現了springboot 約定優於配置的設計思想。我們可以想到,第三方starter組件由不同團隊進行開發,組件的名稱
和包路徑肯定是不一樣的。
因此,springboot 就做出統一約定【定個標准】,你們第三方去開發starter組件,需要把配置類的說明【在某個路徑下的全類名】要告訴
springboot引擎。
定義的標准是:
每一個starter組件,都需要定義路徑classpath:META-INF/spring.factories文件【每一個jar包里面都要有】。有了這個文件,springboot
就很好處理了。只需要掃描並解析classpath*:META-INF/spring.factories文件,就可以獲得相關配置類的全路徑類名,然后傳遞到數組里面就可以了。
注意:
當我寫完代碼之后,針對包結構,做了如下調整:
5.2.2.springboot源碼驗證自動裝配-批量實現原理
5.2.2.1.SpringBootApplication注解開始分析
通過注解類的定義,注解上引用@EnableAutoConfiguration注解,應該就是自動裝配的作用。
1.分析@EnableAutoConfiguration注解
可以看到通過@Import注解,引入Selector接口的實現,下面實現子類的代碼邏輯:
2.分析AutoConfigurationImportSelector動態裝配實現邏輯
public class AutoConfigurationImportSelector implements DeferredImportSelector
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
跟蹤this.getAutoConfigurationEntry方法:
通過debug啟動main入口程序,進入斷點處,可以獲取到配置類的全路徑類名。
再次跟蹤getCandidateConfigurations方法:
顯然,就是在解析classpath下META-INF/spring.factories文件。
5.2.2.2.springboot自動裝配原理圖解
如下圖所示 :
4.1.6.springboot核心特性及設計思想【下】
5.2.3.springboot掃描並解析classpath下META-INF/spring.factories文件的具體實現
通過上面的源碼分析,定位到getCandidateConfigurations方法。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
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; }
方法實現邏輯中,調用SpringFactoriesLoader工具類。
5.2.3.1.SpringFactoriesLoader工具類
根據定義類名,就是加載spring.factories文件。這里調用SpringFactoriesLoader.loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
返回一個List,再次調用 loadSpringFactories()方法,如下所示:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); //先從緩存中獲取 if (result != null) { return result; //緩存中有數據,直接返回 } else { try {//這里會有多個jar包,就對應多個文件 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); //得到文件資源路徑 Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
下面來看一個spring.factories文件是什么樣子的,在springboot項目中,全局搜索spring.factories,如下所示:
然后,任意打開一個文件,如下所示:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
可以看到,和properties文件很像,數據存儲中key/value形式。
key為:org.springframework.boot.autoconfigure.EnableAutoConfiguration
value為:配置類全路徑類名,通過,拼接成字符串
注意:
spring.factory文件可能是存儲其他配置的,並不全是自動裝配的配置。舉例如下:
它是作為starter定義的配置文件。
5.2.3.2.SPI機制
Service provider interface【簡稱SPI】,這是一 種什么樣的機制呢?它的核心思想是什么?
我們以java中數據庫連接為例。jdk定義數據庫連接的接口。但沒有定義實現。而是數據庫廠商提供實現方案。
如:mysql連接,我們會引入一個mysql的驅動包。【這就是一種實現方案】
然后,我在項目上下文環境,一般是application.properties文件中定義數據庫連接參數。 如:jdbc.url/username/password...
在jdk環境中,提供對這些配置參數的解析。
當獲得這些配置參數,這可以引用驅動包的服務, 建立通信。這就是一種擴展機制。這種擴展點設計,不是為了分隔接口定義
與實現分離,而是為功能的擴展。從而,提升框架的擴展性和靈活性。
下面以springboot中spring.factories文件,作為SPI機制的運用來加以說明:
每一個組件,打成一個jar包。在jar包中都會有一個spring.factories文件,對於自動裝配來說,這個文件定義自動裝配類的
實現。key代表着自動裝配的定義,如下所示:
下面以示例代碼來說明,這樣一種SPI機制是什么樣的。
1.創建一個maven項目,模擬jdk定義數據庫連接的接口
項目結構如下所示:
【1】定義一個建立連接的接口
因為我們是站在框架設計的角度,我們沒有具體的實現,不知道它的具體實現是什么樣子的。
但是,我們可以定義一種規范,提供spi擴展點,讓實現方根據我們定義的規范來做具體的實現。
代碼如下所示:
package com.wf.spi; /** * @ClassName DataBaseDriver * @Description 驅動接口定義 * @Author wf * @Date 2020/7/7 14:12 * @Version 1.0 */ public interface DataBaseDriver { String buildConnect(String host); }
【2】使用maven install命令打成一個jar包
2.創建第二個maven項目,模擬mysql實現驅動包
【1】實現項目中引入接口項目的jar包依賴
修改pom.xml,如下所示:
<dependency> <groupId>com.wf.spi</groupId> <artifactId>DataBaseDriver</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】實現驅動服務
package com.wf.spi.mysql; import com.wf.spi.DataBaseDriver; /** * @ClassName MysqlDriver * @Description mysql驅動服務實現 * @Author wf * @Date 2020/7/7 14:31 * @Version 1.0 */ public class MysqlDriver implements DataBaseDriver { @Override public String buildConnect(String url) { return "mysql的驅動實現:"+url; } }
【3】定義擴展點
完成了這樣一個mysql驅動實現,我怎么去使用它呢?
就需要定義規范,通常是由配置文件,進行相關規范定義。
這里,就需要創建resources資源文件夾,下面創建META-INF目錄。再目錄下創建services目錄,
然后在services目錄下創建text文件,文件命名為
驅動接口的全路徑類名。文件的內容也配置為接口實現類的全路徑類名。如下所示:
注意:
這個是針對所有jar包規范。必須按此規范配置。
【4】項目完成后,打包。
3.創建第三個maven項目,模擬用戶使用jdk連接mysql
[1]pom.xml中引入接口包和實現包
<dependency> <groupId>com.wf.spi</groupId> <artifactId>DataBaseDriver</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.wf.spi.mysql</groupId> <artifactId>mysql-driver-connector</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】創建測試代碼
package com.wf.spi.use; import com.wf.spi.DataBaseDriver; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { //加載實現 ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class); for (DataBaseDriver driver : serviceLoader) { System.out.println(driver.buildConnect("Test")); } } }
測試結果如下所示:
說明:
這就是jdk底層的SPI機制應用。這里使用jdk提供的ServiceLoader工具類,加載實現類。
4.java中SPI規范
java中要想實現SPI擴展,需要滿足幾個條件:
》需要在classpath下創建目錄META-INF/services【必須完全一致】
》在該目錄擴展點【定義接口】的text文件,文件名為定義接口全路徑類名。
》文件內容,配置接口實現類的全類名。如果有多個實現,換行配置多個
》文件編碼格式為UTF-8
》實現方式:使用jdk提供ServiceLoader工具類,加載實現類。
5.2.4.再了解springboot條件裝配
我們發現有一個奇怪的現象:spring-boot與redis整合的starter包中沒有spring.factories文件,如下所示:
同樣,整合jdbc的starter包中,也沒有,如下所示:
這到底是為什么呢?這就涉及到springboot官方定義starter包的規范問題。
5.2.4.1.springboot官方定義starter包的規范
springboot官方定義的starter包,存在一些規范:
》官方包命名規范,如:spring-boot-starter-XXX.jar
》第三方包命名規范,如:xxx-spring-boot-starter.jar
如:mybatis定義starter命名,mybatis-spring-boot-starter
springboot官方包,不存放配置類,而是專門定義自動裝配的包,如下所示:
這個包里,會把springboot提供的所有自動裝配的配置類信息,進行統一配置在spring.factories文件中。
這是一種違反常規的設計,那么,spring-boot是如何實現自動裝配的呢?與常規的裝配方式有什么區別嗎?
下面以redis自動裝配為例,來進行說明:
5.2.4.2.springboot自動裝配再理解
以redis自動裝配為例,搜索redis自動裝配配置類RedisAutoConfiguration,代碼實現如下:
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
1.@ConditionalOnClass({RedisOperations.class})
可以看到配置類上,聲明@ConditionalOnClass({RedisOperations.class})注解,它有什么作用呢?
該注解,傳入參數為RedisOperations類的class對象,這個類是定義在spring-data-redis整合包中的。定義如下所示:
這個注解的含義是:
如果傳入參數所在包,未引入,導致條件不滿足,就不允許裝配。
所以,springboot官方starter包,其實是一種條件裝配,不需要引入配置類。
思考:
如果條件傳參對象,所在包不存在,怎么辦?
其實,這就涉及到maven依賴的傳遞依賴。所謂傳遞依賴,當前項目與依賴jar包如果不傳遞,當前項目未引入依賴時不會報錯。
下面以一個demo示例,來說明springboot自動裝配的過程。
5.2.4.3.spring自動裝配示例demo
1.創建第一個maven,模擬第三方組件
【1】pom.xml中引入spring-context依賴
<spring.version>5.2.7.RELEASE</spring.version> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
【2】創建配置類
package com.wf.autoconfig.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @ClassName MyConfiguration * @Description 配置類 * @Author wf * @Date 2020/7/7 16:54 * @Version 1.0 */ @Configuration public class MyConfiguration { @Bean public MyCore myCore(){ return new MyCore(); } }
【3】創建bean類
package com.wf.autoconfig.demo; /** * @ClassName MyCore * @Description 核心類 * @Author wf * @Date 2020/7/7 16:52 * @Version 1.0 */ public class MyCore { public String study(){ System.out.println("I'm learning p6 lesson"); return "Gupaoedu.com"; } }
【4】打成一個jar包。
使用mvn install命令。
2.在springboot項目中使用自定義組件
【1】pom.xml中引入組件依賴
<!--引入自定義第三方組件依賴--> <dependency> <groupId>com.wf.autoconfig.demo</groupId> <artifactId>my-core-app</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】使用第三方組件中bean
要想以spring注入的方式使用,顯然需要先完成自動裝配。先在main入口方法,測試一下bean能否獲取。
代碼如下所示:
package com.wf.demo.springbootdemo; import com.wf.autoconfig.demo.MyCore; import com.wf.demo.example.demo2.MyRedisTemplate; import com.wf.demo.example.demo3.MySqlSessionFactory; import com.wf.demo.example.scan.EnableConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @EnableConfiguration @SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(ca.getBean(MyRedisTemplate.class)); System.out.println(ca.getBean(MySqlSessionFactory.class)); System.out.println(ca.getBean(MyCore.class)); //獲取新定義第三方組件中bean實例 } }
然后,啟動main,進行測試,顯然,無法注入,會報錯,如下所示:
3.修改組件,完成自動裝配相關配置
這里基於SPI機制進行裝配。
創建resources資源文件夾,然后創建META-INF目錄,在目錄創建spring.factories文件,文件配置如下:
具體配置內容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wf.autoconfig.demo.MyConfiguration
然后,重新打包項目。
【1】再次在springboot項目中運行main
測試結果如下:
說明:
自定義第三方組件bean成功注入。
4.修改第三組件,實現條件注入
【1】引入springboot依賴starter包
因為@ConditionalOnClass注解是springboot特有注解。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.1.RELEASE</version> <optional>true</optional> </dependency>
<!--RedisOperations類依賴包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.1.RELEASE</version>
<optional>true</optional>
</dependency>
【2】修改配置類,添加條件裝配
然后,重新打包項目。
【3】回到springboot項目,測試
注釋掉spring-data-redis所在包依賴,如下所示:
說明:
因為未引入所依賴jar,就會導致條件不滿足,無法進行bean裝配。測試肯定會報錯,如下所示:
反之,引入條件傳參依賴包,條件滿足,可完成自動裝配。
5.修改自定義組件,使用第二種配置方式實現條件裝配
在META-INF目錄下,創建spring-autoconfigure-metadata.properties配置文件。如下所示:
配置內容如下:
com.wf.autoconfig.demo.MyConfiguration.ConditionalOnClass=com.wf.DemoClass
這里,其實是以配置文件的方式來完成條件注入。而上面是以注解的方式完成條件注入。
不同的是,這種配置文件的方式注入,如果在組件中能找到com.wf.DemoClass就表示條件滿足,
可以在springboot項目中完成自動裝配。
這里先不定義這個DemoClass類,讓條件不滿足,然后打包項目。
【1】回到springboot項目,測試bean獲取
顯然,是無法自動裝配的。報錯如下所示:
【2】修改自定義組件,定義條件類
如下所示:
然后,重新打包,回到springboot項目進行測試,如下所示:
總結:
使用配置文件實現條件裝配,更為簡單。不需要引入springboot的依賴包。
也不需要springboot項目是否導包來設置條件。