Spring
引言
原生 web 开发中存在哪些问题?
-
传统 Web 开发存在硬编码所造成的过度程序耦合(例如:Service 中作为属性 Dao 对象)。
-
部分Java EE API较为复杂,使用效率低(例如:JDBC 开发步骤)。
-
侵入性强,移植性差(例如:DAO 实现的更换,从 Connection 到 SqlSession)。
Spring 框架概述
【重点】Spring 是一个轻量级的一站式开发框架,核心功能为控制反转和切面编程。
概念
-
Spring 框架,由 Rod Johnson 开发(音乐家)
-
Spring 是一个非常活跃的开源框架,基于 IOC(Inverse of Control 控制反转)和 AOP (Aspect Oriented Programming 面向切面编程为内核)来构架多层JavaEE系统,以帮助分离项目组件之间的依赖关系
-
它的主要目地是简化企业开发
优点
-
方便解耦,简化开发:Spring 就是一个大工厂,可以将所有对象创建和依赖关系维护,交给 Spring 管理
-
AOP 编程的支持:Spring 提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
-
声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程
-
方便程序的测试:Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序
-
方便集成各种优秀框架:Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
-
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,降低 JavaEE API 的使用难度
缺点
-
配置过多
-
启动缓慢,不具备热部署功能
访问与下载
官方网站:Spring | Home
下载地址:JFrog
官方文档:Spring Framework Documentation
GitHub:GitHub - spring-projects/spring-framework: Spring Framework
Spring 架构组成
Spring 架构由诸多模块组成,可分类为:
-
核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
-
测试:模拟对象,TestContext 框架,Spring MVC 测试,WebTestClient。
-
数据访问:事务,DAO支持,JDBC,ORM。
-
Web:WebSocket、Spring MVC 和 Spring Web 框架。
-
集成:远程处理,JMS,JCA,JMX,电子邮件,任务调度,缓存。
-
语言:Kotlin,Groovy,动态语言。
模块说明
GroupId | ArtifactId | 描述 |
---|---|---|
org.springframework | spring-beans | Beans 支持,包含 Groovy |
org.springframework | spring-aop | 基于代理的AOP支持 |
org.springframework | spring-aspects | 基于AspectJ 的切面 |
org.springframework | spring-context | 应用上下文运行时,包括调度和远程抽象 |
org.springframework | spring-context-support | 支持将常见的第三方类库集成到 Spring 应用上下文 |
org.springframework | spring-core | 其他模块所依赖的核心模块 |
org.springframework | spring-expression | Spring 表达式语言,SpEL |
org.springframework | spring-instrument | JVM 引导的仪表(监测器)代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的仪表(监测器)代理 |
org.springframework | spring-jdbc | 支持包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | 支持包括发送/接收JMS消息的助手类 |
org.springframework | spring-messaging | 对消息架构和协议的支持 |
org.springframework | spring-orm | 对象/关系映射,包括对 JPA 和 Hibernate 的支持 |
org.springframework | spring-oxm | 对象/XML 映射(Object/XML Mapping,OXM) |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对 DAO 的支持及 JCA 的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web 服务及 web 应用的 MVC 实现 |
org.springframework | spring-webmvc-portlet | 用于 Portlet 环境的MVC实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 实现,包括对 STOMP 的支持 |
org.springframework | spring-jcl | Jakarta Commons Logging 日志系统 |
Spring IOC & DI
控制反转-IOC
IOC 是 Inversion of Control 的简写,意思是控制反转。是降低对象之间的耦合关系的设计思想。
通过IOC,开发人员不需要关心对象的创建过程,交给Spring容器完成。具体的过程是,程序读取 Spring 配置文件,获取需要创建的 Bean 对象,通过反射机制创建对象的实例。
缺点:对象是通过反射机制实例化出来的,因此对系统的性能有一定的影响。
依赖注入-DI
Dependency Injection,说的是创建对象实例时,同时为这个对象注入它所依赖的属性。相当于把每个 Bean 与 Bean 之间的关系交给容器管理。而这个容器就是 Spring。
例如我们通常在 Service 层注入它所依赖的 Dao 层的实例;在 Controller 层注入 Service 层的实例。
工作原理
入门
1、pom.xml 中导入依赖
<?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.0</modelVersion>
<groupId>com.fc</groupId>
<artifactId>04_Spring_01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--Spring核心依赖,包含aop、beans、core、expression四个jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
2、创建 dao 层接口以及实现类
public interface StudentDao {
void addStudent();
}
public class StudentDaoImpl implements StudentDao {
@Override
public void addStudent() {
System.out.println("addStudent be executed!");
}
}
2、创建 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用bean标签声明一个对象,必须指定class为完整的路径名,id或者name任选一个-->
<bean id="studentDao" class="com.fc.dao.impl.StudentDaoImpl"/>
</beans>
【注意】配置文件要放在 resources 目录下
3、测试
public class StudentDaoTest {
@Test
public void testAddStudent() {
// 根据配置文件创建核心容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据配置文件中bean的id或者name获取对象
StudentDao studentDao = applicationContext.getBean("studentDao", StudentDao);
// 调用对象的方法
studentDao.addStudent();
}
}
【补充】BeanFactory 与 ApplicationContext 区别
ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。
BeanFactory:它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。
ApplicationContext 对 BeanFactory 提供了扩展:
-
国际化处理
-
事件传递
-
Bean 自动装配
-
各种不同应用层的Context实现
Spring 对 bean 的管理
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
bean 标签
使用该标签描述需要 spring 容器管理的对象
Bean 标签的属性
属性 | 描述 |
---|---|
class | 被管理对象的完整的包名.类名 |
name | 给被管理的对象起个名字,可以通过这个名字获取被管理的对象 |
id | 与 name 属性作用相同,如果id和name同时存在,则name属性为别名,都可以使用 |
Scope | Bean的作用域 |
Constructor arguments | 使用构造方法进行依赖注入 |
Properties | 使用set方法进行依赖注入 |
Lazy initialization mode | 懒加载模式 |
Initialization method | 初始化方法 |
Destruction method | 销毁方法 |
创建 bean 的三种方式
使用无参构造方法
StudentDaoImpl
public class StudentDaoImpl implements StudentDao {
@Override
public void addStudent() {
System.out.println("addStudent be executed!");
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--默认使用类中的无参构造方法创建对象-->
<bean id="studentDao" class="com.fc.dao.impl.StudentDaoImpl"/>
</beans>
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建并报错!!!
No default constructor found
使用普通工厂
使用类中的方法创建对象,并存入spring容器
普通工厂类
public class StudentDaoFactory {
public StudentDaoImpl getStudentDaoImpl() {
return new StudentDaoImpl();
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--声明一个工厂对象-->
<bean id="studentDaoFactory" class="com.fc.dao.factory.StudentDaoFactory"/>
<!--通过工厂的方法获取对象-->
<bean id="studentDao2" factory-bean="studentDaoFactory" factory-method="getStudentDaoImpl"/>
</beans>
使用工厂中的静态方法
使用某个类中的静态方法创建对象,并存入spring容器
静态工厂类
public class StaticStudentDaoFactory {
// 通过静态方法获取对象
public static StudentDaoImpl getStudentDaoImpl() {
return new StudentDaoImpl();
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--通过类中的静态方法获取对象-->
<bean id="studentDao3" class="com.fc.dao.factory.StaticStudentDaoFactory" factory-method="getStudentDaoImpl" />
</beans>
bean 的作用范围
bean 标签的 scope 属性用于声明对象的作用域,即指定对象的作用范围。Spring Framework 提供了六种作用域,甚至还可以自定义作用域(了解)。
常用的作用域为 singleton,对应设计模式中的单例模式,使用此模式的 Bean 会在容器加载时创建好。而 prototype 对应设计模式中的原型模式,会在调用 getBean 方法时通过工厂创建。
Scope | Description |
---|---|
singleton | (默认值)每个 Spring IoC 容器将单个 bean 定义范围限制到单个对象实例。(单例) |
prototype | 将单个 bean 定义的作用域限定为任意数量的对象实例。(多例) |
request | 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期;也就是说,每个 HTTP 请求都有一个自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在可感知网络的 Spring ApplicationContext 中有效。 |
session | 将单个 bean 定义的范围限定为 HTTP Session 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
application | 将单个 bean 定义的范围限定为ServletContext 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
websocket | 将单个 bean 定义的范围限定为WebSocket 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
【注意】后五种作用域只有在 Web 项目中可用
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--默认不写作用域为单例模式-->
<bean id="studentDao" class="com.fc.dao.impl.StudentDaoImpl"/>
<!--设置作用域为多例模式-->
<bean id="studentDao4" class="com.fc.dao.impl.StudentDaoImpl" scope="prototype"/>
</beans>
案例代码
public class StudentDaoTest {
// 测试bean作用域
@Test
public void testScope() {
// 根据配置文件创建核心容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据配置文件中bean的id或者name获取对象
// 单例
StudentDao studentDao1 = (StudentDao) applicationContext.getBean("studentDao");
StudentDao studentDao2 = (StudentDao) applicationContext.getBean("studentDao");
StudentDao studentDao3 = (StudentDao) applicationContext.getBean("studentDao");
// 多例
StudentDao studentDao4 = (StudentDao) applicationContext.getBean("studentDao4");
StudentDao studentDao5 = (StudentDao) applicationContext.getBean("studentDao4");
System.out.println(studentDao1 == studentDao2); // true
System.out.println(studentDao2 == studentDao3); // true
System.out.println(studentDao3 == studentDao4); // false
System.out.println(studentDao4 == studentDao5); // false
}
}
bean 的生命周期
// 配置一个方法作为生命周期初始化方法. spring会在对象创建之后立即调用 init-method // 配置一个方法作为生命周期的销毁方法. spring容器在关闭并销毁所有容器中的对象之前调用 destory-method
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--生命周期-->
<bean id="studentDao5" class="com.fc.dao.impl.StudentDaoImpl" init-method="init" destroy-method="destroy"/>
</beans>
案例代码
public class StudentDaoTest {
// 测试Bean的生命周期
@Test
public void testLife() {
// 根据配置文件创建核心容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 根据配置文件中bean的id或者name获取对象
StudentDao studentDao = (StudentDao) applicationContext.getBean("studentDao5");
// 执行对应的方法
studentDao.addStudent();
// 关闭Ioc容器
((ClassPathXmlApplicationContext) applicationContext).close();
}
}
结果
init method be executed!
addStudent be executed!
destroy method be executed!
延迟加载
默认情况下,作为初始化过程的一部分,ApplicationContext
实现会急切地创建和配置所有单例 bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即发现,而不是几小时甚至几天之后。当这种行为不可取时,您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在第一次被请求时创建一个 bean 实例,而不是在启动时。
使用 lazy-init 属性以启用延迟加载,只对单例中有效
<!--默认值,不延迟创建,即在启动时候就创建对象--> lazy-init="false" <!--延迟初始化,在用到对象的时候才会创建对象--> lazy-init="true"
引入其他配置文件
<!--在当前applicationContext.xml配置文件中引入其他spring配置文件--> <import resource="XXX.xml"/>
Spring 依赖注入
Spring 中的依赖注入
依赖注入:Dependency Injection。它是 Spring 框架核心 Ioc 的具体实现。 我们的程序在编写时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。Ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了,我们只需要在配置文件中说明。
构造方法注入
使用类中的构造函数,给成员变量赋值
使用到的标签:
constructor-arg
标签中的属性描述:
属性 | 描述 |
---|---|
type | 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 |
index | 用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始 |
name | 用于指定给构造函数中指定名称的参数赋值 |
value | 用于提供基本类型和String类型的数据 |
ref | 用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象 |
-
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
-
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
Car 实体类
public class Car {
private Float speed;
private String name;
public Car(Float speed, String name) {
this.speed = speed;
this.name = name;
}
}
User 实体类
public class User {
private String name;
private int age;
private Car car;
private List<String> food;
public User(String name, int age, Car car, List<String> food) {
this.name = name;
this.age = age;
this.car = car;
this.food = food;
}
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建Car对象-->
<bean id="car" class="com.fc.bean.Car">
<!--通过构造方法对指定属性赋值-->
<constructor-arg name="speed" value="100.0F" type="java.lang.Float" index="0"/>
<constructor-arg name="name" value="保时捷" type="java.lang.String" index="1"/>
</bean>
<!--创建User对象-->
<bean id="user" class="com.fc.bean.User">
<!--通过构造方法对指定属性赋值-->
<constructor-arg name="name" value="张三" type="java.lang.String" index="0"/>
<constructor-arg name="age" value="20" type="int" index="1"/>
<constructor-arg name="food" type="java.util.List">
<!--给集合中赋值-->
<list>
<value>烤羊排</value>
<value>烤面筋</value>
<value>烤韭菜</value>
</list>
</constructor-arg>
<!--引用一个Car对象-->
<constructor-arg name="car" ref="car" type="com.fc.bean.Car"/>
</bean>
</beans>
测试类
public class UserTest {
// 测试构造方法注入
@Test
public void testUser() {
// 创建Ioc容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取User对象
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
}
set 方法注入【重点】
通过实体类中的 set 方法进行依赖注入
使用到的标签:
property
标签中的属性描述:
属性 | 描述 |
---|---|
name | 用于指定给构造函数中指定名称的参数赋值 |
value | 用于提供基本类型和String类型的数据 |
ref | 用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象 |
Car 实体类
public class Car {
private Float speed;
private String name;
// Constructor、Setters、Getters
}
User 实体类
public class User {
private String name;
private int age;
private Car car;
private List<String> food;
// Constructor、Setters、Getters
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建Car对象-->
<bean id="car2" class="com.fc.bean.Car">
<!--给speed和name属性设置对应的值-->
<property name="speed" value="100.0F"/>
<property name="name" value="兰博基尼"/>
</bean>
<!--创建User对象-->
<bean id="user2" class="com.fc.bean.User">
<!--给User类中的属性设置值-->
<property name="name" value="玉田"/>
<property name="age" value="30"/>
<property name="food">
<!--给List集合设置值-->
<list>
<value>烤地瓜</value>
<value>烤玉米</value>
<value>烤韭菜</value>
</list>
</property>
<!--引用一个Car对象-->
<property name="car" ref="car2"/>
</bean>
</beans>
测试类
public class UserTest {
// 测试构造方法注入
@Test
public void testUser() {
// 创建Ioc容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取User对象
User user = applicationContext.getBean("user2", User.class);
System.out.println(user);
}
}
复杂类型的注入
给 List、Set、Map 以及数组这种复杂类型进行注入
使用到的标签:
标签 描述 list 用于给List集合赋值(标签中声明value标签) set 用于给Set集合赋值(标签中声明value标签) map 用于给Map映射赋值(标签中声明entry标签,使用key和value的属性赋值)【注意与props互斥】 props 用于给Map映射赋值(标签中声明prop标签,使用key属性赋值)【注意与map互斥】 array 用于给数组赋值
ComplexType 实体类
public class ComplexType {
private List<String> list;
private Set<Integer> set;
private Map<Integer, String> map;
private Object[] objects;
// Constructor、Getters、Setters、toString
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--测试复杂类型的注入-->
<bean id="complexType" class="com.fc.bean.ComplexType">
<property name="list">
<!--给List设置值-->
<list>
<value>Hello</value>
<value>World</value>
<value>Java</value>
</list>
</property>
<property name="set">
<!--给Set设置值-->
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
<property name="map">
<!--给Map设置键值对-->
<map>
<entry key="1" value="易烊千玺"/>
<entry key="2" value="迪丽热巴"/>
<entry key="3" value="张三"/>
</map>
<!--使用这种方式也可以-->
<!--<props>-->
<!--<prop key="1">易烊千玺</prop>-->
<!--<prop key="2">迪丽热巴</prop>-->
<!--<prop key="3">张三</prop>-->
<!--</props>-->
</property>
<property name="objects">
<!--给数组设置值-->
<array>
<value>1</value>
<value>1.1</value>
<value>true</value>
<value>吃饭</value>
</array>
</property>
</bean>
</beans>
测试类
public class UserTest {
// 测试复杂类型的注入
@Test
public void testComplexType() {
// 创建Ioc容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取ComplexType对象
ComplexType complexType = applicationContext.getBean("complexType", ComplexType.class);
System.out.println(complexType);
}
}
结果
ComplexType{list=[Hello, World, Java], set=[1, 2, 3], map={1=易烊千玺, 2=迪丽热巴, 3=张三}, objects=[1, 1.1, true, 吃饭]}
注解注入【非常重要】
控制反转常用注解
注解 | 描述 |
---|---|
@Component | 用于把当前类对象存入Spring容器中。value属性:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写 |
@Controller | 一般用在控制层,和 Component 的效果一样 |
@Service | 一般用在业务层,和 Component 的效果一样 |
@Repository | 一般用在持久层,和 Component 的效果一样 |
依赖注入常用注解
注解 | 描述 |
---|---|
@Autowired | 自动按照类型注入(首字母小写) |
@Qualifier | 在根据类型中注入的基础之上再按照名称注入,value属性:用于指定注入bean的id |
@Resource | 直接按照bean的id注入。它可以独立使用,name属性:用于指定bean的id |
@Value | 用于给成员变量注入基本类型和String类型的数据 |
【注意】
1、以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现,另外,集合类型的注入只能通过XML来实现。因为 Resource 注解是J2EE的,而不是Spring本身的,所以在使用时需要在 pom.xml 中导入依赖
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
scope
用于改变作用范围的,作用和在 bean 标签中使用 scope 属性实现的功能是一样的
注解 | 描述 |
---|---|
Scope | 用于指定bean的作用范围,value:指定范围的取值。常用取值:singleton(单例) prototype(多例) |
生命周期
生命周期相关,作用和在bean标签中使用 init-method 和 destroy-method 的作用是一样的
注解 | 描述 |
---|---|
@PreDestroy | 用于指定销毁方法 |
@PostConstruct | 用于指定初始化方法 |
spel 注入【了解】
实体类
@Data
public class Car {
private String name;
}
@Data
public class User {
private String name;
private Integer age;
private Car car;
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="car" class="com.fc.bean.Car">
<property name="name" value="兰博基尼"/>
</bean>
<bean name="user" class="com.fc.bean.User">
<!--使用 spel 注入-->
<property name="name" value="#{car.name}"/>
<property name="age" value="23"/>
<property name="car" ref="car"/>
</bean>
</beans>
测试类
public class SetDITest {
@Test
public void SetTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
}
}
结果
User(name=兰博基尼, age=23, car=Car(name=兰博基尼))
p命名空间注入【了解】
实体类
@Data
public class Car {
private String name;
}
@Data
public class User {
private String name;
private Integer age;
private Car car;
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="car" class="com.fc.bean.Car">
<property name="name" value="兰博基尼"/>
</bean>
<bean name="user" class="com.fc.bean.User" p:name="Smoot" p:age="22" p:car-ref="car"/>
</beans>
测试类
public class SetDITest {
@Test
public void SetTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
}
}
结果
User(name=Smoot, age=22, car=Car(name=兰博基尼))
Spring 访问数据库【了解】
概述
Spring 框架提供了可扩展的 SQL 数据库的支持。JdbcTemplate 是 Spring 提供的最经典也是最流行的 jdbc 实现方式之一。
搭建环境
创建 User 数据表
create table user(id int primary key auto_increment, username varchar(20) unique not null, password varchar(16) not null);
pom.xml 引入依赖
<?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.0</modelVersion>
<groupId>com.fc</groupId>
<artifactId>04_Spring_03</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--Spring核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<!--Spring Jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
声明 User 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
xml 方式访问数据库
Spring的 JdbcTemplate(了解会用)在 Spring 中提供了一个可以操作数据库的对象org.springframework.jdbc.core.JdbcTemplate,对象封装了 jdbc 技术,JDBC 的模板对象与 DBUtils 中的 QueryRunner 非常相似。
声明 dao 层实现类
public class JdbcTemplateDaoImpl {
// 声明一个JdbcTemplate,类似于DbUtils中的QueryRunner
private JdbcTemplate template;
// 查询方法
public void select() {
// 调用查询方法,传入一个Sql语句和结果集映射处理器对象
List<User> list = template.query("select * from user", new BeanPropertyRowMapper<>(User.class));
// 增强for循环遍历
for (User user : list) {
System.out.println(user);
}
}
public void setTemplate(JdbcTemplate template) {
this.template = template;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd ">
<!--引入外部资源文件-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 配置druid数据库连接池,并从外部资源文件中获取对应参数 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
</bean>
<!-- 配置spring的jdbcTemplate-->
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置dao层实现类-->
<bean name="jdbcTemplateDaoImpl" class="com.fc.dao.impl.JdbcTemplateDaoImpl">
<property name="template" ref="jdbcTemplate"/>
</bean>
</beans>
测试类
public class JdbcTemplateTest {
@Test
public void testSelect() {
// 声明Spring容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取dao层实现类对象
JdbcTemplateDaoImpl jdbcTemplateTest = applicationContext.getBean("jdbcTemplateDaoImpl", JdbcTemplateDaoImpl.class);
// 调用方法
jdbcTemplateTest.select();
}
}
注解方式访问数据库
Spring 中的相关注解【非常重要】
注解 | 描述 | 属性 | 注意 |
---|---|---|---|
@Configuration | 指定当前类是一个配置类 | 当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写 | |
@ComponentScan | 用于通过注解指定 Spring 在创建容器时要扫描的包 | value | 等同于xml中: <context:component-scan base-package="com.fc"/> |
@PropertySource | 用于指定 properties 文件的位置 | value | 关键字:classpath,表示类路径下 等同于xml中: <context:property-placeholder location="classpath:jdbc.properties"/> |
@Bean | 用于把当前方法的返回值作为bean 对象存入 Spring 的 ioc 容器中,通常用在配置类中 | name | 当使用注解配置方法时,如果方法有参数,在参数前加:@Qualifier("@Bean注解中name的值"),Spring框架会去容器中查找有没有可用的 bean 对象查找的方式和Autowired注解的作用是一样的。 |
@Import | 用于导入其他的配置类 | value | 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类 等同于xml中: <import resource="xxx.xml"/> |
案例代码
JdbcConfiguration 配置类
// 引入jdbc.properties文件
@PropertySource("classpath:jdbc.properties")
// 扫描指定包下的注解
@ComponentScan("com.fc")
// 声明这是一个配置类
@Configuration
public class JdbcConfiguration {
@Value("${jdbc.driverClassName}")
private String driveClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 此方法用于获取一个JdbcTemplate对象
*
* 并通过@Bean注解将获取到的JdbcTemplate对象注入到Ioc容器中
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate() {
JdbcTemplate jdbcTemplate = null;
// 声明Properties对象并且设置参数
Properties properties = new Properties();
properties.setProperty("driveClassName", driveClassName);
properties.setProperty("url", url);
properties.setProperty("username", username);
properties.setProperty("password", password);
try {
// 使用Properties对象创建Druid连接池数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 通过数据源对象获取JdbcTemplate
jdbcTemplate = new JdbcTemplate(dataSource);
} catch (Exception e) {
e.printStackTrace();
}
return jdbcTemplate;
}
}
测试类
public class AnnotationTest {
@Test
public void testAnnotation() {
// 通过注解获取Ioc容器
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JdbcConfiguration.class);
// 从容器中获取JdbcTemplate模板对象
JdbcTemplate template = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
// 查询数据库并获取List集合
List<User> list = template.query("select * from user", new BeanPropertyRowMapper<>(User.class));
// 增强for循环遍历
for (User user : list) {
System.out.println(user);
}
}
}
Spring AOP
概述
AOP Aspect Oriented Programing 面向切面编程
AOP 采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存、日志)
Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
AOP 底层原理
代理机制
动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
-
基于接口的动态代理
-
基于子类的动态代理
Spring 的 AOP 代理
JDK 动态代理:被代理对象必须要实现接口。才能产生代理对象。如果没有接口将不能使用动态代理技术。
CGLib 动态代理:第三方代理技术,CGLib代理。可以对任何类生成代理。代理的原理是对目标对象进行继承代理,如果目标对象被 final 修饰。那么该类无法被 CGLib 代理。
【重点】Spring 框架,如果类实现了接口,就使用 JDK 的动态代理生成代理对象,如果这个类没有实现任何接口,使用 CGLIB 生成代理对象。
AOP 相关术语
术语 | 描述 |
---|---|
Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点 |
Pointcut(切入点) | 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 |
Advice(通知/增强) | 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) |
Introduction(引介) | 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或字段 |
Target(目标对象) | 代理的目标对象(被代理对象) |
Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。 spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入 |
Proxy(代理) | 一个类被AOP织入增强后,就产生一个结果代理类 |
Aspect(切面) | 是切入点和通知(引介)的结合 |
通知类型
通知类型 | 描述 |
---|---|
Before | 前置通知,目标方法调用之前执行 |
After-returning | 后置通知,目标方法运行之后调用 (如果出现异常不会调用) |
After-throwing | 异常通知,出现异常调用 |
After | 最终通知,在目标方法运行之后调用(无论是否出现异常) |
Around | 环绕通知,目标方法之前和之后都会调用(ProceedingJoinPoint对象 -->> 调用proceed方法) |
Spring 使用 AOP
1、pom.xml 文件中引入依赖
<?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.0</modelVersion>
<groupId>com.fc</groupId>
<artifactId>04_Spring_05_AOP</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
2、UserService 接口
public interface UserService {
void addUser();
void updateUser();
}
3、UserServiceImpl 接口实现类
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加了一位新同学");
}
@Override
public void updateUser() {
int i = 1 / 0;
System.out.println("修改了一位学生的信息");
}
}
通过 XML 配置文件 【重点】
1、使用注解声明切面类
// 增强,切面类
public class XMLAdvice {
// 前置通知
public void before() {
System.out.println("before advice --> 前置通知,方法执行前调用");
}
// 后置通知
public void afterReturning() {
System.out.println("AfterReturning --> 后置通知,出现异常时不执行");
}
// 异常通知
public void afterThrowing() {
System.out.println("AfterThrowing --> 出现异常时执行");
}
// 最终通知
public void after() {
System.out.println("After advice --> 最终通知,出现异常时也会调用");
}
// 环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Around --> 环绕通知,方法调用之前执行");
// 执行方法
Object proceed = joinPoint.proceed();
System.out.println("Around --> 环绕通知,方法调用之后执行");
return proceed;
}
}
2、applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
<!-- 配置目标对象 -->
<bean name="userServiceImpl" class="com.fc.service.impl.UserServiceImpl"/>
<!-- 配置通知对象 -->
<bean name="XMLAdvice" class="com.fc.advice.XMLAdvice"/>
<!--aop配置,声明基于类的方式实现代理-->
<aop:config proxy-target-class="true">
<!--声明切点-->
<aop:pointcut id="pointcut" expression="execution(* com.fc.service.impl.*ServiceImpl.*(..))"/>
<!--配置切面-->
<aop:aspect ref="XMLAdvice">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
3、测试类
public class XMLTest {
@Test
public void testXML() {
// 获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取对象
UserService userService = applicationContext.getBean("userServiceImpl", UserServiceImpl.class);
// 执行方法
userService.addUser();
userService.updateUser();
}
}
【注意】如果出现 BeanNotOfRequiredTypeException 异常时说明使用了接口的动态代理,需要将 proxy-target-class 属性设置为 true 即可
<aop:config proxy-target-class="true"> ... </aop:config>
通过注解【重点】
使用注解声明切面类
// 声明切面类
@Aspect
public class AnnotationAdvice {
// 声明切点
@Pointcut("execution(* com.fc.service.impl.*ServiceImpl.*(..))")
public static void pointCut() {}
// 声明前置通知
@Before("AnnotationAdvice.pointCut()")
public void before() {
System.out.println("before advice --> 前置通知,方法执行前调用");
}
// 声明后置通知,出现异常时不执行
@AfterReturning("AnnotationAdvice.pointCut()")
public void afterReturning() {
System.out.println("AfterReturning --> 后置通知,出现异常时不执行");
}
// 声明后置通知,出现异常时执行
@AfterThrowing("AnnotationAdvice.pointCut()")
public void afterThrowing() {
System.out.println("AfterThrowing --> 出现异常时执行");
}
// 最终通知
@After("AnnotationAdvice.pointCut()")
public void after() {
System.out.println("After advice --> 最终通知,出现异常时也会调用");
}
// 环绕通知
@Around("AnnotationAdvice.pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around --> 环绕通知,方法调用之前执行");
// 执行方法
Object proceed = joinPoint.proceed();
System.out.println("Around --> 环绕通知,方法调用之后执行");
return proceed;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
<!-- 配置目标对象 -->
<bean name="userServiceImpl" class="com.fc.service.impl.UserServiceImpl"/>
<!-- 配置通知对象 -->
<bean name="myAdvice" class="com.fc.advice.AnnotationAdvice"/>
<!-- 开启使用注解完成织入,声明基于类的方式实现代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
测试类
public class AnnotationTest {
@Test
public void testAnnotation() {
// 从配置文件中获取IOC容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取对象
UserService userService = applicationContext.getBean("userServiceImpl", UserServiceImpl.class);
// 执行方法
userService.addUser();
userService.updateUser();
}
}
【注意】如果出现 BeanNotOfRequiredTypeException 异常时说明使用了接口的动态代理,需要将 proxy-target-class 属性设置为 true 即可
<!-- 开启注解配置AOP,并且基于类的动态代理 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 开启注解配置AOP,并且基于接口的动态代理 --> <aop:aspectj-autoproxy proxy-target-class="false"/>
补充:切入点表达式的写法
applicationContext.xml 中 execution 表达式的写法
类型匹配语法
* 匹配任何数量字符,只有一个。 .. 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。 + 匹配指定类型的子类型;仅能作为后缀放在类型模式后边
案例
// 匹配String类型;
java.lang.String
// 匹配java包下的任何“一级子包”下的String类型;如匹配java.lang.String,但不匹配java.lang.ss.Strin
java.*.String
// 匹配java包及任何子包下的任何类型; 如匹配java.lang.String、java.lang.annotation.Annotation
java..*
// 匹配任何java.lang包下的以ing结尾的类型;
java.lang.*ing
// 匹配java.lang包下的任何Number的自类型;如匹配java.lang.Integer,也匹配java.math.BigInteger
java.lang.Number+
基本语法格式
执行(注解 访问修饰符 返回值类型 方法名(参数列表) 异常列表) execution(@org.junit.Test public java.lang.String com.fc.service.impl.UserServceImpl.add(String username, Sring passowrd) )
-
注解:可选,方法上持有的注解,如@Deprecated;
-
修饰符:可选,如public、protected;
-
返回值类型:必填,可以是任何类型模式;“*”表示所有类型;
-
方法名:必填,可以使用“*”进行模式匹配;
-
参数列表:“()”表示方法没有任何参数;“(..)”表示匹配接受任意个参数的方法
-
异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割
模式 | 描述 |
---|---|
public * *(..) | 任何公共方法的执行 |
* cn.javass..IPointcutService.*() | cn.javass包及所有子包下IPointcutService接口中的任何无参方法 |
* cn.javass.. * .*(..) | cn.javass包及所有子包下任何类的任何方法 |
* cn.javass..IPointcutService.*( *) | cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法 |
* (!cn.javass..IPointcutService+).*(..) | 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法 |
* cn.javass..IPointcutService+.*() | cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法 |
* cn.javass..IPointcut*.test *(java.util.Date) | cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
@java.lang.Deprecated * *(..) | 任何持有@java.lang.Deprecated注解的方法 |
常见写法
<aop:config>
<!-- 配置切入点 切入点表达式的写法:execution(表达式)
public void com.fc.service.UserServiceImpl.save()
void com.fc.service.UserServiceImpl.save() 其他修饰符无返回值的save空参方法
* com.fc.service.UserServiceImpl.save() 有或者无返回值的save空参方法
* com.fc.service.UserServiceImpl.*() 有或者无返回值的所有空参方法
* com.fc.service.*ServiceImpl.*(..) 有或者无返回值的所有有参或者空参方法
* com.fc.service..*ServiceImpl.*(..) 一般不用,service包下的子包和孙包以ServiceImpl结尾的类中的方法
-->
<aop:pointcut expression="execution(* com.fc.service.*ServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAdvice" >
<!-- 指定名为before方法作为前置通知 -->
<aop:before method="before" pointcut-ref="pc" />
<!-- 后置通知,出现异常不会执行 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc" />
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pc" />
<!-- 异常拦截通知,出现异常时执行 -->
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<!-- 后置通知,无论是否出现异常都会执行 -->
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
Spring 事务
Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 MyBatis、Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。
相关 API
// 平台事务管理器
public interface PlatformTransactionManager {
// 获取事务状态
TransactionStatus getTransaction(TransactionDefinition definition);
// 回滚事务
void rollback(TransactionStatus status);
// 提交事务
void commit(TransactionStatus status);
}
// 事务定义
public interface TransactionDefinition {
// 事务的隔离级别
ISOLATION_XXX
// 事务的传播行为
PROPAGATION_XXX
// 过期时间
int TIMEOUT_DEFAULT = -1;
}
// 事务状态
public interface TransactionStatus {
// 是否有保存点
boolean hasSavepoint();
// 是否是一个新的事务【子类】
public boolean isNewTransaction();
}
【注意】PlatformTransactionManager 通过 TransactionDefinition 设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由 TransactionStatus 记录。
PlatformTransactionManager
Spring 为不同的持久化框架提供了不同 PlatformTransactionManager 接口实现
类 | 描述 |
---|---|
DataSourceTransactionManager | 使用Spring JDBC或 iBatis 进行持久化数据时使用 |
HibernateTransactionManager | 使用Hibernate进行持久化数据时使用 |
jpa.JpaTransactionManager | 使用JPA进行持久化时使用 |
JdoTransactionManager | 当持久化机制是Jdo时使用 |
JtaTransactionManager | 使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用 |
TransactionDefinition 事务的隔离级别【重点】
Field | 描述 |
---|---|
ISOLATION_DEFAULT | mysql 中对应 repeatable_read,Oracle 对应 read_committed |
ISOLATION_READ_UNCOMMITTED | 读未提交 |
ISOLATION_READ_COMMITTED | 读已提交 |
ISOLATION_REPEATABLE_READ | 可重复读 |
ISOLATION_SERIALIZABLE | 可串行化(可序列化) |
事务的传播行为
【作用】解决业务层之间调用的事务的关系
Field | 作用 | 描述 |
---|---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果不存在就新建一个 | A,B 如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务.(A,B是在一个事务中) |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在,就不使用事务 | A,B 如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在,抛出异常 | A,B 如果A有事务,B使用A的事务,如果A没有事务,抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果有事务存在,挂起当前事务,创建一个新的事务 | A,B 如果A有事务,B将A的事务挂起,重新创建一个新的事务.(A,B不在一个事务中.事务互不影响) |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 | A,B 非事务的方式运行,A有事务,就会挂起当前的事务 |
PROPAGATION_NEVER | 以非事务方式运行,如果有事务存在,抛出异常 | 无 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套事务执行 | 基于SavePoint技术 A,B A有事务,A执行之后,将A事务执行之后的内容保存到SavePoint.B事务有异常的话,用户需要自己设置事务提交还是回滚 |
【重点】
PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_NESTED
事务环境搭建
准备数据库并插入数据
create table account(id int primary key auto_increment, name varchar(20) not null unique, money decimal(11, 2) default 0.00);
insert into account(name, money) values('玉田', 10000);
insert into account(name, money) values('刘英', 10000);
pom.xml 文件
<?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.0</modelVersion>
<groupId>com.fc</groupId>
<artifactId>04_Spring_06_Transaction</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--Spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<!--Spring事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.3</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!--Spring JDBC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--Druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
声明 Account 实体类
public class Account {
private int id;
private String name;
private BigDecimal money;
// Constructor、Getters、Setters
}
Dao 层接口
public interface AccountDao {
// 加钱
void increaseMoney(Integer id, BigDecimal money);
// 减钱
void decreaseMoney(Integer id, BigDecimal money);
}
Dao 层接口实现类
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void increaseMoney(Integer id, BigDecimal money) {
getJdbcTemplate().update("update account set money = money + ? where id = ?", money, id);
System.out.println("加钱成功");
}
@Override
public void decreaseMoney(Integer id, BigDecimal money) {
getJdbcTemplate().update("update account set money = money - ? where id = ?", money, id);
System.out.println("减钱成功");;
}
}
业务层接口
public interface AccountService {
/**
* 转账
*
* @param from 从哪里转
* @param to 转到哪里去
* @param money 转账金额
*/
void transfer(Integer from, Integer to, BigDecimal money);
}
业务层接口实现类
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void transfer(Integer from, Integer to, BigDecimal money) {
// 减钱
accountDao.decreaseMoney(to, money);
System.out.println("发生了异常");
int num = 1 / 0;
// 加钱
accountDao.increaseMoney(from, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd ">
<!--引入外部资源文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--依赖注入-->
<bean name="accountDao" class="com.fc.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="accountService" class="com.fc.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
测试类
public class AccountTest {
@Test
public void testTransfer() {
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取业务层对象
AccountService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
// 执行转账方法
accountService.transfer(1, 2, BigDecimal.valueOf(100.00));
}
}
上述测试中,如果在转账方法中出现异常后,数据前后会产生不一致,此时,我们需要用Spring的事务管理来解决这一问题。
编程式事务【垃圾】
代码量增加,代码有侵入性
业务层接口实现类
public class ProgrammingServiceImpl implements AccountService {
private AccountDao accountDao;
// 声明一个事务模板对象
private TransactionTemplate transactionTemplate;
@Override
public void transfer(final Integer from, final Integer to, final BigDecimal money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
// 减钱
accountDao.decreaseMoney(from, money);
int i = 1 / 0;// 如果发生异常数据(钱)不会丢失
// 加钱
accountDao.increaseMoney(to, money);
}
});
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd ">
<!--引入外部资源文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--声明事务管理器,封装了事务相关的所有操作,必须依赖于连接池-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--声明事务模板对象-->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!--依赖注入-->
<bean name="accountDao" class="com.fc.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="programmingService" class="com.fc.service.impl.ProgrammingServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
</beans>
测试类
public class AccountTest {
// 测试编程式事务
@Test
public void testProgramming() {
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取业务层对象
AccountService accountService = applicationContext.getBean("programmingService", ProgrammingServiceImpl.class);
// 执行转账方法
accountService.transfer(1, 2, BigDecimal.valueOf(100.00));
}
}
声明式事务【重点】
业务层接口实现类
public class StatementServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void transfer(Integer from, Integer to, BigDecimal money) {
// 减钱
accountDao.decreaseMoney(to, money);
System.out.println("发生了异常");
int num = 1 / 0;
// 加钱
accountDao.increaseMoney(from, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
<!--引入外部资源文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--声明事务管理器,封装了事务相关的所有操作,必须依赖于连接池-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--声明指定的方法名以及事务的隔离级别,传播行为,是否只读-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config proxy-target-class="true">
<!--配置切点-->
<aop:pointcut id="pointcut" expression="execution(* com.fc.service.impl.StatementServiceImpl.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
<!--依赖注入-->
<bean name="accountDao" class="com.fc.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="statementService" class="com.fc.service.impl.StatementServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
测试类
public class AccountTest {
// 测试声明式事务
@Test
public void testStatement() {
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取业务层对象
AccountService accountService = applicationContext.getBean("statementService", StatementServiceImpl.class);
// 执行转账方法
accountService.transfer(1, 2, BigDecimal.valueOf(100.00));
}
}
注解式事务【重点】
业务层接口实现类
// 使用注解声明该类中的所有方法都支持事务,此注解也可以配置在方法上
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, readOnly = false)
public class AnnotationServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void transfer(Integer from, Integer to, BigDecimal money) {
// 减钱
accountDao.decreaseMoney(to, money);
System.out.println("发生了异常");
int num = 1 / 0;
// 加钱
accountDao.increaseMoney(from, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
applicationContext.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
<!--引入外部资源文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源-->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--声明事务管理器,封装了事务相关的所有操作,必须依赖于连接池-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启注解式事务支持-->
<tx:annotation-driven/>
<!--依赖注入-->
<bean name="accountDao" class="com.fc.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="annotationService" class="com.fc.service.impl.AnnotationServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
测试类
public class AccountTest {
// 测试注解式事务
@Test
public void testAnnotation() {
// 获取IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Ioc容器中获取业务层对象
AccountService accountService = applicationContext.getBean("annotationService", AccountService.class);
// 执行转账方法
accountService.transfer(1, 2, BigDecimal.valueOf(100.00));
}
}
Spring 整合 Junit
概述
编写测试类时每次都要创建 Spring 容器,Spring 框架提供了一个运行器,通过读取配置文件或者注解的方式创建 Spring 容器。可以使用这个运行器替换掉 Junit 中自带的运行器来完成手动创建的工作。
相关注解
注解 | 描述 |
---|---|
@RunWith | 用于指定 junit 运行环境,是 junit 提供给其他框架测试环境接口扩展 |
@ContextConfiguration | 指定创建Spring容器的方式,常用属性为locations = "classpath:applicationContext.xml",或者classes = SpringConfig.class |
@Transactional | 事务相关必须加上这个注解 |
案例代码
1、导入 pom.xml 中的依赖
<?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.0</modelVersion>
<groupId>com.fc</groupId>
<artifactId>04_Spring_09_Junit</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--Spring核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<!--Spring-test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.5</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
</project>
2、声明一个类并添加对应的注解
@Repository
public class UserDaoImpl {
public void getUser() {
System.out.println("获取一个学生对象");
}
}
通过配置文件的方式
3、声明 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">
<!--开启注解扫描-->
<context:component-scan base-package="com.fc"/>
</beans>
4、编写测试类
// 使用Spring的运行器替换Junit自带运行器,可以自动创建Spring容器对象
@RunWith(SpringJUnit4ClassRunner.class)
// 指定通过配置文件的方式创建Spring容器
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class XMLTest {
// 依赖注入
@Autowired
private UserDaoImpl userDao;
// 单元测试
@Test
public void test() {
// 执行方法
userDao.getUser();
}
}
通过注解的方式
3、声明一个配置类
// 扫描指定包下的注解
@ComponentScan("com.fc")
// 声明配置类
@Configuration
public class SpringConfig {
}
4、编写测试类
// 使用Spring的运行器替换Junit自带运行器,可以自动创建Spring容器对象
@RunWith(SpringJUnit4ClassRunner.class)
// 指定通过配置类的方式创建Spring容器
@ContextConfiguration(classes = SpringConfig.class)
public class AnnotationTest {
// 依赖注入
@Autowired
private UserDaoImpl userDao;
// 单元测试
@Test
public void test() {
userDao.getUser();
}
}
补充:Spring 各 jar 包说明
jar | 描述 |
---|---|
spring-aspects | Spring提供的对 AspectJ 框架的整合,内部使用的还是 aspectjweaver |
spring-beans | Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 |
spring-context | 在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件 服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持 |
spring-context-support | Spring context的扩展支持,用于MVC方面 |
spring-core | Spring的核心工具包 |
spring-expression | Spring表达式语言 |
spring-framework-bom | 统一各个 jar 包的版本,包含了所有的 Spring jar |
spring-instrument | Spring 对服务器的代理接口 |
spring-jcl | 日志框架 |
spring-jdbc | 对 JDBC 的简单封装,操作数据库必须导入此依赖 |
spring-jms | 为简化 jms api 的使用而做的简单封装 |
spring-messaging | 为集成messaging api和消息协议提供支持 |
spring-orm | 整合第三方的orm实现,如hibernate,ibatis,jdo以及spring 的jpa实现 |
spring-oxm | Spring 对于 object/xml 映射的支持,可以让JAVA与XML之间来回切换 |
spring-test | 对 JUNIT 等测试框架的简单封装 |
spring-tx | 为JDBC、Hibernate、JDO、JPA等提供的一致的声明式和编程式事务管理 |
spring-web | 包含Web应用开发时,用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类、Struts与JSF集成类、文件上传的支持类、Filter类和大量工具辅助类 |
spring-webmvc | 包含SpringMVC框架相关的所有类。包含国际化、标签、Theme、视图展现的FreeMarker、JasperReports、Tiles、Velocity、XSLT相关类。当然,如果你的应用使用了独立的MVC框架,则无需这个JAR文件里的任何类 |
spring-websocket | 封装好的 WebSocket 框架 |