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

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


  • 首页

  • 归档

  • 搜索

【微软算法面试高频题】兄弟字符串 1题目 2解析

发表于 2021-06-30

微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播

1.题目

如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)

2.解析

2.1 O(n*m)的轮询方法

判断string2中的字符是否在string1中: String 1: ABCDEFGHLMNOPQRS String 2: DCGSRQPO

判断一个字符串是否在另一个字符串中,最直观也是最简单的思路是,针对第二个字符串string2中每一个字符,与第一个字符串string1中每个字符依次轮询比较,看它是否在第一个字符串string1中。

2.2 O(mlogm)+O(nlogn)+O(m+n)的排序方法

一个稍微好一点的方案是先对这两个字符串的字母进行排序,然后同时对两个字串依次轮询。两个字串的排序需要(常规情况)O(mlogm) + O(nlogn)次操作,之后的线性扫描需要O(m+n)次操作。

步骤如下: 1、判断两个字符串的长度是否一样,如果不一样则退出。 2、每个字符串按字符排序,如acb排序之后是abc,如果是兄弟字符串的话,排序之后是一样的。

2.3 Hashmap

建两个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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
c++复制代码#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;

int main()
{
int hash1[256],hash2[256],i,len1,len2;
char str1[100],str2[100];

printf("请输入2个字符串:(0结束)\n");
while(1)
{
//输入
scanf("%s",str1);
if(!strcmp(str1,"0")) break;
scanf("%s",str2);

//比较长度
len1=strlen(str1);
len2=strlen(str2);
if(len1!=len2)
{
printf("%s,%s,二者不是兄弟字符串\n",str1,str2);
continue;
}

//字符是否相同
memset(hash1,0,sizeof(hash1));
memset(hash2,0,sizeof(hash2));
for(i=0;i<len1;i++)
{
hash1[str1[i]]++;
hash2[str2[i]]++;
}
for(i=0;i<255;i++)
{
if(hash1[i]!=hash2[i])//不同
break;
}
if(i==255)
printf("%s,%s,二者是兄弟字符串\n",str1,str2);
else
printf("%s,%s,二者不是兄弟字符串\n",str1,str2);
}
}
/*
bad abd
abcd abc
aabbccdd abcdabcd
abcdabc aabbccc
aaa bbb
ababa babab
ababa aabba
0
*/

2.4 O(n)到 O(n+m)的素数方法

你可能会这么想:O(n+m)是你能得到的最好的结果了,至少要对每个字母至少访问一次才能完成这项操作,而上一节最后的俩个方案是刚好是对每个字母只访问一次。

给26个字符依次赋予质数。质数是比较特殊的一堆数字,它们只能被1和本身整除。以给a赋值2、给b赋值3、给c赋值5、给d赋值7、给e赋值11、给f赋值13。

加法:两个字符串中的所有字符都赋值了,接着让它们各自相加,如果两个字符串得出的结果是一样的,那它们是兄弟字符串。但是,b+f=3+13=16;c+e=5+11=16,所以有误;

乘法:两个字符串中的所有字符让它们各自相乘,方法是对的,但是会溢出,所以要大整数处理了;用平方和或者立方和:考虑平方和会不会解决加法有误,乘法溢出:bb+ff=33+1313=178;cc+ee=55+1111=146

然后——轮询第二个字符串,用每个字母除它。如果除的结果有余数,这说明有不匹配的字母。如果整个过程中没有余数,你应该知道它是第一个字串恰好的子集了。

如果是加法,那么使用一个类似于set的数据结构来记录字符串中字母出现的频次即可,但是空间开销会比素数更大(素数法要求分配素数也是需要空间的,虽然是常数级)。代码如下:

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
c++复制代码/*
如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,
问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。
*/
#include <iostream>
using namespace std;

int isBroStr(char *str1, char *str2)
{
int a[26 * 2] = {0};
int i, strLen;

if (!str1 && !str2)
return 1;
else if (!str1 || !str2)
return 0;
else
{
if(strlen(str1) != strlen(str2))
return 0;
strLen = strlen(str1);
for(i = 0; i < strLen; i++)
{
++a[str1[i] - 'A'];
--a[str2[i] - 'A'];
}
for(i = 0; i < 26 * 2; i++)
if (a[i])
return 0;
return 1;
}
}

int main()
{
char *str1 = "asdfaabAAB";
char *str2 = "asdfAABaab";
if (isBroStr(str1, str2))
cout << " String 1 and String 2 are brothers!" << endl;
else
cout << " String 1 and String 2 are not brothers!" << endl;
system("PAUSE");
return 0;
}

微软和谷歌的几个大佬组织了一个面试刷题群,可以加管理员VX:sxxzs3998(备注掘金),进群参与讨论和直播

本文转载自: 掘金

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

HTTP方式文件分片断点下载 前言 HTTP之Range 分

发表于 2021-06-30

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

前言

在进行大文件或网络带宽不是很好的情况下,分片断点下载就会显得很有必要,目前各大下载工具,如:迅雷,都是很好的支持分片断点下载功能的。本文就通过http方式进行文件分片断点下载,进行实战说明。

HTTP之Range

在开始之前有必要了解一下相关概念及原理,即:HTTP之Range,才能更好的理解分片断点下载的原理。

什么是Range

Range是一个HTTP请求头,告知服务器要返回文件的哪一部分,即:哪个区间范围(字节)的数据,在 Range 中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略 Range 头,从而返回整个文件,状态码用 200 。

因为有了HTTP中Range请求头的存在,分片断点下载,便简单了许多。

当你正在看大片时,网络断了,你需要继续看的时候,文件服务器不支持断点的话,则你需要重新等待下载这个大片,才能继续观看。而Range支持的话,客户端就会记录了之前已经看过的视频文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个视频文件发送回客户端,以此节省网络带宽。

Range规范

1
2
3
4
sql复制代码Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

:范围所采用的单位,通常是字节(bytes)

:一个整数,表示在特定单位下,范围的起始值

:一个整数,表示在特定单位下,范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束。

1
ini复制代码Range: bytes=1024-2048

分片断点下载之实现

以Java Spring Boot的方式来实现,核心代码如下:

  • serivce层

package com.xcbeyond.common.file.chunk.service.impl;

import com.xcbeyond.common.file.chunk.service.FileService;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**

+ 文件分片操作Service
+ @Auther: xcbeyond
+ @Date: 2019/5/9 23:02
\*/
@Service
public class FileServiceImpl implements FileService {


/\*\*


    - 文件分片下载
    - @param range http请求头Range,用于表示请求指定部分的内容。
    - 
1
sql复制代码         格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容
- @param request - @param response \*/ public void fileChunkDownload(String range, HttpServletRequest request, HttpServletResponse response) { //要下载的文件,此处以项目pom.xml文件举例说明。实际项目请根据实际业务场景获取 File file = new File(System.getProperty("user.dir") + "\pom.xml"); //开始下载位置 long startByte = 0; //结束下载位置 long endByte = file.length() - 1; //有range的话 if (range != null && range.contains("bytes=") && range.contains("-")) { range = range.substring(range.lastIndexOf("=") + 1).trim(); String ranges[] = range.split("-"); try { //根据range解析下载分片的位置区间 if (ranges.length == 1) { //情况1,如:bytes=-1024 从开始字节到第1024个字节的数据 if (range.startsWith("-")) { endByte = Long.parseLong(ranges[0]); } //情况2,如:bytes=1024- 第1024个字节到最后字节的数据 else if (range.endsWith("-")) { startByte = Long.parseLong(ranges[0]); } } //情况3,如:bytes=1024-2048 第1024个字节到2048个字节的数据 else if (ranges.length == 2) { startByte = Long.parseLong(ranges[0]); endByte = Long.parseLong(ranges[1]); }
1
2
3
4
ini复制代码 } catch (NumberFormatException e) {
startByte = 0;
endByte = file.length() - 1;
}
} //要下载的长度 long contentLength = endByte - startByte + 1; //文件名 String fileName = file.getName(); //文件类型 String contentType = request.getServletContext().getMimeType(fileName); //响应头设置 //[developer.mozilla.org/zh-CN/docs/…](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Ranges) response.setHeader("Accept-Ranges", "bytes"); //Content-Type 表示资源类型,如:文件类型 response.setHeader("Content-Type", contentType); //Content-Disposition 表示响应内容以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。 // 这里文件名换成下载后你想要的文件名,inline表示内联的形式,即:浏览器直接下载 response.setHeader("Content-Disposition", "inline;filename=pom.xml"); //Content-Length 表示资源内容长度,即:文件大小 response.setHeader("Content-Length", String.valueOf(contentLength)); //Content-Range 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小] response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length()); response.setStatus(response.SC\_OK); response.setContentType(contentType); BufferedOutputStream outputStream = null; RandomAccessFile randomAccessFile = null; //已传送数据大小 long transmitted = 0; try { randomAccessFile = new RandomAccessFile(file, "r"); outputStream = new BufferedOutputStream(response.getOutputStream()); byte[] buff = new byte[2048]; int len = 0; randomAccessFile.seek(startByte); //判断是否到了最后不足2048(buff的length)个byte while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) { outputStream.write(buff, 0, len); transmitted += len; } //处理不足buff.length部分 if (transmitted < contentLength) { len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted)); outputStream.write(buff, 0, len); transmitted += len; }
1
2
3
ini复制代码 outputStream.flush();
response.flushBuffer();
randomAccessFile.close();
} catch (IOException e) { e.printStackTrace(); } finally { try { if (randomAccessFile != null) { randomAccessFile.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

  • controller层

package com.xcbeyond.common.file.chunk.controller;

import com.xcbeyond.common.file.chunk.service.FileService;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**

+ 文件分片操作Controller
+ @Auther: xcbeyond
+ @Date: 2019/5/9 22:56
\*/
@RestController
public class FileController {
@Resource
private FileService fileService;


/\*\*


    - 文件分片下载
    - @param range http请求头Range,用于表示请求指定部分的内容。
    - 
1
sql复制代码         格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容
- @param request http请求 - @param response http响应 \*/ @RequestMapping(value = "/file/chunk/download", method = RequestMethod.GET) public void fileChunkDownload(@RequestHeader(value = "Range") String range, HttpServletRequest request, HttpServletResponse response) { fileService.fileChunkDownload(range,request,response); } }

通过postman进行测试验证,启动Spring Boot后,如:下载文件前1024个字节的数据(Range:bytes=0-1023),如下:

)​

注:此处 实现中没有提供客户端,客户端可循环调用本例中下载接口,每次调用指定实际的下载偏移区间range。

请注意响应头Accept-Ranges、Content-Range

)​

Accept-Ranges: 表示响应标识支持范围请求,字段的具体值用于定义范围请求的单位,如:bytes。当发现Accept-Range

头时,可以尝试继续之前中断的下载,而不是重新开始。

Content-Range: 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小],如:bytes 0-1023/2185

源码:github.com/xcbeyond/co…

(如果你觉得不错,不妨留下脚步,在GitHub上给个Star)

参考:developer.mozilla.org/zh-CN/docs/…

​

​

本文转载自: 掘金

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

33张图解析ReentrantReadWriteLock源码

发表于 2021-06-30

大家好,我是阿星,今天是一篇硬核文,请各位读者大大们系好安全带,马上要发车了。

晕车的朋友,可以先吃一颗阿星独家秘制的晕车药,童叟无欺,货真价实,还免费,白嫖党狂喜(16张图揭开AQS)。

本文大纲如下

纵观全局

我的英文名叫ReentrantReadWriteLock(后面简称RRW),大家喜欢叫我读写锁,因为我常年混迹在读多写少的场景。

读写锁规范

作为合格的读写锁,先要有读锁与写锁才行。

所以声明了ReadWriteLock接口,作为读写锁的基本规范。

之后都是围绕着规范去实现读锁与写锁。

读锁与写锁

WriteLock与ReadLock就是读锁和写锁,它们是RRW实现ReadWriteLock接口的产物。

但读锁、写锁也要遵守锁操作的基本规范.

所以WriteLock与ReadLock都实现了Lock接口。

那么WriteLock与ReadLock对Lock接口具体是如何实现的呢?

自然是少不了我们的老朋友AQS了。

AQS

众所周知,要实现锁的基本操作,必须要仰仗AQS老大哥了。

AQS(AbstractQueuedSynchronizer)抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,能够极大地减少实现工作,用大白话来说,AQS为加锁和解锁过程提供了统一的模板函数,只有少量细节由子类自己决定。

AQS简化流程图如下

如果读者想深入AQS细节,可以看阿星的这篇文章:16张图揭开AQS

Sync

AQS为加锁和解锁过程提供了统一的模板函数,只有少量细节由子类自己决定,但是WriteLock与ReadLock没有直接去继承AQS。

因为WriteLock与ReadLock觉得,自己还要去继承AQS实现一些两者可以公用的抽象函数,不仅麻烦,还有重复劳动。

所以干脆单独提供一个对锁操作的类,由WriteLock与ReadLock持有使用,这个类叫Sync。

Sync继承AQS实现了如下的核心抽象函数

  • tryAcquire
  • release
  • tryAcquireShared
  • tryReleaseShared

其中tryAcquire、release是为WriteLock写锁准备的。

tryAcquireShared、tryReleaseShared是为ReadLock读锁准备的,这里阿星后面会说。

上面说了Sync实现了一些AQS的核心抽象函数,但是Sync本身也有一些重要的内容,看看下面这段代码

我们都知道AQS中维护了一个state状态变量,正常来说,维护读锁与写锁状态需要两个变量,但是为了节约资源,使用高低位切割实现state状态变量维护两种状态,即高16位表示读状态,低16位表示写状态。

关于读写锁状态设计具体细节可以看阿星的文章:ReentrantReadWriteLock的位运算

Sync中还定义了HoldCounter与ThreadLocalHoldCounter

  • HoldCounter是用来记录读锁重入数的对象
  • ThreadLocalHoldCounter是ThreadLocal变量,用来存放第一个获取读锁线程外的其他线程的读锁重入数对象

如果读者对ThreadLocal不太熟悉,可以去看阿星的文章: 保姆级教学,22张图揭开ThreadLocal

公平与非公平策略

你看,人家ReentrantLock都有公平与非公平策略,所以ReentrantReadWriteLock也要有。

什么是公平与非公平策略?

因为在AQS流程中,获取锁失败的线程,会被构建成节点入队到CLH队列,其他线程释放锁会唤醒CLH队列的线程重新竞争锁,如下图所示(简化流程)。

非公平策略是指,非CLH队列的线程与CLH队列的线程竞争锁,大家各凭本事,不会因为你是CLH队列的线程,排了很久的队,就把锁让给你。

公平策略是指,严格按照CLH队列顺序获取锁,一定会让CLH队列线程竞争成功,如果非CLH队列线程一直占用时间片,那就一直失败,直到时间片轮到CLH队列线程为止,所以公平策略的性能会更差。

回到正题,为了支持公平与非公平策略,Sync扩展了FairSync、NonfairSync子类,两个子类实现了readerShouldBlock、writerShouldBlock函数,即读锁与写锁是否阻塞。

关于readerShouldBlock、writerShouldBlock函数在什么地方使用阿星后面会说。

ReentrantReadWriteLock全局图

最后阿星把前面讲过的内容,全部组装起来,构成下面这张图。

有了全局观后,后面就可以深入细节逐个击破了。

深入细节

后面我们只要攻破5个细节就够了,分别是读写锁的创建、获取写锁、释放写锁、获取读锁、释放读锁。

ReentrantReadWriteLock的创建

读写锁的创建,会初始化化一系列类,代码如下

ReentrantReadWriteLock默认是非公平策略,如果想用公平策略,可以直接调用有参构造器,传入true即可。

但不管是创建FairSync还是NonfairSync,都会触发Sync的无参构造器,因为Sync是它们的父类(本质上它们俩都是Sync)。

因为Sync需要提供给ReadLock与WriteLock使用,所以创建ReadLock与WriteLock时,会接收ReentrantReadWriteLock对象作为入参。

最后通过ReentrantReadWriteLock.sync把Sync交给了ReadLock与WriteLock。

获取写锁

我们遵守ReadWriteLock接口规范,调用ReentrantReadWriteLock.writeLock函数获取写锁对象。

获取到写锁对象后,遵守Lock接口规范,调用lock函数获取写锁。

WriteLock.lock函数是由Sync实现的(FairSync或NonfairSync)。

sync.acquire(1)函数是AQS中的独占式获取锁流程模板(Sync继承自AQS)。

WriteLock.lock调用链如下图

我们只关注tryAcquire函数,其他函数是AQS的获取独占式锁失败后的流程内容,不属于本文范畴,tryAcquire函数代码如下

为了易于理解,阿星把它转成流程图

通过流程图,我们发现了一些要点

  • 读写互斥
  • 写写互斥
  • 写锁支持同一个线程重入
  • writerShouldBlock写锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)

释放写锁

获取到写锁,临界区执行完,要记得释放写锁(如果重入多次要释放对应的次数),不然会阻塞其他线程的读写操作,调用unlock函数释放写锁(Lock接口规范)。

WriteLock.unlock函数也是由Sync实现的(FairSync或NonfairSync)。

sync.release(1)执行的是AQS中的独占式释放锁流程模板(Sync继承自AQS)。

WriteLock.unlock调用链如下图

再来看看tryRelease函数,其他函数是AQS的释放独占式成功后的流程内容,不属于本文范畴,tryRelease函数代码如下

为了易于理解,阿星把它转成流程图

因为同一个线程可以对相同的写锁重入多次,所以也要释放的相同的次数。

获取读锁

我们遵守ReadWriteLock接口规范,调用ReentrantReadWriteLock.readLock函数获取读锁对象。

获取到读锁对象后,遵守Lock接口规范,调用lock函数获取读锁。

ReadLock.lock函数是由Sync实现的(FairSync或NonfairSync)。

sync.acquireShared(1)函数执行的是AQS中的共享式获取锁流程模板(Sync继承自AQS)。

ReadLock.lock调用链如下图

我们只关注tryAcquireShared函数,doAcquireShared函数是AQS的获取共享式锁失败后的流程内容,不属于本文范畴,tryAcquireShared函数代码如下

代码还挺多的,为了易于理解,阿星把它转成流程图

通过流程图,我们发现了一些要点

  • 读锁共享,读读不互斥
  • 读锁可重入,每个获取读锁的线程都会记录对应的重入数
  • 读写互斥,锁降级场景除外
  • 支持锁降级,持有写锁的线程,可以获取读锁,但是后续要记得把读锁和写锁读释放
  • readerShouldBlock读锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)

释放读锁

获取到读锁,执行完临界区后,要记得释放读锁(如果重入多次要释放对应的次数),不然会阻塞其他线程的写操作,通过调用unlock函数释放读锁(Lock接口规范)。

ReadLock.unlock函数也是由Sync实现的(FairSync或NonfairSync)。

sync.releaseShared(1)函数执行的是AQS中的共享式释放锁流程模板(Sync继承自AQS)。

ReadLock.unlock调用链如下图

我们只关注tryReleaseShared函数,doReleaseShared函数是AQS的释放共享式锁成功后的流程内容,不属于本文范畴,tryReleaseShared函数代码如下

为了易于理解,阿星把它转成流程图

这里有三点需要注意

  • 第一点:线程读锁的重入数与读锁数量是两个概念,线程读锁的重入数是每个线程获取同一个读锁的次数,读锁数量则是所有线程的读锁重入数总和。
  • 第二点:AQS的共享式释放锁流程模板中,只有全部的读锁被释放了,才会去执行doReleaseShared函数
  • 第三点:因为使用的是AQS共享式流程模板,如果CLH队列后面的线程节点都是因写锁阻塞的读锁线程节点,会传播唤醒

小结

最后阿星做个小结,ReentrantReadWriteLock底层实现与ReentrantLock思路一致,它们都离不开AQS,都是声明一个继承AQS的Sync,并在Sync下扩展公平与非公平策略,后续的锁相关操作都委托给公平与非公平策略执行。

我们还发现,在AQS中除了独占式模板,还有共享式模板,它们在多线程访问共享资源的流程会有所差异,就如ReentrantReadWriteLock中读锁使用共享式,写锁使用独占式。

最后再捋一捋写锁与读锁的逻辑

  1. 读读不阻塞
  2. 写锁阻塞写之后的读写锁,但是不阻塞写锁之前的读锁线程
  3. 写锁会被写之前的读写锁阻塞
  4. 读锁节点唤醒会无条件传播唤醒CLH队列后面的读锁节点
  5. 写锁可以降级为读锁,防止更新丢失
  6. 读锁、写锁都支持重入

历史好文推荐

  • 透彻Java线程状态转换
  • 图文并茂的聊聊ReentrantReadWriteLock的位运算
  • 通俗易懂的ReentrantLock,不懂你来砍我
  • 万字长文 | 16张图解开AbstractQueuedSynchronizer
  • 写给小白看的LockSupport
  • 13张图,深入理解Synchronized
  • 由浅入深CAS,小白也能与BAT面试官对线
  • 小白也能看懂的Java内存模型
  • 保姆级教学,22张图揭开ThreadLocal
  • 进程、线程与协程傻傻分不清?一文带你吃透!
  • 什么是线程安全?一文带你深入理解

关于我

阿星是一个热爱技术的Java程序猿,公众号 「程序猿阿星」 定期分享有趣有料的精品原创文章!

非常感谢各位小哥哥小姐姐们能看到这里,原创不易,文章有帮助可以关注、点个赞、分享与评论,都是支持(莫要白嫖)!

愿你我都能奔赴在各自想去的路上,我们下篇文章见。

本文转载自: 掘金

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

北京某大公司:SpringBean生命周期

发表于 2021-06-30

《对线面试官》系列目前已经连载25篇啦!有深度风趣的系列!

  • 【对线面试官】Java注解
  • 【对线面试官】Java泛型
  • 【对线面试官】 Java NIO
  • 【对线面试官】Java反射 && 动态代理
  • 【对线面试官】多线程基础
  • 【对线面试官】 CAS
  • 【对线面试官】synchronized
  • 【对线面试官】AQS&&ReentrantLock
  • 【对线面试官】线程池
  • 【对线面试官】ThreadLocal
  • 【对线面试官】CountDownLatch和CyclicBarrier
  • 【对线面试官】为什么需要Java内存模型?
  • 【对线面试官】List
  • 【对线面试官】Map
  • 【对线面试官】SpringMVC
  • 【对线面试官】Spring基础
  • 【对线面试官】SpringBean生命周期
  • 【对线面试官】Redis基础
  • 【对线面试官】Redis持久化
  • 【对线面试官】Kafka基础
  • 【对线面试官】使用Kafka会考虑什么问题?
  • 【对线面试官】MySQL索引
  • 【对线面试官】MySQL 事务&&锁机制&&MVCC
  • 【对线面试官】MySQL调优

关键源码方法(强烈建议自己去撸一遍)

  • org.springframework.context.support.AbstractApplicationContext#refresh(入口)
  • org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
  • org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)
  • org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)(万恶之源,获取并创建Bean的入口)
  • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(实际的获取并创建Bean的实现)
  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)(从缓存中尝试获取)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])(实例化Bean)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(实例化Bean具体实现)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance(具体实例化过程)
  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory(将实例化后的Bean添加到三级缓存)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean(实例化后属性注入)
  • org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)(初始化入口)

去网上看博客的时候,找到了几张比较好的图,这里贴下方便大家理解吧~

来源:https://www.jianshu.com/p/6c359768b1dc

文章以纯面试的角度去讲解,所以有很多的细节是未铺垫的。

鉴于很多同学反馈没看懂【对线面试官】系列,基础相关的知识我确实写过文章讲解过啦,但有的同学就是不爱去翻。

为了让大家有更好的体验,我把基础文章也找出来(重要的知识点我还整理过电子书,比如说像多线程、集合、Spring这种面试必考的早就已经转成PDF格式啦)

我把这些上传到网盘,你们有需要直接下载就好了。做到这份上了,不会还想白嫖吧?点赞和转发又不用钱。

链接:pan.baidu.com/s/1pQTuKBYs… 密码:3wom

欢迎关注我的微信公众号【Java3y】来聊聊Java面试

本文转载自: 掘金

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

Flink 大厂面试题

发表于 2021-06-30

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

一 简单介绍一下 Flink

  Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。并且 Flink 提供了数据分布、容错机制以及资源管理等核心功能。Flink提供了诸多高抽象层的API以便用户编写分布式任务:

  DataSet API, 对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,用户可以方便地使用Flink提供的各种操作符对分布式数据集进行处理,支持Java、Scala和Python。

  DataStream API,对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作,支持Java和Scala。

  Table API,对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类SQL的DSL对关系表进行各种查询操作,支持Java和Scala。

  此外,Flink 还针对特定的应用领域提供了领域库,例如: Flink ML,Flink 的机器学习库,提供了机器学习Pipelines API并实现了多种机器学习算法。 Gelly,Flink 的图计算库,提供了图计算的相关API及多种图计算算法实现。

二 Flink跟Spark Streaming的区别

  这个问题是一个非常宏观的问题,因为两个框架的不同点非常之多。但是在面试时有非常重要的一点一定要回答出来:Flink 是标准的实时处理引擎,基于事件驱动。而 Spark Streaming 是微批(Micro-Batch)的模型。

下面我们就分几个方面介绍两个框架的主要区别:

  1. 架构模型Spark Streaming 在运行时的主要角色包括:Master、Worker、Driver、Executor,Flink 在运行时主要包含:Jobmanager、Taskmanager和Slot。
  2. 任务调度Spark Streaming 连续不断的生成微小的数据批次,构建有向无环图DAG,Spark Streaming 会依次创建 DStreamGraph、JobGenerator、JobScheduler。Flink 根据用户提交的代码生成 StreamGraph,经过优化生成 JobGraph,然后提交给 JobManager进行处理,JobManager 会根据 JobGraph 生成 ExecutionGraph,ExecutionGraph 是 Flink 调度最核心的数据结构,JobManager 根据 ExecutionGraph 对 Job 进行调度。
  3. 时间机制Spark Streaming 支持的时间机制有限,只支持处理时间。 Flink 支持了流处理程序在时间上的三个定义:处理时间、事件时间、注入时间。同时也支持 watermark 机制来处理滞后数据。
  4. 容错机制对于 Spark Streaming 任务,我们可以设置 checkpoint,然后假如发生故障并重启,我们可以从上次 checkpoint 之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义。Flink 则使用两阶段提交协议来解决这个问题。

三 Flink集群有哪些角色?各自有什么作用?

  Flink程序在运行时主要有TaskManager,JobManager,Client三种角色。

  JobManager扮演着集群中的管理者Master的角色,它是整个集群的协调者,负责接收Flink Job,协调检查点,Failover 故障恢复等,同时管理Flink集群中从节点TaskManager。

  TaskManager是实际负责执行计算的Worker,在其上执行Flink Job的一组Task,每个TaskManager负责管理其所在节点上的资源信息,如内存、磁盘、网络,在启动的时候将资源的状态向JobManager汇报。

  Client是Flink程序提交的客户端,当用户提交一个Flink程序时,会首先创建一个Client,该Client首先会对用户提交的Flink程序进行预处理,并提交到Flink集群中处理,所以Client需要从用户提交的Flink程序配置中获取JobManager的地址,并建立到JobManager的连接,将Flink Job提交给JobManager。

本文转载自: 掘金

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

盘点 Seata Server 端事务的 Session

发表于 2021-06-29

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

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

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

Github : 👉 github.com/black-ant

一 .前言

前面说了 Seata Client 的请求流程 , 这一篇从 Session 的处理来看一下 Seata Server 端的处理 .

每一次 Seata 的全局操作都会创建一个 Session , 并且往表中插入事务数据.

二 . global_table 表

先来看一下 global_table 的表结构

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复制代码CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime(6) DEFAULT NULL,
`gmt_modified` datetime(6) DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

三 . Server Session 处理一览

我们通过启动参数 -m 对请求 STORE_MODE 进行配置 : seata-server.bat -m db

整个 Session 的处理会分别对2个操作进行处理 , 一个为 global_table , 一个为 branch_table , 依次来说 :

Pro 1 : global_table 的作用

global_table 用于持久化全局事务 , 可以通过 store.db.global.table 进行配置

Pro 2 : branch_table 的作用

branch_table 用于标识分支事务 , 可以通过 store.db.branch.table 进行配置

数据结构

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
sql复制代码# C- LogStoreDataBaseDAO # insertGlobalTransactionDO : 插入 global_table   
INSERT INTO `seata`.`global_table`
( `xid`,
`transaction_id`,
`status`,
`application_id`,
`transaction_service_group`,
`transaction_name`,
`timeout`,
`begin_time`,
`application_data`,
`gmt_create`,
`gmt_modified` )
VALUES
( '192.168.181.2:8091:8466916507467911205',
8466916507467911205,
1,
'business-seata-example',
'business-service-seata-service-group',
'dubbo-gts-seata-example',
300000,
1624863673423,
NULL,
'2021-06-28 15:01:28',
'2021-06-28 15:01:28' );



# C- LogStoreDataBaseDAO # insertBranchTransactionDO
INSERT INTO `seata`.`branch_table`
(`branch_id`,
`xid`,
`transaction_id`,
`resource_group_id`,
`resource_id`,
`branch_type`,
`status`,
`client_id`,
`application_data`,
`gmt_create`,
`gmt_modified`)
VALUES
(8466916507467911829,
'192.168.181.2:8091:8466916507467911205',
8466916507467911205,
NULL,
'jdbc:mysql://127.0.0.1:3306/seata',
'AT',
0,
'storage-seata-example:192.168.181.2:51964',
NULL,
'2021-06-28 15:35:18.534107',
'2021-06-28 15:35:18.534107');

3.1 global_table 的处理流程

配置 STORE_MODE 为 db 后 , 会使用 DataBaseSessionManager 和 DataBaseTransactionStoreManager 进行业务的处理

1
2
3
4
java复制代码// 创建的调用入口 (此处忽略前置逻辑 , 但从 Session 的创建开始)
C- AbstractSessionManager # onBegin
C- DataBaseSessionManager # addGlobalSession
C- DataBaseTransactionStoreManager # writeSession (此处类型为 GLOBAL_ADD((byte)1))

从 Step 1 中可以看到 , 添加时会调用 writeSession , 这是个很重要的方法 , 基本上所有的编辑session 操作都会经历该类 , 可以通过 Debug 该部分

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
java复制代码/**
* DataBaseTransactionStoreManager 该方法中进行了全局事务和分支事务的管理
**/
public boolean writeSession(LogOperation logOperation, SessionStorable session) {
if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
// 插入全局事务
return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
} else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
// 更新全局事务
return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
} else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
// 删除全局事务
return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
} else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
// 插入分支事务
return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
} else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
// 更新分支事务
return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
} else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
// 删除分支事务
return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
} else {
throw new StoreException("Unknown LogOperation:" + logOperation.name());
}
}

[Pro31001] : logOperation 的作用和来源

LogOperation 作用 :

LogOperation 是一个枚举类 , 用于表示操作的类型

1
2
3
4
5
6
7
8
9
10
11
java复制代码enum LogOperation {
GLOBAL_ADD((byte)1),
GLOBAL_UPDATE((byte)2),
GLOBAL_REMOVE((byte)3),

BRANCH_ADD((byte)4),
BRANCH_UPDATE((byte)5),
BRANCH_REMOVE((byte)6);

private byte code;
}

LogOperation 的来源:

在调用该流程的时候 , 会传入对应的 LogOperation.code . 例如 DataBaseSessionManager 操作中

1
2
3
4
5
6
7
8
java复制代码
C- DataBaseSessionManager
M- addGlobalSession
- transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
M- updateGlobalSessionStatus
- transactionStoreManager.writeSession(LogOperation.GLOBAL_UPDATE, session);
M- removeGlobalSession
- transactionStoreManager.writeSession(LogOperation.GLOBAL_REMOVE, session)

3.2 branch_table 的处理逻辑

1
2
3
4
5
6
7
8
java复制代码//======== 以下是 Beanch 逻辑    
C- DataBaseTransactionStoreManager # writeSession (此处类型为 BRANCH_ADD((byte)4))

// 最终调用有以下方法
C- LogStoreDataBaseDAO
M- insertBranchTransactionDO
M- updateBranchTransactionDO
M- deleteBranchTransactionDO

四 . Session 的初始化流程

4.1 Session 的初始化

Step 1 : 启动入口 Server # main , 其中会开启 Session

1
2
js复制代码// 在 server # main 启动方法中 , 会调用以下语句
SessionHolder.init(parameterParser.getStoreMode());

Step 2 : SessionHolder init 流程

