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

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


  • 首页

  • 归档

  • 搜索

Autowired和Resource的区别 共同点 不同

发表于 2021-10-24

最近开发过程中经常看到项目中有的地方用@Autowired有的地方用@Resource。所以这篇文章主要谈谈这两个注解有什么区别。

共同点

@Resource和@Autowired都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用。

不同点

1、@Resource是JDK原生的注解,@Autowired是Spring2.5 引入的注解

2、@Resource有两个属性name和type。Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。

@Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。

实践说明

定义接口做饭Cook.java,抽象方法open()、cooking()、close()

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

/**
* 开火
*/
String open();
/**
* 炒菜
*/
String cooking();
/**
* 关火
*/
String close();
}

定义实现类炒西红柿CookTomato.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* service接口 实现类
* 炒西红柿
*/
@Service
public class CookTomato implements Cook {
@override
public String open() {
return "炒西红柿前打开油烟机并开火";
}
@override
public String cooking() {
return "炒西红柿中~";
}
@override
public String close() {
return "炒西红柿后关闭油烟机并关火";
}
}

定义Controller类CookController.java,注入Cook接口

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复制代码/**
* controller层
*/
@RestController
@RequestMapping("/cook")
public class CookController {

@Resource
private Cook cook;

@RequestMapping("/open")
public String open() {
return cook.open();
}

@RequestMapping("/cooking")
public String cooking() {
return cook.cooking();
}

@RequestMapping("/close")
public String close() {
return cook.close();
}
}

启动Spring Boot运行起来后请求三个接口都是正常的返回结果。

但是如果我们增加Cook接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* service接口 实现类
* 炒土豆
*/
@Service
public class CookPatato implements Cook {
@override
public String open() {
return "炒土豆丝前打开油烟机并开火";
}
@override
public String cooking() {
return "炒土豆丝中~";
}
@override
public String close() {
return "炒土豆丝后关闭油烟机并关火";
}
}

这个时候启动Spring Boot,控制台会报错

1
bash复制代码2021-10-24 10:24:10.662  WARN 5592 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'CookController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.janeroad.annotation.service.Cook' available: expected single matching bean but found 2: CookTomato,CookPatato

大致意思是我们引入Cook但是Spring框架发现了有两个实现,无法匹配到bean

我们将代码改成这样

1
2
java复制代码@Resource(name="cookTomato")
private Cook cook;

或者

1
2
3
java复制代码@Resource
@Qualifier("cookTomato")
private Cook cook;

上述代码都在做一件事,把Cook实现类指定为炒西红柿实现类,启动Spring Boot后请求Controller接口就会发现一切正常!

如果我们不用@Resource注解改用@Autowire呢?

在上述改动基础上改成@Autowire会报以下错

1
2
3
4
5
6
7
8
9
10
bash复制代码Description:

Field cook in com.janeroad.annotation.controller.CookController required a single bean, but 2 were found:
- cookTomato: defined in file [此处省略路径名]
- cookPatato: defined in file [此处省略路径名]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

报错意思就是CookController需要一个bean但是找到两种实现

所以我们就应该按照报错提示使用@Primary,在有多个实现bean时告诉Spring优先@Primary修饰的那个;或者使用@Qualifier来标注需要注入的类。

@Qualifier修改方式与@Resource的相同,一样是修改Controller代码中注入的Cook上面,这里不再复述

@Primary是修饰实现类的,告诉Spring,如果有多个实现类时,优先注入被@Primary注解修饰的那个。

那么修改CookTomato.java为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码/**
* service接口 实现类
* 炒西红柿
*/
@Service
@Primary
public class CookTomato implements Cook {
@override
public String open() {
return "炒西红柿前打开油烟机并开火";
}
@override
public String cooking() {
return "炒西红柿中~";
}
@override
public String close() {
return "炒西红柿后关闭油烟机并关火";
}
}

启动Spring Boot后会发现,调用接口一切正常。

总结

@Autowired功能虽说非常强大,但是也有些不足之处。比如它跟Spring强耦合了,如果换成了其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。

除此之外,有些场景使用@Autowired无法满足的要求,改成@Resource却能解决问题。

1、@Autowired默认按byType自动装配,而@Resource默认byName自动装配。

2、@Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。

3、@Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。

4、@Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。

5、@Autowired是Spring定义的注解,而@Resource是JSR-250定义的注解。

6、二者装配顺序不同

@Autowired

@Resource

本文转载自: 掘金

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

关于app测试小白也会模拟器①🍗💯💦 1、关于app测试的模

发表于 2021-10-24

1、关于app测试的模拟器

  • 关于模拟器有很多种,有:
    夜神模拟器、mumu模拟器、逍遥模拟器、雷电模拟器、篮叠模拟器等、、、
  • 最常用的模拟器夜神模拟器

1.2 关于模拟器下载与环境的搭建

1.2.1 关于夜神模拟器下载

下载网址:
www.yeshen.com/
注意点:
尽量不要下在含有中文的路径里<不是不能下,就是后期有问题会很麻烦>

1.3模拟内网环境搭建

image-20211018173456901.png

image-20211018173536341.png

image-20211018173826983.png

2、配置adb命令

1、找到adb命令所在的文件路径

1
makefile复制代码D:\Program Files\Nox\bin<每个人下载的地方是不一样的,这个是我的>

2、打开系统高级设置、点击环境变量
image-20211018142431774.png
3、选择系统变量中设置的Path进行添加abd的路径

image-20211018142611290.png
4、点击新建,复制到就好
image-20211018142648083.png
5、成功界面,打开一个cmd终端,直接输入abd是成功的

image.png
6、配置完成

3.0 adb命令相命令

3.1 devices

devices:查找当前电脑中所有的链接模拟器设备
命令语法:
adb devices
返回的是电脑所有模拟器设备的地址和端口

1
2
3
makefile复制代码C:\Users\我是王修呀>adb devices
List of devices attached
127.0.0.1:62001 device

3.2 connect

connect:连接模拟器
命令语法:
adb connect ip:端口
返回的是已连接127.0.0.1:62001<夜神端口>

1
2
arduino复制代码C:\Users\我是王修呀>adb connect 127.0.0.1:62001
already connected to 127.0.0.1:62001

3.2.1 常用的ip和端口

这是举例的端口:

其他的端口可以遍历百度

1
2
3
复制代码夜神 127.0.0.1:62001

Mumu 127.0.0.1:7555

3.3 模拟器app安装

安装 install

方法1:

事先下载好的文件,文件一般是以apk结尾的文件
直接拖进夜神模拟器,会自动安装

方法2:

使用adb命令安装

adb install apk文件所在路径/及安装包包的包名

1
2
3
makefile复制代码C:\Users\我是王修呀>adb install C:\Users\zuoyebang.apk

Success

方法3 :<在有多台模拟器的情况下>

使用adb命令安装

adb -s id:端口 install apk文件所在路径/及安装包包的包名
参数:
-s:序列号

1
2
makefile复制代码C:\Users\我是王修呀>adb -s 127.0.0.1:62001 install  C:\Users\zuoyebang.apk
Success

3.3 卸载 uninstall

  • 方法1 :

手动卸载直接拖动

  • 方法2:

1、查找安装包完成之后的包名

首先使用 :adb shell <链接模拟器,进入模拟器的安装系统>

进入之后,找到软件安装的文件名,一般是data/data 是软件安装包放的文件夹

其次进入:cd/data/data <这是安卓系统软件安装包的文件>

进入之后查看安装包的包名

com.insthub.ecmobile

退出模拟器的安卓系统

exit 退出安卓模拟系统

1
2
3
makefile复制代码R11 Plus:/ # exit

C:\Users\我是王修呀>

执行 adb uninstall 包名

1
2
makefile复制代码C:\Users\我是王修呀>adb uninstall com.baidu.homework
Success
aapt命令

使用aapt命令找到安装包

1、利用aapt 找到包名你需要卸载的安装包名
aapt d badging apk所在路径/和安装包的文件名

会出现很多数据,第一排的是安装包在安卓系统的安装包的包名
image-20211024124220364.png

注意点:需要安装的时候apk文件所在路径保持一致

1
2
makefile复制代码C:\Users\我是王修呀>adb uninstall com.baidu.homework
Success

为了方便查看不出错 我们可以使用管道 “|”

aapt d badging apk所在路径/和安装包的文件名 |find “package”

image.png

1
2
makefile复制代码C:\Users\我是王修呀>adb uninstall com.baidu.homework
Success

常见的安卓安卓包

data 包

image-20211019101025352.png

image-20211019101206497.png
/data/data 里面的com.开头的文件夹都对应的了一个app

ecmobile 文夹夹

ecmobile 在未使用的时候,有几个空包
image-20211019101953119.png
ecmobile
image-20211019102305567.png

从远程的安卓模拟器蒋文杰拉取

1
2
3
4
5
6
kotlin复制代码
adb pull /data/data/com.insthub.ecmobile/shared_prefs/userInfo.xml E:\remotefiles
adb pull 远程地址 本地地址 从模拟器获取数据

adb push 本地地址 远程地址 推送数据
adb push E:\remotefiles /data/data/com.insthub.ecmobile/shared_prefs/userInfo.xml

image-20211019111556345.png

本文转载自: 掘金

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

什么是1024?为什么是1024? 1024 程序员节 节日

发表于 2021-10-24
  • 本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

非常感谢你阅读本文~
欢迎【👍点赞】【⭐收藏】【📝评论】~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子 https://juejin.cn/user/2771185768884824/posts 博客原创~


二当家的祝所有的 程序猿/媛 节日快乐,一日为 程序猿/媛 便要终生学习,希望我们大家都能每天进步一点点~

在这里插入图片描述


1024 程序员节

1024程序员节是广大程序员的共同节日。1024是2的十次方,二进制计数的基本计量单位之一。针对程序员经常周末加班与工作日熬夜的情况,部分互联网机构倡议每年的10月24日为1024程序员节,在这一天建议程序员拒绝加班。
程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。1G=1024M,而1G与1级谐音,也有一级棒的意思。


节日背景

程序员(英文Programmer)是从事前端、后端程序开发、系统运维、测试等的专业人员。一般将程序员分为程序设计人员和程序编码人员,但两者的界限并不非常清楚,特别是在中国。软件从业人员分为初级程序员、中级程序员、高级程序员、系统架构师、运维测试工程师等。
在1834年,人称“数字女王”的阿达·洛芙莱斯(Ada Lovelace)的朋友英国数学家、发明家兼机械工程师查尔斯·巴贝其(Charles Babbage)发明了一台分析机;阿达则致力于为该分析机编写算法,并于1842年,编写了历史上首款电脑程序。1843 年公布了世界上第一套算法。巴贝其分析机后来被认为是最早期的计算机雏形,而阿达的算法则被认为是最早的计算机程序和软件。运行程序的硬件进制是以1024为基础的。例:1G=1024M ;1M=1024KB。

1
2
3
4
5
6
7
8
9
10
java复制代码import java.text.MessageFormat;

public class Test {

public static void main(String[] args) {
// 我只是想要¥,所以这里必须这么写
Integer $1024 = 1024;
System.out.println(MessageFormat.format("{0}的2进制表示为:{1}", $1024.toString(), Integer.toBinaryString($1024)));
}
}

在这里插入图片描述


节日由来

2002年,俄罗斯程序员Valentin Balt收集签名,向俄罗斯联邦政府请愿将9月13日设定为程序员节。
2009年9月11日,俄罗斯总统梅德韦杰夫在节日安排方案上签了名,“程序员节”从此成为了俄罗斯的一个正式节日。 除了俄罗斯之外,其他国家的一些程序员社区也会庆祝这个节日。为什么会选9月13日呢?因为它是每年的第256天(闰年就是9月12日)。256这个数字对程序员来说有着特别的意义,1个字节(等于8位元)最多能表示256个数值,而且在整年中,256是2的最大幂中小于365的值。
在中国,有人提议把10月24日定为中国的程序员节,因为1024不仅同样是程序员的一个常用数字,而且10月24日这个日期非常直观,也不会在平年和闰年有所变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码import java.text.MessageFormat;

public class Test {

public static void main(String[] args) {
// 我只是想要¥,所以这里必须这么写
Integer $powerOf2 = 2;
final int maxDaysPerYear = 366;
while ($powerOf2 * 2 < maxDaysPerYear) {
$powerOf2 *= 2;
}
System.out.println(MessageFormat.format("2的最大幂中小于365的值是:{0}", $powerOf2.toString()));
}
}

在这里插入图片描述


为什么是 1024

