108-Spring的底层原理(下篇)

news2025/2/13 2:31:02
这里续写上一章博客(107章博客):
Spring 声明式事务的支持:
编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
声明式事务:通过xml或者注解配置的方式达到事务控制的⽬的,叫做声明式事务
事务回顾:
事务之所以出现,还是需要归于mysql的原因,任何通过语句操作mysql的,都是相同的操作,无论是使用mysql独有的可视化窗口,还是语言,还是cmd窗口(命令行窗口),都是如此,都是给对应的表(或者说引擎)所操作的,但是sql的诞生通常也会留出一些接口,使得sql与编程语言进行互通和数据的操作,这也是java的jdbc的由来,那么在这个基础上,就存在连接成功,以及执行语句的操作(一般我们称为语句平台)
事务的概念:
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功,从而确保了数 据的准确与安全
例如:A向B转帐,对应于如下两条sql语句:
/*转出账户减钱*/
 update account set money=money-100 where name='a';
 /**转⼊账户加钱*/
 update account set money=money+100 where name='b';
对于事务来说,这两条语句的执行,要么全部成功,要么全部不成功,这种机制的原理在于:
MySQL在事务中执行的SQL操作结果会被记录在事务日志中,事务日志(Transaction Log)是MySQL中的一种机制,用于记录数据库的变更操作,包括插入、更新和删除等,一般来说事务的操作都是修改真正的表数据的,而之所以存在事务日志是为了可以进行回滚,以及其他的操作,主要是回滚的
当执行一个事务时,MySQL会将事务中的每个操作都记录在事务日志中,而不是立即将其应用到实际数据文件中,这个过程被称为"写日志"(write-ahead logging)
在事务提交之前,如果需要回滚事务,MySQL可以使用事务日志中的信息来撤销已经执行的操作,将数据恢复到事务开始前的状态,这意味着事务日志可以用来反向执行操作,实现事务的回滚
撤销日志记录(Undo Log Record):在执行一个事务中的更新或删除操作时,MySQL将相应的写入事务日志的消息拿取,由于事务日志包含了被修改的数据的原始值或修改前的状态,所以可以根据这些来进行回滚,具体操作可以阅读mysql源码
事务的四大特性:
原⼦性(Atomicity) :原⼦性是指事务是一个不可分割的工作单位,也就是说事务自身不能分割了,那么在事务里面的操作自然要么都发生,要么都 不发生,因为他们都是属于事务,这样才可以说事务是一个不可分割的,简单来说原子性就是事务的执行成功与否,所以从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
一致性(Consistency) :事务必须使数据库从一个一致性状态变换到另外一个一致性状态,例如转账前A有1000,B有1000,转账后A+B也得是2000,这个一致性只是代表对数据总量的不变,所以是从数据的⻆度来说的,即可以出现(1000,1000) (900,1100),而不应该出现(900,1000)
隔离性(Isolation) :事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务, 每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离
比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响,简单来说,就是直接改变了硬盘,而不是内存
事务的隔离级别:
隔离级别本质上是锁的缘故以及某些操作,在表中通常存在如下的锁:
行锁:代表对该行数据的同步
表锁:代表是对该整个表数据的同步
一般还有其他方面的别称,如读锁和写锁
读锁(共享锁):使得拿取数据不能写但可以读
写锁(排他锁):使得写上数据不能写也不能读
但是针对自身所加的读锁和写锁,读锁确不能写,但是写锁确可以写和读(但是只是针对设置的会话),一般行锁和表锁的写锁和读锁都是同样的功能说明,或者都是同样的操作,他们之所以存在这样的操作,是因为底层代码就是这样设置的,并且按照惯例,锁只能是锁当前人操作(因为他在操作出现的),写锁自然只能自己操作写或者读取,而读锁谁都不能写,包括自己(因为自己也只有读),这是读锁和写锁在全部的领域中占用大多数的说明,一般在不考虑加上自身说明时,就是读操作时,写不能操作,但是读可以,而写操作读和写都不可以,若加上自身在操作的说明时,而不考虑锁自身的意思时,就是读操作时,只在读(自身和其他),写操作时,存在写和读(自身)
通常为了提高性能,我们只会操作行锁里面的读锁和写锁(简单来说就是对一行操作的读锁和写锁,我们统称为行锁),这里就以行锁的读锁和写锁来进行说明(大多数情况下,读锁操作我们一般并不会添加,因为读取数据实际上对数据没有什么破坏,除非需要避免某些问题,如幻读,这里的隔离级别级别都是写锁,具体的读锁和写锁的说明在96章博客有具体说明)
要说明锁的功能,首先说明四个隔离级别,对应与什么锁操作:
Read uncommitted(读未提交):最低级别,基本对与并发会发生的均⽆法保证不发生,(读未提交)最低
Read committed(读已提交):可避免脏读情况发生,不可重复读和幻读一定会发生, 第三
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生,(幻读有可能发生)第二,该机制下会对要update的行进行加锁
Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生,(串行化)最⾼
默认mysql的隔离级别是Repeatable read(可重复读)
对与他们加上锁的说明:
Read uncommitted(读未提交):代表没有加上任何锁
Read committed(读已提交):代表添加了共享锁,即读锁,但是只是针对在最终读取表数据的加锁,实际上读已提交并不是利用锁的原理来解决其脏读问题,是因为判断形成的,具体原理是因为:数据库为每个事务分配一个唯一的事务ID,在读已提交隔离级别下,事务会记录自己开始的时刻的事务ID,并在读取数据时检查数据的修改者事务ID,只有已经提交的事务的修改会被认可,未提交的事务的修改会被忽略,所以我是可以忽略你的没有提交的,剩下的我进行读取最新的数据,这就是为什么Read committed(读已提交)可以解决脏读的原因,但是读取必然需要与表交互,那么他是保留日志的吗,实则不然,他一般利用了快照,快照读:在读已提交隔离级别下,数据库会为每个事务创建一个独立的读取版本(快照),当事务开始时,它会记录事务开始的时间点,并在读取数据时使用这个时间点的快照,由于忽略的原因,这意味着事务只能读取到在事务开始之前已经提交的数据版本,而不会读取到其他尚未提交的数据,综上所述,Read committed(读已提交)的确解决了脏读,并且单纯的查询虽然没有事务,但是他再这个隔离级别下,也只会考虑已经提交的事务,而非不考虑,这里要注意
Repeatable read(可重复读):代表在Read committed(读已提交)的基础上加上了行锁(行级锁)和间隙锁:
由于是在Read committed(读已提交)的基础上的,所以后面的问题是提交后的问题
行级锁(Row-level Locks):在可重复读隔离级别下,数据库会对读取的每一行数据进行加锁,以保证事务读取期间的数据一致性,行级锁可以防止其他事务修改或删除被锁定的行,从而确保读取的数据在事务结束之前保持不变,这样可以避免了其他事务对同一行数据的并发修改,保持了数据的一致性,当然一开始是没有进行添加该锁的,当你进行DML(增删改)时,就会对对应操作的行加上该锁,使得其他事务不能操作,除非事务提交释放锁了,很明显,对应的行是加上写锁的
间隙锁(Gap Locks):间隙锁用于防止其他事务在已有数据范围之间插入新数据,当事务在可重复读隔离级别下进行范围查询时,数据库会在扫描的范围内设置间隙锁,阻止其他事务在这个范围内插入新数据,这样可以保证事务读取期间,范围查询的结果保持一致,避免了查询期间幻读的问题,他虽然可以在读取期间,使得不被插入,但是读取后的查询可能由于对方提交使得出现幻读(两次查询结果不同,比如添加,修改,删除等等),具体为什么可以解决不可重复读看后面的说明
Serializable(串行化):代表加上表级锁,由于是对表进行添加的,那么他在事务开启时,其中一个事务操作后,基本上,查询都不能进行了,还有事务级锁
表级锁(Table-level Locks):在串行化隔离级别下,数据库会对事务涉及的表进行表级锁定,这意味着在事务执行期间,其他事务无法对这些表进行任何读取或写入操作,表级锁保证了在串行化隔离级别下,只有一个事务可以同时访问被锁定的表,从而避免了并发冲突
事务级锁(Transaction-level Locks):在串行化隔离级别下,数据库会对整个事务进行锁定,以确保只有一个事务能够执行,这种锁定方式可以防止其他事务在同一时间并发执行,并确保事务的串行执行顺序,事务级锁只是建立在表锁的说明而已,正是因为他什么都不能操作,使得事务看起来被锁住一样,所以也成为事务级锁,很明显,由于完全的隔离,所以自然可以解决幻读的问题,也就自然解决前面的所有的问题
不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题
脏读:一个线程中的事务读到了另外一个线程中未提交的数据,没有加上任何锁的情况,那么你在操作事务时,是可以得到对方事务的数据的,因为对与sql的锁来说,可能只能允许一个事务的操作进入(读锁和写锁的性质),具体实现,可以认为是某种标志来操作的
不可重复读:一个线程中的事务读到了另外一个线程中已经提交的(如update)的数据(前后内容不一样)
场景: 员工A发起事务1,查询工资,工资为1w,此时事务1尚未关闭,财务⼈员发起了事务2,给员工A加了2000块钱,并且提交了事务,员工A通过事务1再次发起查询请求,发现工资为1.2w,原来读出来1w读不到了,叫做不可重复读
虚读(幻读):一个线程中的事务读到了另外一个线程中已经提交的insert或者delete的数据(前后条数不一样),一般也包括更新
场景: 事务1查询所有工资为1w的员工的总数,查询出来了10个⼈,此时事务尚未关闭,事务2财务⼈员发起,新来员工,工资1w,向表中插⼊了2条数据,并且提交了事务,事务1再次查询工资为1w的员工个数,发现有12个⼈,⻅了⻤了,这里很明显是第一次操作导致的
所以对应的四种隔离级别就是来对标这些情况的,并且我们可以发现,隔离级别只是利用了锁机制以及某些解决方式(如快照,事务ID等等)来解决的,只要记住:事务最终操作的就是本来的表,那么我们只需要对表进行一些同步操作,就可以解决,且一个锁或者对应部分(如行锁)只能被一个事务进行操作,这样就能理解四个隔离级别的原理了
当然,隔离级别都是建议在对方之上的,所以虽然隔离越好,但是效率也是越来越低,所以为了保证效率比较好,且也比较安全,mysql一般默认隔离级别就是Repeatable read(可重复读)
当然,最好还是建立在测试中来进行理解,测试说明在36章博客有简单的说明(当然可能是存在问题的)
sql相关语句:
-- 查看隔离级别	MySql默认隔离级别  repeatable read
select @@tx_isolation;

-- 设置隔离级别为 读已提交 set global transaction isolation level xxx(xxx代表隔离级别)
set global transaction isolation level read committed; -- Read committed(一般来说,对应的可以首字母小写)
-- 设置的是当前mysql连接会话的,并不是永久改变的,注意:实际上是永久的,只是由于并不会给已经存在的连接或者会话来说是不永久的,所以真实情况需要进行分开说明,属性值改变:永久,当前改变:不永久,需要新建会话
为了验证增删改查对隔离级别的影响,我们来操作如下(只是为了验证问题的出现,而不考虑全部增删改查操作),这里会操作大量的图片,这是保证测试的全面性:
首先是Read uncommitted:
我们先登录进来,查看隔离级别,可以发现的确默认是Repeatable read(可重复读)

在这里插入图片描述

设置隔离级别并查看:

在这里插入图片描述

设置的就是永久的,但不作用于当前窗口(会话,所以可以关闭窗口使得关闭会话,或者使用exit退出会话,然后重新登录也行),这是保证当前隔离级别不会半路变化的,所以我们新建一个dos窗口,我称为dos1

在这里插入图片描述

开了一个新的dos窗口了,发现改变了隔离级别,我们继续开一个,称为dos2,并关掉dos1之前的那一个:

在这里插入图片描述

现在我们创建一个数据库和对应的表,以及添加一些数据,sql语句如下:
CREATE DATABASE testgeli CHARACTER SET utf8;
USE testgeli;
CREATE TABLE test(
id INT(11) PRIMARY KEY,
NAME VARCHAR(20),
pass VARCHAR(20),
url VARCHAR(20)
);
INSERT INTO test  VALUES(1,'张三','123','com.qi');
INSERT INTO test  VALUES(2,'李四','123','com.qi');
INSERT INTO test  VALUES(3,'王五','123','com.qi');
INSERT INTO test  VALUES(4,'赵六','123','com.qi');
在dos2上执行如下:

在这里插入图片描述

dos1和dos2都进行查询:
dos1的查询:

在这里插入图片描述

dos2的查询:

在这里插入图片描述

至此,我们准备完毕,后续操作隔离级别时,最好都到这里的一步,现在我们来进行操作
首先dos1,和dos2开启事务(begin就是开始事务,记得dos2也开启),操作如下:

在这里插入图片描述

我们添加一条数据,看看dos2是否也有,这里就不给出图片了,实际上无论你是否开启事务,dos2都会查询的,因为是操作真的表的,可以发现,的确满足了脏读的问题出现,脏读:一个线程中的事务读到了另外一个线程中未提交的数据,实际上任何操作都是事务,查询也是(可能也不是),所以任何操作都会操作事务,只是是否自动提交与否而已,所以一个线程中的事务读到了另外一个线程中未提交的数据正确出现了
我们继续改变隔离级别,即变成Read committed,按照上面的操作弄好环境:
dos1如下:

在这里插入图片描述

dos2如下:

在这里插入图片描述

我们来看看他是否解决了脏读:
dos1如下:

在这里插入图片描述

dos2:

在这里插入图片描述

即解决了脏读,那么我们来验证他是否解决了不可重复读:一个线程中的事务读到了另外一个线程中已经提交的(如update)的数据(前后内容不一样)
现在我们对dos1进行提交commit;,然后再dos2中进行查询,可以发现,出现了数据,也就是说,他存在不可重复读,即他只解决了脏读,这也是为什么他会称为读已提交的意思,而之前的则是读未提交的意思,因为一个的确读取了已经提交的,而另外一个读取了没有提交的,这是代表他们没有解决的地方
我们继续设置隔离级别,Repeatable read(可重复读):
dos1如下:

在这里插入图片描述

dos2:

在这里插入图片描述

我们来看看他是否解决了不可重复读:
dos1操作如下:

在这里插入图片描述

dos2如下:

在这里插入图片描述

可以发现他还是读到提交的了,但是真的是这样吗,我们继续操作
我们再dos1继续操作如下:

在这里插入图片描述

然后dos2操作如下:

在这里插入图片描述

可以发现,解决了脏读,并且在一个事务中,另外一个dos1添加时,他并没有改变,即解决了不可重复读:一个线程中的事务读到了另外一个线程中已经提交的(如update)的数据(前后内容不一样),并且也可以发现,查询是没有操作事务的,这就是为什么没有开启事务时,查询是可以得到数据,而开启事务中的查询没有得到,这样更加验证了事务是一个操作,而非固定操作,大多数的隔离级别只是针对事务来操作的,所以没有在事务的查询必然是查询真的表,而没有操作隔离级别的忽略(事务ID的忽略,如读已提交,可重复读等等)
我们来验证他是否加上了锁,我们继续操作,将dos1和dos2都进行提交,操作如下,来看看他是否完全解决了
我们使得dos1如下(多余的可以选择通过工具删除):

在这里插入图片描述

dos2:

在这里插入图片描述

现在我们给dos1操作如下:

在这里插入图片描述

dos2操作如下:

在这里插入图片描述

dos1进行提交,看看dos2是否有数据,发现没有数据,这是正常的,因为他们是解决了解决了不可重复读的,但是我们来操作一下更新(你可能有点细心,在说明不可重复读时,有个括号,如"(如update)",他是代表这个更新是重要的,因为其他的我们由于查询的原因,不能直接的看到,虽然他也存在阻塞),dos1操作如下:

在这里插入图片描述

然后dos2操作如下:

在这里插入图片描述

你会发现卡住了,这是因为可重复读是加上锁的(读取是快照,所以并不操作锁哦,所以其他事务是存在读取的,这里就解释了写锁明明存在,其他事务为什么也可以进行读取的原因),当你操作真实的对应信息时,他是不能操作的,删除,添加(操作对应的id,虽然你不存在,但是锁还在,首先通过锁,才能到表,所以删除也会阻塞)也是如此,所以可以发现他的确是操作了锁,你可以选择操作改变id为4的值,其中一个操作后,另外一个不能操作,但是上面的说明为什么可以导致提交后不被操作读取呢,解释如下:我们将之前的版本进行改变,即快照进行改变,这个时候,只要你不是当前事务ID的,那么都进行忽略,而不是只看修改的,所以这就是为什么提交也得不到的原因,并且防止你对信息操作时,你的信息会影响到我,所以加上锁来防止这种情况,这就是为什么你更新后,我会阻塞的原因,因为我不能操作你的了,但是也要注意,操作不同隔离级别时,策略是不同的,因为隔离级别只是针对当前会话的操作,所以如果其中一个隔离级别是读未提交,那么他可能会读取到我这个可重复读的信息
至此可重复读保证解决了不可重复读的情况,但是在这里我们要考虑一个问题,如果其中一个事务改变数据,然后提交(释放锁),那么另外一个事务虽然他不会读取,但是若他操作对应的数据了,那么这个时候数据就会出现问题,所以就引入了串行化,也就是说,你不能进行任何操作,首先我们来显示出这个问题:
隔离级别首先并不改变,操作如下:
dos1:

在这里插入图片描述

dos2:

在这里插入图片描述

然后dos1操作如下:

在这里插入图片描述

提交了,那么就没有他造成的加锁了,那么dos2操作如下:

在这里插入图片描述

可以发现,数据发生了大改变,这就是可重读的还没有解决的问题,虽然他解决了提交后查询没有发生改变,但是并没有解决你操作后数据的改变,对与查询只是根据快照来操作的,然而对与数据的操作我们都是操作真正的表,所以这个问题我们并不能避免,所以引入了Serializable(串行化),我们来设置隔离级别,回到如下的dos1和dos2(修改成如下):
dos1:

在这里插入图片描述

dos2:

在这里插入图片描述

现在我们在dos1中进行开启事务,然后查询,这个时候,dos2虽然可以查询,但是不能增删改了,并且若dos1操作了增删改,那么dos2查询都不能操作,也就是说,这个时候,他没有快照的操作,单纯的就是看锁,当只有查询时,是加上读锁的,而增删改时是加上写锁的,并没有快照哦,所以这个时候查询都不能
至此,所有的隔离级别都已经说明完毕,若有错误,请忽略或者指出
事务的传播行为(虽然在66章有说明,这里我们可以进行回顾):
事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本身都已经被添加了事务控制(准备加事务,但是需要考虑调用关系后来操作加事务,因为事务之间并没有真正的传播关系,即只能先考虑再传播),那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为
A调用B,我们站在B的⻆度来观察来定义事务的传播行为(如果当前:代表是如果当前A没有事务)

