当前位置: 首页 > news >正文

spring教程

目录

  • Spring概述
    • Spring 框架是什么
    • Spring优点
  • IoC控制反转
    • 控制反转介绍
    • 依赖注入介绍
    • Spring的第一个程序
      • 引入 maven 依赖 pom.xml
      • 定义接口与实体类
      • 创建 Spring 配置文件
      • 定义测试类
    • 容器接口和实现类
      • 配置文件在类路径下
      • ApplicationContext 容器中对象的装配时机
      • 使用 spring 容器创建的 java 对象
    • 基于 XML 的 DI
      • 注入分类
        • set 注入(掌握)
          • A、 简单类型
          • B、 引用类型
        • 构造注入(理解)
          • 举例 1:
          • 举例 2:
      • 引用类型属性自动注入
        • byName 方式自动注入
        • byType 方式自动注入
      • 为应用指定多个 Spring 配置文件
    • 基于注解的 DI
      • 指定多个包的三种方式:
      • 定义 Bean 的注解@Component
      • Spring注解@Autowired 自动注入
        • 简单类型属性注入@Value
        • byType 自动注入@Autowired
        • byName 自动注入@Autowired 与@Qualifier
        • required属性
      • JDK 注解@Resource 自动注入
        • byType 注入引用类型属性
        • byName 注入引用类型属性
      • 注解与 XML 的对比
  • AOP 面向切面编程
    • AOP **简介**
    • 面向切面编程对有什么好处?
    • AOP 编程术语
      • 切面(Aspect)
      • 连接点(JoinPoint)
      • 切入点(Pointcut)
      • 目标对象(Target)
      • 通知(Advice)
      • 代理 (Proxy)
      • 织入 (Weaving)
    • AspectJ 对 AOP 的实现
      • AspectJ 的通知类型
      • AspectJ 的切入点表达式
      • AspectJ 的开发环境
      • 实现步骤
        • 定义业务接口与实现类
        • 定义切面类
        • 声明目标对象切面类对象
        • 注册 AspectJ 的自动代理
        • 测试类中使用目标对象的 id
      • [掌握]@Before 前置通知-方法有 JoinPoint 参数
      • [掌握]@AfterReturning 后置通知-注解有 returning 属性
      • [掌握]@Around 环绕通知-增强方法有 ProceedingJoinPoint参数
      • [了解]@AfterThrowing 异常通知-注解中有 throwing 属性
      • [了解]@After 最终通知
      • @Pointcut 定义切入点
  • Spring 集成 MyBatis
    • MySQL 创建数据库 springdb,新建表 Student
    • maven 依赖 pom.xml
    • 定义实体类 Student
    • 定义 StudentDao 接口
    • 定义映射文件 mapper
    • 定义 Service 接口和实现类
    • 定义 MyBatis 主配置文件
    • 修改 Spring 配置文件
      • Druid 数据源 DruidDataSource
      • 从属性文件读取数据库连接信息
      • 注册 SqlSessionFactoryBean
      • 定义 Mapper 扫描配置器 MapperScannerConfigurer
    • 向 Service 注入接口名
    • Spring 配置文件全部配置
  • Spring 事务
    • Spring 的事务管理
    • Spring 事务管理 API
      • 事务管理器接口(重点)
        • 常用的两个实现类
        • Spring 的回滚方式(理解)
        • 回顾错误与异常(理解)
      • 事务定义接口
        • 定义了五个事务隔离级别常量(掌握)
        • 定义了七个事务传播行为常量(掌握)
          • PROPAGATION_REQUIRED:
          • PROPAGATION_SUPPORTS
          • PROPAGATION_REQUIRES_NEW
        • 定义了默认事务超时时限
    • 程序举例环境搭建
      • 创建数据库表
      • maven 依赖 pom.xml
      • 创建实体类
      • 定义 dao 接口
      • 定义异常类
      • 定义 Service 接口
      • 定义 service 的实现类
      • 修改 Spring 配置文件内容
      • 定义测试类
    • 使用 Spring 的事务注解管理事务(掌握)
      • 在容器中添加事务管理器
      • 开启事务注解驱动
      • 业务层 public 方法加入事务属性
    • 使用 AspectJ 的 AOP 配置管理事务(掌握)
      • maven 依赖 pom.xml
      • 在容器中添加事务管理器
      • 配置事务通知属性
      • 配置增强器
      • 修改测试类
  • Spring 与 Web
    • Web 项目使用 Spring 的问题(了解)
      • 新建一个 Maven Project
      • 复制代码,配置文件, jar
      • 定义 index 页面
      • 定义 RegisterServlet(重点代码)
      • 定义 success 页面
      • web.xml 注册 Servlet
      • 运行结果分析
    • 使用 Spring 的监听器 ContextLoaderListener(掌握)
      • maven 依赖 pom.xml
      • 注册监听器 ContextLoaderListener
      • 指定 Spring 配置文件的位置
      • 获取 Spring 容器对象
        • 直接从 ServletContext 中获取
        • 通过 WebApplicationContextUtils 获取


