SpringBoot @import的使用

SpringBoot极大的简化了配置,尤其是繁琐的XML,在Bean定义时,我们只需在类上加上@Configuration,然后定义方法上面@Bean就可以初始化一个Bean。但在SpringBoot的使用中发现,SpringBoot启动类@SpringBootApplication注解默认的扫描路径是当前目录以及子目录,所以如果你应用第三方的包,是不会被扫描到的。怎么才能加载我们自定义路径上的类呢?

指定加载路径

最直接的方式就是指定package,让spring容器去扫描路径下的类并加载,当然被扫类上必须要用注解标识(@Configuration-配置类,@Componse等)

@SpringBootApplication(scanBasePackages ={"yyy.com","xxx"} )

通过spring.factories文件配置

如果没有在package扫描路径里,比如引入的第三方包,可以通过META-INF/spring.factories里用org.springframework.boot.autoconfigure.EnableAutoConfiguration来制定。

spring-boot-autoconfigure包里的配置类都是通过这种方式引入的。

当然,这个方式需要程序使用@EnableAutoConfiguration注解,这个注解是通过AutoConfigurationImportSelector来扫描spring.factories文件,把定义的配置类引入的。

这种方式自定定义starters时常用到。

使用@Import

这是我们的重点,可以通过使用@Import注解导入相关的类,加载到容器?》

@Import注解在4.2之前只支持导入配置类(需要加@Configuartion注解的是配置类),在4.2,@Import注解支持导入普通的java类(什么注解都诶呦),并将其声明成一个bean。

看源码注解

1
2
Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
* {@link ImportBeanDefinitionRegistrar} implementations

可以看出@Import支持三种类型的导入

  • 普通的类也可以,不解释,看文章开头
  • @Configuration注解的类

    这个比较简单,如果明确知道需要引入哪个配置类,直接引入就可以。

  • 实现接口:ImportSelector

    如果并不确定引入哪个配置类,需要根据@Import注解所标识的类或者另一个注解(通常是注解)里的定义信息选择配置类的话,用这种方式。

    比较典型的是注解@EnableTransactionManagement

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({TransactionManagementConfigurationSelector.class})
    public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default 2147483647;
    }

    是通过TransactionManagementConfigurationSelector类,根据注解@EnableTransactionManagement所指定的AdviceMode来选择使用哪个配置类的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
    case PROXY:
    return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
    case ASPECTJ:
    return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
    default:
    return null;
    }
    }
    }
  • 实现接口:ImportBeanDefinitionRegistrar

所有的Bean生成都有个顺序: 定义 --> 创建 --> 初始化.

在使用Spring的时候我们知道如果想自动并且动态的定义一个Bean并加入容器,有几种方法

BeanFactoryPostProcessor,

BeanDefinitionRegistryPostProcessor,

BeanPostProcessor

BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor的子类

BeanFactoryPostProcessorBeanFactory的后置处理器.

BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法在Bean定义但还没被创建的时候执行

BeanFactoryPostProcessorpostProcessBeanFactory方法在Bean创建但还没被初始化的时候执行

BeanPostProcessor是bean的后置处理,可以再Bean初始化前后做一些操作

通过回顾我们知道要自动定义Bean有这几种方式,那么这里ImportBeanDefinitionRegistrar接口也有类似的功能可以通过实现该接口,动态定义BeanDefinition并加入容器。

最典型的应用就是mybatis,使用工具自动生成了一批mapper和entity,而如何把这些普通的类放入容器,就是通过注解@MapperScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Target(ElementType.TYPE)

@Documented

@Import(MapperScannerRegistrar.class)

public @interface MapperScan {

String[] value() default {};

String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

Class<? extends Annotation> annotationClass() default Annotation.class;

Class<?> markerInterface() default Class.class;

String sqlSessionTemplateRef() default "";

String sqlSessionFactoryRef() default "";

Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

private ResourceLoader resourceLoader;

/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}

Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}

Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}

Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}

Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}

scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}

/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

}

​ 这个注解用@Import引入了MapperScannerRegistrar类,这个类里会取得注解@MapperScan作设置的package,然后通过ClassPathMapperScanner(也可以自定义扫描器,ClassPathBeanDefinitionScanner)扫描这个package下所有的类,并放入容器中。

坚持技术分享,您的支持将鼓励我继续创作!