Spring?Boot中@Import三種使用方式實(shí)例詳解
Spring Boot中@Import三種使用方式!

需要注意的是:ImportSelector、ImportBeanDefinitionRegistrar這兩個(gè)接口都必須依賴于@Import一起使用,而@Import可以單獨(dú)使用。
@Import是一個(gè)非常有用的注解,它的長(zhǎng)處在于你可以通過(guò)配置來(lái)控制是否注入該Bean,也可以通過(guò)條件來(lái)控制注入哪些Bean到Spring容器中。
比如我們熟悉的:@EnableAsync 、@EnableCaching、@EnableScheduling等等統(tǒng)一采用的都是借助@Import注解來(lái)實(shí)現(xiàn)的。
下面我們就通過(guò)示例來(lái)了解@Import三種用法!
一、引入普通類
有個(gè)用戶類如下
@Data
public class UserConfig {
/** 用戶名*/
private String username;
/**手機(jī)號(hào)*/
private String phone;
}那么如何通過(guò)@Import注入容器呢?
@Import(UserConfig.class)
@Configuration
public class UserConfiguration {
}當(dāng)在@Configuration標(biāo)注的類上使用@Import引入了一個(gè)類后,就會(huì)把該類注入容器中。
當(dāng)然除了@Configuration 比如@Component、@Service等一樣也可以。
測(cè)試
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private UserConfig userConfig;
@Test
public void getUser() {
String name = userConfig.getClass().getName();
System.out.println("name = " + name);
}
}控制臺(tái)輸出
name = com.jincou.importselector.model.UserConfig
如果@Import的功能僅僅是這樣,那其實(shí)它并沒(méi)什么特別的價(jià)值,我們可以通過(guò)其它方式實(shí)現(xiàn)?
@Configuration
public class UserConfiguration {
@Bean
public UserConfig userConfig() {
return new UserConfig();
}
}再比如直接添加@Configuration注解
@Configuration
public class UserConfig {
// ......
}確實(shí)如果注入靜態(tài)的Bean到容器中,那完全可以用上面的方式代替,但如果需要?jiǎng)討B(tài)的帶有邏輯性的注入Bean,那才更能體現(xiàn)@Import的價(jià)值。
二、引入ImportSelector的實(shí)現(xiàn)類
說(shuō)到ImportSelector這個(gè)接口就不得不說(shuō)這里面最重要的一個(gè)方法:selectImports()。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}這個(gè)方法的返回值是一個(gè)字符串?dāng)?shù)組,只要在配置類被引用了,這里返回的字符串?dāng)?shù)組中的類名就會(huì)被Spring容器new出來(lái),然后再把這些對(duì)象注入IOC容器中。
所以這有啥用呢?我們還是用一個(gè)例子演示一下。
1、靜態(tài)import場(chǎng)景(注入已知的類)
我們先將上面的示例改造下:
自定義MyImportSelector實(shí)現(xiàn)ImportSelector接口,重寫selectImports方法
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//這里目的是將UserConfig 注入容器中
return new String[]{"com.jincou.importselector.model.UserConfig"};
}
}然后在配置類引用
@Import(MyImportSelector.class)
@Configuration
public class UserConfiguration {
}這樣一來(lái)同樣可以通過(guò)成功將UserConfig注入容器中。
如果看到這,你肯定會(huì)有疑問(wèn)。我這又是新建MyImportSelector類,又是實(shí)現(xiàn)ImportSelector重寫selectImports方法,然后我這么做有個(gè)卵用呢?
直接把類上加個(gè)@Component注入進(jìn)去不香嗎?這個(gè)ImportSelector把簡(jiǎn)單的功能搞這么復(fù)雜。
接下來(lái)就要說(shuō)說(shuō)如何動(dòng)態(tài)注入Bean了。
2、動(dòng)態(tài)import場(chǎng)景(注入指定條件的類)
我們來(lái)思考一種場(chǎng)景,就是你想通過(guò)開關(guān)來(lái)控制是否注入該Bean,或者說(shuō)通過(guò)配置來(lái)控制注入哪些Bean,這個(gè)時(shí)候就有了ImportSelector的用武之地了。
我們來(lái)舉個(gè)例子,通過(guò)ImportSelector的使用實(shí)現(xiàn)條件選擇是注入本地緩存還是Redis緩存。
1)、定義緩存接口和實(shí)現(xiàn)類
頂層接口
public interface CacheService {
void setData(String key);
}本地緩存 實(shí)現(xiàn)類
public class LocalServicempl implements CacheService {
@Override
public void setData(String key) {
System.out.println("本地存儲(chǔ)存儲(chǔ)數(shù)據(jù)成功 key= " + key);
}
}redis緩存實(shí)現(xiàn)類
public class RedisServicempl implements CacheService {
@Override
public void setData(String key) {
System.out.println("redis存儲(chǔ)數(shù)據(jù)成功 key= " + key);
}
}2)、定義ImportSelector實(shí)現(xiàn)類
以下代碼中根據(jù)EnableMyCache注解中的不同值來(lái)切換緩存的實(shí)現(xiàn)類再spring中的注冊(cè)。
public class MyCacheSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableMyCache.class.getName());
//通過(guò) 不同type注入不同的緩存到容器中
CacheType type = (CacheType) annotationAttributes.get("type");
switch (type) {
case LOCAL: {
return new String[]{LocalServicempl.class.getName()};
}
case REDIS: {
return new String[]{RedisServicempl.class.getName()};
}
default: {
throw new RuntimeException(MessageFormat.format("unsupport cache type {0}", type.toString()));
}
}
}
}3)、定義注解
@EnableMyCache注解就像一個(gè)開關(guān),通過(guò)這個(gè)開關(guān)來(lái)是否將特定的Bean注入容器。
定義一個(gè)枚舉
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyCacheSelector.class)
public @interface EnableMyCache {
CacheType type() default CacheType.REDIS;
}public enum CacheType {
LOCAL, REDIS;
}4)、測(cè)試
這里選擇本地緩存。
@EnableMyCache(type = CacheType.LOCAL)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private CacheService cacheService;
@Test
public void test() {
cacheService.setData("key");
}
}控制臺(tái)輸出
本地存儲(chǔ)存儲(chǔ)數(shù)據(jù)成功 key= key
切換成redis緩存
@EnableMyCache(type = CacheType.REDIS)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private CacheService cacheService;
@Test
public void test() {
cacheService.setData("key");
}
}控制臺(tái)輸出
redis存儲(chǔ)數(shù)據(jù)成功 key= key
這個(gè)示例不是就是Bean的動(dòng)態(tài)注入了嗎?
3、Spring如何使用ImportSelector的場(chǎng)景
SpringBoot有兩個(gè)常用注解 @EnableAsync @EnableCaching 其實(shí)就是通過(guò)ImportSelector來(lái)動(dòng)態(tài)注入Bean
看下@EnableAsync注解,它有通過(guò)@Import({AsyncConfigurationSelector.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}AsyncConfigurationSelector.class
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
public AsyncConfigurationSelector() {
}
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return new String[]{ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
default:
return null;
}
}
}是不是和我上面寫的示例一樣。
總之,向這種還不能決定去注入哪個(gè)處理器(如果你能決定,那就直接@Import那個(gè)類好了,沒(méi)必要實(shí)現(xiàn)接口了),就可以實(shí)現(xiàn)此接口,寫出一些判斷邏輯,不同的配置情況注入不同的處理類。
三、引入ImportBeanDefinitionRegister的實(shí)現(xiàn)類
當(dāng)配置類實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 接口,你就可以自定義往容器中注冊(cè)想注入的Bean。
這個(gè)接口相比與 ImportSelector 接口的主要區(qū)別就是,ImportSelector接口是返回一個(gè)類,你不能對(duì)這個(gè)類進(jìn)行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加屬性之類的。
public class MyImportBean implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 當(dāng)前類的注解信息
* @param registry 注冊(cè)類,其registerBeanDefinition()可以注冊(cè)bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}1、舉一個(gè)簡(jiǎn)單的示例
我們通過(guò)先通過(guò)一個(gè)簡(jiǎn)單的小示例,來(lái)理解它的基本使用
假設(shè)有個(gè)用戶配置類如下
@Data
public class UserConfig {
/** 用戶名*/
private String username;
/**手機(jī)號(hào)*/
private String phone;
}我們通過(guò)實(shí)現(xiàn)ImportBeanDefinitionRegistrar的方式來(lái)完成注入。
public class MyImportBean implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 當(dāng)前類的注解信息
* @param registry 注冊(cè)類,其registerBeanDefinition()可以注冊(cè)bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//構(gòu)建一個(gè) BeanDefinition , Bean的類型為 UserConfig,這個(gè)Bean的屬性u(píng)sername的值為后端元宇宙
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserConfig.class)
.addPropertyValue("username", "后端元宇宙")
.getBeanDefinition();
//把 UserConfig 這個(gè)Bean的定義注冊(cè)到容器中
registry.registerBeanDefinition("userConfig", beanDefinition);
}
}通過(guò)配置類 中引入MyImportBean對(duì)象。
@Import(MyImportBean.class)
@Configuration
public class UserImportConfiguration {
}我們?cè)賮?lái)測(cè)試下
@EnableMyCache(type = CacheType.REDIS)
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private UserConfig userConfig;
@Test
public void test() {
String username = userConfig.getUsername();
System.out.println("username = " + username);
}
}控制臺(tái)輸出
username = 后端元宇宙
說(shuō)明通過(guò)ImportBeanDefinitionRegistrar方式,已經(jīng)把UserConfig注入容器成功,而且還為給bean設(shè)置了新屬性。
然后我們?cè)賮?lái)思考一個(gè)問(wèn)題,就比如我們?cè)谄渌胤揭呀?jīng)將UserConfig注入容器,這里會(huì)不會(huì)出現(xiàn)沖突,或者不沖突的情況下,屬性能不能設(shè)置成功?
我們來(lái)試下
@Import(MyImportBean.class)
@Configuration
public class UserImportConfiguration {
/**
* 這里通過(guò)@Bean注解,將UserConfig注入Spring容器中,而且名稱也叫userConfig
*/
@Bean
public UserConfig userConfig() {
return new UserConfig();
}
}然后我們?cè)賮?lái)跑下上面的測(cè)試用例,發(fā)現(xiàn)報(bào)錯(cuò)了。

