精读SpringIoC容器理念及springbeans、context模块关键接口类

控制反转

在Spring框架中,Bean的实例化和组装都是由IoC容器通过配置元数据完成的。本节主要介绍Spring IoC容器的理念,以及springbeans模块和spring-context模块中的几个关键接口类。

IoC和DI简介

IoC(Inversion of Control)是“控制反转”的意思。如何理解“控制反转”这个词呢?首先我们需要知道反转的是什么,是由谁来控制。在Spring框架没有出现之前,在Java面向对象的开发中,开发者通过new关键字完成对Object的创建。Spring框架诞生后,是通过Spring容器来管理对象的,因此Object的创建是通过Spring来完成的。最终得出结论:控制反转指的是由开发者来控制创建对象变成了由Spring容器来控制创建对象,创建对象和销毁对象的过程都由Spring来控制。以Spring框架为开发基础的应用尽量不要自己创建对象,应全部交由Spring容器管理。

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接口代表SpringIoC容器,它负责实例化、配置和组装Beans。容器通过读取元数据的配置来获取对象的实例化,以及配置和组装的描述信息。元数据可以用XML、Java注解或Java配置代码表示应用的对象及这些对象之间的内部依赖关系。

Spring框架提供了几个开箱即用的ApplicationContext接口的实现类,如
Class-PathXmlApplicationContext、FileSystemXmlApplicationContext和AnnotationConfigApplicationContext等。在独立应用程序中,通常创建一个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/bea

ns

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="zhangsan"/>

</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

ClassPathXmlApplication

Context("spring.xml");

UserService userService =

context.getBean("userService",

UserService.class);

userService.getUser();

//打印结果

}

}

打印结果:

id:1

name:zhangsan

在上面的示例代码中,
ClassPathXmlApplicationContext可以通过spring.xml文件获取UserService类的配置元数据,通过Spring容器的组装和实例化UserService类,最终正确调用getUser()方法打印出定义的属性值。

2. 基于Java注解的配置

从Spring 2.5开始,支持以Java注解的方式来配置Bean,如@Scope、@Service、@Component、@Controller、@Repository、@Autowired和@Resource等注解。

@Scope注解可以设置Bean的作用域。Spring容器实例化的对象默认是单例的,如果想要修改作用域,可以通过@Scope注解进行修改。

表1.1中列出了@Scope注解使用的一些作用域。

表1.1 @Scope注释的作用域

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("zhangsan");

userService.getUser();

//调用方法,打印属性值

}

}

打印结果:

id:1

name:zhangsan

通过
AnnotationConfigApplicationContext类可以获取被@Service注解的User-Service实例化对象,并正确打印属性值。通过Java注解的方式同样完成了实例的初始化,说明XML配置方式可以完全被替换。

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("zhangsan");

userService.getUser();

//打印属性值

}

}

打印结果:

id:1

name:zhangsan

从上面的例子看,基于Java配置实例化对象的方式不再需要对spring.xml的依赖。基于Java注解或Java配置来管理Bean的方式已经是当今编程的流行方式。后文介绍Spring Boot时,还会介绍一些新的注解或配置方式。

Bean管理

如图1.2所示为Bean被Spring容器组装的简单过程。首先通过XML配置、注解配置或Java配置等3种方式配置元数据,然后装配BeanDefinition属性,如果有增强设置,如实现了BeanFactoryPostProcessor或BeanPostProcessor接口,则进行拦截增强处理,最后通过配置的初始化方法完成Bean的实例化。

图1.2 Bean的组装过程

spring-beans模块是Spring容器组装Bean的核心模块,它提供了组装Bean的几个关键接口,如图1.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接口扩展的例子。BeanFactoryPostProcessor接口方法的输入参数是
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

从打印结果中可以看到,在UserService实例化之前和之后都打印了日志,因此通过BeanPostProcessor可以做一些增强逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值