程序员对于1024的敏感度极高,超过1000这个整数。要是偶然看到或者听到1024,都会那么一激动,之所以1024特殊,是因为计算机普遍使用2进制(也有三进制计算机哦1),1024恰好是2的十次方,所以计算机相关普遍使用1024作为一个基数。

中文单位 中文简称 英文单位 英文简称 进率(Byte=1)
位 比特 bit b 0.125
字节 字节 Byte B 1
千字节 千字节 KiloByte KB 2^10
兆字节 兆 MegaByte MB 2^20
吉字节 吉 GigaByte GB 2^30
太字节 太 TeraByte TB 2^40
拍字节 拍 PetaByte PB 2^50
艾字节 艾 ExaByte EB 2^60
泽字节 泽 ZettaByte ZB 2^70
尧字节 尧 YottaByte YB 2^80
千亿亿亿字节 千亿亿亿字节 BrontoByte BB 2^90

它们之间的换算关系是:
1B=8bit
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
1PB=1024TB
1EB=1024PB
1ZB=1024EB
1YB=1024ZB
1BB=1024YB
1NB=1024BB
1DB=1024NB
1CB=1024DB
1XB=1024CB

虽然我们平时都讨论计算机底层某个数据的二进制表示形式是怎样的1,0…组合,但其实你打开硬盘,内存,cpu寄存器这些硬件,里面当然不会写有一串1,0…。它可能是有磁性和没有磁性,高电压与地电压。

在这里插入图片描述

十进制每一位数字范围:0 - 9
二进制每一位数字范围:0 - 1

计算机采用二进制的主要原因:1、计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开(高电压与低电压,有磁性与没有磁性),这两种状态正好可以用“1”和“0”表示;2、二进制中只使用0和1两个数字,传输和处理时不易出错,因而可以保障计算机具有很高的可靠性。

二生四,四生八,八生十六,十六生万物。


吐槽一下,把我的容量还给我

虽然计算机行业标准TB,GB,MB,KB之间是1024关系。但是硬件厂商可不这么玩,比如二当家的配置的是1TB的硬盘,然而…

在这里插入图片描述

在这里插入图片描述

一般硬盘厂商,基础是1000,即 1TB=1000GB,1GB=1000MB,1MB=1000KB,这是硬盘厂家的标准,这在存储市场已经是公开的秘密了,几乎可以说是“行业标准”了。

是的,买的时候是1TB,你用的时候就只有931GB了,把我的容量还给我。


1024 程序员节快乐

最后二当家的再次祝各位1024程序员节快乐。愿天底下再没有难做的需求和难修的BUG。

好了,二当家的要去看大会了。

在这里插入图片描述

欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章

Footnotes

  1. 三进制计算机,是以三进法数字系统为基础而发展的计算机。曾经被莫斯科大学科研人员用于计算机,在光子计算机研究领域也有涉及。对称三进制能比二进制更方便的表示所有整数。三进制是“逢三进一,退一还三”的进制。 ↩

本文转载自: 掘金

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

Python 垃圾回收总结 前言 衡量 GC 性能的四要素

发表于 2021-10-24

前言

最近在阅读《垃圾回收的算法与实现》,里面讲到了一些常用的垃圾回收(Garbage Collect)算法,如:标记-清除、引用计数、分代回收等等。
后面讲到了 Python 的垃圾回收策略,在此记录一下。

衡量 GC 性能的四要素

  1. 吞吐量
    吞吐量为单位时间内的GC出来能力。计算公式为:GC处理的堆容量 / GC花费的时间。
    通常,人们都喜欢吞吐量高的GC算法。
  2. 最大暂停时间
    GC执行过程中令用户线程暂停的最长时间。如果GC时间过长,会影响程序的正常执行。
    较大的吞吐量和较短的最大暂停时间往往不可兼得。
  3. 堆使用效率
    GC是自动内存管理功能,所以理想情况是在GC时不要占用过量的堆空间。
    影响堆使用效率的两个因素是:头的大小和堆的用法。
    可用的堆越大,GC运行越快;相反,越想有效地利用有限的堆,GC花费的时间就越长。
  4. 访问的局部性
    具有引用关系的对象之间通常很可能存在连续访问的情况。这在多数程序中都很常见,称为“访问的局部性”。
    考虑到访问局部性,把具有引用关系的对象安排在堆中较近的位置,能够提高数据的利用率。

Python 使用引用计数的 GC 的算法,引用计数算法的优势是可以减短最大暂停时间,缺陷是在维护计数的增量和减量上面临很大的挑战(如果忘记执行减量操作就会造成对象没有释放)。

引用计数存在哪里

对于 Python 的数据,像 List、Set、Tuple、Dict、Str、Int,在其底层的数据结构中,都会有一个PyObject类型的成员,用来维护对象的引用计数

1
2
3
4
5
c复制代码typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeoject *ob_type;
} PyObject;

其中的ob_refcnt成员负责维持引用计数
如此,所有的内置型结构在都在开头保留了PyObject结构体来维护引用计数。

内存分配器

Python 在进行内存分配时并不是简单的调用malloc/free函数来向操作系统请求内存的。而是出于效率考虑尽可能减少系统调用,将内存分配器分成了3层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
css复制代码    Object-specific allocators
_____ ______ ______ ________
[ int ] [ dict ] [ list ] ... [ string ] Python core |
+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
_______________________________ | |
[ Python's object allocator ] | |
+2 | ####### Object memory ####### | <------ Internal buffers ------> |
______________________________________________________________ |
[ Python's raw memory allocator (PyMem_ API) ] |
+1 | <----- Python memory (under PyMem manager's control) ------> | |
__________________________________________________________________
[ Underlying general-purpose allocator (ex: C library malloc) ]
0 | <------ Virtual memory allocated for the python process -------> |
=========================================================================
_______________________________________________________________________
[ OS-specific Virtual Memory Manager (VMM) ]
-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |
__________________________________ __________________________________
[ ] [ ]
-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |

第0层到第3层是 Python 实现管理的分布器层级,如果以字典对象为例,

1
2
3
4
5
c复制代码PyDict_New()             ----3层
PyObject_GC_New() ----2层
PyObject_Malloc() ----2层
new_arena() ----1层
malloc() ----0层

第0层就是直接调用操作系统提供的分配函数,如malloc。Python 并不是所有的对象生成都调用第0层,而是根据要分配内存的大小来选择不同的分配方案:

  • 申请的内存大于256字节,使用第0层
  • 申请的内存小于256字节,使用第一层和第二层

基于经验,我们是编码过程中使用的大量对象都是小于256字节的,并且生命周期很短,例如:

1
2
Python复制代码for x in range(100):
print(x)

这个过程中如果频繁调用malloc和free,效率就会很低。Python 通过增加第1层和第2层,并在第一层中会事先从第0层申请一些空间保留管理起来,当分配对象内存小于256时,使用这部分空间。

Python 管理的内存结构有3个部分:block、pool、arena,它们之间的关系如下:

  • arena 用来管理存储 pool
  • pool 用来管理存储 block
  • block 内存申请者可用的最小单位

为了避免频繁调用malloc()和free(),第1层的分配器会以最大的单位 arena 来保留内存。pool 是用于有效管理空的block的单位。

第2层的分配器负责管理 pool 内的 block。将 block 的开头地址返回给申请者,并释放 block 等。
分配器会将 pool 会按照8字节的倍数的大小来分割后产出若干个 block,如:8字节的 block、16字节的 block、24字节的 block、… 256字节的 block。申请内存时,会返回适合大小的 block。

第3层是对象特有的分配器,Python 中各种内置类型:如:list、dict 等,又会有各自特有的分配器,比如 dict 的分配器长这样:

1
2
3
4
5
c复制代码#ifndef PyDict_MAXFREELIST
#define PyDict_MAXFREELIST 80
#endif
static PyDictObject *free_list[PyDict_MAXFREELIST];
static int numfree = 0;

在分配字典对象时,会先检查空闲列表free_list是否有可用对象,如果已被用尽,再去通过第2层PyObject_GC_New去申请对象。

整体下来 Python 生成字典对象时的交互如下:

引用计数法

Python 内实现引用计数法,其实就是对各对象的引用数量变更做相应的操作,如果对象的引用数量增加,就在计数器上加1,反过来如果引用数量减少,就在计数器上减去1。

增量操作

实际的计数操作由宏Py_INCREF来实现

1
2
c复制代码#define Py_INCREF(op) (                  \
((PyObject*)(op))->ob_refcnt++)

ob_refcnt的类型在32位环境下是 int 型,在64位环境下是 long 型。因为有符号位,所以只有一半数值能用非负整数表示。但是因为指针基本上都是按4字节对齐的,所以即使引用计数器被所有指针引用,也不会溢出。
设计成允许存在负数是为了方便调试,当引用计数器存在负数数,就有减量操作过度或者增量操作遗留的可能。

减量操作

实际的计数操作由宏Py_DECREF来实现

1
2
3
4
5
6
7
8
9
c复制代码#define Py_DECREF(op)                          \
if (--((PyObject*)(op))->ob_refcnt != 0) \
_Py_CHECK_REFCNT(op) \
else \
_Py_Dealloc((PyObject*)(op))


# define _Py_Dealloc(op) ( \
(*Py_TYPE(op)->tp_dealloc)((PyObject*)(op)))

先将计数器将量,如果不为0,调用宏_Py_CHECK_REFCNT检查引用计数器是否变为了负数。如果计数器为0,那么就调用宏_Py_Dealloc,通过Py_TYPE识别对象的类型并调用对应的负责释放各个对象的函数指针,比如:负责释放元组对象的函数指针是tupledealloc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c复制代码static void
tupledealloc(register PyTupleObject *op)
{
register Py_ssize_t i;
register Py_ssize_t len = Py_Size(op);

if (len > 0) {
i = len;
/* 将元组内的元素进行减量 */
while(--i >= 0)
Py_XDECREF(op->ob_item[i]);
}
/* 释放元组对象 */
Py_TYPE(op)->tp_free((PyObject *)op);

Py_TRASHCAN_SAFE_END(OP);
}

成员tp_free存着对各个对象的释放处理函数指针,大部分在其内部都是调用PyObect_GC_Del函数

1
2
3
4
5
6
7
c复制代码void
PyObject_GC_Del(void *op)
{
PyGC_Head *g = AS_GC(op);
/* ... */
Pyobject_FREE(g)
}

元组的减量操作调用图如下:

1
2
3
4
5
6
c复制代码Py_DECREF
_Py_Dealloc ———— 减量操作
tupledealloc
PyObject_GC_Del ———— 元组释放处理
PyObject_FREE
PyObject_Free ———— 释放内存

引用的所有权

上面已经阐明了引用计数的核心操作就是计数+1和计数-1。需要明确的是,谁来去负责进行操作,比如:A 对象通过调用函数func1获得了 B 对象,那么 B 对象的引用计数+1应该由谁去负责呢?
这里就涉及到“引用的所有权”, 即谁持有引用的所有权,谁就得承担在不需要此引用时将对象的引用计数器减量的责任。

  1. 传递引用所有权(返回值)
    即函数方把引用的所有权和返回值一起交给调用方。由函数的调用方在引用结束时负责执行减量操作。对象生成时,会把引用所有权交给调用方,比如:字典对象的生成
1
2
3
4
c复制代码PyObject *dict = PyDict_New();
/* 进行一些操作, 结束后*/
Py_DECREF(dict);
dict = NULL;
  1. 出借引用的所有权(返回值)
    即函数方只把返回值交给调用方,至于引用的所有权则只是出借而已,调用方不能对引用计数进行减量操作。负责从“集合”中取出元素的函数,都是出借所有权,比如:元组获取指定索引的函数
1
2
3
4
5
6
c复制代码PyObject *
PyTuple_GetItem(register PyObject *op, register Py_ssize_t i)
{
/*检查操作*/
return ((PyTupleObject *)op) -> ob_item[i]
}
  1. 占据引用的所有权(参数)
    即调用方把参数传递给函数时,函数方有时会占据这个参数的引用所有权,此时由函数方负责该参数的引用管理事宜。往链表中追加元素的函数都会占据参数的引用所有权,比如:向元组指定位置增加元素的函数
1
2
3
4
5
6
c复制代码PyObject *tuple, *dict;

tuple = PyTuple_New(3);
dict = PyDict_New(); /*用所有权返回给了dict*/
PyTuple_SetItem(tuple, 0, dict); /*引用所有权被tuple占据了*/
dict = NULL;
  1. 出借引用所有权(参数)
    即调用方把参数的引用所有权借给函数方。当函数的调用方要出借引用的所有权时,从把对象交给函数之后直到函数执行结束为止,这段时间调用方都必须保留指向对象的引用的所有权。

循环引用垃圾回收

引用计数法有一个缺陷,无法解决循环引用问题,当两个对象之间相互引用,引用计数无法清零,即无法进行 GC。因此,对于循环引用,Python 通过部分标记-清除算法来解决。

算法原理:部分标记-清除算法

比如下图,左侧的三个对象之前存在循环引用,导致正常的引用计数无法将他们回收

我们先将他们当前的引用计数复制到另一块区用于后面操作

有一个前提:Python 对象在生成时会将其自身连接到一个对象链表中(通过双向指向相互连接),图中用双向箭头表示

基于此,我们遍历对象链表中的所有对象,对他们所有引用的对象进行拷贝计数减一

现在进行标记操作了,我们将所有经过上步处理后拷贝计数仍然大于0的对象标记为“可达对象”,即有其他活动对象引用他们,并将他们所引用的对象也标记为可达对象,连接到可达对象链表中;然后将拷贝计数为0的对象记为“不可达对象”,连接到不可达对象链表。

可以看到,不可达对象中即为循环引用的对象,接下来进行清除操作,我们将不可达对象链表中的对象释放内存,将可达对象链表中的对象重新连接会对象链表中

到此,我们完成了对循环引用对象的垃圾回收。

并不是所有对象都会发生循环引用

引起循环引用的根因是有些对象可能保留的指向其他对象的引用,对于这类对象,我们称之为“容器对象”。
像元组、列表和字典等,都属于容器对象,只需要对这些容器对象解决循环引用的问题。容器对象中都被分配了用于循环引用垃圾回收的头结构体。

1
2
3
4
5
6
7
8
c复制代码tyepdef union _gc_head {
struct {
union _ge_head *gc_next; /*用于双向链表*/
union _gc_head *gc_prev; /*用于双向链表*/
Py_ssize_t gc_refs; /*用于复制计数*/
} gc;
long double dummy;
} PyGC_Head;

正如前面所说,容器对象生成时,会被连接到对象链表,以字典对象为例,看一下他生成时代码

1
2
3
4
5
6
7
8
9
c复制代码PyObject *
PyDict_New(void)
{
regiseter PyDictObject *mp;
/*生成对象的操作*/
_PyObject_GC_TRACK(mp);
return (PyObject *)mp;

}

_PyObject_GC_TRACK负责连接到对象链表中的操作

1
2
3
4
5
6
7
8
c复制代码#define _PyObject_GC_TRACK(o) do { \
PyGC_Head *g = _Py_AS_GC(o); \
g->gc.gc_refs = _PyGC_REFS_REACHABLE; \
g->gc.gc_next = _PyGC_generation0; \
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
g->gc.gc_prev ->gc.gc_next = g; \
_PyGC_generation0->gc.gc_prev = g; \
} while (0);

