31

Spring探索01 – @Import注解

 4 years ago
source link: https://www.tuicool.com/articles/riaEVvN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Overview

Spring中 @ Import 注解最初主要是在配置类中使用,目的是引入其他的配置类( @ Configuration )并实现自动注入。

目前 Import 并不只是支持引入 @ Configuration 注解的类,也支持引入 ImportSelector ImportBeanDefinitionRegistrar 接口的实现类,甚至可以引入普通的Java Bean并完成注入。

写了一个简单的应用来进行测试: spring-boot-import

做些说明。在应用中定义了一个 Worker 类,应用做的事情就是结合 @ Import 注解用不同的方式注入 Worker 类的多个Bean实例。 每个 Worker Bean的实例通过name进行区分。

代码的一个核心是 MyConfig 类,代码如下:

@Configuration
@Import({MyAnotherConfig.class,
        MyImportSelector.class,
        WorkerBeanDefinitionRegistrar.class})
public class MyConfig {
 
 
    @Bean("tom")
    public Worker worker() {
        return new Worker("tom", 2);
    }
 
 
}

这个类中包含 @ Configuration 注解,说明是一个配置类,Spring会自动注入这个类的实例。此外这个类还通过 @ Bean 注解注入了一个Worker Bean实例“tom”,又通过 @ Import 接口引用三个其他类,目的是尝试注入其他的Worker Bean实例。最后在 WorkerService 中尝试获取并逐行打印注入的Worker实例:

public class WorkerService {
 
    @Autowired
    private WorkerBeanFactory factory;
 
    public List<Worker> allWorkers() {
        List<Worker> list = new ArrayList<>(4);
        list.add(factory.get("tom"));
        list.add(factory.get("anotherTom"));
        list.add(factory.get("selectTom"));
        list.add(factory.get("jerry"));
 
        list.stream().forEach(System.out::println);
 
        return list;
    }
}

接下来详细介绍下这个过程中是如何使用 @ Import 接口的。

引入普通的Java Bean

MyConfig 类中使用 @ Import 注解注入的 MyAnotherConfig 类没有继承任何超类或实现任何接口:

public class MyAnotherConfig {
 
    @Bean("anotherTom")
    public Worker worker() {
        return new Worker("anotherTom", 2);
    }
 
}

可以看到,如果不是内部的一个方法使用了 @ Bean 注解,它就是一个普通的Java Bean了。也是通过这个 @ Bean 注解,实现了另一个Worker Bean的注入。

引入ImportSelector实现类

根据Spring的文档, ImportSelector 的作用是根据一些注解的属性来决定使用哪些 @ Configuration 类。也就是配置类的选择器。通常在spring的引用包中会看到 ImportSelector 的实现。

因此这里定义了另一个配置类 MySelectConfig ,不过为了避免当前应用下Spring的自动注入,没有在这个类中添加 @ Configuration 注解。

public class MySelectConfig {
 
    @Bean("selectTom")
    public Worker worker() {
        return new Worker("selectTom", 1);
    }
}

看起来和前面的 MyAnotherConfig 是一样的。不过和前例不一样的是: MySelectConfig 类的注入是通过 MyImportSelector 来实现的。

MyImportSelector 的实现如下:

public class MyImportSelector implements ImportSelector {
 
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MySelectConfig.class.getName()};
    }
 
}

这里没有基于AnnotationMetadata进行判定就直接返回了配置类的名称,在实际工作中不是一个好的实践。不过我们这里只是做一个演示,不需纠结太多。

引入ImportBeanDefinitionRegistrar实现类

ImportBeanDefinitionRegistrar ImportSelector 的作用是有着根本上的不同的: ImportSelector 的作用是提供配置类;而 ImportBeanDefinitionRegistrar 的作用则是根据类定义完成相应Bean实例的创建。

通常 ImportBeanDefinitionRegistrar 多与 ClassPathMapperScanner 配合使用。 ClassPathMapperScanner 可以用来扫描指定的package,获取目标类并完成相应实例的创建。具体应用如MyBatis的 @ Mapper 注解的解释。

看下在我们示例中的使用:

public class WorkerBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                                        BeanDefinitionRegistry registry) {
        
        BeanDefinitionBuilder workerBuilder = BeanDefinitionBuilder.rootBeanDefinition(Worker.class);
        registry.registerBeanDefinition("jerry", workerBuilder.getBeanDefinition());
    }
 
}

在这里通过 Worker 类的定义创建了一个名为“jerry”的实例。需要注意:这里虽然完成了 Worker 实例的创建,但是并没有配置任何属性。等在输出注入的Worker Bean的时候我们会看到这个实例的属性都是默认值。

引入Spring Component

使用 @ Import 注解不仅可以引入普通的Java Bean,也可以引入Spring组件类,即需要使用 @ Component 或者 @ Service 等注解标记的类。组建类中通过 @ Autowired 注解引用的其他组件也会被递归引用并注入。

示例应用中的 WorkerService 类并没有使用任何注解标记,而是在使用的时候通过 @ Import 注解进行的引入。

@RestController
@RequestMapping("/worker")
@Import(WorkerService.class)
public class WorkerController {
 
    @Autowired
    private WorkerService service;
 
    @GetMapping("/all")
    public List<Worker> all(){
        return service.allWorkers();
    }
 
}

这样虽然也可以使用,但并不建议这么做。

引入@Configuration注解的类

这个留到最后是因为一开始比较困惑:既然已经有 @ Configuration 注解了,Spring就一定会自动引入这个类的,应该就没必要再使用 @ Import 注解进行引用并注入了。

后来意识到我的想法是有漏洞的:比如一些第三方spring组件包中的配置类,既没有配置packageScan,也没有配置starter,直接使用肯定是不行的。此时使用 @ Import 注解来导入相关的配置类及组件是一个很好地解决方案。

测试

执行 WorkerControllerTest 类的测试方法 all ( ) ,观察测试结果,期间会输出我们创建的几个Worker Bean实例:

Worker{name='tom', age=2}
Worker{name='anotherTom', age=2}
Worker{name='selectTom', age=1}
Worker{name='null', age=0}

可以看到一个Worker实例的属性都是默认值,这个实例即是通过 ImportBeanDefinitionRegistrar 创建的Worker Bean “jerry”。

其他

关于 @ Import 注解的实现原理可以参考 AbstractApplicationContext . refresh -> BeanFactoryPostProcessor -> ConfigurationClassPostProcessor -> ConfigurationClassParser . processImports ( ) 。具体就不展开了。

此外,还有另外一个注解 @ ImportResource 主要用来引入xml或groovy配置文件。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK