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

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


  • 首页

  • 归档

  • 搜索

Java 常用的几个 Json 库,性能强势对比! 简单介绍

发表于 2021-06-20

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

本篇通过JMH来测试一下Java中几种常见的JSON解析库的性能。每次都在网上看到别人说什么某某库性能是如何如何的好,碾压其他的库。但是百闻不如一见,只有自己亲手测试过的才是最值得相信的。

JSON不管是在Web开发还是服务器开发中是相当常见的数据传输格式,一般情况我们对于JSON解析构造的性能并不需要过于关心,除非是在性能要求比较高的系统。

目前对于Java开源的JSON类库有很多种,下面我们取4个常用的JSON库进行性能测试对比, 同时根据测试结果分析如果根据实际应用场景选择最合适的JSON库。

这4个JSON类库分别为:Gson,FastJson,Jackson,Json-lib。

简单介绍

选择一个合适的JSON库要从多个方面进行考虑:

  • 字符串解析成JSON性能
  • 字符串解析成JavaBean性能
  • JavaBean构造JSON性能
  • 集合构造JSON性能
  • 易用性

先简单介绍下四个类库的身份背景

Gson

项目地址:github.com/google/gson

Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。在使用这种对象转换之前,需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson完全可以实现复杂类型的json到bean或bean到json的转换,是JSON解析的神器。

FastJson

项目地址:github.com/alibaba/fas…

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。

Jackson

项目地址:github.com/FasterXML/j…

Jackson是当前用的比较广泛的,用来序列化和反序列化json的Java开源框架。Jackson社区相对比较活跃,更新速度也比较快, 从Github中的统计来看,Jackson是最流行的json解析器之一,Spring MVC的默认json解析器便是Jackson。

Jackson优点很多:

  • Jackson 所依赖的jar包较少,简单易用。
  • 与其他 Java 的 json 的框架 Gson 等相比,Jackson 解析大的 json 文件速度比较快。
  • Jackson 运行时占用内存比较低,性能比较好
  • Jackson 有灵活的 API,可以很容易进行扩展和定制。

目前最新版本是2.9.4,Jackson 的核心模块由三部分组成:

  1. jackson-core 核心包,提供基于”流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
  2. jackson-annotations 注解包,提供标准注解功能;
  3. jackson-databind 数据绑定包,提供基于”对象绑定” 解析的相关 API( ObjectMapper )和”树模型” 解析的相关 API(JsonNode);基于”对象绑定” 解析的 API 和”树模型”解析的 API 依赖基于”流模式”解析的 API。

为什么Jackson的介绍这么长啊?因为它也是本人的最爱。

Json-lib

项目地址:json-lib.sourceforge.net/index.html

json-lib最开始的也是应用最广泛的json解析工具,json-lib 不好的地方确实是依赖于很多第三方包,对于复杂类型的转换,json-lib对于json转换成bean还有缺陷, 比如一个类里面会出现另一个类的list或者map集合,json-lib从json到bean的转换就会出现问题。json-lib在功能和性能上面都不能满足现在互联网化的需求。

编写性能测试

接下来开始编写这四个库的性能测试代码。

添加maven依赖

当然首先是添加四个库的maven依赖,公平起见,我全部使用它们最新的版本:

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
xml复制代码<!-- Json libs-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>

四个库的工具类

FastJsonUtil.java

1
2
3
4
5
6
7
8
9
typescript复制代码public class FastJsonUtil {
public static String bean2Json(Object obj) {
return JSON.toJSONString(obj);
}

public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return JSON.parseObject(jsonStr, objClass);
}
}

GsonUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript复制代码public class GsonUtil {
private static Gson gson = new GsonBuilder().create();

public static String bean2Json(Object obj) {
return gson.toJson(obj);
}

public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return gson.fromJson(jsonStr, objClass);
}

public static String jsonFormatter(String uglyJsonStr) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(uglyJsonStr);
return gson.toJson(je);
}
}

JacksonUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typescript复制代码public class JacksonUtil {
private static ObjectMapper mapper = new ObjectMapper();

public static String bean2Json(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}

public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
try {
return mapper.readValue(jsonStr, objClass);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}

JsonLibUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码public class JsonLibUtil {

public static String bean2Json(Object obj) {
JSONObject jsonObject = JSONObject.fromObject(obj);
return jsonObject.toString();
}

@SuppressWarnings("unchecked")
public static <T> T json2Bean(String jsonStr, Class<T> objClass) {
return (T) JSONObject.toBean(JSONObject.fromObject(jsonStr), objClass);
}
}

准备Model类

这里我写一个简单的Person类,同时属性有Date、List、Map和自定义的类FullName,最大程度模拟真实场景。

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
typescript复制代码public class Person {
private String name;
private FullName fullName;
private int age;
private Date birthday;
private List<String> hobbies;
private Map<String, String> clothes;
private List<Person> friends;

// getter/setter省略

@Override
public String toString() {
StringBuilder str = new StringBuilder("Person [name=" + name + ", fullName=" + fullName + ", age="
+ age + ", birthday=" + birthday + ", hobbies=" + hobbies
+ ", clothes=" + clothes + "]\n");
if (friends != null) {
str.append("Friends:\n");
for (Person f : friends) {
str.append("\t").append(f);
}
}
return str.toString();
}

}

public class FullName {
private String firstName;
private String middleName;
private String lastName;

public FullName() {
}

public FullName(String firstName, String middleName, String lastName) {
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
}

// 省略getter和setter

@Override
public String toString() {
return "[firstName=" + firstName + ", middleName="
+ middleName + ", lastName=" + lastName + "]";
}
}

JSON序列化性能基准测试

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
csharp复制代码@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonSerializeBenchmark {
/**
* 序列化次数参数
*/
@Param({"1000", "10000", "100000"})
private int count;

private Person p;

public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(JsonSerializeBenchmark.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.build();
Collection<RunResult> results = new Runner(opt).run();
ResultExporter.exportResult("JSON序列化性能", results, "count", "秒");
}

@Benchmark
public void JsonLib() {
for (int i = 0; i < count; i++) {
JsonLibUtil.bean2Json(p);
}
}

@Benchmark
public void Gson() {
for (int i = 0; i < count; i++) {
GsonUtil.bean2Json(p);
}
}

@Benchmark
public void FastJson() {
for (int i = 0; i < count; i++) {
FastJsonUtil.bean2Json(p);
}
}

@Benchmark
public void Jackson() {
for (int i = 0; i < count; i++) {
JacksonUtil.bean2Json(p);
}
}

@Setup
public void prepare() {
List<Person> friends=new ArrayList<Person>();
friends.add(createAPerson("小明",null));
friends.add(createAPerson("Tony",null));
friends.add(createAPerson("陈小二",null));
p=createAPerson("邵同学",friends);
}

@TearDown
public void shutdown() {
}

private Person createAPerson(String name,List<Person> friends) {
Person newPerson=new Person();
newPerson.setName(name);
newPerson.setFullName(new FullName("zjj_first", "zjj_middle", "zjj_last"));
newPerson.setAge(24);
List<String> hobbies=new ArrayList<String>();
hobbies.add("篮球");
hobbies.add("游泳");
hobbies.add("coding");
newPerson.setHobbies(hobbies);
Map<String,String> clothes=new HashMap<String, String>();
clothes.put("coat", "Nike");
clothes.put("trousers", "adidas");
clothes.put("shoes", "安踏");
newPerson.setClothes(clothes);
newPerson.setFriends(friends);
return newPerson;
}
}

说明一下,上面的代码中

1
arduino复制代码ResultExporter.exportResult("JSON序列化性能", results, "count", "秒");

这个是我自己编写的将性能测试报告数据填充至Echarts图,然后导出png图片的方法。

执行后的结果图:

图片

从上面的测试结果可以看出,序列化次数比较小的时候,Gson性能最好,当不断增加的时候到了100000,Gson明细弱于Jackson和FastJson, 这时候FastJson性能是真的牛,另外还可以看到不管数量少还是多,Jackson一直表现优异。而那个Json-lib简直就是来搞笑的。^_^

JSON反序列化性能基准测

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
swift复制代码@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonDeserializeBenchmark {
/**
* 反序列化次数参数
*/
@Param({"1000", "10000", "100000"})
private int count;

private String jsonStr;

public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(JsonDeserializeBenchmark.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.build();
Collection<RunResult> results = new Runner(opt).run();
ResultExporter.exportResult("JSON反序列化性能", results, "count", "秒");
}

@Benchmark
public void JsonLib() {
for (int i = 0; i < count; i++) {
JsonLibUtil.json2Bean(jsonStr, Person.class);
}
}

@Benchmark
public void Gson() {
for (int i = 0; i < count; i++) {
GsonUtil.json2Bean(jsonStr, Person.class);
}
}

@Benchmark
public void FastJson() {
for (int i = 0; i < count; i++) {
FastJsonUtil.json2Bean(jsonStr, Person.class);
}
}

@Benchmark
public void Jackson() {
for (int i = 0; i < count; i++) {
JacksonUtil.json2Bean(jsonStr, Person.class);
}
}

@Setup
public void prepare() {
jsonStr="{\"name\":\"邵同学\",\"fullName\":{\"firstName\":\"zjj_first\",\"middleName\":\"zjj_middle\",\"lastName\":\"zjj_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":[{\"name\":\"小明\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null},{\"name\":\"Tony\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null},{\"name\":\"陈小二\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"篮球\",\"游泳\",\"coding\"],\"clothes\":{\"shoes\":\"安踏\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null}]}";
}

@TearDown
public void shutdown() {
}
}

执行后的结果图:

图片

从上面的测试结果可以看出,反序列化的时候,Gson、Jackson和FastJson区别不大,性能都很优异,而那个Json-lib还是来继续搞笑的。

本文转载自: 掘金

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

Elasticsearch和solr的区别

发表于 2021-06-20

1.背景

它们都是基于Lucene搜索服务器基础之上开发,一款优秀的、高性能的企业级搜索服务器

  • 它们都是基于分词技术构建的倒排索引的方式进行查询了
  • 它们都是基于Java语言开发
+ slor诞生于: 2004年诞生;
+ Es诞生于: 2010年诞生;

2.与别

  • 当实时建立索引时,solr会产生io阻塞,es不会,es查询性能要高于solr
  • 在不断动态添加数据的时候,solr的检索效率会变得低下,而es则没有什么变化
  • solr是利用zookeeper进行分布式管理,es自带分布式管理系统;solr一般都要部署到web服务器上,比如tomcat。启动tomcat的时候需要配置tomcat与solr的关联(solr的本质是一个动态web项目)
  • solr支持更多的格式数据(xml,json,csv等),es只支持json文件格式
  • solr是传统搜索应用的有力解决方案,es更适用于新兴的实时搜索应用
+ 单纯对已有的数据进行检索.solr效率更好,高于es
  • solr官网提供了很多功能,而es本身更注重于核心功能,高级功能由第三方插件提供

3.集群搭建

3.1 solr集群

image.png

image.png

3.2 es 集群

image.png

本文转载自: 掘金

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

盘点 Flow Activiti 如何使用命令模式执行流

发表于 2021-06-20

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

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

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

Github : 👉 github.com/black-ant

一 . 前言

在深入 Activiti 的使用者 , 发现他对命令模式进行了很深入的使用 , 这里来看一下他是如何灵活使用该模式的 :

二 . 命令模式简述

原理 : 命令模式的原理在于发送者发送命令 , 执行者执行命令 ,传递者传递命令 , 命令描述执行的动作

角色成员 :

  • Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法
  • ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中
  • Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。
    • invoke 更像是一个传令兵 , 而不是调用者
  • Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。

我们来看一个命令模式的简单 Demo >> 👉

Step 1 : 构建 Command 接口

每个行为 (Command) 都会对应一个具体的 Command 对象 , 但是他们都会通过实现一个具体的 Command 接口来实现 , 对应整个流程中 ,中间对象都面向接口行为 ,而不会关心具体的实现类型

1
2
3
4
java复制代码public interface Command {  
// 每个具体的 Command 都应该实现该方法
public void exe();
}

Step 2 : 构建一个具体的 Command 实现类

Command 单元 , 该对象用于调用 具体的 Command 业务类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class MyCommand implements Command {  

private Receiver receiver;

public MyCommand(Receiver receiver) {
this.receiver = receiver;
}

@Override
public void exe() {
receiver.action();
}
}

Step 3 : 命令接收人执行具体的命令

准确来说 , 该对象不算在命令模式体系中 , 他是具体的的业务对象.

1
2
3
4
5
java复制代码public class Receiver {  
public void action(){
System.out.println("command received!");
}
}

Step 4 : 代理器用于传递命令

总代理类 , 获取 Command 命令 , 进行判断并且调用

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class Invoker {  

private Command command;

public Invoker(Command command) {
this.command = command;
}

public void action(){
command.exe();
}
}

Step End : 测试

1
2
3
4
5
6
7
8
9
java复制代码public class Test {  

public static void main(String[] args) {
Receiver receiver = new Receiver();
Command cmd = new MyCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.action();
}
}

三 . Activiti 中的命令模式

image.png

3.1 命令对象

Activiti 中的命令对象接口为 Command , 他有很多实现类 :

Command-system.png

PS:因为太多 , 这里仅仅展示了一部分 , 这些对象共同实现了 Command 接口

1
2
3
java复制代码public interface Command<T> {
T execute(CommandContext commandContext);
}

还是以 DeleteTaskCmd 为例 :

3.2 Command 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class DeleteTaskCmd implements Command<Void>, Serializable {

public Void execute(CommandContext commandContext) {
// 简单魔改 . 省略主要逻辑
deleteTask(commandContext, taskId);
return null;
}

protected void deleteTask(CommandContext commandContext, String taskId) {
// 调用其他的业务类执行业务
commandContext.getTaskEntityManager().deleteTask(taskId, deleteReason, cascade, cancel);
}
}

3.3 CommandInvoker 执行Command 代理

该类是最后得分发器 , 通过该方法创建新线程执行 Command 命令

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
java复制代码public class CommandInvoker extends AbstractCommandInterceptor {

private static final Logger logger = LoggerFactory.getLogger(CommandInvoker.class);

@Override
@SuppressWarnings("unchecked")
public <T> T execute(final CommandConfig config, final Command<T> command) {
final CommandContext commandContext = Context.getCommandContext();

// 执行命令
commandContext.getAgenda().planOperation(new Runnable() {
@Override
public void run() {
commandContext.setResult(command.execute(commandContext));
}
});

// 循环执行操作
executeOperations(commandContext);

// 最后,调用执行树更改监听器
if (commandContext.hasInvolvedExecutions()) {
Context.getAgenda().planExecuteInactiveBehaviorsOperation();
executeOperations(commandContext);
}

// resultStack.pollLast() 获取结果
return (T) commandContext.getResult();
}

protected void executeOperations(final CommandContext commandContext) {
while (!commandContext.getAgenda().isEmpty()) {
// 构建线程 , 并且调用 executeOperation 执行
Runnable runnable = commandContext.getAgenda().getNextOperation();
executeOperation(runnable);
}
}

public void executeOperation(Runnable runnable) {
if (runnable instanceof AbstractOperation) {
AbstractOperation operation = (AbstractOperation) runnable;

// 操作没有执行或者操作执行了一次,并且没有结束
if (operation.getExecution() == null || !operation.getExecution().isEnded()) {
runnable.run();
}
} else {
runnable.run();
}
}

@Override
public CommandInterceptor getNext() {
return null;
}

@Override
public void setNext(CommandInterceptor next) {
throw new UnsupportedOperationException("CommandInvoker must be the last interceptor in the chain");
}

}

PS : CommandInvoke 整体数据

image.png

这里可以看到 , 实际上 Command 对象已经设置为了 DeleteTaskCmd , 下面看一下 Command 命令谁生成的

3.4 Command 的调用

以上基本上就能看出命令模式的主要成员就已经说清楚了 , 这里还补充一下 Command 的调用

  • Step1 : C- ActivitiTaskRuntimeService # deleteTask
  • Step2 : C- TaskRuntimeImpl # delete
  • Step3 : C- TaskServiceImpl # deleteTask : 生成 Command 命令
  • Step4 : C- CommandExecutorImpl # execute : 核心方法 , 调用拦截器链
  • Step5 : C- CommandContextInterceptor # execute : 命令容器执行 Command

Step 3 : 在其中可以看到创建了 command 对象

1
2
3
java复制代码public void deleteTask(String taskId, String deleteReason, boolean cancel) {
commandExecutor.execute(new DeleteTaskCmd(taskId, deleteReason, false, cancel));
}

Step 5 : CommandContextInterceptor 处理 Command 消息体

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
java复制代码public class CommandContextInterceptor extends AbstractCommandInterceptor {

private static final Logger log = LoggerFactory.getLogger(CommandContextInterceptor.class);

// 工程类及配置对象
protected CommandContextFactory commandContextFactory;
protected ProcessEngineConfigurationImpl processEngineConfiguration;

public CommandContextInterceptor() {
}

public CommandContextInterceptor(CommandContextFactory commandContextFactory, ProcessEngineConfigurationImpl processEngineConfiguration) {
this.commandContextFactory = commandContextFactory;
this.processEngineConfiguration = processEngineConfiguration;
}

public <T> T execute(CommandConfig config, Command<T> command) {

CommandContext context = Context.getCommandContext();

boolean contextReused = false;
// 检查异常,事务可能处于回滚状态,并且会触发一些其他命令进行补偿
if (!config.isContextReusePossible() || context == null || context.getException() != null) {
context = commandContextFactory.createCommandContext(command);
} else {

contextReused = true;
context.setReused(true);
}

try {

// 将操作推到堆栈中 ( getStack(commandContextThreadLocal).push(commandContext);)
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
if (processEngineConfiguration.getActiviti5CompatibilityHandler() != null) {
Context.setActiviti5CompatibilityHandler(processEngineConfiguration.getActiviti5CompatibilityHandler());
}

// 执行操作
return next.execute(config, command);

} catch (Throwable e) {

context.exception(e);

} finally {
try {
if (!contextReused) {
// 关闭容器
context.close();
}
} finally {

// 同时移除相关数据
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
Context.removeBpmnOverrideContext();
Context.removeActiviti5CompatibilityHandler();
}
}

return null;
}

}

总结

目的: 达到命令的发出者和执行者之间解耦 , 实现请求和执行分开

本质: 一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作

结果: 由于引用了抽象类 , 请求发送者 , 具体的命令与请求接收者互相解耦

最后总结一下使用命令模式对 Activiti 的好处 :

  • 在业务中 , 创建一个 command 命令
  • 构建容器后 ,让 command 命令在其中流转
    • 对于容器 , 只需要关注命令的实现方法 , 不需要关系命令的内部逻辑
    • 发送者不需要知道中间发生了什么 , 也不需要关心性能处理
  • 最后 , 命令代理类收到命令 (此时命令中包含让谁去执行)
    • 命令代理类叫来执行对象(对象的引用) ,让其执行
    • 实际上 ,此处可以通过反射来获得执行对象 , 这样命令才真的只是个命令

本文转载自: 掘金

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

【第一篇】jeecg boot 项目理解、使用心得及改进建议

发表于 2021-06-20

为什么会知道JEECG BOOT项目?

2021年6月份由于公司安排一个报表模块的任务给我, 因为之前也没做过专业的报表, 所以就去GitHub或Gitee上去搜索star排名高的项目,但是也对比了其他报表,目前公司用的ureport报表,公司领导决定用JEECG BOOT项目中的积木报表实现公司报表和数据看板的功能, 很大可能会用积木报表,先安排我去熟悉积木报表, 除了以上原因, 本人简单了解一下该项目的技术架构及相关技术, 认为很有学习的价值,涵盖技术面非常广,值得学习。

项目理解

​ 据我所知及结合网上对该项目的介绍, 该项目采用的是前后端分离架构, 符合目前IT行业的开发模式,功能完善 ,是一款基于代码生成器的低代码开发平台,特别适合中小企业作为快速开发项目的首选, 更多信息请前往官网了解,地址:JEECG BOOT。那么我将从以下几个方面来简单介绍该项目:

技术面

​ 该项目涵盖的技术非常广,不仅涉及到前后端当今最主流的技术,还涉及到docker、单点登录、分布式等关于CI/CD相关的运营技术以及一些编码规范,值得我们作为一名程序员学习

设计思想

​ 正如官方所述,这是一款基于代码生成器的低代码、零代码开发平台。 基于这个目标,JEECG官方将其技术架构分为前后端分离,前端采用Ant-design-vue + Vue + Webpack,后端采用

1
sql复制代码SpringBoot_2.1.3.RELEASE + Mybatis-plus_3.1.2 + Shiro_1.4.0 + Jwt_3.7.0 + Swagger-ui + Redis

以此为引入点,更易扩展为分布式架构,目前已经实现

前端项目目录结构如下截图:

image.png

目录结构非常清晰, 从中可以看出支持多环境部署

后端项目结构如下截图:

image.png

从截图可以看出,后端目录结构非常清晰,职责划分明确,符合微服务的思想, 采用maven多模块管理,例如:jeecg-boot-base该模块从字面意思就可以知道它主要是一些核心类、工具类及公共工厂接口等,见面之意

适合项目

  • SAAS项目
  • 企业信息管理系统(MIS)
  • 内部办公系统(OA)
  • 企业资源计划系统(ERP)
  • 客户关系管理系统(CRM)

前后端分离架构带来的好处

  • 利益开发,降低耦合度,前端同学可以专注于前端,后端同学可以专注于后端
  • 便于维护、部署方便及提高开发效率
  • 在后端没有完成的情况下前端同学可以根据产品经理的设计提前做好页面,提现在第一点
  • 在开发时,可以做到代码热替换,可以使用 babel,可以使用 css 预处理器等等
  • 提高应用的性能等等

至于前后端分离架构带来不好的地方大家自行百度哈

所以JEECG BOOT项目的这种架构设计非常符合当今IT行业的开发模式

总结一点是:看业务场景用什么模式开发

文档

该项目的技术文档非常全,除了官方的在线文档,在各大媒体(例如:百度、简书)也能找到别人总结的资料,主要体现在以下几个方面:

  • 开发文档
  • 常见问题
  • 问题反馈

架构模式

单体和分布式模式灵活切换

是否值得学习

答案是:肯定的

  • 前后端分离架构
  • 主流技术
  • 大型项目
  • 社区活跃
  • github上千image.png

等等

使用心得

​ 一开始git clone 下载代码后运行老是报错,控制台提示错误创建bean、字段未定义等等,但通过解决问题会让自己更加的熟悉项目,自己也得到了提升,建议想学习该项目的同学先看文档再本地运行起来, 因为你看了文档后自然而然遇到问题就知道那里配置错了或者如何去改,遇到问题不要慌,要尝试各种方法去解决。该项目最吸引我的地方是报表设计器以、在线表单及与第三方平台交互,对于nacos还好,目前我们公司用阿里的datav做大屏看板,用ureport做报表设计,嵌入到业务系统中,总结为以下几点:

  • 前后端分离架构
  • 微服务
  • 在线表单
  • 第三方平台对接(例如:企业微信等)
  • 大屏设计器
  • 监控

等等

改进建议

  • 【推荐】接口定义不符合Restful风格
  • 【强烈】报表设计器提高隐藏列或移动列功能
  • 【推荐】Token过期不要用提示框,用小提示,3秒后自动关闭且跳转到登录页,我们公司目前的系统是只有token过期了点击即跳转到登录页
  • 【强烈】积木报表源码中的初始化sql脚本建议把所有定义相关sql写到同一个文件中,如果写不写,则可以按顺序并标注
  • 【强烈】报表设计打开新窗口问题建议在本iframe中打开,我们公司用ureport在iframe中打开,不需要跳转
  • 【推荐】基于Golang语言开发一套jeecg-go项目,目前Golang很年轻,很有发展的趋势,虽然生态不是很完善,但我相信以后会越来越完善。例如:go-zero

总结

​ JEECG BOOT 项目非常适合用于类似ERP系统这样的管理项目中,技术栈为当今主流的技术之一等等,主要有以下几点:

  • 社区活跃
  • 文档丰富
  • 技术新颖
  • 低代码开发
  • 学习资料丰富

链接地址

doc.jeecg.com/2043868

最后,希望JEECG BOOT项目能够涵盖所有企业相关的业务,一起加油!!!

本文转载自: 掘金

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

rabbitMQ--广播模式

发表于 2021-06-19

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

我们之前的rabbitMQ的模式都是不存在交换机的,直接发送到队列,将下来讲的是订阅模型,一次像多个消费者发消息

image.png
一个生产者发送消息到交换机,交换机发给绑定在自己上边的队列,消费者在从队列拿到消息消费,
X(Exchange):交换机接受生产者发送的消息,另一方面知道如何处理消息,,发给某个队列,还是发给所有的队列,或者是直接舍弃,取决于交换机是如何配置的,交换机只负责发送,而不去存储消息。

交换机分为几类

1
2
3
4
5
js复制代码Publish/Subscribe:广播,将消息交给所有绑定到交换机的队列

Routing:定向,把消息交给符合指定routing key 的队列

Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

订阅模型–Publish/Subscribe

image.png

在广播模式下,消息发送流程是这样的:

  • 1) 可以有多个消费者
  • 2) 每个消费者有自己的queue(队列)
  • 3) 每个队列都要绑定到Exchange(交换机)
  • 4) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  • 5) 交换机把消息发送给绑定过的所有队列
  • 6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

