Spring–IOC

简介

spring的核心思想是IOC和AOP,IoC - Inversion of Control, 控制反转,通俗点说就是把创建和管理bean的过程转移给了第三方。而这个第三方就是我们说的IOC容器。

Spring容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。控制着bean的依赖注入。

那么这里为什么要叫做控制反转呢?

我们先来对比下两种不同的获取外部对象的资源的方法。

  • 主动去创建相关对象然后再组合。

img

  • IOC/DI容器中

img

在 Spring 中,类的实例化、依赖的实例化、依赖的传入都交由 Spring Bean 容器控制,而不是用new方式实例化对象、通过非构造函数方法传入依赖等常规方式。实质的控制权已经交由程序管理,而不是程序员管理,所以叫做控制反转。

例子

有一个Book类:

1
2
3
4
5
6
7
8
9
public class Book {
private String name ; //书名
private int money; /价格

public Book(String name, int money) {
this.name = name;
this.money = money;
}
}

Person类依赖于Book类,

1
2
3
4
5
6
7
public class Person {
private Book book;

public Person(Book book) {
this.book = book;
}
}

​ 因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现,例如:

1
2
3
4
5
6
7
8
9
<beans>
<bean id="dataSource" class="HikariDataSource" />
<bean id="bookService" class="BookService">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="userService" class="UserService">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>

​ 上述XML配置文件指示IoC容器创建3个JavaBean组件,并把id为dataSource的组件通过属性dataSource(即调用setDataSource()方法)注入到另外两个组件中。

总结

在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean,控制反转通过依赖注入(DI)方式实现对象之间的松耦合关系。

Spring–Bean

简介

​ spring官方文档对bean的解释是:在spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bea是一个由Spring IoC容器实例化、组装和管理的对象。

bean主要包含以下几个概念:

  • Bean容器,或称spring ioc容器,主要用来管理对象和依赖,以及依赖的注入。
  • bean是一个java对象,根据bean规范编写出来的类,并由bean容器生成的对象就是一个bean。
  • bean规范

img

Bean实例化

1.普通构造方法创建

使用比较多的一种创建方式,可以直接配置bean节点,例如:

1
2
3
4
5
public class Demo {
public void add() {
System.out.println("add()---------");
}
}

在xml中简单配置一个bean节点,如下:

1
<bean class="org.test.Demo" id="test"/>

通过以下代码进行简单的测试:

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo test = (Demo) context.getBean("test");
System.out.println(test);
2.静态工厂创建

通过静态构造方法来创建一个bean的实例,如下:

1
2
3
4
5
public class Demo2 {
public void add() {
System.out.println("add2()---------");
}
}

创建一个静态工厂,如下:

1
2
3
4
5
public class Demo2Factory {
public static Demo2 getInstance() {
return new Demo2();
}
}

该工厂中有一个静态方法,该静态方法返回一个的实例,通过Spring的配置文件生成Demo2的实例:

1
<bean id="demo2" class="org.test.demo2Factory" factory-method="getInstance"/>

factory-method属性,该属性指明该类中的静态工厂方法名为getInstance,spring框架根据属性来调用方法来获取Demo2的实例了,测试代码如下:

1
2
3
4
5
6
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo2 demo = (Demo2) context.getBean("demo2");
demo.add();
}
实例工厂创建

通过实例工厂来创建bean实例,例如:

1
2
3
4
5
public class Demo3 {
public void add() {
System.out.println("add3()---------");
}
}

同时有一个工厂方法,如下:

1
2
3
4
5
public class Demo3Factory {
public Demo3 getDemo3() {
return new Demo3();
}
}

在Demo3Factory类中有一个getDemo3的方法,该方法返回一个Demo3类的实例,Spring的配置文件如下:

1
2
<bean class="org.test.Demo3Factory" id="demo3Factory"/>
<bean id="demo3" factory-bean="demo3Factory" factory-method="getDemo3"/>

第一个bean用来获取demo3Factory的实例,第二个bean则根据demo3Factory的实例,然后指定factory-method,通过getDemo3方法来获取Demo3的实例。
测试代码如下:

1
2
3
4
5
6
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo3 demo = (Demo3) context.getBean("demo3");
demo.add();
}

Bean作用域

spring bean 一共有五种作用域。

1.Singleton (缺省作用域、单例类型)

​ 容器中只存在一个共享的Bean,只要id与Bean定义相匹配,那就会是同一个Bean。在容器启动(实例化)时Bean就实例化和初始化(可以通过lazy-init=”true”来设置使得Bean被调用时才初始化)。

2.Prototype (原型类型)

​ 对有状态的Bean建议使用Prototype,对无状态建议使用Singleton。
容器启动时并没有实例化Bean,只有获取Bean时才会被创建,并且每一次都是新建一个对象。

3.request(web的Spring ApplicationContext)

​ 每个HTTP 都会有自己的Bean,当处理结束时,Bean销毁。

4.session(web的Spring ApplicationContext)

​ 每一个Http session有自己的Bean

5.global session(web的Spring ApplicationContext)

​ global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

Bean生命周期

  Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。

img

img

单例管理的对象