在这里插入图片描述

这个传播行为在后面的事务定义里面进行操作的,看后面的代码就知道了
在这之前,首先需要一个项目来进行理解:
创建的项目如下:

在这里插入图片描述

对应的Account类:
package com.lagou.domain;

/**
 *
 */
public class Account {
    private Integer id;
    private String name;
    private Double money;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}

对应的AccountDao接口及其实现类:
package com.lagou.dao;

/**
 *
 */
public interface AccountDao {

    //转出操作 减钱
    public void outMoney(String outUser, double money);

    //转出操作 加钱
    public void inMoney(String inUser, double money);
}

package com.lagou.dao.impl;

import com.lagou.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 *
 */
@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void outMoney(String outUser, double money) {
        String sql = "update account set money = money - ? where name = ?";
        jdbcTemplate.update(sql, money, outUser);
    }

    @Override
    public void inMoney(String inUser, double money) {
        String sql = "update account set money = money + ? where name = ?";
        jdbcTemplate.update(sql, money, inUser);
    }
}

对应的AccountService接口及其实现类:
package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 *
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {

        accountDao.outMoney(outUser, money);
        accountDao.inMoney(inUser, money);
    }


}
对应的AccountServiceTest类:
package com.lagou.test;

import com.lagou.service.AccountService;
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("classpath:Spring.xml")
//这两个注解是一起的,当然了main方法也是可以执行的
public class AccountServiceTest {

    @Autowired
    //由于扫描时,直接忽略静态的,即注解不操作静态的
    //所以当你是静态时,无论什么情况,都不会使得你报错,比如没有对应对象等等(因为不会找)
    private AccountService accountService;


    @Test
    public void textTransfer() {
        accountService.transfer("李四", "jerry", 100d);
    }
}
对应的application.properties:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false
# 加上useSSL=false防止使用注解时,出现的关闭错误,默认是true
jdbc.username=root
jdbc.password=123456
对应的Spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/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">

    <context:component-scan base-package="com.lagou"></context:component-scan>

    <context:property-placeholder location="classpath:application.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>

    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>
</beans>
对应的sql:
CREATE DATABASE spring_db CHARACTER SET utf8;
USE spring_db;
CREATE TABLE account(
id INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) DEFAULT NULL,
money DOUBLE DEFAULT NULL
);
INSERT INTO account (NAME,money) VALUES('张三','300');
INSERT INTO account (NAME,money) VALUES('李四','300');
INSERT INTO account (NAME,money) VALUES('王五','300');
INSERT INTO account (NAME,money) VALUES('赵六','300');
INSERT INTO account (NAME,money) VALUES('jerry','300');
我们执行测试,查看数据库信息,若数据发生改变,说明操作成功
编程式事务控制相关对象(在代码中的操作):
三个接口:PlatformTransactionManager,TransactionDefinition,TransactionStatus
PlatformTransactionManager:
public interface PlatformTransactionManager {
    //获取事务状态信息
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;

    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

实际上事务在代码层面上,只是多执行一次而已,比如之前的代码可以修改成这样:
 @Override
    public void outMoney(String outUser, double money) {
        String sql = "update account set money = money - ? where name = ?";
        jdbcTemplate.update(sql, money, outUser);
        sql = "update account set money = money - ?+10 where name = ?";
        jdbcTemplate.update(sql, money, outUser);
    }
所以他们也只是相当于在会话中执行一次,有些可以得到对应或者对应事务的结果,即是否快照,即认为他们是单纯的执行就行了
当然,他是一个接口,所以需要实现类,一般他有如下的操作:
/*
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类
Dao层技术是jdbcTemplate或mybatis(一般mybatis可能并不存在这个类,所以通常只是操作jdbcTemplate,因为他通常属于Spring框架的,所以这里的mybatis通常只是说是整合Spring的mybatis)时:
 DataSourceTransactionManager 也是需要对应数据源的,是为了可以创建新的事务
 
Dao层技术是hibernate时:
 HibernateTransactionManager
 
Dao层技术是JPA时:
 JpaTransactionManager
*/
TransactionDefinition:
//他里面存在这样的方法:
public interface TransactionDefinition {
    //..
//获得事务的转播行为
 int getPropagationBehavior();

//获得事务的隔离级别
    int getIsolationLevel();

//获得超时时间
    int getTimeout();

//是否只读
    boolean isReadOnly();
    //..
}
他的实现类一般是DefaultTransactionDefinition,这个接口和后面的TransactionStatus会在后面说明的
TransactionStatus:
public interface TransactionStatus extends SavepointManager, Flushable {
    
    //是否是新事务
    boolean isNewTransaction();

    //是否是回滚点
    boolean hasSavepoint();

    void setRollbackOnly();
    //事务是否回滚
    boolean isRollbackOnly();

    void flush();

    //事务是否完成
    boolean isCompleted();
}

PlatformTransactionManager接口,是spring的事务管理器
TransactionDefinition接口提供事务的定义信息(事务隔离级别、事务传播行为等等)
TransactionStatus 接口提供的是事务具体的运行状态
可以简单的理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事务状态
实现代码:
在对应的Spring.xml中加上如下:
 <!--事务管理器交给IOC,注意:这个id会检查,也就是说,名称必须是transactionManager,否则报错-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
修改对应的实现类AccountServiceImpl:
package com.lagou.service.impl;

import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 *
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    //事务管理器
    @Autowired
    private PlatformTransactionManager transactionManager;

