开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

说完了 xxl-job 的执行器原理,再来聊聊调度中心是如何

发表于 2021-06-22

前言

在上一篇 xxl-job 执行器原理分析 一文中,我们提到了 xxl-job 框架中包含了两个核心模块:调度中心 和 执行器, 其中调度中心主要负责 任务的调度 , 而执行器负责 任务的执行, 两者各司其职。 紧接着我们通过画图的方式对 执行器 的内部构造进行了分析,并且还对 Job 的执行流程进行了梳理。

本文我们继续围绕任务的调度流程对 调度中心 进行剖析, 内容依然参照 xx-job v2.x 版本的源码。

正文

再看一遍 xxl-job 架构图:
在这里插入图片描述
调度中心主要提供了两个功能: 系统管理 和 任务调度。其余的都是一些辅助功能。

  • 系统管理正如图中所示的那样, 包括任务管理、执行器管理、日志管理。还提供了管理界面。
  • 任务调度就是负责从数据中心拉取任务,并按照执行时间将任务投递给执行器。

调度器的组成结构

在这里插入图片描述

两个核心线程

当调度中心启动后,会启动以下两个线程:

  1. schedulerThread
    scheudlerThread主要做如下两件事情:
  • 从数据中心(db),也就是 xxl_job_info表中扫描出符合 条件 1 的任务, 条件1 限制如下:

    • 任务执行时间 小于(当前时间 + 5 s)
    • 限制扫描个数, 这个值是动态的,会根据后面的提到的 快慢线程池 中线程数量有关系。
      1
      2
      3
      4
      java复制代码count = treadpool-size * trigger-qps  (each trigger cost 50ms, qps = 1000/50 = 20) 

      treadpool-size = (getTriggerPoolFastMax() + getTriggerPoolSlowMax()) * 20
      // 看完快慢线程池的介绍,再回过头来看这里,会更容易理解
  • 扫描出来的任务被划分为以下 3 类:
    在这里插入图片描述

  1. ringThread

ringThread的作用就是不断从 容器 中读取 当前时间点需要执行 的任务, 读取出来的任务会交给一个叫 快慢线程池 的东西去将任务传递给调度器去执行。

时间轮

上述的 ringThread和 容器 共同组成了一个时间轮。

关于时间轮,如果展开来讲内容会很多,需要详细了解的可以参考 这篇文章,或者自行搜索, 本文只做简单了解。

简单来讲,时间轮实现了 延迟执行 的功能,它在 xxl-job 中的作用就是让 还未到达执行时间 的任务,按照预计的时间通过 快慢线程池 一个一个送到 执行器 中去执行。

  • 时间轮的数据结构一般是 数组 + 链表, 和 jdk1.7 中的 HashMap 是一个道理,链表中的每个节点就是一个待执行的任务。
  • xxl-job 中的时间轮可以形象描述为以下这张图,像一个只有秒针的钟表一样。
  • ringThread 线程运行过程中,每秒会扫过一个刻度,假设当前刻度位置存在 job 链表,就把链表中的所有 job 取出来,最后丢给 快慢线程池。
  • 当然 xxl-job 为了避免处理耗时太长,会跨过刻度,多向前校验一个刻度;也就是当指针指到 2s 时,会把 1s 和 2s 位置的任务同时读取出来。

在这里插入图片描述

快慢线程池

上面提到任务从数据中心扫描出来后,随之就会被丢到快慢线程池中,快慢线程池的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码      fastTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
}
});

slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
}
});

由上可知, 快慢线程池包含了两个线程池 fast 和 slow,当一个 job 提交到快慢线程池后,快慢线程池会根据一些条件, 选择其中一个线程池去执行后续的操作。

快慢线程池的作用如下:

实现线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入”Slow”线程池,避免耗尽调度线程,提高系统稳定性;

什么是慢任务?

如果一个任务在 1 分钟内,它的执行超时次数超过 10 次,就被归为 慢任务

当具体的快或者慢线程池接收到调度任务时,会通过 RPC 远程调用去触发 执行器 完成任务的执行逻辑。

当执行器接收到调度任务时,具体是如何执行任务的,可以参考 xxl-job 执行器原理分析 一文。

源码入口

1
2
3
4
5
java复制代码com.xxl.job.admin.core.thread.JobScheduleHelper#start

com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTrigger

com.xxl.job.core.biz.client.ExecutorBizClient#run

总结

本文基于xxl-job v2.x的源码分析了 xxl-job 调度器 的组成结构 以及 调度中心 是如何触发任务的。

调度器主要包含了以下模块:

  • schedulerThread: 负责从数据中心扫描需要执行的任务
  • ringThread: 负责精准地控制预计需要执行的任务
  • 快慢线程池:通过包装两个线程池,去分别执行 快任务 和 慢任务 的调度过程。

这里面的主要难点是引出了一个时间轮的概念,文中并未做详细的描述.

另外, 时间轮 的用法在很多地方都有应用, 比如 Netty、Dubbo、Kafka 等。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

详解 SQL 中的子查询

发表于 2021-06-22

这是我参与更文挑战的第 21 天,活动详情查看: 更文挑战”

你好,我是悟空。

本文已收录至Github,欢迎 Star:github.com/Jackson0714…

个人网站:www.passjava.cn

数据库专题

  • 有了 MySQL,为什么还要 NoSQL?
  • TempDB 的使用和性能问题
  • 数据库实战:表表达式(一)
  • 数据库实战:表表达式(二)
  • 详解 SQL 的集合运算
  • 详解 SQL 中的联接查询
  • 详解 SQL 中的子查询

概述

本篇主要是子查询基础的总结。

关键词解释:

外部查询:查询结果集返回给调用者

内部查询:查询结果集返回给外部查询。

独立子查询:独立子查询独立于其外部查询的子查询,可以单独运行子查询。在逻辑上,独立子查询在执行外部查询之前先执行一次,接着外部查询再使用子查询的结果继续进行查询。

相关子查询:引用了外部查询中出现的表的子查询,查询要依赖于外部查询,不能独立地调用它。在逻辑上,子查询会为每个外部行单独计算一次。

标量子查询:返回单个值的子查询。标量子查询可以出现在外部查询中期望使用单个值的任何地方。

多值子查询:在一个列中

为什么要使用子查询?

可以避免在查询解决方案中把操作分成多个步骤,并在变量中保存中间查询结果的需要。

一、独立子查询

1. 独立标量子查询

例子:从 HR.Employees 表中返回 empid 最大的员工信息。

可以分两步:

a. 定义一个变量 maxid ,通过独立标量子查询查询出 empid 最大的员工的 empid,然后将这个 empid 保存到变量@maxid 中

b. 在 WHERE 条件中过滤出 empid = @maxid 的记录

1
2
3
4
5
6
sql复制代码DECLARE @maxid AS INT = ( SELECT    MAX(empid)
FROM HR.Employees
)
SELECT *
FROM HR.Employees
WHERE empid = @maxid

更简单的方法是嵌套子查询,只需要一条查询语句就可以查询出 empid 最大的员工信息

1
2
3
4
5
sql复制代码SELECT  *
FROM HR.Employees
WHERE empid = ( SELECT MAX(empid)
FROM HR.Employees
)

注意:

  1. 对于有效的标量子查询,它的返回值不能超过一个,如果标量子查询返回了多个值,在运行时则可能会失败。
  2. 如果标量子查询没有返回任何值,其结果就转换为 NULL,和 NULL 行进行比较得到的是 UNKNOWN,查询过滤器不会返回任何让过滤表达式计算结果为 UNKNOWN 的行。
  1. 独立多值子查询

(1) 多值子查询的语法格式

<标量表达式、> IN ( <多值子查询、> )

例子:返回 title 包含 manager 的雇员处理过的订单的信息

方案一:独立多值子查询

1
2
3
4
5
sql复制代码SELECT  *
FROM Sales.Orders
WHERE empid IN ( SELECT empid
FROM HR.Employees
WHERE HR.Employees.title LIKE '%Manager' )

方案二:内联接查询

1
2
3
4
sql复制代码SELECT  *
FROM Sales.Orders
INNER JOIN HR.Employees ON Sales.Orders.empid = HR.Employees.empid
WHERE HR.Employees.title LIKE '%Manager'

类似地,很多地方既可以用子查询也可以用联接查询来解决问题。数据库引擎对两种查询的解释有时候是一样的,而在另外一些情况下,对二者的解释则是不同的。可以先用一种查询解决问题,如果性能不行,再尝试用联接替代子查询,或用子查询替代联接。

3. 子查询之 distinct 关键字

当我们想要剔除掉子查询中的重复值时,会想到在子查询中不必指定 distinct 关键字,其实是没有必要的,因为数据库引擎会帮助我们删除重复的值,而不用我们显示指定 distinct 关键字。

二、相关子查询

1. 相关子查询

什么是相关子查询:引用了外部查询中出现的表的列,依赖于外部查询,不能独立地运行子查询。在逻辑上,子查询会为每个外部行单独计算一次。

例子:查询每个客户返回在他参与活动的最后一天下过的所有订单。

期望结果:

影响行数:90

  1. 首先用独立标量子查询查询出最大的订单日期,返回给外部查询
1
2
sql复制代码SELECT  MAX(orderdate)
FROM sales.Orders AS O2
  1. 外部查询用 O1.orderdate 进行过滤,过滤出等于最大订单日期的订单
  2. 因为要查询出每个客户参与的订单,所以将独立标量子查询改成相关子查询,用子查询 O2.custid 与外查询 O1.custid 关联。

对于 O1 中每一行,子查询负责返回当前客户的最大订单日期。如果 O1 中某行的订单日期和子查询返回的订单日期匹配,那么 O1 中的这个订单日期就是当前客户的最大的订单日期,在这种情况下,查询便会返回 O1 表中的这个行。

1
2
3
sql复制代码SELECT  MAX(orderdate)
FROM sales.Orders AS O2
WHERE O2.custid = O1.custid

综合上面的步骤,得到下面的查询语句:

1
2
3
4
5
6
sql复制代码SELECT  orderid,orderdate,custid
FROM sales.Orders AS O1
WHERE O1.orderdate = ( SELECT MAX(orderdate)
FROM sales.Orders AS O2
WHERE O2.custid = O1.custid
)

2.EXISTS 谓词

  1. <外查询> WHERE EXISTS ( 子查询 )
  2. 它的输入是一个子查询,:如果子查询能够返回任何行,改谓词则返回 TRUE,否则返回 FALSE.
  3. 如果子查询查询结果又多条,SQL SERVER 引擎查询出一条记录后,就会立即返回,这种处理方式叫做短路处理。
  4. Exist 谓词只关心是否存在匹配行,而不考虑 SELECT 列表中指定的列,所有使用 SELECT * FROM TABLE,并没有什么负面影响,但是为了展开代码的列名会有少少量的开销,但是还是推荐使用通配符,查询语句应该尽可能保持自然和直观,除非有非常令人信服的理由,才可以牺牲代码在这方面的要求。
  5. NOT EXISTS 谓词是 EXISTS 谓词的反面

三、练习题

1. 写一条查询语句,返回 Orders 表中活动的最后一天生成的所有订单。

期望结果:

本题考察独立子查询的基本用法,首先用独立子查询返回最后一天的日期,然后外部查询过滤出订单日期等于最后一天的所有订单。

1
2
3
4
5
6
7
8
9
sql复制代码SELECT  orderid ,
orderdate ,
custid ,
empid
FROM Sales.Orders
WHERE orderdate = ( SELECT MAX(orderdate)
FROM Sales.Orders

)

2. 查询出拥有订单数量的最多的客户下过的所有订单。

期望结果:

本题考察独立子查询的用法,和第一题类似,分两个步骤:

(1)先用子查询查询出订单数量最多的客户 id

(2)然后将 id 返回给外部查询,外部查询通过客户 id 过滤出客户下过的所有订单

方案一:独立标量子查询

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码SELECT  custid ,
orderid ,
orderdate ,
empid
FROM Sales.Orders
WHERE custid = ( SELECT TOP ( 1 ) WITH TIES
O.custid
FROM Sales.Orders AS O
GROUP BY custid
ORDER BY COUNT(*) DESC

)

注意:

1
scss复制代码TOP ( 1 ) WITH TIES O.custid

查找出排序后与第一条记录 O.custid 相等的所有行

因为下过订单数最多的客户的总订单数是 31,且只有一个客户(custid=71),所以最后的查询结果中只有 custid=71 的客户下过的所有订单。

3. 查询出 2008 年 5 月 1 号(包括这一天)以后没有处理过订单的雇员。

期望结果:

本题考察独立子查询的用法,本题也可以采用两步来查询出结果。

(1)首先用子查询返回所有 2008 年 5 月 1 号(包括这一天)以后处理过订单的雇员,将这些雇员的 empid 返回给外部查询

(2)然后外部查询用 NOT IN 过滤出所有 2008 年 5 月 1 号(包括这一天)之后没有处理过订单的雇员

方案一:独立标量子查询 + NOT IN

1
2
3
4
5
sql复制代码SELECT  *
FROM HR.Employees
WHERE empid NOT IN ( SELECT empid
FROM Sales.Orders
WHERE orderdate >= '20080501' )

4. 查询 2007 年下过订单,而在 2008 年没有下过订单的客户

期望输出:

方案一:内联接+独立标量子查询

  1. 查询出 20070101~20071231 所有下过订单的客户集合 Collection1
1
2
3
sql复制代码SELECT DISTINCT C.custid,companyname FROM Sales.Orders O
INNER JOIN Sales.Customers AS C ON C.custid = O.custid
WHERE (orderdate <= '20071231' AND orderdate >= '20070101')
  1. 查询出 20080101~20081231 所有下过订单的客户结合 Collection2
1
2
3
sql复制代码SELECT C.custid FROM Sales.Orders O
INNER JOIN Sales.Customers AS C ON C.custid = O.custid
WHERE (orderdate <= '20081231' AND orderdate >= '20080101')

3.Collection1 不包含 Collection2 的子集就是 2007 年下过订单而在 2008 年下过订单的客户

1
2
3
4
5
6
7
8
9
sql复制代码SELECT DISTINCT C.custid,companyname FROM Sales.Orders O
INNER JOIN Sales.Customers AS C ON C.custid = O.custid
WHERE (orderdate <= '20071231' AND orderdate >= '20070101')
AND C.custid NOT IN
(
SELECT C.custid FROM Sales.Orders O
INNER JOIN Sales.Customers AS C ON C.custid = O.custid
WHERE (orderdate <= '20081231' AND orderdate >= '20080101')
)

方案二:相关子查询 EXISTS+NOT EXISTS

  1. 查询出 20070101~20071231 所有下过订单的客户集合 Collection1
  2. 查询出 20080101~20081231 所有下过订单的客户结合 Collection2

3.Collection1 不包含 Collection2 的子集就是 2007 年下过订单而在 2008 年下过订单的客户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码SELECT  C.custid ,
companyname
FROM Sales.Customers AS C
WHERE EXISTS ( SELECT *
FROM Sales.Orders AS O
WHERE O.custid = C.custid
AND ( orderdate <= '20071231'
AND orderdate >= '20070101'
) )
AND NOT EXISTS ( SELECT *
FROM Sales.Orders AS O
WHERE O.custid = C.custid
AND ( orderdate <= '20081231'
AND orderdate >= '20080101'
) )

由方案一和方案二,我们可以总结出:INNER JOIN+独立子查询可以用 Exists+相关子查询代替

5. 查询订购了第 12 号产品的客户

期望结果:

方案一:内联接多张表

1
2
3
4
5
6
7
sql复制代码SELECT DISTINCT
C.custid ,
companyname
FROM Sales.Customers AS C
INNER JOIN Sales.Orders AS O ON C.custid = O.custid
INNER JOIN Sales.OrderDetails AS D ON O.orderid = D.orderid
WHERE D.productid = '12'

方案二:嵌套相关子查询

1
2
3
4
5
6
7
8
9
10
sql复制代码SELECT  C.custid ,
companyname
FROM Sales.Customers AS C
WHERE EXISTS ( SELECT *
FROM Sales.Orders AS O
WHERE O.custid = C.custid
AND EXISTS ( SELECT *
FROM Sales.OrderDetails AS D
WHERE D.orderid = O.orderid
AND D.productid = '12' ) )

参考资料:

《SQL2008 技术内幕:T-SQL 语言基础》

欢迎关注我的公众号:「悟空聊架构」

作者简介:8 年互联网职场老兵|全栈工程师|90 后超级奶爸|开源践行者|公众号万粉原创号主。 蓝桥签约作者,著有《JVM 性能调优实战》专栏,手写了一套 7 万字 SpringCloud 实战总结和 3 万字分布式算法总结。 欢迎关注我的公众号「悟空聊架构」,免费获取资料学习。

我是悟空,努力变强,变身超级赛亚人!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MyBatis SQL 批量更新(代码➕案例)

发表于 2021-06-21

写于20210618 21:00 北京望京

一条记录update一次,性能比较差,容易造成阻塞。基于 mybatis 批量更新,特此记录。


微信搜:JavaPub ,有疑惑留言。

@[toc]

1.场景

当我们在做更新或者是插入操作时,数据为多对多、一一对应的情况

例如:

1
2
3
4
bash复制代码编号。  名字。  状态
1 tom 0
2 jerry 0
3 jeck 1

代码中循环写入、更新这是大多数人做法,但是肯定不是最优解

2.MyBatis XML

  • 先直接上个终极版

这里数据库中存储了下划线式,代码中用驼峰式。

这里是通过userId修改userStatus。当user_id为1时、user_status为0,当user_id为3时、user_status为1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xml复制代码    <update id="updateBatch">

update
<include refid="tableName"/>
<trim prefix="set" suffixOverrides=",">
<trim prefix="user_status =case" suffix="end,">
<foreach collection="list" item="i" index="index">
<if test="i.userId!=null">
when user_id=#{i.userId} then #{i.userStatus}
</if>
</foreach>
</trim>
</trim>
where user_id in
<foreach collection="list" item="i" index="index" open="(" separator="," close=")">
#{i.userId}
</foreach>

