认识SSM框架----Spring

前言

为什么学Spring技术:

  1. 简化开发。降低企业及开发的复杂性
  2. 整合框架。高效整合其他技术,提高开发效率

学哪些东西:

  1. Spring Framework Spring全家桶中的技术都依赖Spring框架
  2. SpringBoot 在简化开发的基础上加速开发,写更少的东西
  3. SpringCloud 分布式开发的相关技术

Spring发展史

  1. Spring1.0 纯配置方式
  2. Spring2.0 为加速开发引入注解
  3. Spring3.0 可以不写配置的开发模式
  4. Spring4.0 对部分API做了调整
  5. Spring5.0 Spring框架全面支持JDK8

Spring框架

系统架构

在这里插入图片描述
红色数字为建议学习顺序

SPring框架中有两个核心理念:
IoC(Inversion of Control控制反转)和 AOP(Aspect Oriented Programming面向切面编程
AOP下又衍生出了Spring独到的事务处理

一、Core Container核心容器

现状

代码书写时耦合度过高,当数据层实现想添加新功能时,业务层的数据层对象也得跟着变,整个项目又要重新遍历、测试、发布

解决方案:

使用对象时,不主动new,转换为由外部提供

这就是IoC(Inversion of Control控制反转)的思想,对象的创建控制权由程序转移到外部

IoC 控制反转

Spring技术实现了IoC思想。Sping提供了一个容器,称为IoC容器,用来充当IoC思想中的外部
IoC容器负责对象的创建、初始化等工作,被创建的对象在IoC容器中统称为Bean

使用流程

XML版

  1. 在maven项目配置文件pom.xml中导入spring的坐标,解决依赖

  2. 解决依赖后,在resources目录下新建一个Spring config文件

  3. 在spring config文件中配置bean

    id:bean名,唯一       class:bean的类型,全路径类名
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    
  4. 在程序中获取容器,然后通过容器获取bean

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    

DI 依赖注入

代码运行时不同Bean之间可能存在依赖关系,如Service对象会依赖dao对象
为了解决这个问题,容器自动建立bean与bean间的依赖关系,这个建立依赖的过程称为依赖注入
如A对象中依赖B对象,当我们需要用A对象时,容器会自动为我们创建所需的B对象,我们只需要请求A对象即可

使用流程

XML版

  1. 删除service中使用new形式创建对象的代码,只留对象声明

  2. 在service中提供对应对象的set方法,参数为传入对象类型,供容器传入bean

  3. 在Spring的xml中配置service与dao的关系,将被依赖的bean写到service的bean配置中

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
         <property name="bookDao" ref="bookDao"/>
    </bean>
    

IoCDI的目标就是充分解耦,最终使用对象时可以直接从容器中获取,且获取到的bean已经绑定了依赖关系

Bean

基础配置
  • 给bean起别名

在pom.xml文件中的< bean> 标签中使用name属性,多个名称可以用“,” “;“ “ ”隔开

  • bean的作用范围

spring 默认创建的bean是单例的(scope属性默认为singleton),即无论获取多少对象,只要bean名相同,都指向同一个bean。若想要非单例的,只需要设置bean标签中的scope属性值为prototype
单例模式可以减小容器压力
适合交给容器管理的bean:可复用的,如表现层、业务层、数据层对象
不适合容器管理的bean:封装实体的域对象,要记录状态的对象

实例化方式(3种)
  • 使用构造方法

spring通过调用类的无参构造方法创建bean

  • 静态工厂

定义一个A工厂类,其中定义一个静态方法以new的方式返回一个A的bean,通过直接调用工厂类的方法去获取A的bean

如何通过spring托管呢?
在spring的xml文件中配置工厂类的bean标签,设置factory-method方法值为工厂类中返回bean的那个方法名
这样通过容器获取工厂类的bean时就会自动执行factory-method对应的方法返回A的bean

  • 实例工厂

定义一个A工厂类,其中定义一个非静态方法以new的方式返回一个A的Bean
由于是非静态方法,我们需要先得到工厂类的bean,然后通过工厂bean执行非静态方法去获取A的bean

若要通过spring托管
首先在spring的xml文件中配置工厂类的bean标签,然后配置A的bean标签,其中需要设置factory-bean属性为工厂类bean的id(指向工厂bean),factory-method属性值为工厂类返回A的bean的方法名

  • FactoryBean方式(实例工厂的格式优化)

该方法和实例工厂相似,但是简化了spring的配置过程,统一了返回A对象的方法名,不需要再配置两个bean标签

首先还是定义一个A工厂类,但是这个工厂类要实现FactoryBean接口,指定泛型类型(即A的类)并重写getObject和getObjectType两个方法,分别返回A的bean实例和A的class字节码,可选重写isSingleton方法,返回true表示单例模式,false表示非单例
然后配置spring的xml,bean标签的class设置为A工厂类即可,然后通过id获取bean时就会得到A的bean

生命周期

生命周期:从创建到销毁的整个过程
生命周期控制:在bean创建后到销毁前做一些事情

Bean的生命周期:

  1. 初始化容器
  • 创建对象(内存分配)
    执行构造方法
    执行属性注入(set操作)
    执行bean初始化方法
    
  1. 使用bean
  • 执行业务操作
    
  1. 关闭容器
  • 执行bean销毁方法
    

控制流程

  • 在bean的对应类中声明两个方法(一般为init和destroy),分别用于bean创建前执行和销毁后执行
  • 在spring的配置文件中设置对应bean标签的init-methoddestroy-method属性值为两个方法名,这样在容器创建和销毁bean前就会自动执行对应方法

Spring标准化版控制流程(接口控制)

  • bean的对应类实现两个接口:InitializingBean, DisposableBean,并重写afterPropertiesSet()和destroy()两个方法,分别在加载bean之前和销毁bean之前执行
  • 在spring配置文件中只需要配置对应bean标签中的id、class等属性即可,不需要再额外配置init-method和destroy-method属性

有时候程序执行完成后jvm直接退出,不给bean销毁的机会,如何执行destroy方法呢?有两种方式

  • 手动关闭容器 调用ClassPathXMLApplicationContext类对象的close方法暴力关闭容器
  • 设置关闭钩子 获取容器对象ctx后调用其registerShutdownHook()方法注册关闭钩子,就是说告诉虚拟机在退出之前先把容器关了
  • 注意!!!!!ApplicationContext类的对象没有注册关闭钩子和容器关闭的方法。要用该类的子类对象

依赖注入

–setter注入–

引用类型:在bean类中声明引用类型变量,提供该变量的set方法,在Spring的配置文件中的"< bean>< /bean>"间利用property标签的name属性指定变量名,ref属性指定变量对应的bean, 依赖将通过参数将引用类型实例传入。当有多个引用类型变量需要注入时,在bean类中提供每个变量的set方法,通过写多个property标签实现多次注入

普通类型:步骤与引用类型注入相同,区别在于引用类型注入通过ref属性指定注入的bean,而普通类型注入通过value属性指定要注入的值

–构造器注入–

引用类型:在bean类中创建带参的构造方法,参数为要注入的bean。然后在Spring的配置文件中的"< bean>< /bean>"间利用constructor-arg标签的nameref属性配置构造器的参数。若有多个参数则写多个constructor-arg

普通类型:步骤与引用类型注入相同,区别在于引用类型注入通过ref属性指定注入的bean,而普通类型注入通过value属性指定要注入的值

上述方式其实耦合度很高,当构造方法的参数名改变了之后,spring配置文件中constructor-arg标签中name属性的值也要变,这违背了spring解耦的初衷。
因此我们可以不通过name属性确定传给哪个参数,参数名可能变,但是参数类型不会变,因此可以利用指定type属性的值,按形参类型注入。

又有问题了:如果参数类型重复呢?参数的位置次序不会变吧,可以利用指定index属性的值,按形参位置注入。

注入方式的选择

什么时候用setter注入,什么时候用构造器注入?

强制依赖用构造器注入,setter注入有概率不进行注入导致null对象出现
可选依赖用setter注入,灵活性强,不配置就不注
必要时可以 构造器注入+setter注入
如果要管理的bean没有提供setter方法,必须用构造器注入
自己开发的模块推荐使用setter注入

–自动装配–

什么是自动装配:IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程,不用再写< property>和< constructor-arg>进行配置了

配置流程:
bean类中依赖对象的set方法得有,然后通过设置spring配置文件中bean标签的autowire属性值设置自动装配,该属性的值有两种:

  • byName按名称:根据bean类中set方法名(去掉set剩余的部分)和spring配置文件中被依赖bean的id匹配来进行依赖注入。需要被依赖对象的bean标签必须有id属性
  • byType按类型:根据类型匹配来实现注入,set方法得有,容器会根据依赖的类型自动在容器中找有没有对应的bean,然后进行setter注入。注意,此时spring配置文件中有且只能有一个依赖的bean,否则会找到多个可用bean而报错

自动装配的特征

  • 只能装配引用类型,不能操作简单类型
  • 按类型装配,要求容器中相同类型的bean唯一 推荐使用
  • 按名称装配,要求容器中指定名称的bean必须存在,但变量名和配置耦合,不推荐
  • 依赖注入的优先级,setter注入和构造器注入 > 自动装配,同时出现自动装配失效
集合注入

引用类型和普通类型的注入实现了,集合类型怎么注入呢?

总的来说需要以下几步:
在spring的xml配置文件中的双目bean标签内通过property标签的name属性指明要注入的集合变量名
在property标签内用对应集合类型的标签指明集合变量类型
在集合类型标签内用多个标签设置集合内的元素

  1. 数组
    <property name="array">    <!-- 设置要注入的bean中的变量名 -->
        <array>    <!-- 指明是数组类型 -->
            <value>100</value>    <!-- 设置数组内的元素 -->
            <value>200</value>
            <value>300</value>
        </array>
    </property>
    
  2. List
    <property name="array">
        <list>    <!-- 指明是List类型 -->
            <value>100</value>    <!-- 设置数组内的元素 -->
            <value>200</value>
            <value>300</value>
        </list>
    </property>
    
  3. Set
    <property name="array">
        <set>    <!-- 指明是set类型 -->
            <value>itcast</value>      <!-- 设置数组内的元素 -->
            <value>itheima</value>
            <value>zjh</value>
            <value>zjh</value>      <!-- set标签会自动过滤重复元素 -->
        </set>
    </property>
    
  4. Map
    <property name="array">
    	<map>
    		<entry key="country" value="china"/>
            <entry key="province" value="gansu"/>
            <entry key="city" value="lanzhou"/>
        </map>
    </property>
    
  5. Properties
    <property name="array">
        <props>
    	    <prop key="country">china</prop>
    	    <prop key="province">gansu</prop>
    	    <prop key="city">lanzhou</prop>
        </props>
    </property>
    
管理第三方bean(拿到别人的东西应该怎么思考)

现在我们用spring管理一个c3p0的连接池对象(完全没见过的如何分析管理)

  1. 获取c3p0的坐标。去maven repository里去查,选择对应版本并复制其坐标,到pom.xml进行配置
  2. 获取c3p0的类型。去搜索引擎搜索,或者到spring配置文件中写一个bean标签,在class属性中输一部分内容,根据提示找到目标类型
  3. 配置对象的依赖。如c3p0是一个数据库连接池的对象,一般需要驱动、url、用户名和密码,试着去看看c3p0中对应的依赖变量叫啥名,进行配置
  4. 可能会报错提示没找到啥啥啥的,可以去maven repository中找对应模块的坐标导入
加载properties文件

像用户名、密码等信息,我们一般不直接写死在spring的配置文件中,而是单独写一个property文件记录,spring是如何使用properties文件的呢

  • 加载properties文件。

    1. 在spring配置文件中开启新命名空间(假设名为context)
      复制下图中白框中的内容得到红框中的内容,注意标注出的红框与白框中不相同的部分
      在这里插入图片描述

    2. 使用context空间加载properties文件
      在这里插入图片描述
      若有多个properties文件,可以用逗号分隔多个文件名。也可以用通配符的形式全部加载classpath*: *.properties,其中classpath表示类路径,意味着加载当前工程及其依赖jar包中的properties文件
      若properties文件中变量名称命名不规范可能会与系统变量冲突,系统变量优先级会高。为了避免咱们的配置失效,可以在加载properties文件时设置system-properties-modeNever,表示不加载系统属性
      在这里插入图片描述

  • 将properties文件中的配置信息替换原来的属性注入
    在这里插入图片描述

容器相关

  1. 创建容器
  • 加载类路径下的配置文件, 自动去resources目录下去找

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  • 加载文件路径下的配置文件必须写配置文件的绝对路径

    ApplicationContext ctx = new FileSystemXmlApplicationContext("S:\\tech\\SSM\\Spring_10_container\\src\\main\\resources\\applicationContext.xml");
    
  1. 获取bean
  • 使用bean名称,并强转类型

    BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
  • 使用bean名称获取时指定bean的类型

    BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
    
  • 使用bean类型获取(此时容器中的同类型bean要唯一)

    BookDao bookDao = ctx.getBean(BookDao.class);
    
  1. 容器接口层次结构
    在这里在这里插入图片描述
插入图片描述
    Spring中有很多方法能够构建IoC容器,我们用的ApplicationContext接口是其一。其中最顶层的接口是BeanFactory接口。
    二者有一个区别是:BeanFactory默认是延迟加载bean,而ApplicationContext默认是立即加载bean。
    延迟加载bean是指容器构建好后并不创建bean的实例,而是获取时时候容器才给你创建;立即加载bean是容器构建好后立即创建bean的实例,需要时直接拿
    如何设置特定bean的延迟加载呢?在对应的bean标签里设置lazy-init标签值为true即可

注解开发

注解开发是Spring简化开发的重要手段
Spring2.0开始提供各种各样的注解,到2.5的时候注解比较完善了
Spring3.0提供了纯注解开发

定义bean
  1. 在bean类的上面用@Component定义bean

    @Component("bookDao")
    public class BookDaoImpl implements BookDao {}
    @Component
    public class BookDaoImpl implements BookDao {}
    
    Component后括号内为bean的名称,相当于spring的xml中bean标签中的id,可以省略,省略的话通过容器获取bean只能通过类型
    
  2. 在spring配置文件中通过组件扫描加载bean

    <context:component-scan base-package="com.itheima"/>
    
  3. 衍生注解
    项目中所有bean都用@Component定义缺乏区分度,因此spring提供三个衍生注解,其功能和用法与@Component一样

    @Controller    用于表现层bean定义
    @Service    用于业务层bean定义
    @Repository  用于数据层bean定义
    
纯注解开发模式
一点配置都不写了,Spring3.0使用Java类代替配置文件,使用配置类上方的各种注解的形式去描述原来的配置文件中的信息
  1. 用配置类替代原来的Spring配置xml文件
    @Configuration注解 设定当前类为配置类
    @ComponentScan注解 设定bean的扫描路径,此注解只能添加一次,多个数据用数组格式{name1, name2, …}

    @Configuration
    @ComponentScan({"com.itheima.dao", "com.itheima.service"})
    public class SpringConfig(){}
    
  2. 读取spring xml文件初始化容器对象 换为 通过java配置类初始化容器对象

    // 加载配置文件初始化容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 加载配置类初始化容器
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    
bean管理
作用范围

用类外@Scope注解控制bean是否为单例,singleton为单例,prototype为非单例

生命周期

在类内、方法外用注解标记
用@PostConstruct注解标记init方法,在构造函数后自动执行
用@Predestroy注解标记destroy方法,在对象销毁前执行

依赖注入

为加速开发,这里只用自动装配实现依赖注入

  1. 引用类型对象的注入

对于类内的被依赖对象,用@Autowired注解标记该对象进行自动装配,容器会byType自动装配。与配置自动装配中的利用setter方法完成注入不同,注解的自动装配基于反射创建对象,并暴力反射初始化私有属性,因此不需要setter方法,但需要有无参构造方法

但是这样有个问题,当用注解标记了多个同类型的bean时byType会冲突报错,此时应该在@Autowired下面加上@Qualifier(“bean的id”)注解同时限定,按名称进行注入。注意@Qualifier注解依赖于@Autowired注解,不能单独使用

@Component
public class BookServiceImpl implements BookService{
	@Autowired
	private BookDao bookDao;
	@Autowired
	@Qualifier("bookDao")
	private BookDao bookDao1;
}
  1. 简单类型的注入

使用@value(“值”)注解实现简单类型的注入

@Repository("bd")
public class BookDaoImpl implements BookDao {
    @Value("zjh")
    private String name;

    @Value("18")
    private int age;

    public void save() {
        System.out.println("book dao save..." + name + age);
    }
}

这样写是不是感觉和直接给变量赋值没啥区别,这样好就好在可以从外部properties文件中读取数据,而不是把值写死在程序里
首先在Spring配置类上使用@PropertySource(“jdbc.properties”)注解加载外部文件,多个外部文件用数组格式{name1, name2, …},与配置xml加载properties不同,@PropertySource注解中的文件名不支持通配符*
在@Value标签里原来填值的地方用 ${} 从外部文件中取值

jdbc.properties文件中的内容:
name=zjh
age=18

@Configuration
@ComponentScan({"com.itheima.dao", "com.itheima.service"})
@PropertySource("jdbc.properties")
public class SpringConfig {
}

@Repository("bd")
public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;

    @Value("${age}")
    private int age;
}
第三方bean管理

