Spring

77 阅读14分钟

Spring Framework

IOC(Inversion of Control)

控制反转,由主动的 new 对象的过程转变为对象创建的控制权交给外部,由 Spring 来创建对象,而程序员只需要使用对象就可以。

核心概念

  1. Spring 提供了一个容器成为 IoC 容器,用来充当 IoC 思想中的外部。
  2. IoC容器负责对象的创建,初始化等一系类工作,被创建或被管理的对象在 IoC 容器中统称为 Bean。
  3. DI(Dependency Injection):在容器中建立 bean 与 bean 中如果存在依赖关系的整个过程,成为依赖注入。

解决问题

  1. IoC 用来管理什么
  • Service与Dao(Data Access Object)
  1. 如何将被管理的对象告知 IoC 容器
  • 通过 spring 的核心配置文件 application.xml 配置
  1. 被管理的对象交给 IoC 容器,如何获取到 IoC 容器(接口)?
  • ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  • ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. IoC 容器得到之后,怎么获取 Bean ?
  • 通过 Bean id 获取:UserDao userDao = (UserDao) ctx.getBean("userDao")
  • 通过 Bean id 和 类型获取:UserDao userDao = ctx.getBean("userDao", UserDao.class)
  • 通过 Bean 类型获取:UserDao userDao = ctx.getBean(UserDao.class)
  1. Spring需要导入的坐标有那些?
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

Spring 的核心配置文件

applicationContext.xml

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

    <bean id="bookDao" class="com.lmb.dao.impl.BookDaoImpl" />
    <bean id="bookService" class="com.lmb.service.impl.BookServiceImpl" />
</beans>
  1. Bean
  • 使用 标签设置 bean

  • attr

    • id:bean 的 id 标识
    • class:类的全限定类名
    • name:配置别名,在获取 bean 时可以使用 id 获取,也可以使用别名获取,以 空格或,或;隔开可以设置多个别名
    • scope:(singleton/prototype) 默认值为 singleton,spring给我们创建的对象默认是单例的,设置为 prototype 即为多例的
      • 什么对象适合单例:表现层对象,业务层对象,数据层对象,工具类对象等
      • 什么对象不适合单例:封装的实体类对象,因为实体类中封装的数据可能每次都不一样
    • factory-method:实例化 bean 对象工厂方法
    • factory-bean:用于实例化 bean 对象的 bean 实例工厂‘
    • autowire:自动装配注入
      • IoC容器根据 bean 所在的容器自动查找并注入到 bean 中的过程成为自动装配
      • 自动装备配只能装配引用数据类型,不能装配简单数据类型
      • 使用按类型装备配(byType)时,必须保容器中相同类型的 bean 唯一,推荐使用
      • 使用按名称装配时(byName)时,必须保证容器中具有指定名称的 bean,因变量名于配置耦合,不推荐使用
      • 自动装备配优先级低于 setter 注入和构造器注入,同时出现时自动装配失效。
      • 为 bean 指定 aotowire 属性
        1. byType:按照成员变量的类型自动装配
        2. byName:安装成一团变量名称和配置的 bean 的 id 相匹配装配
    • lazy-init:控制 bean 延迟加载
  • 内置标签,用于注入属性

    • property:注入 bean 的属性(使用 setter 方法注入,需要提供属性对应的 setter 方法)
      • name:属性名称
      • ref:参照哪一个 bean,取 bean 配置的 id
      • value:对于基础数据类型(int,char, String),使用 value 指定注入值(一定要提供可访问的 setter 方法)
    • constructor-arg:注入 bean 的属性,(使用构造函数注入,需要定义构造方法,本质是指定构造函数的参数的入参)
      • name
      • ref
      • value 三个参数的含义同上
      • type:指定注入的参数的类型(例如type指定为 java.lang.String,注入值就会在参数列表中找一个参数类型是 String 注入)
      • index:指定注入给第几个参数
    • setter 注入和构造器注入都可以完成属性的注入,spring 推荐使用构造器的注入方式,构造器注入的成员变量是必要的,所以必须要注入的可以使用构造器注入,而可注入可不注的可以使用 setter 注入
    • 自己开发的模块建议使用 setter 注入
  • 数组,集合的注入