</update>

<trim 属性说明

  1. prefix,suffix 表示在 trim 标签包裹的部分的前面或者后面添加内容
  2. 如果同时有 prefixOverrides,suffixOverrides 表示会用 prefix,suffix 覆盖 Overrides 中的内容。
  3. 如果只有 prefixOverrides,suffixOverrides 表示删除开头的或结尾的 xxxOverides 指定的内容。

2.1.打印sql

1
2
3
bash复制代码==>  Preparing: update `table_test_01` set user_status =case when user_id=? then ? when user_id=? then ? end where user_id in ( ? , ? )
==> Parameters: 1(Long), 10(Integer), 2(Long), 20(Integer), 1(Long), 2(Long)
<== Updates: 2

2.2.数据库结构

SQL结构体:

1
2
3
4
5
6
7
8
9
10
sql复制代码CREATE TABLE `table_test_01`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`status` tinyint(4) NOT NULL DEFAULT '0',
`test_column` varchar(32) NOT NULL DEFAULT '' COMMENT '测试字段',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '测试字段id',
`user_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '测试字段status',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
1
2
3
4
5
sql复制代码INSERT INTO `table_test_01`
VALUES (1, 'tom', 0, '', 1, 10),
(2, 'jetty', 0, '', 2, 20),
(3, 'dog', 0, '', 3, 1),
(4, 'cat', 0, '', 4, 1);

3.实例二

  • 多个字段更新,那就增加 <item 。

使用 case when 语法。

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码UPDATE course
SET name = CASE id
WHEN 1 THEN 'name1'
WHEN 2 THEN 'name2'
WHEN 3 THEN 'name3'
END,
title = CASE id
WHEN 1 THEN 'New Title 1'
WHEN 2 THEN 'New Title 2'
WHEN 3 THEN 'New Title 3'
END
WHERE id IN (1,2,3)

这条sql的意思是,如果id为1,则name的值为name1,title的值为New Title1;依此类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xml复制代码    <update id="updateBatch1" parameterType="list">

update course
<trim prefix="set" suffixOverrides=",">
<trim prefix="name=case" suffix="end,">
<foreach collection="list" item="item" index="index">
<if test="item.name!=null">
when id=#{item.id} then #{item.name}
</if>
</foreach>
</trim>
<trim prefix="title =case" suffix="end,">
<foreach collection="list" item="item" index="index">
<if test="item.title!=null">
when id=#{item.id} then #{item.title}
</if>
</foreach>
</trim>
</trim>
where
<foreach collection="list" separator="or" item="item" index="index">
id=#{item.id}
</foreach>
</update>

4.重点

但是大家要注意一点,这种情况如果出错,我们并不知道是哪条错误,如果使用事务,就会全部回滚,好的办法就是一次批量一部分,分担出错概率。


我是JavaPub,我们下期见。

源码案例下载:download.csdn.net/download/qq…

参考:
blog.csdn.net/lu102418831…
blog.csdn.net/xyjawq1/art…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一个小时肝了一周的需求,看我如何使用EasyCode完成封神

发表于 2021-06-21

这是我参与更文挑战的第 10 天,活动详情查看:更文挑战

作者:JavaGieGie

微信公众号:Java开发零到壹

前言

你想要一小时搞定一周的需求吗?你想要每天可以开心划水、按时下班吗?有了它,再也不用搬砖写注释重复搬砖搬到头大了。

20190326530257_nPYcRv.png

EasyCode是基于IntelliJ IDEA Ultimate版开发的一个代码生成插件, 主要通过自定义模板(基于velocity)来生成各种你想要的代码。 通常用于生成Entity、Dao、Service、Controller。如果你动手能力 强还可以用于生成HTML、JS、PHP等代码。理论上来说只要是与 数据有关的代码都是可以生成的。

可以不用敲一句代码,就可以完成这种结构:

image.png

正文

1. 安装

  1. 打开IDEA -> file -> setting
  2. 点击 file/setting
  3. 找到 plugins (步骤1、2)打开搜索框,输入 Easy Code
  4. 点击 Install 安装
  5. 点击 Other Settings 查看 Easy Code 是否安装成功

image.png

image.png

2. 离线安装

  1. 下载稳定版本的安装包 gitee.com/makejava/Ea…
  2. 打开 IDEA
  3. 点击 file/setting
  4. 找到 Plugins 点击设置
  5. 选择 Install plugin from disk 选择下载好的 zip 包直接导入
  6. 点击 Other Settings 查看 Easy Code 是否安装成功/s

执行完上面的安装步骤,就可以完成安装,我们接下来连接数据库。

3. 新建数据库

1
2
3
4
5
6
7
mysq复制代码CREATE TABLE `user` (
`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(10) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`address` varchar(128) DEFAULT NULL COMMENT '家庭住址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3. 使用idea连接数据库

image.png

4.填写连接信息

输入完连接信息,可以点击Test Connection进行测试。

image.png

4.使用easycode生成代码

选择需要生成的表,这里可以选择多张表,然后右键选择EasyCode -> Generate Code。

image.png

在弹框界面需要注意以下几点:

  1. Group:选择组,这里EasyCode提供了两个默认的,也可以自定义模板
  2. Package,选择代码目录
  3. Template:选择生成代码模板

image.png

点击ok即可生成代码,如下图:

image.png

启动前配置项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
yaml复制代码#application.yml配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/jxey-app?characterEncoding=utf8
username: root
password:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource


#mybatis
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.yunjiahealth.pcloudbusiness.dao
global-config:
# 数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: id_worker
#字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
field-strategy: not_empty
#驼峰下划线转换
column-underline: true
#数据库大写下划线转换
#capital-mode: true
#逻辑删除配置
logic-delete-value: 0
logic-not-delete-value: 1
db-type: h2
#刷新mapper 调试神器
refresh: true
# 原生配置
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
xml复制代码// pom 配置

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

其他配置

  • 在dao层加上@mapper注解
  • 启动类增加@MapperScan(“com.example.demo.dao”)

启动项目

通过上面一系列的配置,我们就能根据快速搭建一个完整的项目,并完成所有单表的增删改查操作。

自定义模板

除了EasyCode自带的两套模板,我们可以根据自身的需求,生成符合自己的代码风格,这里使用的是Velocity,有兴趣的小伙伴可以根据下方花Gie提供的链接快速上手。

image.png

拓展信息

  • Easy Code 官网地址:plugins.jetbrains.com/plugin/1095…
  • Easy Code 开源地址:gitee.com/makejava/Ea…
  • 入门教程地址:gitee.com/makejava/Ea…
  • Velocity语法:ifeve.com/apache-velo…

自定义带Swagger注释模板

这里花Gie写了一个能够生成Swagger注释的一套模板,对Dto/vo/controller中常用的@Api/@ApiModel/@NotNull/@ApiModelProperty实现自动注入,为防止白嫖客,这里就放到公众号下面了,有需要的小伙伴可以关注公众号【Java开发零到壹】回复【EasyCode】获取。

image.png

总结

能够耐心看完本文的小伙伴,也是执着追求划水的高端人士,虽然文章标题有点夸张(小伙伴:臭不要脸,就想骗我进来)。其实我们可能会经常遇到大量增删改查的简单操作,特别是类似管理平台部分。

还有就是用过swagger的小伙伴肯定对注释这块肯定十分头大,这部分的编码没有技术含量又十分耗时,而使用EasyCode就可以很好的解决这个问题。

除了EasyCode,后面也会介绍其他工具,这样我们就可以腾出更多时间去学习有意义的东西,不再一直疯狂搬砖又没有收获,我们要学会使用工具,将无意义的工作时间缩短。

点关注,防走丢

以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花GieGie ,有问题大家随时留言讨论 ,我们下期见🦮。

文章持续更新,可以微信搜一搜 Java开发零到壹 第一时间阅读,并且可以获取面试资料学习视频等,有兴趣的小伙伴欢迎关注,一起学习,一起哈🐮🥃。

qrcode_for_gh_6c44fed6833c_258.jpg

原创不易,你怎忍心白嫖,如果你觉得这篇文章对你有点用的话,感谢老铁为本文点个赞、评论或转发一下,因为这将是我输出更多优质文章的动力,感谢!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

springboot + mybatis 增删改查和动态查询

发表于 2021-06-21

这是我参与更文挑战的第21天,活动详情查看: 更文挑战

MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。

  1. 依赖
1
2
3
4
5
xml复制代码<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

其他依赖,web,lombok,mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
  1. 数据库建表如下,配置连接
    id设为自增
    在这里插入图片描述

配置文件properties

1
2
3
4
ini复制代码#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=

3.创建mvc三层(domain和dao,service,ctrl)

domain

1
2
3
4
5
6
7
8
9
java复制代码@Data
public class Test {
private int id;
private String name;
private int age;
//格式化查询日期
@JsonFormat(pattern = "yyyy-MM-dd")
private Date workDate;
}