    @Override
    public void transfer(String outUser, String inUser, Double money) {
        // 创建事务定义对象
        //DefaultTransactionDefinition是TransactionDefinition 的实现类
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 设置是否只读,false支持事务
        def.setReadOnly(false);
        // 设置事务隔离级别,可重复读mysql默认级别
        /*
            ISOLATION_DEFAULT 使用数据库默认级别
            ISOLATION_READ_UNCOMMITTED 读未提交
            ISOLATION_READ_COMMITTED 读已提交
            ISOLATION_REPEATABLE_READ 可重复读
            ISOLATION_SERIALIZABLE 串行化
         */
        def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        // 设置事务传播行为,必须有事务
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 通过事务管理器,拿取对应的状态对象,并在后面给出他作为参数,来保存对应的状态,这里主要是为了开启事务,要不然哪里来的开启事务呢(内部进行开启事务)
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 转账
            accountDao.outMoney(outUser, money);
            accountDao.inMoney(inUser, money);
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback(status);
        }
    }

}
执行后,看看数据库的结果即可,若正确,说明操作完毕,这里只是回顾一下66章博客的内容,其中66章博客的内容操作了注解,实际上就是通过代理使得加上上面的信息的,即将上面的代码用注解以及xml来完成,具体就不回顾了,可以去看一看
Spring AOP源码深度剖析:
首先我们回到之前下载好的Spring源码,创建如下子模块:

在这里插入图片描述

对应的LagouAspect类:
package com.lagou;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 *
 */
@Component
@Aspect
public class LagouAspect {
	@Pointcut("execution(* com.*.*.*(..))")
	public void pointcut() {
	}

	@Before("pointcut()")
	public void before() {
		System.out.println("before method ......");
	}
}
对应的LagouBean类:
package com.lagou;

import org.springframework.stereotype.Component;

/**
 *
 */
@Component
public class LagouBean {
	public void tech() {
		System.out.println("java learning......");
	}
}
对应的SpringConfig类:
package com.lagou;

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

/**
 *
 */
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy
    public class SpringConfig {
}

对应的test类:
import com.lagou.LagouBean;
import com.lagou.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 *
 */
public class test {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
		LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
		lagouBean.tech();
	}
}

执行看看是否出现对应的结果,这里有个问题,为什么不使用@Test呢,实际上他并不支持,@Test是需要插件的,maven或者说idea一般是自带的,要不然单纯的依赖是怎么操作结构性的操作呢,要知道依赖只是对类进行操作而已,而非结构性的操作,当然,依赖也并非不能,具体可以百度,只是这里的@Test需要插件而已(junit的)
为了看看他的原理,我们需要进行改变,这里我们按照xml的方式来进行看看原理,因为注解只是建立在xml上的数据拿取方式的改变,即是演化来的(不好观察),所以需要来操作xml的方式来更加的容易理解,首先,将对应的LagouAspect修改成如下:
package com.lagou;

public class LagouAspect {
	public void before() {
		System.out.println("haha");
	}
}
对应的LagouBean修改如下:
package com.lagou;


public class LagouBean {
	public void tech() {
		System.out.println("java learning......");
	}
}
SpringConfig类进行删除
然后test类修改如下:
import com.lagou.LagouBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class test {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml");
		LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
		lagouBean.tech();
	}
}

在资源文件夹下,加上app.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   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">

	<!--目标类放入IOC容器-->
	<bean id="lagouBean" class="com.lagou.LagouBean"></bean>

	<!--通知类放入IOC容器-->
	<bean id="lagouAspect" class="com.lagou.LagouAspect"></bean>


	<!--AOP配置-->
	<aop:config>
		<!--配置切面:切入点+通知-->
		<aop:aspect ref="lagouAspect"> <!--ref指定通知的类名位置,即上面的id-->
			<aop:before method="before"
						pointcut="execution(void com.lagou.LagouBean.tech(..))"/>

		</aop:aspect>
	</aop:config>

</beans>
执行看看结果,若出现结果,说明操作完毕,要看看他具体操作,自然还是需要从bean的创建入手,因为他要代理,自然是改变原来的对象,所以我们回到之前操作过的finishBeanFactoryInitialization(beanFactory);方法里面,给他加上断点,调试到如下(前面也给出过说明):
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {

    //..
    
    //最终到这里(按照顺序来的)
    	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
        //为了验证代理是否是后置方法中的操作,我们给这里打上断点
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
            //经过大量的测试,只要你存在<aop:config>标签,都会给一个代理类,也就是说,代理每个人都是操作的,只是可能只对一些方法进行监听(大量的判断),当对应的current是一个代理时,那么我们进入对应的postProcessAfterInitialization里面看看(看后面)
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}
    
 //..
    
 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
            //创建bean实例,仅仅调用构造方法,但是并没有设置属性
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
        //开始考虑三级缓存
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
        //到这里
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
            //这里就是创建(一般一开始就有的)或者给三级缓存添加信息的地方,我们进入
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
        //初始化bean实例
		Object exposedObject = bean;
		try {
            
            //前面是创建bean,这里是填充属性(bean),现在我们进入了(之前没有进入)
			populateBean(beanName, mbd, instanceWrapper);
            //调用初始化方法,应用BeanPostProcessor后置处理器,我们进入这里
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
            
            //后面的省略了
 //..   
            
            //到这里,在前面我们分析过这里,操作后置的,现在他其实在操作后置之后,会操作一下代理,使得我们的aop的bean对象是代理对象了,前提是对应的aop是看到有这个方法,否则对应的类可能并不会变成代理类,这是因为bean的产生是在原来的后置方法后执行完毕后,bean才会考虑变成实例,而操作代理,必然是在bean的构造方法后进行操作,而在构造方法后的拦截,自然就是后置方法了,所以代理的操作也必然在后置方法中进行处理(后置方法的返回值就是其bean的实例值,最终被赋值的,前置方法也是如此,但是其为了保证不为null,事先进行的保留,也就是说,如果是null,结果不变,但是如果不是,那么改变,所以这里必然是代理的最终操作,即代理就是后置方法的对bean的操作),现在我们来看看后面的操作
            protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
            //这里就是前置
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
            //原来的方法(一系列)执行
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
            //进入这里面就是操作后置方法的(是bean的后置,而不是一开始的后置(应该还记得"后后"吧),前面我们可是测试过案例的)
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}
            
            //..
    
}
对应代理最终到的地方:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

 //..
    //当存在对应的标签时,就会到这里了
    @Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                //最终关键代码
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
    
    //..
    
    //到这里
    //这里会进行判断,而确定是否创建对应的代理类
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
        //这里来判断是否有缓存的操作,具体可以百度
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
        //查找出和当前bean匹配的Advisor(存在,则返回保留了他的数组),看看是否存在对应的方法匹配于bean,若有,那么考虑变成代理,否则直接的返回bean,而不是变成代理(事务管理也是这个地方,所以当aop和他操作的事务一起时,是同时操作的,只是可能会分先后,但是为了保证事务在前面,所以当对应的aspect在advisor前面时,会报错,所以若存在advisor和aspect一起,那么必然是事务先操作,在事务之中操作aop的通知(不是事务的),然后操作方法)
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //进入这里
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

    
    //..
    //创建代理的地方
    	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

            //创建代理的工作交给ProxyFactory
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

            //判断是否要设置ProxyTargetClass为true
		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

            //把增强和通用拦截器对象合并,都适配成Advisor
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
            //设置参数
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

            //准备工作做完就开始创建代理,我们进入
		return proxyFactory.getProxy(getProxyClassLoader());
	}
    //..
}


