小白 の Spring 学习笔记

841 阅读19分钟

1. Spring 简介

1.1 Spring 定义

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。 提供了展现层 SpringMVC持久层 Spring JDBC_Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 Spring 优势

  1. 方便解耦,简化开发 通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
  2. AOP 编程的支持 通过 Spring 的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
  3. 声明式事务的支持 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。
  4. 方便程序的测试 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
  5. 方便集成各种优秀框架 Spring 支持各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)
  6. 降低 JavaEE API 的使用难度 Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
  7. Java 源码是经典学习范例 Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着前人对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

1.3 Spring 体系结构

2. IOC & DI

2.1 快速入门 の 开发步骤

1)导入 Spring 开发的基本包坐标

2)编写 Dao 接口和实现类

public interface UserDao {
    public void save();
}

public class UserDaoImpl implements UserDao {
    
    @Override
    public void save() {
        System.out.println("UserDaoImpl implements UserDao...");
    }
}

3)创建 Spring 核心配置文件

4)在 Spring 配置文件中配置 Dao 接口的实现类 UserDaoImpl

5)使用 Spring 的相关 API 获得 Bean 实例(ApplicationContextgetBean()

@Test
public void test() {
    ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    /* 根据id标识找到对应的对象,并进行强转(后文会详细讲解关于getBean()的重载方法!) */
    UserDao userDaoImpl = (UserDao) app.getBean("userDaoImpl");
    userDaoImpl.save();
}

2.2 基于 XML の IOC 开发

2.2.1 id & class 属性

用于配置对象交由 Spring 来创建。 默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功

基本属性:

  • id:Bean 实例在 Spring 容器中的唯一标识
  • class:Bean 的全限定名称

2.2.2 scope 属性

scope:指对象的作用范围,取值如下:

取值范围说明
singleton单例的(默认值)
prototype多例的
requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
global sessionWEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 globalSession 相当于 session

1)当 scope 的取值为 singleton

Bean的实例化个数:1个

Bean的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

Bean 的生命周期:

  • 对象创建:当应用加载,创建容器时,对象就被创建了
  • 对象运行:只要容器在,对象一直存在
  • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

2)当 scope 的取值为 prototype

Bean 的实例化个数:多个

Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean

  • 对象创建:当使用对象时,创建新的对象实例
  • 对象运行:只要对象在使用中,就一直活着对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
  • 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

2.2.3 init-method & destroy-method 属性

有关 Bean 生命周期配置,需要用到以下两个属性:

  • init-method:指定类中的初始化方法名称
  • destroy-method:指定类中销毁方法名称

2.2.4 Bean 实例化 の 三种方式

  • 无参构造方法实例化
  • 工厂静态方法实例化
  • 工厂实例方法实例化
1)无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败。

<bean id="userDao" class="com.one.dao.impl.UserDaoImpl"/>
2)工厂静态方法实例化

工厂的静态方法返回 Bean 实例:

测试:

3)工厂实例方法实例化

工厂的实例方法返回 Bean 实例

测试:

2.2.5 依赖注入(DI)

1)为什么要有依赖注入?

按照目前所学知识, UserService 实例和 UserDao 实例都存在于 Spring 容器中,当前的做法是在容器外部获得 UserService 实例和 UserDao 实例,然后在程序中进行结合。

因为 UserService 和 UserDao 都在 Spring 容器中,而最终程序直接使用的是 UserService,所以干脆在 Spring 容器中,将 UserDao 设置到 UserService 内部。

2)依赖注入的概念

依赖注入(Dependency Injection):DI 是 Spring 框架核心 IOC 的具体实现

在编写程序时,通过控制反转,把对象的创建交给了 Spring 容器,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低它们的依赖关系,但不可能完全消除。例如:业务层(ServiceImpl)仍会调用持久层(UserDaoImpl)的方法。那这种业务层和持久层的依赖关系,在使用 Spring 框架之后,就让 Spring 容器来维护了。

简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

3)依赖注入的方式