使用@Bean注解将某个方法的返回对象定义为一个bean
在这里插入图片描述
简单类型注入:

利用成员变量的形式注入值

引用类型装配:

只需要为bean定义方法设置形参,容器会根据类型自动装配,前提是容器中有该类型的bean,注意用@ComponentScan扫描相应路径,

XML配置与注解配置的对比

在这里插入图片描述

二、AOP面向切面编程

AOP是一种编程范式(类似于OOP面向对象编程)
作用:在不修改原始设计的基础上为其进行功能增强,这是Spring倡导的一种理念:无入侵式编程

相关概念

连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。

  • Spring AOP中理解为可以追加功能的那些方法

切入点:匹配连接点的式子

  • Spring AOP中切入点可以描述一个具体方法,也可以匹配多个方法
  • 如BookDao接口中的save方法;所有get开头的方法、所有Dao结尾的接口中的任意方法

通知:在切入点处执行的操作,即共性功能

  • Spring AOP中以方法的形式呈现,定义该方法的类叫通知类

切面:描述通知和切入点的对应关系,相当于绑定通知和切入点

简单实现

目标:在每个方法执行前输出当前时间

  1. 导入依赖坐标:

    <dependencies>
    	<!-- 1. 导入aop开发依赖的包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
    
  2. 写业务逻辑代码

    package com.itheima.dao.impl;
    import com.itheima.dao.BookDao;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class BookDaoImpl implements BookDao {
        // 2. 写业务逻辑代码(连接点)
        public void update() {
            System.out.println("book dao update...");
        }
        public void delete() {
            System.out.println("book dao delete...");
        }
    }
    
  3. 编写通知,定义切入点,编写切面(绑定切入点和通知)

    package com.itheima.aop;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Component    // 告诉Spring容器将通知类管理上
    @Aspect    // 告诉Spring这是AOP,不是普通的bean
    public class MyAdvice {
        // 3. 编写共性功能(通知)
        // 5. 定义切面
        @Before("cut()")
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
        // 4. 定义切入点,通过Pointcut注解描述. 切入点方法本身无参无返无方法体内容
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void cut(){}
    }
    
  4. 告知Spring有用注解开发的AOP

    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy  // 告诉Spring有用注解开发的AOP,开启Spring对AOP注解的驱动支持
    public class SpringConfig {
    }
    
  5. 结果
    在这里插入图片描述