生产者

生产者声明交换机,不在声明队列,消息发送到交换机,比在发送到队列

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
java复制代码public class p1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为fanout
channel.exchangeDeclare("Subscribe_exchange", "fanout");
// 消息内容
String message = "Hello_Subscribe";
// 发布消息到Exchange
channel.basicPublish("Subscribe_exchange", "", null, message.getBytes());
System.out.println("生产者发送消息=:'" + message + "'");

channel.close();
connection.close();

}
}

消费者1

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复制代码public class c1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("Subscribe_queue_1", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("Subscribe_queue_1", "Subscribe_exchange", "");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("c1消费消息: "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("Subscribe_queue_1", false, consumer);
}
}

消费者2

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
java复制代码public class c2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("Subscribe_queue_2", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("Subscribe_queue_2", "Subscribe_exchange", "");

// 定义队列的消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("c2消费消息: "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};

// 监听队列,自动返回完成
channel.basicConsume("Subscribe_queue_2", false, consumer);
}
}

启东消费者,生产者发送一条消息,看输出

image.png

image.png

Routing–有选择的发送消息

订阅模式,在这个中我们可以做到不同的队列接受不同的消息,队列与交换机绑定必须指定,消息发送时也必须指定发送消息的routingKey
image.png

如上图所示生产者生产消息发送到交换机,交换机通过与rontingkley的匹配的队列发送消息。

生产者–分别发送三次不同的消息,匹配不同的routingkey

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复制代码public class p {
public static void main(String[] args) throws IOException, TimeoutException {

//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为fanout
channel.exchangeDeclare("routing_exchange", "direct");
// 消息内容
//String message = "新增";
//String message = "删除";
String message = "更新";
// 发布消息到Exchange
//channel.basicPublish("routing_exchange", "insert", null, message.getBytes());
//channel.basicPublish("routing_exchange", "delect", null, message.getBytes());
channel.basicPublish("routing_exchange", "update", null, message.getBytes());
System.out.println("生产者发送消息=:开始'" + message + "'");

channel.close();
connection.close();


}
}