<bean>
<!--    1. 数组的注入-->
    <property name="array">
        <array>
            <value>100</value>
            <value>200</value>
        </array>
    </property>
<!--    2. 集合的注入-->
    <property name="list">
        <list>
            <!-- 简单类型 -->
            <value>Lee</value>
            <value>Noco</value>
            <!-- 引用类型 -->
            <ref bean="beanId" />
        </list>
    </property>
<!--    3. set 注入-->
    <property name="set">
        <set>
            <value>Lee</value>
            <value>Noco</value>
        </set>
    </property>
<!--    4. map 注入-->
    <property name="map">
        <map>
            <entry key="name" value="Lee" />
            <entry key="age" value="16" />
        </map>
    </property>
<!--    5. Properties 对象注入 -->
    <property name="map">
        <props>
            <prop key="name">Lee</prop>
            <prop key="age">18</prop>
        </props>
    </property>
</bean>
  • IoC容器管理其他框架中的类实例

    • 先查看对应的类有没有满足要求的构造函数,有的话可以使用构造函数注入
    • 再查看对应的类有没有为成员变量提供 setter 方法,有的话可以使用 setter 注入
  • Bean 的实例化

    1. 使用可访问的构造方法来创建 bean 对象,默认调用无参的构造方法,如果没有提供无参的构造方法会出现异常
    2. 使用静态工厂来实例化 bean
    • class:指定静态工厂的类名
    • factory-method:指定实例化 bean 的工厂方法
    1. 使用实例工厂来实例化 bean
    • 先定义一个 bean 作为实例工厂
    • 再使用 factory-bean 指定实例工厂 bean
    • factory-method:指定实例工厂的实例化方法
    1. 创建一个工厂类实现实现 spring 提供的 FactoryBean 接口
    • void getObject():返回指定类型的对象
    • boolean isSingleton():返回 false 代表非单例,true 代表单例
    • 在配置文件的 bean 配置中指定 class 为该工厂类
  • Bean 的生命周期

    1. Bean 的生命周期
    • 初始化容器
      1. 创建对象(分配内存)
      2. 执行构造方法
      3. 执行属性的注入 (set 操作)
      4. 执行 Bean 的初始化方法
    • 使用 bean
      1. 执行业务操作
    • 关闭/销毁 bean
      1. 执行 bean 的销毁方法
    1. 通过配置文件,在 bean 标签上配置
    • init-method:指定 bean 创建的钩子函数
    • destroy-method:指定 bean 销毁的钩子函数
    1. 通过实现 spring 提供的 InitializingBean, DisposableBean 接口实现
    • void destroy():来自 DisposableBean,提供了销毁的生命周期钩子
    • void afterPropertiesSet():来自 InitializingBean,提供了初始化的生命周期钩子
  • 加载 properties 文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 在配置文件头部中的5个 context -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
      ">
    <!-- 指定使用 properties 文件 -->
    <!--
        system-properties-mode:指定系统的环境变量是否生效
        在使用 properties 文件是,系统的环境变量优先级高于我们自己创建的 properties 文件
        例如:我们在 jdbc.properties z中定义一个 username 的变量,而系统环境变量中也存在 username 的变量,最终使用的就是系统的变量
        
        location:指定 properties 文件,加载多个文件可以使用,隔开
          使用通配符加载所有的 properties 文件:*.properties
          只加载当前工程中的所有 properties 文件:classpath:*.properties
          不仅可以从当前工程中读取,还要测评所依赖的 jar 包中读取:classpath*:*.properties
    -->
    <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER" />
    
    <!-- 使用占位符 ${属性名} -->
    <bean id="bookDao" class="com.lmb.dao.impl.BookDaoImpl">
        <property name="driver" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.password}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
</beans>

注解式开发

