springboot:@ConditionalOnProperty根據不同配置注入不同實現的bean
- 2021 年 9 月 20 日
- 筆記
- spring, springboot
一、引言
在開發中經常會碰到這樣的情形,一個接口會有不同的實現,但在開發中都是基於接口的注入,那麼怎麼根據不同的需求注入不同的類型就是一個值得考慮的問題。在注入屬性時常用的兩個註解是@Autowired和@Resource,使用它們便可以實現,同時spring提供了很多@ConditionalXXX的註解,可以很好的完成上述功能;
二、代碼演示
1、問題代碼描述
使用代碼的方式描述下上面提到的問題,後面給出解決方案。
controller類,TestConditionalOnProperty.java
package com.atssg.controller; import com.atssg.service.MyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestConditionalOnProperty {
//注入MyService @Autowired private MyService myService; @GetMapping("/test/test1") public void test(){ myService.test(); } }
下面是MyService接口,MyService.java
package com.atssg.service; public interface MyService { void test(); }
下面是兩個實現類,MyServiceImpl.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @Slf4j public class MyServiceImpl implements MyService { @Override public void test() { log.info("I am Myservice"); } }
下面是MyServiceImpl2.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @Slf4j public class MyServiceImpl2 implements MyService { @Override public void test() { log.info("I am MyServiceImpl2"); } }
程序啟動報錯,
Description: Field myService in com.atssg.controller.TestConditionalOnProperty required a single bean, but 2 were found: - myServiceImpl: defined in file [D:\code\cloud2020\cloud-sync-7002\target\classes\com\atssg\service\impl\MyServiceImpl.class] - myServiceImpl2: defined in file [D:\code\cloud2020\cloud-sync-7002\target\classes\com\atssg\service\impl\MyServiceImpl2.class]
大體意思是TestConditionalOnProperty需要一個單例bean,但是發現了兩個,也就是MyServiceImpl和MyServicImpl2。那如何才能注入一個那。
2、解決方案
2.1、@Qualifier
@Autowired默認條件下會按照id注入,找不到id會按照類型注入,上面的錯誤便是這種情況,我們可以給@Autowired指定要注入的id即可,使用@Qualifier可以實現指定id。
TestConditionalOnProperty改動如下,
package com.atssg.controller; import com.atssg.service.MyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestConditionalOnProperty { @Qualifier("myServiceImpl2") @Autowired private MyService myServiceImpl; @GetMapping("/test/test1") public void test(){ myServiceImpl.test(); } }
同理,兩個MyService的實現類也要指定生成bean的id。默認情況下是其類名首字母小寫,如MyServiceImpl如果不指定生成id則為myServiceImpl。修改如下
MyServiceImpl.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service("myServiceImpl") @Slf4j public class MyServiceImpl implements MyService { @Override public void test() { log.info("I am Myservice"); } }
MyServiceImpl2.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service("myServiceImpl2") @Slf4j public class MyServiceImpl2 implements MyService { @Override public void test() { log.info("I am MyServiceImpl2"); } }
@Service可以指定value值,也就是指定生成bean的id值。
測試結果如下,
從上面可以看出已經可以實現注入一個MyService的實現類,但是這種方式有一個弊端,那就是不夠靈活,雖然實現了加載一個實現類,但是每次都需要修改代碼,而且有可能會修改錯誤,而且是硬編碼。
其實還有另外一種方式,也是使用@Autowired,只不過被@Autowired註解修飾的變量名必須是要注入的bean的id,如
這裡注入的是myServiceImpl,也就是MyServiceImpl的實現類,測試結果如下,
這樣便實現了注入一個bean的目的,但這種方式和上面的方式是一樣的,不夠靈活且是硬編碼。下面看springboot為我們提供的另外一種方式。
2.2、@ConditionalOnProperty
@ConditionalOnProperty註解是springboot開發的眾多@ConditionalXX註解中的一個,根據properties文件中的屬性值來決定注入哪一個。
先在applicaiton.properties文件中定義一個變量
myService=service1
TestConditionalOnProperty中使用@Resource進行注入
package com.atssg.controller; import com.atssg.service.MyService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class TestConditionalOnProperty { @Resource private MyService myService; @GetMapping("/test/test1") public void test(){ myService.test(); } }
MyService的兩個實現類,MyServiceImpl.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @Component @ConditionalOnProperty(name = "myService",havingValue = "service1") @Slf4j public class MyServiceImpl implements MyService { @Override public void test() { log.info("I am Myservice"); } }
MyServiceImpl2.java
package com.atssg.service.impl; import com.atssg.service.MyService; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @Component @ConditionalOnProperty(name = "myService",havingValue = "service2") @Slf4j public class MyServiceImpl2 implements MyService { @Override public void test() { log.info("I am MyServiceImpl2"); } }
每個實現類中均使用了@ConditionalOnProperty註解,並指定了name和havingValue屬性,name指定applicaiton.properties文件中的屬性名,havingValue指定了屬性值,在上面的配置的是service1,即調用MyServiceImpl中的test()方法,測試如下
2021-09-20 19:18:44.499 INFO 23892 --- [)-192.168.117.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
2021-09-20 19:23:38.576 INFO 23892 --- [nio-8080-exec-1] com.atssg.service.impl.MyServiceImpl : I am Myservice
從測試結果來看,調用的的確是MyServiceImpl中的test()方法,那麼改成service2的結果如下,
這樣只需要修改配置文件便可以實現動態加載不同的實現類。
三、總結
要想實現加載不同的實現類,還有其他的方式,這裡不一一列舉,本文旨在介紹@ConditionalOnProperty註解的使用。@ConditionalOnProperty註解可以實現根據配置文件中的值注入不同的實現類。