C- SessionHolder # init 中进行了如下操作 :

  • 获得配置的 store.mode , 从下面得代码可以看到支持 DB , FILE , REDIS
  • 通过 EnhancedServiceLoader#load 加载 SessionManager
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
js复制代码public static void init(String mode) {

// 获取配置文件中的 store.mode 属性
if (StringUtils.isBlank(mode)) {
mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
}

// 构建 StoreMode
StoreMode storeMode = StoreMode.get(mode);
if (StoreMode.DB.equals(storeMode)) {
// 基础会话管理器
ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
// 异步会话管理器
ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
// 重试提交会话管理器
RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
// 重试回退会话管理器
RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
} else if (StoreMode.FILE.equals(storeMode)) {
//..... 省略
} else if (StoreMode.REDIS.equals(storeMode)) {
//..... 省略
} else {
// unknown store
throw new IllegalArgumentException("unknown store mode:" + mode);
}
// 详见最后 , 刷新操作
reload(storeMode);
}

Step 3 : InnerEnhancedServiceLoader 加载的方式

1
2
3
4
5
6
7
8
9
10
11
12
js复制代码public static <S> S load(Class<S> service, String activateName) throws EnhancedServiceNotFoundException {
// SPI 核心 : 这里就是一个简单的下层调用 , 这里简单说一下 InnerEnhancedServiceLoader
return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, findClassLoader());
}


// getServiceLoader 获取 ServiceLoader
private static <S> InnerEnhancedServiceLoader<S> getServiceLoader(Class<S> type) {
// 主要就是通过 SERVICE_LOADERS 获取整个集合 , 另外可以看到 , 这里是每次调用时为空就会先创建 , 再缓存一遍
return (InnerEnhancedServiceLoader<S>)CollectionUtils.computeIfAbsent(SERVICE_LOADERS, type,
key -> new InnerEnhancedServiceLoader<>(type));
}

[PRO:] InnerEnhancedServiceLoader 的作用 ?

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复制代码InnerEnhancedServiceLoader 是 EnhancedServiceLoader 的内部类 :

// Pro : EnhancedServiceLoader 作用
EnhancedServiceLoader 是 Seata SPI 实现核心类 , Seata 通过 SPI 机制来实现 seata 的扩展 , 使其可以兼容多种注册中心 :

EnhancedServiceLoader 中 load 有如下的方式加载一个服务 :
M- load(Class<S> service, ClassLoader loader) : 通过服务类型和加载器加载
M- load(Class<S> service) : 通过服务类型加载
M- load(Class<S> service, String activateName) : 通过服务类型和激活名 (加载 ExtensionDefinition 会使用该名称)
M- load(Class<S> service, String activateName, ClassLoader loader)
M- load(Class<S> service, String activateName, Object[] args) : 带属性参数 (Instance 创建实例时会使用该参数)
// load 同时提供载入一组服务
M- loadAll(Class<S> service)
M- loadAll(Class<S> service, Class[] argsType, Object[] args)

// Pro : SPI Server 存放的位置
Seata 的 Service 类和 Spring factories 基本上一直 , 也是放在 META-INF.service 中 , 其中提供了如下配置 -> PIC30001


// Pro : EnhancedServiceLoader 的 子类
EnhancedServiceLoader 中有一个内部类 : C- InnerEnhancedServiceLoader , 主要作用为避免多次载入时出现不必要的载入

InnerEnhancedServiceLoader 中提供了如下的参数 :
// class 对应的 InnerEnhancedServiceLoader 集合
ConcurrentMap<Class<?>, InnerEnhancedServiceLoader<?>> SERVICE_LOADERS = new ConcurrentHashMap<>();
// Holder 内部有一个 volatile 参数用于保存对象, 保证多线程可见
Holder<List<ExtensionDefinition>> definitionsHolder = new Holder<>();
// ExtensionDefinition 集合
ConcurrentMap<ExtensionDefinition, Holder<Object>> definitionToInstanceMap = new ConcurrentHashMap<>();
// name 对应的 ExtensionDefinition 集合
ConcurrentMap<String, List<ExtensionDefinition>> nameToDefinitionsMap = new ConcurrentHashMap<>();
// ExtensionDefinition class类型 对应的 ExtensionDefinition
ConcurrentMap<Class<?>, ExtensionDefinition> classToDefinitionMap = new ConcurrentHashMap<>();

PIC30001 : META-INF.service 数据

image.png

Step 4 : 指定classLoader来加载 server provider

该单元是主要的处理流程 , 用于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码C- EnhancedServiceLoader
private S loadExtension(String activateName, ClassLoader loader, Class[] argTypes,
Object[] args) {

// activateName 判空操作 , 为空抛出异常 IllegalArgumentException
try {
// 1 . 从配置文件 (META-INF 中加载所有的 Extension 对象)
loadAllExtensionClass(loader);
// 2 . 通过激活名获得 ExtensionDefinition 类数据
ExtensionDefinition cachedExtensionDefinition = getCachedExtensionDefinition(activateName);
// 3 . 获得实例
return getExtensionInstance(cachedExtensionDefinition, loader, argTypes, args);
} catch (Throwable e) {
// .... 异常处理省略
}
}

Step 5 : loadAllExtensionClass 从配置文件中获取所有的 Extension

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
java复制代码C- EnhancedServiceLoader
// 1. 判断和发起加载
private List<Class> loadAllExtensionClass(ClassLoader loader) {
List<ExtensionDefinition> definitions = definitionsHolder.get();
if (definitions == null) {
synchronized (definitionsHolder) {
definitions = definitionsHolder.get();
if (definitions == null) {
// 加锁后查询所有的 ExtensionDefinition , 避免线程冲突
definitions = findAllExtensionDefinition(loader);
definitionsHolder.set(definitions);
}
}
}
return definitions.stream().map(def -> def.getServiceClass()).collect(Collectors.toList());
}

// 2. 加载流程
private List<ExtensionDefinition> findAllExtensionDefinition(ClassLoader loader) {

// 从 META-INF.service 和 META-INF.seata 中获取配置
List<ExtensionDefinition> extensionDefinitions = new ArrayList<>();
try {
loadFile(SERVICES_DIRECTORY, loader, extensionDefinitions);
loadFile(SEATA_DIRECTORY, loader, extensionDefinitions);
} catch (IOException e) {
throw new EnhancedServiceNotFoundException(e);
}

// 加载所有扩展后,按顺序对缓存进行排序 -> nameToDefinitionsMap
if (!nameToDefinitionsMap.isEmpty()) {
for (List<ExtensionDefinition> definitions : nameToDefinitionsMap.values()) {
Collections.sort(definitions, (def1, def2) -> {
int o1 = def1.getOrder();
int o2 = def2.getOrder();
return Integer.compare(o1, o2);
});
}
}

// 对加载的 extensionDefinitions 进行排序
if (!extensionDefinitions.isEmpty()) {
Collections.sort(extensionDefinitions, (definition1, definition2) -> {
int o1 = definition1.getOrder();
int o2 = definition2.getOrder();
return Integer.compare(o1, o2);
});
}

return extensionDefinitions;
}

Step 6 : getCachedExtensionDefinition 获取 BeanDefinition 基础信息

1
2
3
4
5
java复制代码// 比较简单 , 就是获取 ConcurrentMap<String, List<ExtensionDefinition>> nameToDefinitionsMap 
private ExtensionDefinition getCachedExtensionDefinition(String activateName) {
List<ExtensionDefinition> definitions = nameToDefinitionsMap.get(activateName);
return CollectionUtils.getLast(definitions);
}

Step 7 : 反射进行初始化

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
js复制代码// 发起流程 :
loadExtension -> getExtensionInstance -> createNewExtension

// getExtensionInstance 逻辑比较简单 , 就是判断是否为单例从而进行了一个单例模式的创建

// createNewExtension 创建实例
private S createNewExtension(ExtensionDefinition definition, ClassLoader loader, Class[] argTypes, Object[] args) {
Class<?> clazz = definition.getServiceClass();
try {
S newInstance = initInstance(clazz, argTypes, args);
return newInstance;
} catch (Throwable t) {
throw new IllegalStateException(....);
}
}

// initInstance 初始化实例
private S initInstance(Class implClazz, Class[] argTypes, Object[] args)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
S s = null;
if (argTypes != null && args != null) {
// 获取 构造函数的参数
Constructor<S> constructor = implClazz.getDeclaredConstructor(argTypes);
// 如果有参数 ,通过参数创建实例
s = type.cast(constructor.newInstance(args));
} else {
// 使用默认构造器创建 (无参数的情况)
s = type.cast(implClazz.newInstance());
}
if (s instanceof Initialize) {
// 核心 7-1 实例init初始化
((Initialize)s).init();
}
return s;
}