Spring概述

Spring 框架是什么

Spring是一个轻量级的java开发框架,核心是控制反转(IoC)和面向切面编程(AOP)

Spring的主要作用是降低代码间的耦合度。就是让对象和对象之间关系不是使用代码关联,而是通过配置说明。

Spring使用IoC降低业务对象之间的耦合度,不用自己创建要使用的对象。而是由Spring容器统一管理,自动“注入”,注入就是赋值。而AOP编程思想可以将业务逻辑系统级服务隔离,使业务逻辑跟各个系统级服务的耦合度降低,提高程序的通用性和开发效率,使得开发人员可以更加专注于业务逻辑本身。

Spring优点

  • 轻量
  • 针对接口编程,解耦合
  • AOP 编程的支持,允许将一些通用任务,如安全、事务、日志等进行集中式处理,从而提高了程序的复用性
  • 支持声明式事务处理,只需要通过配置就可以完成对事务的管理
  • 方便集成各种优秀框架

IoC控制反转

控制反转介绍

控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的装配和管理。

IoC理论提出的观点大体是这样的:借助于**“第三方”实现具有依赖关系的对象之间的解耦**。如下图:

img

于引进了中间位置的“第三方”,也就是IoC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IoC容器,所以,IoC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IoC容器比喻成“粘合剂”的由来。

我们再来做个试验:把上图中间的IoC容器拿掉,然后再来看看这套系统:

img

我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。


我们再来看看,控制反转(IoC)到底为什么要起这么个名字?我们来对比一下:

软件系统在没有引入IoC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

软件系统在引入IoC容器之后,这种情形就完全改变了,如图3所示,由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。

通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

依赖注入介绍

IoC的一个重点是 在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。

比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。

那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。


2004年,Martin Fowler探讨了同一个问题,既然IoC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IoC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IoC的方法:注入。所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。

Spring的第一个程序

引入 maven 依赖 pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

定义接口与实体类

接口

package com.bjpowernode.service;

public interface SomeService {
    void doSome();
}

实体类

package com.bjpowernode.service;

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行了doSome方法");
    }
}

创建 Spring 配置文件

在 src/main/resources/目录创建 applicationContext.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="someService" class="com.bjpowernode.service.SomeServiceImpl">

    </bean>
</beans>

:用于定义一个实例对象。一个实例对应一个 bean 元素。

id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean, Bean 与 Bean 间的依
赖关系也是通过 id 属性关联的。

class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

定义测试类

    @Test
    public void test02(){
        //1.指定spring配置文件名称
        String config = "beans.xml";
        //2.创建表示spring容器的对象 ApplicationContext
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //3.从spring容器中获取对象 使用id
        SomeService service = (SomeService) ac.getBean("someService");
        //4.执行对象的业务方法
        service.doSome();
    }

容器接口和实现类

ApplicationContext 用于加载 Spring 的配置文件,在程序中充当“容器”的角色。其实现
类有两个。

image-20210303212726030

配置文件在类路径下

若 Spring 配置文件存放在项目的类路径下,则使用 ClassPathXmlApplicationContext 实现
类进行加载。

image-20210303212810560

ApplicationContext 容器中对象的装配时机

ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNCZmM6O-1615022130450)(…/Library/Application Support/typora-user-images/image-20210303212841671.png)]

使用 spring 容器创建的 java 对象

image-20210303212913182

基于 XML 的 DI

注入分类

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化
是由容器自动完成的,称为注入。

根据注入方式的不同,常用的有两类: set 注入、构造注入。

set 注入(掌握)

set 注入也叫设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、
直观,因而在 Spring 的依赖注入中大量使用。

