Spring注入Bean的四种方式,循环依赖

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 一提到 Spring,大家最先想到的是啥?是 AOP和 IOC的两大特性?是 Spring中 Bean的初始化流程?还是基于 Spring的 Spring Cloud全家桶呢?今天我们就从Spring的IOC特性入手,聊一聊Spring中把Bean注入Spring容器的几种方式。

一提到 Spring,大家最先想到的是啥?是 AOP和 IOC的两大特性?是 Spring中 Bean的初始化流程?还是基于 Spring的 Spring Cloud全家桶呢?

今天我们就从Spring的IOC特性入手,聊一聊Spring中把Bean注入Spring容器的几种方式。


我们先来简单了解下IOC的概念:IOC即控制反转,也称为依赖注入,是指将对象的创建或者依赖关系的引用从具体的对象控制转为框架或者IOC容器来完成,也就是依赖对象的获得被反转了。



可以简单理解为原来由我们来创建对象,现在由Spring来创建并控制对象。


xml 方式


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


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


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

<bean name="teacher" class="org.springframework.demo.model.Teacher">
    <property name="name" value="阿Q"></property>
</bean>

对应的实体类代码

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


xml方式存在的缺点如下:


xml文件配置起来比较麻烦,既要维护代码又要维护配置文件,开发效率低;

项目中配置文件过多,维护起来比较困难;

程序编译期间无法对配置项的正确性进行验证,只能在运行期发现并且出错之后不易排查;

解析xml时,无论是将xml一次性装进内存,还是一行一行解析,都会占用内存资源,影响性能。


注解方式


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


@Configuration + @Bean


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


@Configuration用来声明一个配置类,可以理解为xml的<beans>标签

@Bean 用来声明一个bean,将其加入到Spring容器中,可以理解为xml的<bean>标签



简单样例:将 RedisTemplate 注入 Spring


@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        ......
        return redisTemplate;
    }
}


@Import

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


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

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


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


@Configuration
@Import(MyImportSelector.class)
public class MyConfig {}
public class MyImportSelector implements ImportSelector {
 @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"org.springframework.demo.model.Teacher","org.springframework.demo.model.Student"};
    }
}


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

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


@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    ......
}


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

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


@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 : 是 Factory, IOC容器或者对象工厂,所有的Bean都由它进行管理

FactoryBean : 是Bean ,是一个能产生或者修饰对象生成的工厂 Bean,实现与工厂模式和修饰器模式类似

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


先定义实现了FactoryBean接口的类


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加入到容器中


@Configuration
public class MyConfig {
 @Bean
 public TeacherFactoryBean teacherFactoryBean(){
  return new TeacherFactoryBean();
 }
}


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


BDRegistryPostProcessor


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


源码


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的后置处理器,方法postProcessBeanFactory对bean的定义进行控制。今天我们重点来看看postProcessBeanDefinitionRegistry方法:它的参数是BeanDefinitionRegistry,顾名思义就是与BeanDefinition注册相关的。


0.png


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


1.png


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


while (reiterate) {
    ......
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    ......
}


进入该方法


private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, 
    BeanDefinitionRegistry registry) {
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}


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


实战

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


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 {}
}

启动类代码


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);
}


启动并打印结果


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


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

以上就是我们总结的几种将bean注入Spring容器的方式,赶快行动起来实战演练一下吧!


总结


如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、收藏,您的支持是我坚持写作最大的动力。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2天前
|
安全 Java Spring
Spring框架中的单例Bean是线程安全的吗?
Spring框架中的单例Bean是线程安全的吗?
10 1
|
2天前
|
XML 前端开发 Java
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)
5 0
|
2天前
|
存储 缓存 Java
【JavaEE】Spring中注解的方式去获取Bean对象
【JavaEE】Spring中注解的方式去获取Bean对象
3 0
|
2天前
|
存储 Java 对象存储
【JavaEE】Spring中注解的方式去存储Bean对象
【JavaEE】Spring中注解的方式去存储Bean对象
7 0
|
2天前
|
存储 Java 对象存储
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
【JavaEE】DI与DL的介绍-Spring项目的创建-Bean对象的存储与获取
9 0
|
2天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
24 0
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
408 0
|
8月前
|
存储 缓存 Java
Spring为何需要三级缓存解决循环依赖,而不是二级缓存?
今天给大家分享一道大厂面试真题,Spring为何需要三级缓存解决循环依赖,而不是二级缓存?我一共分为五个部分来给大家介绍: 1、什么是循环依赖? 循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。在代码中,如果将两个或多个Bean互相之间持有对方的引用,因为Spring中加入了依赖注入机制,也就是自动给属性赋值。Spring给属性赋值时,将会导致死循环。那么,哪些情况会出现循环依赖呢?
158 0
|
9月前
|
缓存 Java Spring
最通俗的方式理解Spring循环依赖三级缓存
有位粉丝找我,说要耽误我5分钟时间,想让我帮助它理解一下Spring循环依赖的三级缓存,绕晕了一个星期,没有想明白。我想今天,用最通俗易懂的方式给大家重新梳理一下,保证让你听懂了。
81 0
|
缓存 Java Spring
Spring的循环依赖和三级缓存
Spring的循环依赖和三级缓存
811 0
Spring的循环依赖和三级缓存