消费者insert

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复制代码public class insert {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("routing_queue_insert", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("routing_queue_insert", "routing_exchange", "insert");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("insert 接收消息 : "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("routing_queue_insert", false, consumer);
}
}

消费者delect

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复制代码public class delect {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("routing_queue_delect", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("routing_queue_delect", "routing_exchange", "delect");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("delect 接收消息 : "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("routing_queue_delect", false, consumer);
}
}

消费者update

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复制代码public class update {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("routing_queue_update", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("routing_queue_update", "routing_exchange", "update");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("delect 接收消息 : "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("routing_queue_update", false, consumer);
}
}

看控制台输出,可以看到,绑定了不同routingkey的收到不同的消息,每个队列可以有很多个routingkey

image.png

image.png

topic–

不同于Direct的交换机,topic匹配可以通过通配符

image.png

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割

1
2
js复制代码*(星号)可以正好代替一个词。
# (hash) 可以代替零个或多个单词。

生产者

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复制代码public class p {
public static void main(String[] args) throws IOException, TimeoutException {

//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为fanout
channel.exchangeDeclare("topic_exchange", "topic");
// 消息内容
String message = "新增";
//String message = "删除";
//String message = "更新";
// 发布消息到Exchange
channel.basicPublish("topic_exchange", "goods.insert", null, message.getBytes());
//channel.basicPublish("topic_exchange", "goods.delect", null, message.getBytes());
// channel.basicPublish("topic_exchange", "goods.update", null, message.getBytes());
System.out.println("生产者发送消息=:开始'" + message + "'");

channel.close();
connection.close();


}
}

消费者1 只接收insert和delect

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复制代码public class c1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("topic_queue_1", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("topic_queue_1", "topic_exchange", "goods.insert");
channel.queueBind("topic_queue_1", "topic_exchange", "goods.delect");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("新增删除 接收消息 : "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("topic_queue_1", false, consumer);
}
}

消费者2 只要匹配到 goods.全拿下

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复制代码public class c2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.145.3");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/zhaojin");
connectionFactory.setUsername("zhaojin");
connectionFactory.setPassword("zhaojin");
//3.创建连接
Connection connection = connectionFactory.newConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare("topic_queue_2", false, false, false, null);

// 绑定队列到交换机
channel.queueBind("topic_queue_2", "topic_exchange", "goods.*");

Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("商品 接收消息 : "+new String(body));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,自动返回完成
channel.basicConsume("topic_queue_2", false, consumer);
}
}

运行发送推广三条消息,看控制台输出

image.png

image.png

完美、

本文转载自: 掘金

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

Java final关键字总结 final关键字

发表于 2021-06-19

final关键字

final关键字主要作用为防止数据被修改,保证代码安全。

可以用于修饰:类、类中方法、变量、参数;

修饰类

相关代码

1
2
3
4
5
6
java复制代码final class Person {
}

// 错误,不可以继承被final修饰的类
class Student extends Person {
}

概念

当使用final关键字修饰类的时候,表示该类不可以被继承,类中所有方法默认均为final。

用final修饰类可以防止被继承,如jdk代码中的String类就是被final修饰的,从而防止被其他类继承,导致内部逻辑被破坏。

修饰类中方法

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码class Person {
// 禁止子类重写该方法,修改其含义
final String getIdentity() {
return "I am a person!";
}
}

class Student extends Person {
// 错误,禁止重写父类的该方法
@Override
final String getIdentity() {
return "I am a Student!";
}
}

概念

当我们不希望子类重写父类的方法,修改父类该方法的定义时可以使用final修饰。

这样任何子类都不可以重写父类的该方法。

修饰变量

代码

1
2
3
4
5
6
7
8
9
10
java复制代码public static void main(String[] args) {
final int num;
final Object o;
num = 10;
o = new Object();

// 下边两行是错误的
num = 20;
o = new Object();
}
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class Test {
// 类中的final成员变量必须在构造器中进行初始化
private final Object object;

public Test() {
this.object = new Object();
}

// 错误,被final修饰的成员变量必须确保类中的构造器执行后,该变量完成初始化!!!
public Test(Object object) {
}
}

概念

当我们使用final关键字对变量进行修饰的时候。

  1. 当修饰的变量为内置类型时,一但完成初始化后,该变量的值不可以进行修改。
  2. 当修饰的变量为引用类型时,一但完成初始化后,该变量中的对象引用不会指向其他对象,但可以修改对象内部的内容。
  3. 当我们用final修饰成员变量时,必须确保类中的构造器执行后,该变量完成初始化
    用final修饰变量可以防止数据被修改,保证安全。

修饰参数

代码

1
2
3
4
5
6
7
java复制代码public void testInnerType(final int num) {
num = 10; // error: Cannot assign a value to final variable 'num';
}

public void testObject(final Object o) {
o = new Object(); // error: Cannot assign a value to final variable 'o';
}

概念

用final修饰参数虽然可以做到参数不被修改,这么做并无任何意义。

java中的内置类型作为参数为值拷贝,用final修饰参数保证了形参不会被修改,并不能保证实参被修改。

java中的引用类型作为参数为引用拷贝,相当于增加一个引用指向实参所指向的对象,用final修饰参数保证了形参不会修改其指向的对象,并不能保证实参被修改。

因此,使用final修饰参数,并不能做到防止实参被修改,因此不需要在参数处加final。如需要形参不被该方法修改,才需要使用final修饰。

本文转载自: 掘金

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

读完这章,你还敢说你不会SpringBoot源码?

发表于 2021-06-19

前言

笔者把之前的Spring的格式重新排版,并且细致到一行一个讲解,希望大家喜欢。

大家使用Spring Boot这么久了,有没有知道Spring Boot的启动流程是怎样的呢?Spring是如何扫描到使用@Component的类并且把它放进BeanFactory呢?它是如何启动的呢?现在我们就一起看看。本系列会分几个章节去讲述这个Spring Boot的启动过程。由于写这篇文章的时候,在很多环境写过,所以可能由于Spring Boot版本不同代码也不同,但是思路是一样的。

1.创建SpringApplication对象

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
java复制代码// 1.main方法
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {

public static void main(String[] args) {
// 启动入口,请点击run方法
SpringApplication.run(DemoApplication.class, args);
}
}
// 2.main方法进来后先创建一个实例new SpringApplication 蹩脚翻译一下
/**
* 创建一个新的SpringApplication实例也就是要new SpringApplication(),并且定义主要的类作为Bean的加载来源
*/
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 设置类入口也就是启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 设置这个应用的类型,分别为SERVLET、NONE、REACTIVE。具体可以看WebApplicationType枚举类
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器,这个getSpringFactoriesInstances()方法是从spring.factories中读取
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器,更上面一样也就是只要看到这个方法就是从spring.factories中读取要的类并且加载
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

2.在SpringApplication对象调用run()

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
java复制代码/**
* 1.跑run方法
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
// 开启计时器
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置一些外设
configureHeadlessProperty();
// 获取监听器,这个getRunlisteners方法里面也是一样获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 监听器开始执行
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = printBanner(environment);
// 创建应用上下文,请看2
context = createApplicationContext();
// 加载异常报告期
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文,请看3
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
/**
* 2.这个方法是从创建SpringApplication实例加载的应用类型去创建不同的上下文实例,这里由于是SERVLET类型所以会创建
*/
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
// 叫做注解配置上下文实例(蹩脚翻译)
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
/**
* 3.准备上下文,这里其实挺重要的,讲述了启动类如何加载到BeanFactory
* 其实就一个重点,在load()这个方法里面
*/
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 这里context因为是AnnotationConfigServletWebServerApplicationContext
// 所以他继承于ServletWebServerApplicationContext
// ServletWebServerApplicationContext继承GenericWebApplicationContext
// GenericWebApplicationContext继承GenericApplicationContext
// 所以他的beanFactory是默认的private final DefaultListableBeanFactory beanFactory;
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 这个方法讲述了启动类如何注册到beanFactory里面,为下面的refreshContext做了铺垫
// 而且这个load方法里面还有启动类如何注册到BeanDefinition里面去的,去看4
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
/**
* 4.这里就先说下doRegisterBean()
* 每个bean被执行doGetBean方法前都是要把Bean的定义信息拿到,也就是通俗的BeanDefinition,类的元数据
* 而AnnotatedGenericBeanDefinition就是为了公开这些类元数据做的接口
* 当adb和beanName一起在BeanDefinitionHolder初始化的时候
*/
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// 定义bean的元数据对象,下面都是获取bean名字之类的
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
// 当元数据对象与beanName创建了BeanDefinitionHolder
// 他会把一些alias和beanName全部保存下来
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 这里就会正式把此bean注册到this.beanDefinitionMap和this.beanDefinitionNames属性里面
// 而这两个属性就是刚刚的默认beanFactory也就是DefaultListableBeanFactory.class
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