A、 简单类型

image-20210303213125315

创建 java.util.Date 并设置初始的日期时间,Spring 配置文件:

image-20210303213223577

B、 引用类型

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。 ref
的值必须为某 bean 的 id 值。

image-20210303213340735

对于其它 Bean 对象的引用,使用标签的 ref 属性

image-20210303213419382

构造注入(理解)

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设
置依赖关系

举例 1:

image-20210303213521996

<constructor-arg />标签中用于指定参数的属性有(二选一):

name:指定参数名称。

index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

举例 2:

使用构造注入创建一个系统类 File 对象

image-20210303213622642

测试类:

image-20210303213634059

引用类型属性自动注入

对于引用类型属性的注入,也可不在配置文件中显式的注入。可以通过为<bean/>标签设置 autowire 属性值,为引用类型属性进行隐式自动注入。根据自基于注解的 DI动注入判断标准的不同,可以分为两种:

byName:根据名称自动注入

byType:根据类型自动注入

byName 方式自动注入

当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

举例:

image-20210303213856735

image-20210303213927855

byType 方式自动注入

使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

举例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUCCZS2i-1615022130460)(…/Library/Application Support/typora-user-images/image-20210303214111554.png)]

image-20210303214124991

为应用指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变
得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将
Spring 配置文件分解成多个配置文件。

包含关系的配置文件:多个配置文件中有一个总文件,总配置文件将各其它子文件通过引入。在 Java代码中只需要使用总配置文件对容器进行初始化即可。

举例:

image-20210303214242725

也可使用通配符*。但,此时要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为spring-total.xml

image-20210303214313329

基于注解的 DI

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。

需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--声明组件扫描器-->
    <context:component-scan base-package="com.bjpowernode.ba03" />
</beans>

指定多个包的三种方式:

1)使用多个 context:component-scan 指定不同的包路径

image-20210303221808582


2)指定 base-package 的值使用分隔符

分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。

image-20210303221832247


3)base-package 指定到父包名

base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。

image-20210303221923971

或者最顶级的父包

image-20210303221929364

但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在 com.bjpowernode.beans 包中

image-20210303221956807

定义 Bean 的注解@Component

需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。

另外, Spring 还提供了 3 个创建对象的注解:

➢ @Repository 用于对 DAO 实现类进行注解

➢ @Service 用于对 Service 实现类进行注解

➢ @Controller 用于对 Controller 实现类进行注解

这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义, @Service创建业务层对象业务层对象可以加入事务功能, @Controller 注解创建的对象可以作为处理器接收用户的请求。

@Repository, @Service, @Controller 是对@Component 注解的细化,标注不同层的对
象。 即持久层对象,业务层对象,控制层对象。


如果@Component 不指定 value 属性, bean 的 id 是类名的首字母小写

image-20210304145420563

Spring注解@Autowired 自动注入

简单类型属性注入@Value

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。**使用该注解完成属性注入时,类中无需 setter。**当然,若属性有 setter,则也可将其加到 setter 上。

举例:

image-20210304145811121

byType 自动注入@Autowired

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

举例:

image-20210304150134288

byName 自动注入@Autowired 与@Qualifier

需要在引用属性上联合使用注解@Autowired@Qualifier@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。 类中无需 set 方法,也可加到 set 方法上。

举例:

image-20210304172236417

required属性

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null

image-20210304172358895

JDK 注解@Resource 自动注入

Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean。 默认是按名称注入。 使用该注解,要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。

byType 注入引用类型属性

@Resource 注解若不带任何参数, 采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。

举例:

image-20210304172953045

byName 注入引用类型属性

@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

举例:

image-20210304173023466

注解与 XML 的对比

注解优点是:

  • 方便
  • 直观
  • 高效(代码少,没有配置文件的书写那么复杂)

其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。


XML 方式优点是:

  • 配置和代码是分离的
  • xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

AOP 面向切面编程

AOP 简介

AOP(Aspect Orient Programming),面向切面编程。 面向切面编程是从动态角度考虑程序运行过程。

AOP 底层,就是采用动态代理模式实现的。采用了两种代理: JDK 的动态代理,与 CGLIB的动态代理。

AOP 是 Spring 框架中的一个重要内容。利用 AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。

若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

面向切面编程对有什么好处?

1.减少重复;

2.专注业务;