AOP工作流程(本质是代理模式)

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

AOP切入点表达式

切入点:要增强的方法
切入点表达式:要增强的方法的描述方式,可以面向接口方法,也可以面向实现类的方法

语法格式

在这里插入图片描述
动作关键字:描述切入点的行为动作,如execution表示执行到切入点时
访问修饰符:public、private等,可省略
返回值
包名.类名[接口名].方法名(参数)

通配符

可以使用通配符描述切入点,加速描述。常用的通配符有:

  1. * 匹配单个独立的任意符号
    如匹配com.itheima包下的任意包中的UserService类或接口中find开头的带一个参数的方法
    execution(public * com.itheima..UserService.find(*))
  2. . . 匹配多个连续的任意符号,常用于简化包名与参数
    如匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法
    execution(public User com. .UserService.findbyId(. .))
  3. + 专用于匹配子类类型
书写技巧
  1. 描述切入点,通常描述接口,不描述实现类(解耦)
  2. 访问控制修饰符对接口开发均采用public描述,一般省略
  3. 增删改方法的返回值用精准类型加速匹配(一般都是boolean),查询方法的返回值使用*快速描述(因为不同查询方法返回值的对象不一样)
  4. 包名 尽量不使用. .匹配,效率很低,常用*做单个包描述匹配
  5. 接口/类名与模块相关的用*匹配,如UserService写成*Service
  6. 方法名动词进行精准匹配,名词采用*匹配,如findById写成findBy*