在纯注解开发中,不再使用 applicationContext.xml 的配置文件,而是改为创建一个配置类 SpringConfig.java 来开发,通过定义类的成员变量配合注解实现替换配置文件

使用注解开发首先要在配置文件 applicationContext.xml 中配置以下

  • <context:component-scan base-package="com.lmb.dao.impl" />
    • base-package:代表需要扫描的,只有在所注册的包中的类注解才会生效
  1. 常用的注解
  • @Component("BeanId"):标记一个 bean,参数为设置 Bean 的 id
    • 当没有填写参数时,不能使用 id 来加载 bean,改用对应类的类名来加载 bean
  • @Repository("BeanId"):注册一个数据层的 Bean
  • @Service
  • @Controller
  • @Bean:标记一个方法的返回值为一个 Bean,通常用来管理第三方的对象
  • @ComponentScan("package"):定义扫描路径,此注解只能加一个,多个数据请使用数组的形式
  • @Scope("prototype"/"singleton"):指定 Bean 为单例或者多例
  1. 配置注解
  • @Configuration:设定当前类为配置文件,替代 applicationContext.xml 配置文件
  • @PropertySource("配置文件"):加载 properties 配置文件
    • 多个 properties 文件使用数组的格式
    • 不支持使用通配符 * 匹配
  • @Import(子配置类.class)
    • 引入子配置类,从而可以将写多个配置类文件
    • 引入多个配置类使用数组 { Config1.class, Config2.class }
  1. 生命周期方法注解
  • @PostConstruct:Bean 初始化,在构造函数之后执行
  • @PreDestroy:Bean 销毁之前执行

4依赖注入

  • @Autowired:按类型自动注入,使用该注解可以不需要自己写 setter 方法,自动装配基于反射机制创建对象并暴力反射对应的属性为私有属性初始化数据,交易提供无参的构造方法
  • @Qualifier("BeanId"):如果存在相同类型的 Bean,使用该注解指定 Bean 的 id 注入
    • 该注解依赖 @Autowired,所以应该配合使用
  • @Value(value):可以进行简单数据类型的注入
  1. 在 properties @PropertySource("文件地址")文件中配置的属性如何在 Bean 中使用
  • 在配置类中先使用 配置 properties 文件
  • 在 Bean 中使用 ${属性名} 即可引用
  1. 管理第三方 Bean
  • @Bean:标记一个方法的返回值为一个 Bean
    • 在配置我呢见写一个方法用于创建和初始化第三方对象并返回该对象
    • 加上 @Bean 注解,将返回的对象作为一个 Bean。
    • 第三方 Bean 的依赖注入
      1. 引用类型:方法形参, spring 会根据形参的类型自动装配
      2. 简单类型:成员变量
package spring;

import com.alibaba.druid.pool.DruidDataSource;
import com.lmb.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class Jdbc {
    /* 简单类型,使用成员变量注入 */
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Override
    public String toString() {
        return "Jdbc{" +
                "driver='" + driver + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    @Bean("dataSource")
    public DataSource dataSource(BookDao bookDao) {
        System.out.println(this);
        // 将已注册的 Bean BookDao 作为形成,IoC 容器将会自动注入
        System.out.println(bookDao);

        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

}

spring 整合 MyBatis 框架

  1. Mybatis 提供了 mybatis-spring 包用于给 spring 整合 MyBatis。同时还需要引入 spring-jdbc 包
<!-- spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.22</version>
</dependency>

<!-- mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>
  1. 添加一个 MyBatisConfig.java 类用于整合 MyBatis 框架
package config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/**
 * @author Lenovo
 */
public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setTypeAliasesPackage("com.lmb.domain");
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.lmb.dao");
        return msc;
    }
}
  1. 在 dao 层中新建 Mapper.xml 和 Mapper.java,按照 MyBatis 的方式,即可操作数据库