怎么将 UserDao 怎样注入到 UserService 内部呢?

  • 构造方法 — constructor-arg

  • Setter 方法 — property

1. 构造方法

<bean id="userDaoImpl" class="com.one.dao.impl.UserDaoImpl"/>

<bean id="userService" class="com.one.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDaoImpl"/>
</bean>

2. Setter 方法

<bean id="userDaoImpl" class="com.one.dao.impl.UserDaoImpl"/>

<bean id="userServiceImpl" class="com.one.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDaoImpl"/>
</bean>


p 命名空间引入

<!--p空间-->
<beans xmlns:p="http://www.springframework.org/schema/p">...</beans>
<bean id="userServiceImpl" class="com.one.service.impl.UserServiceImpl" p:userDao-ref="userDaoImpl"/>

4) 依赖注入的数据类型

上面的操作,都是注入的引用 Bean,除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。 注入数据的三种数据类型 :

  • 普通数据类型
  • 引用数据类型
  • 集合数据类型

其中引用数据类型,此处就不再赘述了,之前的操作都是对 UserDao 对象的引用进行注入的,下面将以 setter 方法注入为例,演示普通数据类型和集合数据类型的注入。

(1)普通数据类型注入

public class UserDaoImpl implements UserDao {
    private String name;
    private int age;
    
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

applicationContext.xml

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
    <property name="name" value="w"></property>
    <property name="age" value="19"></property>
</bean>

(2)集合数据类型注入

<!-- 依赖注入的数据类型 -->
<bean id="u1" class="com.one.demo.User"/>
<bean id="u2" class="com.one.demo.User"/>
<bean id="injection" class="com.one.demo.Injection">
    <property name="name" value="w"/>
    <property name="age" value="19"/>
    <!-- String[] -->
    <property name="arr">
        <array>
            <value>$Arr1</value>
            <value>$Arr2</value>
            <value>$Arr3</value>
        </array>
    </property>
    <!-- List<String> -->
    <property name="list">
        <list>
            <value>$List1</value>
            <value>$List2</value>
            <value>$List3</value>
        </list>
    </property>
    <!-- List<Users> -->
    <property name="users">
        <list>
            <ref bean="u1"/>
            <ref bean="u2"/>
        </list>
    </property>
    <!-- Map<Integer, User> -->
    <property name="userMap">
        <map>
            <entry key="1" value-ref="u1"/>
            <entry key="2" value-ref="u2"/>
        </map>
    </property>
    <!-- Set<String> -->
    <property name="set">
        <set>
            <value>set_1</value>
            <value>set_2</value>
            <value>set_3</value>
        </set>
    </property>
    <!-- Properties -->
    <property name="properties">
        <props>
            <prop key="key1">$property1</prop>
            <prop key="key2">$property2</prop>
        </props>
    </property>
</bean>

2.2.6 引入其他配置文件(分模块开发)

实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在 Spring 主配置文件通过 import 标签进行加载。

2.3 Spring 配置数据源(xml 方式)

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.13</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
</dependencies>

jdbc.properties

applicationContext.xml

2.4 Spring 相关 API

2.4.1 ApplicationContext 继承体系

ApplicationContext:接口类型(interface),代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象。

2.4.2 ApplicationContext 实现类

1)ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件,推荐使用这种。

2)FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

3)AnnotationConfigApplicationContext 当使用完全注解配置容器对象时,需要使用此类来创建 spring 容器(因为此时没有 xml 配置文件),它用来读取注解。

2.4.3 getBean() 方法使用

ApplicationContext app = new ClasspathXmlApplicationContext("xml文件")
// 仅根据id查找Spring容器中的对象,允许容器中出现多个类型相同的Bean,但是需要强转
app.getBean("id");

// 仅根据字节码查找Bean对象,但不允许Spring容器出现多个类型相同的Bean
app.getBean(class);

// 唯一确定获取到某一 Bean 对象
app.getBean("id", class);

2.5 基于注解 の IOC 开发

Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml 配置文件可以简化配置,提高开发效率。

2.5.1 Spring 原始注解

