Bean注入的四种方式

xml 方式

依稀记得最早接触Spring的时候,用的还是SSH框架,不知道大家对这个还有印象吗?所有的bean的注入得依靠xml文件来完成。

它的注入方式分为:set方法注入、构造方法注入、字段注入,而注入类型分为值类型注入(8种基本数据类型)和引用类型注入(将依赖对象注入)。

以下是set方法注入的简单样例

1
2
3
<bean name="teacher" class="cn.vipwen.domain.Teacher">
<property name="name" value="vipwen"></property>
</bean>

对应的实体类代码

1
2
3
4
5
6
7
8
public class Teacher {

private String name;

public void setName(String name) {
this.name = name;
}
}

xml方式存在的缺点如下:

  1. xml文件配置起来比较麻烦,既要维护代码又要维护配置文件,开发效率低;
  2. 项目中配置文件过多,维护起来比较困难;
  3. 程序编译期间无法对配置项的正确性进行验证,只能在运行期发现并且出错之后不易排查;
  4. 解析xml时,无论是将xml一次性装进内存,还是一行一行解析,都会占用内存资源,影响性能。

注解方式

随着Spring的发展,Spring 2.5开始出现了一系列注解,除了我们经常使用的@Controller、@Service、@Repository、@Component 之外,还有一些比较常用的方式,接下来我们简单了解下。

@Configuration + @Bean

当我们需要引入第三方的jar包时,可以用@Bean注解来标注,同时需要搭配@Configuration来使用。

  • @Configuration用来声明一个配置类,可以理解为xml的``标签
  • @Bean 用来声明一个bean,将其加入到Spring容器中,可以理解为xml的``标签

简单样例:将 RedisTemplate 注入 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
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);

// 用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key
redisTemplate.setKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域(field,get,set),访问修饰符(public,private,protected)
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//value
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

@Import

我们在翻看Spring源码的过程中,经常会看到@Import注解,它也可以用来将第三方jar包注入Spring,但是它只可以作用在上。

例如在注解EnableSpringConfigured上就包含了@Import注解,用于将SpringConfiguredConfiguration配置文件加载进Spring容器。

1
2
@Import(SpringConfiguredConfiguration.class)
public @interface EnableSpringConfigured {}

@Importvalue值是一个数组,一个一个注入比较繁琐,因此我们可以搭配ImportSelector接口来使用,用法如下:

1
2
3
4
5
6
7
8
9
10
@Configuration
@Import(MySelector.class)
public class MyConfig {}

public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"org.springframework.data.redis.core.RedisTemplate"};
}
}

其中selectImports方法返回的数组就会通过@Import注解注入到Spring容器中。

无独有偶,ImportBeanDefinitionRegistrar接口也为我们提供了注入bean的方法。

1
2
3
4
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
......
}

我们点击AspectJAutoProxyRegistrar类,发现它实现了ImportBeanDefinitionRegistrar接口,它的registerBeanDefinitions方法便是注入bean的过程,可以参考下。

如果觉得源代码比较难懂,可以看一下我们自定义的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@Import(value = {MyImportBeanDefinitionRegistrar.class})
public class MyConfig {}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
RootBeanDefinition tDefinition = new RootBeanDefinition(Teacher.class);
// 注册 Bean,并指定bean的名称和类型
registry.registerBeanDefinition("teacher", tDefinition);
}
}
}

这样我们就把Teacher类注入到Spring容器中了。

FactoryBean

提到FactoryBean,就不得不与BeanFactory比较一番。

  • BeanFactory : 是 FactoryIOC容器或者对象工厂,所有的Bean都由它进行管理
  • FactoryBean : 是Bean ,是一个能产生或者修饰对象生成的工厂 Bean,实现与工厂模式和修饰器模式类似

那么FactoryBean是如何实现bean注入的呢?

先定义实现了FactoryBean接口的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TeacherFactoryBean implements FactoryBean<Teacher> {

/**
* 返回此工厂管理的对象实例
**/
@Override
public Teacher getObject() throws Exception {
return new Teacher();
}

/**
* 返回此 FactoryBean 创建的对象的类型
**/
@Override
public Class<?> getObjectType() {
return Teacher.class;
}

}

然后通过 @Configuration + @Bean的方式将TeacherFactoryBean加入到容器中

1
2
3
4
5
6
7
@Configuration
public class MyConfig {
@Bean
public TeacherFactoryBean teacherFactoryBean(){
return new TeacherFactoryBean();
}
}

注意:我们没有向容器中注入Teacher, 而是直接注入的TeacherFactoryBean,然后从容器中拿Teacher这个类型的bean,成功运行。

BDRegistryPostProcessor

看到这个接口,不知道对于翻看过Spring源码的你来说熟不熟悉。如果不熟悉的话请往下看,要是熟悉的话就再看一遍吧😃。

源码

1
2
3
4
5
6
7
8
9
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
// 注册bean到spring容器中
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor接口是BeanFactory的后置处理器,方法postProcessBeanFactorybean的定义进行控制。今天我们重点来看看postProcessBeanDefinitionRegistry方法:它的参数是BeanDefinitionRegistry,顾名思义就是与BeanDefinition注册相关的。

0_jpg

通过观察该类,我们发现它里边包含了registerBeanDefinition方法,这个不就是我们想要的吗?为了能更好的使用该接口来达到注入bean的目的,我们先来看看Spring是如何操作此接口的。

看下invokeBeanFactoryPostProcessors方法,会发现没有实现PriorityOrderedOrderedbean(这种跟我们自定义的实现类有关)会执行以下代码。

1
2
3
4
5
while (reiterate) {
......
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
......
}

进入该方法

1
2
3
4
5
6
7
8
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors,
BeanDefinitionRegistry registry) {

for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

会发现实现了BeanDefinitionRegistryPostProcessor接口的bean,其postProcessBeanDefinitionRegistry方法会被调用,也就是说如果我们自定义接口实现该接口,它的postProcessBeanDefinitionRegistry方法也会被执行。

实战

话不多说,直接上代码。自定义接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

/**
* 初始化过程中先执行
**/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Teacher.class);
//Teacher 的定义注册到spring容器中
registry.registerBeanDefinition("teacher", rootBeanDefinition);
}

/**
* 初始化过程中后执行
**/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

启动类代码

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
MyBeanDefinitionRegistryPostProcessor postProcessor = new MyBeanDefinitionRegistryPostProcessor();
//将自定义实现类加入 Spring 容器
context.addBeanFactoryPostProcessor(postProcessor);
context.refresh();
Teacher bean = context.getBean(Teacher.class);
System.out.println(bean);
}

启动并打印结果

1
org.springframework.demo.model.Teacher@2473d930

发现已经注入到Spring容器中了。