Spring Ioc容器与Bean管理
- Spring快速入门
- Spring XML配置
- 对象实例化配置
- 依赖注入配置
- 注解与Java Config
- Spring 单元测试
文章目录
IoC控制反转
- IoC控制反转,全称Inverse of Control,是一种设计理念
- 由代理人来创建与管理对象,消费者通过代理人来获取对象
- Ioc的目的是降低对象之间之间耦合
- 加入Ioc容器将对象统一管理,让对象关联变为弱耦合。
DI依赖注入
- IoC是设计理念,是现代程序设计遵循的标准,是宏观目标
- DI(Dependency Injection)是具体技术的实现,是微观实现
- DI在java中利用反射技术实现对象注入(Injection)
Spring的含义
- Spring可从侠义和广义两个角度看待
- 侠义的Spring是指Spring框架(Spring Framework)
- 广义的Spring是指Spring生态体系
狭义的Spring框架
- Spring框架是企业开发复杂性的一站式解决方案。
- Spring框架的核心是IoC容器(用于对对象进行统一管理)与AOP面向切面编程。
- Spring IoC负责创建与管理系统对象,并在此基础上扩展功能。
广义的Spring生态体 系
https://spring.io/
- 如分布式微服务, Microservices可以轻松实现分布式架构。
- Reactive代表响应式编程,这是一种基于异步的非阻塞的全新的开发理念和技术。
- Cloud云端技术:在云端自动扩展,自动连接。
- Web apps SpringMVC拜托使用Servelet的繁琐的开发方式,能更简洁更优雅完成API完成web应用程序的开发。
- Serverless:无服务器编程,在运行过程中网页以单页形式拜托服务器的束缚,从而让用户获得更好的使用体验。
- Event Driven 事件驱动
- Batch 批处理
传统的开发方式
对象直接引用导致对象硬性关联,程序难以扩展维护
Spring IoC容器
- IoC容器是Spring生态基地,用于统一创建与管理对象依赖
Spring IoC容器职责
- 对象的控制权交由第三方统一管理(IoC控制反转)
- 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
- 基于配置提高应用程序的可维护性与扩展性。
Spring IoC初体验
分析传统编码方式的不足
案例:孩子如何得到喜欢的苹果?
- 妈妈在早晨后给三个孩子分发餐后水果
- 盘子里有三个苹果:红富士、青苹果、金帅
- 孩子们口味不同:莉莉喜欢甜的、安妮喜欢酸的、露娜喜欢软软的
创建苹果和孩子实体类:
Apple 苹果类
package ioc.entity;
public class Apple {
private String title;
private String Color;
private String origin;
public Apple() {
}
public Apple(String title, String color, String origin) {
this.title = title;
Color = color;
this.origin = origin;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return Color;
}
public void setColor(String color) {
Color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
Child 孩子类,增加了eat方法
package ioc.entity;
public class Child {
private String name;
private Apple apple;
public Child() {
}
public Child(String name, Apple apple) {
this.name = name;
this.apple = apple;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public Apple getApple() {
return apple;
}
public void eat(){
System.out.println(name + "吃到了:" + apple.getOrigin() + "种植的" + apple.getColor() + "的" + apple.getTitle());
}
}
Application 运行入口类
package ioc;
import ioc.entity.Apple;
import ioc.entity.Child;
public class Application {
public static void main(String[] args) {
Apple apple1 = new Apple("红富士", "红色", "欧洲");
Apple apple2 = new Apple("青苹果", "绿色", "中亚");
Apple apple3 = new Apple("金帅", "黄色", "中国");
Child lily = new Child("莉莉", apple1);
Child andy = new Child("安迪", apple2);
Child luna = new Child("露娜", apple3);
lily.eat();
andy.eat();
luna.eat();
}
}
数据都写在源代码中,必须要修改源代码才能重新调整;对象的数量固定;对象是硬关联,通过构造函数直接赋值;涉及到源码的修改就要重新测试、审批、发布;一切的根源就是在使用new时对象与对象之间进行了强制的绑定
Spring IoC方式
Spring IoC可以使用配置的形式完成对对应的实例化和对象与对象的依赖关系。
maven引入spring
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
引入的文件:
- spring-core:整个spring最核心的代码,所有spring最底层的代码都是从这里进行写的
- spring-beans:是spring对bean(也就是java对象)的管理模块,实例化、对象关联都需要通过这个模块
- spring-expression:是spring内置的表达式模块,spring在使用过程中有一个属于自己的成体系的表达式语言,在实际开发的时候这些表达式就会通过这个依赖来进行解析和处理
- spring-jcl:jcl是spring与日志交互的模块,我们在运行过程中所产生的的一系列底层的日志都是由这个日志模块来处理的
- spring-aop:则是对应了面向切面编程的模块,在引入context之后,AOP模块也会自动引入
- spring-context:spring的上下文,通过spring-context中的applictionContext对象可以让我们通过代码来对spring IoC容器进行创建
Spring框架组成模块
在resources目录新建applicationContext.xml
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sweetApple" class="ioc.entity.Apple">
<property name="title" value="红富士"></property>
<property name="origin" value="欧洲"></property>
<property name="color" value="红色"></property>
</bean>
<bean id="sourApple" class="ioc.entity.Apple">
<property name="title" value="青苹果"></property>
<property name="origin" value="中亚"></property>
<property name="color" value="绿色"></property>
</bean>
<bean id="softApple" class="ioc.entity.Apple">
<property name="title" value="金帅"></property>
<property name="origin" value="中国"></property>
<property name="color" value="黄色"></property>
</bean>
<bean id="lili" class="ioc.entity.Child">
<property name="name" value="莉莉"/>
<property name="apple" ref="softApple"/>
</bean>
<bean id="andi" class="ioc.entity.Child">
<property name="name" value="安迪"/>
<property name="apple" ref="sweetApple"/>
</bean>
<bean id="luna" class="ioc.entity.Child">
<property name="name" value="露娜"/>
<property name="apple" ref="sourApple"/>
</bean>
</beans>
SpringApplication 测试类
package ioc.entity;
import ioc.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
//创建Spring IoC容器,并根据配置文件在容器中实例化对象
//classpath: 代表当前目录下
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = (Apple)context.getBean("sweetApple");
System.out.println(sweetApple.getTitle());
//从IoC容器中提取beanId=lily的对象
Child lili = context.getBean("lili", Child.class);
Child andi = context.getBean("andi", Child.class);
Child luna = context.getBean("luna", Child.class);
lili.eat();
andi.eat();
luna.eat();
System.out.println("是否是同一个对象");
System.out.println(andi.getApple().hashCode());
System.out.println(sweetApple.hashCode());
}
}
利用IoC容器可以让对象和对象之间有效的解耦。
新增问题:安迪想吃蛇果
只需要增加配置即可;增加蛇果,任何将安迪的引用改为蛇果的id
<bean id="rdApple" class="ioc.entity.Apple">
<property name="title" value="蛇果"></property>
<property name="origin" value="美国"></property>
<property name="color" value="红色"></property>
</bean>
<bean id="andi" class="ioc.entity.Child">
<property name="name" value="安迪"/>
<property name="apple" ref="rdApple"/>
</bean>
在最原始的代码中,所有对象都是通过new关键字做出来的,在编译时就决定了他们的依赖关系,对象的创建和依赖都是由程序主动发起的,而引入了Spring IoC之后,所有对象的创建不再是由程序自己来做,而是通过Spring IoC容器在创建时自动的对其进行实例化,它的本质在最底层也是利用反射机制将这些对象创建并贴上对应标签,设置里面的属性,而这个过程交给了Spring IoC来做,开发者不需要关心底层的依赖关系,只需要知道从哪个标签里面可以获取到需要的对象,而对象底层依赖的其他的哪个对象我们不需要知道,因为对象关联的工作全部是底层的IoC容器做好的,在运行时IoC容器会主动的进行对象的注入,而我们在获取对象的时候是从容器中提取,将现成的对象拿出来。
初始化IoC容器
三种配置方式
- 基于XML配置Bean
- 基于注解配置Bean
- 基于Java代码配置Bean
XML关联对象(Bean)
XML方式创建IoC容器
//初始化IoC容器并实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
三种XML实例化Bean的配置方式
- 基于构造方法实例化对象(90%以上的应用场景)
- 基于静态工厂实例化对象
- 基于工厂实例方法实例化对象
基于构造方法实例化对象
默认无参构造方法
<bean id="sweetApple" class="ioc.entity.Apple">
</bean>
利用构造方法参数名实例化
没有constructor-arg则代表用默认构造方法实例化
Spring会根据构造函数参数对应数量进行赋值
<bean id="sweetApple" class="ioc.entity.Apple">
<constructor-arg name="title" value="红富士"/>
<constructor-arg name="origin" value="欧洲"/>
<constructor-arg name="color" value="红色"/>
</bean>
利于构造方法参数位置实例化(不推荐)
<bean id="sweetApple" class="ioc.entity.Apple">
<constructor-arg index="0" value="红富士"/>
<constructor-arg index="1" value="红色"/>
<constructor-arg index="2" value="欧洲"/>
</bean>
基于静态工厂实例化对象
AppleStaticFactory.java
package ioc.factroy;
import ioc.entity.Apple;
/**
* 静态工厂通过静态方法创建对象,隐藏创建对象的细节
*/
public class AppleStaticFactory {
public static Apple createSweetApple(){
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
applicationContext.xml
<!--利用静态工厂获取对象-->
<bean id="apple4" class="ioc.factroy.AppleStaticFactory" factory-method="createSweetApple"/>
基于工厂实例方法实例化对象
AppleInstanceFactory.java
package ioc.factroy;
import ioc.entity.Apple;
/**
* 工厂实例方法创建对象是指IoC容器对工厂类进行实例化并调用对应的实例方法创建对象的过程
*/
public class AppleInstanceFactory {
public Apple createSweetApple(){
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
applicationContext.xml
<!--利用工厂实例方法获取对象,需要创建两个bean -->
<bean id="appleInstanceFactory" class="ioc.factroy.AppleInstanceFactory"></bean>
<bean id="apple5" factory-bean="appleInstanceFactory" factory-method="createSweetApple"></bean>
两种工厂实例化方法都是通过封装对应的方法隐藏创建对象的细节,当数据变化是只需要修改工厂内部数据的代码,不会影响到其他任何类的运行,从这个角度看也实现了对象和对象之间的解耦,同时无论静态方法还是示例方法都可以增加需要的额外业务逻辑,处理非常灵活。
从IoC容器获取bean
方法一(推荐):
Apple apple = context.getBean("sweetApple",Apple.class);
方法二:
Apple apple = (Apple)context.getBean("sweetApple");
id与name属性相同点
- bean id 和name都是设置对象在IoC容器中唯一标识
- 两者在同一配置文件中都不允许出现重复
- 两者允许在多个配置文件中出现重复重复,新对象覆盖旧对象
id和name属性区别
- id要求更为严格,一次只能定义一个对象标识(推荐使用id)
- name更为宽松,一次允许定义多个对象标识
- tips:id和name的命名要求有意义,按驼峰命名书写
没有id和name的bean
没有id和name的bean,默认使用类名全称作为bean标识
Apple apple = context.getBean("ioc.entity.Apple",Apple.class);
路径表达式用法
//初始化IoC容器并根据配置文件创建对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
路径表达式
表达式实例 | 说明 |
---|---|
classpath:applicationContext.xml.xml | 扫描classpath根路径(不包含jar)的applicationContext.xml.xml |
classpath:ioc/applicationContext.xml.xml | 扫描classpath下(不包含jar)ioc包中的applicationContext.xml.xml文件 |
classpath*:ioc/applicationContext.xml.xml | 扫描classpath下(包含jar)ioc包中的applicationContext.xml.xml文件 |
classpath:config-*.xml | 扫描classpath根路径下所有以config-开头的XML文件 多个文件按名称ASCLL码升序加载 |
classpath:ioc/*/applicationContext.xml.xml | 扫描classpath下ioc包下(包含任何子包)的applicationContext.xml |
file:c:/applicationContext.xml.xml | 扫描c盘根路径applicationContext.xml |
对象依赖注入
- 依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作
- 基于setter方法注入对象
- 基于构造方法注入对象
利用setter实现对象依赖注入
利用setter实现静态数值注入
bean中设置property属性,spring IoC容器利用反射机制运行时自动调用setXxx方法为属性赋值
<bean id="sweetApple" class="ioc.entity.Apple">
<property name="title" value="红富士"></property>
<property name="origin" value="欧洲"></property>
<property name="color" value="红色"></property>
</bean>
利用setter实现对象注入
ref代表关联,其数值是IoC容器中一个有效的bean id
<bean id="lili" class="ioc.entity.Child">
<property name="name" value="莉莉"/>
<property name="apple" ref="softApple"/>
</bean>
基于构造方法注入对象
applicationContext.xml
<bean id="sourApple" class="ioc.entity.Apple">
<property name="title" value="青苹果"></property>
<property name="origin" value="中亚"></property>
<property name="color" value="绿色"></property>
</bean>
<bean id="andy" class="ioc.entity.Child">
<constructor-arg name="name" value="安迪"/>
<constructor-arg name="apple" ref="sourApple"/>
</bean>
IoC在实际项目中的重要用途
体验依赖注入的优势
多人开发,或者方便项目升级维护
建立数据和服务两个功能的模块
ioc.bookshop.dao.BookDao.java 数据操作接口,写一个插入方法
package ioc.bookshop.dao;
public interface BookDao {
public void insert();
}
BookDaoImpl.java 接口实现类
package ioc.bookshop.dao;
public class BookDaoImpl implements BookDao{
public void insert() {
System.out.println("向MySQL Book表插入一条数据");
}
}
BookService.java 图书服务类,将数据操作接口作为属性,增加采购图书插入方法
package ioc.bookshop.service;
import ioc.bookshop.dao.BookDao;
public class BookService {
private BookDao bookDao;
public void purchase() {
System.out.println("正在执行图书采购业务方法");
bookDao.insert();
}
public BookDao getBookDao() {
return bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
resources/applicationContext-dao.xml 只声明数据接口的实现类
<bean id="bookDao" class="ioc.bookshop.dao.BookDaoImpl"></bean>
resources/applicationContext-service.xml 引用约定好的bookDao
<bean id="bookService" class="ioc.bookshop.service.BookService">
<property name="bookDao" ref="bookDao"/>
</bean>
BookShopApplication.java 运行入口
package ioc.bookshop;
import ioc.bookshop.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BookShopApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.purchase();
}
}
执行结果:
正在执行图书采购业务方法
向MySQL Book表插入一条数据
开发服务类不需要了解Dao数据操作类的细节,只需要引用约定好的接口即可
当项目需求变更,数据库使用Oracle时,只需要增加修改相关的数据操作实现类
BookDaoOracleImpl.java
package ioc.bookshop.dao;
public class BookDaoOracleImpl implements BookDao{
public void insert() {
System.out.println("向Oracle Book表插入一条数据");
}
}
resources/applicationContext-dao.xml 引用类修改
<bean id="bookDao" class="ioc.bookshop.dao.BookDaoOracleImpl"></bean>
而不需要修改服务类的任何内容,甚至service模块的开发这都不必需要数据库做了升级,就可以完成MySQL数据库到Oracle数据库的变更
运行结果:
正在执行图书采购业务方法
向Oracle Book表插入一条数据
注入集合对象
注入List
<bean id="..." class="...">
<property name="someList">
<list>
<value>具体值1</value>
<ref bean="beanId"></ref>
</list>
</property>
</bean>
注入Set
<bean id="..." class="...">
<property name="someSet">
<set>
<value>具体值1</value>
<ref bean="beanId"></ref>
</set>
</property>
</bean>
注入Map
<bean id="..." class="...">
<property name="someMap">
<map>
<entry key="key1" value="v1"></entry>
<entry key="key2" value-ref="beanId"></entry>
</map>
</property>
</bean>
注入Properties
Properties的key和value都是字符串
<bean id="..." class="...">
<property name="someProperties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
</props>
</property>
</bean>
案例:公司资产配置清单
Computer 计算机类
package ioc.entity;
public class Computer {
//品牌
private String brand;
//类型:台式、笔记本、服务器
private String type;
private String sn;
private Float price;
public Computer() {
}
public Computer(String brand, String type, String sn, Float price) {
this.brand = brand;
this.type = type;
this.sn = sn;
this.price = price;
}
@Override
public String toString() {
return "Computer{" +
"brand='" + brand + '\'' +
", type='" + type + '\'' +
", sn='" + sn + '\'' +
", price=" + price +
'}';
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
Company 公司类
package ioc.entity;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Company {
//房间
private List<String> rooms;
private Set<String> roomsSet;
private Map<String,Computer> computers;
//保存公司基础信息
private Properties info;
@Override
public String toString() {
return "Company{" +
"rooms=" + rooms +
", roomsSet=" + roomsSet +
", computers=" + computers +
", info=" + info +
'}';
}
public List<String> getRooms() {
return rooms;
}
public void setRooms(List<String> rooms) {
this.rooms = rooms;
}
public Set<String> getRoomsSet() {
return roomsSet;
}
public void setRoomsSet(Set<String> roomsSet) {
this.roomsSet = roomsSet;
}
public Map<String, Computer> getComputers() {
return computers;
}
public void setComputers(Map<String, Computer> computers) {
this.computers = computers;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
}
使用List
<bean id="company" class="ioc.entity.Company">
<property name="rooms">
<list>
<value>301-总裁办</value>
<value>302-总经理办公室</value>
<value>303-研发部会议室</value>
</list>
</property>
</bean>
测试:
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Company company = context.getBean("company", Company.class);
System.out.println(company.getRooms());
}
}
使用Set List修改为set即可;作为注入的set对象,实际类型为LinkedHashSet,基于双向链表存储,所以数据是有序的。
<property name="roomsSet">
<set>
<value>301-总裁办</value>
<value>302-总经理办公室</value>
<value>303-研发部会议室</value>
</set>
</property>
使用Map Map存放的实际类型为LinkedHashMap,同样是有序的
<bean id="c1" class="ioc.entity.Computer">
<constructor-arg name="brand" value="联想"></constructor-arg>
<constructor-arg name="price" value="8999"></constructor-arg>
<constructor-arg name="sn" value="456751546"></constructor-arg>
<constructor-arg name="type" value="笔记本"></constructor-arg>
</bean>
<bean id="company" class="ioc.entity.Company">
<property name="computers">
<map>
<entry key="dev_1001" value-ref="c1"></entry>
<!--内置bean方式-->
<entry key="dev_1002">
<bean class="ioc.entity.Computer">
<constructor-arg name="brand" value="联想"></constructor-arg>
<constructor-arg name="price" value="8999"></constructor-arg>
<constructor-arg name="sn" value="456751546"></constructor-arg>
<constructor-arg name="type" value="笔记本"></constructor-arg>
</bean>
</entry>
</map>
</property>
</bean>
使用Property Propertie一般存放一些静态的信息,注意key和value必须是字符串
<property name="info">
<props>
<prop key="phone">1388181183</prop>
<prop key="address">沈阳</prop>
</props>
</property>
查看容器内对象
内部bean不会被获取,没有id的匿名bean会获取到"类路径+#序号"
如果要获取匿名类需要加"#序号",否则默认获取第一个
//获取容器内所以beanId数组
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
System.out.println("类型:" + context.getBean(beanName).getClass().getName());
System.out.println("内容:" + context.getBean(beanName).toString());
}
bean scope属性
- bean scope属性用于决定对象何时被创建与作用范围
- bean scope配置将影响容器内对象的数量
- bean scope默认值singleton(单例),指全局共享同一个对象实例
在IoC容器中,bean scope属性:默认情况下bean会在IoC容器创建后自动实例化,全局唯一
scope用法
<bean id="c1" class="ioc.entity.Computer" scope="prototype"></bean>
bean scope属性清单
scope属性 | 说明 |
---|---|
singleton | 单例(默认值),每一个容器有且只有唯一的实例,实例被全局共享 |
prototype | 多例,每次使用时都是创建一个实例 |
request | web环境下,每一次独立请求存在唯一实例 |
session | web环境下,每一个session存在有唯一实例 |
application | web环境下,ServletContext存在唯一实例 |
websocket | 每一次WebSocket连接中存在唯一实例 |
singleton单例
singleton在容器中是单例多线程执行,存在线程安全风险
singleton的线程安全问题
用户1设置1,而紧接着用户2设置2,用户1提取数据输出了2
解决方法如下:
- 在设置时加锁
- 不要设置成单例的,设置成prototype多例
prototype多例
prototype在容器中多实例,占用更多资源,不存在线程安全问题
singleton单例与prototype多例对比
singleton | prototype | |
---|---|---|
对象数量 | 全局唯一 | 存在多个 |
实例化时机 | IoC容器启动时 | getBean()或对象注入时 |
线程安全问题 | 存在 | 不存在 |
执行效率 | 高 | 低 |
bean scope的实际应用
单例模式IoC初始化时会创建对象,多例模式要等待对象使用的时候再创建。
单例模式中属性引用多例模式的对象,会先创建该多例模式,再创建多例模式。
创建多个单例模式引用多例模式对象,也会依次创建多个单例模式和多例模式。
实际项目中dao和service这些具体的类大部分是单例的。
一个类对象在程序运行过程中恒定不变,设置为单例模式。
bean的生命周期
演示bean的生命周期
Order.java 订单类
package ioc.entity;
public class Order {
//单价
private float price;
//数量
private Integer quantity;
//总价=单价*数量
private float total;
public Order() {
System.out.println("无参构造函数-创建order对象");
}
public void init(){
System.out.println("======执行init方法=====");
this.total=price*quantity;
}
public void destroy(){
System.out.println("========容器销毁是执行-释放与订单对象相关的资源(资源:可以是文件||网络的连接)========");
}
public void pay(){
System.out.println("订单金额为:" + total);
}
public void setPrice(float price) {
System.out.println("setPrice=" + price);
this.price = price;
}
public void setQuantity(Integer quantity) {
System.out.println("setQuantity=" + quantity);
this.quantity = quantity;
}
public void setTotal(float total) {
this.total = total;
}
}
applicationContext.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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--init-method="init"是在设置完属性之后,执行的方法-->
<!--//registerShutdownHook是销毁IoC容器的方法,这个registerShutdownHook()执行的过程中,会自动的调用destroy-method="destroy"中的方法-->
<bean id="order1" class="ioc.entity.Order" init-method="init" destroy-method="destroy">
<property name="price" value="5.6"/>
<property name="quantity" value="10"/>
</bean>
</beans>
测试:
package ioc;
import ioc.entity.Order;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context=
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Order order = context.getBean("order1", Order.class);
order.pay();
//registerShutdownHook是销毁IoC容器的方法,这个registerShutdownHook()执行的过程中,会自动的调用destroy-method="destroy"中的方法
((ClassPathXmlApplicationContext) context).registerShutdownHook();
}
}
输出结果:
无参构造函数-创建order对象
setPrice=5.6
setQuantity=10
======执行init方法=====
订单金额为:56.0
========容器销毁是执行-释放与订单对象相关的资源(资源:可以是文件||网络的连接||其他系统方法的调用)========
实现极简IoC容器(不使用Spring)
不使用Spring自己实现IoC容器,通过此案例更好的理解Spring IoC容器背后是如何通过反射机制完成对象的创建和数据的注入
创建一个全新的项目
ioc.entity.Apple.java 苹果类
package ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public Apple() {
}
public Apple(String title, String color, String origin) {
this.title = title;
this.color = color;
this.origin = origin;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
resources.applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<property name="title" value="红富士"/>
<property name="color" value="红色"/>
<property name="origin" value="欧洲"/>
</bean>
</beans>
pom.xml 引入组件
<dependencies>
<!-- Dom4j是Java的XML解析组件 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<!-- Jaxen是Xpath表达式解释器 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
ioc.context.ApplicationContext.java 创建接口
package ioc.context;
public interface ApplicationContext {
public Object getBean(String beanId);
}
ioc.context.ClassPathXmlApplicationContext
package ioc.context;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map iocContainer = new HashMap();
public ClassPathXmlApplicationContext() {
try {
String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
filePath = new URLDecoder().decode(filePath, "UTF-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node node : beans) {
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
//利用反射得到类对象
Class c = Class.forName(className);
Object obj = c.newInstance();
List<Node> properties = ele.selectNodes("property");
for (Node p : properties) {
Element property = (Element) p;
String propName = property.attributeValue("name");
String propValue = property.attributeValue("value");
//得到set方法名
String setMathodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
System.out.println("准备执行" + setMathodName + "方法注入数据");
Method setMethod = c.getMethod(setMathodName, String.class);
setMethod.invoke(obj, propValue); //荣光setter方法注入数据
}
iocContainer.put(id, obj);
}
System.out.println(iocContainer);
System.out.println("IOC容器初始化完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
Application 运行入口测试
package ioc;
import ioc.context.ApplicationContext;
import ioc.context.ClassPathXmlApplicationContext;
import ioc.entity.Apple;
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext();
Apple apple = (Apple) context.getBean("sweetApple");
System.out.println(apple);
}
}
基于注解配置IoC容器
基于注解的优势
- 摆脱频繁的XML形式的bean与依赖注入配置
- 基于"声明式"的原则,更适合轻量级的现代企业应用
- 让代码的可读性变得更好,研发人员用有更好的开发体验
三种注解类型
-
组件类型注解:声明当前类的功能与职责
-
自动装配注解:更加属性特征自动注入对象
-
元数据注解:更细化的辅助IoC容器管理对象的注解
四种组件类型注解
注解 | 说明 |
---|---|
@Component | 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化 |
@Controller | 语义注解,说明当前类是MVC应用中的控制器类 |
@Service | 语义注解,说明当前类是Service业务服务类 |
@Repository | 语义注解,说明当前类用于业务持久层,通常描述对应的Dao类 |
Component: 组件注解,在实际开发中,当遇到无法确认当前类是repository 还是 Sevice 还是 Controller时,它的边界是模糊的,这个时候就可以使用@Component,@Component组件注解是一个最统称的注解,当不清楚类的职责时,就使用@Component就可以了
开启组件扫描
只有开启了组件扫描,才能使用注解;扫描base-package=""包下所有的类
<!--XML配置开启组件扫描,才能使用注解-->
<context:component-scan base-package="">
<!--这里是排除expression="",不需要扫描的包,可以使用正则表达式,只有类名匹配这个正则表达式,就会被排除在外-->
<contex:exclude-filter type="regex" expression="" />
</context:component-scan>
基于注解初始化IoC容器
新建一个maven项目
头部配置信息:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config
resources/applicationContext.xml 基于注解的配置增加了context命名空间
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--在IoC容器初始化时自动扫描四种组件类型注解
@Respository
-->
<context:component-scan base-package="ioc"/>
</beans>
UserDao
package ioc.dao;
import org.springframework.stereotype.Repository;
//组件类型注解默认beanId为类名首字母小写
//beadId - userDao
//手动设置@Repository("beanId名")
@Repository
public class UserDao {
}
UserService
package ioc.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
}
UserController
package ioc.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}
package ioc.utils;
import org.springframework.stereotype.Component;
@Component
public class StringUtils {
}
SpringApplication 程序入口
package ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
String[] ids = context.getBeanDefinitionNames();
for (String id : ids) {
System.out.println(id + ":" + context.getBean(id));
}
}
}
自动装配注解
分类 | 注解 | 说明 |
---|---|---|
按类型装配 | @Autowired | 按容器内对象类型动态注入属性,由Spring机构提供 |
@Inject | 基于JSR-330(Dependency Injection for java)标准,其他同@Autowired,但不支持required属性 | |
按名称装配 | @Named | 与@Inject配合使用,JSR-330规范,按属性名自动装配属性 |
@Resource | 基于JSR-250规范,优先按名称、再按类型智能匹配 |
Autowired注解
在上一个案例基础上修改:@Autowired按类型装配
UserService
package ioc.service;
import ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public UserService() {
System.out.println("正在创建UserService:" + this);
}
//java的反射机制底层功能,如果将注解放在属性名称上,在IoC容器UserService对象创建之后,由Spring自动通过反射机制将属性的private修饰符改成public,可以从外侧直接赋值,这个过程是程序运行时动态完成的
// @Autowired
private UserDao userDao;
//如果装配注解放在set方法上,则自动按类型/名称对set方法参数进行注入,实际项目这种一般不使用
//如果放在属性上,则不再执行set方法
@Autowired
public void setUserDao(UserDao userDao) {
System.out.println("setUserDao:" + userDao);
this.userDao = userDao;
}
public UserDao getUserDao(){
return userDao;
}
}
UserDao
package ioc.dao;
import ioc.service.UserService;
import org.springframework.stereotype.Repository;
//组件类型注解默认beanId为类名首字母小写
//beadId - userDao
//手动设置@Repository("beanId名")
@Repository
public class UserDao {
public UserDao() {
System.out.println("正在创建UserDao:" + this);
}
}
@Autowired按类型装配在工作中不推荐使用:
一个接口有多个实现类的解决方法
按类型注入时,当一个接口有多个实现类时的解决方法如下:
- 将另外一个实现类的@Repository注解去掉
- 在其中一个实现类中加上@Primary注解
案例:增加Oracle数据库
增加IUserDao接口,内容不写
UserDao继承接口
package ioc.dao;
import ioc.service.UserService;
import org.springframework.stereotype.Repository;
//组件类型注解默认beanId为类名首字母小写
//beadId - userDao
//手动设置@Repository("beanId名")
@Repository
public class UserDao implements IUserDao{
public UserDao() {
System.out.println("正在创建UserDao:" + this);
}
}
UserOracleDao继承接口 增加@Primary
package ioc.dao;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository
@Primary
public class UserOracleDao implements IUserDao {
public UserOracleDao() {
System.out.println("正在创建UserOracleDao:"+this);
}
}
Resource注解(推荐)-按名称装配
@Resource装配规则
@Resource设置name属性,则按name在IoC容器中将bean注入
@Resource未设置name属性
- 以属性名作为bean name在IoC容器中匹配bean,如有匹配则注入
- 按属性名未匹配,则按类型进行匹配,同@Autowired,需加入@Primary解决类型冲突
使用建议:
在使用@Resource对象时推荐设置name或保证属性名与bean名称一致
创建DepartmentService
package ioc.service;
import ioc.dao.IUserDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class DepartmentService {
// //指定name方式
// @Resource(name="userOracleDao")
// private IUserDao udao;
//规范属性名方式
@Resource
private IUserDao userOracleDao;
public void joinDepartment() {
System.out.println(userOracleDao);
}
}
SpringApplication 运行测试
package ioc;
import ioc.service.DepartmentService;
import ioc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
DepartmentService departmentService = context.getBean("departmentService", DepartmentService.class);
departmentService.joinDepartment();
}
}
运行结果:输出了UserOracleDao 如果DepartmentService中IUserDao属性改为userDao,会输出UserDao;如果改成其他比如uDao,同时也没有设置@Primary则会报错无法确认注入哪个bean
正在创建UserDao:ioc.dao.UserDao@52525845
正在创建UserOracleDao:ioc.dao.UserOracleDao@3b94d659
正在创建UserService:ioc.service.UserService@4bb4de6a
ioc.dao.UserOracleDao@3b94d659
元数据注解
注解 | 说明 |
---|---|
@Primary | 按类型装配时出现多个相同类型对象,拥有此注解对象优先被注入 |
@PostConstruct | 描述方法,相当于XML中init-method配置的注解版本 |
@PreDestroy | 描述方法,相当于XML中的destroy-method配置的注解版本 |
@Scope | 设置对象的Scope属性(生存周期) |
@Value | 为属性注入静态数据 |
使用演示:
package ioc.service;
import ioc.dao.IUserDao;
import ioc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Service
@Scope("prototype") //设置单例/多例,XML中bean scope完全相同
public class UserService {
// @Value("ioc") //直接写死在源代码中
@Value("${metaData}") //读取config.properties的metaData属性值;原理和之前@Resource/@Autowired也是在运行时动态将private改为public
private String metaData;
public UserService() {
System.out.println("正在创建UserService:" + this);
}
@PostConstruct
public void init() {
System.out.println("初始化UserService对象,metaData=" + metaData);
}
//java的反射机制底层功能,如果将注解放在属性名称上,在IoC容器UserService对象创建之后,由Spring自动通过反射机制将属性的private修饰符改成public,可以从外侧直接赋值,这个过程是程序运行时动态完成的
@Resource
private IUserDao udao;
//如果装配注解放在set方法上,则自动按类型/名称对set方法参数进行注入,实际项目这种一般不使用
//如果放在属性上,则不再执行set方法
// @Autowired
public void setIUserDao(IUserDao udao) {
System.out.println("setDao:" + udao);
this.udao = udao;
}
public IUserDao getIUserDao(){
return udao;
}
}
配置文件
resources/config.properties 可以用于保存一些配置信息
metaData=ioc
connection.driver=xxx
connection.url=xxx
connection.username=xxx
connection.password=xxx
总结:XML方式配置Ioc容器基于配置文件,维护方便,但是开发麻烦,注解使用方便,但是写死在源代码中,不容易维护
基于Java Config配置IoC容器
Java Config是在Spring3.0之后推出的全新配置方式
基于Java Config的优势
- 完全摆脱XML的束缚,使用独立Java类管理对象与依赖
- 注解配置相对分散,利用Java Config可对配置集中管理
- 可以在编译时进行依赖检查,不容易出错
基于Java Config的缺点
- 如果后续需要更改接口实现类,则需要修改源码,重新编译打包发布
Java Config核心注解
注解 | 说明 |
---|---|
@Configuration | 描述类,说明当前类是Java Config配置类,完全替代XML文件 |
@Bean | 描述方法,方法返回对象将被IoC容器管理,beanId默认为方法名 |
@ImportResource | 描述类,加载静态文件,可使用@Value注解获取 |
@ComponentScan | 组件扫描,同XML的<context:compoment-scan>标签 |
Java Config对象实例化
先定义好MVC架构测试类:UserDao,UserService包含属性UserDao,UserController包含属性UserService
UserDao
package ioc.dao;
public class UserDao {
}
UserService
package ioc.service;
import ioc.dao.UserDao;
public class UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
UserController
package ioc.controller;
import ioc.service.UserService;
public class UserController {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
Config 配置类
package ioc;
import ioc.controller.UserController;
import ioc.dao.UserDao;
import ioc.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //当前类是一个配置类,用于替代applicationContext.xml
public class Config {
@Bean Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
public UserDao userDao() {
UserDao userDao = new UserDao();
return userDao;
}
@Bean
public UserService userService(){
return new UserService();
}
@Bean
public UserController userController(){
return new UserController();
}
}
SpringApplication 程序测试入口
package ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
//基于Java Config配置IoC容器的初始化
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
String[] ids = context.getBeanDefinitionNames();
for (String id : ids) {
System.out.println(id + ":" + context.getBean(id));
}
}
}
测试结果:对象成功被实例化
config:ioc.Config$$EnhancerBySpringCGLIB$$cb99e0@c540f5a
userDao:ioc.dao.UserDao@770c2e6b
userService:ioc.service.UserService@1a052a00
userController:ioc.controller.UserController@4d826d77
Java Config对象依赖注入
对象依赖注入需要依托于set方法,要set的对象来自ioc容器,这时就需要bean对应方法的参数
UserService注入UserDao,UserController注入UserService
Config
package ioc;
import ioc.controller.UserController;
import ioc.dao.UserDao;
import ioc.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration //当前类是一个配置类,用于替代applicationContext.xml
public class Config {
@Bean Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
public UserDao userDao() {
UserDao userDao = new UserDao();
System.out.println("已创建" + userDao);
return userDao;
}
@Bean //先按name尝试注入,name不存在则按类型注入(参数名改为uDao)
// @Primary //按类型注入尽量避免,因为如果出现多个对象会无法区分,不过可以使用Primary优先注入
public UserService userService(UserDao userDao){
UserService userService = new UserService();
System.out.println("已创建" + userService);
userService.setUserDao(userDao);
System.out.println("调用setUserDao:" + userDao);
return new UserService();
}
@Bean
public UserController userController(UserService userService){
UserController userController = new UserController();
System.out.println("已创建" + userController);
userController.setUserService(userService);
System.out.println("调用setUserService:" + userService);
return new UserController();
}
}
测试结果:创建的对象和注入的是同一个
已创建ioc.dao.UserDao@453da22c
已创建ioc.service.UserService@473b46c3
调用setUserDao:ioc.dao.UserDao@453da22c
已创建ioc.controller.UserController@797badd3
调用setUserService:ioc.service.UserService@77be656f4
同样Java Config也可以兼容其他方式的对象注入,类上方增加@ComponentScan
比如其他人开发一个EmployeeDao,他使用的注解形式
package ioc.dao;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao {
}
Config ioc容器启动时扫描ioc包下组件类(开启组件扫描)
@Configuration //当前类是一个配置类,用于替代applicationContext.xml
@ComponentScan(basePackages = "ioc") //可以兼容注解形式,类似于xml中兼容注解的方法
public class Config {
如何对EmployeeDao进行注入,比如UserService也需要依赖于员工Dao
UserService
package ioc.service;
import ioc.dao.EmployeeDao;
import ioc.dao.UserDao;
public class UserService {
private UserDao userDao;
private EmployeeDao employeeDao;
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
Config类中userService方法的参数列表后面额外增加EmployeeDao和set注入
@Bean //先按name尝试注入,name不存在则按类型注入(参数名改为uDao)
// @Primary //按类型注入尽量避免,因为如果出现多个对象会无法区分,不过可以使用Primary优先注入
public UserService userService(UserDao userDao, EmployeeDao employeeDao) {
UserService userService = new UserService();
System.out.println("已创建" + userService);
userService.setUserDao(userDao);
System.out.println("调用setUserDao:" + userDao);
userService.setEmployeeDao(employeeDao);
System.out.println("调用setEmployeeDao:" + employeeDao);
return new UserService();
}
总结:Config配置类完全是为了替代XML出现的,只不过是把XML中的配置信息通过注解的方式写在源代码中,好处是在编译阶段及时发现问题,缺点是如果调整则要重新编译源代码。在实际开发中Java Config开发方式更多的用在敏捷开发,特别适合这种快速迭代,快速上线的工程。
后面要学习的Spring Boot默认就基于Java Config进行配置,而XML更多是用在大型项目的团队合作中,通过配置文件将每一个模块或者每一个团队的工作切分开,各司其职。
综上所述,基于Java Config的配置用有更好的开发体验,而基于XML的配置则拥有更好的程序可维护性。
Spring与JUnit4整合
Spring Test测试模块
- Spring Test是Spring中用于测试的模块
- Spring Test对JUnuit单元测试框架有良好的整合
- 通过Spring Test可在JUnit在单元测试时自动初始化IoC容器
Spring与JUnit4整合过程
- Maven工程依赖spring-test
- 通过@RunWith与@ContextConfiguration描述测试用例类
- 测试用例类从容器获取对象完成测试用例的执行
案例演示:
pom.xml 先引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>spring-test</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
写一个Spring注入案例一会测试:
UserDao
package ioc.dao;
public class UserDao {
public void insert() {
System.out.println("新增用户数据");
}
}
UserService
package ioc.service;
import ioc.dao.UserDao;
public class UserService {
private UserDao userDao;
public void createUser() {
System.out.println("调用创建用户业务代码");
userDao.insert();
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
resources/applicationContext.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/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="ioc.dao.UserDao">
</bean>
<bean id="userService" class="ioc.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
之前的方式,需要写SpringApplication程序入口类测试,但是复杂场景需要多个测试用例类,使用JUnit
pom.xml spring-test是spring的测试模块,junit是Java中最常用的测试类;注意要引用Junit4.12以上的版本
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
test/java/SpringTestor
import ioc.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
//将Junit4的执行权限交由Spring Test,在测试用例执行前自动吃时候IoC容器
@RunWith(SpringJUnit4ClassRunner.class)
//在IoC容器初始化过程总通知IoC要加载哪个配置文件
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringTestor {
@Resource
private UserService userService;
@Test
public void testUserService() {
userService.createUser();
}
}
测试结果,跟预期相符:
调用创建用户业务代码
新增用户数据