容器对象分代管理

Python 将上面提到的容器对象链表划分为“0代”、“1代”、“2代”,通过下面的结构体管理

1
2
3
4
5
6
7
8
9
c复制代码struct gc_generation {
PyGC_Head head; /* 头指正 */
int threshold; /* 开始GC的阈值 */
int count; /* 改代的对象数 */
}

# define GEN_HEAD(n) (&generations[n].head)

PyGC_Head *_PyGC_generation0 = GEN_HEAD(0); /* 0代的容器对象 */

一开始所有的容器对象都连接着0代的对象。当0代容器对象经过1次循环引用垃圾回收,仍然存活下来的对象晋升为1代;再次从循环引用垃圾回收过程中存活下来的对象晋升为2代。

移除特例

在循环引用垃圾回收过程中,我们会将有终结器的对象从不可达链表中移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c复制代码static void
move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
{
PyGC_Head *gc;
PyGC_Head *next;

for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) {
PyObject *op = FROM_GC(gc);
next = gc->gc.gc_next;
if (has_finalizer(op)) {
gc_list_move(gc, finalizers);
gc->gc.gc_refs = GC_REACHABLE;
}
}
}

之所以如此,是因为想要释放循环引用的有终结器的对象是非常麻烦的。我们无法确定先释放那个对象时合理的,如果先将第1个对象释放,再释放第二个对象时执行的终结器引用了第一个对象怎么办?

对于这些含有终结器的循环引用垃圾对象, Python 提供了全局变量garbage,让开发者内从应用程序的角度来去移除对象的循环引用。

1
2
Python复制代码import gc
gc.grabage

总结

Python 的 GC 分为两部分:

  • 通过引用计数算法来管理常规对象的垃圾回收,并通过优化的内存结构来尽可能减少 GC
  • 通过分代+清除-标记来管理循环引用对象的垃圾回收

本文转载自: 掘金

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

如何正确使用RestTemplate【四】

发表于 2021-10-24

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

复习一下下

前几篇文章,我们将RestTemplate所提供的各个请求类型的方法都看了个遍,相信现在已经有了基础的认识了,但是我们没有说到的一项,那就是‘参数多个可选’,到底是哪些参数呢?要如何选择呢?

从这篇文章开始,我们就要开始学习具体的方法使用方式了,准备好了,每天进步一点点。

请求方法参数分析

Get请求

共有参数介绍:

url:访问链接Url,没什么可说的。

responseType:返回响应的参数类型,比如返回的参数是个List,那么这个参数就应该传List.class。

getForObject

1.public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)

此方法我们来主要介绍一下Object… uriVariables,从名称来看,其实就是访问请求的url参数,至于Object…,那自然就是可以传输多个,使用起来如下代码即可:

1
java复制代码List<Map<String,String>> list = restTemplate.getForObject(url, List.class,"first param","two param");

2.public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

此方法我们来主要介绍一下Map<String, ?> uriVariables,上面说过了,请求URL的参数,这里不同的是Map类型,使用起来如下代码即可:

1
2
3
4
java复制代码Map<String,String> map = new HashMap<>;
map.put("Frist","first param");
map.put("Two","two param");
List<Map<String,String>> list = restTemplate.getForObject(url, List.class, map);

3.public <T> T getForObject(URI url, Class<T> responseType)

这个方法就不用多说了,只传输url和对应的返回参数类型即可。

getForEntity

getForEntiy,除了返回的参数的不同之外,没有什么的区别,不做过多的解释,给个代码示例自己学习吧。

1.public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)

1
2
java复制代码ResponseEntity<List> entity = restTemplate.getForEntity(url, List.class,"first param","two param");
System.out.println(entity.getBody());

2.public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)

1
2
3
4
5
java复制代码Map<String,String> map = new HashMap<>;
map.put("Frist","first param");
map.put("Two","two param");
ResponseEntity<List> entity = restTemplate.getForEntity(url, List.class, map);
System.out.println(entity.getBody());

3.public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)

小结

今天我们又学习了Get请求相关方法的使用方式,你是否有所收获呢?

本文转载自: 掘金

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

大名鼎鼎的 OceanBase 居然在买Star !?

发表于 2021-10-24

图片

OceanBase,相信大家都不陌生了吧?

OceanBase是由蚂蚁集团完全自主研发的企业级分布式关系数据库,始创于2010年。OceanBase具有数据强一致、高可用、高性能、在线扩展、高度兼容SQL标准和主流关系数据库、低成本等特点。

作为国内最知名的分布式关系数据库,是经常被蚂蚁集团拿出来炫耀的拳头科技产品了。

OceanBase在业内的认可度很广,不光互联网领域,在很多传统行业如:银行、证券、电信等业务上也都有成功应用案例,中国工商银行、招商证券等知名企业都有应用,完全可以说是国产数据库之光!

在2021年6月,OceanBase 3.0版本正式发布,该版本产品同时具备在事务处理和数据分析两类任务的高性能能力,升级为一款支持 HTAP 混合负载的企业级分布式数据库。 同时,OceanBase宣布正式开源,并成立OceanBase开源社区,社区官网同步上线,300万行核心代码向社区开放。截止到2021年10月23日,该项目斩获了3.5k的Star。

图片

按照几年前的标准,其实这个Star数据已经非常不错了。但是放在现在,尤其是与其他阿里系的开源项目相比,似乎确实不那么理想(好多都是过万的)。于是,就在昨天,这款备受瞩目的数据库产品,被传有买Star的行为出现。

为什么说他们在买Star呢?看看下面这张网上流传的照片:

图片

小编扫描上面的加群二维码,加入了这个群,看到了这样的群公告:

图片

给Star送礼物、邀请亲朋好友一起点,送的礼物更好!\

这不是赤裸裸的买Star行为吗?


再看看群里,点赞换东西的活动也是进行的如火如荼:****

图片

下午17点多,有网友在群里提出了质疑,表示这样的做法是违法GitHub用户协议的!\

图片

\

也有网友直接@了群里的P9大佬,质问:这样的行为运营同学不知道,难道技术P9也不知道吗?

图片

面对网友的质疑,P9大佬回复:2点多就停了\

图片

然而,在群里的小伙伴们反馈似乎并没有结束,从下面网友提供的信息:\

图片

16点17分,还有小伙伴在问:“活动只限今天吗?” ,运营回复:“不止!\

那么P9大佬真的不知道这个事情的严重性吗?只是单纯的运营行为?P9大佬完全不知情?

从群成员信息里看,相关P9大佬其实早早就在群里了,想必这个事情本身也是大佬认可的吧?

图片

关于国内开源项目的Star水份,其实一直以来都有被大家所诟病。\

原本作为衡量一个开源项目好坏的重要指标,如今却沦为各大厂商考核运营的指标,于是各种刷赞的灰产出现、运营买赞行为的出现,让开源项目的Star数据丧失了原来该有的意义。

那么你认为现在国内开源项目刷Star的现象普遍吗?

对于OceanBase这样的操作,你怎么看呢?

留言区说说你的看法吧!

本文转载自: 掘金

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

关于MongoDB的使用 1 MongoDB的概述 2 Mo

发表于 2021-10-24

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

记录MongoDB的使用相关流程,从数据库的简介到实际代码的应用.

参考资料:

www.runoob.com/mongodb/mon…

1 MongoDB的概述

1 MongoDB简介

MongoDB是基于C++语言编写的基于分布式文件存储的开源数据库系统,主要是为了给WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

image-20211012224812149

2 MongoDB体系

将MongoDB与关系型数据库MySQL结构对比:

MySQL MongoDB
database数据库 database数据库
table表 collection集合
row表的一行 document文档
column行的一列 field文档某个属性
index索引 index索引
表连接 不支持跨集合查询
primary key主键 primary key

3MongoDB数据类型

MongoDB的小存储单位就是文档(document)对象,数据在MongoDB中以BSON(Binary-JSON)文档的格式存储在磁盘上.

数据类型 说明
String 字符串
Integer 整型数值
Boolean 布尔值
Double 双精度浮点值
Min/Max keys 将一个值与 BSON元素的最低值和最高值相对比
Array 用于将数组或列表或多个值存储为一个键
Timestamp 时间戳
Object 用于内嵌文档
Null 用于创建空值
Symbol 符号
Date 日期时间
Object ID 对象 ID
Binary Data 二进制数据
Code 代码类型
Regular expression 正则表达式类型

2 MongoDB的配置说明

1 Windows安装步骤

以mongodb-win32-x86_64-2008plus-ssl-4.0.12.zip为例

1 下载MongoDB软件

官网下载:

www.mongodb.com/try/downloa…

2 解压文件包

解压mongodb-win32-x86_64-2008plus-ssl-4.0.12.zip得到mongodb-win32-x86_64-2008plus-ssl-4.0.12文件目录

3 创建文件存放日志和配置文件

创建log目录和config目录.在config目录中创建配置文件mongod.conf.内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
conf复制代码systemLog:
destination: file
# 下面地址都不能使用\反斜杠, 而要使用斜杠/
# 修改为日志log目录地址
path: "D:/mongodb-win32-x86_64-2008plus-ssl-4.0.12/log/mongod.log"
logAppend: true
net:
port: 27017
bindIp: "127.0.0.1"
storage:
# 修改成为自己数据库db地址
dbPath: "D:/mongodb-win32-x86_64-2008plus-ssl-4.0.12/data/db"
journal:
enabled: true

4 启动服务

服务端启动

进入文件bin目录,在地址栏使用cmd命令,进入黑窗口, 输入命令: mongod –config …/config/mongod.conf

客户端启动

进入文件bin目录,在地址栏使用cmd命令,进入黑窗口, 输入命令: mongo –host=127.0.0.1 –port=27017

2 MongoDB连接

