Spring IoC 容器是Java世界对于IoC实现的事实上的工业标准。基本上大型 Java应用都绕不过它。以至于滴滴在转型golang的时候搞了一个go-spring出来。本文介绍了Spring IoC 容器的基本使用。
org.springframework.beans和org.springframework.context两个包是Spring框架IoC容器的基础。它们分别提供了BeanFactory和ApplicationContext。顾名思义BeanFactory是一个bean的工厂,它提供了一种配置的机制,可以管理任何对象。而ApplicationContext是它的子接口,对他实现了一些增强,如:
简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多增强功能。ApplicationContext是BeanFactory的完整超集。
Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中许多对象中的一个。bean以及它们之间的依赖关系反映在容器使用的配置元数据中。
// 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容器
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容器
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的功能。
<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标签的使用。
<alias name="fromName" alias="toName"/>
这么做只是为了拥有更好的语义性。假设有两个子系统,它们都各自声明了数据源,其实是同一个。在一个应用中集成了两个子系统。则不需要初始化两个数据源。但是又改变其中的配置实现。那么只需要初始化myApp-dataSource,将其设置别名,两个不同的子系统就分别能够引用到了。
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
一致地命名bean使配置更易于阅读和理解。另外,如果使用Spring AOP,按名称相关的bean进行配置,语义性的作用就更加明显了。
Spring容器的初始化bean可以通过几种方式:
<bean id="exampleBean" class="examples.ExampleBean"/>
实际使用的是反射,容器在进行实例化的时候直接使用反射调用了ExampleBean的构造函数
// 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来告诉容器实例化的时候调用此方法就能实例化,而不同通过反射的方式去创建类对象。
// 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容器的依赖注入方式分为以下几种
<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指定类型进行注入,因为有时候类型推断是一件不靠谱的事情。
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
<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。
如果主要使用构造函数注入,则有可能创建一个无法解决的循环依赖场景。
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;
}
}
如上述两个类,使用基于构造函数的依赖注入,配置如下
<?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注入则可以解决。
// 需要改造下上述两个类的实现
<?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
顾名思义是一个单例模式的bean,也就是在容器中只有一个实例存在。
每次需要的时候进行创建
Request, Session, Application, and WebSocket 作用域只存在于 XmlWebApplicationContext,通常是把Web相关的bean创建托管到Spring 容器的时候才存在。最典型的应用就是Spring MVC,其中请求或者会话对象的。
Spring IoC容器 提供了一套基础配置框架,让使用者把应用程序中类的管理托管到容器中。被托管到容器中实例化的对象我们称为Bean。通常使用ApplicationContext 及其子类来实例化Spring IoC容器。
Bean可以进行命名和使用别名、指定实例化的方式、指定实例化后的作用域、设定依赖注入的方式 和 注入的Bean。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有