dao,增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码//增加方法,传入的是对象 , 也可以使用@Param接收
@Insert("insert into mybatis (name,age,work_date) values(#{name},#{age},#{workDate})")
void insert(Test test);
// @Insert("insert into mybatis (name,age,work_date) values(#{test.name},#{test.age},#{test.workDate})")
// void insert(@Param("test") Test test);

//删除方法,通过id删除
@Delete("delete from mybatis where id = #{id}")
void delete(int id);

//修改方法,通过id修改
@Update("update mybatis set age = #{age} where id =#{id}")
void update(int id,int age);

//查询方法,所有查询
@Select("select * from mybatis")
List<Test> listTest();

4.动态查询详解
1)传多个参数,参数使用包装类型Integer传递

1
2
3
4
5
6
7
8
9
swift复制代码@Select("<script>select * from mybatis m "
+" where 1 = 1"
+"<if test='name!=null and name !=\"\"'>"
+"and m.name = #{name,jdbcType=VARCHAR}"
+"</if>"
+"<if test='age!=null and age !=\"\"'>"
+"and m.age = #{age,jdbcType=INTEGER}"
+"</if> </script>")
List<Test> listTestQuery(String name,Integer age);

2)传对象,和新增时传对象一样

1
2
3
4
5
6
7
8
9
10
swift复制代码@Select("<script>select * from mybatis m "
+" where 1 = 1"
+"<if test='name!=null and name !=\"\"'>"
+"and m.name = #{name,jdbcType=VARCHAR}"
+"</if>"
+"<if test='age!=null and age !=\"\"'>"
+"and m.age = #{age,jdbcType=INTEGER}"
+"</if> "
+"</script>")
List<Test> listTestQuery2(Test test);

5.分页
1)sql分页
传入当前页和分页大小,调用时传入的currIndex-1

1
2
java复制代码@Select("select * from mybatis limit #{currIndex},#{pageSize}")
List<Test> listTest(int currIndex,int pageSize);

2)数组分页,查询所有返回部分

1
2
3
4
5
java复制代码    @RequestMapping("/findall")
public List<Test> findall(int currIndex,int pageSize) {
List<Test> tests = testDao1.listTest();
return tests.subList(currIndex-1,pageSize);
}

6.其他:
接收对象时注意使用@RequestBody 接收
mybatis配置

1
2
3
ini复制代码#mybatis 驼峰映射 、显示sql(com.mybatis.demo.dao 需要显示的包)
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.mybatis.demo.dao=debug

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

盘点 Seata Client 端配置流程

发表于 2021-06-21

这是我参与更文挑战的第13天,活动详情查看: 更文挑战

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

一 . 前言

这一篇来看一下 Seate Client 端的配置文件 , 以及配置的方式和流程 , 先来看一下有哪些配置 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
yml复制代码#====================================Seata Config===============================================
seata:
enabled: true
application-id: business-seata-example
tx-service-group: business-service-seata-service-group # 事务群组(可以每个应用独立取名,也可以使用相同的名字)
client:
rm-report-success-enable: true
rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false)
rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5)
rm-async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000)
rm:
lock:
lock-retry-internal: 10 # 校验或占用全局锁重试间隔(默认10ms)
lock-retry-times: 30 # 校验或占用全局锁重试次数(默认30)
lock-retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略(优先释放本地锁让回滚成功)
tm-commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1)
tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1)
undo:
undo-data-validation: true # 二阶段回滚镜像校验(默认true开启)
undo-log-serialization: jackson # undo序列化方式(默认jackson)
undo-log-table: undo_log # 自定义undo表名(默认undo_log)
log:
exceptionRate: 100 # 日志异常输出概率(默认100)
support:
spring:
datasource-autoproxy: true
service:
vgroup-mapping:
my_test_tx_group: default # TC 集群(必须与seata-server保持一致)
enable-degrade: false # 降级开关
disable-global-transaction: false # 禁用全局事务(默认false)
grouplist:
default: 127.0.0.1:8091
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true)
registry:
file:
name: file.conf
type: nacos
nacos:
server-addr: localhost:8848
namespace:
cluster: default
config:
file:
name: file.conf
type: nacos
nacos:
namespace:
server-addr: localhost:8848

二 . 配置对象

2.1 Seata 顶级对象

该对象对应的 seata.xxx 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码@ConfigurationProperties(prefix = "seata")
@EnableConfigurationProperties(SpringCloudAlibabaConfiguration.class)
public class SeataProperties {
/**
* 是否启用自动配置
*/
private boolean enabled = true;
/**
* application id
*/
private String applicationId;
/**
* 事务服务组
*/
private String txServiceGroup;
/**
* 是否启用数据源bean的自动代理
*/
private boolean enableAutoDataSourceProxy = true;
/**
* 数据源代理模式
*/
private String dataSourceProxyMode = DefaultValues.DEFAULT_DATA_SOURCE_PROXY_MODE;
/**
* 是否使用JDK代理而不是CGLIB代理
*/
private boolean useJdkProxy = false;
/**
* 指定哪个数据源bean不符合自动代理的条件
*/
private String[] excludesForAutoProxying = {};

}

2.2 seata.client 配置对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码 client:
rm-report-success-enable: true
rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false)
rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5)
tm-commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1)
tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1)

// 一级目录下有如下配置 , 对应的对象为 :
public class RmProperties {
private int asyncCommitBufferLimit = 10000;
private int reportRetryCount = 5;
private boolean tableMetaCheckEnable = false;
private boolean reportSuccessEnable = false;
private boolean sagaBranchRegisterEnable = false;
private String sagaJsonParser = fastjson;
}

以上是 client 一级配置对应的类 , 我们来看一下他的子配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码// 二级目录 : seata.client.log
public class LogProperties {
private int exceptionRate = 100;
}

// 二级目录 : seata.client.undo
public class UndoProperties {
private boolean dataValidation = true;
private String logSerialization = "jackson";
private String logTable = "undo_log";
private boolean onlyCareUpdateColumns = true;
}

// 二级目录 : seata.client.support

2.3 seata.service 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Component
@ConfigurationProperties(prefix = "seata.service")
public class ServiceProperties implements InitializingBean {
/**
* vgroup->rgroup
*/
private Map<String, String> vgroupMapping = new HashMap<>();
/**
* group list
*/
private Map<String, String> grouplist = new HashMap<>();
/**
* degrade current not support
*/
private boolean enableDegrade = false;
/**
* disable globalTransaction
*/
private boolean disableGlobalTransaction = false;

}

2.4 seata.transport

我们来看一下配置类和默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
java复制代码@Component
@ConfigurationProperties(prefix = TRANSPORT_PREFIX)
public class TransportProperties {
/**
* tcp, unix-domain-socket
*/
private String type = "TCP";
/**
* NIO, NATIVE
*/
private String server = "NIO";
/**
* enable heartbeat
*/
private boolean heartbeat = true;
/**
* serialization
*/
private String serialization = "seata";
/**
* compressor
*/
private String compressor = "none";

/**
* enable client batch send request
*/
private boolean enableClientBatchSendRequest = true;

}


@Component
@ConfigurationProperties(prefix = "seata.transport.thread-factory")
public class ThreadFactoryProperties {
private String bossThreadPrefix = "NettyBoss";
private String workerThreadPrefix = "NettyServerNIOWorker";
private String serverExecutorThreadPrefix = "NettyServerBizHandler";
private boolean shareBossWorker = false;
private String clientSelectorThreadPrefix = "NettyClientSelector";
private int clientSelectorThreadSize = 1;
private String clientWorkerThreadPrefix = "NettyClientWorkerThread";
}

2.5 seata.config

1
2
3
4
5
6
7
8
9
10
11
java复制代码@Component
@ConfigurationProperties(prefix = CONFIG_PREFIX)
public class ConfigProperties {
/**
* file, nacos, apollo, zk, consul, etcd3, springCloudConfig
*/
private String type = "file";

}

ConfigNacosProperties 等 ConfigXXXProperties

注意 ,此处有多个实现类 , 每一个都对应一个配置类

image.png

2.6 seata.registry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Component
@ConfigurationProperties(prefix = "seata.registry")
public class RegistryProperties {
/**
* file, nacos, eureka, redis, zk, consul, etcd3, sofa
*/
private String type = "file";
/**
* the load balance
*/
private String loadBalance = DEFAULT_LOAD_BALANCE;
/**
* 负载均衡虚拟节点
*/
private int loadBalanceVirtualNodes = VIRTUAL_NODES_DEFAULT;

}

三 . Client 端的初始化流程

3.1 初始化配置类的流程

来看一下主配置类 SeataAutoConfiguration , 此处会扫描 io.seata.spring.boot.autoconfigure.properties 包下的所有配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
java复制代码// Client 的配置化主要基于 SeataAutoConfiguration 类来完成 Client 配置操作

@ComponentScan(basePackages = "io.seata.spring.boot.autoconfigure.properties")
@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
@Configuration
@EnableConfigurationProperties({SeataProperties.class})
public class SeataAutoConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoConfiguration.class);

@Bean(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER)
@ConditionalOnMissingBean(name = {BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER})
public SpringApplicationContextProvider springApplicationContextProvider() {
return new SpringApplicationContextProvider();
}