代码中连接MongoDB服务的标准URI语法:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

  • mongodb:// 固定格式
  • username:password@ 可选,如果设置,在连接数据库服务器之后,驱动都会尝试登录这个数据库
  • host1 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址
  • portX 可选的指定端口,如果不填,默认为27017
  • database 如果指定username:password@,连接并验证登录指定数据库。若不指定,默认打开 test 数据库
  • ?options 连接选项。如果不使用/database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开
选项 描述
replicaSet=name 验证replica set的名称。 Impliesconnect=replicaSet.
slaveOk=true false
safe=true false
w=n 驱动添加 { w : n } 到getLastError命令. 应用于safe=true
wtimeoutMS=ms 驱动添加 { wtimeout : ms } 到 getlasterror 命令. 应用于 safe=true
fsync=true false
journal=true false
connectTimeoutMS=ms 可以打开连接的时间
socketTimeoutMS=ms 发送和接受sockets的时间

3 Java中MongoDB的使用

Java和MongoDB的基本操作

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
java复制代码package com.demo.spring.mongoDB;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.bson.Document;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import sun.net.www.content.audio.basic;

/**
* @Description:
* @Author: cf
* @Date: 2021/10/9
*/
public class TestDemo {

private MongoClientURI mongoClientURI;
private MongoClient mongoClient;

@Before
public void init() {
// 无密码模式
// mongoClientURI = new MongoClientURI("mongodb://localhost:27017/mycol");
// 有密码模式
mongoClientURI = new MongoClientURI("mongodb://cf:123456@localhost:27017/mycol");
mongoClient = new MongoClient(mongoClientURI);
System.out.println("mongoClient 对象生成");
// // 连接到 mongodb 服务
// mongoClient = new MongoClient("localhost", 27017);

}

@After
public void drop() {
mongoClient.close();
System.out.println("mongoClient 对象销毁");
}

// 正则查询
@Test
public void getByPattern() {
// 连接到 mongodb 服务
// MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

/**
* 左匹配:Pattern.compile("^王.*$", Pattern.CASE_INSENSITIVE);
* 右匹配:Pattern.compile("^.*王$", Pattern.CASE_INSENSITIVE);
* 完全匹配:Pattern.compile("^王$", Pattern.CASE_INSENSITIVE);
* 模糊匹配:Pattern.compile("^.*王.*$", Pattern.CASE_INSENSITIVE);
*/
// 正则查询:查询所有以王开头的用户姓名
Pattern pattern = Pattern.compile("^王.*$", Pattern.CASE_INSENSITIVE);
BasicDBObject searchCond = new BasicDBObject("description",
new BasicDBObject("$regex", pattern));
MongoCursor<Document> cursor = users.find(searchCond).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 包含查询
@Test
public void getByInOrNin() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 包含查询:查询likes = 2、3、10的信息
List<Integer> searchList = new ArrayList<Integer>();
searchList.add(2);
searchList.add(3);
searchList.add(10);
BasicDBObject searchCond = new BasicDBObject("likes",
new BasicDBObject("$in", searchList));
MongoCursor<Document> cursor = users.find(searchCond).iterator();

while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 统计查询
@Test
public void getByCount() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 统计查询:查看密码为likes的一共有多少人
BasicDBObject filter = new BasicDBObject("likes", 2);
long count = users.countDocuments(filter);
System.out.println(count);
}

// 排序查询
@Test
public void getByOrder() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 排序查询:排序规则:1(升序)、-1(降序)
MongoCursor<Document> cursor = users.find().sort(new BasicDBObject("likes", 1)).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 投影查询
@Test
public void getByProjection() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 投影查询:只显示_id和指定字段信息 value值无意义
MongoCursor<Document> cursor = users.find().projection(new BasicDBObject("likes", 1))
.iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 分页查询
@Test
public void getByPage() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 分页查询:跳过第一条,查询两条数据
MongoCursor<Document> cursor = users.find().skip(1).limit(2).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 类型查询
@Test
public void getByType() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

// 类型查询:查询所有用户名是字符串类型的用户信息
BasicDBObject searchCond = new BasicDBObject();
// searchCond.append("likes", new BasicDBObject("$type", "string"));
searchCond.append("likes", new BasicDBObject("$type", "int"));

MongoCursor<Document> cursor = users.find(searchCond).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 连接查询: OR查询
@Test
public void getByORCondition() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

BasicDBList condList = new BasicDBList();
condList.add(new BasicDBObject("likes", new BasicDBObject("$gt", 50)));
condList.add(new BasicDBObject("likes", new BasicDBObject("$lt", 40)));

// 条件查询:likes小于200大于50的用户信息
BasicDBObject searchCond = new BasicDBObject();
searchCond.put("$or", condList);
MongoCursor<Document> cursor = users.find(searchCond).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 连接查询: AND查询
@Test
public void getByANDCondition() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);
MongoDatabase articledb = mongoClient.getDatabase("mycol");
MongoCollection<Document> users = articledb.getCollection("test");

BasicDBList condList = new BasicDBList();
condList.add(new BasicDBObject("likes", new BasicDBObject("$gt", 50)));
condList.add(new BasicDBObject("likes", new BasicDBObject("$lt", 200)));

// 条件查询:likes小于200大于50的用户信息
BasicDBObject searchCond = new BasicDBObject();
searchCond.put("$and", condList);
MongoCursor<Document> cursor = users.find(searchCond).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 条件查询 常见操作符:等于、$ne、$gte、$lte、$gt、$lt
// TODO 等于怎么表示? 直接进行比较, 不用添加操作符(如下)
@Test
public void getByCondition() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 如果存在articledb,返回articledb,如果不存在articledb,创建articledb
MongoDatabase articledb = mongoClient.getDatabase("mycol");
// 如果存在users,返回users,如果不存在,创建users
MongoCollection<Document> users = articledb.getCollection("test");
// 条件查询:查询likes不等于200的用户信息
BasicDBObject searchCond = new BasicDBObject();

// 常见操作符:等于、$ne、$gte、$lte、$gt、$lt
searchCond.append("likes", new BasicDBObject("$ne", 200));

// 等于
// searchCond.append("likes", 100);
MongoCursor<Document> cursor = users.find(searchCond).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next());
}
}

// 删除文档, 符合条件的第一个/符合条件的所有文档
@Test
public void deleteDocument() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");

MongoCollection<Document> collection = mongoDatabase.getCollection("test");
System.out.println("集合 test 选择成功");

// deleteMany 删除多条
// deleteOne 删除一条
//删除符合条件的第一个文档
collection.deleteOne(Filters.eq("likes", 200));
//删除所有符合条件的文档
collection.deleteMany(Filters.eq("likes", 200));
//检索查看结果
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> mongoCursor = findIterable.iterator();
while (mongoCursor.hasNext()) {
System.out.println(mongoCursor.next());
}

} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 更新文档
@Test
public void updateDocument() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");

MongoCollection<Document> collection = mongoDatabase.getCollection("test");
System.out.println("集合 test 选择成功");

// 如果我们只想修改文档的某个字段请使用$set操作符修改,否则其它字段都会不见了
// updateMany 修改所有符合条件的
// updateOne 修改符合条件的一条
//更新文档 将文档中likes=100的文档修改为likes=200
collection.updateMany(
Filters.eq("likes", 100), new Document("$set", new Document("likes", 200)));
//检索查看结果
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> mongoCursor = findIterable.iterator();
while (mongoCursor.hasNext()) {
System.out.println(mongoCursor.next());
}

} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 查询文档
@Test
public void getDocument() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");

MongoCollection<Document> collection = mongoDatabase.getCollection("test");
System.out.println("集合 test 选择成功");

//检索所有文档
/**
* 1. 获取迭代器FindIterable<Document>
* 2. 获取游标MongoCursor<Document>
* 3. 通过游标遍历检索出的文档集合
* */
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> mongoCursor = findIterable.iterator();
while (mongoCursor.hasNext()) {
System.out.println(mongoCursor.next());
}

} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 插入文档$
@Test
public void insertDocument() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");

MongoCollection<Document> collection = mongoDatabase.getCollection("test");
System.out.println("集合 test 选择成功");
//插入文档
/**
* 1. 创建文档 org.bson.Document 参数为key-value的格式
* 2. 创建文档集合List<Document>
* 3. 将文档集合插入数据库集合中 mongoCollection.insertMany(List<Document>) 插入单个文档可以用 mongoCollection.insertOne(Document)
* */
Document document = new Document("title", "MongoDB").
append("description", "database").
append("likes", 100).
append("by", "Fly");
List<Document> documents = new ArrayList<Document>();
documents.add(document);
collection.insertMany(documents);
System.out.println("文档插入成功");
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 获取集合
@Test
public void getCollection() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");

MongoCollection<Document> collection = mongoDatabase.getCollection("test");
System.out.println("集合 test 选择成功");
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 创建集合
@Test
public void createCollection() {
try {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");
mongoDatabase.createCollection("test");
System.out.println("集合创建成功");

} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 连接服务2
@Test
public void test2() {
try {
//连接到MongoDB服务 如果是远程连接可以替换“localhost”为服务器所在IP地址
//ServerAddress()两个参数分别为 服务器地址 和 端口
ServerAddress serverAddress = new ServerAddress("localhost", 27017);
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);

//MongoCredential.createScramSha1Credential()三个参数分别为 用户名 数据库名称 密码
MongoCredential credential = MongoCredential
.createScramSha1Credential("username", "databaseName",
"password".toCharArray());
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);

//通过连接认证获取MongoDB连接
MongoClient mongoClient = new MongoClient(addrs, credentials);

//连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("databaseName");
System.out.println("Connect to database successfully");
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}

// 连接服务1
@Test
public void test1() {
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient("localhost", 27017);

// 连接到数据库
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
System.out.println("Connect to database successfully");
}

}

本文转载自: 掘金

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

面试官又问什么是面向接口编程!送分题! 🐹 一、前言 🐣 二

发表于 2021-10-24

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。


  • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)。

🐹 一、前言

  • 大家好,我是小诚,又到了愉快的学习时间,最近收到小伙伴投稿,在面试的时候被面试官询问到一个比较少见的问题: 什么是面向接口编程? 说实话,之前我也面试过很多公司,也没有遇到过这个问题,感觉挺新颖的,故特以此文记录学习,因为本人水平有限,看待问题的时候可能不够全面,如有不同看法,欢迎大家在下方留言讨论。
  • 如果文章对你有帮助,可以帮忙一键三连哦! 如面试中遇到一些奇怪或者比较新颖的题目,欢迎私信投稿,感谢阅读!

🐣 二、面试官这样询问的用意

  面试官提问一个问题的时候,我们需要不是马上去答复,而是要揣摩出面试官提问的用意,这样才能够给出更全面的回答。关于这个问题,我个人猜想面试官的大概用意如下:

  1、看你是否了解过面向接口编程(不用奇怪,很多人只是听说过,有个模糊的概念,叫他说并不一定能说清楚)

  2、看你是否清楚面向接口编程的使用初衷(如果一个技术/思想出现没有解决存在的某些问题,那它存在的价值在哪里?)

  3、看你是否真正的将面向接口编程运用到开发中

🐰 三、什么是接口

  在介绍什么是面向接口编程之前,让我们先来认识下什么是接口,这样才能够”知其然,知其所以然”。

  接口,英文名叫Interface,可以理解成是一种标准(规范),在广义泛指一组标准的集合,它规定了实现该接口的类或者接口必须也拥有这一组规则,当然存在一种特殊情况,即空接口(不存在任何方法的接口)后续会介绍。

  在一个面向对象的系统中,系统之间的各项功能小到类之间,大到模块、系统之间的交互实际上是通过不同对象的相互协作来实现的,在设计阶段,并不会太关注内部的实现细节,而是着重于设计对象之间的协作关系,尽量达到高内聚、低耦合的目的。

  接口作为实体抽象出来的一种表现形式,用于抽离内部实现进行外部沟通,最终实现内部变动而不影响外部与其他实现交互,可以理解成按照这种思想来设计编程的方式就可以称为面向接口编程(现在讲得比较抽象,下面通过具体的例子形象化)。

  举个例子: 就像电脑上的USB插口,如果你觉的有线鼠标用着不过瘾,完全可以购买一个无线鼠标插入原来有线鼠标的插口即可实现无缝切换,而不需要修改计算机中的任何地方,无论你的无线鼠标是否和有线鼠标是同种类型,因为这个插口就是一种标准,只要你购买的鼠标实现了这个标准,它就可以通过这个插口使用对应的鼠标。

  PS:USB,是英文Universal Serial Bus(通用串行总线)的缩写(不是你是傻x的缩写….),是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。