3.核心方法refreshContext

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
java复制代码/**
* 1.此方法其实就是Spring整个启动过程或者说ioc等等的核心流程了
* 这个方法分几步去讲解
*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 准备上下文
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
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.
// 这个方法是扫描所有的Bean和加载所有的Bean
invokeBeanFactoryPostProcessors(beanFactory);


// Instantiate all remaining (non-lazy-init) singletons.
// 前面是扫描和加载,这个步骤就是真正的创建Bean实例了
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();
}
}
}
/**
* 2.这里进来就是beanFactory增强器加载
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// getBeanFactoryPostProcessors()这个方法就是获取当前所有的beanFactory增强器进去可以看看,目前默认有3个
// 1.SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor在prepareContext中的applyInitializers插入
// 2.ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor在prepareContext中的applyInitializers插入
// 3.ConfigFileApplicationListener$PropertySourceOrderingPostProcessor在prepareContext最后一行listeners.contextLoaded(context)插入
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()));
}
}
/**
* 3.这方法重中之重,里面包涵了大量的操作我们来一起看看
*/
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<>();
// 这个context是AnnotationConfigServletWebServerApplicationContext实例他继承的类有BeanDefinitionRegistry接口
// 所以这里一定会进来
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
// 这个循环是把那3个增强器分类
// CachingMetadataReaderFactoryPostProcessor实现BeanDefinitionRegistryPostProcessor
// ConfigurationWarningsPostProcesso实现BeanDefinitionRegistryPostProcessor
// PropertySourceOrderingPostProcessor实现BeanFactoryPostProcessor
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
// 这里把BeanDefinition注册进去
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}

// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
// 这里获取到的beanName不是刚刚的增强器而是常量org.springframework.context.annotation.internalConfigurationAnnotationProcessor
// 并且下面这个循环会把ConfigurationClassPostProcessor加载出来。
// 那么大家会问ConfigurationClassPostProcessor什么时候加载的呢?
// createApplicationContext这个方法还记得吧!就是在这里加载的当Spring选择了Servlet模式就会加载配置
// CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME就会作为beanNamePut进beanFactory
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 当ConfigurationClassPostProcessor回去跑processConfigBeanDefinitions方法,跳去4
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();

// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
reiterate = false;
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}

// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
}

else {
// Invoke factory processors registered with the context instance.
invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
}

// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}

// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
sortPostProcessors(orderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

// Finally, invoke all other BeanFactoryPostProcessors.
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

// Clear cached merged bean definitions since the post-processors might have
// modified the original metadata, e.g. replacing placeholders in values...
beanFactory.clearMetadataCache();
}
/**
* 4.这里就是把启动类选出来并且开始ComponentScan的地方,自动装配也是这里完成的@Import
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
//configCandidates这会加入启动类
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
/**
* 省略代码
*/
// Parse each @Configuration class
// 这就是找Component的实现类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 这个是重点parse有很多Component类这里用来
parser.parse(candidates);
/**
* 省略代码
*/
}
while (!candidates.isEmpty());
/**
* 省略代码
*/
}
/**
* 5.扫描Component和自动装配,这个也很重要,而且段代码有几个递归也是比较复杂
* 也是希望大家能够自己debug进去看看这到底是如何递归的
*/
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {

/**
* 省略代码
*/
// Process any @ComponentScan annotations
// 这个为什么是数组呢?当你使用@ComponentScans(value = {@ComponentScan("com.example.test"), @ComponentScan("com.example.test1")})
// 这里的长度就会变成3
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// 开始以启动类扫描,这就是为什么启动类永远在所有包的最外层,如果要扫描其他模块或者启动类以外的包就要@ScanComponent这个注解
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 这里面就开始扫描doScan方法,只要是有@Component注解的都会把每个类的Definition放到scannedBeanDefinitions里面
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 这里有事解析这个Bean,跟刚刚的parser.parse一样,只是一个是数组一个是单个Bean
// 并且他会递归调用doProcessConfigurationClass()方法,并且重新执行一次获取看看这个类有没有@ComponentScan,并且继续扫描直至结束
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}

// Process any @Import annotations
// 其实每一个parse方法都会走到这里,把每个类的注解都会去循环一次,直至没有注解位置,会把@Import注解的类全部加载出来,这就是自动装配的原理
// 这就是为什么我其他jar包的类可以给Spring管理@Import就是一个重点,把这个类导入到BeanFactory里面
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

/**
* 省略代码
*/
return null;
}

创建Bean实例

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复制代码/**
* 创建bean实例,这里的方法过于复杂用文章非常难解释,笔者这里就把大致的思路说下
* 1.getBean的时候先去createBean如果有就返回没有的话doCreateBean
* 2.当doCreateBean的时候就会触发bean的生命周期的各个接口
* 3.其实笔者发现一个东西,ApplicationContextAware并不算是bean生命周期的一环把,而是输入上下文的一环
* 4.因为ApplicationContextAware其实是由添加了AnnotationConfigServletWebServerApplicationContext类所导致的
* 5.创建bean的我会有下面的uml图让大家更能理解Bean是如何创建的
*/
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}

// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

// Trigger initialization of all non-lazy singleton beans...
// 获取所有的BeanName
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}

// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}

关于ApplicationContext

实现ApplicationContextAware接口会调用setApplicationContext方法,而ApplicationContextAwareProcessor又是实现BeanPostProcessor,而ApplicationContextAwareProcessor又被Spring强制注册,所以说如果一个Bean实现ApplicationContextAware和BeanPostProcessor,在先初始化有关于BeanPostProcessor的Bean时候会创建这个Bean创建这个Bean的时候又会调用setApplicationContext方法调用完之后最后才会调用BeanPostProcessor实现的方法,其实听拗口的,所以最后还是希望自己能Debug一下

bean生命周期流程图

小结

当我重新重构这篇文章的时候,其实是因为我自己都读不懂之前的文章,希望这章大家可以看得懂。大家读不懂肯定是说明我的文采还不够好,也希望大家多多指出笔者的错误。

说到Spring其实在座的各位都是Spring工程师,老搬砖奴,但是大家肯定也因为忙没时间去研究整个Spring的流程。这里笔者帮大家都全部准备好了。

本文转载自: 掘金

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

redis入门一篇就够了!(两万字总结) 上

发表于 2021-06-19

文章大纲:

image.png

1.官方中文文档

www.redis.cn/

2.linux下安装

  • 官网下载安装包 redis-6.0.6.tar.gz
  • 放到服务器的usr/local/redis目录下
  • 解压:tar -zxvf redis-6.06.tar.gz
  • 在redis目录下进行基本的环境安装:yum install gcc-c++
  • 检查gcc的版本:gcc -v
  • 在redis目录下执行make(若报错可能是gcc的版本问题)

  • 执行make install

  • 进入/usr/local/bin目录,这是redis的默认安装目录。在这个目录创建一个myredisconfig的文件夹。然后将/usr/local/redis/redis-6.0.6下的redis.conf拷贝到这个文件夹下

  • 设置为后台自动启动:修改myredisconfig目录下的redis.conf daemonize no改为daemonize yes,然后:wq!保存退出

  • 通过配置文件启动redis(在/usr/local/bin目录下)

redis-server myredisconfig/redis.conf

  • redis客户端测试连接

redis-cli -p 6379

ping

  • 退出redis

shutdown

exit

  • 查看redis进程

ps -ef|grep redis

3.安装出现的问题

  • 在安装redis6以上的版本时make命令会出现错误,原因是gcc版本太低(centos默认版本为4),可以选择安装低版本redis5或者升级gcc到8版本,通过gcc -v命令查看版本.
  • 升级gcc版本的方法

www.cnblogs.com/jixiaohua/p…

  1. 压力测试工具

redis-benchmark是官方自带的性能测试工具,可以有效的测试redis服务的性能。在bin目录下。

测试demo:

测试100个并发连接,10000个请求,检测host为localhost 端口为6379的redis服务器性能。

redis-benchmark -h localhost -p 6379 -c 100 -n 10000

相关参数说明:

  1. 基本的数据库命令

  • redis命令手册:redis.com.cn/commands.ht…
  • redis默认有16个数据库 0~15,默认使用第0个。

查看redis.conf可以得知:

  • 进入redis:redis-server myredisconfig/redis.conf redis-cli -p 6379
  • 退出redis:shutdown exit
  • 输入密码:auth 311046
  • 切换数据库:select n n表示第几个数据库
  • 查看当前数据库的key的数量:dbsize
  • 查看具体的key:key *
  • 清空当前库:flushdb
  • 清空全部的库:flushall
  • 设置key值:set name coderchen
  • 获取key值:get name
  • 查看 key是否存在:exists keyname例如 exists name
  • 设置key生存时间当key过期会被自动删除:expire key 秒数
  • 查看还有多少秒过期:ttl name -1表示永不过期,-2表示已经过期。
  • 查看key的类型:type key
  • 删除key: del key

注意:redis命令不区分大小写,但是记录的数据区分大小写

  • 为什么redis默认端口是6379?

www.zhihu.com/question/20…

  1. redis单线程快?

  • 为什么redis是单线程?

redis是基于内存的操作,cpu不是redis的瓶颈,redis的瓶颈是机器内存大小或者网络带宽。既然单线程容易实现而且cpu不会成为瓶颈,那么就顺理成章的采用单线程的方案了。redis是C语言编写的,官方数据可以达到100000+QPS(每秒查询次数)

  • redis为什么这么快?

