我们在了解完什么是IoC之后,现在我们就来了解Spring中的IoC的用法
文章目录
使用 spring 的 IoC 解决程序耦合
入门案例
接下来我们通过账户的业务层和持久层间的依赖关系的案例来演示 Spring 的 IoC。
- 导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
(Spring 目录结构:)
- docs :API 和开发规范.
- libs :jar 包和源码.
- schema :约束.
注意,因为 Spring 5 版本是用 JDK8 编写的,所以我们的 JDK 版本需要是 8 及以上,同时如果使用 Tomcat 的话, Tomcat 版本要求 8.5 及以上。
- 创建业务层接口和实现类
package com.cz.service;
/**
* 账户业务层接口
*/
public interface AccountService {
/**
* 模拟保存账户
*/
void saveAccount();
}
package com.cz.service.impl;
import com.cz.dao.AccountDao;
import com.cz.dao.impl.AccountDaoImpl;
import com.cz.service.AccountService;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
- 创建持久层接口和实现类
package com.cz.dao;
/**
* 账户的持久层接口
*/
public interface AccountDao {
/**
* 模拟保存账户
*/
void saveAccount();
}
package com.cz.dao.impl;
import com.cz.dao.AccountDao;
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements AccountDao {
public void saveAccount(){
System.out.println("保存了账户");
}
}
- 编写配置文件
bean.xml
(该文件需要放在resources
路径下,名称是任意的,但是不能是中文)
<?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">
<!-- 把对象的创建交给Spring来管理 -->
<!-- 配置serive -->
<bean id="accountService" class="com.cz.service.impl.AccountServiceImpl"></bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.cz.dao.impl.AccountDaoImpl"></bean>
</beans>
- 测试代码及运行结果如下
package com.cz.ui;
import com.cz.dao.AccountDao;
import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 模拟表现层,用于调用业务层
*/
public class Client {
/**
* 获取Spring容器IoC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
AccountService as = (AccountService)ac.getBean("accountService");
AccountDao adao = ac.getBean("accountDao",AccountDao.class);
System.out.println(as);
System.out.println(adao);
as.saveAccount();
}
}
Spring 基于 XML 的 IOC 详解细节
spring 中工厂的类结构图
ApplicationContext 接口的常用实现类
在开发中我们一般使用BeanFactory
的子接口ApplicationContext
,它的常用实现类如下表:
类名称 | 作用 |
---|---|
ClassPathXmlApplicationContext |
从类的根路径加载配置文件,要求配置文件必须在类路径下,不在的话,加载不了。推荐 |
FileSystemXmlApplicationContext |
从磁盘任意路径加载配置文件,必须要有访问权限,不推荐 |
AnnotationConfigApplicationContext |
使用注解配置容器对象时,需要使用此类来创建 Spring 容器。它用来读取注解 |
BeanFactory 和 ApplicationContext 的区别
BeanFactory
才是Spring
核心容器的顶级接口
(多例对象适用)。- 在构建核心容器时,创建对象的默认方式为
延迟加载
。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
- 在构建核心容器时,创建对象的默认方式为
ApplicationContext
是BeanFactory
的子接口
(单例子对象适用)。- 在构建核心容器时,创建对象的默认方式为
立即加载
。也就是说,只要一读取完配置文件就会马上创建配置文件中配置的所有对象(singleton对象
)。
- 在构建核心容器时,创建对象的默认方式为
Bean 标签
- 作用:
- 用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
- 属性:
id
:对象的唯一标识,用于获取对象。class
: 对象的全限定类名,用于反射创建对象。scope
: 指定对象的作用范围,有以下取值:singleton
: 默认值,单例的。prototype
: 多例的。request
: WEB 项目中的请求范围,Spring 创建一个 Bean 对象,并将对象存入request
域中。session
: WEB 项目中的会话范围,Spring 创建一个 Bean 对象,并将对象存入session
域中。global-session
: WEB 项目中,应用在 Portlet (集群)环境的会话范围(全局会话范围),如果没有 Portlet 环境则相当于 session。
init-method
: 指定类中的初始化方法名称,创建对象前调用。destroy-method
: 指定类中的销毁方法名称,销毁对象前调用。factory-bean
: 指定工厂 Bean 对象的唯一标识 (Id),将通过工厂创建对象。factory-method
: 指定工厂中创建对象的方法,Spring 会通过该方法来创建对象。
global-session解析:
bean 的作用范围和生命周期
-
单例对象:
scope="singleton
"
一个应用只有一个对象的实例。它的作用范围就是整个引用。- 生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
总结:单例对象的生命周期和容器相同。
- 生命周期:
-
多例对象:
scope="prototype"
每次访问对象时,都会重新创建对象实例。- 生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时且没有别的对象引用时,被 java 的垃圾回收器回收了。
- 生命周期:
实例化 Bean 的三种方式
- 使用默认无参构造方法
<bean id="accountService" class="com.cz.service.impl.AccountServiceImpl"></bean>
在Spring的配置文件中使用
bean
标签,配以id
和class
属性之后,且没有其他属性和标签时,使用的就是默认构造函数创建bean对象。
在默认情况下,它会根据无参构造方法来创建类对象。如果类中没有无参构造方法,将会创建失败
- 使用实例工厂的方法,使用实例工厂的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
package com.cz.factory;
import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;
/**
* 模拟工厂类(该类可能是存在jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFactory {
public AccountService getAccountService(){
return new AccountServiceImpl();
}
}
<bean id="instanceFactory" class="com.cz.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
先把工厂的创建交给 Spring 来管理,然后再使用工厂中的方法来创建对象。
factory-bean
属性:用于指定实例工厂 bean 的 id。
factory-method
属性:用于指定实例工厂中创建对象的方法。
- 使用静态工厂的静态方法,使用静态工厂的方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
package com.cz.factory;
import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;
public class StaticFactory {
public static AccountService getAccountService(){
return new AccountServiceImpl();
}
}
<bean id="accountService" class="com.cz.factory.StaticFactory" factory-method="getAccountService"></bean>
因为是静态工厂,所以无需先创建工厂,直接通过工厂的静态方法来创建对象。
使用 StaticFactory 类中的静态方法 getAccountService 创建对象,并存入 spring 容器
id 属性
:指定 bean 的 id,用于从容器中获取
class 属性
:指定静态工厂的全限定类名
factory-method 属性
:指定生产对象的静态方法
Spring 的依赖注入
依赖注入的概念
依赖注入:(Dependency Injection,缩写 DI)。它是 spring 框架核心IoC的具体实现
。我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。IoC解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
-
能注入的数据:
- 基本类型和String
- 其它bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
-
注入的方式:
- 使用构造函数提供
- 使用set方法提供
- 使用注解提供(下篇文章说明)
构造函数注入
- 顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,
赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入
。具体代码如下:
package com.cz.service.impl;
import com.cz.service.AccountService;
import javafx.scene.chart.PieChart;
import java.util.Date;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements AccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age,Date birthday){
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了");
System.out.println(name+","+age+","+birthday);
}
}
<!-- 构造函数注入 -->
<bean id="accountService" class="com.cz.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="一个Java小白"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>
<!-- 配置一个日期对象用于注入到 service 中 -->
<bean id="date" class="java.util.Date"></bean>
<constructor-arg>
标签用于构造函数注入,属性如下:
index
: 指定要注入的参数在构造方法参数列表中索引位置,索引从0开始。type
:指定要注入的参数的数据类型,该数据类型也是构造方法中某个或某些参数的类型。name
:指定要注入的参数的名称,这个是最方便也是最常用的。value
: 用于提供基本类型和 String 类型的数据。ref
:用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
set 方法注入(更常用的方式)
- 顾名思义,就是在类中提供需要注入成员的 set 方法,通过类中的 setXxx() 方法给成员变量赋值。具体代码如下:
package com.cz.service.impl;
import com.cz.service.AccountService;
import java.util.Date;
public class AccountServiceImpl2 implements AccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了");
System.out.println(name+","+age+","+birthday);
}
}
<!-- set 方法注入 -->
<bean id="accountService2" class="com.cz.service.impl.AccountServiceImpl2">
<property name="name" value="一个Java小白"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="date"></property>
</bean>
<property>
标签用于 set 方法注入,属性如下:
name
: 指定注定时所调用的 set 方法名称。value
: 用于提供基本类型和 String 类型的数据。
-ref
:用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行。
注入集合属性
- 顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,
List,Set,Map,Properties
。具体代码如下:
package com.cz.service.impl;
import com.cz.service.AccountService;
import java.util.*;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl3 implements AccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount(){
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<!-- 注入集合属性-->
<bean id="accountService3" class="com.cz.service.impl.AccountServiceImpl3">
<!-- 给数组注入数据 -->
<property name="myStrs">
<array>
<value>123</value>
<value>456</value>
<value>789</value>
</array>
</property>
<!-- 给 List 注入数据 -->
<property name="myList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<!-- 给 Set 注入数据 -->
<property name="mySet">
<set>
<value>qqq</value>
<value>www</value>
<value>eee</value>
</set>
</property>
<!-- 给 Map 注入数据 -->
<property name="myMap">
<map>
<entry key="1">
<value>1111</value>
</entry>
<entry key="2" value="2222"></entry>
</map>
</property>
<!-- 给 Properties 注入数据 -->
<property name="myProps">
<props>
<prop key="aaa">mmmmmm</prop>
<prop key="bbb">nnnnnn</prop>
</props>
</property>
</bean>
- 对于集合类型的注入,可以将所涉及的标签分为两组:
- 用于给 List 结构(单列)注入的:
<list>
、<array>
、<set>
- 用于给 Map 结构(键值对)注入的 :
<map>
、<props>
- 当结构相同时,使用的标签可以互换,也就是说,给 List 集合注入数据时,使用
<array>
或者<set>
也是可以的。