public class ProxyFactory extends ProxyCreatorSupport {
//..
    
    public Object getProxy(@Nullable ClassLoader classLoader) {
        //进入
		return createAopProxy().getProxy(classLoader);
	}
    //..
    
    
}

public class ProxyCreatorSupport extends AdvisedSupport {

 //..
    protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
        //进入
		return getAopProxyFactory().createAopProxy(this);
	}

    //..
    
}

//@SuppressWarnings("serial"),在说明源码时这样的注解(或者注释)可能忽略了,因为并不需要特别的了解
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    //最终到这里
	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
            /*
当bean实现接口时,会用JDK代理模式
当bean没有实现接口,用cglib实现
那么很明显,我们测试的是没有实现接口的,那么应该在return new ObjenesisCglibAopProxy(config);里面
    */
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
            //到这里了
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
    
    //..
    
}

//看看ObjenesisCglibAopProxy的方法getProxy,因为最终返回的是return createAopProxy().getProxy(classLoader);
//class ObjenesisCglibAopProxy extends CglibAopProxy {
class CglibAopProxy implements AopProxy, Serializable {

    //..
    @Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
		}

		try {
			Class<?> rootClass = this.advised.getTargetClass();
			Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

			Class<?> proxySuperClass = rootClass;
			if (ClassUtils.isCglibProxyClass(rootClass)) {
				proxySuperClass = rootClass.getSuperclass();
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
					this.advised.addInterface(additionalInterface);
				}
			}

			// Validate the class, writing log messages as necessary.
			validateClassIfNecessary(proxySuperClass, classLoader);

			// Configure CGLIB Enhancer...
            //最终创建的代理(记得CGLIB(cglib)的创建吗:Enhancer.create(accountService.getClass(), new MethodInterceptor() {)
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
            //到这里
			return createProxyClassAndInstance(enhancer, callbacks);
		}
		catch (CodeGenerationException | IllegalArgumentException ex) {
			throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
					": Common causes of this problem include using a final class or a non-visible class",
					ex);
		}
		catch (Throwable ex) {
			// TargetSource.getTarget() failed
			throw new AopConfigException("Unexpected AOP exception", ex);
		}
	}

    protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
		enhancer.setInterceptDuringConstruction(false);
		enhancer.setCallbacks(callbacks);
        //是否眼熟:Enhancer.create(accountService.getClass(), new MethodInterceptor() {,虽然他是另外一种方式,但是都是创建对应的cglib代理对象
		return (this.constructorArgs != null && this.constructorArgTypes != null ?
				enhancer.create(this.constructorArgTypes, this.constructorArgs) :
				enhancer.create());
	}
    
    //..
    
}
所以可以看到,操作了对应的aop的xml代理时,是操作代理对象的,经过我的测试,aop事务增强也是这里,并且事务是先操作的,且要注意,事务是操作覆盖,而通知是补充(id相同的时候),至此,我们查看xml的方式可以明白他的确是解决增强的,因为操作代理的,到这里,可能并没有过对注解源码的说明,那么这里我们来以这个为例,因为前面有一个案例了,也作为第一次操作注解源码说明的例子,这里开一个开头:
要知道注解为什么可以,实际上与xml是一样的,只是获取数据不同而已,注解之所以简便是因为注解他所标注的位置有隐藏信息,比如对应所注解的类的信息,如全限定名等等,特别的,在扫描时,这是可以找到的,并且也由于这个名称存在,即可以得到该类的任何信息,这些信息足以完成xml的替换,因为xml就是根据类信息来的,既然注解可以得到,自然可以替换xml
当然,这里我们可以将原来注解方式变成xml的修改回去,即操作注解(xml可以保留,我们不使用就行了),虽然现在我们来说明事务了
Spring声明式事务控制(操作注解和xml,编程式是没有利用其他的如xml和注解来完成的):
声明式事务很方便,尤其纯注解模式,仅仅⼏个注解就能控制事务了
如@EnableTransactionManagement和@Transactional,其中@EnableTransactionManagement代表事务的扫描,而@Transactional代表配置属性和指定给谁加上的事务增强(这里再66章博客有具体的说明和使用)
@EnableTransactionManagement:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class) //导入其他配置类
public @interface EnableTransactionManagement {
//..
}
@EnableTransactionManagement 注解使用 @Import 标签(主要用来导入其他配置类,不是配置类也可以(有些对应版本必须要配置类,但现在基本可以不是配置类了,但因为这个存在,如果对方存在其他扫描的,那么会进行处理(通常这个为主,当然,可能会出现错误,但是一般没有,因为基本不会出现,即他们是覆盖的关系),即他也看成一个bean了,即通过@Import注解导入的类可以成为Spring中的bean,并且由于是导入,所以就算他不是配置类,也可以读取到@Bean))引⼊了TransactionManagementConfigurationSelector类,这个类⼜向容器中导⼊了两个重要的组件
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    
    //一般他最终会操作的,被@Import导入的会变成bean,他通常返回一些全限定名称来操作对应的bean
@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
                //两个重要的组件,即AutoProxyRegistrar和ProxyTransactionManagementConfiguration
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}
    //..
    
}

