依赖注入(Dependency Injection)是 Martin Fowler 在 2004 年提出的关于 “控制反转” 的解释。Martin Fowler 认为 “控制反转” 一词让人产生疑惑,无法直白地理解到底哪方面的控制被反转了。所以 Martin Fowler 建议采用 “依赖注入” 一词来代替 “控制反转”。“依赖注入” 和 “控制反转” 其实就是一个事物的两种不同的说法而已,本质上是一回事。“依赖注入” 是一个程序设计模式和架构模型,有些时候也称为 “控制反转”。尽管在技术上来讲,“依赖注入” 是一个 “控制反转” 的特殊实现,但 “依赖注入” 还指一个对象应用另外一个对象来提供一个特殊的能力。例如,把一个数据库连接以参数的形式传到一个对象的结构方法里,而不是在那个对象内部自行创建一个连接。“依赖注入” 和 “控制反转” 的基本思想就是把类的依赖从类内部转到外部以减少依赖。利用 “控制反转”,对象在被创建时,会由一个调控系统统一进行对象实例的管理,将该对象所依赖对象的引用通过调控系统传递给它。也可以说,依赖被注入对象中。所以 “控制反转” 是关于一个对象如何获取它所依赖对象的引用的过程,而这个过程体现为谁来传递依赖的引用这个职责的反转。控制反转一般分为依赖注入(Dependency Injection,DI)和依赖查找(Dependency Lookup)两种实现类型。其中依赖注入应用比较广泛,Spring 就是采用依赖注入这种方式来实现控制反转的。
Spring 通过 IOC 容器来管理所有 Java 对象及其相互间的依赖关系。在软件开发过程中,系统的各个对象之间、各个模块之间、软件系统与硬件系统之间,或多或少都会存在耦合关系,如果一个系统的耦合度过高,就会造成难以维护的问题。但是完全没有耦合的代码是不能工作的,代码之间需要相互协作、相互依赖来完成功能。IOC 技术恰好解决了这类问题,各个对象之间不需要直接关联,而是在需要用到对方时由 IOC 容器来管理对象之间的依赖关系;对于开发人员来说,只需维护相对独立的各个对象代码即可。
IOC 是一个过程,即对象定义其依赖关系,而其他与之配合的对象只能通过构造函数参数、工厂方法的参数,或者在从工厂方法构造或返回后在对象实例上设置的属性来定义依赖关系,随后,IOC 容器在创建 Bean 时会注入这些依赖项。这个过程在职责上是反转的,就是把原先代码中需要实现的对象创建、依赖的代码反转给容器来帮忙实现和管理,所以称为 “控制反转” 。IOC 的应用有以下两种设计模式。
♞ 反射:在运行状态中,根据提供的类的路径或类名,通过反射来动态地获取该类的所有属性和方法。
♞ 工厂模式:把 IOC 容器当作一个工厂,在配置文件或注解中给出定义,然后利用反射技术,根据给出的类名生成相应的对象。对象生成的代码及对象之间的依赖关系在配置文件中定义,这样就实现了解耦。
org.springframework.beans
和 org.springframework.context
包是 Spring IOC 容器的基础。BeanFactory 接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext 是 BeanFactory 的子接口,它更容易与 Spring 的 AOP 功能集成,进行消息资源处理(用于国际化)、事件发布,以及作为应用层特定的上下文(如用于 Web 应用程序的 WebApplicationContext)。简言之,BeanFactory 提供了基本的配置功能,而 ApplicationContext 在此基础之上增加了更多的企业特定功能。在 Spring 应用中,Bean 是由 Spring IOC 容器进行实例化、组装并受其管理的对象。Bean 和它们之间的依赖关系反映在容器使用的配置元数据中。
通过 Maven 的依赖管理机制 我们只需要写 spring-context 的坐标即可,Maven 会自动将其依赖的 jar 导;但是不使用 Maven 则需要加入 spring-context、spring-beans、spring-core、spring-expression 这4个 jar 包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/11
* @description 持久层
*/
public class HelloWorldDao {
public String get() {
return "Hello World!";
}
}
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/11
* @description 服务层
*/
public class HelloWorldService {
public String show() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
HelloWorldDao helloWorldDao = (HelloWorldDao) applicationContext.getBean("helloWorldDao");
return helloWorldDao.get();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.dao.HelloWorldDao" id="helloWorldDao"></bean>
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService"></bean>
</beans>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/11
* @description 控制层
*/
public class HelloworldController {
@Test
public void show() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
System.out.println(helloWorldService.show());
}
}
用于配置对象交由 Spring 来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。基本属性:id
:Bean 实例在 Spring 容器中的唯一标识;class
:Bean 的全限定名称。
scope:指对象的作用范围,取值如下表:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session |
当 scope 的取值为 singleton 时【单例】 ♞ Bean 的实例化个数:1个 ♞ Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例 ♞ Bean 的生命周期: (生命周期和容器一样长) ♘ 对象创建:当应用加载,创建容器时,对象就被创建了 ♘ 对象运行:只要容器在,对象一直活着 ♘ 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
当 scope 的取值为 prototype 时【多例】 ♞ Bean 的实例化个数:多个 ♞ Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean,每次从容器获取对象,都会创建。 ♞ Bean 的生命周期: ♘ 对象创建:当使用对象时,创建新的对象实例 ♘ 对象运行:只要对象在使用中,就一直活着 ♘ 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了;
♞ init-method
:指定类中的初始化方法名称
♞ destroy-method
:指定类中销毁方法名称
⚐ 无参构造方法实例化【常用】 它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService"></bean>
⚐ 工厂静态方法实例化 工厂的静态方法返回 Bean 实例
public class StaticFactoryBean {
public static HelloWorldService create(){
return new HelloWorldService();
}
}
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService" factory-method="create"/>
⚐ 工厂实例方法实例化 工厂的非静态方法返回 Bean 实例
public class DynamicFactoryBean {
public HelloWorldService create(){
return new HelloWorldService();
}
}
<bean id="factoryBean" class="com.softrware.spring.factory.DynamicFactoryBean"/>
<bean id="helloWorldService" factory-bean="factoryBean" factory-method="create"/>
依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
基于构造函数的依赖注入是通过调用具有多个参数的构造函数的容器来完成的,每个参数表示依赖关系,这与调用具有特定参数的静态工厂方法来构造 Bean 几乎是等效的。基于构造函数的依赖注入通常需要处理传参。构造函数的参数解析是通过参数的类型来匹配的。如果需要注入多个,那么构造器参数的顺序也就是这些参数实例化及装载的顺序即 XML 中配置的顺序。
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 服务层
*/
public class HelloWorldService {
private HelloWorldDao helloWorldDao;
public HelloWorldService(HelloWorldDao helloWorldDao) {
this.helloWorldDao = helloWorldDao;
}
public String show() {
return helloWorldDao.get();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.dao.HelloWorldDao" id="helloWorldDao"></bean>
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<constructor-arg name="helloWorldDao" ref="helloWorldDao"/>
</bean>
</beans>
基于 set 方法的依赖注入是在通过调用无参数构造函数或无参数静态工厂方法来实例化 Bean 后,通过容器调用 Bean 的 set 方法完成的。
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 服务层
*/
public class HelloWorldService {
private HelloWorldDao helloWorldDao;
public void setHelloWorldDao(HelloWorldDao helloWorldDao) {
this.helloWorldDao = helloWorldDao;
}
public String show() {
return helloWorldDao.get();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.dao.HelloWorldDao" id="helloWorldDao"></bean>
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="helloWorldDao" ref="helloWorldDao"/>
</bean>
</beans>
P 命名空间注入本质也是 set 方法注入,但比起上述的 set 方法注入更加方便,主要体现在配置文件中,如下:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.dao.HelloWorldDao" id="helloWorldDao"></bean>
<bean class="~.~.~.service.HelloWorldService" id="helloWorldService" p:helloWorldDao-ref="helloWorldDao"/>
</beans>
直接赋值支持字符串、原始类型的数据。<property/> 中的 value 属性允许以对人友好、易读的形式配置属性或构造参数。Spring 的便利之处就是将这些字符串的值转换为指定的类型。注意:value
它能赋的值是基本数据类型和 String 类型;ref
它能赋的值是其他 Bean 类型,必须得是在配置文件中配置过的 Bean。
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 注入普通数据类型
*/
public class HelloWorldService {
private String name;
private Integer age;
private Boolean sex;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public String show() {
return name + " -- " + age + " -- " + sex;
}
}
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="name" value="张三"/>
<property name="age" value="15"/>
<property name="sex" value="true"/>
</bean>
<!-- 也可以使用 P 命名空间 -->
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService" p:name="" />
</beans>
⚐ List<String>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 注入 List<String>
*/
public class HelloWorldService {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
public String show() {
return list.toString();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="list" >
<list>
<value>大张伟</value>
<value>abc</value>
<value>123</value>
<value>%^*</value>
</list>
</property>
</bean>
</beans>
⚐ List<T>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 注入 List<T>
*/
public class HelloWorldService {
private List<Student> list;
public void setList(List<Student> list) {
this.list = list;
}
public String show() {
return list.toString();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.entity.Student" id="stu1"/>
<bean class="com.softrware.spring.entity.Student" id="stu2"/>
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="list" >
<list>
<ref bean="stu1"/>
<ref bean="stu2"/>
<bean class="com.softrware.spring.entity.Student"/>
<bean class="com.softrware.spring.entity.Student"/>
</list>
</property>
</bean>
</beans>
定义在 <bean> 标签的 <property>,或者 <constructor-arg> 之内的 Bean,称为内部 Bean。内部 Bean 的定义是不需要指定 id 或 name 的。如果指定了,容器也不会用其作为区分 Bean 的标识符,反而会无视内部 Bean 的 scope 属性。所以内部 Bean 总是匿名的,而且总是随着外部 Bean 来创建的。开发者是无法将内部的 Bean 注入外部 Bean 以外的其他 Bean 的。
⚐ Map<T,T>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 注入 Map<String, Student>
*/
public class HelloWorldService {
private Map<String, Student> map;
public void setMap(Map<String, Student> map) {
this.map = map;
}
public String show() {
return map.toString();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.entity.Student" id="stu1"/>
<bean class="com.softrware.spring.entity.Student" id="stu2"/>
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="map" >
<map>
<entry key="stu1" value-ref="stu1"/>
<entry key="stu2" value-ref="stu2"/>
</map>
</property>
</bean>
</beans>
⚐ Properties
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 注入 Properties
*/
public class HelloWorldService {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public String show() {
return properties.toString();
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.softrware.spring.service.HelloWorldService" id="helloWorldService">
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
</beans>
如果一个 Bean 是另一个 Bean 的依赖,通常来说,这个 Bean 也就是另一个 Bean 的属性之一。多数情况下,开发者可以在配置 XML 元数据时使用 <ref/> 标签。然而,有时 Bean 之间的依赖关系不是直接关联的,如需要调用类的静态实例化工具来触发,一个典型的例子是数据库驱动注册。depends-on 属性会使明确的、强迫依赖的 Bean 在引用之前就会初始化。如果想要依赖多个 Bean,可以提供多个名称作为 depends-on 的值,以逗号、空格或分号分割。
默认情况下,ApplicationContext 会在实例化的过程中创建和配置所有的单例 Bean。总的来说,这个预初始化是很不错的。因为这样能及时发现环境上的一些配置错误,而不是系统运行了很久之后才发现。如果这个行为不是迫切需要的,开发者可以通过将 Bean 标记为延迟加载阻止预初始化。延迟初始化的 Bean 会通知 IOC 不要让 Bean 预初始化,而是在被引用时才会实例化。在 XML 中,可以通过 <bean/> 标签的 lazy-init 属性来控制这个行为。 ApplicationContext 之中的延迟加载 Bean 是不会随着 ApplicationContext 的启动而进入预初始化状态的,而那些非延迟加载的 Bean 是处于预初始化状态的。然而,如果一个延迟加载的 Bean 作为另外一个非延迟加载的单例 Bean 的依赖而存在,延迟加载的 Bean 仍然会在 ApplicationContext 启动时加载。因为作为单例 Bean 的依赖,它会随着单例 Bean 的实例化而实例化。可以通过使用 <beans/> 的 default-lazy-init 属性在容器层次控制 Bean 是否延迟初始化。
ClassPathXmlApplicationContext
:它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext
:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext
:当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
其中,当参数的数据类型是字符串时,表示根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。当参数的数据类型是 Class 类型时,表示根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/12
* @description 示例
*/
public class HelloworldController {
@Test
public void show() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
System.out.println(helloWorldService.show());
}
}