@Import是一个非常有用的注解,它的长处在于你可以通过配置来控制是否注入该Bean,也可以通过条件来控制注入哪些Bean到Spring容器中。
比如我们熟悉的:@EnableAsync 、@EnableCaching、@EnableScheduling等等统一采用的都是借助@Import注解来实现的。
一、引入普通类
有个用户类如下
public class Test implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("注入成功");
}
}
那么如何通过@Import注入容器呢?
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Import({Test.class})
@Configuration
public class Myclass2 {
}
当在@Configuration标注的类上使用@Import引入了一个类后,就会把该类注入容器中。
当然除了@Configuration 比如@Component、@Service等一样也可以。
测试效果如下:
二、引入ImportSelector的实现类
1、静态import场景(注入已知的类)
我们先将上面的示例改造下:
自定义MyImportSelector实现ImportSelector接口,重写selectImports方法
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.example.eureka.config.Test"};
}
}
然后在配置类引用
package com.example.eureka.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Import({MyImportSelector.class})
@Configuration
public class Myclass2 {
}
2、动态import场景(注入指定条件的类)
我们来思考一种场景,就是你想通过开关来控制是否注入该Bean,或者说通过配置来控制注入哪些Bean,这个时候就有了ImportSelector的用武之地了。
我们来举个例子,通过ImportSelector的使用实现条件选择是注入本地缓存还是Redis缓存。
1)、定义缓存接口和实现类
package com.example.eureka.cache;
public interface CacheService {
void setData(String key);
}
本地缓存 实现类
package com.example.eureka.cache.impl;
import com.example.eureka.cache.CacheService;
public class LocalServicempl implements CacheService {
@Override
public void setData(String key) {
System.out.println("本地存储数据成功,key="+key);
}
}
redis缓存实现类
public class RedisServicempl implements CacheService {
@Override
public void setData(String key) {
System.out.println("redis存储数据成功 key= " + key);
}
}
2)、定义ImportSelector实现类
以下代码中根据EnableMyCache注解中的不同值来切换缓存的实现类再spring中的注册。
public class MyCacheSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(EnableMyCache.class.getName());
CacheType type = (CacheType)annotationAttributes.get("type");
switch (type){
case LOCAL:
return new String[]{
LocalServicempl.class.getName()
};
case REDIS:
return new String[]{
RedisServiceImpl.class.getName()
};
default:
throw new RuntimeException();
}
}
}
3)、定义注解
package com.example.eureka.cache.Annotion;
import com.example.eureka.cache.Enums.CacheType;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Import({com.example.eureka.cache.MyCacheSelector.class})
public @interface EnableMyCache {
CacheType type() default CacheType.REDIS;
}
4)定义枚举
package com.example.eureka.cache.Enums;
public enum CacheType {
LOCAL,REDIS;
}
三、引入ImportBeanDefinitionRegister的实现类
当配置类实现了 ImportBeanDefinitionRegistrar 接口,你就可以自定义往容器中注册想注入的Bean。
这个接口相比与 ImportSelector 接口的主要区别就是,ImportSelector接口是返回一个类,你不能对这个类进行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加属性之类的。
1.定义实体
public class UserConfig implements InitializingBean {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(this.username+"-"+this.password);
}
}
我们通过实现ImportBeanDefinitionRegistrar的方式来完成注入。
package com.example.eureka.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
@Configuration
@Import({MyImportBean.class})
public class MyImportBean implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserConfig.class).addPropertyValue("username", "fengmin").addPropertyValue("password", "12222").getBeanDefinition();
registry.registerBeanDefinition("userconfig",beanDefinition);
}
}
四、复杂运行
Mybatis的@MapperScan就是用这种方式实现的,@MapperScan注解,指定basePackages,扫描Mybatis Mapper接口类注入到容器中。
- 这里我们自定义一个注解@MyMapperScan来扫描包路径下所以带@MyMapperBean注解的类,并将它们注入到IOC容器中。
package com.example.eureka.exmple;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyMapperBeanImport.class)
public @interface MyMapperScan {
/**
* 扫描包路径
*/
String[] basePackages() default {};
}
- 定义MyMapperBean注解
package com.example.eureka.exmple;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyMapperBean {
}
- 定义ImportBeanDefinitionRegister的实现类
package com.example.eureka.exmple;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
public class MyMapperBeanImport implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private final static String PACKAGE_NAME_KEY="basePackages";
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
if(annotationAttributes ==null||annotationAttributes.isEmpty() ){
return;
}
String[] basePackages =(String[]) annotationAttributes.get(PACKAGE_NAME_KEY);
ClassPathBeanDefinitionScanner scanner=new ClassPathBeanDefinitionScanner(registry,false);
scanner.setResourceLoader(resourceLoader);
//路径包含MapperBean的注解的bean
scanner.addIncludeFilter(new AnnotationTypeFilter(MyMapperBean.class));
scanner.scan(basePackages);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader=resourceLoader;
}
}
- 测试
package com.example.eureka.exmple;
import com.example.eureka.exmple.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class TestRunnerApi2 implements ApplicationRunner {
@Autowired
private UserMapper userMapper;
@Override
public void run(ApplicationArguments args) throws Exception {
userMapper.select("user");
}
}
效果如下: