Spring Ioc(控制反转)和DI(依赖注入)
在 Spring 框架中,Bean 的实例化和组装都是由 IoC 容器通过配置元数据完成的。本文主要介绍 Spring IoC 容器的理念,以及 spring-beans 模块和 spring-context 模块中的几个关键接口类。
在 Spring 框架没有出现之前,在 Java 面向对象的开发中,开发者通过 new 关键字完成对 Object 的创建。Spring 框架诞生后,是通过 Spring 容器来管理对象的,因此 Object 的创建是通过 Spring 来完成的。
最终得出结论:控制反转指的是由开发者来控制创建对象变成了由 Spring 容器来控制创建对象,创建对象和销毁对象的过程都由 Spring 来控制。
这就是 Class A 依赖 Spring 容器的注入。
在 Spring 框架中,org.springframework.context.ApplicationContext 接口代表 Spring IoC 容器,它负责实例化、配置和组装 Beans。
容器通过读取元数据的配置来获取对象的实例化,以及配置和组装的描述信息。元数据可以用 XML、Java 注解或 Java 配置代码表示应用的对象及这些对象之间的内部依赖关系。
Spring 框架提供了几个开箱即用的 ApplicationContext 接口的实现类,主要包括:
在独立应用程序中,通常创建一个 ClassPathXmlApplication-Context 或 FileSystemXmlApplicationContext 实例对象来获取 XML 的配置信息。
开发者也可以指示容器使用 Java 注解或 Java 配置作为元数据格式,通过 Annotation-ConfigApplicationContext 来获取 Java 配置的 Bean。
在 src/main/resources 目录下新建 spring.xml 文件,内容如上面的代码所示,<bean> 和 </bean>标签用来描述 Bean 的元数据信息。在上面的代码中声明了一个 UserService 类,该类有两个属性,即 id 和 name,通过 <property> 和 </property> 标签直接进行赋值。
UserService实体类的声明代码如下:
编写测试代码,展示通过 Spring 上下文获取 UserService 对象,具体代码如下:
@Scope 注解可以设置 Bean 的作用域。Spring 容器实例化的对象默认是单例的,如果想要修改作用域,可以通过 @Scope 注解进行修改。表 1 中列出了 @Scope 注解使用的一些作用域。
request、session、application 和 websocket 作用域只在 Web 应用环境中使用。在普通的 Spring IoC 容器里只有 singleton 和 prototype 两种作用域,其他的设置会抛出异常。
下面改造基于 XML 配置元数据的例子,将其改成基于 Java 注解的方式来注入 Bean,具体代码如下:
下面增加一个注解类,添加 @ComponentScan 注解,代码如下:
@Configuration 注解一般用来配置类,配置类中可以使用 @Bean 注解来声明某个类的初始化操作;@Import 注解可以导入由 @Configuration 注解的配置类;@Profile 注解可以根据不同环境生成不同的实例。
下面改造基于 Java 注解的案例,给出一个基于 Java 配置的示例。UserService 类去掉 @Service 注解后,将变成普通的 Bean。UserService 类的声明代码如下:
添加测试类代码如下:

图2 Bean的组装过程
spring-beans 模块是 Spring 容器组装 Bean 的核心模块,它提供了组装 Bean 的几个关键接口,如图 2 中的 BeanDefinition、BeanFactoryPostProcessor、BeanPost-Processor 和 BeanFactory 等。
本文将通过两个简单的例子,展现 BeanFactoryPostProcessor 和 BeanPostProcessor 接口的扩展能力。
首先来看一个 BeanFactoryPostProcessor 接口扩展的例子。BeanFactory-PostProcessor 接口方法的输入参数是 ConfigurableListableBeanFactory,使用该参数可以获取相关 Bean 的定义信息。示例代码如下:
对于 BeanPostProcessor 接口的扩展,可以在 Spring 容器实例化 Bean 之后或者执行 Bean 的初始化方法之前添加一些自己的处理逻辑。示例代码如下:
声明:《Java系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
IoC 和 DI 简介
IoC(控制反转)
IoC(Inversion of Control)是“控制反转”的意思。如何理解“控制反转”这个词呢?首先我们需要知道反转的是什么,是由谁来控制。在 Spring 框架没有出现之前,在 Java 面向对象的开发中,开发者通过 new 关键字完成对 Object 的创建。Spring 框架诞生后,是通过 Spring 容器来管理对象的,因此 Object 的创建是通过 Spring 来完成的。
最终得出结论:控制反转指的是由开发者来控制创建对象变成了由 Spring 容器来控制创建对象,创建对象和销毁对象的过程都由 Spring 来控制。
以 Spring 框架为开发基础的应用尽量不要自己创建对象,应全部交由 Spring 容器管理。
DI(依赖注入)
DI(Dependency Injection)称为依赖注入。在 Java 程序中,类与类之间的耦合非常频繁,如 Class A 需要依赖 Class B 的对象 b。而基于 Spring 框架的开发,在 Class A 中不需要显式地使用 new 关键字新建一个对象 b,只需在对象 b 的声明之上加一行注解 @Autowired,这样在 Class A 用到 b 时,Spring 容器会主动完成对象 b 的创建和注入。这就是 Class A 依赖 Spring 容器的注入。
总结/对比
通过上面的解释,我们可以发现 IoC 和 DI 其实是同一概念从不同角度的解释。在 Spring 框架中,org.springframework.context.ApplicationContext 接口代表 Spring IoC 容器,它负责实例化、配置和组装 Beans。
容器通过读取元数据的配置来获取对象的实例化,以及配置和组装的描述信息。元数据可以用 XML、Java 注解或 Java 配置代码表示应用的对象及这些对象之间的内部依赖关系。
Spring 框架提供了几个开箱即用的 ApplicationContext 接口的实现类,主要包括:
- Class-PathXmlApplicationContext
- FileSystemXmlApplicationContext
- AnnotationConfig-ApplicationContext
在独立应用程序中,通常创建一个 ClassPathXmlApplication-Context 或 FileSystemXmlApplicationContext 实例对象来获取 XML 的配置信息。
开发者也可以指示容器使用 Java 注解或 Java 配置作为元数据格式,通过 Annotation-ConfigApplicationContext 来获取 Java 配置的 Bean。
元数据配置
1) 基于 XML 的配置
Spring 框架最早是通过 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"> <!--定义UserService类 --> <bean id="userService" class="com.spring.boot.UserService"> <!--id属性 --> <property name="id" value="1"/> <!--name属性 --> <property name="name" value="www.weixueyuan.net"/> </bean> </beans>
在 src/main/resources 目录下新建 spring.xml 文件,内容如上面的代码所示,<bean> 和 </bean>标签用来描述 Bean 的元数据信息。在上面的代码中声明了一个 UserService 类,该类有两个属性,即 id 和 name,通过 <property> 和 </property> 标签直接进行赋值。
UserService实体类的声明代码如下:
//声明 UserService 类 public class UserService { private Integer id;//用户ID private String name; //用户名称 //getter和setter方法 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } //打印属性值 public void getUser() { System.out.println("id:"+this.id); System.out.println("name:"+this.name); } }以上代码声明了一个 UserService 类,并实现了属性 id 和属性 name 的 setter 和 getter 方法,通过 getUser() 方法打印属性值。
编写测试代码,展示通过 Spring 上下文获取 UserService 对象,具体代码如下:
//测试类 public class SpringXmlTest { public static void main(String[] args) { //通过spring.xml获取Spring应用上下文 ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = context.getBean("userService",UserService.class); userService.getUser(); //打印结果 } }输出结果:
id:1
name:www.weixueyuan.net
2) 基于 Java 注解的配置
从 Spring 2.5 开始,支持以 Java 注解的方式来配置 Bean,如 @Scope、@Service、@Component、@Controller、@Repository、@Autowired 和 @Resource 等注解。@Scope 注解可以设置 Bean 的作用域。Spring 容器实例化的对象默认是单例的,如果想要修改作用域,可以通过 @Scope 注解进行修改。表 1 中列出了 @Scope 注解使用的一些作用域。
作用域 | 说 明 |
---|---|
singleton | Spring IoC容器只有一个单实例 |
prototype | 每次获取一个新实例 |
request | 每一次有HTTP请求时都会产生一个新的实例,同时该实例仅在当前HTTP request 内有效 |
session | 每一次有HTTP请求时都会产生一个新的实例,同时该实例仅在当前HTTP session 内有效 |
application | 全局Web应用级别的作用域,也是在Web环境中使用的,一个Web应用程序对 应一个Bean实例 |
websocket | WebSocket的单实例作用域 |
request、session、application 和 websocket 作用域只在 Web 应用环境中使用。在普通的 Spring IoC 容器里只有 singleton 和 prototype 两种作用域,其他的设置会抛出异常。
下面改造基于 XML 配置元数据的例子,将其改成基于 Java 注解的方式来注入 Bean,具体代码如下:
//注解的方式声明UserService @Service public class UserService { private Integer id;//用户ID private String name;//用户名称 //getter和setter方法 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } //属性值打印 public void getUser() { System.out.println("id:"+this.id); System.out.println("name:"+this.name); } }上面的代码在 UserService 类中加了一个 @Service 注解,spring.xml 配置文件不再使用。
下面增加一个注解类,添加 @ComponentScan 注解,代码如下:
//@ComponentScan 注解用来扫描 UserService 类 @ComponentScan("com.spring.boot") public class SpringAnnotationTest { } @ComponentScan 注解的值是 com.spring.boot,说明 Spring 容器可以自动扫描这个包路径下可管理的类,并对该类进行实例化。添加测试类代码如下: @ComponentScan("com.spring.boot") public class SpringAnnotationTest { public static void main(String[] args) { //通过注解类获取应用上下文 ApplicationContext context = new AnnotationConfigApplication Context(SpringAnnotationTest.class); //获取UserService对象 UserService userService = context.getBean(UserService.class); userService.setId(1); userService.setName("www.weixueyuan.net"); userService.getUser(); //调用方法,打印属性值 } }输出结果:
id:1
name:www.weixueyuan.net
3) 基于 Java 配置的示例
从 Spring 3.0 开始,Spring 框架开始支持基于 Java 的方式来配置元数据,如 @Configuration、@Bean、@Import和@Profile 等注解。@Configuration 注解一般用来配置类,配置类中可以使用 @Bean 注解来声明某个类的初始化操作;@Import 注解可以导入由 @Configuration 注解的配置类;@Profile 注解可以根据不同环境生成不同的实例。
下面改造基于 Java 注解的案例,给出一个基于 Java 配置的示例。UserService 类去掉 @Service 注解后,将变成普通的 Bean。UserService 类的声明代码如下:
//声明 UserService 类 public class UserService { private Integer id; //用户ID private String name; //用户名称 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } //属性值打印 public void getUser() { System.out.println("id:"+this.id); System.out.println("name:"+this.name); } }新增配置类,代码如下:
//基于 @Configuration 注解生成 UserService 对象 @Configuration public class SpringConfigTest { @Bean public UserService userService() { return new UserService(); } }SpringConfigTest 类由 @Configuration 注解,表明这个类是个配置类。由 @Bean 注解的 userService() 方法返回了 UserService 类的实例。
添加测试类代码如下:
@Configuration public class SpringConfigTest { @Bean public UserService userService() { return new UserService(); } public static void main(String[] args) { //通过配置类获取Spring应用上下文 ApplicationContext context = new AnnotationConfigApplication Context(SpringConfigTest.class); UserService userService = context.getBean(UserService.class); userService.setId(1); userService.setName("www.weixueyuan.net"); userService.getUser(); //打印属性值 } }输出结果:
id:1
name:www.weixueyuan.net
Bean 管理
如图 2 所示为 Bean 被 Spring 容器组装的简单过程。首先通过 XML 配置、注解配置或 Java 配置等 3 种方式配置元数据,然后装配 BeanDefinition 属性,如果有增强设置,如实现了 BeanFactoryPostProcessor 或 BeanPostProcessor 接口,则进行拦截增强处理,最后通过配置的初始化方法完成 Bean 的实例化。
图2 Bean的组装过程
spring-beans 模块是 Spring 容器组装 Bean 的核心模块,它提供了组装 Bean 的几个关键接口,如图 2 中的 BeanDefinition、BeanFactoryPostProcessor、BeanPost-Processor 和 BeanFactory 等。
- BeanDefinition:该接口继承自 AttributeAccessor 和 BeanDefinition 两个接口。该接口可以获取 Bean 的元数据配置信息,也可以改变 Bean 的属性。
- BeanFactoryPostProcessor:该接口为函数接口,只有一个方法 postProcessBean-Factory()。 该接口可以通过 ConfigurableListableBeanFactory 参数获取 Bean-Definition,然后对 Bean 的属性进行修改,如把 Bean 的 Scope 从 singleton 改为 prototype 等。
- BeanPostProcessor:该接口有两个方法,即 postProcessBeforeInitialization() 和 postProcessAfterInitialization(),分别用于在 Bean 实例化之前和实例化之后进行额外的操作。 BeanPostProcessor 接口与 BeanFactoryPostProcessor 接口的区别在于,BeanFactoryPostProcessor 接口是在 Bean 实例化之前进行修改。
本文将通过两个简单的例子,展现 BeanFactoryPostProcessor 和 BeanPostProcessor 接口的扩展能力。
首先来看一个 BeanFactoryPostProcessor 接口扩展的例子。BeanFactory-PostProcessor 接口方法的输入参数是 ConfigurableListableBeanFactory,使用该参数可以获取相关 Bean 的定义信息。示例代码如下:
@Component public class BeanFactoryPostProcessorImpl implements BeanFactory PostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBean Factory beanFactory) throws BeansException { //获取UserService的BeanDefinition BeanDefinition beanDefinition = beanFactory.getBeanDefinition ("userService"); //修改Scope属性 beanDefinition.setScope("prototype"); System.out.println(beanDefinition. getScope()); } }输出结果:
prototype
通过输出的结果可以看到,在 UserService 实例化之前修改了该类的作用域,将其从 singleton 改为了 prototype。对于 BeanPostProcessor 接口的扩展,可以在 Spring 容器实例化 Bean 之后或者执行 Bean 的初始化方法之前添加一些自己的处理逻辑。示例代码如下:
@Component public class BeanPostProcessorImpl implements BeanPostProcessor { //在实例化之前操作 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //判断Bean的类型 if(bean instanceof UserService){ System.out.println("postProcessBeforeInitialization bean : " + beanName); } return bean; } //在实例化之后操作 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //判断Bean的类型 if(bean instanceof UserService){ System.out.println("postProcessAfterInitialization bean : " + beanName); } return bean; } }输出结果:
postProcessBeforeInitialization bean : userService
postProcessAfterInitialization bean : userService
声明:《Java系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。