注意:面向切面编程只是面向对象编程的一种补充。

使用 AOP 减少重复代码,专注业务实现:

image-20210304175812669

AOP 编程术语

切面(Aspect)

切面泛指交叉业务逻辑。事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强

连接点(JoinPoint)

程序执行过程中的某个阶段点,指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

切入点(Pointcut)

切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

是指切面与程序流程的交叉点。

目标对象(Target)

目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。 上 例 中 的StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

通知(Advice)

AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以理解为切面类中的方法,它是切面的具体实现。

代理 (Proxy)

将通知应用到目标对象之后,被动态创建的对象。

织入 (Weaving)

将切面代码插入到目标对象上,从而生成代理对象的过程。


切入点定义切入的位置,通知定义切入的时间

AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。 Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以, Spring又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。

在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

AspectJ 是一个优秀面向切面的框架, 它扩展了 Java 语言,提供了强大的切面实现。

官网地址: http://www.eclipse.org/aspectj/

AspetJ 是 Eclipse 的开源项目,官网介绍如下

  • a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台的面向切面编程的语言)

  • Java platform compatible(兼容 Java 平台,可以无缝扩展)

  • easy to learn and use(易学易用)

AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:

(1)前置通知

(2)后置通知

(3)环绕通知

(4)异常通知

(5)最终通知

AspectJ 的切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution ( [modifiers-pattern] 访问权限类型

ret-type-pattern 返回值类型

[declaring-type-pattern] 全限定性类名

name-pattern(param-pattern)方法名(参数名)

[throws-pattern] 抛出异常类型

)

以上表达式共 4 个部分:

execution(访问权限 方法返回值 方法名(参数) 异常类型)


切入点表达式要匹配的对象就是目标方法的方法名。所以, execution 表达式中明显就是方法的签名。 注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

image-20210304190207783

execution(public * *(..))
指定切入点为:任意公共方法。

execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

...

AspectJ 的开发环境

maven 依赖

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.2.5.RELEASE</version>
</dependency>

实现步骤

AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。

定义业务接口与实现类

public interface SomeService {
    void doSome(String name, Integer age);
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("目标方法 doSome()");
    }
}

定义切面类

类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

image-20210304211102595

声明目标对象切面类对象

applicationContext.xml文件添加:

<!-- 声明目标类对象 -->
<bean id="someService" class="com.bjpowernode.ba01.SomeServiceImpl" />
<!-- 声明切面类类对象 -->
<bean id="myAspect" class="com.bjpowernode.ba01.MyAspect" />

注册 AspectJ 的自动代理

在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect注解,并按通知类型与切入点,将其织入,并生成代理。

applicationContext.xml文件添加:

<!-- 声明自动代理生成器 创建代理 -->
<aop:aspectj-autoproxy />

<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。

其工作原理是, <aop:aspectj-autoproxy/>通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点(本例是Before)。

测试类中使用目标对象的 id

image-20210304211746852

[掌握]@Before 前置通知-方法有 JoinPoint 参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等

所有的通知方法均可包含 JoinPoint 类型参数。

image-20210304212844470

[掌握]@AfterReturning 后置通知-注解有 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning 属性就是用于指定接收方法返回值的变量名的。

所以,被注解为后置通知的方法,除了可以包含JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

returing属性所指定的形参名必须对应增强处理中的一个形参名,当目标方法执行返回后,返回值作为相应的参数值传入增强处理方法中。

虽然AfterReturning增强处理可以访问到目标方法的返回值,但它不可以改变目标方法的返回值


接口增加方法:

image-20210305181102489

实现方法:
image-20210305181114181

定义切面:

image-20210305181128283

[掌握]@Around 环绕通知-增强方法有 ProceedingJoinPoint参数

在目标方法执行之前和之后执行。

被注解为环绕增强的方法要有返回值, Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

接口增加方法:

image-20210305181519565

接口方法的实现:

image-20210305181532189

定义切面:

image-20210305181545556

[了解]@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。

当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

增加业务方法:

image-20210305182216118

方法实现:

image-20210305182235401

定义切面:

image-20210305182255343

[了解]@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行

增加方法:

image-20210305203905541

方法实现:

image-20210305203927008

定义切面:

image-20210305203953052

@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。

AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法

image-20210305182523782

Spring 集成 MyBatis

将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。