Spring 中整合 Junit 测试框架

  1. 引入所需的依赖,分别为 junit 和 spring-test
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.22</version>
</dependency>
  1. 创建测试类
  • @RunWith(SpringJUnit4ClassRunner.class):设定运行器类,这是 spring 运行 junit 的装用类运行器
  • @ContextConfiguration(classes = SpringConfig.class):告诉 junit 需要使用 spring 配置类
package service;

import com.lmb.service.UserService;
import config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testFindById() {
        System.out.println(userService.findById(1));
    }
}

AOP(面向切面编程)

AOP:Aspect Oriented Programming,一种编程范式,指导开发者如何组织程序结构。 OOP:

  1. 作用
  • 在不惊动原始设计的基础之上为其进行功能的增强

基础概念

  1. 连接点
  • Join Point:程序执行过程中任意位置,粒度为执行方法,抛出异常,设置变量等
  • 在 Spring AOP 中理解为:方法的执行
  • 连接点包含切入点
  1. 切入点
  • PointCut:匹配连接的式子
  • 在 Spring AOP 中一个切入点可以描述一个具体的方法,也可以匹配多个方法。
  1. 通知
  • Advice:在切入点处执行的操做,也就是共性功能
  1. 通知类
  • 定义通知的类
  1. 切面
  • Aspect:描述通知与切入点的对应关系
// 要执行的主程序
public class Main {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i ++) {
            System.out.println("console 1");  // 连接点
        } 
        long endTime= System.currentTimeMillis();
        System.out.println("万次输出时间为:" + (endTime - startTime));
    }
    
    // 切入点
    // 该方法是要放进通知中执行的方法,称为:切入点
    public void console2() {
        System.out.println("console 2"); // 连接点
    }
    
    // 切入点
    public void console3() {
        System.out.println("console 3"); // 连接点
    }
    
}

// 对于上面的执行代码来说,我们是要分别测试输出10000次 "console 1"/"console 2"/"console 3" 需要的总时间
// 可以将公共的代码提取出来成一个方法,公共的方法称为:通知
// 定义通知的类称为通知类
class Notice {
    // 通知
    public void notice() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i ++) {
            // 这里调用原始操作
        }
        long endTime= System.currentTimeMillis();
        System.out.println("万次输出时间为:" + (endTime - startTime));
    }
}

/*
 * 切面:描述通知和切入点的关系
 * 这个通知有那些切入点,这些切入点的连接点在哪里?
 * */

如何进行 AOP 开发

  1. 导入坐标(pom.xml)
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
  1. 在配置类中添加 @EnableAspectJAutoProxy 注解告诉 spring 我们需要使用 AOp 开发
package com.lmb.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.lmb")
@EnableAspectJAutoProxy
public class SpringConfig {

}
  1. 制作连接点方法(原始操作,Dao接口和实现类)
  2. 制作共性功能(通知和通知类)
  3. 定义切入点
  4. 绑定切入点和通知的关系

AOP 的工作流程

  1. Spring 容器启动
  2. 读取所有的切面配置中的切入点
  3. 初始化 Bean,判定 bean 对应的类中的方法是否匹配到任意的切入点
  • 匹配失败,创建对象
  • 匹配成功,创建原始对象(目标对象)的代理对象
  1. 获取 bean 的执行方法
  • 获取 bean,调用方法并执行,完成操作
  • 获取 bean 是代理对象时,根据代理对象的运行模式运行原始方法和增强的内容,完成操作。

常用注解

  1. @EnableAspectJAutoProxy
  • 告诉 spring 我们需要使用 AOP 开发
  1. @Aspect
  • 定义通知类受 Spring 容器管理,并定义当前类为切面类
  1. @Pointcut("execution(void com.lmb.dao.BookDao.update())")
  • 定义一个切入点
  • 需要定义一个私有的无返回值的函数,且该函数为空方法体
  1. @Before("切入点方法名称()")
  • 绑定切入点和通知的关系,标识该通知将在切入点之前先执行