//进入AutoProxyRegistrar(加载事务控制组件)
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    private final Log logger = LogFactory.getLog(getClass());

	/**
	 * Register, escalate, and configure the standard auto proxy creator (APC) against the
	 * given registry. Works by finding the nearest annotation declared on the importing
	 * {@code @Configuration} class that has both {@code mode} and {@code proxyTargetClass}
	 * attributes. If {@code mode} is set to {@code PROXY}, the APC is registered; if
	 * {@code proxyTargetClass} is set to {@code true}, then the APC is forced to use
	 * subclass (CGLIB) proxying.
	 * <p>Several {@code @Enable*} annotations expose both {@code mode} and
	 * {@code proxyTargetClass} attributes. It is important to note that most of these
	 * capabilities end up sharing a {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME
	 * single APC}. For this reason, this implementation doesn't "care" exactly which
	 * annotation it finds -- as long as it exposes the right {@code mode} and
	 * {@code proxyTargetClass} attributes, the APC can be registered and configured all
	 * the same.
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		for (String annType : annTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
                //到这里
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
                    //我们进入这里
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		if (!candidateFound && logger.isInfoEnabled()) {
			String name = getClass().getSimpleName();
			logger.info(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}

}


public abstract class AopConfigUtils {
 //..
    
    //到这里
    	@Nullable
	public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
		return registerAutoProxyCreatorIfNecessary(registry, null);
	}
    @Nullable
	public static BeanDefinition registerAutoProxyCreatorIfNecessary(
			BeanDefinitionRegistry registry, @Nullable Object source) {

        //我们看看InfrastructureAdvisorAutoProxyCreator类:
        /*
        public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
你全局搜索一下AbstractAutoProxyCreator,看看与后置通知有什么联系呢,自己全局搜索看看就知道了
        */
		return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
	}
    //..
    
    @Nullable
	private static BeanDefinition registerOrEscalateApcAsRequired(
			Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
				int requiredPriority = findPriorityForClass(cls);
				if (currentPriority < requiredPriority) {
					apcDefinition.setBeanClassName(cls.getName());
				}
			}
			return null;
		}

		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
		beanDefinition.setSource(source);
		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
		return beanDefinition;
	}
    
    //..
}


//我们进入ProxyTransactionManagementConfiguration
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
        //事务增强器
		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        //向事务增强器中注入属性解析器transactionAttributeSource
		advisor.setTransactionAttributeSource(transactionAttributeSource());
        //向事务增强器中注入事务拦截器transactionInterceptor
		advisor.setAdvice(transactionInterceptor());
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

   @Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
        //我们来看看属性解析器
		return new AnnotationTransactionAttributeSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor() {
        //我们来看看事务拦截器
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource());
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}
    
}
属性解析器:
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource
		implements Serializable {
private static final boolean jta12Present;

	private static final boolean ejb3Present;

	static {
		ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader();
		jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
		ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute", classLoader);
	}

	private final boolean publicMethodsOnly;

    //注解解析器集合
	private final Set<TransactionAnnotationParser> annotationParsers;
 //..   
    
}

public interface TransactionAnnotationParser {
    //..
}
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {
//..
    
    //看到下面的参数是否感觉有点熟悉呢,如isolation是否与隔离级别相关呢,如@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = -1,readOnly = false)
    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
		RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

		Propagation propagation = attributes.getEnum("propagation");
		rbta.setPropagationBehavior(propagation.value());
		Isolation isolation = attributes.getEnum("isolation");
		rbta.setIsolationLevel(isolation.value());
		rbta.setTimeout(attributes.getNumber("timeout").intValue());
		rbta.setReadOnly(attributes.getBoolean("readOnly"));
		rbta.setQualifier(attributes.getString("value"));

		List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
		for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
		for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		rbta.setRollbackRules(rollbackRules);

		return rbta;
	}

    
    //..
    
}
很明显,对应的属性解析器变成bean后,存在的对应方法是操作对应的@Transactional的属性的,即实际上属性解析器是解析@Transactional的事务属性
事务拦截器:TransactionInterceptor interceptor = new TransactionInterceptor();
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    //其中,他实现的MethodInterceptor接口是否与cglib对应的那个参数一样呢(并不一样,所以这里是invoke),且可以在前面看到:
    /*
      //把增强和通用拦截器(MethodInterceptor)对象合并,都适配成Advisor(具体怎么得到和生成,这里就不说明了,具体可以百度)
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		当然,有很多方法会操作,这里只是给出主要的,一般都是与xml类似操作的,只是读取方式不同,这里给出主要的操作(主要的扫描,到配置,都没有说明)

    */

 //..   
    
    	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
        //会触发原有业务逻辑调用,在调用时会增强事务(即对应的找到方法操作就在这里,当然,可能有其他操作使得确定的,这里了解即可),这样在属性解析后,我们进行增强,现在我们进入
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

    
    //..
}

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
//..
    
    @Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
         //获取属性解析器
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        //获取事务管理器,现在事务有了,属性有了,那么就能增强了
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
            //开启(创建)事务
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
                //如果目标方法抛出异常,会执行这个方法(获取事务管理器,执行回滚操作)
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
            //执行正常,那么提交(获取事务管理器,执行提交操作)
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
			Object result;
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					}
					catch (Throwable ex) {
						if (txAttr.rollbackOn(ex)) {
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
								throw (RuntimeException) ex;
							}
							else {
								throw new ThrowableHolderException(ex);
							}
						}
						else {
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					}
					finally {
						cleanupTransactionInfo(txInfo);
					}
				});
			}
			catch (ThrowableHolderException ex) {
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}

			// Check result state: It might indicate a Throwable to rethrow.
			if (throwableHolder.throwable != null) {
				throw throwableHolder.throwable;
			}
			return result;
		}
	}

    
    //..
    
    
}
至此,对应注解的操作的确操作了事务的增强,好了Spring的底层原理大致说明完毕,当然,还有很多,具体可以到网上学习

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程间通信(管道)

文章目录 进程通信的目的进程间通信发展进程间通信分类管道System V IPCPOSIX IPC 管道什么是管道管道的读写规则管道的特点&#xff1a;匿名管道处理退出问题命名管道创建一个命名管道匿名管道与命名管道的区别命名管道的打开规则 进程通信的目的 数据传输&#xff1a;一个进程…

应用层协议 —— websocket

websocket介绍 websocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制。 传统的web程序都是属于“一问一答”的形式&#xff0c;即客户端给服务器发送了一个HTTP请求&#xff0c;服务器给客户端返回一个HTTP响应。这种情况下服务器属于被动的一方&#xff…

前端食堂技术周刊第 86 期:Remix 拥抱 RSC、2023 React 生态系统、从 0 实现 RSC、字节跳动 Mobile DevOps 工程实践

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;椰子水 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 本期摘要 Remix 拥抱 RSCWebContainers 原生支持 npm、yarn 和 pnpm2023 React 生态系…