Bean在容器启动时就会实例化和初始化,但是可以通过Bean的设置来设置对象的初始化时机
第一种:通过设置

1
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

第二种:通过设置来修改所有bean默认方式的初始化时机

1
<beans default-lazy-init="true">
非单例管理的对象

Spring读取xml文件的时候,并不会立刻创建对象,而是在第一次请求该bean时才初始化(如调用getBean方法时)。容器只会实例化和初始化Bean,不会销毁Bean。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。在初始化后交由调用者管理

Spring–依赖注入(DI)

简介

依赖注入(Dependency Injection,DI),和控制反转含义相同,它们是从两个角度描述的同一个概念。

​ 当某个java实例(调用者)需要另一个java实例(被调用者),我们通常采用的方法是由调用者来创建被调用者的实例。(使用new关键字获得被调用者的实例)。

​ 在spring中,spring在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,调用者通过spring容器获得被调用者实例,被称为依赖注入。

注入方式(bean装配)

​ 依赖注入的本质就是装配,装配是依赖注入的具体行为。在Spring中,注入依赖对象可以采用手工装配或自动装配。

手工装配一般分为两种方式

  • 一种是在XML文件中,通过在bean节点下配置;比如使用属性的setter方法注入依赖对象或者使用构造方法注入。

  • 一种就是在java代码中使用注解的方式进行装配,在代码中加入@Resource或者@Autowired。

setter方法注入

​ 由于setter注入方式具有可选择性和灵活性高的特点,因此它也是实际开发中最常用的注入方式。setter方法更加直观,我们来看一下spring的配置文件:

1
2
3
4
5
6
7
8
9
<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->    
<bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>
<bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>
<bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">
<!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->
<!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->
<!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->
<property name="userDao" ref="userDao4Oracle"></property>
</bean>

接着我们来看一下,setter表示依赖关系的写法

1
2
3
4
5
6
7
8
9
10
11
12
import com.tgb.spring.dao.UserDao;    
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用设值方式赋值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}
构造函数注入

​ 构造函数注入是除setter注入之外的另一种常用的注入方式,它可以保证一些必要的属性在bean实例化时就得到了设置,并在实例化后就可以使用。

使用构造函数注入的前提是: bean必须提供带参的构造函数。

对于构造函数的注入,配置文件可以有以下几种方式:

  • 按类型匹配入参
  • 按索引匹配入参
  • 联合使用类型和索引匹配入参
  • 通过自身类型反射匹配入参

我们看一下spring的配置文件:

1
2
3
4
5
6
7
8
9
10
11

<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->
<bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>
<bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>
<bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">
<!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->
<!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->
<!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->
<constructor-arg ref="userDao4Oracle"/>
</bean>
</beans>

我们再来看一下,构造器表示依赖关系的写法,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
import com.tgb.spring.dao.UserDao;    
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用构造方式赋值
public UserManagerImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}

基于注解

​ 使用注解注入依赖对象不用再在代码中写依赖对象的setter方法或者该类的构造方法,并且不用再配置文件中配置大量的依赖对象,使代码更加简洁,清晰,易于维护。

在Spring IOC编程的实际开发中推荐使用注解的方式进行依赖注入。

  • Autowired是自动注入,自动从spring的上下文找到合适的bean来注入
  • Resource用来指定名称注入
  • Qualifier和Autowired配合使用,指定bean的名称,如
1
2
3
@Autowired  
@Qualifier("userDAO")
private UserDAO userDAO;

Spring容器的配置文件applicationContext.Xml文件中配置以下信息,是一个Spring配置文件的模板:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>   
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
">
</beans>

Spring配置隐式的注册了多个对注释进行解析的处理器,例如:

  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor

在配置文件中打开context:annotation-config节点,告诉Spring容器可以用注解的方式注入依赖对象;其在配置文件中的代码如下:

1
<beans><context:annotation-config></context:annotation-config></beans>

在配置文件中配置bean对象,如下:

1
2
<bean id="userDao" class="com.springtest.dao.impl.UserDAOImpl"></bean>  
<bean id="userBiz" class="com.springtest.biz.impl.UserBizImpl"></bean>

最后,在需要依赖注入的类中,声明一个依赖对象,不用生成该依赖对象的setter方法,并且为该对象添加注解:

1
2
3
4
5
6
7
public class UserBizImpl implements UserBiz {  
@Resource(name="userDao")
private UserDAO userDao = null;
public void addUser() {
this.userDao.addUser();
}
}

@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean时,才会按类型装配。

控制反转与依赖注入的区别

依赖注入和控制反转是对同一件事情的不同描述,

  • 依赖注入是从应用程序的角度在描述:应用程序依赖容器创建并注入它所需要的外部资源;

  • 控制反转是从容器的角度在描述:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

参考文章

Spring注入Bean的几种方式 (juejin.cn)

详解Spring中bean的几种注入方式 - 编程语言 - 亿速云 (yisu.com)

https://www.w3cschool.cn/wkspring

(15条消息) Spring Bean自动装配和注解注入_似水流年-CSDN博客

小白也看得懂的 Spring IoC 核心流程介绍 - 知乎 (zhihu.com)

Spring IOC原理总结 - 简书 (jianshu.com)