AOP 切入点表达式

  1. 什么是切入点 要增强的方法
  2. 切入点表达式 要进行增强方法的描述
  • 标准格式:动作关键字 (访问修饰符 返回值 包名.类/接口.方法(参数) 异常名)
    • 访问修饰符 和 异常名 可以省略
  • 按接口中无参的 update 方法描述:execution(void com.lmb.dao.BookDao.update())
  • 按实现类中的无参的 update 方法描述:execution(void com.lmb.BookDaoImpl.update())
  • 通配符描述
    1. 通配符 *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的通配符出现
    • execution(public * com.lmb..UserService.find(*) )
    1. 两个点 ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
    • execution(public User com..UserService.findById(..))
    1. +:专用于匹配子类类型
    • execution(* *..Service)+.(.. ))
  1. 表达式书写的常用技巧
  • 所有的代码按照标准规范开发,否则以下的技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用 public 描述(可省略访问控制修饰符)
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用 * 通配符快速描述
  • 包名书写尽量不使用 .. 匹配,效率过低,常用 * 做单个包描述匹配,或精准匹配
  • 接口名/类名/书写名称与模块相关的采用 * 匹配,例如 UserService 书写成 *Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用 * 匹配,例如 getById 书写成 getBy*,selectAll 书写成 selectAll
  • 参数规则比较复杂,根据业务方法灵活调整
  • 通常比使用异常作为匹配规则
  1. 切入点参数获取
  1. AOP 的通知类型
  • 前置
    • @Before("切入点()")
  • 后置
    • @After("切入点()")
  • 环绕 (重点)
    • @Around("切入点()")
    • 环绕通知必须依赖 ProceedingJoinPoint 形参才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
    • 通知中如果未使用 ProceedingJoinPoint对原始方法的调用将跳过原始方法的执行
    • 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置为 void,如果接受返回值,必须设定为 Object 类型
    • 由于无法确定原始方法是否会抛出异常,因此环绕通知方法必须抛出 Throwing 对象
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAdvice() {
    @Pointcut("execution(String com.lmb.dao.*Dao.select(int))")
    private void pt2() {
    }

    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint joinPoint) {
        System.out.println("before select");
        Object ret = proceedingJoinPoint.proceed();
        System.out.println("after select");
        return ret;
    }
}
  • 运行方法运行成功之后再执行通知方法
    • @AfterReturning("切入点()")
    • 和 @After 的区别是只有当切入点执行成功,没有抛出异常之后才会执行。
  • 抛出异常之后才执行通知方法
    • @AfterThrowing
    • 如果一个切入点正常执行完成没有抛出异常,那么这个通知方法将不会执行
  1. 获取通知数据
  • ProceedingJoinPoint 类上有一个 getSignature 方法,会返回一个 Signature 对象

    • 使用于环绕通知
    • signature.getDeclaringType()):获取切入点的全限定类名
    • signature.getName():获取切入点的名称
  • JoinPoint 是 ProceedingJoinPoint 的接口

    • 适用于除了环绕通知之外的所有通知类型
  • ProceedingJoinPoint 和 JoinPoint 参数在通知方法中必须是第一个参数

  • 获取切入点方法的参数(在通知方法中注入参数)

    1. 环绕通知
    • 在通知方法中注入 ProceedingJoinPoint 参数
    • 通过 Object[] ProceedingJoinPoint.getArgs() 获取一个参数数组
    • 在执行 ProceedingJointPoint.proceed() 时,可以将获取的参数传递进去,或者对原有的参数做一些改造之后传递给 proceed
    • ProceedingJointPoint.proceed() 时默认使用原本的参数组
public class MyAdvice() {
    @Pointcut("execution(void com.lmb.dao.*Dao.update(..))")
    private void pt() {}
    
    @Around("pt()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("before");
        
        Object[] args = proceedingJoinPoint.getArgs();
        // 对参数做一些自己的修改之后,再传递给 proceed
        args[0] = new User(2, "Lee", "15");
        // 标识对原始操作的调用
        proceedingJoinPoint.proceed(args);
        
        System.out.println("after");
    }
}
2. 其他通知
  + 使用 JoinPoint 接口,同样是调用 Object[] JoinPoint.getArgs()
  • 获取切入点方法的返回值
    1. 返回后通知
    • 在通知方法中注入一个 Object 参数 ret
    • 在 @AfterReturning() 使用 returning 标记返回的参数注入给 ret