Spring 原始注解主要是替代 配置!

注解说明
@Component使用在类上用于实例化 Bean
@Controller使用在 web 层类上用于实例化 Bean
@Service使用在 service 层类上用于实例化 Bean
@Repository使用在 dao 层类上用于实例化 Bean
@Autowired使用在字段上用于根据类型依赖注入
@Qualifier结合 @Autowired 一起使用,用于根据名称进行依赖注入
@Resource相当于 @Autowired + @Qualifier,按照名称进行注入
@Value注入普通属性
@Scope标注 Bean 的作用范围
@PostConstruct使用在方法上标注该方法是 Bean 的初始化方法(init-method
@PreDestroy使用在方法上标注该方法是 Bean 的销毁方法(destroy-method

注意:使用注解进行开发时,需要在 applicationContext.xml 中配置组件扫描Component-scan),作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。

<!-- 开启组件扫描: applicationContext.xml 配置文件才能根据相应注解解析(从而实现对象创建 & 依赖注入) -->
<context:component-scan base-package="com.one">
    <!-- 只扫描 Component & Service 注解 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    <!-- 忽略扫描 Repository 注解 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

pom.xml@Resource、@PostConstruct、@PreDestory 都需要使用到该依赖...

注解开发

2.5.2 Spring 新注解

使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:

非自定义的 Bean 的配置:<bean> 加载 properties 文件的配置:<context:property-placeholder> 组件扫描的配置:<context:component-scan> 引入其他文件:<import>

Spring 新注解

注解说明
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。
等价于 Spring 的 xml 配置文件中的 <context:component-scan base-package=""/>
@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource加载 .properties 文件中的配置
@Import导入其他配置类

测试使用 AnnotationConfigApplicationContext();

3. AOP

3.1 AOP 简介

3.1.1 AOP 定义及其优势

AOPAspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOPOOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  • 优势:减少重复代码,提高开发效率,并且便于维护

3.1.2 AOP 底层实现

1JDK 动态代理:基于接口的动态代理技术

2cglib 动态代理:基于父类的动态代理技术

3.1.3 AOP 相关概念

  • 通知(Advice):切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题
  • 连接点(Join point):连接点是在应用执行过程中能够插入切面的一个点。
  • 切点(Pointcut):一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。因此,切点其实就是定义了需要执行在哪些连接点上执行通知。
  • 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。
  • 引入(Introduction):引入允许我们向现有的类添加新方法或属性。
  • 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有很多个点可以进行织入:
    • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面的。
    • 类加载期:切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入就支持这种方式织入切面。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态的创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

3.2 详解 XML 配置 AOP

3.2.1 切点表达式的书写方式

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 修饰符可省略
  • 返回值类型、包名、类名、方法名可以使用 * 代替:表示任意
  • 参数列表可以使用两个点 .. 表示任意个数、任意类型的参数列表
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包

例如:

execution(public void com.one.aop.Target.method())	
execution(void com.one.aop.Target.*(..))
execution(* com.one.aop.*.*(..))
execution(* com.one.aop..*.*(..))
execution(* *..*.*(..))

3.2.2 通知的类型

通知的配置语法:

<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"/>

通知类型:

名称标签说明
前置通知<aop:before>用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知<aop:after-returning>用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知<aop:around>用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知<aop:throwing>用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知<aop:after>用于配置最终通知,无论增强方式执行是否有异常都会执行

3.2.3 切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式

<aop:config>
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.one.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

3.3 基于 XML の AOP 开发

3.3.1 导入 AOP 相关依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--导入 spring 的 context 坐标, context 依赖 aop-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!-- AspectJ 的织入 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.4</version>
    </dependency>
</dependencies>

3.3.2 创建目标类(内部含切点)

public class Target {
    public void targetMethod() {
        System.out.println("被增强的目标方法执行...");
    }
}

3.3.3 创建通知类(内部含通知)

public class Advice {
    public void enhance() {
        System.out.println("通知方法...");
    }
}

3.3.4 Bean 对象管理及配置织入

<?xml version="1.0" encoding="UTF-8"?>
<!-- 记得导入 aop 命名空间! -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 切点 -->
    <bean id="target" class="com.one.aop.Target"/>

    <!-- 通知类 -->
    <bean id="advice" class="com.one.aop.Advice"/>

    <!-- 切面 -->
    <aop:config>
        <aop:aspect ref="advice">
            <aop:before method="enhance" pointcut="execution(public void com.one.aop.Target.targetMethod())"/>
        </aop:aspect>
    </aop:config>

</beans>

3.3.5 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class AOP_Test {
    @Autowired
    private Target target;

    @Test
    public void testAop() {
        target.targetMethod();
    }
}

综上:

3.4 详解注解配置 AOP

3.4.1 注解通知的类型

通知的配置语法:@通知注解(“切点表达式")

名称注解说明
前置通知@Before用于配置前置通知,指定增强的方法在切入点方法之前执行
后置通知@AfterReturning用于配置后置通知,指定增强的方法在切入点方法之后执行
环绕通知@Around用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
异常抛出通知@AfterThrowing用于配置异常抛出通知,指定增强的方法在出现异常时执行
最终通知@After用于配置最终通知,无论增强方式执行是否有异常都会执行

3.4.2 切点表达式的抽取

// 抽取切点表达式!
@Pointcut("execution(* com.one.anno.*.*(..))")
public void mutualPointExecution() {
}

// 引用一!
@Before("mutualPointExecution()")
public void beforeEnhance() {
    System.out.println("beforeEnhance...");
}

// 引用二!
@AfterReturning("Advice.mutualPointExecution()")
public void afterReturnEnhance() {
    System.out.println("afterReturnEnhance...");
}

3.5 基于注解 の AOP 开发

applicationContext-annotation.xml

Advice.java

Anno_Test.java

4. JdbcTemplate

4.1 JdbcTemplate 概述

JdbcTemplate 是 spring 框架中提供的一个对象,是对原始繁琐的 Jdbc API 对象的简单封装。Spring 框架为我们提供了很多的操作模板类。例如:操作关系型数据的 JdbcTemplateHibernateTemplate,操作 nosql 数据库的 RedisTemplate,操作消息队列的 JmsTemplate 等等。

4.2 JdbcTemplate 开发步骤

4.2.1 导入 spring-jdbc & spring-tx 坐标

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.13</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
</dependencies>

4.2.2 创建数据库表和实体

public class User {
    private String username;
    private int balance;
    //此处省略 getter & setter	
}

4.2.3 创建 JdbcTemplate 对象

4.2.4 执行数据库操作

5. Spring 事务控制

5.1 编程式事务控制相关对象

编程式事务仅作了解即可!

5.1.1 PlatformTransactionManager

PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法:

方法说明
TransactionStatus getTransaction(TransactionDefination defination)获取事务的状态信息
void commit(TransactionStatus status)提交事务
void rollback(TransactionStatus status)回滚事务

注意:PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如: Dao 层技术是 jdbcmybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager Dao 层技术是 hibernate 时:org.springframework.orm.hibernate5.HibernateTransactionManager

5.1.2 TransactionDefinition

TransactionDefinition 用于定义事务的信息:

方法说明
int getIsolationLevel()获得事务的隔离级别
int getPropogationBehavior()获得事务的传播行为
int getTimeout()获得超时时间
boolean isReadOnly()是否只读

1)事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读不可重复读虚读

  • ISOLATION_DEFAULT
  • ISOLATION_READ_UNCOMMITTED
  • ISOLATION_READ_COMMITTED
  • ISOLATION_REPEATABLE_READ
  • ISOLATION_SERIALIZABLE

2)事务传播行为

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
  • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
  • 是否只读:建议查询时设置为只读