🐷 四、面向过程编程、面向对象编程、面向接口编程对比
==========================

  在编码过程中,会听到关于这三者的介绍,有时候虽然对它们的概念有大致的了解,但是让你表达出来的时候总感觉表达不明白,下面通过具体的例子更加形象直观对它们重新的认识吧。

🍅 4.1、面向过程编程(POP)

  面向过程编程(Procedure Oriented Programming 简称POP :如C语言),着重的是过程,解决一个问题的时候,先分析出解决这个问题需要的步骤,然后使用函数将这些步骤一步步实现,然后处理问题的时候按照一定的顺序将这个函数一个个调用,方法执行完后问题也解决了。

  举例说明:肚子痛、怎么解决?(这是一个有味道的例子,但是为了让大家理解,所以举例得更去贴近生活些,如果觉的味道太大,下方留言,下次换个好例子)

  如果使用面向过程编程方式解决这个问题,那么流程大概如下:

  1、执行查找纸巾方法(不找纸巾难道用手?)

  2、执行坐上马桶方法(没有马桶你不会站着?)

  3、执行清理人体食物残渣排泄物方法(哎,舒服!)

  4、执行清洁方法(不清洁,难道上完号就提起裤子就走?)

  5、执行冲马桶方法(不冲!臭的可是你自己!)

  面向过程编程小结:

  通过上面的例子会发现,面向过程编程的思想在解决问题时,是将问题拆分成一个个步骤,每个步骤封装成对应的函数,然后按照某个顺序去执行,从而解决问题。

🍑 4.2、面向对象编程(OOP)

  面向对象编程(Object Oriented Programming 简称OOP :如C++,JAVA等语言),侧重点在对象,解决一个问题时,先将问题中的包含的事物抽象成对象概念,对象中包含具体的属性和行为,真正执行时再让每个对象去执行自己的某些方法,从而解决问题。

  举例说明:肚子痛、怎么解决?

  使用面向对象编程方式解决这个问题的大概流程如下:

  1、根据问题涉及到的实体,抽象出“人”对象、”马桶“对象、”纸巾“对象

  2、针对“人”对象添加一些属性和方法,属性如:xx 28cm、黑长直。方法:寻找纸巾方法、坐上马桶方法、清除排泄物方法、清洁xx工具方法

  3、针对“马桶”对象添加一些属性和方法,属性如:白色、长15m、椭圆形。方法:冲马桶方法

  4、针对“纸巾”对象添加一些属性和方法,属性如:白/黑/金色

  5、执行:

    人对象.寻找纸巾方法

    人对象.坐上马桶方法

    人对象.清除排泄物方法

    马桶.冲马桶方法(是的,马桶是自感应的,有钱你也可以这样玩,有钱人的生活就是舒服)

    人对象.清洁xx工具方法(清洁哪里不用我说你也懂吧!DDDD)

  面向对象编程小结:

  通过例子可以发现,解决相同的问题,面向对象编程的方式是先将问题中的实体抽象成具体的对象,然后再将属性和方法封装到对象中,最后通过不同的对象执行相应的方法解决问题。

🍒 4.3、面向接口编码(IOP)

  面向接口编程(Interface Oriented Programming:OIP)是一种编程思想,接口作为实体抽象出来的一种表现形式,用于抽离内部实现进行外部沟通,最终实现内部变动而不影响外部与其他实现交互,可以理解成按照这种思想来设计编程的方式就可以称为面向接口编程。

  它并不是比面向对象编程更先进的一种独立的编程思想,可以说属于面向对象思想体系的一部分或者说它是面向对象编程体系中的思想精髓之一。

  注: 上图中红框标出来的地方就是区别于面向对象编程的一些特点。

  上面面向过程和面向对象都举例肚子痛问题解决的例子,结合我们平常生活中经验可以知道,无论男女、都可能出现肚子痛需要上厕所的问题,但是不同的人有不同的方式解决这个问题,不可能每有一个不同处理方式的人就修改一次原来代码,这样不仅不符合面向对象编程中的开闭原则【对拓展开放,对修改关闭】,还可能带来潜在的风险。

  因此、我们可以将上面面向对象编程中上厕所的五个步骤抽取到【人肚子痛处理接口]和】【马桶接口】,哪个人肚子痛就实现这个【人肚子痛处理接口】,每个马桶类型都实现【马桶接口】(因为不是每个马桶的清理细节都一样,有的是自动,有的是手动)。

  具体怎么上厕所、冲马桶由你自己定义,你可以站着上、坐着上、倒立上都可以,这样就达到了内部变动而不影响外部交互的目的,我使用这个接口类型接收实现了这个接口的实现者,实现者中的方法逻辑修改了,并不影响我接收它,进行方法调用。

🐴 五、面向过程编程特点

  优点:

  将问题拆解成一个个步骤、类似流水线一样,一步步执行,将复杂的问题流程化进而简单化。

  性能比面向对象编程高,面向对象编程中类调用过程需要加载、实例化,资源消耗更大;对向性能要求高的比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发。

  缺点:

  没有面向对象编程易维护、易复用、易扩展

  使用场景:

  适用于性能要求较高的系统开发中

🐓 六、面向对象编程特点

  优点:

  易理解:采用面向对象思想设计开发,更符合人的思考方式,可读性高。

  易维护:面向对象有封装、继承、多态性的特性,即使需求有变动,需要维护的更多是局部模块,维护起来更加方便和更低成本。

  易扩展:面向对象有封装、继承、多态性的特性,在设计系统阶段可以设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展。

  易复用:也是同理,因为面向对象的三大特性,使得通用代码可以更简单的复用,提高开发效率。

  缺点:

  性能会比面向过程低,编程复杂度较高

  使用场景:

  用户需求经常变化,互联网应用,游戏,企业内部应用

🐋 七、面向接口编程的作用

  在讲解面向接口编程的一些特点时,让我们先来了解下面向对象编程中五大设计原则中的两个:

🍑 7.1、符合开闭、依赖倒置原则、增强拓展性

  开闭原则OCP(Open-Close Principle): 对拓展开放、对修改关闭。

  依赖倒置原则DIP(Dependency Inversion Principle): 抽象不应该依赖于细节、细节应该依赖于抽象

  在软件开发中,因为业务的不断变化,系统拓展性是时刻存在的。以数据库为例,在没有引入接口之前,项目一开始因为业务量较小的原因,设计的时候我们会是直接通过代码来实现对某一数据库的一系列操作,抽象出来就如下图显示:

  但是随着业务的增多和复杂,单个传统关系型数据库并不能满足我们的业务,需要引入非关系型数据库做中间层和引入更先进数据库做数据存储,抽象出来如下:

  很明显,因为我们一开始业务简单就只考虑了一种数据库,导致操作数据库的代码强依赖了具体的实现类,如今因为业务变化需要引入了其他不同的数据库,为了达到这个目的,我们不得已要去修改原来业务的代码,这样的修改显然是违背了面向对象编程的开闭原则、依赖倒置原则和带来了潜在的风险,因此,这样的设计并不合理,故我们引入了接口,抽象出来如下:

  如上图所示,有了接口这一标准,我们的JAVA程序无需直接依赖对应的实现类,每种数据库厂商实现JDBC标准,给使用者提供一个驱动,如果我们因为业务变动需要引入第三方数据库,只需要修改对应的驱动即可,不需要变动原来的逻辑,不仅满足了业务代码不再依赖实现类,而是实现类依赖接口即依赖倒置原则「抽象不应该依赖于细节,细节应该依赖于抽象」和开闭原则[对拓展开放,对修改关闭],还避免了改动带来的潜在风险。

  接口中定义了规范、不同的实现者去根据自己需求实现规范,后面如果有新的需求,可以创建新的实现者实现接口规范即可,这样做到了代码的松耦合。

  最常见的例子就是: JDBC(JAVA数据库连接),它是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,是Java访问数据库的标准规范。

  无规则聚不成方圆,如果没有JDBC的话访问的数据库操作就没有了规范,所有的数据库厂商都会有自己的一套数据库的访问规则,一旦公司的数据库从切换数据库,整个涉及到数据库的代码都需要重新写、这对开发者来说简直是世界末日。

  所以sun公司统一了数据库的规范,不同的数据库厂商需要提供实现了这个规范的一个驱动,用于开发者连接数据库时进行通信,有了JDBC,开发者不需要关注如何去编写针对不同数据库的访问代码,变动数据库时,只需要去切换不同的驱动即可。

  PS:JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库! 每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。

🐇 八、抽象类和接口的关系

  看到这里,相信有很多小伙伴会有疑问,能否使用抽象类替换接口,毕竟两者概念上很容易模糊。在个人看来,JAVA中,除了类只能被单继承,但是接口可以多实现(接口之间可以多继承)这个限制外,最重要的是两者设计的一个目的。

  抽象类设计出来的目的是为了抽取出某一个种类的一些共有特性或者默认行为,以达到代码复用的目的。而接口则是为了规定某一种标准而设计出来,它强调的是规范,在面向对象语言中体现就是多态性的使用。 所以,如果出现该设计为抽象类还是接口纠结的场景,建议可以从设计的动机方面进行考虑,应该能得到一个比较好的结果。

🐈 九、标记接口

  特点:

  标记接口(Market Interface),也叫标签接口(Tag Interface),它表示的是没有任何方法和属性的接口,它仅仅表明被标记的类属于一个特定的类型,供其他代码来测试允许做一些事情。

The tag/marker interface pattern is a design pattern in computer science, used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata.

  上面的英文是维基百科关于标记接口的一个描述,由此,我们可以知道,标记接口并不是JAVA语言独有的东西,而是计算机科学中的一种设计理念,用于给面向对象语言描述对象。

  因为编程语言本身并不支持为类维护元数据,而标记接口可以用作描述类的元数据,弥补了这个功能上的缺失。对于实现了标记接口的类,我们就可以在运行时通过反射机制去获取元数据。

  在JDK中,我们最常接触到的标记接口有许多如: Serializable、Cloneable、RandomAccess等,如果一个类实现了Serializable接口,表示这个类是可以被序列化的,实际上的原理就是实现了这个接口的类就会被打上一个【可序列化】的标签,在运行时可以通过反射去获取然后进行一系列的操作。

  在JAVA中,标记接口的作用主要在以下两方面:

  1、建立一个公共的父接口:

  正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

   2、向一个类添加数据类型

  这个是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型即可以使用这个接口去接收实现了它的具体类的实例。

🐧 十、关于面向接口编程在实际运用的一些问题

10.1、为什么现在很多项目从controller、service到dao层都是使用: 接口 + 实现类的方式、但是只有一个实现类,这种方式是否有必要?

  我敢说,这个问题的争论一定不会少,大家都认为自己说得有理,很难去让一个使用接口+ 实现类的人去说服一个直接使用实现类的人,反之亦然,在此处,收集了许多看法,欢迎大家讨论。

  一、支持:接口 + 实现类的模式、理由如下

  看法1: 如果使用Spring框架作为项目开发,推荐使用这种方式,这样才能最大化的发挥Spring IOC的用途,更方便使用和减少内存使用。

  看法2: 利于维护和拓展,你现在看到的是只有一个实现类,但是随着业务的复杂变化,后面就可能出现多个实现类,使用该种方式如果有新的业务变动,直接添加新的实现类即可,无需改动原来的代码。

  看法3: 利于团队合作,不同的人可能负责不同模块的开发如Controller、Service、Dao,只要定义好接口,负责各个模块的人即可去负责对应的开发。

  看法4: 控制暴露给外部的内容,保证安全性的问题。接口只会暴露定义公开的部分,但是直接使用对象则可以看到私有部分,这些私有部分可能会有安全性问题。

  看法5: 这个属于编程规范要求,封装实现细节,暴露对外接口,这也是“高内聚,低耦合”的思想。

  看法6: 符合开闭原则,降低变动带来的风险,便于理解,因为很多源码也是使用这种方式。

  看法7: 利用多态性,同一个接口可以存在多个实现类,可以配合反射和配置文件的使用,实现代码的解耦,需要增强功能时,只需要添加新的类和修改配置文件即可达到目的。

  二、不支持:接口 + 实现类的模式,支持直接使用实现类方式,理由如下

  看法1: 经过大多数实际项目经验,百分之99接口和实现类都是1:1的实现,在这种情况下,多定义一个接口就没有必要,而且还需要维护额外的代码,所以没必要为了规范而规范,如果是小团队,公司技术水平比较平均,直接使用实现类即可,没必要定义接口。

🐉 十一、写在最后

  好了,关于面向接口编程的介绍今天介绍到这里,大家有不同看法的欢迎在下方留言,欢迎大家向我投稿面试中遇到的奇葩问题噢,学习了就要运行,赶紧拿着文章找个面试官对线一波吧。如果文章对你有帮助,不要忘记三连呀!