@Bean(BEAN_NAME_FAILURE_HANDLER)
@ConditionalOnMissingBean(FailureHandler.class)
public FailureHandler failureHandler() {
return new DefaultFailureHandlerImpl();
}

@Bean
@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
@ConditionalOnMissingBean(GlobalTransactionScanner.class)
public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Automatically configure Seata");
}
return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);
}

/**
* 数据源配置
*/
@Configuration
@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = {"enableAutoDataSourceProxy", "enable-auto-data-source-proxy"}, havingValue = "true", matchIfMissing = true)
static class SeataDataSourceConfiguration {

/**
* The bean seataDataSourceBeanPostProcessor.
*/
@Bean(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR)
@ConditionalOnMissingBean(SeataDataSourceBeanPostProcessor.class)
public SeataDataSourceBeanPostProcessor seataDataSourceBeanPostProcessor(SeataProperties seataProperties) {
return new SeataDataSourceBeanPostProcessor(seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
}

/**
* The bean seataAutoDataSourceProxyCreator.
*/
@Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
@ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
}
}
}

其中涉及到以下几个对象 :

  • C- SpringApplicationContextProvider
  • C- FailureHandler
  • C- GlobalTransactionScanner
  • C- SeataDataSourceBeanPostProcessor
  • C- SeataAutoDataSourceProxyCreator

3.1.1 SpringApplicationContextProvider

1
2
3
4
5
6
java复制代码public class SpringApplicationContextProvider implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext);
}
}

这里可以看到 , 调用了ObjectHolder存储了 applicationContext

之前分析 Spring 的时候说过 , Aware 可以用于创建后通知 , 此处是往枚举类中设置了一个参数.

第一次看到枚举类这样用的 ,这是不是实现了单例??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public enum ObjectHolder {

INSTANCE;
private static final int MAP_SIZE = 8;
private static final Map<String, Object> OBJECT_MAP = new ConcurrentHashMap<>(MAP_SIZE);

public Object getObject(String objectKey) {
return OBJECT_MAP.get(objectKey);
}

public <T> T getObject(Class<T> clasz) {
return clasz.cast(OBJECT_MAP.values().stream().filter(clasz::isInstance).findAny().orElseThrow(() -> new ShouldNeverHappenException("Can't find any object of class " + clasz.getName())));
}

public Object setObject(String objectKey, Object object) {
return OBJECT_MAP.putIfAbsent(objectKey, object);
}
}

3.1.2 FailureHandler

FailureHandler 中提供了 以下方法 :

1
2
3
4
5
6
7
8
9
10
11
java复制代码public interface FailureHandler {

// 启动错误
void onBeginFailure(GlobalTransaction tx, Throwable cause);
// 提交异常
void onCommitFailure(GlobalTransaction tx, Throwable cause);
// 回退异常
void onRollbackFailure(GlobalTransaction tx, Throwable originalException);
// 重试操作
void onRollbackRetrying(GlobalTransaction tx, Throwable originalException);
}

默认实现类为 DefaultFailureHandlerImpl , 这里可配置意味着可以自己实现和扩展

3.1.3 GlobalTransactionScanner

该类中涉及几个抽象和接口 :

  • AbstractAutoProxyCreator : 使用AOP代理包装bean的BeanPostProcessor实现
  • ConfigurationChangeListener : 配置修改监听
  • InitializingBean : 初始化调用
  • ApplicationContextAware : Aware 监听处理
  • DisposableBean : 销毁处理

C- AbstractAutoProxyCreator # wrapIfNecessary

AbstractAutoProxyCreator 适用于代理 postProcess , 主要运行的是在 postProcessAfterInitialization 阶段调用 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
java复制代码    // 此方法用于校验
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
try {
synchronized (PROXYED_SET) {
if (PROXYED_SET.contains(beanName)) {
return bean;
}
interceptor = null;
// 检测是否存在 TCC 代理
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
// 创建一个 TccActionInterceptor 拦截器 , 并且添加到监听器
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)interceptor);
} else {
// 获取目标类class
Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
//
Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);

// 判断是否存在 GlobalTransactional 注解 , 不存在直接返回
if (!existsAnnotation(new Class[]{serviceInterface})
&& !existsAnnotation(interfacesIfJdk)) {
return bean;
}

// 如果存在注解 , 则为其创建一个 GlobalTransactionalInterceptor 用于事务拦截
if (interceptor == null) {
if (globalTransactionalInterceptor == null) {
globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
ConfigurationCache.addConfigListener(
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)globalTransactionalInterceptor);
}
interceptor = globalTransactionalInterceptor;
}
}

// 如果不是 AOP 代理 , 则直接放回 , 否者 , 进行 AOP 实际处理
if (!AopUtils.isAopProxy(bean)) {
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
for (Advisor avr : advisor) {
advised.addAdvisor(0, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
} catch (Exception exx) {
throw new RuntimeException(exx);
}
}

PS : AdvisedSupport 作用

简单点说 , 就是为这个类做了代理 . 使用AOP代理包装每个合格bean的BeanPostProcessor实现,在调用bean本身之前将委托给指定的拦截器。

ConfigurationChangeListener

1
2
3
4
5
6
7
8
9
10
java复制代码@Override
public void onChangeEvent(ConfigurationChangeEvent event) {
if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) {
disableGlobalTransaction = Boolean.parseBoolean(event.getNewValue().trim());
if (!disableGlobalTransaction && initialized.compareAndSet(false, true)) {
// 可以看到 , 再修改之后会重新初始化一次客户端
initClient();
}
}
}

InitializingBean

1
2
3
4
5
6
7
8
9
10
11
java复制代码public void afterPropertiesSet() {
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)this);
if (disableGlobalTransaction) {
return;
}
if (initialized.compareAndSet(false, true)) {
// 同样 , 初始化的时候调用了 initClient
initClient();
}
}

这里看到此处初始化了 TM 及 RM Client 端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码private void initClient() {

if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
}
//init TM
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);

//init RM
RMClient.init(applicationId, txServiceGroup);

// 注册销毁钩子
registerSpringShutdownHook();

}

ApplicationContextAware

此处通过 Aware 完成通知设置 ApplicationContext

1
2
3
4
java复制代码public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.setBeanFactory(applicationContext);
}

DisposableBean

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public void destroy() {
ShutdownHook.getInstance().destroyAll();
}

private void registerSpringShutdownHook() {
if (applicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) applicationContext).registerShutdownHook();
ShutdownHook.removeRuntimeShutdownHook();
}
ShutdownHook.getInstance().addDisposable(TmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
ShutdownHook.getInstance().addDisposable(RmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
}

3.1.4 SeataDataSourceBeanPostProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor {

private static final Logger LOGGER = LoggerFactory.getLogger(SeataDataSourceBeanPostProcessor.class);

private final List<String> excludes;
private final BranchType dataSourceProxyMode;

public SeataDataSourceBeanPostProcessor(String[] excludes, String dataSourceProxyMode) {
this.excludes = Arrays.asList(excludes);
this.dataSourceProxyMode = BranchType.XA.name().equalsIgnoreCase(dataSourceProxyMode) ? BranchType.XA : BranchType.AT;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource) {
//当不在排除时,放置和初始化代理
if (!excludes.contains(bean.getClass().getName())) {
//只放置和初始化代理,不返回代理
DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode);
}

//如果为SeataDataSourceProxy,则返回原始数据源
if (bean instanceof SeataDataSourceProxy) {
return ((SeataDataSourceProxy) bean).getTargetDataSource();
}
}
return bean;
}
}


SeataDataSourceProxy 是一个接口 , 它提供了多个方法 :
public interface SeataDataSourceProxy extends DataSource {

// 获取目标数据源。
DataSource getTargetDataSource();

// 得到分支类型。
BranchType getBranchType();
}

而该代理类 , 有如下几个实现 :

seata-system-SeataDataSourceProxy.png

SeataAutoDataSourceProxyCreator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class);
private final List<String> excludes;
private final Advisor advisor;

public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
this.excludes = Arrays.asList(excludes);
this.advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode));
setProxyTargetClass(!useJdkProxy);
}

@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
return new Object[]{advisor};
}

@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
return !DataSource.class.isAssignableFrom(beanClass) ||
SeataProxy.class.isAssignableFrom(beanClass) ||
excludes.contains(beanClass.getName());
}
}

总结

其实整篇文章最重要的一个操作就是构建了一个 globalTransactionalInterceptor , 后续主流程中我们会用上这个拦截器

注意 , 每个标注了注解的方法都会通过拦截器进行处理.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Boot整合Elasticsearch详细教程

发表于 2021-06-21

一、主要内容

  • spring boot 引入Elasticsearch
  • ElasticsearchTemplate的使用
  • ElasticsearchRepository的使用

二、环境整合

创建Elasticsearch工程,引入依赖

一般情况下,都会单独创建一个工程,用于操作es。

1
2
3
4
5
pom复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.5.1</version>
</dependency>

版本统一

目前springboot-data-elasticsearch最新版本是2.5.1,这个版本使用的es版本为7.12.1,需要注意的是,我们这里引入的版本需要与服务器安装的版本一致。