5.1.3 TransactionStatus

TransactionStatus 接口提供的是事务具体的运行状态:

方法说明
boolean hasSavepoint()是否存储回滚点
boolean isCompleted()事务是否完成
boolean isNewTransaction()是否是新事务
boolean isRollbackOnly()事务是否回滚

5.2 基于 XML 的声明式事务控制

5.2.1 声明式事务控制

Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替编程式的事务处理。

声明式事务处理的作用

  • 事务管理不侵入开发的组件
  • 根据配置文件可以轻松的移除或添加事务管理服务,无需改变代码重新编译,这样维护起来极其方便

注意Spring 声明式事务控制底层就是AOP(声明式事务控制的思路)!

5.2.2 声明式事务控制的实现

明确三件事:

  • 谁是切点?
  • 谁是通知
    • PlatformTransactionManager
    • TransactionDefinition
  • 配置切面?

先简单了解下事务的背景

1)引入 tx、aop 命名空间

2)配置事务增强(通知)

<!--平台事务管理器-->
<bean id="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="*"/>
    </tx:attributes>
</tx:advice>

其中,<tx:method> 代表切点方法的事务参数的配置,例如: <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>

  • name:切点方法名称
  • isolation:事务的隔离级别
  • propogation:事务的传播行为
  • timeout:超时时间
  • read-only:是否只读