实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理Spring 像插线板一样, mybatis 框架是插头,可以容易的组合到一起。 插线板 spring 插上 mybatis, 两个框架就是一个整体。

MySQL 创建数据库 springdb,新建表 Student

image-20210305222444591

maven 依赖 pom.xml

		<dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>

定义实体类 Student

image-20210305222916126

定义 StudentDao 接口

image-20210305222953585

定义映射文件 mapper

在 Dao 接口的包中创建 MyBatis 的映射文件 mapper,命名与接口名相同,本例为StudentDao.xml。 mapper 中的 namespace 取值也为 Dao 接口的全限定性名。

image-20210305223056369

定义 Service 接口和实现类

接口定义:

image-20210305223150159

实现类定义:

image-20210305223226827

定义 MyBatis 主配置文件

在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml

这里有两点需要注意:

(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。

(2) 这里对 mapper 映射文件的注册,使用<package/>标签,即只需给出 mapper 映射文件所在的包即可。因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。 这种方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的<resource/>标签方式。

image-20210305223456090

修改 Spring 配置文件

Druid 数据源 DruidDataSource

数据源直接以 Bean 的形式配置在 Spring 配置文件中。

Druid 是阿里的开源数据库连接池。 是 Java 语言中最好的数据库连接池。 Druid 能够提供强大的监控和扩展功能。 Druid 与其他数据库连接池的最大区别是提供数据库的监控功能。

官网: https://github.com/alibaba/druid

使用地址: https://github.com/alibaba/druid/wiki/常见问题

配置连接池:

image-20210305223811299

Spring 配置文件:

image-20210305223835524

从属性文件读取数据库连接信息

为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取数据。属性文件名称自定义,但一般都是放在 src 下

image-20210305224318118

Spring 配置文件从属性文件中读取数据时,需要在<property/>的 value 属性中使用${ }
将在属性文件中定义的 key 括起来,以引用指定属性的值。

image-20210305224404066

该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。 使用<context>标签。

<context:property-placeholder/> 该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文,标签中有一个属性 location,用于指定属性文件的位置。

image-20210305224611012

注册 SqlSessionFactoryBean

image-20210305224630963

定义 Mapper 扫描配置器 MapperScannerConfigurer

Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本包中 mapper 的代理对象。该 Bean 无需设置 id 属性。 basePackage 使用分号或逗号设置多个包。

image-20210305224727145

向 Service 注入接口名

向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口的对象。

image-20210305225050242

Spring 配置文件全部配置

    <!--引用数据库信息文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!-- 配置阿里云的Druid连接池 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!-- 动态代理对象 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.bjpowernode.dao" />
    </bean>

    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

Spring 事务

Spring 的事务管理

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务

(2)使用 AspectJ 的 AOP 配置管理事务

Spring 事务管理 API

Spring 的事务管理,主要用到两个事务相关的接口。

事务管理器接口(重点)

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息

image-20210306121048820

常用的两个实现类

PlatformTransactionManager 接口有两个常用的实现类:

➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。

➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

Spring 的回滚方式(理解)

Spring 事务的默认回滚方式是: 发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。 不过,对于受查异常,程序员也可以手工设置其回滚方式。

回顾错误与异常(理解)

image-20210306121203220

Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时, 才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。

Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、ThreadDeath、 NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出)的, JVM 一般会终止线程。

程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。

异常分为运行时异常与受查异常。

运行时异常,是 RuntimeException 类或其子类, 即只有在运行时才出现的异常。如,NullPointerException、 ArrayIndexOutOfBoundsException、 IllegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如 SQLException, ClassNotFoundException, IOException 等都属于受查异常。RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为 RuntimeException 的子类,那么定义的就是受查异常。

事务定义接口

事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。

image-20210306121447205

定义了五个事务隔离级别常量(掌握)

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

➢ DEFAULT: 采用 DB 默认的事务隔离级别。 MySql 的默认为 REPEATABLE_READ; Oracle
默认为 READ_COMMITTED。

➢ READ_UNCOMMITTED: 读未提交。未解决任何并发问题。

➢ READ_COMMITTED: 读已提交。解决脏读,存在不可重复读与幻读。

➢ REPEATABLE_READ: 可重复读。解决脏读、不可重复读,存在幻读

➢ SERIALIZABLE: 串行化。不存在并发问题。

定义了七个事务传播行为常量(掌握)

所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如, A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_SUPPORTS