AOP通知类型

AOP通知共分为5种类型

  • 前置通知 @Before(“pc()”)
  • 后置通知 @After(“pc()”)
  • 环绕通知 @Around(“pc()”)
  • 返回后通知 @AfterReturning(“pc()”)
  • 抛出异常后通知 @AfterThrowing(“pc()”)
    ps:pc()是切入点对应的名称

后置通知和返回后通知的区别
后置通知方法停止(无论正常结束还是抛异常)后就执行,返回后通知只有在方法正常结束后才执行

环绕通知要点
环绕通知可以模拟其他所有类型的通知,但需要通过ProceedingJoinPoint参数在通知里对原始方法进行调用,以区分前后置通知,写法是固定的如代码所示;
如果不显示调用则只会执行通知不执行原操作(隔离原始操作,可以通过这个做权限校验)。

有的切入点还有返回值,因此环绕通知方法还要有一个Object类型的返回值。没有返回值的切入点会返回一个null
环绕通知方法必须抛出Throwable对象,因为无法预知原始方法会不会抛异常。

@Around("pc()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
	Signature signature = pjp.getSignature();  // 获取切入点方法一次执行的签名信息,其中封装了执行过程
    String classname = signature.getDeclaringTypeName();// 切入点所属类
    String methodname = signature.getName();// 切入点方法名

    System.out.println("around before advice");
    // 表示执行原始操作,用于区分前置通知和后置通知
    Object re = pjp.proceed();
    System.out.println("around after advice");
    return re;
    }

AOP通知获取数据

前面已经实现了对现有方法进行增强,但通常增强方案会根据情况的不同而变化,这就需要得到原始操作中的相关数据。

AOP通知可以获得原始操作中的三类数据:

  • 参数
  • 返回值
  • 异常

不是所有的通知都能拿到上面三种数据。

  • 参数。所有通知都能拿到。环绕通知最屌,获取参数后还能修改后再传入

    @Before("pc()")
    public void before(JoinPoint jp){
        Object[] args = jp.getArgs();  // 获取参数列表,封装在一个数组中
        System.out.println(Arrays.toString(args));
    }
    
    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;  //修改原始参数
        System.out.println("around before advice");
        // 表示执行原始操作,可以用于区分前置通知和后置通知
        // pjp的proceed方法可以传入一个数组,而这个数组可以作为切入点方法的参数值
        // 因此通过这个能力,可以对进行参数校验
        Object num = pjp.proceed(args);
        System.out.println("around after advice");
        return num;
    }
    
  • 返回值。只有后置通知和环绕通知能拿到,环绕通知中获取pjp.proceed()的返回值

  • 异常。只有抛出异常后通知和环绕通知能拿到,环绕通知中用try catch包围pjp.proceed()

三、Spring事务

事务作用:保障数据层操作的一致性。保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层业务层保障一系列的数据库操作同成功同失败