MySQL InnoDB集群部署及管理全教程

MySQL InnoDB 集群提供完整的高可用性 MySQL 的解决方案。通过使用MySQL Shell附带的AdminAPI&#xff0c;您可以轻松 配置和管理至少三个MySQL服务器的组 实例以充当 InnoDB 集群。 InnoDB 集群中的每个 MySQL 服务器实例都运行 MySQL 组复制&#xff0c;提供复制机制 InnoDB…

LoadRunner 2023 下载和安装

下载 LoadRunner目前最新的版本是2023版&#xff0c;需要到Micro Focus公司的官网注册账号然后申请下载&#xff0c;比较麻烦&#xff0c;这里我把大家常用的社区版本&#xff0c;搬运到阿里云盘上&#xff0c;供下载&#xff1a; https://www.aliyundrive.com/s/WtHSzD4MrXw …

面试了十几家软件测试公司谈谈我最近面试的总结

由于互联网裁员&#xff0c;最近在 bosss 上投了些简历&#xff0c;测试开发岗&#xff0c;看看目前市场情况。 虽然都在说大环境不好&#xff0c;失业的人很多&#xff0c;我最近约面试的还是比较多的&#xff0c;说说最近的体会吧&#xff0c;希望能给大家提供价值。 1、20K…

教你制作一个简单的进销存管理软件,值得收藏!

首先要制作进销存软件&#xff0c;要具体了解进销存到底是什么含义&#xff0c;这三个字分别代表什么流程&#xff0c;在整个进销存管理中的组成。再根据不同的流程制作进销存软件相对应的部分—— 01进销存的定义 “进”——采购 采购是进销存管理的重要组成部分&#xff0…

微信开放平台第三方开发,注册试用小程序,一整套流程

大家好&#xff0c;我是小悟 对服务商来说&#xff0c;试用小程序的好处不言而喻&#xff0c;主打一个先创建后认证的流程。只需要提供小程序名称和openid便可快速注册一个试用小程序&#xff0c;在认证之前&#xff0c;有效期14天&#xff0c;大致流程如下。 注册试用小程序 …

HCIA-RS实验-配置DHCP

什么是DHCP DHCP是动态主机配置协议&#xff08;Dynamic Host Configuration Protocol&#xff09;的缩写&#xff0c;它是一种网络协议&#xff0c;用于自动分配IP地址、子网掩码、网关以及DNS服务器等网络参数给计算机&#xff0c;从而简化了网络管理和配置。 DHCP服务器的…

robotframework+python接口自动化的点滴记录

在robotframeworkpython框架上写了两三天的接口自动化&#xff0c;做了一些笔记。 1.在断言的时候经常由于数据类型导致较验不通过&#xff0c;值得注意的是&#xff0c;在定义常量或者变量的时候&#xff0c;使用${}代表int类型&#xff0c;例如${2}就代表数字2&#xff0c;另…

Qt学习09:其他基本小控件

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 QSpinBoxQDateTimeEditQComboBoxQSliderQRubberBand QSpinBox 微调框&#xff0c;可以通过点击增加减小或者输入来调整数据。 继承自&#xff1a;QAbstractSpinBox 同时这个类还具有Double类型的版本。 常用操…

搭建Scala开发环境

一、Windows上安装Scala 1、到Scala官网下载Scala Scala2.13.10下载网址&#xff1a;https://www.scala-lang.org/download/2.13.10.html 单击【scala-2.13.10.msi】超链接&#xff0c;将scala安装程序下载到本地 2、安装Scala 双击安装程序图标&#xff0c;进入安装向导&…

笔试强训错题总结(二)

笔试强训错题总结&#xff08;二&#xff09; 选择题 下列哪一个是析构函数的特征&#xff08;&#xff09; A. 析构函数定义只能在类体内 B. 一个类中只能定义一个析构函数 C. 析构函数名与类名不同 D. 析构函数可以有一个或多个参数 析构函数可以在类中声明&#xff0c…

np.arange()用法+reshape+np.dot()

1.np.arange()用法 np.arange()函数返回一个有终点和起点的固定步长的排列 # 参数个数情况&#xff1a; np.arange()函数分为一个参数&#xff0c;两个参数&#xff0c;三个参数三种情况 # 1&#xff09;一个参数时&#xff0c;参数值为终点&#xff0c;起点取默认值0&#xff…

SpringBootSecurity 简单明了

在autoConfiguration Jar的imports文件里面有 SecurityFilterAutoConfiguration类&#xff0c;这样springboot会自己加载这个类。 该类的作用是向容器内部注入一个RegisterBean叫DelegatingFilterProxyRegistrationBean&#xff0c;由于它同时实现了ServletContextInitializer接…

Redis问题处理

1、jemalloc/jemalloc.h&#xff1a;没有那个文件或目录 解决方法&#xff1a; 正确解决办法(针对2.2以上的版本) 清理上次编译残留文件&#xff0c;重新编译 make distclean && make

【学术小白如何写好论文】文献综述

文章目录 一、前言1.目的2.作用 二、切入角度三、写作方法 一、前言 前言&#xff1a;在撰写这部分的时候&#xff0c;我们首先要明确文献综述的目的是什么&#xff0c;作用是什么。 1.目的 梳理前人研究的脉络找出前人研究的不足 2.作用 让本研究更充实&#xff0c;告诉读者…

路径规划算法:基于蛾群优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于蛾群优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于蛾群优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法蛾群…

SpringSecurity实现前后端分离登录token认证详解

目录 1. SpringSecurity概述 1.1 权限框架 1.1.1 Apache Shiro 1.1.2 SpringSecurity 1.1.3 权限框架的选择 1.2 授权和认证 1.3 SpringSecurity的功能 2.SpringSecurity 实战 2.1 引入SpringSecurity 2.2 认证 2.2.1 登录校验流程 2.2.2 SpringSecurity完整流程 2.2.…

翻译的技巧

400字左右的文章中划出5个句子&#xff0c; 30分钟内将其翻译成中文&#xff0c;分值10分。文章的题材大多是有关政治、经济、文化、教育、科普以及社会生活&#xff0c;议论文为主&#xff0c;说明文为辅&#xff0c;结构严谨&#xff0c;逻辑性强&#xff0c;长难句较多。不仅…