Import注解

概述

在spring的很多自动配置类中,一般都会被注释一个enable注解,而在这个enable注解上一般都会使用@Import注解将一些类注册到spring容器中,这和我们平常使用的@Bean@Configuration注解有什么不同呢?本质上它们都是注册bean的,而@Import注解常见的用法是注解在需要通过注解属性动态注入的配置类的类上,这个说法可能有点绕口,我们可以理解为它需要根据注解里面的某些属性选择性的将一些类注入到spring容器中

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

// 要导入的Configuration,ImportSelector,ImportBeanDefinitionRegistrar或常规组件类
Class<?>[] value();

}

value方法表明该注解可以导入被@Configuration注解的类,ImportSelector类型的类,ImportBeanDefinitionRegistrar类型的类,或者是普通的java类,下面我们就分别看一下如何通过@Import注解将bean注入到spring容器中。

普通java类

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
/**
* @author zyc
*/
@Import(Person.class)
@SpringBootApplication
public class DemoApplication {

private static ApplicationContext CONTEXT;

@Autowired
public DemoApplication(ApplicationContext applicationContext) {
CONTEXT = applicationContext;
}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println(CONTEXT.getBean(Person.class));
}

static class Person {

}

}

执行以上代码一个普通的Person对象就被注入到spring容器中了

spring组件类

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
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.example.demo.DemoApplication.Person;

/**
* @author zyc
*/
@Import(Person.class)
@SpringBootApplication
public class DemoApplication {

private static ApplicationContext CONTEXT;

@Autowired
public DemoApplication(ApplicationContext applicationContext) {
CONTEXT = applicationContext;
}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println(CONTEXT.getBean(Person.class));
}

@Configuration
static class Person {

}
}

@Import导入了被@Configuration注解的类,最终Person对象被注入到spring容器中了,这有点多此一举,将

@Import(Person.class)去掉,效果也是一样的

ImportSelector

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.example.demo;

import com.example.demo.DemoApplication.EnablePerson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* @author zyc
*/
@EnablePerson(speak = true)
@SpringBootApplication
public class DemoApplication {

private static ApplicationContext CONTEXT;

@Autowired
public DemoApplication(ApplicationContext applicationContext) {
CONTEXT = applicationContext;
}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
Person person = CONTEXT.getBean(Person.class);
person.speak();
person.sing();
}

@Configuration
static class Person {

SpeakAbility speakAbility;

SingAbility singAbility;

@Autowired(required = false)
void setSpeakAbility(SpeakAbility speakAbility) {
this.speakAbility = speakAbility;
}

@Autowired(required = false)
void setSingAbility(SingAbility singAbility) {
this.singAbility = singAbility;
}

void speak() {
if (speakAbility != null) {
speakAbility.speak();
return;
}
System.out.println("我不能够说话");
}

void sing() {
if (singAbility != null) {
singAbility.sing();
return;
}
System.out.println("我不能够唱歌");
}
}

@Retention(RUNTIME)
@Target(TYPE)
@Documented
@Import(PersonSelector.class)
@interface EnablePerson {

boolean speak() default false;

boolean sing() default false;

}

static class SpeakAbility {
void speak() {
System.out.println("我能够说话");
}
}

static class SingAbility {
void sing() {
System.out.println("我能够唱歌");
}
}

static class PersonSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 获取EnablePerson注解里面的所有参数值
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(EnablePerson.class.getName(), false);
// 将map转换为AnnotationAttributes
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(annotationAttributes);
Assert.notNull(attributes, () -> "attributes can not be null");
boolean speak = attributes.getBoolean("speak");
boolean sign = attributes.getBoolean("sing");
List<String> list = new ArrayList<>(2);
// 如果speak为true的话,将SpeakAbility注入到spring容器中
if (speak) {
list.add(SpeakAbility.class.getName());
}
// 如果sing为true,将SingAbility注入到spring容器中
if (sign) {
list.add(SingAbility.class.getName());
}
return list.toArray(new String[0]);
}
}
}
  1. 定义一个@EnablePerson注解,该注解有两个属性,代表是否能够说话和唱歌

  2. @EnablePerson注解被@Import(PersonSelector.class)注解,这个PersonSelector很关键,spring会在应用启动时根据这个PersonSelector方法返回的类名称数组,将这些类都注册到spring容器中,在PersonSelector的selectImports方法中,我们能够获取到AnnotationMetadata,这个AnnotationMetadata就是@EnablePerson注解的类上的所有注解信息,(这里就是DemoApplication类上的注解信息,分别是@EnablePerson(speak = true)@SpringBootApplication),然后我们就可以拿到@EnablePerson注解配置的属性,再根据这些属性动态的配置需要注册到spring容器中的bean的名字,因为speak=true,所以最终只有一个SpeakAbility类被注册到了spring容器中

  3. 最终程序输出

    1
    2
    我能够说话
    我不能够唱歌

    当然了,我们也可以修改sing=true,SingAbility就被注册到spring容器中了

ImportBeanDefinitionRegistrar类型

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
package com.example.demo;

import com.example.demo.DemoApplication.PersonRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
* @author zyc
*/
@Import(PersonRegistrar.class)
@SpringBootApplication
public class DemoApplication {

private static ApplicationContext CONTEXT;

@Autowired
public DemoApplication(ApplicationContext applicationContext) {
CONTEXT = applicationContext;
}

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println(CONTEXT.getBean(Person.class));
}

static class Person {

}

static class PersonRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Person.class);
registry.registerBeanDefinition("person", rootBeanDefinition);
}
}
}

@Import(PersonRegistrar.class)添加了一个ImportBeanDefinitionRegistrar实例,使用该方式注册,PersonRegistrar就拥有了手动注册bean的能力,它能够将某个类构造成RootBeanDefinition,然后将这个bean注册到BeanDefinitionRegistry中

总结

  • 前两种注册bean的方式一般很少用到,因为我们可以直接通过@Configuration这种方式让spring帮我们注册bean
  • 后面两种注册方式多见于spring的一些自动配置中,当我们需要根据代码中的配置动态的在程序运行时注册一些bean时,(例如根据注解的属性注册不同的配置)我们就可以使用该方式注册