🐬 十二、参考资料

  1、JAVA教程

  2、JAVA标记接口

本文转载自: 掘金

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

《游戏系统设计七》重现游戏的抽奖系统

发表于 2021-10-24

小知识,大挑战!本文正在参与“ 程序员必备小知识

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

image.png

有粉丝私聊我做个大生意,让我在王者荣耀中随机模式卡出自己想用的影响,我想这个粉丝一定不知道这种随机都是服务器控制的,不是客户端可以操作的,如果想黑到服务器有点难度,如果再能找到对战的局修改数据可真是太牛逼了,我只想说我不行,这1000+ 我挣不到。

抽奖也几乎是每个游戏的标配,也是氪金的最重要的地方,现在仍然记得当初在一款游戏的时候,对抽奖欲罢不能,现在的我写了好多次的抽奖,具体的实现逻辑早都熟于心,所以也没有那么多的执念。今天写一下抽奖。

1、抽奖的需求:

抽奖的实现每个游戏大都不一样,具体抽奖如何实现是跟着具体的策划需求,策划的需求大部分分为几类:

1、抽奖分免费次数和付费抽取,有的游戏会多久cd时间送一次免费次数,或者签到多少次送一次抽奖次数

2、抽奖分十连抽(多次抽取)和单抽

3、抽奖分为是否必出某个奖励和不要求必出,比如十连抽必出某个牛逼的英雄

4、多次抽取的时候是否奖励池全随机,可出现重复的奖励,还是奖励池内随机十个

5、抽奖有的还分为是否有保底次数,是否首抽的判断逻辑,保底必出英雄,首抽送固定的英雄

image.png

2、抽奖的业务逻辑整理

协议定义:我之前接触的项目是用protobuf通信的,所以我就选择protobuf定义通信格式,你可以根据自己的项目进行定义,不重要。

C 表示客户端发送给服务器的消息

S 表示服务器返回的消息

1
2
3
4
5
6
7
8
ini复制代码message C_DRAW{
required int32 rewardType = 1; // 抽奖类型 1单抽 2 10连抽
}
message S_DRAW{
required int32 rewardType = 1; // 抽奖类型 1单抽 2 10连抽
required string rewardContent = 2; // 抽奖结果
optional string specialReward = 3; // 比如十连抽送的奖励
}

协议解释:

界面的逻辑很简单,客户端发送抽奖类型到服务器,服务器进行业务处理。

服务器的逻辑分为以下几步

1、检查客户端发送的抽奖类型是否合法,如果不是定义1 和 2 ,则报错,因为有人传递非法的数据。

2、检查对应抽奖类型的次数是否足够,如果次数不足则报错

3、检查对应的抽奖类型需要的资源是否足够,如果资源不足则报错给客户端。

4、获取对应的抽奖类型的基础数据配置,检查下是否异常,异常则报错

5、执行对应的抽奖逻辑

6、将抽取的资源加入到玩家身上

7、将抽取的资源组装成消息传递给客户端,进行页面展示。

从上面的逻辑可以看到,在客户端做任何操作都不会影响最终的抽奖结果,大生意做不成了!

3、代码实现

1、基础数据的配置

image.png

解释:

id 是表示数据类型,

cost 是代表抽奖的消耗资源配置

data 是抽奖池,A_50 A代表一种资源,比如A= 2_100 表示2号资源给100个。下划线最后的一个值50 是权重

2、基础数据的解析
注意点:

1.注意数据的格式

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
typescript复制代码​
import javafx.util.Pair;
​
import java.util.*;
​
/**
* 奖励池的配置
* @author 香菜
*/
public class RewardPoolConfig {
Map<Integer, ConfigPair> rewardConfigMap;
public RewardPoolConfig config;
​
public RewardPoolConfig() {
readConfig();
}
private void readConfig() {
// TODO readFromSource();
// 根据自己项目的不同进行
rewardConfigMap = new HashMap<>();
// 模拟下
rewardConfigMap.put(1, new ConfigPair("A_1", "A_50#B_50#C_50#D_50#E_50#F_50#G_50#H_50"));
rewardConfigMap.put(2, new ConfigPair("B_1", "A_50#B_50#C_50#D_50#E_50#F_50#G_50#H_50"));
}
​
public RewardPoolConfig getInstance() {
if (config == null) {
config = new RewardPoolConfig();
}
return config;
}
​
public Map<Integer, ConfigPair> getRewardConfigMap() {
return rewardConfigMap;
}
​
/**
* 单个奖励池的解析
*/
static class ConfigPair {
// 消耗的资源
String cost;
// 权重列表
List<Pair<String, Integer>> weightList;
​
public ConfigPair(String cost, String data) {
this.cost = cost;
String[] split = data.split("#");
List<Pair<String, Integer>> tmpList = new ArrayList<>();
for (String itemPair : split) {
int i = itemPair.lastIndexOf("_");
String reward = itemPair.substring(0, i);
String weightStr = itemPair.substring(i + 1);
tmpList.add(new Pair<>(reward, Integer.valueOf(weightStr)));
}
weightList = Collections.unmodifiableList(tmpList);
}
​
public List<Pair<String, Integer>> getWeightList() {
return weightList;
}
​
public String getCost() {
return cost;
}
}
​
public static void main(String[] args) {
ConfigPair a_1 = new ConfigPair("A_1", "A_50#B_50#C_50#D_50#E_50#F_50#G_50#H_50");
System.out.println();
}
}

1、上面的加载数据的数据源要根据具体项目的设计,从恰当的数据源读入,比如有的是用xml,Excel,或者数据库,Json等

2、可以根据需求进行扩展,比如有其他的十连抽必送,可增加字段,这里只是写核心代码。

3、权重随机算法

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
java复制代码import com.google.common.collect.Lists;
import javafx.util.Pair;
​
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
​
/**
* 权重随机
* @author 香菜
* @param <K>
* @param <V>
*/
public class WeightRandom<K, V extends Number> {
private TreeMap<Double, K> weightMap = new TreeMap<>();
public WeightRandom(List<Pair<K, V>> list) throws Exception {
if (list == null || list.size() == 0) {
throw new Exception("list is null");
}
for (Pair<K, V> pair : list) {
if (pair.getValue().doubleValue() <= 0) {
throw new Exception("weight is wrong");
}
double lastWeight = this.weightMap.size() == 0 ? 0 : this.weightMap.lastKey().doubleValue();//统一转为double
this.weightMap.put(pair.getValue().doubleValue() + lastWeight, pair.getKey());//权重累加
}
}
​
public K random() {
double randomWeight = this.weightMap.lastKey() * Math.random();
SortedMap<Double, K> tailMap = this.weightMap.tailMap(randomWeight, false);
return this.weightMap.get(tailMap.firstKey());
}
​
public static void main(String[] args) throws Exception {
List<Pair<String, Integer>> weighList = Lists.newArrayList();
weighList.add(new Pair<>("AAA", 10));
weighList.add(new Pair<>("BBB", 10));
weighList.add(new Pair<>("CCC", 10));
weighList.add(new Pair<>("DDD", 10));
String random = new WeightRandom<>(weighList).random();
System.out.println(random);
}
​
}
4、调用逻辑
import javafx.util.Pair;
import java.util.List;
import java.util.Map;
​
/**
* 模拟抽奖
* @author 香菜
*/
public class Aain {
​
public static void main(String[] args) throws Exception {
int rewardType = 1;
Map<Integer, RewardPoolConfig.ConfigPair> rewardConfigMap = RewardPoolConfig.getInstance().getRewardConfigMap();
//1、检查客户端发送的抽奖类型是否合法,如果不是定义1 和 2 ,则报错,因为有人传递非法的数据。
if (!rewardConfigMap.containsKey(rewardType)) {
// 非法的抽奖类型
return;
}
//2、检查对应抽奖类型的次数是否足够,如果次数不足则报错
// TODO checkPlayerData();
RewardPoolConfig.ConfigPair configPair = rewardConfigMap.get(rewardType);
//3、检查对应的抽奖类型需要的资源是否足够,如果资源不足则报错给客户端。
String cost = configPair.getCost();
// TODO checkResource();
//4、获取对应的抽奖类型的基础数据配置,检查下是否异常,异常则报错
// 在最开始的时候检测了,这不忽略
//5、执行对应的抽奖逻辑
List<Pair<String, Integer>> weightList = configPair.getWeightList();
String reward = new WeightRandom<>(weightList).random();
//6、将抽取的资源加入到玩家身上
// TODO addResource(reward);
//7、将抽取的资源组装成消息传递给客户端,进行页面展示。
// sendResultToClient(reward)
System.out.println(reward);
}
​
}

5、其他功能完成

1
2
3
4
5
6
7
bash复制代码5.1 十连抽,只要改动抽奖循环就可以了

5.2 十连抽额外送,只要在基础数据配一下送的道具,最后加资源到玩家身上的时候直接加上

5.3 十连抽不重复,只要在抽完一次直接从池子里移除道具

5.4 免费次数抽完进行cd ,则直接记录玩家的抽取时间就可以了,下次判断时间间隔是否大于cd时间就可以了

4、总结:

抽奖的逻辑是相对来说比较简单,但是在项目中会和其他模块进行关联,比如任务系统的进度提升,比如要求不能出重复的武将,这些五花八门的要求只要在前置处理,把最后的Weigh 处理好就可以了

本文转载自: 掘金

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

分库分表利器之Sharding Sphere(深度好文,看过

发表于 2021-10-24

Sharding-Sphere

Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 ShardingSphere,2020年4⽉16⽇正式成为 Apache 软件基⾦会的顶级项⽬。

随着版本的不断更迭 ShardingSphere 的核心功能也变得多元化起来。如图7-1,ShardingSphere生态包含三款开源分布式数据库中间件解决方案,Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar。

image-20210716164452547

图7-1
Apache ShardingSphere 5.x 版本开始致力于提供可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,而且仍在不断增加中。

如图7-2,是Sharding-Sphere的整体架构。

图7-2
Sharding-JDBC


Sharding-JDBC是比较常用的一个组件,它定位的是一个增强版的JDBC驱动,简单来说就是在应用端来完成数据库分库分表相关的路由和分片操作,也是我们本阶段重点去分析的组件。

我们在项目内引入Sharding-JDBC的依赖,我们的业务代码在操作数据库的时候,就会通过Sharding-JDBC的代码连接到数据库。也就是分库分表的一些核心动作,比如SQL解析,路由,执行,结果处理,都是由它来完成的,它工作在客户端。

图7-3
Sharding-Proxy


Sharding-Proxy有点类似于Mycat,它是提供了数据库层面的代理,什么意思呢?简单来说,以前我们的应用是直连数据库,引入了Sharding-Proxy之后,我们的应用是直连Sharding-Proxy,然后Sharding-Proxy通过处理之后再转发到mysql中。

这种方式的好处在于,用户不需要感知到分库分表的存在,相当于正常访问mysql。目前Sharding-Proxy支持Mysql和PostgreSQL两种数据库协议,如图7-4所示。

图7-4
Sharding-Sidecar(TODO)


看到Sidecar,大家应该就能想到服务网格架构,它主要定位于 Kubernetes 的云原生数据库代理,以 Sidecar 的形式代理所有对数据库的访问。目前Sharding-Sidecar还处于开发阶段未发布。

Sharding-JDBC

Sharding-JDBC是对原有JDBC驱动的增强,在分库分表的场景中,为应用提供了如图7-5所示的功能。

图7-5

Sharding-JDBC的整体架构

如图7-6所示,Java应用程序通过Sharding-JDBC驱动访问数据库,而在Sharding-JDBC中,它会根据相关配置完成分库分表路由、分布式事务等功能,所以我们可以认为它是对JDBC驱动的增强。

Registry Center表示注册中心,用来实现集中化分片配置规则管理、动态配置、以及数据源等信息。

“ alt=”ShardingSphere-JDBC Architecture)

图7-6
Sharding-JDBC的基本使用


为了让大家更好的理解Shading-JDBC,我们通过一个案例来简单认识一下Sharding-JDBC。‘

shardingsphere.apache.org/document/cu…

为了更直观的理解Sharding-JDBC,下面通过一个原生的案例进行演示。

image-20210717164207374

图7-7
图7-8表示整体项目结构。

image-20210717170042801

引入Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.0.0-alpha</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

Order

定义Order表的实体对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Data
public class Order implements Serializable {

private static final long serialVersionUID = 661434701950670670L;

private long orderId;

private int userId;

private long addressId;

private String status;
}