Step 7-1 : DataBaseSessionManager 中 init 操作

其中关于 DataBase 会使用 DataBaseSessionManager 操作 , 这一块看一下整体的体系 :

seata-Initialize-system.png

1
2
3
4
5
6
7
8
9
java复制代码public void init() {
// 初始化 DataBaseTransactionStoreManager
transactionStoreManager = DataBaseTransactionStoreManager.getInstance();
}

// PS : Initialize 的实现类都需要实现 init 方法
public interface Initialize {
void init();
}

Step 7-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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
java复制代码

C- DataBaseTransactionStoreManager
P- int DEFAULT_LOG_QUERY_LIMIT = 100;

private DataBaseTransactionStoreManager() {
logQueryLimit = CONFIG.getInt(ConfigurationKeys.STORE_DB_LOG_QUERY_LIMIT, DEFAULT_LOG_QUERY_LIMIT);
// 获取 Datasource 类型
String datasourceType = CONFIG.getConfig(ConfigurationKeys.STORE_DB_DATASOURCE_TYPE);
// 初始化 dataSource
DataSource logStoreDataSource = EnhancedServiceLoader.load(DataSourceProvider.class, datasourceType).provide();
// 构建 LogStoreDataBaseDAO
logStore = new LogStoreDataBaseDAO(logStoreDataSource);
}


// [Pro] : ConfigurationKeys 的参数
String STORE_DB_LOG_QUERY_LIMIT = STORE_DB_PREFIX + "queryLimit";

// [Pro] : DataSourceProvider 的实现类
io.seata.server.store.DbcpDataSourceProvider
io.seata.server.store.DruidDataSourceProvider
io.seata.server.store.HikariDataSourceProvider


// 此处构建了一个 DataSource
@LoadLevel(name = "hikari")
public class HikariDataSourceProvider extends AbstractDataSourceProvider {

@Override
public DataSource generate() {
Properties properties = new Properties();
properties.setProperty("dataSource.cachePrepStmts", "true");
properties.setProperty("dataSource.prepStmtCacheSize", "250");
properties.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
properties.setProperty("dataSource.useServerPrepStmts", "true");
properties.setProperty("dataSource.useLocalSessionState", "true");
properties.setProperty("dataSource.rewriteBatchedStatements", "true");
properties.setProperty("dataSource.cacheResultSetMetadata", "true");
properties.setProperty("dataSource.cacheServerConfiguration", "true");
properties.setProperty("dataSource.elideSetAutoCommits", "true");
properties.setProperty("dataSource.maintainTimeStats", "false");

HikariConfig config = new HikariConfig(properties);
config.setDriverClassName(getDriverClassName());
config.setJdbcUrl(getUrl());
config.setUsername(getUser());
config.setPassword(getPassword());
config.setMaximumPoolSize(getMaxConn());
config.setMinimumIdle(getMinConn());
config.setAutoCommit(true);
config.setConnectionTimeout(getMaxWait());
config.setInitializationFailTimeout(-1);
return new HikariDataSource(config);
}
}

Step 8 : reload 刷新 SessionManager

在执行完上述逻辑后还没完全 , 注意 Step 2 中最后还有个 Reload 操作 ,该操作会继续处理 DataBaseSessionManager

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
java复制代码c- SessionHolder 

// 这里会议一下之前的属性
private static SessionManager ROOT_SESSION_MANAGER;
private static SessionManager ASYNC_COMMITTING_SESSION_MANAGER;
private static SessionManager RETRY_COMMITTING_SESSION_MANAGER;
private static SessionManager RETRY_ROLLBACKING_SESSION_MANAGER;

protected static void reload(StoreMode storeMode) {
if (ROOT_SESSION_MANAGER instanceof Reloadable) {
((Reloadable) ROOT_SESSION_MANAGER).reload();
}

//
Collection<GlobalSession> allSessions = ROOT_SESSION_MANAGER.allSessions();
if (CollectionUtils.isNotEmpty(allSessions)) {
List<GlobalSession> removeGlobalSessions = new ArrayList<>();
Iterator<GlobalSession> iterator = allSessions.iterator();
while (iterator.hasNext()) {
GlobalSession globalSession = iterator.next();
GlobalStatus globalStatus = globalSession.getStatus();
// 通过属性来判断处理的方式
switch (globalStatus) {
case UnKnown:
case Committed:
case CommitFailed:
case Rollbacked:
case RollbackFailed:
case TimeoutRollbacked:
case TimeoutRollbackFailed:
case Finished:
removeGlobalSessions.add(globalSession);
break;
case AsyncCommitting:
if (storeMode == StoreMode.FILE) {
queueToAsyncCommitting(globalSession);
}
break;
default: {
// TODO : 此处的原理在后面说 Lock 逻辑的时候统一说
if (storeMode == StoreMode.FILE) {
lockBranchSessions(globalSession.getSortedBranches());
// 如果上述都没有 , 需要先处理分支事务
switch (globalStatus) {
case Committing:
case CommitRetrying:
queueToRetryCommit(globalSession);
break;
case Rollbacking:
case RollbackRetrying:
case TimeoutRollbacking:
case TimeoutRollbackRetrying:
queueToRetryRollback(globalSession);
break;
case Begin:
globalSession.setActive(true);
break;
default:
throw new ShouldNeverHappenException("NOT properly handled " + globalStatus);
}
}
break;
}
}
}
for (GlobalSession globalSession : removeGlobalSessions) {
removeInErrorState(globalSession);
}
}
}

C- 以 Database 为例 ,allSessions 又如下操作
String ROOT_SESSION_MANAGER_NAME = "root.data";
String ASYNC_COMMITTING_SESSION_MANAGER_NAME = "async.commit.data";
String RETRY_COMMITTING_SESSION_MANAGER_NAME = "retry.commit.data";
String RETRY_ROLLBACKING_SESSION_MANAGER_NAME = "retry.rollback.data";

public Collection<GlobalSession> allSessions() {
// get by taskName
if (SessionHolder.ASYNC_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
return findGlobalSessions(new SessionCondition(GlobalStatus.AsyncCommitting));
} else if (SessionHolder.RETRY_COMMITTING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.CommitRetrying, GlobalStatus.Committing}));
} else if (SessionHolder.RETRY_ROLLBACKING_SESSION_MANAGER_NAME.equalsIgnoreCase(taskName)) {
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {GlobalStatus.RollbackRetrying,
GlobalStatus.Rollbacking, GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying}));
} else {
// all data
return findGlobalSessions(new SessionCondition(new GlobalStatus[] {
GlobalStatus.UnKnown, GlobalStatus.Begin,
GlobalStatus.Committing, GlobalStatus.CommitRetrying, GlobalStatus.Rollbacking,
GlobalStatus.RollbackRetrying,
GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbackRetrying, GlobalStatus.AsyncCommitting}));
}
}

[Pro] : Reloadable 对象体系

1
2
3
4
5
java复制代码public interface Reloadable {
void reload();
}

// 这里的实现类主要为 FileSessionManager

五 . 扩展知识点

5.1 LoadLevel 的作用

