前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring IoC 容器基础使用

Spring IoC 容器基础使用

作者头像
李鸿坤
发布2020-07-18 18:40:57
5590
发布2020-07-18 18:40:57
举报
文章被收录于专栏:泛泛聊后端泛泛聊后端

Spring IoC 容器是Java世界对于IoC实现的事实上的工业标准。基本上大型 Java应用都绕不过它。以至于滴滴在转型golang的时候搞了一个go-spring出来。本文介绍了Spring IoC 容器的基本使用。

基本概念

org.springframework.beans和org.springframework.context两个包是Spring框架IoC容器的基础。它们分别提供了BeanFactory和ApplicationContext。顾名思义BeanFactory是一个bean的工厂,它提供了一种配置的机制,可以管理任何对象。而ApplicationContext是它的子接口,对他实现了一些增强,如:

  • 更容易与Spring的AOP特性集成
  • 消息资源处理,可以用于国际化
  • 容器事件的发布
  • 特定于应用程序层的上下文,如用于web应用程序的WebApplicationContext。

简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多增强功能。ApplicationContext是BeanFactory的完整超集。

Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中许多对象中的一个。bean以及它们之间的依赖关系反映在容器使用的配置元数据中。

容器使用
代码语言:javascript
复制
// beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="hhdDevice" class="com.lihongkun.labs.spring.container.devicewriter.HhdWriter"></bean>
    <bean id="ssdDevice" class="com.lihongkun.labs.spring.container.devicewriter.SsdWriter"></bean>
    <bean id="deviceWriter" class="com.lihongkun.labs.spring.container.devicewriter.DeviceWriter">
        <property name="deviceWriter" ref="hhdDevice" />
    </bean>
</beans>

先前举过一个例子,如果托管给spring容器则配置如上。每个对象都是一个bean,用bean的标签来声明,id即其唯一标识,class 为其实现类,容器会通过这个类型去初始化一个对象。上述配置文件中的deviceWriter依赖了一个IDeviceWriter接口的实现,使用hhdDevice注入其中,DeviceWriter的实现中并不知道其存在。

使用BeanFactory来实例化一个IoC容器

代码语言:javascript
复制
package com.lihongkun.labs.spring.container;

import com.lihongkun.labs.spring.container.devicewriter.DeviceWriter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;

public class BeanFactoryLab {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));

        DeviceWriter deviceWriter = beanFactory.getBean("deviceWriter", DeviceWriter.class);
        deviceWriter.saveToDevice();
    }
}

使用ApplicationContext来实例化一个IoC容器

代码语言:javascript
复制
package com.lihongkun.labs.spring.container;

import com.lihongkun.labs.spring.container.devicewriter.DeviceWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextLab {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        DeviceWriter deviceWriter = applicationContext.getBean("deviceWriter", DeviceWriter.class);
        deviceWriter.saveToDevice();
    }
}

实现同样的功能,使用方式类似,bean定义文件可以互通。

配置引用

简单的场景使用一个bean配置文件可以解决问题,如果项目比较复杂,可以按bean的领域进行分类来划分配置文件。这时候就需要使用到import的功能。

代码语言:javascript
复制
<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>
命名和别名

bean除了id 还可以使用name为其命名,name必须是容器中唯一。不仅如此,还能为其使用别名,即alias标签的使用。

代码语言:javascript
复制
<alias name="fromName" alias="toName"/>

这么做只是为了拥有更好的语义性。假设有两个子系统,它们都各自声明了数据源,其实是同一个。在一个应用中集成了两个子系统。则不需要初始化两个数据源。但是又改变其中的配置实现。那么只需要初始化myApp-dataSource,将其设置别名,两个不同的子系统就分别能够引用到了。

代码语言:javascript
复制
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

一致地命名bean使配置更易于阅读和理解。另外,如果使用Spring AOP,按名称相关的bean进行配置,语义性的作用就更加明显了。

Bean实例化方式

Spring容器的初始化bean可以通过几种方式:

构造函数
代码语言:javascript
复制
<bean id="exampleBean" class="examples.ExampleBean"/>

实际使用的是反射,容器在进行实例化的时候直接使用反射调用了ExampleBean的构造函数

静态工厂方法
代码语言:javascript
复制
// application.xml
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

// static factory method
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

class的指定没有区别,只是使用factory-method来告诉容器实例化的时候调用此方法就能实例化,而不同通过反射的方式去创建类对象。

实例工厂方法
代码语言:javascript
复制
// application.xml
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

// instance
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