可以通过Maven仓库进行查看版本信息,地址:mvnrepository.com/artifact/or…

修改配置文件

1
2
3
4
5
yml复制代码spring: 
data:
elasticsearch:
cluster-name: es-cluster
cluster-nodes: 10.133.28.55:9300

三、ElasticsearchTemplate的使用

ElasticsearchTemplate类似于RedisTemplate,是spring提供的较为底层的与Elasticserch交互的类。

常用注解

@Document

注解作用在类上,标记实体类为文档对象,指定实体类与索引对应关系。常用配置项有:

  • indexName:索引名称
  • type: 索引类型,默认为空
  • shards: 主分片数量,默认5
  • replicas:复制分片数量,默认1
  • createIndex:创建索引,默认为true

@Id

指定文档ID,加上这个注解,文档的_id会与我们的数据ID是一致的,否则在不给定默认值的情况下,es会自动创建。

@Field

指定普通属性,标明这个是文档中的一个字段。常用的配置项有:

  • type: 对应Elasticsearch中属性类型。默认自动检测。使用FiledType枚举可以快速获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public enum FieldType {
Text,//会进行分词并建立索引的字符类型
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,//自动判断字段类型
Nested,//嵌套对象类型
Ip,
Attachment,
Keyword//不会进行分词建立索引的类型
}
  • index: 是否创建倒排索引,一般不需要分词的属性不需要创建索引
  • analyzer:指定索引类型。
  • store:是否进行存储,默认不进行存储。

其实不管我们将store值设置为true或false,elasticsearch都会将该字段存储到Field域中;但是他们的区别是什么?

1. store = false时,默认设置;那么给字段只存储在"\_source"的Field域中;
2. store = true时,该字段的value会存储在一个跟\_source平级的独立Field域中;同时也会存储在\_source中,所以有两份拷贝。

那么我们在什么样的业务场景下使用store field功能?

1. \_source field在索引的mapping 中disable了。这种情况下,如果不将某个field定义成store=true,那些将无法在返回的查询结果中看到这个field。
2. \_source的内容非常大。这时候如果我们想要在返回的\_source document中解释出某个field的值的话,开销会很大(当然你也可以定义source filtering将减少network overhead),比例某个document中保存的是一本书,所以document中可能有这些field: title, date, content。假如我们只是想查询书的title 跟date信息,而不需要解释整个\_source(非常大),这个时候我们可以考虑将title, date这些field设置成store=true。

创建实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码// 关联的索引是item,类型是_doc,直接使用而不创建索引
@Document(indexName = "item",type = "_doc",createIndex = "false")
public class Item {
@Id
private Long id;
// title使用ik进行分词
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;
// brand 不被分词
@Field(type=FieldType.Keyword)
private String brand;
@Field(type=FieldType.Double)
private Double price;
// brand 不被分词,且不创建索引
@Field(index = false,type = FieldType.Keyword)
private String images;
}

索引管理

ElasticsearchTemplate提供了创建索引的方法,但是不建议使用 ElasticsearchTemplate 对索引进行管理(创建索引,更新映射,删除索引)。

索引就像是数据库或者数据库中的表,我们平时是不会是通过java代码频繁的去创建修改删除数据库或者表的相关信息,我们只会针对数据做CRUD的操作。

1
2
3
4
java复制代码// 创建索引
elasticsearchTemplate.createIndex(Class<T> clazz);
// 删除索引,有好几个方法,有兴趣的同学可以自行翻阅源码
elasticsearchTemplate.deleteIndex(Class<T> clazz);

创建文档

新增单条文档

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ESTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void insertItemDoc(){
Item item = new Item(1001L,"XXX1","XXX1","XXX1");
IndexQuery indexQuery = new IndexQueryBuilder().withObject(item).build();
elasticsearchTemplate.index(indexQuery);
}
}

批量新增文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ESTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void insertItemDocBulk() {
List<Item> list = new ArrayList<>();
list.add(new IndexQueryBuilder().withObject(new Item(1001L,"XXX1","XXX1","XXX1")).build())
list.add(new IndexQueryBuilder().withObject(new Item(1002L,"XXX2","XXX2","XXX2")).build())
IndexQuery indexQuery = new IndexQueryBuilder().withObject(item).build();
elasticsearchTemplate.index(indexQuery);
}
}

删除文档

删除都是根据主键进行删除,提供了两个方法:

  • delete(String indexName,String typeName,String id); 通过字符串指定索引,类型和id值;
  • delete(Class,String id) 第一个参数传递实体类类类型,建议使用此方法,减少索引名和类型名由于手动编写出现错误的概率。

代码比较简单,不再示例。

修改文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Test
public void updateItemDoc() {
Map<String, Object> sourceMap = new HashMap<>();
sourceMap.put("title", "YYY");

IndexRequest indexRequest = new IndexRequest();
indexRequest.source(sourceMap);

UpdateQuery updateQuery = new UpdateQueryBuilder()
.withClass(Stu.class)
.withId("1001")
.withIndexRequest(indexRequest)
.build();
elasticsearchTemplate.update(updateQuery);
}

查询文档

模糊查询

使用查询条件与所有的field进行匹配,进行查询

对于一些入参是不是看着很陌生?感觉头大是不是,别急,最后有解释。

1
2
3
4
5
6
7
8
9
10
11
java复制代码@Test
public void queryItemDoc() {
// 少年去和所有field进行匹配
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("少年");
// 查询条件SearchQuery是接口,只能实例化实现类。
SearchQuery searchQuery = new NativeSearchQuery(queryStringQueryBuilder);
List<Item> list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}

使用match_all查询所有文档

1
2
3
4
5
6
7
8
java复制代码@Test
public void matchAllItemDoc() {
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
List<Item> list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}

使用match查询分页文档并排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码@Test
public void matchItemDoc() {
// 构造分页信息:第一个参数是页码,从0算起。第二个参数是每页显示的条数
Pageable pageable = PageRequest.of(0, 2);
// 构造排序信息
SortBuilder sortBuilder = new FieldSortBuilder("price")
.order(SortOrder.DESC);
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("title", "english"))
.withPageable(pageable)
.withSort(sortBuilder)
.build();
AggregatedPage<Item> pagedItem = elasticsearchTemplate.queryForPage(query, Item.class);
System.out.println("查询后的总分页数目为:" + pagedItem.getTotalPages());
List<Item> list = pagedItem.getContent();
for (Item item : list) {
System.out.println(item);
}
}

多条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码 @Test
public void mustShouldItemDoc(){
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List<QueryBuilder> listQuery = new ArrayList<>();
listQuery.add(QueryBuilders.matchPhraseQuery("title","XX"));
listQuery.add(QueryBuilders.rangeQuery("price").gte(10).lte(100));
//boolQueryBuilder.should().addAll(listQuery); // 逻辑或
boolQueryBuilder.must().addAll(listQuery); // 逻辑与
SearchQuery searchQuery = new NativeSearchQuery(boolQueryBuilder);
List<Item> list = elasticsearchTemplate.queryForList(searchQuery, Item.class);
for(Item item : list){
System.out.println(item);
}
}

高亮查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
java复制代码@Test
public void highlightStuDoc(){
// 指定高亮的信息用什么标签包裹,默认使用<em>
String preTag = "<font color='red'>";
String postTag = "</font>";

Pageable pageable = PageRequest.of(0, 10);

SortBuilder sortBuilder = new FieldSortBuilder("price")
.order(SortOrder.DESC);
// 构造查询规则
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("title", "XXX"))
.withHighlightFields(new HighlightBuilder.Field("title")
.preTags(preTag)
.postTags(postTag))
.withSort(sortBuilder)
.withPageable(pageable)
.build();
AggregatedPage<Item> paged = esTemplate.queryForPage(query, Item.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
// 定义一个list存放最终返回的数据
List<Item> listHighlight = new ArrayList<>();
// 取出命中的信息
SearchHits hits = response.getHits();
for (SearchHit h : hits) {
// 取出高亮的数据
HighlightField highlightField = h.getHighlightFields().get("title");
String title = highlightField.getFragments()[0].toString();
// 取出其他数据
Object id = (Object)h.getSourceAsMap().get("id");
String brand = (String)h.getSourceAsMap().get("brand");
Object price = (Object)h.getSourceAsMap().get("price");
String images = (String)h.getSourceAsMap().get("images");
// 处理数据
Item itemHL = new Item();
item.setId(Long.valueOf(id.toString()));
item.setTitle(title);
item.setBrand(brand);
item.setPrice(Double.valueof(price.toString));
item.setImages(images);

listHighlight.add(itemHL);
}

if (listHighlight.size() > 0) {
return new AggregatedPageImpl<>((List<T>)listHighlight);
}
return null;
}
});
System.out.println("查询后的总分页数目为:" + paged.getTotalPages());
List<Item> list = paged.getContent();
for (Item item : list) {
System.out.println(item);
}
}

四、ElasticsearchRepository的使用

ElasticsearchRepository是基于ElasticsearchTemplate封装的一个接口用于操作Elasticsearch。