PROPAGATION_MANDATORY

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

PROPAGATION_REQUIRED:

指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。

如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

image-20210306121811559
PROPAGATION_SUPPORTS

指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

image-20210306121837706
PROPAGATION_REQUIRES_NEW

总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

image-20210306121921149

定义了默认事务超时时限

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限, sql 语句的执行时长。注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

程序举例环境搭建

举例: 购买商品 trans_sale 项目

本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。

实现步骤:

创建数据库表

创建两个数据库表 sale , goods

sale 销售表

image-20210306122039549

goods 商品表

image-20210306122101023

goods 表数据

image-20210306122118358

maven 依赖 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.bjpowernode</groupId>
  <artifactId>ch08</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>

      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

</project>

创建实体类

创建实体类 Sale 与 Goods

image-20210306135508359

定义 dao 接口

定义两个 dao 的接口 SaleDao , GoodsDao

image-20210306135534019

定义 dao 接口对应的 sql 映射文件

SaleDao.xml

image-20210306135608113

GoodsDao.xml

image-20210306135647524

定义异常类

定义 service 层可能会抛出的异常类 NotEnoughException

public class NotEnoughException extends RuntimeException{

    public NotEnoughException() {
        super();
    }

    public NotEnoughException(String message) {
        super(message);
    }
}

定义 Service 接口

定义 Service 接口 BuyGoodsService

public interface BuyGoodsService {
    void buy(Integer goodsId, Integer nums);
}

定义 service 的实现类

定义 service 层接口的实现类 BuyGoodsServiceImpl

public class BuyGoodsServiceImpl implements BuyGoodsService {
    private SaleDao saleDao;
    private GoodsDao goodsDao;
    
    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
    
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("=======buy方法开始=======");
        //记录销售信息
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);
        //更新库存
        Goods goods = goodsDao.selectGoods(goodsId);
        if(goods == null){
            throw new NullPointerException("编号是 "+goodsId+" 商品不存在");
        } else if(goods.getAmount() < nums){
            throw new NotEnoughException("编号是 "+goodsId+" 商品库存不足");
        }
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=======buy方法结束=======");
    }
}

修改 Spring 配置文件内容

声明 Mybatis 对象

<!--引用数据库信息文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 使用Druid数据库连接池 -->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.passwd}" />
    <property name="maxActive" value="${jdbc.max}" />
</bean>

<!-- 声明sqlSessionFactoryBean,创建sqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="myDataSource" />
    <property name="configLocation" value="classpath:mybatis.xml" />
</bean>

<!-- 声明mybatis的扫描器对象,创建dao对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    <property name="basePackage" value="com.bjpowernode.dao" />
</bean>

声明业务层对象

<bean id="buyGoodsService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
    <property name="goodsDao" ref="goodsDao" />
    <property name="saleDao" ref="saleDao" />
</bean>

定义测试类

定义测试类 MyTest。现在就可以在无事务代理的情况下运行了。

@Test
public void test01()
{
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
    buyGoodsService.buy(1001, 200);
}

使用 Spring 的事务注解管理事务(掌握)

通过@Transactional 注解方式, 可将事务织入到相应 public 方法中,实现事务管理。

@Transactional 的所有可选属性如下所示:

➢ propagation: 用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED

➢ isolation: 用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT

➢ readOnly: 用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。

➢ timeout: 用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。

➢ rollbackFor: 指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

➢ rollbackForClassName: 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

➢ noRollbackFor: 指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

➢ noRollbackForClassName: 指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。


需要注意的是, @Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public方法,如果加上了注解@Transactional, 虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。

@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。


实现注解的事务步骤:

复制 trans_sale 项目,新项目 trans_sale_annotation

在容器中添加事务管理器

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--指定数据源-->
    <property name="dataSource" ref="myDataSource" />
</bean>

开启事务注解驱动

告诉spring框架要使用注解的方式管理事务。

spring使用aop的环绕通知机制,创建@Transactional所在类的代理对象,给方法加入事务功能:在业务方法执行之前先开启事务,在业务方法之后提交或回滚事务。

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

业务层 public 方法加入事务属性

image-20210306141316600

直接加@Transactionl表示使用默认值,默认传播行为是REQUIRED,默认的隔离级别DEFAULT,默认抛出运行异常回滚事务。

使用 AspectJ 的 AOP 配置管理事务(掌握)

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。