OrderReporitoryImpl

定义数据库操作层

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
java复制代码public interface OrderRepository {

void createTableIfNotExists() throws SQLException;

Long insert(final Order order) throws SQLException;
}

public class OrderRepositoryImpl implements OrderRepository {

private final DataSource dataSource;

public OrderRepositoryImpl(final DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void createTableIfNotExists() throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS t_order (order_id BIGINT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, address_id BIGINT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id))";
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.executeUpdate(sql);
}
}
@Override
public Long insert(final Order order) throws SQLException {
String sql = "INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)";
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
preparedStatement.setInt(1, order.getUserId());
preparedStatement.setLong(2, order.getAddressId());
preparedStatement.setString(3, order.getStatus());
preparedStatement.executeUpdate();
try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {
if (resultSet.next()) {
order.setOrderId(resultSet.getLong(1));
}
}
}
return order.getOrderId();
}
}

OrderServiceImpl

定义数据库访问层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface ExampleService {
/**
* 初始化表结构
*
* @throws SQLException SQL exception
*/
void initEnvironment() throws SQLException;

/**
* 执行成功
*
* @throws SQLException SQL exception
*/
void processSuccess() throws SQLException;
}
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复制代码public class OrderServiceImpl implements ExampleService {
private final OrderRepository orderRepository;
Random random=new Random();

public OrderServiceImpl(final DataSource dataSource) {
orderRepository=new OrderRepositoryImpl(dataSource);
}

@Override
public void initEnvironment() throws SQLException {
orderRepository.createTableIfNotExists();
}

@Override
public void processSuccess() throws SQLException {
System.out.println("-------------- Process Success Begin ---------------");
List<Long> orderIds = insertData();
System.out.println("-------------- Process Success Finish --------------");
}
private List<Long> insertData() throws SQLException {
System.out.println("---------------------------- Insert Data ----------------------------");
List<Long> result = new ArrayList<>(10);
for (int i = 1; i <= 10; i++) {
Order order = insertOrder(i);
result.add(order.getOrderId());
}
return result;
}
private Order insertOrder(final int i) throws SQLException {
Order order = new Order();
order.setUserId(random.nextInt(10000));
order.setAddressId(i);
order.setStatus("INSERT_TEST");
orderRepository.insert(order);
return order;
}
}

DataSourceUtil

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

private static final String HOST = "192.168.221.128";

private static final int PORT = 3306;

private static final String USER_NAME = "root";

private static final String PASSWORD = "123456";

public static DataSource createDataSource(final String dataSourceName) {
HikariDataSource result = new HikariDataSource();
result.setDriverClassName("com.mysql.jdbc.Driver");
result.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", HOST, PORT, dataSourceName));
result.setUsername(USER_NAME);
result.setPassword(PASSWORD);
return result;
}
}

Sharding-JDBC分片规则配置

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
java复制代码public class ShardingDatabasesAndTableConfiguration {
//创建两个数据源
private static Map<String,DataSource> createDataSourceMap(){
Map<String, DataSource> dataSourceMap=new HashMap<>();
dataSourceMap.put("ds0",DataSourceUtil.createDataSource("shard01"));
dataSourceMap.put("ds1",DataSourceUtil.createDataSource("shard02"));
return dataSourceMap;
}

private static ShardingRuleConfiguration createShardingRuleConfiguration(){
ShardingRuleConfiguration configuration=new ShardingRuleConfiguration();
configuration.getTables().add(getOrderTableRuleConfiguration());
// configuration.getBindingTableGroups().add("t_order,t_order_item");
//
//
/**
* 设置数据库的分片规则
* inline表示行表达式分片算法,它使用groovy的表达式,支持单分片键,比如 t_user_$->{uid%8} 表示t_user表根据u_id%8分成8张表
*/
configuration.setDefaultDatabaseShardingStrategy(
new StandardShardingStrategyConfiguration("user_id","inline"));
/**
* 设置表的分片规则
*/
configuration.setDefaultTableShardingStrategy(new StandardShardingStrategyConfiguration("order_id","order_inline"));
Properties props=new Properties();
props.setProperty("algorithm-expression","ds${user_id%2}"); //表示根据user_id取模得到目标表
/**
* 定义具体的分片规则算法,用于提供分库分表的算法规则
*/
configuration.getShardingAlgorithms().put("inline",new ShardingSphereAlgorithmConfiguration("INLINE",props));
Properties properties=new Properties();
properties.setProperty("algorithm-expression","t_order_${order_id%2}");
configuration.getShardingAlgorithms().put("order_inline",new ShardingSphereAlgorithmConfiguration("INLINE",properties));
configuration.getKeyGenerators().put("snowflake",new ShardingSphereAlgorithmConfiguration("SNOWFLAKE",getProperties()));
return configuration;
}
private static Properties getProperties(){
Properties properties=new Properties();
properties.setProperty("worker-id","123");
return properties;
}
//创建订单表的分片规则
private static ShardingTableRuleConfiguration getOrderTableRuleConfiguration(){
ShardingTableRuleConfiguration tableRule=new ShardingTableRuleConfiguration("t_order","ds${0..1}.t_order_${0..1}");
tableRule.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("order_id","snowflake"));
return tableRule;
}

public static DataSource getDataSource() throws SQLException {
return ShardingSphereDataSourceFactory.createDataSource(createDataSourceMap(), Collections.singleton(createShardingRuleConfiguration()),new Properties());
}
}

Main方法测试

1
2
3
4
5
6
7
8
java复制代码public class ExampleMain {
public static void main(String[] args) throws SQLException {
DataSource dataSource=ShardingDatabasesAndTableConfiguration.getDataSource();
ExampleService exampleService=new OrderServiceImpl(dataSource);
exampleService.initEnvironment();
exampleService.processSuccess();
}
}

Sharding-JDBC使用总结

从上述的案例来看,Sharding-JDBC相当于通过配置化的方式帮我们提供了分片规则的配置,但是基于原生的使用方式,配置起来比较复杂,我们可以直接集成到Spring-Boot中,使用起来会比较简洁。

Spring Boot集成Sharding-JDBC分片实战

下面给大家演示一下在springboot应用中集成mybatis的情况下,如何实现分库分表的配置。

项目代码参考: sharding-jdbc-spring-boot-example,项目结构如图7-8所示。

image-20210719150821495

图7-8
其中,MybatisPlusGeneratorConfig,用来生成t_order表的dal、service、controller代码,由于代码是基于mybatis-plus生成,这里就不做过多描述了

引入pom依赖

1
xml复制代码<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <scope>runtime</scope>    </dependency>    <dependency>        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-boot-starter</artifactId>        <version>3.4.3</version>    </dependency>    <dependency>        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-generator</artifactId>        <version>3.4.1</version>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.12</version>    </dependency>    <dependency>        <groupId>org.apache.shardingsphere</groupId>        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>        <version>5.0.0-alpha</version>    </dependency>    <dependency>        <groupId>com.zaxxer</groupId>        <artifactId>HikariCP</artifactId>        <version>3.4.2</version>    </dependency></dependencies>

application.properties配置

1
properties复制代码#配置数据源名称spring.shardingsphere.datasource.names=ds-0,ds-1spring.shardingsphere.datasource.common.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.common.driver-class-name=com.mysql.jdbc.Driver# 分别配置多个数据源的详细信息spring.shardingsphere.datasource.ds-0.username=rootspring.shardingsphere.datasource.ds-0.password=123456spring.shardingsphere.datasource.ds-0.jdbc-url=jdbc:mysql://192.168.221.128:3306/shard01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8spring.shardingsphere.datasource.ds-1.username=rootspring.shardingsphere.datasource.ds-1.password=123456spring.shardingsphere.datasource.ds-1.jdbc-url=jdbc:mysql://192.168.221.128:3306/shard02?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8# 配置数据库的分库策略,其中database-inline会在后面声明spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-column=user_idspring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=database-inline# 配置t_order表的分表策略,其中t-order-inline会在后面声明# 行表达式标识符可以使用 ${...} 或 $->{...},但前者与 Spring 本身的属性文件占位符冲突,因此在 Spring 环境中使用行表达式标识符建议使用 $->{...}spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..1}spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_idspring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=t-order-inline# 配置order_id采用雪花算法生成全局id策略spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=order_idspring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=snowflake# 配置具体的分库分表规则spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.type=INLINEspring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.props.algorithm-expression=ds-$->{user_id % 2}spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.type=INLINEspring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.props.algorithm-expression=t_order_$->{order_id % 2}spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-item-inline.type=INLINEspring.shardingsphere.rules.sharding.sharding-algorithms.t-order-item-inline.props.algorithm-expression=t_order_item_$->{order_id % 2}# 配置雪花算法spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKEspring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123

增加逻辑代码

TOrderMapper

1
java复制代码@Update("CREATE TABLE IF NOT EXISTS t_order (order_id BIGINT AUTO_INCREMENT, user_id INT NOT NULL, address_id BIGINT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id))")void createTableIfNotExists();

TOrderServiceImpl

1
java复制代码@Servicepublic class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {    @Autowired    TOrderMapper orderMapper;    Random random=new Random();    @Override    public void initEnvironment() throws SQLException {        orderMapper.createTableIfNotExists();    }    @Override    public void processSuccess() throws SQLException {        System.out.println("-------------- Process Success Begin ---------------");        List<Long> orderIds = insertData();        System.out.println("-------------- Process Success Finish --------------");    }    private List<Long> insertData() throws SQLException {        System.out.println("---------------------------- Insert Data ----------------------------");        List<Long> result = new ArrayList<>(10);        for (int i = 1; i <= 10; i++) {            TOrder order = new TOrder();            order.setUserId(random.nextInt(10000));            order.setAddressId(i);            order.setStatus("INSERT_TEST");            orderMapper.insert(order);            result.add(order.getOrderId());        }        return result;    }}

TOrderController

提供测试接口。

1
java复制代码@RestController@RequestMapping("/t-order")public class TOrderController {    @Autowired    ITOrderService orderService;    @GetMapping    public void init() throws SQLException {        orderService.initEnvironment();        orderService.processSuccess();    }}

Sharding-JDBC的相关概念说明

前面我们通过两种方式演示了Sharding-JDBC的分库分表功能的用法,其实,从这个层面来说,Sharding-JDBC相当于增强了JDBC驱动的功能,使得开发者只需要通过配置就可以轻松完成分库分表功能的实现。

在Sharding-JDBC中,有一些表的概念,需要给大家普及一下,逻辑表、真实表、分片键、数据节点、动态表、广播表、绑定表。

逻辑表

逻辑表可以理解为数据库中的视图,是一张虚拟表。可以映射到一张物理表,也可以由多张物理表组成,这些物理表可以来自于不同的数据源。对于mysql, Hbase和ES,要组成一张逻辑表,只需要他们有相同含义的key即可。这个key在mysql中是主键,Hbase中是生成rowkey用的值,是ES中的key。

在前面的分库分表规则配置中,就有用到t_order这个逻辑表的定义,当我们针对t_order表操作时,会根据分片规则映射到实际的物理表进行相关事务操作,如图7-9所示,逻辑表会在SQL解析和路由时被替换成真实的表名。

1
properties复制代码spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..1}

img

图7-9
广播表


广播表也叫全局表,也就是它会存在于多个库中冗余,避免跨库查询问题。

比如省份、字典等一些基础数据,为了避免分库分表后关联表查询这些基础数据存在跨库问题,所以可以把这些数据同步给每一个数据库节点,这个就叫广播表,如图7-10所示。

img

图7-10
在Sharding-JDBC中,配置方式如下

1
properties复制代码# 广播表, 其主节点是ds0spring.shardingsphere.sharding.broadcast-tables=t_configspring.shardingsphere.sharding.tables.t_config.actual-data-nodes=ds$->{0}.t_config

绑定表

我们有些表的数据是存在逻辑的主外键关系的,比如订单表order_info,存的是汇总的商品数,商品金额;订单明细表order_detail,是每个商品的价格,个数等等。或者叫做从属关系,父表和子表的关系。他们之间会经常有关联查询的操作,如果父表的数据和子表的数据分别存储在不同的数据库,跨库关联查询也比较麻烦。所以我们能不能把父表和数据和从属于父表的数据落到一个节点上呢?

比如order_id=1001的数据在node1,它所有的明细数据也放到node1;order_id=1002的数据在node2,它所有的明细数据都放到node2,这样在关联查询的时候依然是在一个数据库,如图7-11所示

img

图7-11