并不是多线程一定比单线程快,效率高。这与cpu,内存,硬盘的速度有关。

redis单线程快是因为redis是基于内存的操作。单线程效率最高,因为多线程的本质就是cpu模拟多个线程的情况,这其中有一个大的开销就是cpu上下文的切换。而redis单线程对内存中的数据进行读写时,速度比较快。举个例子就是读取1MB的数据时,可能多个进程cpu上下文的切换时间就大于单个进程读取数据的时间

key值加引号和不加的区别?

7.字符串String(单值单value)

  • string是redis 的基本类型,一个key对应一个value。string类型是二进制安全的,即redis的string可以包含任何数据,比如jpg图片或者序列化的对象,string类型redis最基本的数据类型,一个redis中字符串value最多可以是512M

常用命令说明:

官方命令手册:redis.com.cn/commands.ht…

string数据结构是简单的key-value类型,value不仅可以是string,也可以是数字。

  • append key1 "hello":对不存在的key进行append,等同于set key1 "hello"
  • append key1 ",world":对已经存在的key append就是在后面追加。
  • strlen key1:获取字符串长度

  • set views 0:要设置为数字才能用incr,decr命令 ,本质上这是一个字符串操作,因为Redis没有专门的整数类型。存储在 key 中的字符串被转换为十进制有符号整数
  • incr views:执行一次就将key中存储 的数字值增一
  • decr views:执行一次就将key中存储的数字值减一
  • incrby views 10:执行一次key中存储的值+10
  • decrby views 10:执行一次key中存储的值-10

  • getrange key2 m n:获取[m,n]区间范围内的字符串。n可以为负数,表示偏移,-1 表示最后一个字符, -2 表示倒数第二个字符,以此类推。
  • setrange key2 n value :将key2的值从第n个开始替换为指定的value
  • setex key2 10 "hello":将key2的值设置为hello,并且key2的生存时间设置为10s。这个命令等价于set key2 "hello" expire key2 10 但区别是setex 命令是一个原子操作,在同一时间内完成设置值和设置过期时间这两个操作。
  • setnx key3 "redis":在指定的key不存在时,为key设置指定的值,这种情况下等同于set,当key存在时,什么也不做。返回值:0 被设置了 1 没有被设置。

  • mset key1 value1 key2 value2 ......keyn valuen:设置多个key和value,如果这其中已经存在key,则会覆盖掉。
  • mget key1 key2 ...keyn:返回所有给定的key的值
  • msetnx key1 value1 key2 value2 ...keyn valuen:设置多个 key value,当且仅当所有给定的健都不存在时,即使有一个健存在值,msetnx也会拒绝执行操作。

  • getset key value:将key的值修改为value,并返回在修改之前的值。如果key在执行这个命令前不存在,则返回nil。

  • 可以用来缓存对象

mset user:1:name zhangsan user:1:age 2

mget user:1:name user:1:age

应用

  • 微博数,粉丝数等
  1. 列表List(单值多value)

  • redis的列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边)
  • 它的底层就是一个链表

常用命令说明

官方命令手册:redis.com.cn/commands.ht…

  • lpush list "one":将值插入到列表的头部(左边)
  • rpush list "two":将值插入到列表的尾部(右边)
  • lrange list n m:返回指定区间的元素 [n,m] m为-1表示返回n后面所有的元素。

  • lpop list:移除并返回列表的第一个元素,左边的第一个
  • rpop list:移除并返回列表的最后一个元素,右边第一个。

  • lindex list n:按照索引下标n获得元素(-1代表最后一个,0表示第一个,以此类推)


  • llen list:返回列表的长度


  • lrem list n value :移除列表中与value相等的元素,移除n个,顺序是从前往后。n>0 从头到尾删除值为value的元素, n<0,从尾到头删除值为value的元素, n=0,移除所有值为value的元素


  • ltrim list n m:对一个列表进行修剪,只保留区间[n,m]内的元素。其他全部删除。


  • rpoplpush list1 list2:移除列表list1的最后一个元素,并将该元素添加到list2的前边。这个操作是原子性的。从6.2.0起被废除了。


  • lset list n value:将list中第n个元素的值设置为value


  • linsert list before|after value value1:将value1插入到value的前边或者后边。


性能总结

  • 它是一个字符串列表,left,right都可以插入添加
  • 如果键不存在,创建新的链表。
  • 如果已经存在,新增内容
  • 如果值全部移除,对应的键也就消失了
  • 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就比较低。

应用

  • 最新消息排行功能
  • 消息对列
  1. 集合Set(单值多value)

  • redis的集合set是string类型的无序集合,是通过HashTable实现的。

常用命令说明

  • sadd key member[...]:将一个或者多个成员元素加入到集合中。
  • smembers key:返回存储在key中的所有成员
  • sismember key member:判断元素member是否是集合key中的成员。返回1表示是,0表示不是。


  • scrad key:返回集合中元素的数量。


  • srem key value:移除集合key中的一个或者多个元素value


  • srandmember key [count]:返回集合key中的随机元素,count表示返回几个元素。


  • spop key [count]:从集合key中删除一个或者多个元素,并返回删除的元素。(删除是随机的)


  • smove key1 key2 member :将指定的元素从集合key1移动到key2 。这是一个原子操作。


  • sdiff key [key...]:返回第一个集合key与其他key之间的差异,即第一个集合key独有的元素。
  • sinter key [key...]:返回所有给定集合key的成员交集
  • sunion key [key...]:返回所有给定集合key的并集

应用

  • 实现共同关注,共同好友等功能。
  1. 哈希Hash(单值多value,v是一个键值对)

  • Hash是一个键值对集合,类似于java中的map,是一个String类型的field和value的映射表,特别适合存储对象。

常用命令

  • hset key field value [field value ....]: 为哈希表key的field字段赋值
  • hget key field:返回哈希表key中的field字段的值
  • hgetall key:返回哈希表key中所有的域和值
  • hdel key field [field...]:删除哈希表key中的一个或者多个指定字段。


  • hlen key:获取哈希表key中的字段fields的数量


  • hexists key field:查看哈希表的指定字段field是否存在。0表示不存在,1表示存在
  • hkeys key:返回哈希表key中所有的域field
  • hvals key:返回哈希表中所有域的值


  • hincrby key field increment: 为哈希表key中的域field的值加上增量increment.增量为负数表示进行减法操作。


  • hsetnx key field value:为哈希表中不存在的字段field赋值为value。0表示设置失败,1表示设置成功。

应用

  • Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。
  1. 有序集合Zset

  • 在set基础上,加上一个score。之前set是sadd set1 v1 v2 v3…,现在Zset是zadd zset1 score1 v1 score2 v2。

常用命令

  • zadd key score1 member1[score2 member2...] :将一个或者多个member元素及其score值加入到有序集合key中。其中score可以是整数值或者双精度浮点数。可以为正或者负数
  • zrange key n m:返回有序集合key中的[n,m]区间内的元素。下标从0开始。如果m超出范围也不会报错,只会返回包含的。


  • zrangebyscore key n m:返回有序集合key中的成员,按照score的从小到大排序,范围是[n,m],n,m可以为-inf,+inf,这样在不知道有序集合数量的时候获取所有的成员。注意[n,m]这个范围是score的范围,不是成员的下标


  • zrem key member1[member2...]:从有序集合key中删除指定的成员member


  • zcard key:返回有序集合key中的成员个数


  • zcount key n m :返回有序集key中,score在[n,m]之间的成员数量。


  • zrank key member:返回有序集key中成员的排名,按照score从低到高排名,从0开始


  • zrevrank key member:返回有序集key中成员的排名,按照score从高到低排名,从0开始

应用

  • 排行榜应用,取top n操作。
  1. 三种特殊的数据类型

12.1 GEO地理位置

简介:

redis的GEO特性在redis3.2版本中推出,这个功能可以将用户给定的地理位置信息存储起来,并对这些信息进行操作。来实现诸如附近位置,摇一摇这类依赖于地理位置信息的功能。GEO的数据类型为Zset。

GEO的数据结构总共有六个常用命令:geoadd ,geopos ,geodist , georadius, georadiusbymember , gethash

官方文档:www.redis.net.cn/order/3685.…

命令:

geoadd

将指定的地理空间位置(经度,纬度,名称)添加到指定的key中。这些数据会存储到有序集合Zset中,目的是为了方便使用georadius,或者georadiusbymember命令对数据进行半径查询等操作。

geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 120.21 30.20 hangzhou

注意点:该命令采用标准格式的参数xy,所以经度必须在纬度之前。有效的经度 -180~180度,有效的纬度 -85.05~85.05


geopos

从key里面返回所有给定位置元素的位置(经度和纬度)

geopos key member[member...]

geopos china:city shanghai hangzhou


geodist

返回两个给定位置之间的距离,指定的参数unit必须是以下中的一种。m 米 ,km 千米, mi 英里,ft 英尺,默认是m。

geodist key member1 member2 [unit]

geodist china:city shanghai hangzhou km


georadius

以给定的经纬度为中心,找出某一半径内的元素。

范围的单位:m米,km千米 ,mi英里 ,ft英尺

georadius key longitude(经度) latitude(纬度) radius m|km|ft|mi [withcoord] [withdist][withhash][asc|desc][count count]

