Spring 概述
Spring 是什么
Spring 是分层的 Java SE/EE 应用 full-stack (全栈式) 轻量级开源框架。
全栈式:对各种主流技术和框架都进行了整合,同时对三层架构都提供解决方案。
轻量级和重量级的划分主要依据就是看它使用了多少服务,启动时需要加载的资源多少以及耦合度等等。
提供了表现层 Spring MVC 和持久层 Spring JDBC Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
Spring 两大核心:
IOC - Inverse Of Control 控制反转:把对象的创建权交给 Spring
AOP - Aspect Oriented Programming 面向切面编程:在不修改源码的情况下,对方法进行增强
Spring 发展历程
EJB - Enterprise Java Beans:
1997 年,IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB 1.0
1999 年,EJB 1.1 发布
2001 年,EJB 2.0 发布
2003 年,EJB 2.1 发布
2006 年,EJB 3.0 发布
Spring:
Rod Johnson( Spring 之父)改变 Java 世界的大师级人物
2002 年编著《Expert one on one J2EE design and development》
指出了 Java EE 和 EJB 组件框架中的存在的一些主要缺陷;提出普通 java 类依赖注入更为简单的解决方案
2004 年编著《Expert one-on-one J2EE Development without EJB》阐述了 Java EE 开发时不使用 EJB 的解决方式(Spring 雏形),同年 4 月 spring 1.0 诞生
2006 年 10 月,发布 Spring 2.0
2009 年 12 月,发布 Spring 3.0
2013 年 12 月,发布 Spring 4.0
2017 年 9 月, 发布最新 Spring 5.0 通用版(GA)
Spring 优势
耦合:程序间的依赖关系
解耦:降低程序间的依赖关系;体现在编译期不依赖,运行期才依赖
JDBC 例子:
public class JDBCTest {
@Test
public void test1() throws ClassNotFoundException, SQLException {
// 1.注册驱动
// 存在编译期依赖:耦合重的体现
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 去掉 new 关键字:编译期不依赖,运行期才依赖。虽然解决了编译期依赖,但是仍存在硬编码问题
Class.forName("com.mysql.jdbc.Driver");
...
}
}
解耦思路:配置文件 + 反射
Spring:
1)方便解耦,简化开发
Spring 就是一个容器,可以将所有对象创建和关系维护交给 Spring 管理
什么是耦合度?对象之间的关系,通常说当一个模块 (对象) 更改时也需要更改其他模块 (对象),这就是耦合,耦合度过高会使代码的维护成本增加。要尽量解耦
2)AOP 编程的支持
Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
3)声明式事务的支持
通过配置完成事务的管理,无需手动编程
4)方便测试,降低 Java EE API 的使用
Spring 对 Junit 4 支持,可以使用注解测试
5)方便集成各种优秀框架
不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持
Spring 体系结构
以下八大模块可根据需求引入项目使用:
Data Access/Integration
JDBC
ORM
OXM
JMS
Transactions
Web
WebSocket
Servlet
Web
Portlet
AOP
Aspects
Instrumentation
Messaging
Core Container - 相当于盖房子的地基
Beans
Core
Context
SpEL
Test
初识 IOC
概述
控制反转(Inverse Of Control)是一种设计思想,它的目的是指导我们设计出更加松耦合的程序。
控制:在 java 中指的是对象的控制权限(创建、销毁)。
反转:指的是对象控制权从由“开发者在类中手动控制”反转到由“ Spring 容器控制”。
例子:
传统方式 - 需要一个
userDao
实例,需要开发者自己手动创建new UserDao();
IOC 方式 - 需要一个
userDao
实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了spring 控制
自定义 IOC 容器
介绍
需求:实现 Service 层与 Dao 层代码解耦合
步骤分析:
创建 java 项目,导入自定义 IOC 相关坐标
编写 Dao 接口和实现类
编写 Service 接口和实现类
编写测试代码
实现
创建 java 项目,导入自定义 IOC 相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.rendagroupId>
<artifactId>jdbc_springartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8maven.compiler.encoding>
<java.version>1.11java.version>
<maven.compiler.source>1.11maven.compiler.source>
<maven.compiler.target>1.11maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>dom4jgroupId>
<artifactId>dom4jartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.1.6version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
project>
编写 Dao 接口和实现类
public interface IUserDao {
void save();
}
public class UserDaoImpl implements IUserDao {
@Override
public void save() {
System.out.println("UserDao: Successfully Saved.");
}
}
编写 Service 接口和实现类
public interface IUserService {
void save();
}
public class UserServiceImpl implements IUserService {
private IUserDao userDao = new UserDaoImpl();
@Override
public void save() {
userDao.save();
}
}
编写测试代码
public class SpringTest {
@Test
public void test1() {
// 获取业务层对象
IUserService userService = new UserServiceImpl();
// 调用 save 方法
userService.save();
}
}
问题:当前 service 对象和 Dao 对象耦合度太高,而且每次 new 的都是一个新的对象,导致服务器压力过大。
解耦合的原则是编译期不依赖,而运行期依赖就行了。
采用反射方式:
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
// 传统方式使用 new 方式,导致编译期依赖,耦合度太高
// userDao = new UserDaoImpl();
// 反射方式,存在硬编码问题
userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance();
}
@Override
public void save() {
userDao.save();
}
}
测试代码:
@Test
public void test1() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
IUserService userService = new UserServiceImpl();
userService.save();
}
改造步骤分析:
准备一个配置文件
编写一个工厂工具类,工厂类使用 dom4j 来解析配置文件,获取到类的全路径
使用反射生成对应类的实例对象,存到 Map 中,这个 Map 就是 IOC 容器
为了解决反射方式的硬编码问题,先编写 beans.xml
配置文件
<beans>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">bean>
beans>
编写 BeanFactory
工具类
public class BeanFactory {
private static Map iocMap = new HashMap<>();// 程序启动时,初始化对象实例static {// 读取配置文件
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");try {// 解析 xml(dom4j)
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(resourceAsStream);// 编写 xpath 表达式
String xpath = "//bean";// 获取到所有的 bean 标签
List list = document.selectNodes(xpath);// 遍历并使用反射创建对象实例,存到 map 集合(ioc 容器)中for (Element element : list) {
String id = element.attributeValue("id");
String className = element.attributeValue("class");// 使用反射生成实例对象
Object o = Class.forName(className).newInstance();// 存到 map 中:key-id,value-o
iocMap.put(id, o);
}
} catch (DocumentException | InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}public static Object getBean(String beanId) {return iocMap.get(beanId);
}
}
修改 UserServiceImpl
实现类
public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
// 传统方式使用 new 方式,导致编译期依赖,耦合度太高
// userDao = new UserDaoImpl();
// 反射方式,存在硬编码问题
// userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance();
// 反射方式 + 配置文件
userDao = (IUserDao) BeanFactory.getBean("userDao");
}
小结
其实升级后的
BeanFactory
就是一个简单的 Spring 的 IOC 容器所具备的功能。之前需要一个
userDao
实例,开发者自己手动创建new UserDao();
现在需要一个
userDao
实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了 spring 控制最终目标:代码解耦合
Spring 快速入门
介绍
需求:借助 spring 的 IOC 实现 service 层与 DAO 层代码解耦合
步骤分析:
创建 java 项目,导入 spring 开发基本坐标
编写 DAO 接口和实现类
创建 spring 核心配置文件
在 spring 配置文件中配置
UserDaoImpl
使用 spring 相关 API 获得 Bean 实例
实现
创建 java 项目,导入 spring 开发基本坐标
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
编写 Dao
接口和实现类
public interface IUserDao {
void save();
}
public class UserDaoImpl implements IUserDao {
@Override
public void save() {
System.out.println("UserDao: save...");
}
}
创建 spring 核心配置文件 applicationContext.xml
,并在 spring 配置文件中配置 UserDaoImpl
Bean
<?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 id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
beans>
使用 spring 相关 API 获得 Bean
实例
@Test
public void test1(){
// 获取到了 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象 ,加载的同时就创建了 bean 对象存到容器中
// ApplicationContext XmlApplicationContext = new FileSystemXmlApplicationContext("D:\\gitee_repository\\stage-6-module-2\\code\\spring_quickstart\\src\\main\\resources\\applicationContext.xml");
ApplicationContext XmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用上下文对象从 IOC 容器中获取到了 bean 对象
// 方法一:根据 bean id 在容器中找对应的 bean 对象
// IUserDao userDao = (IUserDao) XmlApplicationContext.getBean("userDao");
// 方法二:根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。
// IUserDao userDao = XmlApplicationContext.getBean(IUserDao.class);
// 方法三:根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。
IUserDao userDao = XmlApplicationContext.getBean("userDao", IUserDao.class);
// 调用方法
userDao.save();
}
Spring 的开发步骤总结
导入坐标
创建
Bean
创建
applicationContext.xml
在配置文件中进行 Bean 配置
创建
ApplicationContext
对象,执行getBean
Spring 相关 API
API 继承体系介绍
Spring 的 API 体系异常庞大,现在只关注两个 BeanFactory 和 ApplicationContext
BeanFactory 是 ApplicationContext 的父接口,ApplicationContext 接口下又有 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 子接口
BeanFactory
BeanFactory 是 IOC 容器的核心接口,它定义了 IOC 的基本功能
特点:在第一次调用 getBean() 方法时,创建指定对象的实例
@Test
public void test2(){
// 核心接口,不会创建 bean 对象存到容器中
BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// getBean 的时候才真正创建 bean 对象
IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");
// 调用方法
userDao.save();
}
ApplicationContext
代表应用上下文对象,可以获得 spring 中 IOC 容器的 Bean 对象
特点:在 spring 容器启动时,加载并创建所有对象的实例
常用实现类:
ClassPathXmlApplicationContext
- 它是从类的根路径下加载配置文件,推荐使用这种。FileSystemXmlApplicationContext
- 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。AnnotationConfigApplicationContext
- 当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解。
常用方法:
Object getBean(String name);
- 根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。T getBean(ClassrequiredType);
- 根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。T getBean(String name,ClassrequiredType);
- 根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。
Spring 配置文件
Bean 标签基本配置
用于配置对象交由 Spring 来创建。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
<bean id="" class=""/>
Bean 标签范围配置
<bean id="" class="" scope="">bean>
scope
属性指对象的作用范围,取值如下:
singleton
- 默认值,单例的prototype
- 多例的request
- WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中session
- WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中global session
- WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 global Session 相当于 session
测试 scope 属性:
@Test
public void test3(){
ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao_1);
System.out.println(userDao_2);
}
当 scope 的取值为 singleton 时:
- Bean 的实例化个数:1 个
- Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例
- Bean 的生命周期:
+ 对象创建:当应用加载,创建容器时,对象就被创建了
+ 对象运行:只要容器在,对象一直活着
+ 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
当 scope 的取值为 prototype 时:
- Bean 的实例化个数:多个
- Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean
- Bean 的生命周期:
+ 对象创建:当使用对象时,创建新的对象实例
+ 对象运行:只要对象在使用中,就一直活着
+ 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean 生命周期配置
applicationContext.xml
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
init-method
:指定类中的初始化方法名称destroy-method
:指定类中销毁方法名称
public class UserDaoImpl implements IUserDao {
public void init() {
System.out.println("UserDao: Initialize...");
}
public void destroy() {
System.out.println("UserDao: Destroy");
}
...
}
测试代码
@Test
public void test4(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao_1);
System.out.println(userDao_2);
// 触发 destroy 方法
classPathXmlApplicationContext.close();
}
Bean 实例化三种方式
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
工厂静态方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果频繁使用 B 对象,此时可以将 B 对象的创建权交给 spring 的 IOC 容器,以后在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class StaticFactoryBean {
public static IUserDao createUserDao(){
return new UserDaoImpl();
}
}
<?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 id="userDao" class="com.renda.factory.StaticFactoryBean" factory-method="createUserDao"/>
beans>
工厂普通方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class DynamicFactoryBean {
public IUserDao createUserDao(){
return new UserDaoImpl();
}
}
<?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 id="dynamicFactoryBean" class="com.renda.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>
beans>
Bean 依赖注入方式
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用手动去获取。
Bean 依赖注入方式
构造方法
在 UserServiceImpl
中创建有参构造
public class UserServiceImpl implements IUserService {
IUserDao userDao;
public UserServiceImpl(IUserDao userDao) {
this.userDao = userDao;
}
public UserServiceImpl() {
}
@Override
public void save() {
// 调用 dao 层的 save 方法
userDao.save();
}
}
配置 Spring 容器调用有参构造时进行注入
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
bean>
`set` 方法
在 UserServiceImpl
中创建 set
方法
public class UserServiceImpl implements IUserService {
IUserDao userDao;
public UserServiceImpl(IUserDao userDao) {
this.userDao = userDao;
}
public UserServiceImpl() {
}
@Override
public void save() {
// 调用 dao 层的 save 方法
userDao.save();
}
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
配置 Spring 容器调用 set
方法进行注入
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao">property>
bean>
P 命名空间注入
P 命名空间注入本质也是 set
方法注入,但比起上述的 set
方法注入更加方便,主要体现在配置文件 applicationContext.xml
中。
首先,需要引入 P 命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式:
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
Bean 依赖注入的数据类型
上面操作,都是注入 Bean 对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。
注入数据的三种数据类型:
普通数据类型
引用数据类型
集合数据类型
之前的操作都是对 UserDao 对象的引用进行注入的,属于引用数据类型注入。下面将以 set
方法注入为例,演示普通数据类型和集合数据类型的注入。
注入普通数据类型
public class UserDaoImpl implements IUserDao {
private String username;
private Integer age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
@Override
public void save() {
System.out.println(username);
System.out.println(age);
System.out.println("UserDao: save...");
}
}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="username" value="张人大"/>
<property name="age" value="25"/>
bean>
注入集合数据类型
List 集合注入
public class User {
private String username;
private Integer age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class UserDaoImpl implements IUserDao {
private List list;public void setList(List list) {this.list = list;
}@Overridepublic void save() {
System.out.println("List:" + list);
}
}
<bean id="user" class="com.renda.domain.User">
<property name="username" value="布莱尔"/>
<property name="age" value="18"/>
bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="list">
<list>
<value>aaavalue>
<ref bean="user"/>
list>
property>
bean>
Set 集合注入
public class UserDaoImpl implements IUserDao {
private Set set;public void setSet(Set set) {this.set = set;
}@Overridepublic void save() {
System.out.println("Set:" + set);
}
}
<bean id="user" class="com.renda.domain.User">
<property name="username" value="布莱尔"/>
<property name="age" value="18"/>
bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="set">
<set>
<value>bbbvalue>
<ref bean="user"/>
set>
property>
bean>
Array 数组注入
public class UserDaoImpl implements IUserDao {
private Object[] array;
public void setArray(Object[] array) {
this.array = array;
}
@Override
public void save() {
System.out.println("Array:" + Arrays.toString(array));
}
}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="array">
<array>
<value>cccvalue>
<ref bean="user"/>
array>
property>
bean>
Map 集合注入
public class UserDaoImpl implements IUserDao {
private Map map;public void setMap(Map map) {this.map = map;
}@Overridepublic void save() {
System.out.println("Map:" + map);
}
}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="map">
<map>
<entry key="k1" value="ddd"/>
<entry key="k2" value-ref="user"/>
map>
property>
bean>
Properties 配置注入
public class UserDaoImpl implements IUserDao {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void save() {
System.out.println("Properties:" + properties);
}
}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="properties">
<props>
<prop key="k1">v1prop>
<prop key="k2">v2prop>
<prop key="k3">v3prop>
props>
property>
bean>
配置文件模块化
实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。
并列的多个配置文件:
ApplicationContext ac = new ClassPathXmlApplicationContext("beans1.xml", "beans2.xml", "...");
主从配置文件:
<import resource="applicationContext-xxx.xml"/>
注意:
同一个 xml
中不能出现相同名称的 bean
,如果出现会报错。
多个 xml
如果出现相同名称的 bean
,不会报错,但是后加载的会覆盖前加载的 bean
。
小结
Spring 的重点配置
<bean> 标签:创建对象并放到 spring 的 IOC 容器
id 属性: 在容器中 Bean 实例的唯一标识,不允许重复
class 属性: 要实例化的 Bean 的全限定名
scope 属性: Bean 的作用范围,常用是 Singleton (默认) 和 prototype
<constructor-arg> 标签:属性注入
name 属性:属性名称
value 属性:注入的普通属性值
ref 属性:注入的对象引用值
<property> 标签:属性注入
name 属性:属性名称
value 属性:注入的普通属性值
ref 属性:注入的对象引用值
<list>
<set>
<array>
<map>
<properties>
<import> 标签: 导入其他的 Spring 的分文件
IOC 实战 - DbUtils
DbUtils 是什么?
DbUtils 是 Apache 的一款用于简化 Dao 代码的工具类,它底层封装了 JDBC 技术。
核心对象:
QueryRunner queryRunner = new QueryRunner(DataSource dataSource);
核心方法:
int update()
- 执行增、删、改语句T query()
- 执行查询语句ResultSetHandler
- 这是一个接口,主要作用是将数据库返回的记录封装到实体对象
查询数据库所有账户信息到 Account
实体中:
public class DbUtilsTest {
@Test
public void findAllTest() throws Exception {
// 创建 DBUtils 工具类,传入连接池
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
// 编写 sql
String sql = "select * from account";
// 执行 sql
List list = queryRunner.query(sql, new BeanListHandler (Account.class));// 打印结果for (Account account : list) {
System.out.println(account);
}
}
}
Spring 的 `xml` 整合 `DbUtils`
介绍
需求:基于 Spring 的 xml
配置实现账户的 CRUD 案例
步骤分析:
准备数据库环境
创建 java 项目,导入坐标
编写 Account 实体类
编写 AccountDao 接口和实现类
编写 AccountService 接口和实现类
编写 spring 核心配置文件
编写测试代码
实现
准备数据库环境
CREATE DATABASE `spring_db`;
USE `spring_db`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
);
insert into `account`(`id`,`name`,`money`) values (1,'tom',1000),
(2,'jerry',1000);
创建 java 项目,导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.rendagroupId>
<artifactId>spring_dbutilsartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8maven.compiler.encoding>
<java.version>1.11java.version>
<maven.compiler.source>1.11maven.compiler.source>
<maven.compiler.target>1.11maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.9version>
dependency>
<dependency>
<groupId>commons-dbutilsgroupId>
<artifactId>commons-dbutilsartifactId>
<version>1.6version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
dependencies>
project>
编写 Account
实体类
public class Account {
private Integer id;
private String name;
private Double money;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
编写 AccountDao
接口和实现类
public interface AccountDao {
List findAll();
Account findById(Integer id);
void save(Account account);
void update(Account account);
void delete(Integer id);
}
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
@Override
public List findAll() {
List list = null;// 编写 sql
String sql = "select * from account";try {// 执行 sql
list = queryRunner.query(sql, new BeanListHandler(Account.class));
} catch (SQLException e) {
e.printStackTrace();
}return list;
}@Overridepublic Account findById(Integer id) {
Account query = null;// 编写 sql
String sql = "select * from account where id = ?";try {
query = queryRunner.query(sql, new BeanHandler(Account.class), id);
} catch (SQLException e) {
e.printStackTrace();
}return query;
}@Overridepublic void save(Account account) {
String sql = "insert into account values(null, ?, ?)";try {
queryRunner.update(sql, account.getName(), account.getMoney());
} catch (SQLException e) {
e.printStackTrace();
}
}@Overridepublic void update(Account account) {
String sql = "update `account` set `name` = ?, `money` = ? where `id` = ?";try {
queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}@Overridepublic void delete(Integer id) {
String sql = "delete from `account` where `id` = ?";try {
queryRunner.update(sql, id);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
编写 AccountService 接口和实现类
public interface AccountService {
List findAll();
Account findById(Integer id);
void save(Account account);
void update(Account account);
void delete(Integer id);
}
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List findAll() {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
}
编写 spring 核心配置文件
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"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
">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
bean>
<bean id="accountDao" class="com.renda.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
bean>
<bean id="accountService" class="com.renda.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
bean>
beans>
编写测试代码
public class AccountServiceTest {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
// 测试添加
@Test
public void testSave(){
Account account = new Account();
account.setName("renda");
account.setMoney(888d);
accountService.save(account);
}
// 测试查询
@Test
public void testFindById(){
Account account = accountService.findById(3);
System.out.println(account);
}
// 测试查询所有
@Test
public void testFindAll(){
List all = accountService.findAll();for (Account account : all) {
System.out.println(account);
}
}// 测试更新@Testpublic void testUpdate(){
Account account = new Account();
account.setId(3);
account.setName("Blair");
account.setMoney(2000d);
accountService.update(account);
}// 测试删除@Testpublic void testDelete(){
accountService.delete(3);
}
}
抽取 JDBC 配置文件
applicationContext.xml
加载 jdbc.properties
配置文件获得连接信息。
首先,需要引入 context
命名空间和约束路径:
* 命名空间:
xmlns:context="http://www.springframework.org/schema/context"
* 约束路径:
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
小结
* DataSource 的创建权交由 Spring 容器去完成
* QueryRunner 的创建权交由 Spring 容器去完成,使用构造方法传递 DataSource
* Spring 容器加载 properties 文件
<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>
Spring 注解开发
Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml
配置文件可以简化配置,提高开发效率。
Spring 常用注解
介绍
Spring 常用注解主要是替代 的配置
@Component
- 使用在类上用于实例化 Bean@Controller
- 使用在 web 层类上用于实例化 Bean@Service
- 使用在 service 层类上用于实例化 Bean@Repository
- 使用在 dao 层类上用于实例化 Bean@Autowired
- 使用在字段上用于根据类型依赖注入;当使用注解注入属性时,set 方法可以省略@Qualifier
- 结合@Autowired
一起使用,根据名称进行依赖注入;在自动按照类型注入基础之上,再按照 Bean 的 id 注入;它给字段注入时必须和@Autowired
一起使用,但是给方法参数注入时可以独立使用@Resource
- 相当于@Autowired
+@Qualifier
,按照名称进行注入@Value
- 注入普通属性@Scope
- 标注 Bean 的作用范围@PostConstruct
- 使用在方法上标注该方法是 Bean 的初始化方法@PreDestroy
- 使用在方法上标注该方法是 Bean 的销毁方法
说明:JDK 11 以后完全移除了 javax
扩展导致不能使用 @resource
注解,如果要使用它需要在 Maven 引入依赖:
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
注意:使用注解进行开发时,需要在 applicationContext.xml
中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。
<context:component-scan base-package="com.renda">context:component-scan>
实现
Bean 实例化(IOC)
<bean id="userDao1" class="com.renda.dao.impl.UserDaoImpl">bean>
使用 @Component
或 @Repository
标识 UserDaoImpl
需要 Spring 进行实例化。
// @Component(value = "userDao")
@Repository // 如果没有写 value 属性值,Bean 的 id 为:类名首字母小写
public class UserDaoImpl implements UserDao {
...
}
属性依赖注入(DI)
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao1"/>
bean>
使用 @Autowired
或者 @Autowired
+ @Qulifier
或者 @Resource
进行 userDao
的注入
@Service
public class UserServiceImpl implements UserService {
@Autowired
// @Qualifier("userDao1")
// @Resource(name = "userDao1")
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.uDao = userDao;
}
}
`@Value`
使用 @Value
进行字符串的注入,结合 SpEL (Spring Expression Language)
表达式获得配置参数
@Service
public class UserServiceImpl implements UserService {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
}
`@Scope`
<bean scope=""/>
使用 @Scope
标注 Bean 的范围
@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {
...
}
`Bean` 生命周期
<bean init-method="init" destroy-method="destory" />
使用 @PostConstruct
标注初始化方法,使用 @PreDestroy
标注销毁方法
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
Spring 常用注解整合 `DbUtils`
步骤分析:
拷贝
xml
配置项目,改为注解配置项目修改
AccountDaoImpl
实现类修改
AccountServiceImpl
实现类修改 Spring 核心配置文件
编写测试代码
修改 `AccountDaoImpl` 实现类
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
...
}
修改 `AccountServiceImpl` 实现类
@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
...
}
修改 spring 核心配置文件
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.renda"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
bean>
beans>
Spring 新注解
使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:
非自定义的 Bean 的配置:<bean>
加载 properties 文件的配置:<context:property-placeholder>
组件扫描的配置:<context:component-scan>
引入其他文件:<import>
@Configuration
- 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解@Bean
- 使用在方法上,标注将该方法的返回值存储到 Spring 容器中@PropertySource
- 用于加载 properties 文件中的配置@ComponentScan
- 用于指定 Spring 在初始化容器时要扫描的包@Import
- 用于导入其他配置类
Spring 纯注解整合 `DbUtils`
步骤分析:
编写 Spring 核心配置类
编写数据库配置信息类
编写测试代码
编写 Spring 核心配置类
@Configuration
@ComponentScan("com.renda")
@Import(DataSourceConfig.class)
public class SpringConfig {
@Bean("queryRunner")
public QueryRunner getQueryRunner(@Autowired DataSource dataSource){
return new QueryRunner(dataSource);
}
}
编写数据库配置信息类
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driverClassName}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
编写测试代码
public class AccountServiceTest {
/* ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/renda/service/applicationContext.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService"); */
// 当前改成了纯注解形式
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
...
}
Spring 整合 Junit
普通 Junit 测试问题
在普通的测试类中,需要开发者手动加载配置文件并创建 Spring 容器,然后通过 Spring 相关 API 获得 Bean 实例;如果不这么做,那么无法从容器中获得对象。
开发者可以让 Spring Junit 负责创建 Spring 容器来简化这个操作,直接在测试类注入 Bean 实例;但是需要将配置文件的名称告诉它。
Spring 整合 Junit
步骤分析:
导入 spring 集成 Junit 的坐标
使用
@Runwith
注解替换原来的运行器使用
@ContextConfiguration
指定配置文件或配置类使用
@Autowired
注入需要测试的对象创建测试方法进行测试
导入 spring 集成 Junit 的坐标
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
使用 `@Runwith` 注解替换原来的运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
...
}
使用 `@ContextConfiguration` 指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration(value = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
...
}
使用 `@Autowired` 注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
@Autowired
private AccountService accountService;
...
}