目录
5.4.1事务传播行为(propagation behavior)
一、Spring概念
(1)、Spring是轻量级的开源的JavaEE框架
(2)、Spring可以解决企业应用开发发复杂性
(3)、Spring两个核心部件:IOC和Aop
1)、IOC:控制反转,把创建对象过程交给Spring进行管理
2)、Aop:面向切面,不修改源代码进行功能增强
(4)、Spring特点:
1)方便解耦,简化开发
2)、Aop编程支持
3)、方便程序测试
4)、方便和其他框架进行整合
5)、方便进行事务操作
6)、降低API开发难度
1.1入门案例
jJDK要求:spring6版本jdk最低要求jdk17
1.1.1、入门案例开发步骤![](https://img-blog.csdnimg.cn/10946a1db4f949de9fc213f88694273d.png)
第一步 引入spring相关依赖
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- spring context依赖-->
<!-- 当你引入spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!-- Junit-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.3</version>
</dependency>
</dependencies>
第二步 创建类,定义属性和方法
public class User {
private String userName;
private Integer age;
public void add(){
System.out.println("add....");
}
}
第三步 按照spring要求创建配置文件(xml格式)
在resources目录下创建spring的xml配置文件
第四步 在spring配置文件配置相关信息
<!--完成user对象创建
bean标签
id属性:唯一标识这个类(可以随便取,但是一般都是用这个类的名称)
class属性:要创建对象所在类的全路径(包名称+类名称)
-->
<bean id="user" class="com.ltc.spring6.User"></bean>
第五步 进行最终测试
package com.ltc.spring6;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void testUserObject1(){
//加载spring配置文件,对象创建
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//获取创建的对象(传入参数:字符串类型,值与bean.xml文件中的id属性值相同)
User user = (User)context.getBean("user");
//使用对象调用方法进行测试
user.add();
}
}
入门案例解析
利用反射来进行创建对象,然后放入到Map集合的beanDefinitionMap属性中,然后可以通过key的值来找到对象。
启用加入log4j2日志框架
引入Log4j2依赖
<!-- log4j2依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
二、IOC容器
2.1简述IOC容器
IOC :是 Inversion of Control 的简写,意为 “控制反转”。
Spring通过IOC容器来管理bean。
所有java对象的实例化和初始化,控制对象与对象之间的依赖关系。
我们由IOC容器管理的 java 对象称为 Spring Bean,它与使用关键字 new 创建的java对象没有任何区别。
容器放bean对象,使用map集合。
2.1.1、IOC的原理![](https://img-blog.csdnimg.cn/aa2a98168f7d49f2b3d1d4450390b528.png)
1、Bean的定义信息
首先我们要添加一个Bean的xml配置文件,里面定义着Bean定义信息(BeanDefinition),在配置文件中定义好类的信息,告诉IOC容器,想要的类信息,交给IOC容器,让IOC容器给我们创建并给我们返回。
2、将bean的定义信息加载读取到IOC容器中
在这个过程中我们用到一个接口 BeanDefinitionReader(为什么用接口?在开发过程中我们由很多配置方法,如xml、properties和注解等方式,在接口中就可以通过不同的Bean定义方式进行不同的信息加载,不同的定义信息方式使用不同的实现类,可以实现不同的方式加载配置信息)
BeanDefinitionReader接口下的不同的实现类:
3、实例化Bean信息的类
以前我们是用new的方式、反射的方式和通过工厂来进行实例化。
现在的Spring是通过 BeanFactory工厂+反射 来进行实例化的,得到的是空的实例对象(属性的值都是null),通过初始化才会得到最终的对象,最后通过context.getBean("user");方法来获得这个最终类。
总结:
我们通过bean.xml配置信息告诉IOC容器我们要什么样的类对象,IOC容器通过配置信息给我们实例化对象(使用到BeanFactory工厂+反射),在实例化得到最终的实例对象,最后通过context.getBean("user");方法获得最终对象。
2.1.2、依赖注入
依赖注入 :DI(Dependency Injection)依赖注入实现了控制反转的思想。
依赖注入: 指spring创建对象的过程中,将对象依赖属性通过配置进行注入(通过在配置时将要创建的实例的实现值配置到配置文件中)
依赖注入常见实现方式包括两种:
第一种:set注入
第二种:构造注入
结论:IOC就是一种控制反转的思想,而DI是对IOC的一种具体实现。
Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
2.1.3、BeanFectory接口
BeanFectory接口:是IOC容器的基本实现,是spring内部使用的接口。面向spring本身,不提供给开发人员使用。
ApplicationContext接口
BeanFactory的子接口,提供了更多高级特性。面向spring的使用者,几乎所有场合都使用ApplicationContext,而不是使用底层的BeanFactory。
ApplicationContext的主要实现类
2.2、Bean获取方式(三种)
@org.junit.Test
public void beanTest(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
//1、通过id取到bean
User user1 = (User) classPathXmlApplicationContext.getBean("user");
System.out.println("通过id获取bean:"+user1);
//2、通过类型获取bean
User user2 = classPathXmlApplicationContext.getBean(User.class);
System.out.println("通过类型获取bean:"+user2);
//3、通过id和类型获取bean
User user3 = classPathXmlApplicationContext.getBean("user", User.class);
System.out.println("通过id和类型获取bean:"+user3);
}
注意的地方
当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个
错误bean配置:
<bean id="user" class="com.ltc.pojo.User"></bean>
<bean id="user2" class="com.ltc.pojo.User"></bean>
拓展知识
如果组件类实现了接口,根据接口类型可以获取bean(前提是bean必须是唯一的,如果接口有两个实现类,bean.xml配置文件也配置了,通过接口)
2.3、依赖注入(DI)
2.3.1依赖注入方式(DI)
(1)setter注入方式
<bean id="book" class="com.ltc.di.Book">
<property name="bookName" value="西游记"></property>
<property name="aoter" value="xxx"></property>
</bean>
在bean标签中通过property标签设置属性注入的值
property标签的name属性是指定了实例化对象的属性名
property标签的value属性的值是赋给实例化对象的属性值
property标签的ref属性可以和name属性连用(和name与value类似)ref的值可以指定上一个bean的实例对象,赋值给name属性指定的实例属性的值。
(2)有参构造器注入方法
<bean id="book1" class="com.ltc.di.Book">
<constructor-arg name="bookName" value="ltc"></constructor-arg>
<constructor-arg name="aoter" value="陆天成"></constructor-arg>
</bean>
可以通过在bean标签内加入constructor-arg标签进行构造器注入
name指定实例化属性的属性名(也可以通过index下标的形式来指定属性名,1表示第一个属性名,不推荐使用)
value指定要注入的属性值
ref属性可以和name属性连用(和name与value类似)ref的值可以指定上一个bean的实例对象,赋值给name属性指定的实例属性的值。
2.3.2依赖注入:特殊值处理
(1)字面量赋值
(2)null值
(3)xml实体
当遇到与xml配置文件中的特殊符号相同的符号时,就要通过转义成传义字符了
(4)CDATA节
<bean id="book1" class="com.ltc.di.Book">
<constructor-arg name="bookName" value="ltc"></constructor-arg>
<!-- <constructor-arg name="aoter" value="陆天成"></constructor-arg>-->
<constructor-arg name="aoter" >
<value><![CDATA[a>b]]]></value>
<!--在不想用转义字符时,但还想要特殊出现,就可以用<![CDATA[a>b]]]来实现-->
</constructor-arg>
2.3.3依赖注入:特殊类型注入
1)、对象类型注入
为对象类属性赋值
方式一:引入外部bean
<bean id="dept" class="com.ltc.ditest.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="销售部"></property>
</bean>
<bean id="emp" class="com.ltc.ditest.Emp">
<property name="eId" value="1"></property>
<property name="eName" value="张三"></property>
<property name="dept" ref="dept"></property>
</bean>
通过property标签中的ref属性引入对象类属性
方式二:内部bean
<bean id="emp2" class="com.ltc.ditest.Emp">
<property name="eId" value="2"></property>
<property name="eName" value="王五"></property>
<property name="dept">
<!--内部bean-->
<bean id="dept2" class="com.ltc.ditest.Dept">
<property name="deptId" value="2"></property>
<property name="deptName" value="安保部"></property>
</bean>
</property>
</bean>
通过在property标签内部直接加入bean标签
2)、为数组类型属性赋值
<bean id="dept" class="com.ltc.ditest.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="技术部"></property>
</bean>
<bean id="emp" class="com.ltc.ditest.Emp">
<property name="eId" value="14"></property>
<property name="eName" value="张三"></property>
<property name="dept" ref="dept"></property>
<property name="loves">
<!--为数组添加值-->
<array>
<value>吃</value>
<value>喝</value>
<value>嫖赌</value>
</array>
</property>
</bean>
在property标签中添加array标签,在array标签中value标签可以对数组赋值
3)、为集合类型属性赋值
<property name="dept" ref="dept"></property>
</bean>
<bean id="emp2" class="com.ltc.ditest.Emp">
<property name="eId" value="15"></property>
<property name="eName" value="李四"></property>
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.ltc.ditest.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="开发部" ></property>
<property name="emps">
<list>
<ref bean="emp1"></ref>
<ref bean="emp2"></ref>
</list>
</property>
</bean>
通过在property标签中添加list标签,如果添加的是基本类型,使用value标签,如果类实例,就要使用ref标签,在ref(引入其他的bean)标签中的bean属性指定已经创建的类实例id即可
(2)、Map类型属性注入
<bean id="stu1" class="com.ltc.di_map.Stu">
<property name="sId" value="125"></property>
<property name="sName" value="王麻子"></property>
</bean>
<bean id="stu2" class="com.ltc.di_map.Stu">
<property name="sId" value="126"></property>
<property name="sName" value="王跳跳"></property>
</bean>
<bean id="teacher" class="com.ltc.di_map.Teacher">
<property name="tId" value="1478"></property>
<property name="tName" value="李老师"></property>
<property name="stuMap" >
<map>
<entry>
<key>
<value>1425</value>
</key>
<ref bean="stu1"></ref>
</entry>
<entry>
<key>
<value>1452</value>
</key>
<ref bean="stu2"></ref>
</entry>
</map>
</property>
</bean>
map主要注入结构:
(3)、引入集合类型的bean
使用util标签需要在bean.xml头部添加标签约束(红色部分)
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
lesson1" class="com.ltc.di_map.Lesson">
<property name="lessonName" value="java开发"></property>
</bean>
<bean id="lesson2" class="com.ltc.di_map.Lesson">
<property name="lessonName" value="大数据开发"></property>
</bean>
<bean id="teacher1" class="com.ltc.di_map.Teacher">
<property name="tId" value="147"></property>
<property name="tName" value="张老师"></property>
</bean>
<bean id="teacher2" class="com.ltc.di_map.Teacher">
<property name="tId" value="258"></property>
<property name="tName" value="王老师"></property>
</bean>
<bean id="stu" class="com.ltc.di_map.Stu">
<property name="sId" value="3698"></property>
<property name="sName" value="李华"></property>
<property name="lessonsList" ref="lessonsList"></property>
<property name="teacherMap" ref="teacherMap"></property>
</bean>
<util:list id="lessonsList" >
<ref bean="lesson1"></ref>
<ref bean="lesson2"></ref>
</util:list>
<util:map id="teacherMap">
<entry>
<key>
<value>1485</value>
</key>
<ref bean="teacher1"></ref>
</entry>
<entry>
<key>
<value>956</value>
</key>
<ref bean="teacher2"></ref>
</entry>
</util:map>
(4)、p命名空间注入
<bean id="stup" class="com.ltc.di_map.Stu" p:sId="58" p:sName="小三"
p:lessonsList-ref="lessonsList" p:teacherMap-ref="teacherMap">
</bean>
<bean id="lesson1" class="com.ltc.di_map.Lesson">
<property name="lessonName" value="java开发"></property>
</bean>
<bean id="lesson2" class="com.ltc.di_map.Lesson">
<property name="lessonName" value="大数据开发"></property>
</bean>
<bean id="teacher1" class="com.ltc.di_map.Teacher">
<property name="tId" value="147"></property>
<property name="tName" value="张老师"></property>
</bean>
<bean id="teacher2" class="com.ltc.di_map.Teacher">
<property name="tId" value="258"></property>
<property name="tName" value="王老师"></property>
</bean>
<util:list id="lessonsList" >
<ref bean="lesson1"></ref>
<ref bean="lesson2"></ref>
</util:list>
<util:map id="teacherMap">
<entry>
<key>
<value>1485</value>
</key>
<ref bean="teacher1"></ref>
</entry>
<entry>
<key>
<value>956</value>
</key>
<ref bean="teacher2"></ref>
</entry>
</util:map>
4、引入外部属性文件
1)引入外部属性文件实现步骤(对数据库连接的连接信息注入):
(1)、引入数据库相关依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--数据源(德鲁伊)-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
(2)、创建外部属性文件,properties格式,定义数据库信息:用户名 密码 地址等。
jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.driver=com.mysql.cj.jdbc.Driver
(3)、创建spring配置文件,引入context命名空间引入属性文件,使用表达式完成注入。
在resources文件中定义spring配置文件bean-jdbc.xml文件。
引入context命名空间:
在bean-jdbc.xml文件中引入属性文件jdbc.properties文件
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
以前我们使用德鲁伊连接池的方式:
//创建德鲁伊连接池
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/db1");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
使用spring的IOC容器来对德鲁伊连接池的创建和注入依赖:
在bean-jdbc.xml文件中完成数据库信息注入:
<!--完成数据库信息注入-->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
</bean>
2.4、bean的作用域
1)、概念
在spring中可以通过配置bean标签的scope属性类指定bean的作用域范围,各取值含义参加表:
<!--通过scope属性配置 单实例-->
<bean id="orders" class="com.ltc.scope.Orders" scope="singleton"></bean>
<!--通过scope属性配置 多实例-->
<bean id="orders1" class="com.ltc.scope.Orders" scope="prototype"></bean>
如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用):
2.5、bean的生命周期
1)、具体的生命周期过程
2)、生命周期测试
创建bean类
public class User {
private String name;
public User(){
System.out.println("1、bean对象创建(调用无参构造器)");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("2、给bean对象设置相关属性");
this.name = name;
}
public void init(){
System.out.println("4、bean对象初始化(调用指定初始化方法)");
}
public void stroy(){
System.out.println("7、bean对象销毁(配置指定销毁方法)");
}
}
在bean类中实现了初始化的方法和销毁实例对象的方法
编写bean.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="user" class="com.ltc.life.User" scope="singleton" init-method="init" destroy-method="stroy">
<property name="name" value="李四"></property>
</bean>
</beans>
在bean.xml文件中bean标签中init-method属性可以指定bean类中的初始化方法,在实例化时自动调用。
destroy-method属性可以指定类的销毁方法,可以使用context.clos();来调用destroy-method指定类的销毁方法来销毁实例化对象
注意:在加载bean.xml文件是使用ClassPathXmlApplicationContext
测试类
public class Bean_LifeTest {
@Test
public void beanLifeTest(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml");
User user = context.getBean(User.class);
System.out.println("6、"+user+"在使用中.....");
context.close();
}
}
结果
2.6、FactoryBean
1)、简介
将来我们整合Mybatis时,Spring就是通过FactoryBean机制类帮我们创建SqlSessionFactory对象的。
代码演示
创建MyFactoryBean类继承FactoryBean接口,并且实现接口中的两个方法:
public class MyFacroryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception{
return new User();
}
@Override
public Class<?> getObjectType(){
return User.class;
}
}
配置bean信息:
<bean id="user" class="com.ltc.factorybean.MyFacroryBean"></bean>
测试方法:
public class FactoryBeanTest {
@Test
public void factoryBean(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-factorybean.xml");
Object user = context.getBean("user");
System.out.println(user);
}
}
最终得到的结构:
可以看出,FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中的这个类的对象,而是得到getObject()方法返回的值。
2.7、基于xml自动装配
自动装配介绍:
根据指定的策略,在IOC容器中装配某一个bean,自动为指定的bean中所依赖的类类型或者接口类型属性赋值。
代码演示:
测试代码
public class AutoTest {
@Test
public void autoTest(){
//传统方法
// UserController userController = new UserController();
// userController.addUser();
//通过spring自动装配进行演示代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");
UserController controller = context.getBean("userController", UserController.class);
controller.addUser();
}
}
Controller层
public class UserController {
private UserService userService;
//这个是set方法,用于给自动装配是进行注入属性依赖
public void setUserService(UserService userService) {
this.userService = userService;
}
public void addUser(){
userService.addUserService();
//传统方法
System.out.println("userController 执行了...");
// UserService userService = new UserServiceImpl();
// userService.addUserService();
}
}
service层
service接口:
public interface UserService {
void addUserService();
}
service实现类:
public class UserServiceImpl implements UserService {
private UserDao userDao;
//set方法,用给自动装配是注入属性值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUserService() {
userDao.addUserDao();
//传统方法
System.out.println("addUserService 执行了.....");
// UserDao userDao = new UserDaoImpl();
// userDao.addUserDao();
}
}
dao层
dao接口:
public interface UserDao{
void addUserDao();
}
dao实现类:
public class UserDaoImpl implements UserDao {
@Override
public void addUserDao() {
System.out.println("addUserDao 执行了.....");
}
}
总结:
通过spring自动装配,可以省掉了繁琐的创建对象,把创建对象交给IOC容器进行处理。
通过spring自动装配也可以省去了通过set和构造器的方式对对象属性值的属性注入依赖。
配置bean
使用bean标签的autowire属性设置自动装配效果
自动装配方式:byType
byType:根据类型匹配IOC容器中的某个兼容型的bean,为属性自动赋值
若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null。
若在IOC中,有多个兼容类型的bean能够为属性赋值,则会抛出异常NoUniqueBeanDefinitionException.
自动装配方式:byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相应的bean进行赋值。(类中的属性 名要和bean中的id一致)
2.8、基于注解管理bean(重点)
从java5开始,java增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变代码和逻辑的情况下,在源代码中嵌入补充信息。
spring从2.5版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化spring的xml配置。
2.8.1spring通过注解实现自动装配的步骤如下:
1、引入依赖
2、开启组件扫描
spring默认不使用注解装配bean,因此我们需要spring的 XML 配置中,通过context:component-scan元素开启spring bean的自动扫描功能。开启此功能后,spring会自动从扫描指定的包(base-package属性设置)及其子包下的所有类,如果类上使用了@Component注解,就将该类装配到容器中。
1)引入context命名
2)开启组件扫描
<!--开启组件扫描-->
<context:component-scan base-package="com.ltc"></context:component-scan>
3、使用注解定义bean
spring提供了以下多个注解,这些注解可以直接标注在java 类上,将它们定义成spring bean。
2.8.2、依赖注入
1)、@Autowired注入
单独使用@Autowired注解,默认根据数据类型装配。【默认是byType】
场景一:属性注入(在属性上加入@Autowired)
场景二:set注入(在属性的set() 方法上加@Autowired)
场景三:构造方法注入(在构造方法上加@Autowired)
场景四:参数上注入(在有参构造器上的形参上加@Autowired)
场景五:只有一个构造函数,无注解(当有参构造器只有一个时,@Autowired注解可以省略,当有多个构造器是这种方式会报错)
场景六:@Autowired注解和@Qualifier注解联合(当接口有多个实现类时,使用@Autowired自动装配就会报错,这时就要使用@Autowired和@Qualifier注解联合使用,@Qualifier(value="实现类首字母小写"))
bean.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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.ltc"></context:component-scan>
</beans>
controller层
//创建controller
@Controller
public class UserController {
//注入service
//第一种方式:属性注入
// @Autowired //根据类型找到对应对象,完成注入
// private UserService userService;
//第二种方式:set方式注入
// private UserService userService;
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
//第三种方式:构造方法注入
// private UserService userService;
// @Autowired
// public UserController(UserService userService) {
// this.userService = userService;
// }
//第四种方式:形参上注入
// private UserService userService;
//
// public UserController(@Autowired UserService userService) {
// this.userService = userService;
// }
//第五种方式:只有一个构造函数,无注解(当有参数构造器只有一个时,@Autowired注解可以省略)
// private UserService userService;
//
// public UserController(UserService userService) {
// this.userService = userService;
// }
//第六种方式:@Autowired注解和@Qualifier注解联合(当接口有多个实现类时,使用@Autowired自动装配就会报错,
// 这时就要使用@Autowired和@Qualifier注解一起使用,@Qualifier(value="实现类首字母小写"))
@Autowired
@Qualifier(value = "userServiceImp")
private UserService userService;
public void add(){
System.out.println("controller..........");
userService.add();
}
}
service层
service接口:
public interface UserService {
void add();
}
service实现类:
//创建service
@Service
public class UserServiceImp implements UserService{
//注入dao
//第一种方式:属性注入
// @Autowired //根据类型找到对应对象,完成注入
// private UserDao userDao;
//第二种方式:set方式注入
// private UserDao userDao;
// @Autowired
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
//第三种方式:构造方法注入
// private UserDao userDao;
// @Autowired
// public UserServiceImp(UserDao userDao) {
// this.userDao = userDao;
// }
//第四种方式:形参上注入
// private UserDao userDao;
// public UserServiceImp(@Autowired UserDao userDao) {
// this.userDao = userDao;
// }
//第五种方式:只有一个构造函数,无注解(当有参数构造器只有一个时,@Autowired注解可以省略)
// private UserDao userDao;
//
// public UserServiceImp(UserDao userDao) {
// this.userDao = userDao;
// }
//第六种方式:@Autowired注解和@Qualifier注解联合(当接口有多个实现类时,使用@Autowired自动装配就会报错,
// 这时就要使用@Autowired和@Qualifier注解一起使用,@Qualifier(value="实现类首字母小写"))
@Autowired
@Qualifier(value = "userDaoImp")
private UserDao userDao;
public void add(){
System.out.println("service............");
userDao.add();
}
}
dao层
dao接口:
public interface UserDao {
void add();
}
dao实现类:
//创建dao
@Repository
public class UserDaoImp implements UserDao{
public void add(){
System.out.println("dao............");
}
}
2)、@Resource注入
@Resoutce注解也可以完成属性注入。
(1)、@Autowired和@Qualifier注解的区别:
(1)@Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。
(2)@Autowired注解是spring框架自己的。
(3)@Resource注解默认根据名称装配byName,为指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。
(4)@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。
(5)@Resource注解用在属性上、setter方法上。
(6)@Autowired注解用在属性上、setter方法上、构造方法上、构造方法形参上。
(7)@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以上依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或者低于JDK8需要引入一下依赖】
(2)、引入可以用Resource注解的依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
通过属性名方式注入依赖
2.9、全注解开发
全注解开发就是不在使用spring配置文件了,写一个配置类来代替配置文件。
配置类:
@Configuration 注解是将类标注为一个配置类
@ComponentScan("com.ltc") 开启组件扫描,和xml配置文件中的
<context:component-scan base-package="com.ltc"></context:component-scan>
开启组件扫描一样。
@Configuration //配置类
@ComponentScan("com.ltc") //开启组件扫描
public class SpringConfig {
}
通过ApplicationContext接口下的AnnotationConfigApplicationContext实现类来读取到配置类。
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
2.10、原理-手写IOC
我们都知道,spring框架的IOC是基于java反射机制实现的。
2.10.1回顾java反射
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用她的任意方法和属性;这种动态代理获取信息以及动态调用对象方法的功能称为java语言的反射机制。简单来说,反射机制指的是程序在运行时能获取自身的信息。
要想解剖一个类,必须先获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class (2)java.lang.reflect,所以,Class对象是反射的根源。
2.10.2、实现spring的IOC
我们知道,IOC(控制反转)和DI(依赖注入)是spring里面核心的东西,那么,我们如何自己手写这样的代码?
1)、搭建子模块
子模块ltc-spring
2)、准备测试需要的bean
service层
service接口
public interface UserService {
void add();
}
service实现类
@Bean
public class UserServiceImp implements UserService {
@DI
private UserDao userDao;
@Override
public void add() {
userDao.add();
System.out.println("service add......");
}
}
dao 层
dao 接口
public interface UserDao {
void add();
}
dao实现类
@Bean
public class UserDaoImp implements UserDao {
@Override
public void add() {
System.out.println("dao add........");
}
}
两个注解
@DI
//两个原生注解
@Target(ElementType.FIELD) //指定在什么地方用,FIELD在属性上用
@Retention(RetentionPolicy.RUNTIME) //表示作用范围,RUNTIME在运行时生效
public @interface DI {
}
@Bean
//两个原生注解
@Target(ElementType.TYPE) //指定在什么地方用,TYPE在类上用
@Retention(RetentionPolicy.RUNTIME) //表示作用范围,RUNTIME在运行时生效
public @interface Bean {
}
ApplicationContext(重点)
ApplicationContext接口
public interface ApplocationContext {
Object getBean(Class clazz);
}
ApplicationContext实现类
public class AnnotationApplicationContext implements ApplocationContext{
private static String rootPath;
//创建map集合,放bean对象
private Map<Class,Object> beanFactory=new HashMap<>();
//放回对象
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
//创建有参数构造器,传递包路径,设置包扫描规则
//当前包极其子包,那个类有@Bean注解,把这个类通过反射实例化
public AnnotationApplicationContext(String basePackage){
try {
//com.ltc
//1、把.替换成\
String packagePath = basePackage.replaceAll("\\.", "\\\\");
//2、获取包绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (urls.hasMoreElements()){
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
//包扫描
loadBean(new File(filePath));
//属性注入
loadDi();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void loadBean(File file) throws Exception {
//1、判断当前路径是否是文件夹
if (file.isDirectory()){
//2、获取文件夹里面的所有内容
File[] childrenFiles = file.listFiles();
//3、判断文件夹里面为空,直接返回
if (childrenFiles==null || childrenFiles.length==0){
return;
}
//4如果文件夹里面不为空,遍历文件夹所有内容
for (File f:childrenFiles) {
//4.1、遍历得到每个File对象,继续判断,如果是文件夹,进行loaBean方法递归
if(f.isDirectory()){
loadBean(f);
}else {
//4.2、遍历得到File对象不是文件夹,就是文件
//4.3、得到包路径+类名称部分-字符串截取
String pathWithClass = f.getAbsolutePath().substring(rootPath.length()-1);
//4.4、判断当前文件类型是否是.class
if (pathWithClass.contains(".class")){
//4.5、如果是.class类型,把路径\替换成. 把.class去掉
//com.ltc.service.UserServiceImp
String allName = pathWithClass.replaceAll("\\\\", "\\.").replace(".class","");
//4.6、判断类上面是否有注解@Bean,如果有,就实例化过程
//4.6.1、获取类的Class
Class<?> clazz = Class.forName(allName);
// 4.6.2\判断是否是接口
if (!clazz.isInterface()){
//4.6.3、判断类上面是否有注解@Bean
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation!=null){
//4.6.4、实例化
Object instance = clazz.getDeclaredConstructor().newInstance();
//4.7、把对象实例化之后,放到map集合beanFactory
//4.7.1、判断当前类如果有接口,让接口class作为name的key
if (clazz.getInterfaces().length>0){
beanFactory.put(clazz.getInterfaces()[0], instance);
}else {
//4.7.2、如果没有,则把类名作为key
beanFactory.put(clazz,instance);
}
}
}
}
}
}
}
}
//属性注入
private void loadDi() {
//实例化对象在beanGactory的集合map里面
//1、遍历beanFactory的map集合
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entriy:entries) {
//2、获取map集合每个对象(value),每个对象属性获取到
Object obj = entriy.getValue();
//获取对象Cladd
Class<?> clazz = obj.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
//3、遍历得到每个对象属性数组,得到每个属性
for (Field field:declaredFields) {
//4、判断属性上是否有@Di注解
DI di = field.getAnnotation(DI.class);
if (di!=null){
//如果是私有属性,设置可以设置值
field.setAccessible(true);
//5、如果有@Di注解,把对象进行设置(注入)
try {
field.set(obj,beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
测试类
public class UserTest {
public static void main(String[] args) {
ApplocationContext context =new AnnotationApplicationContext("com.ltc");
UserService userService = (UserService) context.getBean(UserService.class);
System.out.println(userService);
userService.add();
}
}
三、AOP
3.1场景模拟
搭建子模块:spring6-aop
3.1.1声明接口
声明计数器接口Calulator,包含加减乘除的抽象方法
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
3.1.2实现接口的实现类
public class CalculatorImpl implements Calculator{
@Override
public int add(int i, int j) {
int result=i+j;
System.out.println("方法内部result="+result);
return result;
}
@Override
public int sub(int i, int j) {
int result=i-j;
System.out.println("方法内部result="+result);
return result;
}
@Override
public int mul(int i, int j) {
int result=i*j;
System.out.println("方法内部result="+result);
return result;
}
@Override
public int div(int i, int j) {
int result=i/j;
System.out.println("方法内部result="+result);
return result;
}
}
3.1.3创建带日志功能的实现类
//带日志
public class CalculatorLogImpl implements Calculator{
@Override
public int add(int i, int j) {
System.out.println("[日志]add方法开始了,参数是:"+i+","+j);
int result=i+j;
System.out.println("方法内部result="+result);
System.out.println("[日志] add方法结束了,结果是:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("[日志]sub方法开始了,参数是:"+i+","+j);
int result=i-j;
System.out.println("方法内部result="+result);
System.out.println("[日志] sub方法结束了,结果是:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("[日志]mul方法开始了,参数是:"+i+","+j);
int result=i*j;
System.out.println("方法内部result="+result);
System.out.println("[日志] mul方法结束了,结果是:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("[日志]div方法开始了,参数是:"+i+","+j);
int result=i/j;
System.out.println("方法内部result="+result);
System.out.println("[日志] div方法结束了,结果是:"+result);
return result;
}
}
3.1.4提出问题
3.2代理模式
3.1.1概念
1)介绍
二十三中设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不在是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来--解耦。调用目标方法是先调用代理对象的方法,减少目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
2)生活中的代理
--广告商找大明星拍广告需要经济人
--合作伙伴找大老板谈合作要约见面时间需要经过秘书
--房产中介是买卖双方的代理
3)相关术语
--代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
--目标:被代理“套用”了非核心逻辑代码的类、对象、方法。
3.2.2、静态代理
package com.ltc.example;
public class CalculatorStaticProxy implements Calculator{
//把核心代码类做为属性传进来
private CalculatorImpl calculator;
public CalculatorStaticProxy(CalculatorImpl calculator) {
this.calculator = calculator;
}
@Override
public int add(int i, int j) {
System.out.println("[日志]add方法开始了,参数是:"+i+","+j);
//调用核心代码类的方法
int addResult = calculator.add(i, j);
System.out.println("[日志] add方法结束了,结果是:"+addResult);
return addResult;
}
@Override
public int sub(int i, int j) {
System.out.println("[日志]sub方法开始了,参数是:"+i+","+j);
//调用核心代码类的方法
int subResult = calculator.sub(i, j);
System.out.println("[日志] sub方法结束了,结果是:"+subResult);
return subResult;
}
@Override
public int mul(int i, int j) {
System.out.println("[日志]mul方法开始了,参数是:"+i+","+j);
//调用核心代码类的方法
int mulResult = calculator.mul(i, j);
System.out.println("[日志] mul方法结束了,结果是:"+mulResult);
return mulResult;
}
@Override
public int div(int i, int j) {
System.out.println("[日志]div方法开始了,参数是:"+i+","+j);
//调用核心代码类的方法
int divResult = calculator.div(i, j);
System.out.println("[日志] div方法结束了,结果是:"+divResult);
return divResult;
}
}
3.2.3、动态代理
动态代理类
创建一个动态代理类,把带有实际功能的目标类对象通过构造方法传入代理类中,在代理类中有一个反对代理对象getProxy()方法。返回代理对象的方法中要通过调用Proxy类中的newProxyInstance()方法,返回一个代理类对象proxy给调用的类,调用newProxyInstance()方法要传入三个参数(加载动态生成代理类的加载器ClassLoader、实现代码的目标接口的实现类的class类型数组Class[] interfaces、设置代理对象实现目标对象方法的过程InvocationHandler)
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//返回代理对象
public Object getProxy(){
/**
* Proxy.newProxyInstance()方法
* 有三个参数
* 第一个参数:ClassLoader:加载动态生成代理类的加载器
* 第二个参数:Class[] interfaces:目标对象实现的所有接口的class类型数组
* 第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程
*/
//第一个参数:ClassLoader:加载动态生成代理类的加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//第二个参数:Class[] interfaces:目录对象实现的所有接口的class类型数组
Class<?>[] interfaces = target.getClass().getInterfaces();
//第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程(InvocationHandler:是一个接口,
// 接口中有一个invoke方法,可以通过实现类来实现invoke方法,也可以通过匿名内部类来实现invoke方法)
InvocationHandler invocationHandler=new InvocationHandler(){
/*
*第一个参数:代理对象
* 第二个参数:需要重写目标对象方法
* 第三个参数:method方法里面参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法调用之前输出
System.out.println("[动态代理][日志]"+method.getName()+",参数:"+ Arrays.toString(args));
//调用目标的方法
Object result = method.invoke(target, args);
//方法调用之后输出
System.out.println("[动态代理][日志]"+method.getName()+",结构:"+result);
return null;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
}
测试类
public class TestCal {
public static void main(String[] args) {
//创建代理对象(动态)
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator proxy = (Calculator) proxyFactory.getProxy();
//通过代理对象可以调用实际功能对象的方法
proxy.add(4,5);
}
}
3.3、AOP概念及相关术语
3.3.1、概述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它可以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个本分进行隔离,从而使得业务逻辑各部分之间的耦合降低,提高程序的可重用性,同时提高了开发的效率。
3.3.2、相关术语
1)、横切关注点
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
2)、通知(增强)
增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。
每个横切关注点上要做的事情都需要写一个方法实现,这样的方法就叫通知方法。
--前置通知:在被代理的目标方法前执行
--返回通知:在被代理的目标方法成功结束执行后执行
--异常通知:在被代理的目标方法异常结束后执行
--后置通知:在被代理的目标方法最终结束执行后执行
--环绕通知:使用try....cath.....finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
3)切面
封装通知方法的类
4)、目标
被代理的目标对象。
5)代理
向目标对象应用通知之后创建代理类对象。
6)、连接点
这也是一个纯逻辑概念,不是语法定义。
把方法排成一排,每个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交点就是连接点。通俗说,就是spring允许你使用通知的地方。
7)、切入点
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事务(从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切点就是查询记录的SQL语句。
spring的AOP技术可以通过切入点定位到特定的连接点。俗话说,要实际去增强的方法
切点通过 org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
3.3.3作用
--简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
--代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
3.4基于注解的AOP
3.4.1技术说明
3.4.2准备工作
1)添加依赖
在IOC所需依赖基础上在加入下面依赖即可:
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
3.4.3创建面向切面类并配置
在spring的配置文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.ltc.annoaop"></context:component-scan>
<!--开启aspectj自动代理,为目标对象生成代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
3.4.4各种通知类型
3.4.5切入点表达式
语法细节:
3.4.6重用切入点表达式
1)声明
在代理类中创建一个方法,加上@Pointcut注解,value值等于要重用的切入表达式
/重用切入点表达式
@Pointcut(value = "execution(* com.ltc.annoaop.Calculator.* (..))")
public void pointCut(){
}
2)在同一个切面中使用
在通知的注解里的value值赋值上重用表达式的方法就可以
// 前置 @Before()
@Before(value = "pointCut()")
public void beforeMethod(){
System.out.println("前置通知.......");
}
3)在不同切面中使用
在不同的切面中使用,要在通知的注解里的value值赋值上重用表达式的方法之前要加上切面的类路径
// 异常 @AfterThrowing
@AfterThrowing(value = "com.ltc.annoaop.LogAspect.pointCut()")
public void afterThrowingMethod(){
System.out.println("异常通知.......");
}
3.4.7切面的优先级
3.5基于xml的AOP
3.5.1准备工作
和注解的AOP一样
3.5.2实现
<!--开启组件扫描-->
<context:component-scan base-package="com.ltc.xmlaop"></context:component-scan>
<!--创建相关的bean类对象-->
<bean id="logAspect" class="com.ltc.xmlaop.LogAspect" ></bean>
<bean id="calculator" class="com.ltc.xmlaop.CalculatorImpl"></bean>
<!--配置aop五种通知类型-->
<aop:config>
<!--配置切面类-->
<aop:aspect ref="logAspect">
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.ltc.xmlaop.LogAspect.*(..))"/>
<!--配置五种通知类型-->
<!--前置通知-->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<!--后置通知-->
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<!--返回通知-->
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
<!--环绕通知-->
<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
</aop:config>
四、单元测试:JUnit
4.1整合JUnit5
4.1.1搭建模块
搭建spring-Junit模块
4.1.2引入依赖
<!--spring对Junit的支持相关依赖依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.2</version>
</dependency>
<!--junit5测试依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
</dependency>
引入context约束和开启context组件扫描
使用@SpringJUnitConfig(locations = "classpath:bean.xml")注解来告诉spring我们的配置文件在哪里
4.2整合JUnit4
Junit4在工程也会经常使用
4.2.1添加依赖
<!--junit4测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
通过这两个注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath*:bean.xml")
完成让spring指定我们的bean.xml文件在哪里
五、事务
5.1事务理解
例如:你去ATM机取钱,你取1000,机器出1000,然后扣除你账号的1000,。不管在出钱还是扣除你账号上的金额处理问题,都会带来损失。所以这个事务里的事件要么同时发生,要么同时不发生。
事务就是来解决操作数据库数据在一个业务中只更新一部分,造成的损失。如果业务在操作数据库的一部分数据,在中途就有错误或者异常,就会让整个过程都能回滚,取消整个对数据库操作的sql语句。
5.1.1事务特性:
事务是用来保证数据的完整性和一致性的技术,事务有四个特性:ACID
原子性:(Atomicity)
事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
一致性:(Consistency)
一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。(如转账操作中,转账前和转账后,两个用户的总余额都有保持一致性)
隔离性:(Isolation)
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性:(Durability)
一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
5.2核心接口
Spring事务管理涉及的接口的联系如下:
5.3事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:
spring事务管理最大的有点就是对不同的持久框架通过了操作事务的一致的编程模型,它不关心如何实现,如何实现是交给各大厂商来自己实现。
5.4基本事务属性的定义
事务管理器PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来获取事务,这个方法的参数TransactionFefinition是一个接口类,这个类定义了事务的一些基本事务属性。
事务的基本属性有五个方面:
TransactionDefinition接口类内容:
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}
5.4.1事务传播行为(propagation behavior)
事务传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。
如:方法A调用方法B时,方法B是继续在调用者A方法的事务中运行呢?还是为自己开启一个新的事务运行,这就是有方法B的事务传播行为决定的。
一、事务的7种传播行为
spring在TransactionDefinition接口中规定了7种类型的事务传播行为。
事务传播行为是spring框架独有的事务增强特性。
PROPAGATION_REQUITED
如果当前没有事务,就创建一个新的事务,如果当存在事务,就加入改事务,这是最常见的选择,也是spring默认的事务传播行为。(required需要,没有新建,有加入)
(需要)表示当前方法需要事务,如果外界方法有事务,则融入外界事务(和外界事务一起提交事务和回滚事务),如果外界方法没有事务,则新建一个事务
PROPAGATION_REQUIRES_NEW
创建新事务,如果当前存不存在事务,都创建新事务。(requires_new需要新的,不管有没有,直接创建新事务)
(需要新事务)表示当前需要一个事务,如果外界有事务,则将外界事务挂起,新建一个事务,当前事务执行完后,重启开启外界事务(起到一个事务交替),如果外界没有事务,则新建一个事务
PROPAGATION_SUPPORTS
支持当前事务,如果存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。(supports支持,有则加入,没有则不管了,非事务运行)
(支持)表示支持外界方法事务,如果外界方法有事务,则融入外界方法事务(和外界方法事务一起体检和回滚事务),如果外界没有事务,则以无事务执行
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(not supported不支持事务,存在就挂起)
(不支持)表示当前方法以无事务执行,如果外界方法有事务,则挂起外界方法事务,让当前方法以无事务执行,如果外界方法无事务,当前方法也是以无事务执行
PROPAGATION_MANDATORY
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(mandatory强制性,有则加入,没有异常)
(强制性的) 表示当前方法必须执行在事务中,如果外界方法有事务,则融入外界方法事务,如果当前方法不存在事务则抛异常。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛异常。(never不支持事务,存在就异常)
(不会运行有事务环境)表示当前不应该运行在事务中,如果外界有事务,则抛异常
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUITED属性执行。(nested存在就嵌套的执行,没有就找是否在外面的事务,有则加入,没有则新建)
(嵌套事务)表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与required一样。
对事务的要求程度可以从小到大排序:mandatory | supports | required | requires_new | nestd | not supported | never
实战
1、描述
- 外围无事务,内部有事务,外围管不着内部
@Test
void test_PROPAGATION_REQUIRED() {
// add方法 @Transactional(propagation = Propagation.REQUIRED)
userService.add(user);
// add方法 @Transactional(propagation = Propagation.REQUIRED)
userRoleService.add(userRole);
//抛异常,不影响上面的add方法执行,外部异常不影响内部
throw new RuntimeException();
}
2、描述
- 外围方法Propagation.REQUIRED
- 内部方法Propagation.REQUIRED
- 修饰的内部方法会加入到外围方法的事务中
- 内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚
@Transactional // 默认Required
@Test
void test_PROPAGATION_REQUIRED() {
// 增加用户表 Required 加入了外部事务
userService.add(user);
// 增加用户角色表 Required 加入了外部事务
userRoleService.add(userRole);
//抛异常 所有都回滚
throw new RuntimeException();
}
3、描述
- 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行
- 外围方法没有开启事务,add方法直接无事务执行
@Test
void test_PROPAGATION_SUPPORTS() {
// 增加用户表 @Transactional(propagation = Propagation.SUPPORTS)
userService.add(user);
// 增加用户角色表 @Transactional(propagation = Propagation.SUPPORTS)
userRoleService.add(userRole);
//抛异常,当前无事务,直接无事务执行
throw new RuntimeException();
}
4、描述
- 外围加入事务,默认Propagation.REQUIRED
- 内部使用Propagation.SUPPORTS
- 内部发现有事务,加入,外部异常,内部回滚
5、描述
- 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常
- 外围不存在事务
- 内部add方法使用@Transactional(propagation = Propagation.MANDATORY)
- 内部发现当前没事务,直接抛出异常