2、舉一個(gè)復(fù)雜點(diǎn)的例子
Mybatis的@MapperScan就是用這種方式實(shí)現(xiàn)的,@MapperScan注解,指定basePackages,掃描Mybatis Mapper接口類注入到容器中。
這里我們自定義一個(gè)注解@MyMapperScan來(lái)掃描包路徑下所以帶@MapperBean注解的類,并將它們注入到IOC容器中。
1)、先定義一個(gè)@MapperBean注解,就相當(dāng)于我們的@Mapper注解
/**
* 定義包路徑。(指定包下所有添加了MapperBean注解的類作為bean)
* 注意這里 @Import(MyMapperScanImportBean.class) 的使用
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MapperBean {
}2)、一個(gè)需要注入的bean,這里加上@MapperBean注解。
package com.jincou.importselector.mapperScan;
import com.jincou.importselector.config.MapperBean;
@MapperBean
public class User {
}3)、再定一個(gè)掃描包路徑的注解@MyMapperScan 就相當(dāng)于mybatis的@MapperScan注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyMapperScanImportBean.class)
public @interface MyMapperScan {
/**
* 掃描包路徑
*/
String[] basePackages() default {};
}4)、MyMapperScanImportBean實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口
public class MyMapperScanImportBean implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private final static String PACKAGE_NAME_KEY = "basePackages";
private ResourceLoader resourceLoader;
/**
* 搜索指定包下所有添加了MapperBean注解的類,并且把這些類添加到ioc容器里面去
*
* @param importingClassMetadata 當(dāng)前類的注解信息
* @param registry 注冊(cè)類,其registerBeanDefinition()可以注冊(cè)bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//1. 從BeanIocScan注解獲取到我們要搜索的包路徑
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
if (annoAttrs == null || annoAttrs.isEmpty()) {
return;
}
String[] basePackages = (String[]) annoAttrs.get(PACKAGE_NAME_KEY);
// 2. 找到指定包路徑下所有添加了MapperBean注解的類,并且把這些類添加到IOC容器里面去
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
scanner.setResourceLoader(resourceLoader);
//路徑包含MapperBean的注解的bean
scanner.addIncludeFilter(new AnnotationTypeFilter(MapperBean.class));
//掃描包下路徑
scanner.scan(basePackages);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
5)測(cè)試
這里掃描的路徑就是上面User實(shí)體的位置
@RunWith(SpringRunner.class)
@SpringBootTest
@MyMapperScan(basePackages = {"com.jincou.importselector.mapperScan"})
public class UserServiceTest {
@Autowired
private User user;
@Test
public void test() {
System.out.println("username = " + user.getClass().getName());
}
}運(yùn)行結(jié)果
username = com.jincou.importselector.mapperScan.User
完美,成功!
實(shí)現(xiàn)它的基本思想是:當(dāng)自己需要操作BeanFactory里面的Bean的時(shí)候,那就必須只有它才能做到了。而且它還有個(gè)方便的地方,那就是做包掃描的時(shí)候,比如@MapperScan類似這種的時(shí)候,用它處理更為方便(因?yàn)閽呙璧搅酥苯幼?cè)即可)
到此這篇關(guān)于Spring Boot中@Import三種使用方式!的文章就介紹到這了,更多相關(guān)Spring Boot @Import使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?Mybatis使用resultMap時(shí),屬性賦值順序錯(cuò)誤的巨坑
這篇文章主要介紹了Java?Mybatis使用resultMap時(shí),屬性賦值順序錯(cuò)誤的巨坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java多線程中關(guān)于join方法的使用實(shí)例解析
本文通過(guò)實(shí)例代碼給大家實(shí)例介紹了Java多線程中關(guān)于join方法的使用,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-01-01
Springboot讀取templates文件html代碼實(shí)例
這篇文章主要介紹了Springboot讀取templates文件html代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
springboot中Getmapping獲取參數(shù)的實(shí)現(xiàn)方式
這篇文章主要介紹了springboot中Getmapping獲取參數(shù)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Web服務(wù)器實(shí)例解析
這篇文章主要介紹了java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Web服務(wù)器實(shí)例解析,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02