public class MyAdvice {
    @AfterReturning(value = "pt2()", returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning");
        System.out.println("切入点返回值:" + ret);
    }
}
2. 环绕通知
  + 环绕通知获取返回值就是执行 Object ret = proceedingJoinPoint.proceed(args) 获取的返回结果。
  • 获取切入点方法运行异常信息
    1. 抛出异常后通知
    • 在通知方法中注入一个参数 Throwable t 用来接收切入点的异常信息
    • 在 @AfterReturning() 使用 throwing 标记返回的参数注入给 t
    • 不能和返回值一起存在,抛出异常了怎么可能有返回值?
public class MyAdvice {
    @AfterReturning(value = "pt2()", throwing = "t")
    public void afterReturning(Object ret, Throwable t) {
        System.out.println("afterReturning");
        System.out.println("异常信息:" + t);
    }
}
2. 环绕通知
  + 环绕通知中抛出的 Throwable 改为 try catch 的写法,在 catch 中获取到异常做相应的处

事务管理

事务的作用就是要保障数据层的一系类的数据库操作同时成功或者同时失败 Spring事务作用就是要保障在数据层或者业务层中的一系列数据库的操作成功或者失败

会出发回滚的异常

并不是所有的异常哦都会出发回滚机制 只有 Error 和运行时异常(NoFoundException,NullPointException ...)会触发回滚机制,其他的异常 其他的异常不会出发回滚机制

Spring 中事务管理如何实现

Spring 提供了一个事务管理接口 PlatformTransactionManager,和一个实现类 DataSourceTransactionManger

import org.apache.ibatis.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;

public interface PlatformTransactionManager {
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

public class DataSourceTransactionManger implements PlatformTransactionManager {
    ...
}

事务编程步骤

  1. Spring核心配置类中开启事务管理
  • @EnableTransactionManagement
  • 将上注解加到 spring 配置类中
  1. 注册 Spring 事务管理器的 Bean
  • 在 JdbcConfig.java 的 JDBC 配置类中添加一个 @Bean
public class JdbcConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

  1. 开启事务管理
  • @Transactional
  • 在要做事务管理的方法上使用 @Transactional 注解
  • 通常在接口的方法中定义中使用该注解,当然也可以在实现类方法中使用该注解
  • 也可以直接写在整个接口上,这样代表对该接口内的所有方法都开启事务管理。

事务管理员和事务协调员

  1. 事务管理员 发起事务方,在 spring 总通常代指业务开启事务的方法
  2. 事务协调员 加入事务方,在 spring 中通常代指数据层方法,也可以是业务成方法

@Transactional事务的相关配置

@Transactional(readOnly=true, timeout=-1,...)

  1. readOnly:设置是否是只读事务 readOnly=true 只读事务
  2. timeout:设置事务的超时时间,timeout=-1:永不超时
  3. rollbackFor:设置事务的回滚异常(class),rollbackFor=NullPointException.class,事务遇到该异常就会回滚
  4. rollbackForClassName:设置事务回滚异常(class)
  5. noRollbackFor:设置事务不回滚异常,事务即使遇到该异常也不会回滚
  6. noRollbackForClassName:设置事务不回滚异常
  7. propagation:设置事务传播行为
  • 传播属性 事务管理员 事务协调员
  • REQUEST(默认): 开启/无 加入/新建
  • REQUEST_NEW: 开启/无 新建/新建
  • SUPPORTS 开启/无 加入/无
  • NOT_SUPPORTED 开启/无 加入/无
  • MANDATORY 开启/无 加入/ERROR
  • NEVER 开启/无 ERROR/无
  • NESTED:设置 savepoint,一旦事务回滚,事务将回滚到 savepoint,由用户相响应提交/回滚