实例工厂的方式需要先实例化工厂类,然后使用factory-bean来引用。进而使用factory-method来指定使用factory-bean的哪个方法来实例化bean。

依赖注入

Spring容器的依赖注入方式分为以下几种

构造函数
代码语言:javascript
复制
<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg index="0" ref="beanTwo"/>
        <constructor-arg index="1" ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

beanOne使用constructor-arg指定了构造函数的参数进行依赖bean的注入。

如果是一些原始类型常量可以使用type指定类型进行注入,因为有时候类型推断是一件不靠谱的事情。

代码语言:javascript
复制
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
Setter函数
代码语言:javascript
复制
<beans>
    <bean id="hhdDevice" class="com.lihongkun.labs.spring.container.devicewriter.HhdWriter"></bean>
    <bean id="deviceWriter" class="com.lihongkun.labs.spring.container.devicewriter.DeviceWriter">
        <property name="deviceWriter" ref="hhdDevice" />
    </bean>
</beans>

如果class的实现里面有setXXX的属性 则可以使用 property 标签进行属性的注入。

如何选择

基于构造函数和基于Setter函数的依赖注入并不冲突,它们可以混合使用。选择的原则应该倾向于,如果是强依赖则使用构造函数注入,如果是可选的依赖则使用Setter函数注入。

Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,非空检查必须在代码使用依赖项的任何地方执行。setter注入的一个好处是setter方法使该类的对象能够在以后重新配置或重新注入。因此,通过JMX MBeans进行管理是setter注入的一个引人注目的用例。

这个关注点其实做国内业界已经被滥用了,基本上大部分的注入都是基于Setter。

循环依赖

如果主要使用构造函数注入,则有可能创建一个无法解决的循环依赖场景。

代码语言:javascript
复制
public class CircularBeanA {
    private CircularBeanB circularBeanB;

    public CircularBeanA(CircularBeanB circularBeanB){
        this.circularBeanB = circularBeanB;
    }
}

public class CircularBeanB {
    private CircularBeanA circularBeanA;

    public CircularBeanB(CircularBeanA circularBeanA){
        this.circularBeanA = circularBeanA;
    }
}

如上述两个类,使用基于构造函数的依赖注入,配置如下

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="circularBeanA" class="com.lihongkun.labs.spring.container.circular.CircularBeanA">
        <constructor-arg ref="circularBeanB" />
    </bean>

    <bean id="circularBeanB" class="com.lihongkun.labs.spring.container.circular.CircularBeanB">
        <constructor-arg ref="circularBeanA" />
    </bean>

</beans>

那么容器初始化的时候将抛出一个BeanCurrentlyInCreationException并且提示是不是循环依赖了。这时候如果使Setter注入则可以解决。

代码语言:javascript
复制
// 需要改造下上述两个类的实现
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="circularBeanA" class="com.lihongkun.labs.spring.container.circular.CircularBeanA">
        <property name="circularBeanB" ref="circularBeanB" />
    </bean>

    <bean id="circularBeanB" class="com.lihongkun.labs.spring.container.circular.CircularBeanB">
        <property name="circularBeanA" ref="circularBeanA" />
    </bean>

</beans>
作用域

Spring Bean的作用域除了常见的Singleton 和 Prototype 还有随着扩展功能而增加的Request, Session, Application, and WebSocket Scopes

Singleton

顾名思义是一个单例模式的bean,也就是在容器中只有一个实例存在。

Prototype

每次需要的时候进行创建

Request, Session, Application, and WebSocket

Request, Session, Application, and WebSocket 作用域只存在于 XmlWebApplicationContext,通常是把Web相关的bean创建托管到Spring 容器的时候才存在。最典型的应用就是Spring MVC,其中请求或者会话对象的。

小结

Spring IoC容器 提供了一套基础配置框架,让使用者把应用程序中类的管理托管到容器中。被托管到容器中实例化的对象我们称为Bean。通常使用ApplicationContext 及其子类来实例化Spring IoC容器。

Bean可以进行命名和使用别名、指定实例化的方式、指定实例化后的作用域、设定依赖注入的方式 和 注入的Bean。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 泛泛聊后端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本概念
  • 容器使用
  • 配置引用
  • 命名和别名
  • Bean实例化方式
    • 构造函数
      • 静态工厂方法
        • 实例工厂方法
        • 依赖注入
          • 构造函数
            • Setter函数
              • 如何选择
                • 循环依赖
                • 作用域
                  • Singleton
                    • Prototype
                      • Request, Session, Application, and WebSocket
                      • 小结
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档