它主要干了啥呢?我们能通过它干些什么呢?

  • 它主要帮我们封装了一些常用的方法。如下图:

image-20210617133707448

  • 我们还可以通过使用Not Add Like Or Between等关键词自动创建查询语句。它会自动帮你完成,无需写实现类。

比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。

【自定义方法命名约定】:

image-20210617134220264

自定义Repository接口

这个是遵循SpringData的规范,在自定义接口中直接指定查询方法名称便可查询,无需进行实现。在idea里面也会提示ES里面有的字段,写起来挺方便的。

1
2
3
4
5
6
7
8
9
10
java复制代码/**
* 需要继承ElasticsearchRepository接口
*/
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
/**
* 方法名必须遵守SpringData的规范
* 价格区间查询
*/
List<Item> findByPriceBetween(double price1, double price2);
}

创建文档

新增单条文档

可以直接调用ItemRepository的save()方法。

1
2
3
4
5
6
7
java复制代码@Autowired
private ItemRepository itemRepository;
@Test
public void insert(){
Item item = new Item(1L, "小米手机7", "小米", 3499.00, "http://xxx/xxx.jpg");
itemRepository.save(item);
}

批量新增文档

插入多条文档数据使用的saveAll()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Autowired
private ItemRepository itemRepository;
@Test
public void indexList() {
List<Item> list = new ArrayList<>();
list.add(new Item(1L, "小米手机7", "小米", 3299.00, "http://xxx/1.jpg"));
list.add(new Item(2L, "坚果手机R1", "锤子", 3699.00, "http://xxx/1.jpg"));
list.add(new Item(3L, "华为META10", "华为", 4499.00, "http://xxx/1.jpg"));
list.add(new Item(4L, "小米Mix2S", "小米", 4299.00, "http://xxx/1.jpg"));
list.add(new Item(5L, "荣耀V10", "华为", 2799.00, "http://xxx/1.jpg"));
// 接收对象集合,实现批量新增
itemRepository.saveAll(list);
}

修改文档

修改文档数据也是使用的save()方法。

查询参数介绍

我们通过上面的方法截图,可以看出来ElasticsearchRepository提供了一些特殊的search()方法,用来构建一些查询。这几个方法里面的参数主要是SearchQuery和QueryBuilder,所以要完成一些特殊查询主要就是构建这两个参数。

SearchQuery

通过源码我们可以看到,SearchQuery是一个接口,有一个实现类叫NativeSearchQuery,实际使用中,我们的主要任务就是构建NativeSearchQuery来完成一些复杂的查询的。

image-20210618093342034

我们可以看到NativeSearchQuery的主要入参是QueryBuilder、SortBuilder、HighlightBuilder

一般情况下,我们不是直接是new NativeSearchQuery,而是使用NativeSearchQueryBuilder来完成NativeSearchQuery的构建。

1
2
3
4
5
6
scss复制代码NativeSearchQueryBuilder		
.withQuery(QueryBuilder1)
.withFilter(QueryBuilder2)
.withSort(SortBuilder1)
.withXXXX()
.build();

QueryBuilder

QueryBuilder是一个接口,它有非常多的实现类,可以用于不同的查询条件构建。

要构建QueryBuilder,我们可以使用工具类QueryBuilders,里面有大量的方法用来完成各种各样的QueryBuilder的构建,字符串的、Boolean型的、match的、地理范围的等等。

1
2
3
4
java复制代码// 构建一个普通的查询
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("spring boot OR 书籍"))
.build();

查询方法跟上面介绍的ElasticsearchTemplate差不多,就不再单独举例了。

综上,我们在一般的场景下使用ElasticsearchRepository就可以满足我们的需要,在一些复杂的查询场景下,可以配合ElasticsearchTemplate来使用。

最后,欢迎关注「Hugh的白板」公号,私信我一起学习,一起成长!
最最后,如果对你有一点帮助,可否给一个赞?非常感谢哦~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

猛男,从不用fuck命令

发表于 2021-06-21

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

Linux上有一个命令,带着动物最原始的冲动,从一开始在github露面,就获得了无数粉丝的关注,如今已经有60k+的star数。

这个带着光环的命令,就是fuck,捷克斯洛伐克的伐克。这个词,主要用于形容你在把一件事情办砸的时候、或者犯了一些低级的错误,心中那种愤懑的情绪,无处发泄的感觉。

在线markdown脑图直接看,访问 mind.xjjdog.cn 即可获取

百闻不如一见,我们看一下它到底是啥。

example.gif

很好很好,图片表达的,就是我们这种情绪。它能让我们偏离正轨的命令,重新回到赛道上来,非常有的有魔力。

比如,你忘记了git分支切换的命令,输入出了下面的东东。这对一些英文盲来说,是常见的事。

1
bash复制代码git brnch

brnch是个什么鬼?我的双手怎么不听大脑指挥了?fuck!当你这么想的时候,你也会这么做,fuck命令就会给你修正。

1
2
3
bash复制代码fuck
git branch [enter/↑/↓/ctrl+c]
* master

它证明了,在Linux上,没有什么不是来一炮不能解决的了。

  1. 如何安装它?

fuck命令是个python程序,所以我们可以使用pip来安装。

1
bash复制代码pip3 install thefuck

然后,在.bashrc或者.zshrc下(你的$SHELL)里添加一行。

1
bash复制代码eval $(thefuck --alias)

我们来测试一下。输入ystemct

1
2
bash复制代码>> ystemct
zsh: command not found: ystemct

我们输入fuck,就可以修正成systemctl。

除了能够修正错误的拼写,fuck无所不能。

  • 忘记了加sudo执行一些超级权限命令,可以fuck解决
  • 当一些明显的参数输入出现了错误,也可以fuck它

很好用是不是?但是,猛男并不需要fuck命令。

why?我们需要看下它的实现原理。

  1. 它的原理

fuck命令,是不是洞悉了我的脑电波,在不知不觉之间窃听了我的想法?才能准确的识别我想要真正看的事?

这太魔幻了,应该是深度学习,把常见的输入错误和解决方式给强行学习了一遍。

但也不是。

在thefuck/rules目录下,静悄悄的躺着上百个规则。所谓的规则,就是白名单。

image-20210429103606857.png

仅仅是git命令,就给出了几十项的匹配,可以说fuck命令对常见的指令是下足了功夫。

所以,你可以创建自己的规则。实现两个方法,然后把它们放在~/.config/thefuck/rules下即可。

1
2
python复制代码match(command: Command) -> bool
get_new_command(command: Command) -> str | list[str]

还是很方便的。

  1. 不要形成依赖

但是猛男,从来不用fuck命令。

规则和修正,都在猛男的脑子里。

fuck命令的智能,并不是真正的智能,它是经验教条主义的集合,是规则的穷举,是有限的集合。

每一次命令输入,都是一种考验。当这种考验形成了精神反射,fuck命令就变的可有可无,最后变成了一个笑话。fuck命令,是一种精神的腐朽,是妄图使用工具实现智能化的妄想。不需要这些遮遮掩掩,扭扭捏捏,单刀直入直奔主题是猛男的真实表现。

所以,从安装到卸载fuck命令,猛男只花费了不到2分钟。

作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring IoC容器设计原理(上)

发表于 2021-06-21

Spring IoC容器设计原理

对Spring IoC容器,总是能听到几个类似的描述:”Spring IoC原理”,”Spring IoC加载过程”等等,这两个描述都对,容器加载的过程,也就是它的原理。关于Spring IoC,先梳理一下容器体系,主线有两条,一条是以BeanFactory继承体系,一条是以ApplicationContext继承体系。

image.png

BeanFactory作为基本的容器接口,提供了最基本的功能,从方法名可以看出各个方法的功能

image.png

image.png

ApplicationContext是高级形态的容器接口,从继承体系看到,ApplicationContext继承了LifeCycle,具备了在容器启动或者关闭时,触发start/stop调用,在AbstractApplicationContext里,会触发各种实现了LifeCycle的start/stop方法的调用。MessageSource跟国际化有关,ApplicationEventPublisher在IoC容器体系中,是一个事件发布器,发布ApplicationEvent事件。

以上的整体体系,大概有个印象即可,接下来,我们从一个简单的测试用例开始,一步步了解IoC容器到底是怎样的,这里还是以最常用的ClassPathXmlApplicationContextTests入手。

容器的加载过程

1、定位Bean的资源文件
2、从资源文件里解析并转为Spring的BeanDefinition,并放入容器
3、初始化Bean,依赖注入

ClassPathXmlApplicationContextTests

1
2
3
4
5
6
java复制代码@Test
public void testSingleConfigLocation() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(FQ_SIMPLE_CONTEXT);
assertTrue(ctx.containsBean("someMessageSource"));
ctx.close();
}

这里创建了ClassPathXmlApplicationContext对象,构造器最终是调用

ClassPathXmlApplicationContext

1
2
3
4
5
6
7
java复制代码public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

refresh是IoC容器启动的核心方法

AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
java复制代码
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备容器环境,如设置启动时间,激活flag状态
prepareRefresh();

// 由子类refresh容器
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

obtainFreshBeanFactory方法

AbstractApplicationContext