使用 XML 配置顾问(advisor)方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。


复制 trans_sale 项目,并重命名为 trans_sal_aspectj。在此基础上修改。

maven 依赖 pom.xml

新加入 aspectj 的依赖坐标

<!--aspectj依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

在容器中添加事务管理器

<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="myDataSource" />
</bean>

配置事务通知属性

为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法

例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。

image-20210306142428948

配置增强器

指定将配置好的事务通知,织入给谁。

<!-- aop配置:通知应用的切入点 -->
<aop:config>
    <!--配置切入点表达式 指定哪些包中类要使用事务-->
    <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
    <!--配置增强器 关联advice和pointCut-->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
</aop:config>

修改测试类

测试类中要从容器中获取的是目标对象。

@Test
public void test01()
{
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
    buyGoodsService.buy(1001, 10);
}

Spring 与 Web

在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet) 中获取到 Spring容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。

Web 项目使用 Spring 的问题(了解)

举例: springWeb 项目(在 spring-mybatis 基础上修改)

新建一个 Maven Project

类型 maven-archetype-webapp

复制代码,配置文件, jar

将 spring-mybatis 项目中以下内容复制到当前项目中:

(1) Service 层、 Dao 层全部代码

(2)配置文件 applicationContext.xml 及 jdbc.properties, mybatis.xml

(3) pom.xml

(4)加入 servlet jsp 依赖

在之前原有的 pom.xml 文件中再加入以下的内容:

<dependency>
  <groupId>javax.servlet.jsp</groupId>
  <artifactId>javax.servlet.jsp-api</artifactId>
  <version>2.3.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.5</version>
  <scope>provided</scope>
</dependency>

定义 index 页面

image-20210306154953766

定义 RegisterServlet(重点代码)

public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取参数
        String strId = request.getParameter("id");
        String strName = request.getParameter("name");
        String strEmail = request.getParameter("email");
        String strAge = request.getParameter("age");
        //创建spring容器对象
        ServletContext sc = getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
        System.out.println("容器对象="+ctx);
        //获取service
        StudentService service = (StudentService) ctx.getBean("studentService");
        Student student = new Student();
        student.setId(Integer.valueOf(strId));
        student.setName(strName);
        student.setEmail(strEmail);
        student.setAge(Integer.valueOf(strAge));
        //调用service方法
        service.addStudent(student);
        //给下一个页面
        request.getRequestDispatcher("/result.jsp").forward(request,response);
    }
}

定义 success 页面

<body>
    注册成功
</body>

web.xml 注册 Servlet

<servlet>
  <servlet-name>RegisterServlet</servlet-name>
  <servlet-class>com.bjpowernode.controller.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>RegisterServlet</servlet-name>
  <url-pattern>/reg</url-pattern>
</servlet-mapping>

运行结果分析

当表单提交,跳转到 success.jsp 后,多刷新几次页面,查看后台输出,发现每刷新一次页面,就 new 出一个新的 Spring 容器。即,每提交一次请求,就会创建一个新的 Spring 容器。对于一个应用来说,只需要一个Spring 容器即可。所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Bt6b6Y4-1615022130511)(…/Library/Application Support/typora-user-images/image-20210306155417314.png)]

此时,可以考虑,将 Spring 容器的创建放在 Servlet 进行初始化时进行,即执行 init() 方法时执行。并且,Servlet 还是单例多线程的,即一个业务只有一个 Servlet 实例,所有执行该业务的用户执行的都是这一个Servlet 实例。这样, Spring 容器就具有了唯一性了。

但是, Servlet 是一个业务一个 Servlet 实例,即LoginServlet 只有一个,但还会有StudentServlet、 TeacherServlet 等。每个业务都会有一个 Servlet,都会执行自己的 init()方法,也就都会创建一个 Spring 容器了。这样一来, Spring 容器就又不唯一了。

使用 Spring 的监听器 ContextLoaderListener(掌握)

举例: springweb-2 项目(在 spring-web 项目基础上修改)

对于 Web 应用来说, ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletContext 对象, 该对象是在 Web 应用装载时初始化的。 若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证Spring 容器的创建只会执行一次,也就保证了Spring 容器在整个应用中的唯一性。

当 Spring 容器创建好后,在整个应用的生命周期过程中, Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性

上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中:spring-web-5.2.5.RELEASE