withdist:返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位与用户给定范围单位保持一致。

withcoord:将位置元素的经纬度也返回。

withhash:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大

asc: 根据中心的位置, 按照从近到远的方式返回位置元素。

desc:根据中心的位置, 按照从远到近的方式返回位置元素。

count:获取前n个匹配的元素,可以减少带宽当数据量大时。

举例:

georadius china:city 120 30 1500 km withdist


georadiusbymember

找出位于指定范围内的元素,中心点是由给定的位置元素决定的。

georadiusbymember key member radius m|km|ft|mi [withcoord][withdist][withhash][asc|desc][count count]

举例:

georadiusbymember china:city shanghai 1500 km


geohash

返回一个或者多个位置元素的geohash表示。

redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。

geohash key member[member...]

举例:

geohash china:city shanghai hangzhou


zrem

geo没有提供删除成员的方法,但是因为geo的底层是zset,所有可以用zrem命令实现对地理位置信息的删除。

zrem china:city shanghai:移除元素

zrange china:city 0 -1:查看所有元素

命令演示

12.2 HyperLogLog

简介

redis在2.8.9版本添加了HyperLogLog结构。

redis HyperLogLog是用来做基数统计的算法。HyperLogLog的优点是在输入元素的数量和体积非常大时,计算基数所需的空间总是固定的,并且很小。

基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

在redis里面,每个HyperLogLog键只需要花费12kb内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

HyperLogLog则是一种算法,提供了不精确的去重基数方案。

举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的 解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量 用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存 用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户 数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的

基本命令

pfadd key element[element...]:添加指定元素到HyperLogLog

pfcount key[key...]:返回给定HyperLogLog的基数估计值。

pfmerge destkey sourcekey [sourcekey...]:将多个HyperLogLog合并为一个HyperLogLog,并集计算。

12.3 BitMap

简介

​ 在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这种数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111………………………,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。

​ BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。

基本命令
  • setbit key n value:设置key的第n位的值value是0或1,n是从0开始的。
  • getbit key n:获取第n位的值。如果没有设置返回0
  • bitcount key[start,end]:统计key上值为1的个数。

  1. redis事务

理论


redis事务的概念:

redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有的命令都会被序列化。在事务执行过程中,会按照顺序串行化执行对列中 的命令,其他客户端提交的命令请求不会插入到事务执行的命令序列中。

总结的说:redis事务就是一次性,顺序性,排他性的执行一个对列中的一系列命令。


redis事务没有隔离级别的概念:

批量操作在发送exec命令前被放入对列缓存,并不会被实际执行。


redis不保证原子性:

redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。


redis事务的三个阶段:

开始事务

命令入队

执行事务


redis事务的相关命令

  • watch key1 key2...:监视一个或者多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断(类似乐观锁)
  • unwatch:取消watch对所有key的监控。
  • multi:标记一个事务块的开始(queued)
  • exec:执行所有事务块的命令(一旦执行exec后,之前加的监控锁都会被取消掉)
  • discard:取消事务,放弃事务块中所有 的命令

乐观锁和悲观锁?

  • 悲观锁(Pessimistic Lock),顾名思义就是很悲观,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到这个数据就会block知道它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁写锁等,都是在操作之前先上锁。
  • 乐观锁(Optimistic Lock)顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。

实践

  • 正常执行

  • 放弃事务

  • 如果事务对列中存在命令性错误(类似java编译性错误),执行exec命令时,所有命令都不会执行。

  • 如果在事务对列中存在语法性错误(类似于java的1/0运行时异常),则执行exec命令时,其他正确命令会被执行,错误命令抛出异常。

  • watch命令的演示(watch 用来监控key在事务执行的前后是否变化)–事务成功执行

  • 使用watch–事务执行失败

由于在事务执行过程中,watch监视的值出现了变化,因此导致了错误,这时要放弃监视,然后重来。

​

**注意:**一旦exec执行事务后,无论事务执行成功还是失败,watch对变量的监控都被取消。因此当事务执行失败后,需要重新执行watch对变量进行监控,并开启新的事务进行操作。

watch指令类似于乐观锁,事务提交时,如果watch监控的多个key中任何key的值被其他客户端更改,则使用exec执行事务时,事务对列不会被执行,同时返回Nullmulti-bulk 通知调用者事务执行失败。

  1. springboot整合redis

步骤:

  • 导入依赖
1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 在application.properties中配置
1
2
3
4
properties复制代码spring.redis.host=115.29.232.195
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=50000
  • 测试
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
java复制代码@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
/*
* opsForList:操作list,类似string
* opsForGeo 操作geo
* opsForSet
* .......
* 和redis命令行一样。
* */
/*获取redis的连接对象*/
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
redisTemplate.opsForSet().add("k5","v9");
Set k5 = redisTemplate.opsForSet().members("k5");
System.out.println(k5);

}
}

redis要做的配置

  • 阿里云设置redis的安全组:端口6379


  • 修改redis.conf

(在/usr/local/bin/myredisconfig/redis.conf)

bind 127.0.0.1 改为 bind 0.0.0.0(注释掉也行)

protected-mode yes 改为 protected-mode no: (即该配置项表示是否开启保护模式,默认是开启,开启后Redis只会本地进行访问,拒绝外部访问)

daemonize no 改为 daemonize yes

注意:修改后要重新登录redis


  • 关于防火墙的设置

rpm -qa|grep firewalld;rpm -qa|grep firewall-config :检查系统是否安装了firewalld和firewall-config.CentOS中系统默认安装firewalld, firewall-config要自己安装

yum -y update firewalld:将firewalld更新为最新版

yum -y install firewall-config:安装firewall-config

systemctl start firewalld:启动firewalld服务

systemctl status firewalld:查看firewalld的状态

systemctl stop firewalld:停止firewalld服务

systemctl enable firewalld:恢复开机自动启动firewalld服务

推荐参考文章:CentOS7中firewalld的安装与使用详解 blog.csdn.net/solaraceboy…


  • 检查阿里云的防火墙是否开放端口号

firewall-cmd --query-port=6379/tcp 如果是 yes,则就是开放的。

如果是no

则永久开放6379端口:firewall-cmd --zone=public --add-port=6379/tcp --permanent

重新加载:firewall-cmd --reload

然后再查看端口是否开启:firewall-cmd --query-port=6379/tcp

  • 查看redis服务进程
1
perl复制代码 ps -ef | grep redis
  • 如果上面的方法都试过了但还是不行,试试重启服务器

推荐一个好用的redis客户端工具

​ Another Redis Desktop Manager

下载地址:github.com/qishibo/Ano…

可以用它来测试和管理redis

redis的序列化配置

  • 通过源码可知,springboot自动帮我们在容器中生成了一个redisTemplate和一个StringRedisTemplate,但是这个redisTemplate的泛型是<ooject,object>,写代码不方便,需要类型转换的代码,并且没有设置数据存在redis时,key及value的序列化方式。因此要自定义一个配置类。
  • 为什么要序列化:www.jianshu.com/p/cc5a29b06…
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
java复制代码package com.kuang.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:05
*/
@Configuration

public class redisConfig {
/*编写自己的redisTemplate----固定模板*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
/*为了自己开发方便,一般直接使用<String,object>*/
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
/*json序列化配置*/
Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer.setObjectMapper(om);
/*string序列化配置*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


/*配置具体的序列化方式*/
/*key采用string的序列化方式*/
template.setKeySerializer(stringRedisSerializer);
/*hash的key采用string的序列化方式*/
template.setHashKeySerializer(stringRedisSerializer);
/*value序列化方式采用jackson*/
template.setValueSerializer(Jackson2JsonRedisSerializer);
/*hash的value序列化方式采用jackson*/
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

redis的工具类

  • 直接使用redisTemplate操作redis时,需要很多代码,因此直接封装一个redisUtils,这样写代码方便一点。这个redisUtils交给spring容器实例化,使用时直接注解注入。
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
java复制代码package com.kuang.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:52
*/
/*在真实开发中,经常使用*/
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,
TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================

/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================

/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
* 时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}