1
properties复制代码# 绑定表规则,多组绑定规则使用数组形式配置spring.shardingsphere.rules.sharding.binding-tables=t_order,t_order_item

如果存在多个绑定表规则,可以用数组的方式声明

1
properties复制代码spring.shardingsphere.rules.sharding.binding-tables[0]= # 绑定表规则列表spring.shardingsphere.rules.sharding.binding-tables[1]= # 绑定表规则列表spring.shardingsphere.rules.sharding.binding-tables[x]= # 绑定表规则列表

Sharding-JDBC中的分片策略

Sharding-JDBC内置了很多常用的分片策略,这些算法主要针对两个维度

  • 数据源分片
  • 数据表分片

Sharding-JDBC的分片策略包含了分片键和分片算法;

  • 分片键,用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。
  • 分片算法,就是用来实现分片的计算规则。

Sharding-JDBC提供内置了多种分片算法,包含四种类型分别是

  • 自动分片算法
  • 标准分片算法
  • 复合分片算法
  • Hinit分片算法

自动分片算法

自动分片算法,就是根据我们配置的算法表达式完成数据的自动分发功能,在Sharding-JDBC中提供了五种自动分片算法

  • 取模分片算法
  • 哈希取模分片算法
  • 基于分片容量的范围分片算法
  • 基于分片边界的范围分片算法
  • 自动时间段分片算法

取模分片算法

最基础的取模算法,它会根据分片字段的值和sharding-count进行取模运算,得到一个结果。

ModShardingAlgorithm

1
properties复制代码# database-mod是自定义字符串名字spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=database-mod# MOD表示取模算法类型spring.shardingsphere.rules.sharding.sharding-algorithms.database-mod.type=MOD# 表示分片数量spring.shardingsphere.rules.sharding.sharding-algorithms.database-mod.props.sharding-count=2

哈希取模分片算法

和取模算法相同,唯一的区别是针对分片键得到哈希值之后再取模

HashModShardingAlgorithm

1
properties复制代码# database-mod是自定义字符串名字spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=database-hash-modspring.shardingsphere.rules.sharding.sharding-algorithms.database-hash-mod.type=HASH_MODspring.shardingsphere.rules.sharding.sharding-algorithms.database-hash-mod.props.sharding-count=2

分片容量范围

分片容量范围,简单理解就是按照某个字段的数值范围进行分片,比如存在下面这样一个需求,怎么配置呢?

1
txt复制代码(0~199)保存到表0[200~399]保存到表1[400~599)保存到表2

参考7.2.3章节中的方式,构建一个t_order_colume_range表,使用mybatis-plus生成相关代码,如图7-12所示

image-20210720174801870

图7-12
添加如下配置,通过spring.profiles.active=volumn-range来激活不同的配置信息。

1
properties复制代码server.port=8080spring.mvc.view.prefix=classpath:/templates/spring.mvc.view.suffix=.htmlspring.shardingsphere.datasource.names=ds-0spring.shardingsphere.datasource.common.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.common.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds-0.username=rootspring.shardingsphere.datasource.ds-0.password=123456spring.shardingsphere.datasource.ds-0.jdbc-url=jdbc:mysql://192.168.221.128:3306/shard01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8spring.shardingsphere.rules.sharding.tables.t_order_volume_range.actual-data-nodes=ds-0.t_order_volume_range_$->{0..2}spring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-column=user_idspring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-algorithm-name=t-order-volume-rangespring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.column=order_idspring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.key-generator-name=snowflakespring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.type=VOLUME_RANGE#最小的范围,0-200spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.range-lower=200#最大的范围,600 ,如果超过600,会报错spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.range-upper=600# 表示每张表的容量为200spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.sharding-volume=200spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKEspring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123

基于分片边界的范围分片算法

前面讲的分片容量范围分片,是一个均衡的分片方法,如果存在不均衡的场景,比如下面这种情况

1
txt复制代码(0~1000)保存到表0[1000~20000]保存到表1[20000~300000)保存到表2[300000~无穷大)保存到表3

我们就可以用到基于分片边界的范围分片算法来完成,配置方法如下

BoundaryBasedRangeShardingAlgorithm

1
properties复制代码# BOUNDARY_RANGE 表示分片算法类型spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-boundary-range.type=BOUNDARY_RANGE# 分片的范围边界,多个范围边界以逗号分隔spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-boundary-range.props.sharding-ranges=1000,20000,300000

自动时间段分片算法

IntervalShardingAlgorithm

根据时间段进行分片,如果想实现如下功能

1
txt复制代码(1970-01-01 23:59:59 ~ 2020-01-01 23:59:59) 表0[2020-01-01 23:59:59 ~ 2021-01-01 23:59:59) 表1[2021-01-01 23:59:59 ~ 2021-02-01 23:59:59) 表2[2022-01-01 23:59:59 ~ 2024-01-01 23:59:59) 表3

配置方法如下,表示从2010-01-01到2021-01-01这个时间区间内的数据,按照每一年划分一个表

1
properties复制代码spring.shardingsphere.rules.sharding.tables.t_order_volume_range.actual-data-nodes=ds-0.t_order_volume_range_$->{0..2}spring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-column=create_datespring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-algorithm-name=t-order-auto-intervalspring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.column=order_idspring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.key-generator-name=snowflakespring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.type=AUTO_INTERVAL# 分片的起始时间范围,时间戳格式:yyyy-MM-dd HH:mm:ssspring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.datetime-lower=2010-01-01 23:59:59# 分片的结束时间范围,时间戳格式:yyyy-MM-dd HH:mm:ssspring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.datetime-upper=2021-01-01 23:59:59# 单一分片所能承载的最大时间,单位:秒,下面的数字表示1年spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.sharding-seconds='31536000'

需要注意,如果是基于时间段来分片,那么在查询的时候不能使用函数查询,否则会导致全路由。

1
sql复制代码select * from t_order where to_date(create,'yyyy-mm-dd')=''

标准分片算法

标准分片策略(StandardShardingStrategy),它只支持对单个分片健(字段)为依据的分库分表,Sharding-JDBC提供了两种算法实现

行表达式分片算法

类型:INLINE

使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持,只支持单分片键。 对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发,如: t_user_$->{u_id % 8} 表示 t_user 表根据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7

配置方法如下。

1
properties复制代码spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.type=INLINEspring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.props.algorithm-expression=ds-$->{user_id % 2}spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.type=INLINEspring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.props.algorithm-expression=t_order_$->{order_id % 2}

时间范围分片算法

和前面自动分片算法的自动时间段分片算法类似。

类型:INTERVAL

可配置属性:

属性名称 数据类型 说明 默认值
datetime-pattern String 分片键的时间戳格式,必须遵循 Java DateTimeFormatter 的格式。例如:yyyy-MM-dd HH:mm:ss -
datetime-lower String 时间分片下界值,格式与 datetime-pattern 定义的时间戳格式一致 -
datetime-upper (?) String 时间分片上界值,格式与 datetime-pattern 定义的时间戳格式一致 当前时间
sharding-suffix-pattern String 分片数据源或真实表的后缀格式,必须遵循 Java DateTimeFormatter 的格式,必须和 datetime-interval-unit 保持一致。例如:yyyyMM -
datetime-interval-amount (?) int 分片键时间间隔,超过该时间间隔将进入下一分片 1
datetime-interval-unit (?) String 分片键时间间隔单位,必须遵循 Java ChronoUnit 的枚举值。例如:MONTHS

复合分片算法

使用场景:SQL 语句中有>,>=, <=,<,=,IN 和 BETWEEN AND 等操作符,不同的是复合分片策略支持对多个分片健操作。

Sharding-JDBC内置了一种复合分片算法的实现。

类型: COMPLEX_INLINE,实现类:ComplexInlineShardingAlgorithm

属性名称 数据类型 说明 默认值
sharding-columns (?) String 分片列名称,多个列用逗号分隔。如不配置无法则不能校验 -
algorithm-expression String 分片算法的行表达式 -
allow-range-query-with-inline-sharding (?) boolean 是否允许范围查询。注意:范围查询会无视分片策略,进行全路由

目前版本还未发布(在github仓库中已经提供了实现),如果要实现符合分片算法,需要自己手动实现。

自定义分片算法

除了默认提供了分片算法之外,我们可以根据实际需求自定义分片算法,Sharding-JDBC同样提供了几种类型的扩展实现

  • 标准分片算法
  • 复合分片算法
  • Hinit分片策略
  • 不分片策略

分片策略的接口定义如下,它有四个子类,分别对应上面四种分片策略,我们可以通过继承不同的分片策略完成自定义分片策略的扩展。

1
java复制代码public interface ShardingStrategy {    Collection<String> getShardingColumns();    ShardingAlgorithm getShardingAlgorithm();    Collection<String> doSharding(Collection<String> var1, Collection<ShardingConditionValue> var2, ConfigurationProperties var3);}

image-20210722160919076

图7-13

自定义标准分片算法

1
java复制代码public class StandardModTableShardAlgorithm implements StandardShardingAlgorithm<Long> {    private Properties props=new Properties();    /**     * 用于处理=和IN的分片。     * @param collection 表示目标分片的集合     * @param preciseShardingValue 逻辑表相关信息     * @return     */    @Override    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {        for(String name:collection){            //根据order_id的值进行取模,得到一个目标值            if(name.endsWith(String.valueOf(preciseShardingValue.getValue()%4))){                return name;            }        }        throw new UnsupportedOperationException();    }    /**     * 用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理     * @param collection     * @param rangeShardingValue     * @return     */    @Override    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {        Collection<String> result=new LinkedHashSet<>(collection.size());        for(Long i=rangeShardingValue.getValueRange().lowerEndpoint();i<=rangeShardingValue.getValueRange().upperEndpoint();i++){            for(String name:collection){                if(name.endsWith(String.valueOf(i%4))){                    result.add(name);                }            }        }        return result;    }    /**     * 初始化对象的时候调用的方法     */    @Override    public void init() {    }    /**     * 对应分片算法(sharding-algorithms)的类型     * @return     */    @Override    public String getType() {        return "STANDARD_MOD";    }    @Override    public Properties getProps() {        return this.props;    }    /**     * 获取分片相关属性     * @param properties     */    @Override    public void setProps(Properties properties) {        this.props=properties;    }}

通过SPI机制进行扩展

  • 在resource目录下创建META-INF/service/org.apache.shardingsphere.sharding.spi.ShardingAlgorithm文件
  • 把自定义标准分片算法的全路径写如到上述文件中
1
txt复制代码  com.gupao.sharding.example.StandardModTableShardAlgorithm

增加application-custom-standard.properties文件

1
properties复制代码spring.shardingsphere.rules.sharding.tables.t_order_standard.actual-data-nodes=ds-0.t_order_standard_$->{0..3}spring.shardingsphere.rules.sharding.tables.t_order_standard.table-strategy.standard.sharding-column=order_idspring.shardingsphere.rules.sharding.tables.t_order_standard.table-strategy.standard.sharding-algorithm-name=standard-modspring.shardingsphere.rules.sharding.tables.t_order_standard.key-generate-strategy.column=order_idspring.shardingsphere.rules.sharding.tables.t_order_standard.key-generate-strategy.key-generator-name=snowflakespring.shardingsphere.rules.sharding.sharding-algorithms.standard-mod.type=STANDARD_MODspring.shardingsphere.rules.sharding.sharding-algorithms.standard-mod.props.algorithm-class-name=com.gupao.sharding.example.StandardModTableShardAlgorithm

其中,STANDARD_MOD是我们自定义的取模分片算法的类型。

表以及代码生成

把t_order表复制一张t_order_standard,通过mybatis-plus生成业务代码。

代码工程详见: sharding-jdbc-springboot-example

分布式序列算法

Sharding-JDBC中默认提供了两种分布式序列算法

  • UUID
  • 雪花算法

这两种在前面都说过,就不在重复说明。

分布式序列算法是为了保证水平分表之后,保证全局唯一性,关于雪花算法的定义如下。

类型:SNOWFLAKE

可配置属性:

属性名称 数据类型 说明 默认值
worker-id (?) long 工作机器唯一标识 0
max-vibration-offset (?) int 最大抖动上限值,范围[0, 4096)。注:若使用此算法生成值作分片值,建议配置此属性。此算法在不同毫秒内所生成的 key 取模 2^n (2^n一般为分库或分表数) 之后结果总为 0 或 1。为防止上述分片问题,建议将此属性值配置为 (2^n)-1 1
max-tolerate-time-difference-milliseconds (?) long 最大容忍时钟回退时间,单位:毫秒

本文转载自: 掘金

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

1…473474475…956

开发者博客

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