3)配置事务 AOP 织入(切面)

<!--事务的aop增强-->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.one.service.impl.*.*(..))"/>
</aop:config>

综上:

总结

5.3 基于注解的声明式事务控制

AccountServiceImpl.java

@Service("accountServiceImpl")
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.MANDATORY)
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    
    @Transactional(value = "transactionManager", isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, timeout = -1, readOnly = true)
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan, money);
        // RuntimeException
        int i = 1 / 0;
        accountDao.in(inMan, money);
    }
}

applicationContext.xml

<!-- 开启组件扫描! -->
<context:component-scan base-package="com.one"/>

<!-- 事务的注解驱动! -->
<tx:annotation-driven transaction-manager="transactionManager"/>

注意:使用 @Transactional 在需要进行事务控制的或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别(isolation)、传播行为(propagation)...

  • 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
  • 使用在方法上,不同的方法可以采用不同的事务参数配置覆盖类上的 @Transaction(即 @Transaction 优先级:方法 > 类)
  • Xml 配置文件中要开启事务的注解驱动: <tx:annotation-driven />

applicationContext.xml 详解

6. Spring 集成

6.1 Spring 集成 JUnit

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

UserDaoImpl udi = ac.getBean("userDaoImpl",UserDaoImpl.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

解决办法:让 SpringJunit 负责创建 Spring 容器,但是需要将配置文件的名称告诉它,然后将需要进行测试 Bean 直接在测试类中进行注入。


Spring 集成 Junit 步骤

1)导入 Spring 集成 Junit 的坐标

<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <!--4.12版本及以上!-->
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2)使用 @RunWith 注解替换掉原来的运行期

@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}

3)使用 @ContextConfiguration 指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
// 加载 Spring 核心配置文件
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
// 加载 Spring 核心配置类
//@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest{
}

4)使用 @Autowired 注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest{
    @Autowired
    private UserDao userDao;
}

5)创建测试方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest{
    @Autowired
    private UserDao userDao;
    
    @Test
    public void testSpringJUnit() {
        userDao.save();
    }
}

6.2 Spring 集成 Web 环境

应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得 Bean 时都要编写 new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次

在 Web 项目中,可以使用 ServletContextListener 监听 Web 应用的启动,我们可以在 Web 应用启动时,就加载 Spring 的配置文件,创建应用上下文对象 ApplicationContext将其存储到最大的域 servletContext 域中,这样就可以在任意位置从域中获得应用上下文 ApplicationContext 对象了。

6.2.1 导入 Spring 集成 web 坐标

<!-- Spring集成-web-环境! -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

6.2.2 配置 ContextLoaderListener 监听器

<!-- 全局初始化参数: 将 "applicationContext.xml" 字符串与类解耦! -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!-- 配置监听器 -->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

6.2.3 通过工具获得应用上下文对象

public class Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    }
}

原创不易🧠 转载请标明出处🌊
若本文对你有所帮助,点赞支持嗷🔥