1
2
3
4
5
6
7
8
java复制代码protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

refreshBeanFactory()和getBeanFactory()都是抽象方法,留给子类实现

AbstractRefreshableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码protected final void refreshBeanFactory() throws BeansException {
// 如果容器已存在,就销毁
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 加载Bean的入口,里面的调用栈很深,比较容易迷失自我
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

refreshBeanFactory()首先是判断容器是否存在,存在就销毁,然后重新创建一个DefaultListableBeanFactory,接下来的核心是loadBeanDefinitions(beanFactory)

loadBeanDefinitions()方法

AbstractRefreshableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// beanDefinitionReader可以由子类实现,比如我们使用xml定义Bean,使用的是XmlBeanDefinitionReader
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

从一开始的setConfigLocations,已经定位到Bean的资源文件,而这里,就是要加载并解析Bean定义,解析的过程实质是委托给XmlBeanDefinitionReader

XmlBeanDefinitionReader

1
2
3
4
5
6
7
8
java复制代码protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
...
}

真正把xml解析成BeanDefinition是在registerBeanDefinition方法

XmlBeanDefinitionReader

1
2
3
4
5
6
java复制代码public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

在这个方法里,documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) 进行解析,具体的解析工作,是委托给BeanDefinitionParserDelegate类完成的

在这里要注意,XmlBeanDefinitionReader是持有BeanFactory对象的,createReaderContext(resource)方法里,把XmlBeanDefinitionReader的this传递给了XmlReaderContext(createReaderContext的返回对象),也就是说,XmlReaderContext间接持有BeanFactory对象

DefaultBeanDefinitionDocumentReader

1
2
3
4
5
6
java复制代码public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}

DefaultBeanDefinitionDocumentReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}

preProcessXml(root);
// 委托BeanDefinitionParserDelegate解析
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

this.delegate = parent;
}

从这个方法可以知道,真正解析的确实是BeanDefinitionParserDelegate,而BeanDefinitionParserDelegate也是间接持有BeanFactory对象。继续进入parseBeanDefinitions(root, this.delegate),会进入到

AbstractBeanDefinitionParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
// BeanDefinition封装成BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 注册BeanDefinitionHolder
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
catch (BeanDefinitionStoreException ex) {
String msg = ex.getMessage();
parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
return null;
}
}
return definition;
}

这里再说明一下,ParserContext也是间接持有BeanFactory对象,而直接持有的是XmlReaderContext对象。BeanDefinition已经解析出来,并且再次封装为BeanDefinitionHolder,接下来就该是放入到容器里了,逻辑在registerBeanDefinition(holder, parserContext.getRegistry());进去后,会进入到BeanDefinitionReaderUtils

BeanDefinitionReaderUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {

// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

再走进registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()),这个方法有点长,省去了前面部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
java复制代码public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

// 省略...

if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {

// ...
// 如果已存在,直接覆盖
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}

if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

结果一目了然,this.beanDefinitionMap.put(beanName, beanDefinition); 所以说,Spring IoC容器本质就是个HashMap。解析工作到这,基本ok了,但是放入容器的beanDefinition,解决了依赖的问题了吗?没有。

到这里,我们已经进入到很深的层级了,所以要退回去,退回到AbstractApplicationContext的obtainFreshBeanFactory(),其实这个方法也基本结束了,直接返回beanFactory,所以我们还得回到refresh(),再贴一下代码

prepareBeanFactory方法

AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
java复制代码public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备容器环境,如设置启动时间,激活flag状态
prepareRefresh();

// 由子类refresh容器
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 准备容器的一些特性,比如ClassLoader、post-processors
prepareBeanFactory(beanFactory);

try {
// 空实现,由子类注册callback
postProcessBeanFactory(beanFactory);

// 执行eBeanFactoryPostProcessor的方法
invokeBeanFactoryPostProcessors(beanFactory);

// 注册BeanPostProcessors
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

prepareBeanFactory方法,先看一下代码

AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
java复制代码protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);

// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

// Register default environment beans.
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}

这里比较感兴趣的是注册的Aware类,Aware类是Spring提供的一种回调机制,比较常用的如ApplicationContextAware,这里实质是放在一个HashSet里。Aware本身是一个没有定义方法的接口,ApplicationContextAware只有一个setApplicationContext方法,ApplicationEventPublisherAware只有一个setApplicationEventPublisher方法,那么这些Aware方法,是在哪里调用呢?

查一下测试demo:org.springframework.context.support.StaticApplicationContextTests#count,另一个类是org.springframework.context.ACATester#setApplicationContext,断点setApplicationContext,启动count

image.png

看调用链得知,是在实例化Bean的时候调用的,实例化过程,后面会继续讲,从调用链来看,有意思的是ApplicationContextAwareProcessor类,这是个BeanPostProcessor,是在Bean实例化时做扩展的,很多场景都有用到,BeanPostProcessor提供了两个方法postProcessBeforeInitialization和postProcessAfterInitialization,所以得知,Aware的那些方法,是在BeanPostProcessor触发调用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}

postProcessBeanFactory方法是抽象方法,由子类实现,如AbstractRefreshableWebApplicationContext,把servletContext和servletConfig封装成BeanPostProcessor,注册到容器中

invokeBeanFactoryPostProcessors方法

1
2
3
4
5
6
7
8
9
10
java复制代码protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}

invokeBeanFactoryPostProcessors主要是执行之前注册的BeanFactoryPostProcessor,BeanFactoryPostProcessor和BeanPostProcessor一样,都是Spring的扩展点,单二者的使用场景不同,BeanFactoryPostProcessor提供了一个方法postProcessBeanFactory,在容器的BeanFactory初始化之后(指BeanDefinition已经加载进容器了,但是还没实例化),可以通过这个方法去对早期暴露的Bean覆盖或者增加Bean的一些配置

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public interface BeanFactoryPostProcessor {

/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

一个典型的例子是ConfigurationClassPostProcessor,该接口继承BeanFactoryPostProcessor,用来对Configure配置类做增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码/**
* Prepare the Configuration classes for servicing bean requests at runtime
* by replacing them with CGLIB-enhanced subclasses.
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}

enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

registerBeanPostProcessors方法

registerBeanPostProcessors方法注册BeanPostProcessor,在Bean实例化是调用其方法,该方法委托PostProcessorRegistrationDelegate的registerBeanPostProcessors实例化和注册BeanPostProcessor,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
java复制代码public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

// Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}

// First, register the BeanPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

// Next, register the BeanPostProcessors that implement Ordered.
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);

// Now, register all regular BeanPostProcessors.
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

// Finally, re-register all internal BeanPostProcessors.
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);

// Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

这个方法的逻辑挺简单,主要是把BeanPostProcessor根据PriorityOrdered和Ordered排序,并添加到BeanFactory里,最后是把没有排序的也添加到BeanFactory

到此,需要说明一下,BeanDefinition已经注册到容器里了,但是还没进行实例化,也没有进行依赖注入,BeanPostProcessor也注册到容器了。

看到这,可以发现AbstractApplicationContext继承体系,就是典型的模板方法的使用案例

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

电商技术总结之SpringCloud+SpringBoot+

发表于 2021-06-21

近期我参与了公司电子商务平台中“海播”模块设计,其中包括直播、短视频带货两个模块,下面介绍一下直播带货模块:

业务流程如下

第一步:商家以“商家入驻”模式入驻电子商务平台后,对自己的店铺进行装修、发布商品等操作(具体会在后面商家详情里面进行讲解)。

第二步:商家对商品设置分销,商家在发布商品的时候,设置商品分销比例,如:一个杯子标价为120元,其中拿出20元进行分销设计,其中14元钱设置为一级分销,剩下的6元钱设置为二级分销。

第三步:如果用户在平台上进行直播带货,首先要开通直播服务,如:上传真实资料,购买主播服务(不是所有人都可以进行免费直播)。

第四步:主播提交资料后,后台进行严格审核后方可直播。

第五步: 成为主播后,主播可以打开主播端,可直接进行直播,如:创建直播间、分享直播间、创建预播、可以去平台选择自己要带的货(商品),数据统计等。

平台、技术、架构、设计思想

涉及平台

平台管理、商家端(PC端、手机端)、买家平台(H5/公众号商城、小程序商城、APP端(IOS/Android)、微服务平台(业务服务)、系统服务(SpringCloud相关:Eureka、Config、Gateway)

核心架构

Spring Cloud、Spring Boot、Mybatis、Redis、RabbitMQ、

前端框架

VUE、Uniapp、Bootstrap/H5/CSS3、IOS、Android、小程序

核心思想

分布式、微服务、云架构、模块化、原子化、热插拔

开发模式

前后端分离、微服务开发、持续集成、集群部署、前后端分离、支持阿里Docker

创建直播间介绍

进入主播端,点击“创建直播”,进入创建直播界面,添加直播间封面、直播标题、添加宝贝(商品),如下图:

image.png

2.jpg

3.jpg

4.jpg

5.jpg

6.jpg

前端直播列表(C端观看)

7.jpg

8.jpg

后台管理截图

9.png

10.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…636637638…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%