/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove (String key,long count, Object value){
try {
Long remove = redisTemplate.opsForList().remove(key, count,value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}

测试代码

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
kotlin复制代码package com.kuang;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kuang.pojo.User;
import com.kuang.utils.RedisUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Set;

@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
@Qualifier("redisTemplate")/*避免与源码重合,跳转到自定义redisTempalte*/
private RedisTemplate redisTemplate;

@Autowired
private RedisUtil redisUtil;


@Test
void contextLoads() {
/*
* opsForList:操作list,类似string
* opsForGeo 操作geo
* opsForSet
* .......
* 和redis命令行一样。
* */
/*获取redis的连接对象*/
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
redisTemplate.opsForSet().add("k5","v9");
Set k5 = redisTemplate.opsForSet().members("k5");
System.out.println(k5);



}

@Test
public void test() throws JsonProcessingException {
/*真实开发一般使用json传递数据*/
User user = new User("kuangshen", 3);
// String jsonUser = new ObjectMapper().writeValueAsString(user);
// redisTemplate.opsForValue().set("user", jsonUser);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));

}

@Test
public void test1() {
redisUtil.set("username", "coderchen");
System.out.println(redisUtil.get("username"));
}

}

本文转载自: 掘金

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

6 月份最热 GitHub 盘点

发表于 2021-06-19

2021 年 6 月份最热 GitHub 盘点,这些 GitHub 项目是本月 Star 攀升最多的 repo,它们分别是:

  • HarmonyOS 鸿蒙操作系统
  • 自行车自动驾驶系统
  • 金庸群侠传 3D 重制版
  • 阿里云盘小白羊版
  • 计算机基础知识笔记
  • Spring Boot 相关漏洞学习资料
  • 阿里巴巴 MySQL binlog 增量订阅&消费组件
  • 一键换脸应用
  • 用 Python 编写的一些简单的项目
  • 命令行的艺术

01. HarmonyOS 鸿蒙

本月第一个盘点,一定给鸿蒙。HarmonyOS 华为鸿蒙操作系统开源,迅速霸占 GitHub 热榜。

1
arduino复制代码地址:https://github.com/Awesome-HarmonyOS/HarmonyOS

鸿蒙系统是第一款基于微内核的全场景分布式OS,是华为自主研发的操作系统。2019年8月9日,鸿蒙系统在华为开发者大会上正式发布,华为会率先部署在智慧屏、车载终端、穿戴等智能终端上,未来会有越来越多的智能设备使用开源的鸿蒙OS。

鸿蒙OS实现模块化耦合,对应不同设备可弹性部署,鸿蒙OS有三层架构,第一层是内核,第二层是基础服务,第三层是程序框架 。可用于大屏、PC、汽车等各种不同的设备上。还可以随时用在手机上,但暂时华为手机端依然优先使用安卓。

鸿蒙 OS 底层由鸿蒙微内核、Linux 内核、Lite OS 组成,未来将发展为完全的鸿蒙微内核架构。


02. 自动驾驶自行车

这个项目重新定义了自行车,这个项目的作者正式 B站 Up主 稚晖君。之前他搞过很多有意思的小项目,比如前段时间大火的迷你电视机就是出自他手。

这次,他闭关修炼了四个与,搞出来一个自动驾驶系统。不过这个自动驾驶系统不是用在电动汽车上的,而是自行车。这个项目算是这位 UP 最硬核的一个期了,整个自动驾驶系统研发过程断断续续花了四个月。

1
ruby复制代码视频:https://www.bilibili.com/video/BV1fV411x72a地址:https://github.com/peng-zhihui/XUAN-Bike

03. 金庸群侠传 3D 重制版

金庸群侠传 3D 重制版是一个非盈利游戏项目,重制经典游戏《金庸群侠传》并支持后续一系列 MOD 和二次开发。

重置版是作者纯粹为了兴趣和学习在业余时间打磨出来的,最近作者比较忙,于是开源出来,希望感兴趣的朋友一起开发。

项目作者也在B站发布了重置版的介绍视频。视频中可以看到,重置版画质更精良,细节更生动。

1
arduino复制代码地址:https://github.com/jynew/jynew

视频:www.bilibili.com/video/BV1Yw…


04. 阿里云盘小白羊版

这是作者基于阿里云盘网页版开发的 PC 客户端,已经实现了阿里云盘官方客户端的基本功能。

1
bash复制代码https://github.com/liupan1890/aliyunpan

05. CS - Notes

这个 repo 是技术面试必备基础知识,包括算法、计算机操作系统、计算机网络、系统设计等等。

项目内容不是将网上的资料的随意拼凑,除了少部分引用书上和技术文档的原文,其余都是博主的原创。

1
arduino复制代码地址:https://github.com/CyC2018/CS-Notes


06. Spring Boot 相关漏洞学习资料

本文是 Spring Boot 相关漏洞的学习资源,仅作为安全研究和授权测试使用。不要用于非法活动。

这个项目从信息泄露、远程代码执行两大方面,总结了 16 个案例,提供了详细的步骤对提到的漏洞进行攻击,同时还会阐述漏洞原理,进行漏洞分析。

1
arduino复制代码地址:https://github.com/LandGrey/SpringBootVulExploit

比如下图就是如何利用漏洞获取被星号脱敏的密码:


07. 阿里巴巴 MySQL binlog 增量订阅&消费组件

早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。

1
arduino复制代码地址:https://github.com/alibaba/canal

08. 一键换脸

FaceSwap 是一种利用深度学习算法来换掉图片和视频中的人脸的工具。基于 Tensorflow、Keras 和 Python,Faceswap 可以在 Windows、macOS 和 Linux 上运行。

安装了这个应用,你就能在你电脑上通过可视化交互的方式构建自己的换脸模型了。

1
arduino复制代码地址:https://github.com/deepfakes/faceswap

09. 用 Python 编写的一些简单的项目

这个仓库包含了用 Python 编写的一些简单的项目,包括全能媒体播放器、病毒扫描软件、Bilibili 视频一键下载工具。

还有暴力破解压缩密码、密码本、破解wifi密码、调用WinRAR破解压缩密码、聚合搜索、批量重命名、各平台热榜等等。

1
arduino复制代码https://github.com/cnzbpy/simplepy

10. 命令行的艺术

命令行可以提高我们的工作效率、增加灵活性。该项目总结了在 Linux 上使用命令行时的相关技巧,内容覆盖基础使用、文件操作、数据处理等等。

有专门针对 Windows 或 macOS 的部分,本教程既适合初学者,也适合经验丰富的人,并附有具体的实战例子帮助大家的理解。

1
arduino复制代码地址:https://github.com/jlevy/the-art-of-command-line

本文转载自: 掘金

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

SpringBoot RestTemplate使用工具类

发表于 2021-06-19

1.常用方法

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
typescript复制代码public class RestTemplateUtil {

private static final String EMPTY_STR = "";

/**
* get请求
* @param restTemplate
* @param url
* @param headerMap
* @param paramMap
* @return
*/
public static String get(RestTemplate restTemplate, String url, Map<String,String> headerMap, Map<String,String> paramMap){
HttpHeaders headers = new HttpHeaders();
if(!CollectionUtils.isEmpty(headerMap)){
headerMap.forEach((k,v)-> headers.set(k,v));
}
StringBuffer paramStr = new StringBuffer(EMPTY_STR);
if(!CollectionUtils.isEmpty(paramMap)){
paramMap.forEach((k,v)->{
if(paramStr.toString().equals(EMPTY_STR)){
paramStr.append("?").append(k).append("=").append(v);
}else{
paramStr.append("&").append(k).append("=").append(v);
}
});
}
HttpEntity<String> httpEntity = restTemplate.exchange(url+paramStr.toString(), HttpMethod.GET,CollectionUtils.isEmpty(headerMap) ? null : new HttpEntity<>(headers),String.class);
return httpEntity.getBody();
}

/**
* post JSON
* @param restTemplate
* @param url
* @param headerMap
* @param paramObjectStr
* @return
*/
public static String postJson(RestTemplate restTemplate, String url, Map<String,String> headerMap, String paramObjectStr){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
if(!CollectionUtils.isEmpty(headerMap)){
headerMap.forEach((k,v)-> headers.set(k,v));
}
String resultStr = restTemplate.postForObject(url,new HttpEntity<>(paramObjectStr,headers),String.class);
return resultStr;
}

/**
* post Form
* @param restTemplate
* @param url
* @param headerMap
* @param paramMap
* @return
*/
public static String postForm(RestTemplate restTemplate, String url, Map<String,String> headerMap, MultiValueMap<String,Object> paramMap){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
if(!CollectionUtils.isEmpty(headerMap)){
headerMap.forEach((k,v)-> headers.set(k,v));
}
String resultStr = restTemplate.postForObject(url,new HttpEntity<>(paramMap,headers),String.class);
return resultStr;
}
}

1.1测试用例

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
typescript复制代码@Test
public void tt01(){
RestTemplate restTemplate = new RestTemplate();
Map<String,String> paramMap = new HashMap<>();
paramMap.put("id","2");
String resultStr = RestTemplateUtil.get(restTemplate,"http://127.0.0.1:8080/user/getById",null,paramMap);
System.out.println(resultStr);
}

@Test
public void tt02(){
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String,Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("id","2");
String resultStr = RestTemplateUtil.postForm(restTemplate,"http://127.0.0.1:8080/user/getById",null,paramMap);
System.out.println(resultStr);
}

@Test
public void tt03(){
RestTemplate restTemplate = new RestTemplate();
User user = new User();
user.setUserName("xxxx");
user.setPwd("1231231132");
String resultStr = RestTemplateUtil.postJson(restTemplate,"http://127.0.0.1:8080/user/add",null, JSON.toJSONString(user));
ResponseMessage<User> responseMessage = JSON.parseObject(resultStr,new TypeReference<ResponseMessage<User>>(){});
System.out.println(responseMessage.getData());
}

2.文件相关用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ini复制代码二、上传文件
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add("file", new ClassPathResource("/tmp/user.txt"));
RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(multiPartBody);

三、下载文件
// 小文件
RequestEntity requestEntity = RequestEntity.get(uri).build();
ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);
byte[] downloadContent = responseEntity.getBody();

// 大文件
ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {
@Override
public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {
File rcvFile = File.createTempFile("rcvFile", "zip");
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);
}
};
File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor);

本文转载自: 掘金

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

1…638639640…956

开发者博客

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