Spring框架:利用 XML 文档配置依赖注入
- 1 Java 示例代码
- 1.1 Service 与 Dao 层
- 1.2 Web 层
- 2 IOC 中的对象管理
- 2.1 bean 标签
- 2.2 bean 的作用范围与生命周期
- 2.2.1 单例对象
- 2.2.2 多例对象
- 2.3 实例化 Bean 对象的三种方式
- 2.3.1 使用默认无参构造函数
- 2.3.2 使用静态工厂
- 2.3.3 使用实例工厂
- 3 使用XML文档配置依赖
- 3.1 有参构造注入 & setter方法注入
- 3.1.1 有参构造
- 3.1.2 setter 方法
- 3.1.3 使用 p 名称空间注入数据
- 3.2 基本数据类型及 String 类型注入
- 3.3 集合类型注入
- 3.4 从外部文件导入注入内容
- 3.5 导入其它 XML 配置文件
- 4 利用 Spring 容器创建对象
1 Java 示例代码
以下展示一个简单的后端三层架构的 demo,暂时不考虑数据库连接,Web 层简化为一个 main() 方法的调用。后续大部分 XML 配置方法均基于此 demo 演示。
假设一个后端服务涉及到对用户数据的 CRUD 操作和相关方法调用。在 domain 包中定义了对应的封装类 User:
public class User {
private String name;
private String addr;
/*getter & setter*/
}
对应的业务代码和控制调用如下所示。以下省略接口代码的展示,约定类名中包含 Impl
字样的类名去掉 Impl
后就是对应的接口。UserDao 提供一个 save() 方法以打印各种成员属性来模拟具体的业务操作,顶层代码会逐层调用直至该方法。整体业务结构如下图所示:
1.1 Service 与 Dao 层
UserServiceImpl.java
public class UserServiceImpl implements UserService {
private UserDao userDao; // 持久层依赖
public void save() {
userDao.save();
}
/*其他成员&方法*/
}
UserDaoImpl.java
public class UserDaoImpl implements UserDao {
public void save() {
// 方法逻辑:打印成员变量
}
/*其他成员&方法*/
}
1.2 Web 层
UserController.java
public class UserController {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.save();
}
}
2 IOC 中的对象管理
2.1 bean 标签
作用:用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
- id:给对象在容器中提供一个唯一标识。用于获取对象。
- class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
- scope:指定对象的作用范围。
- singleton:单例的,默认值
- prototype:多例的
- request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
- session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
- global session:WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么
globalSession 相当于 session.
- init-method:指定类中的初始化方法名称。
- destroy-method:指定类中销毁方法名称。
2.2 bean 的作用范围与生命周期
2.2.1 单例对象
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象存活:只要容器在,对象一直存活。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
2.2.2 多例对象
每次访问对象时,都会重新创建对象实例。
生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象存活:只要对象在使用中,就一直存活。
- 对象死亡:当对象长时间不用时,被 Java 的垃圾回收器回收。
2.3 实例化 Bean 对象的三种方式
2.3.1 使用默认无参构造函数
在默认情况下:Spring 容器会根据默认无参构造函数来创建类对象。如果 bean 类中没有定义默认无参构造函数,将会创建失败。
<bean id="accountService" class="com.abe.service.impl.UserServiceImpl"/>
2.3.2 使用静态工厂
定义静态工厂:StaticFactory.java
public class StaticFactory {
public static UserDao getUserDao() {
System.out.println("静态工厂:UserDaoImpl创建...");
return new UserDaoImpl();
}
}
使用 StaticFactory 类中的静态方法 getUserDao() 创建对象,并存入 Spring 容器。
- id 属性:指定 bean 的 id,用于从容器中获取
- class 属性:指定静态工厂的全限定类名
- factory-method 属性:指定生产对象的静态方法
!--静态工厂构造配置-->
<bean id="userDao" class="com.abe.factory.StaticFactory" factory-method="getUserDao"/>
2.3.3 使用实例工厂
定义实例工厂:Factory.java
public class Factory {
public UserDao getUserDao() {
System.out.println("工厂:UserDaoImpl创建...");
return new UserDaoImpl();
}
}
先把工厂的创建交给 Spring 来管理,然后在使用工厂的 bean 对象来调用里面的方法
- factory-bean 属性:用于指定实例工厂 bean 的 id。
- factory-method 属性:用于指定实例工厂中创建对象的方法。
<!--定义工厂实例配置-->
<bean id="factory" class="com.abe.factory.Factory"/>
<!--定义Bean实例配置-->
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"/>
3 使用XML文档配置依赖
通过 IOC,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。这种业务层和持久层的依赖关系,在使用 Spring 之后, 就让 Spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
3.1 有参构造注入 & setter方法注入
以 UserService 类为例,实例化 UserService 对象时需要在其内部实例化一个 UserDao 成员。
3.1.1 有参构造
此方式要求类中提供一个对应参数列表的构造函数。
public class UserServiceImpl implements UserService {
// 依赖对象
private UserDao userDao;
// 利用有参构造方法实现依赖注入
public UserServiceImpl(UserDao userDao) {
System.out.println("有参构造:UserService创建");
this.userDao = userDao;
}
/*其它方法和成员*/
涉及的标签:constructor-arg
属性:
- index:指定参数在构造函数参数列表的索引位置
- type:指定参数在构造函数中的数据类型
- name:指定参数在构造函数中的名称,用这个找给谁赋值
以上三个都是找给谁赋值,下面两个指的是赋什么值
- value:它能赋的值是基本数据类型和 String 类型
- ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
<bean id="userService" class="com.abe.service.impl.UserServiceImpl">
<!--实例对象的依赖注入:有参构造方法注入DAO依赖-->
<constructor-arg name="userDao" ref="userDao"/>
</bean>
注意:name 属性的 userDao 值是 UserDao 接口名的首字母小写后得到的。而 ref 属性的 userDao 值是 applicationContext.xml 配置文件中为UserDaoImpl 配置的 bean 标签的 id 值。两者仅仅名字相同,但不代表同一事物。
3.1.2 setter 方法
此方式要求类中提供需要注入成员的 set 方法。在 Service 对象实例化时,Spring 自动调用 set 方法将一个 DAO 实例赋值给 Service 对象,并作为 Service 对象中的一个成员变量。
public class UserServiceImpl implements UserService {
// 依赖对象
private UserDao userDao;
// 利用set方法实现依赖注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
涉及的标签:property
属性:
- name:找的是类中 set 方法后面的部分
- ref:给属性赋值是其他 bean 类型的
- value:给属性赋值是基本数据类型和 String 类型的
<bean id="userService" class="com.abe.service.impl.UserServiceImpl">
<!--实例对象的依赖注入:set方法注入DAO依赖-->
<property name="userDao" ref="userDao"/>
</bean>
实际开发中,此种方式较为常用
注意:依赖注入只发生在 Spring 容器中,只有启动 Spring 容器并利用 Spring 容器生成的 Service 对象才能正确调用 Dao 对象的相关方法,外部自行实例化的 Service 对象在执行 Dao 操作时,由于不存在依赖注入,Dao 成员为 null,进而调用相关方法时会抛出空指针异常。
3.1.3 使用 p 名称空间注入数据
此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的 set 方法实现注入功能。
导入 p 名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
">
简化配置
<bean id="userService" class="com.abe.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
3.2 基本数据类型及 String 类型注入
例1:以注入 UserDaoImpl 类中的 String 类型变量 username
和 int 类型变量 age
为例,采用 setter 方法注入方式。在 UserDaoImpl.java 中已经定义了对应的 set 方法:
public class UserDaoImpl implements UserDao {
/*基本数据类型和引用数据类型的依赖注入*/
private String username;
private int age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
/*其他成员和方法*/
}
<bean id="userDao" class="com.abe.dao.impl.UserDaoImpl">
<!--基本数据类型和引用数据类型的依赖注入,利用value属性赋值-->
<property name="username" value="zhangsan"/>
<property name="age" value="18"/>
</bean>
例2:User 封装类中的属性依赖注入
public class User {
private String name;
private String addr;
public void setName(String name) {
this.name = name;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
可以为一个封装类定义多个 bean 对象,届时可以根据 id 属性来实例化不同的对象。
<bean id="user1" class="com.abe.domain.User">
<property name="name" value="tom"/>
<property name="addr" value="beijing"/>
</bean>
<bean id="user2" class="com.abe.domain.User">
<property name="name" value="lucy"/>
<property name="addr" value="tianjin"/>
</bean>
3.3 集合类型注入
顾名思义,就是给类中的集合成员传值,它用的也是 set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组, List,Set,Map,和 Properties。以 AccountServiceImpl.java 为例:
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myProps;
/* setter 及其他方法 */
}
相关属性:
- List 结构的:array,list,set
- Map 结构的:map,entry,props,prop
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"/>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>
3.4 从外部文件导入注入内容
在某些配置中,需要从外部文件中导入一些数据注入到对象中加以使用,典型的案例就是配置数据库连接源。诚然,在配置数据库源 bean 对象时,可以在 <bean> 标签内部添加 <property> 标签配置数据库连接信息。但是在实际开发中,这些数据库连接配置信息通常都会单独保存一个配置文件中以便于维护。
以配置 Druid 数据源为例,为了能够读取外部配置信息,需要在 XML 文档中导入 context 名称空间用于加载外部文件。
<beans xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
完整的数据库源连接配置如下:
<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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--配置xmlns context属性头-->
<!--加载外部jdbc.properties文件,location表示数据库配置文档保存路径-->
<context:property-placeholder location="classpath:sql/jdbc.properties"/>
<!--Spring配置druid数据源-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
对应的数据库配置文件 jdbc.properties(MySQL 8.1):
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1?\
useUnicode=true&\
characterEncoding=UTF-8&\
useOldAliasMetadataBehavior=true&\
autoReconnect=true&\
serverTimezone=UTC
jdbc.username=root
jdbc.password=******
3.5 导入其它 XML 配置文件
随着业务逻辑不断增加,项目体量也随之增大,这时把所有的依赖配置都放在一个 XML 文档中维护起来会十分麻烦。XML 配置支持从其它文档中导入配置,这样,可以对不同架构层面或者业务执行类分成多个配置页面。然后使用一个总配置文档加载所有的子文档,实现以来配置的管理优化。
实现导入其它 XML 配置文件需要 <import> 标签。完整的配置示例如下:
<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
">
<!--导入数据库配置-->
<import resource="classpath:sql/app-sql.xml"/>
<!--导入DAO层的User类配置-->
<import resource="classpath:dao/app-user-dao.xml"/>
<!--导入Service层的User类配置-->
<import resource="classpath:service/app-user-service.xml"/>
</beans>
另外,IntelliJ IDEA 支持可视化配置文档之间的依赖关系。
点击左上角的 Spring 提示图标,选择 Application Context Dependencies
,可以生成 XML 文档的依赖关系,效果非常直观。
4 利用 Spring 容器创建对象
以上总结了使用 XML 文档配置依赖注入的方式,当做好依赖配置后,下面就需要通过 Spring 容器来调用这些类来实现相关的业务了。在原来的 Controller.java 程序中,我们是通过实例化 UserService 的方式来获取对象。
UserService userService = new UserServiceImpl();
而在 Spring 框架下,则需要先获取一个 Spring 容器。获取容器的方式有三种:
- ClassPathXmlApplicationContext:从类的根路径下加载配置文件,当采用 XML 文档配置依赖注入时,推荐使用此类。
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类来创建 Spring 容器,用来读取注解。
获得容器对象后,通过容器提供的 getBean() 方法获取 Service 对象,此时 Spring 在底层会自动配置相关依赖,进而执行具体的业务。获取 Service 对象的方式有两种:
- getBean(“id名”):使用配置文件 bean 标签的 id 属性获取实例对象,需要类型强转。
- getBean(类名.class):利用指定类的 class 对象获取实例对象,无需类型强转。
class 属性重名问题:配置文件中的 bean 标签具有唯一性,即使出现 class 属性重复的情况,方法1依然有效,但是方法2会抛出异常。
利用 Spring 容器的完整 Controller.java 实例如下:
public class UserController {
public static void main(String[] args) {
// 获取IOC容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
// 调用Service执行业务
UserService userService = (UserService) app.getBean("userService");
userService.save();
// 或者
UserService userService1 = app.getBean(UserService.class);
userService1.save();
}
}