开启Spring事务

  1. 在业务层接口上用@Transactional注解开启事务
    为降低耦合,一般不在业务层实现类上开启事务,虽然也可以;
    也可以写在接口上,对接口中的所有方法都开启事务

    public interface AccountService {
        @Transactional  //1. 开启事务
        public void transfer(String in, String out, Double money);
    }
    
  2. 在config中配置一个事务管理bean

    @Bean  //2. 配置一个事务管理器
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        // Mybatis用的是jdbc的事务,因此这里注入一个JDBC的数据源
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
  3. 开启Spring的事务管理功能

    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("jdbc.properties")
    @Import({jdbcConfig.class, MybatisConfig.class})
    @EnableTransactionManagement    //3. 开启注解式事务驱动
    public class SpringConfig {
    }
    

Spring事务角色

Spring事务是通过什么机制实现的呢?事务合并!这是基于Spring事务管理器Mybatis共用同一个DataSource,否则没法搞
事务管理员:事务发起方,Spring中通常指业务层开启事务的方法
事务协调员:事务加入方,通常指代数据层方法,也可以是业务层方法

Spring事务将事务发起方中的子事务合并成为一个事务,从而实现事务管理

Spring事务配置

为了更精细化的管理事务,spring提供了若干配置项,可以在注解上开启这些配置项
在这里插入图片描述
Spring事务不是遇到异常就回滚,只有遇到运行时异常和errors类异常才回滚,这会导致不一致性问题(如遇到IO异常不回滚),因此需要手动添加回滚的异常类型

public interface AccountService {
    @Transactional(rollbackFor = {IOException.class})  //1. 开启事务
    public void transfer(String in, String out, Double money);
}

事务传播行为

有时候一个事务中的子任务不想受到其他子任务的限制,无论其他任务回滚不回滚,它都不想回滚。例如:在银行转账时,无论转账是否成功,都要记录转账信息,如果转账失败导致事务回滚,转账信息也就不能记录了。
因此,可以设置一个事务为事务协调员时(该方法在其他方法内被调用),不受事务管理员控制,单独开一个新事务

public interface LogService {
    // 设置当该事务为事务协调员时,不受事务管理员的控制,单独开一个新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out, String in, Double money, boolean flag);
}

@Transactional注解的propagation还有很多可选值
在这里插入图片描述

问题

Error:(2, 35) java: 程序包org.springframework.context不存在 问题的解决

循环依赖解决
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值