@LoadLevel(name = "file", scope = Scope.PROTOTYPE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码
LoadLevel 注解提供了三个参数 :

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface LoadLevel {

String name();

// 在类似链式调用的过程中 , 可以对 Provider 进行排序
int order() default 0;

Scope scope() default Scope.SINGLETON;
}

// 作用域范围
public enum Scope {

SINGLETON,
PROTOTYPE

}

总结

LoadLevel 和 MATA-INF 真正的作用是用于扩展不同的数据库 , 后续等 seata 梳理完成后 , 再来看一下如何进行定制.

自此 Session 的处理类初始化完成 , 后面来看一下 Session 在调用过程中的处理和数据库处理

本文转载自: 掘金

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

推荐 7 个牛哄哄 Spring Cloud 实战项目

发表于 2021-06-29

把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,这就是微服务架构的架构概念,通过将功能分解到各个离散的服务中以实现对解决方案的解耦。

关于微服务相关的学习资料不多,而 GitHub 上的开源项目可以作为你微服务之旅的第一站。本文推荐 7 个非常火的微服务项目,从入门到实战,这篇文章值得收藏。


01. 不二之选

第一个推荐的项目是高赞教程:PiggyMetrics ,这个开源项目是你开启微服务之旅的不二之选。

PiggyMetrics是一个很全面的微服务实践入门的实例集,它可以指导开发者使用 Spring Boot、Spring Cloud 和 Docker 搭建微服务架构。

该开源项目有一个典型的微服务实现案例 - 个人理财微服务系统。采用Spring Boot/Spring Cloud等技术栈,来实现微服务的开发、构建和治理,麻雀虽小五脏俱全。

在这个案例中,你可以全面地了解到微服务的注册发现、配置中心、熔断、路由、负载均衡、注解式Http客户端、认证鉴权和全链追踪等技术,同时还有日志、监控、度量指标等运维指标统计分析。

PiggyMetrics 被分解为三个核心微服务,它们都是可独立部署的应用程序。如果你具备了微服务的基础知识,没有实战经验,从这个项目开始吧。

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


02. 分布式电商项目

基于 Spring Cloud 的分布式电商项目,该项目使用分库设计方案,不同的模块依赖不同的数据库实例。后台登陆采用 Oauth 2.0 授权,支持密码登陆、授权码登陆、短信验证码登陆、注册中心与配置中心已使用 alibaba naco。

目标打造顶级多模块,高可用,高扩展电商项目。

技术栈基于 Spring Boot、Spring Cloud、Spring Oauth2 和 Spring Cloud Netflix 等框架,可以借助该项目学习Spring Cloud 技术栈,作为练手项目。

1
bash复制代码地址:https://github.com/SiGuiyang/spring-cloud-shop
1
复制代码


03. 轻松阅读微服务项目

轻松阅读是一款图书阅读类 APP,基于 Spring Cloud 开发的微服务实战项目,涉及 SpringCloud-Gateway、Nacos、Hystrix、OpenFeign、Jwt、ElasticSearch 等技术栈的应用。

1
bash复制代码客户端:https://github.com/Zealon159/light-reading-cloud-clientapi:https://github.com/Zealon159/light-reading-cloud

核心架构图如下:


04. SpringBlade 微服务开发平台

SpringBlade 采用前后端分离的模式,前端基于 React、Ant Design、Vue、Element-UI。后端采用 Spring Cloud 全家桶,注册中心、配置中心选型 Nacos,简封装了多租户底层,用更少的代码换来拓展性更强的 SaaS 多租户系统。

1
arduino复制代码地址:https://gitee.com/smallc/SpringBlade


05. Cloud-Platform

Cloud-Platform是国内首个基于Spring Cloud微服务化开发平台,具有统一授权、认证后台管理系统,其中包含具备用户管理、资源权限管理、网关API 管理等多个模块,支持多业务系统并行开发,可以作为后端服务的开发脚手架。代码简洁,架构清晰,适合学习和直接项目中使用。

核心技术采用Spring Boot 2.4.1、Spring Cloud (2020.0.0)以及Spring Cloud Alibaba 2.2.4 相关核心组件,采用Nacos注册和配置中心,集成流量卫兵Sentinel,前端采用vue-element-admin组件,Elastic Search自行集成。

1
arduino复制代码地址:https://gitee.com/geek_qi/cloud-platform


06. 网约车项目

看图吧。

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


07. 互联网云快速开发框架

一款免费开源的 Java 互联网云快速开发平台,微服务分布式代码生成的敏捷开发系统架构。项目代码简洁,注释丰富,上手容易,还同时集中分布式、分布式事务、微服务,同时包含许多基础模块和监控、服务模块。

被评为 2018 年度最受欢迎中国开源软件项目。

1
bash复制代码地址:https://gitee.com/JeeHuangBingGui/jeeSpringCloud

本文转载自: 掘金

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

美丽代码的秘密-《重构》如何让你的代码和你一样赏心悦目

发表于 2021-06-29

在掘金,同学们往往喜欢分享技术原理性或教程类的文章,这是程序员的天性使然。然而,在我们科学性的技术范畴之外,软件工程则是另外一个重要的话题。通俗地讲,计算机科学是让我们如何把事情做对,即不能出错;而软件工程则教会我们如何做对的事情,也就是做得更好。这篇文章所要介绍的 《重构》 正是属于软件工程的话题,没有它不会耽误你编程,但拥有之后你可能会写出更好的代码。

《重构》由ThoughtWorks的首席科学家、软件架构领域的前辈Martin Fowler编写,著作经典且口碑极好。有人说,重构是程序员的洗髓经,可见这本书的价值。有兴趣的同学,可以浏览他的个人博客,相信你会很有收获,也可以去豆瓣了解本书,从书评中了解他人的观点。

本文原是我此前的Keynote文稿,今天稍微整理后分享给大家,希望对你有一点点的帮助。另外,我注意到前端的同学较多,前两年基于JavaScript版本的《重构》也已经上市,值得大家阅读。

由于图片较多,可能引起部分同学不适,你可以直接划拉到本文底部,有PDF版本的获取提示,可以直接获取PDF版本阅读、收藏。

重构-改善既有代码的设计20210601.001.jpeg

重构-改善既有代码的设计20210601.002.jpeg

重构-改善既有代码的设计20210601.003.jpeg

重构-改善既有代码的设计20210601.004.jpeg

重构-改善既有代码的设计20210601.005.jpeg

重构-改善既有代码的设计20210601.006.jpeg

重构-改善既有代码的设计20210601.007.jpeg

重构-改善既有代码的设计20210601.008.jpeg

重构-改善既有代码的设计20210601.009.jpeg

重构-改善既有代码的设计20210601.010.jpeg

重构-改善既有代码的设计20210601.011.jpeg

重构-改善既有代码的设计20210601.012.jpeg

重构-改善既有代码的设计20210601.013.jpeg

重构-改善既有代码的设计20210601.014.jpeg

重构-改善既有代码的设计20210601.015.jpeg

重构-改善既有代码的设计20210601.016.jpeg

重构-改善既有代码的设计20210601.017.jpeg

重构-改善既有代码的设计20210601.018.jpeg

重构-改善既有代码的设计20210601.019.jpeg

重构-改善既有代码的设计20210601.020.jpeg

重构-改善既有代码的设计20210601.021.jpeg

重构-改善既有代码的设计20210601.022.jpeg

重构-改善既有代码的设计20210601.023.jpeg

重构-改善既有代码的设计20210601.024.jpeg

重构-改善既有代码的设计20210601.025.jpeg

重构-改善既有代码的设计20210601.026.jpeg

重构-改善既有代码的设计20210601.027.jpeg

重构-改善既有代码的设计20210601.028.jpeg

重构-改善既有代码的设计20210601.029.jpeg

重构-改善既有代码的设计20210601.030.jpeg

重构-改善既有代码的设计20210601.031.jpeg

重构-改善既有代码的设计20210601.032.jpeg

重构-改善既有代码的设计20210601.033.jpeg

重构-改善既有代码的设计20210601.034.jpeg

重构-改善既有代码的设计20210601.035.jpeg

重构-改善既有代码的设计20210601.036.jpeg

重构-改善既有代码的设计20210601.037.jpeg

重构-改善既有代码的设计20210601.038.jpeg

重构-改善既有代码的设计20210601.039.jpeg

重构-改善既有代码的设计20210601.040.jpeg

重构-改善既有代码的设计20210601.041.jpeg

重构-改善既有代码的设计20210601.042.jpeg

重构-改善既有代码的设计20210601.043.jpeg

重构-改善既有代码的设计20210601.044.jpeg

重构-改善既有代码的设计20210601.045.jpeg

重构-改善既有代码的设计20210601.046.jpeg

重构-改善既有代码的设计20210601.047.jpeg

重构-改善既有代码的设计20210601.048.jpeg

重构-改善既有代码的设计20210601.049.jpeg

重构-改善既有代码的设计20210601.050.jpeg

重构-改善既有代码的设计20210601.051.jpeg

重构-改善既有代码的设计20210601.052.jpeg

重构-改善既有代码的设计20210601.053.jpeg

重构-改善既有代码的设计20210601.054.jpeg

重构-改善既有代码的设计20210601.055.jpeg

重构-改善既有代码的设计20210601.056.jpeg

重构-改善既有代码的设计20210601.057.jpeg

重构-改善既有代码的设计20210601.058.jpeg

PDF版本获取方式

  • 关注公众号【MetaThoughts】,回复关键字“01”即可。

其他品质干货推荐:

  • 《起底JVM内存管理及性能调优【80+页Keynote私享】》
  • 《写给JAVA开发者的自动化测试不完全指南【90+页Keynote纯享干货】》

本文转载自: 掘金

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

测试平台系列(11) 封装Request类

发表于 2021-06-29

封装Request类

回顾

上次我们完善了登录注册相关页面,还有后端接口,算是从0到1完成了一个功能的编码工作。可能前端部分会讲的比较粗糙,因为第一可能是笔者造诣不够,第二也跟我们直接从现有的框架进行改造有关,很多东西不是从0写到1,而是从1到1.1,但是后面不一样:

后面的页面都是咱们自己写,自己用,从0写到1

安装requests库

终端输入pip3 install requests并执行。

构思后端Request相关接口

要知道,咱们这是一个接口测试平台(后续可能会集成UI自动化,但是这个笔者也还没有想好)。一个接口自动化平台,最核心的当然是对api的请求操作,所以咱们刻不容缓,加快进度,趁热打铁,来点干货吧。用requests来协助我们完成接口自动化请求。

我们新建一个文件: middleware/HttpClient.py

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
Python复制代码import datetime

import requests


class Request(object):

def __init__(self, url, session=False, **kwargs):
self.url = url
self.session = session
self.kwargs = kwargs
if self.session:
self.client = requests.session()
return
self.client = requests

def get(self):
return self.request("GET")

@staticmethod
def get_elapsed(timer: datetime.timedelta):
if timer.seconds > 0:
return f"{timer.seconds}.{timer.microseconds // 1000}s"
return f"{timer.microseconds // 100}ms"

def request(self, method: str):
status_code = 0
elapsed = "-1ms"
try:
if method.upper() == "GET":
response = self.client.get(self.url, **self.kwargs)
elif method.upper() == 'POST':
response = self.client.post(self.url, **self.kwargs)
else:
response = self.client.request(method, self.url, **self.kwargs)
status_code = response.status_code
if status_code != 200:
return Request.response(False, status_code)
elapsed = Request.get_elapsed(response.elapsed)
data = response.json()
return Request.response(True, 200, data, response.headers, response.request.headers, elapsed=elapsed)
except Exception as e:
return Request.response(False, status_code, msg=str(e), elapsed=elapsed)

def post(self):
return self.request("POST")

@staticmethod
def response(status, status_code=200, response=None, response_header=None,
request_header=None, elapsed=None, msg="success"):
request_header = {k: v for k, v in request_header.items()}
response_header = {k: v for k, v in response_header.items()}
return {
"status": status, "response": response, "status_code": status_code,
"response_header": response_header, "request_header": request_header,
"msg": msg, "elapsed": elapsed,
}

如果我是产品经理的话,那么postman就是我的原型图:

讲解一下各个方法,首先这是一个Request请求类,他拥有核心方法: request,目前咱们暂时先只做到支持json类的请求,后续补全form, file等类型的请求。

其实这个类做的事情很简单,就是把requests相关的方法剥离了出来,封装了一层。

其中构造函数提供了一些选项,包括请求的信息,url,是否以session的方式请求等等,kwargs涵盖了requests原生的参数,只要你想传,你都可以传进来。

get_elapsed是根据postman为参照,对请求时间做的一个处理,如果大于1s的请求响应时间,那我们以秒为单位显示,否则以毫秒为单位显示。

response是构造返回结构对象,对本次请求的数据进行整理。

request就封装了requests库的核心操作,基本上属于原生处理,并且判断了http状态码。

编写request接口

新建controllers/request/http.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PYTHON复制代码from flask import Blueprint
from flask import jsonify
from flask import request

from app.middleware.HttpClient import Request

req = Blueprint("request", __name__, url_prefix="/request")


@req.route("/http", methods=['POST'])
def http_request():
data = request.get_json()
method = data.get("method")
if not method:
return jsonify(dict(code=101, msg="请求方式不能为空"))
url = data.get("url")
if not url:
return jsonify(dict(code=101, msg="请求地址不能为空"))
body = data.get("body")
headers = data.get("headers")
r = Request(url, data=body, headers=headers)
response = r.request(method)
return jsonify(dict(code=0, data=response, msg="操作成功"))

其实和登录/注册接口都很相似,基本上就是创立了一个blueprint,前缀是/request,后续就是引入刚才的request类,进行http请求,最后返回response。

调整run.py

我们新建了一个蓝图,需要去run.py进行注册:

2行代码搞定

1
2
Python复制代码from app.controllers.request.http import req
pity.register_blueprint(req)

最终的效果如图所示

本节内容就到这里了,下一节咱们编写属于自己的第一个组件: postman。又是前端内容了,咱们做一个接近postman的页面即可,不需要多高大上,做东西要抓住用户的使用习惯。毕竟咱们不是🍎,它的理念是让用户去适应它。

后端代码地址: github.com/wuranxu/pit…

前端代码地址: github.com/wuranxu/pit…

本文转载自: 掘金

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

Java下变量大小写驼峰、大小写下划线、大小写连线转换

发表于 2021-06-29

写在前面
想很简单,做很难,坚持更难,克服惰性。每天学一点,不会的就少一点。
养成习惯很重要,先从点赞开始吧!关注[程序员之道],前行道路不再迷茫!

有时候需要处理对象属性的getter、setter方法,或者将属性与数据表字段进行相互转换,这时候就需要用到将小写驼峰转换为小写下划线方式,当然我们可以自己手撸一段代码来实现,但Google的大神们,已经给我们提供了一个现成的开发包,也就是Google guava包。直接拿来主义吧!

引入guava依赖包

这个非常简单,只需要在工程的pom.xml中引入依赖的坐标即可。

1
2
3
4
5
java复制代码        <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>

可以做什么

变量的一些转换处理,只需要用到博大精深的guava包中的一个枚举类CaseFormat.class。可以看到该枚举类有5个枚举变量。

在这里插入图片描述

枚举就是一个单例,我们可以直接使用枚举变量的方法,因为已经是一个单例对象了嘛!如果你还不了解单例,那这里简单解释一下,单例就是在一个java进程(也就是当前工程的JVM中)只存在唯一一个对象,枚举据说是最简单的单例实现方式了。

那这几个枚举常量分别代表什么意思呢?

其实代码的注释里已经解释的很清楚了,不愧为大神之作啊!

以CaseFormat.LOWER_HYPHEN为例,注释如下:

1
arduino复制代码/** Hyphenated variable naming convention, e.g., "lower-hyphen". */

代表连字符的变量命名规范,例如user-name,user-age等。

为了减少大家的阅读源码的工作量,这里把5个枚举及意义都拿出来说一下。

枚举变量 说明
CaseFormat.LOWER_HYPHEN 连字符的变量命名规范,形式lower-hyphen
CaseFormat.LOWER_UNDERSCORE C++变量命名规范,形式lower_underscore
CaseFormat.LOWER_CAMEL Java变量命名规范,形式lowerCamel
CaseFormat.UPPER_CAMEL Java和C++类名命名规范,形式UpperCamel
CaseFormat.UPPER_UNDERSCORE Java和C++常量命名规范,形式UPPER_UNDERSCORE

一共有5个枚举变量,通过排列组合知识,可以知道,我们可以进行变量转换的形式总共有5*4=20种。

怎么做变量转换

下面通过几个典型的例子,演示怎么将一种变量命名规范转换为另一种。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码package com.chan.test;

import com.google.common.base.CaseFormat;

public class GuavaTest {

public static void main(String[] args) {
// 变量小写连接线转小写驼峰
System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, "user-name"));//userName
// 变量小写连接线转小写下划线
System.out.println(CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_UNDERSCORE, "user-name"));//user_name
// 变量小写下划线转小写驼峰
System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "user_name"));//userName
// 变量下划线转大写驼峰
System.out.println(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "user_name"));//UserName
// 变量小写驼峰转大写驼峰
System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, "userName"));//UserName
// 变量小写驼峰转小写下划线
System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "userName"));//user_name
// 变量小写驼峰转小写下划线
System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "UserName"));//user_name
// 变量小写驼峰转小写连接线
System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "userName"));//user-name
}
}

能够读到最后,说明你很棒哦,给道哥来个双击三连吧,奥利给!

本文转载自: 掘金

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

1…626627628…956

开发者博客

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