maven 依赖 pom.xml

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-web</artifactId>
	<version>5.2.5.RELEASE</version>
</dependency>

注册监听器 ContextLoaderListener

若 要 在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听。 在 web.xml 中注册该监听器

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Spring 为该监听器接口定义了一个实现类 ContextLoaderListener, 完成了两个很重要的工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。

打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化方法,一个销毁方法

image-20210306155954490

所以,在这四个方法中较重要的方法应该就是 contextInitialized(), context 初始化方法。

image-20210306160045162

跟踪 initWebApplicationContext() 方法,可以看到,在其中创建了容器对象。

image-20210306160127305

并且,将创建好的容器对象放入到了 ServletContext 的空间中, key 为一个常量:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

image-20210306160217569

指定 Spring 配置文件的位置

ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认的 Spring 配置文件位置与名称为: WEB-INF/applicationContext.xml。但,一般会将该配置文件放置于项目的 classpath 下,即src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:applicationContext.xml</param-value>
</context-param>

从监听器 ContextLoaderListener 的父类 ContextLoader 的源码中可以看到其要读取的配置文件位置参数名称 contextConfigLocation

image-20210306160511017

获取 Spring 容器对象

在 Servlet 中获取容器对象的常用方式有两种:

直接从 ServletContext 中获取

从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。

image-20210306160610500

通过 WebApplicationContextUtils 获取

工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring容器对象: getRequiredWebApplicationContext(ServletContext sc)

调用 Spring 提供的方法获取容器对象

//创建spring容器对象
ServletContext sc = getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
System.out.println("容器对象="+ctx);

查其源码,看其调用关系,就可看到其是从 ServletContext 中读取的属性值,即 Spring
容器。

image-20210306160828375

以上两种方式,无论使用哪种获取容器对象, 刷新 success 页面后,可看到代码中使用
的 Spring 容器均为同一个对象。

相关文章:

  • ModuleNotFoundError: No module named ‘kamene‘
  • 解决Ubuntu安装VMware后 无法ping通虚拟机的Win7
  • Mac/Linux/Ubuntu下 视频 显示双语/中英字幕 的方法
  • 如何将chrome浏览器中隐藏的扩展显示出来 恢复扩展
  • Windows XP系统中 设置DPI缩放 字体大小的简单方法
  • 谷歌adsense自动广告 没效果 不显示不行 的原因
  • access的立即窗口在哪里 怎么打开access的 立即窗口
  • Access数据库 找不到使用控件向导的位置 的解决方法
  • rapid-framework的使用
  • idea mac 搜索项目所有文件的内容
  • SpringMVC新手教程
  • Windows XP系统 删除文件不显示确认对话框 的设置
  • Ubuntu18.04 安装nixnote2 使用Linux版本印象笔记
  • Windows XP系统 显示实时内存利用率百分比 的方法
  • Ubuntu18.04 安装深度终端 Deepin Terminal 的方法
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • gf框架之分页模块(五) - 自定义分页
  • Javascript 原型链
  • LeetCode算法系列_0891_子序列宽度之和
  • REST架构的思考
  • VUE es6技巧写法(持续更新中~~~)
  • vue2.0项目引入element-ui
  • 从重复到重用
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 鱼骨图 - 如何绘制?
  • 云大使推广中的常见热门问题
  • 在weex里面使用chart图表
  • Spring Batch JSON 支持
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • # Redis 入门到精通(一)数据类型(4)
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • #define与typedef区别
  • #VERDI# 关于如何查看FSM状态机的方法
  • #Z0458. 树的中心2
  • (06)Hive——正则表达式
  • (1)Nginx简介和安装教程
  • (2)leetcode 234.回文链表 141.环形链表
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (十六)串口UART
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • .gitignore文件---让git自动忽略指定文件
  • .net core Redis 使用有序集合实现延迟队列
  • .NET MVC之AOP
  • .NET 发展历程
  • .NET/C# 的字符串暂存池
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • @Async注解的坑,小心
  • @entity 不限字节长度的类型_一文读懂Redis常见对象类型的底层数据结构
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [ C++ ] STL---仿函数与priority_queue
  • [BZOJ3223]文艺平衡树
  • [C#]winform基于深度学习算法MVANet部署高精度二分类图像分割onnx模型高精度图像二值化
  • [C++] Windows中字符串函数的种类