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

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


  • 首页

  • 归档

  • 搜索

Python hashlib模块 前言 1 hashlib

发表于 2021-11-03

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

前言

在互联网的时代,我们在网络中传输过程中都是要经过加密的,常见网络数据传输的加密方式有MD5和RSA算法。

当然,加密算法还有很多中按加密、解密方式不同分为:对称加密、非对称加密、hash加密。

其中MD5算法的基础是使用的hash算法,针对不同安全的hash安全算法,Python也提一个通用接口hashlib模块。

本期,我们来对Python 提供的安全哈希算法的通用接口-hashlib模块进行学习,Let’s go ~

  1. hashlib 模块概述

hashlib 是Python 内置用于对安全哈希和消息摘要的通用接口模块。hashlib 模块支持提供目前主流hash算法如MD5、SHA-1、SHA-2、SHA-256等通用构造方法,并且返回带有同样接口的hash对象。

  • hashlib 模块特点

+ 提供主流的hash算法操作,替代md5模块和sha模块
+ 模块调用简单,无需下载安装
+ 主要应用在文本加密场景如用户登录认证
  • hashlib 模块使用步骤

+ 导入hashlib库:import hashlib
+ 建立加密对象
+ 对字符串进行加密处理
+ 获取转换成新的N个Bit![](https://gitee.com/songjianzaina/juejin_p17/raw/master/img/1eaed52b43d8d535ec80ce1674e2074f304cb59d0b981602ccc975ab2268beeb)
  1. hashlib 工作原理

hashlib 模块中加密方式是我们常见的hash算法。hash算法又称为散列表(hash table),也叫做哈希表。

  • hash 算法特点

+ 具有唯一确定性,不同字符串加密后都是不一样的
+ 不可逆性,hash只有加密没有解密过程
+ 散列碰撞,输出和输入值不是一一对应关系
  • hash 构造方法

+ 直接定址方法


    - 公式一:hash(key) = key
    - 公式二:hash(key) = a\*key+b
    - 我们可以查看到直接定址方法具有线性的特点,因此适用在关键字分布连续的情况
+ 数学分析方法


    - 方式:提取关键字中随机性好的数字位,将其拼接为哈希地址
    - 适用场景:关键字已知的情况下,对关键字中每一位进行取值情况分析
+ 除留余数方法


    - 公式:hash(key) = key % p
    - 除数采用取余模运算
  • hash 算法过程

+ hash 函数接收到输入的字符串,首先会先进行预处理-哈希计算-输入摘要
+ 预处理:将字符串进行填充、分割N块,为hash进行初始化
+ 哈希计算:将预处理的数据完成指定算法生成消息摘要
+ 每指定一种hash算法只会生成固定长度的摘要,长度越长的安全性越高
![image.png](https://gitee.com/songjianzaina/juejin_p17/raw/master/img/751148b3557de0edf29446898d388a50b7100149d97d54bc8a04ca5708bb548b)
  • hash 常用算法

+ ### MD5


MD5:message-Digest Algorithm 5 信息-摘要算法5,该算法用于信息完整性进行校验


    - 计算方式:求余、取余、调整长度、进行链接变量循环运算求出结果
    - 用途:主要用于文件校验
+ ### SHA-1


SHA:secure Hash Algorithm 安全散列算法1,是一种密码散列算法,SHA-1可以生成摘要消息为40位的16进制即160位(20字节)的散列值


    - 用途:TSL、SSL、PGP、SSH等协议中广泛使用
  1. hashlib 属性方法

hashlib 模块相关属性

属性 作用
hashlib.algorithms_guaranteed 保证所有平台上都支持哈希算法的名称
hashlib.algorithms_available 保证平台上运行的Python解释器上可用的hash算法的名称

hashlib 构造对象相关的属性

属性 作用
hash.digest_size 以字节表示哈希对象的大小
hash.block_size 以字节表示哈希算法的内部块大小
hash.name 哈希对象的名称

hashlib 模块相关方法目前可以支持主流hash算法。

方法 作用
hashlib.pbkdf2_hmac(hash_name,password,salt,itera,dklen=None) PKCS#5基于密码的秘钥派生函数2,作为HMAC作为伪随机函数
hashlib.scrypt(password,*,salt) 基于密码加密的秘钥派生函数
hashlib.md5() md5加密方式
hashlib.sha1 sha1加密方式
hashlib.sha256 sha256加密方式
hashlib.blake2b () blake2b加密方式
hashlib.blake2s black2s加密方式

hashlib 构造对象相关的方法

方法 作用
hash.update(data) 以字节表示哈希对象
hash.digest() 返回当前已传给update()方法的数据摘要
hash.hexdigest() 以16进制的字符串表示哈希数据值
hash.copy() 将hash对象复制,共享初始数据的摘要
  1. 小试牛刀

我们学习hashlib模块中对字符串进行hash算法处理,我们来实操一下吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码import hashlib

text_md5 = hashlib.md5()
text_md5.update(bytes("hello juejing",encoding="utf-8"))
print("md5:",text_md5.hexdigest())

text_sha1 = hashlib.sha1()
text_sha1.update(bytes("hello juejing",encoding="utf-8"))
print("sha1:",text_sha1.hexdigest())

text_sha256 = hashlib.sha3_256()
text_sha256.update(bytes("hello juejing",encoding="utf-8"))
print("sha256:",text_sha256.hexdigest())

text_crc32 = hashlib.()
text_sha256.update(bytes("hello juejing",encoding="utf-8"))
print("sha256:",text_sha256.hexdigest())

  • 💬 重要说明

+ 添加自定义key与字符串进行组合加密(加盐)
+ 以MD5加密为例如下
    - 加盐写法一:
1
2
python复制代码text_md5 = hashlib.md5(b"key")
text_md5.update("字符串".encode("utf-8"))
- 加盐写法二:
1
2
3
4
python复制代码key = "字符串"
yan = "字符串2"
text_md5 = hashlib.md5()
text_md5.upadte((key+yan).encode("utf-8"))

总结

本期,我们对hashlib模块中hash算法特点、hashlib模块相关支出算法的方法进行学习和实操。

在hash算法中我们常用使用md5/sha1/sha256主要用于文本校验、用户登录认证等数据校验

同时,hash算法确保数据的完整性,具有不可逆性,同时它会遭遇到暴力碰撞,因此hash长度越长的安全性也越高。

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

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

【Spring Boot 快速入门】十四、Spring Bo

发表于 2021-11-03

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」

前言

  嗨,大家好,相信大家在开发过程中经常编写后台接口,在前端调用后台接口的时候,需要对前端输入的参数进行合法性和有效性进行校验。有多种方式针对数据进行校验。本次给大家介绍一下基于@Valid注解的参数合法性校验。希望可以帮助到大家。下面开始上正题。

初始

  @Valid注解校验是基于标准JSR-303规范开发的。@Valid注解可以用在类的方法、构造函数、方法参数和成员属性(字段)上面。当请求参数输入后,会对参数的合法性进行校验,如果参数异常,则直接会返回异常的message提示信息。
  当然,在项目中使用@Valid注解,需要引入相关的依赖。本次根据Spring Boot的版本,引入如下pom依赖文件。大家可以根据自己使用的Spring Boot 版本引入相应的依赖文件即可。
Spring Boot版本如下:

1
js复制代码<version>2.3.0.RELEASE</version>
1
2
3
4
5
js复制代码      <dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>

快速开始

  相信大家已经引入了响应的依赖,下面开始针对的注解进行介绍。

定义接口

   如下是一个普通的接口,其中请求方式是:POST。在请求参数中,使用@Valid对参数体OrderDto进行了注释。当OrderDto作为参数进行访问时,会自动根据OrderDto中属性的注解进行校验。如果不满足条件,直接返回message异常信息。

1
2
3
4
5
6
7
8
9
10
11
js复制代码 @PostMapping("/V1/save")
public String addInfo(HttpServletRequest request, @Valid @RequestBody OrderDto orderDto) {

log.info("信息新增接口,请求参数是:{}", JSON.toJSONString(orderDto));
try {
return JSON.toJSONString(orderDto);
} catch (Exception e) {
log.error("信息新增接口,请求参数是:{},异常原因是:", JSON.toJSONString(orderDto), e);
return "推送接口失败。失败原因为:" + e.getMessage();
}
}

@Null

介绍:带@Null注释的元素必须是null。

参数类型:接受任何类型参数。

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Null(message = "id必须为空")
private String id;

@NotNull

介绍:带@NotNull注释的元素必须不为null。

参数类型:接受任何类型参数。

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @NotNull(message = "姓名不能为空")
private String name;

@AssertFalse

介绍:带AssertFalse注释的元素必须为false

参数类型:支持的类型为:boolean 和 Boolean

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @AssertFalse
private boolean bf;

@AssertTrue

介绍:带@AssertTrue注释的元素必须为true。

参数类型:支持的类型为:boolean 和 Boolean

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @AssertTrue
private boolean bt;

@AssertTrue

介绍:带@AssertTrue注释的元素必须为true。

参数类型:支持的类型为:boolean 和 Boolean

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @AssertTrue
private boolean bt;

@AssertTrue

介绍:带@AssertTrue注释的元素必须为true。

参数类型:支持的类型为:boolean 和 Boolean

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @AssertTrue
private boolean bt;

@DecimalMax

介绍:带@DecimalMax注释的元素的值必须小于或等于的数字等于指定的最大值。。

参数类型:BigDecimal BigInteger CharSequence及基础类型:byte short int long 和这些基础类型的包装类型

不支持参数类型:由于四舍五入错误,不支持double和float

使用地方:方法、属性、注释类型、构造函数、参数

null值被认为是有效的。

value : 元素的值必须小于或等于

inclusive:指定的最大值是否包含。默认情况下,它是包含的。

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @DecimalMax(value = "100.00")
private BigDecimal bdMax;

@DecimalMin

介绍:带@DecimalMin注释的元素的值必须大于或等于的数字等于指定的最小值。。

参数类型:BigDecimal BigInteger CharSequence及基础类型:byte short int long 和这些基础类型的包装类型

不支持参数类型:由于四舍五入错误,不支持double和float

使用地方:方法、属性、注释类型、构造函数、参数

null值被认为是有效的。

value : 元素的值必须大于或等于

inclusive:指定的最小值是否包含。默认情况下,它是包含的。

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @DecimalMin(value = "60.00")
private BigDecimal dmMin;

@Digits

介绍:@Digits限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction,带注释的元素必须是可接受范围内的数字。

参数类型:BigDecimal BigInteger CharSequence及基础类型:byte short int long 和这些基础类型的包装类型

null值被认为是有效的。

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码   @Digits(integer = 100,fraction = 100)
private double aDouble;

@Future

介绍:带@Future注释的元素必须是将来的一瞬间、日期或时间。

参数类型:Date; Calendar ;Instant; LocalDate; LocalDateTime; LocalTime ;MonthDay ;OffsetDateTime ;OffsetTime ;Year ;YearMonth;
ZonedDateTime; HijrahDate ;JapaneseDate; MinguoDate; ThaiBuddhistDate;

使用地方:方法、属性、注释类型、构造函数、参数

null值被认为是有效的。

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Future
private Date futureDate;

@Max

介绍:带@Max注释的元素值必须小于或者等于指定的最大值。

参数类型:BigDecimal BigInteger byte short int long 和这些基础类型的包装类型

不支持参数类型:不支持double和float

null值被认为是有效的。

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Max(value = 1000)
private Long maxInfo;

@Min

介绍:带@Min注释的元素值必须大于或者等于指定的最小值。

参数类型:BigDecimal BigInteger byte short int long 和这些基础类型的包装类型

不支持参数类型:不支持double和float

null值被认为是有效的。

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Min(value = 1)
private Long minInfo;

@Past

介绍:带@Past注释的元素必须是过去的瞬间、日期或时间。现在默认定义为虚拟机的当前时间。

参数类型:Date; Calendar ;Instant; LocalDate; LocalDateTime; LocalTime ;MonthDay ;OffsetDateTime ;OffsetTime ;Year ;YearMonth;
ZonedDateTime; HijrahDate ;JapaneseDate; MinguoDate; ThaiBuddhistDate;

使用地方:方法、属性、注释类型、构造函数、参数

null值被认为是有效的。

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Past
private Date pastDate;

@Pattern

介绍:带@Pattern注释的必须与指定的正则表达式匹配。

参数类型:正则表达式遵循Java正则表达式约定

使用地方:方法、属性、注释类型、构造函数、参数

regexp:正则表达式

flags:正则表达式是否启用的枚举数组

message:自定义返回的异常信息。

示例:

1
2
3
js复制代码     @Pattern(regexp = "^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$",
flags = Pattern.Flag.CASE_INSENSITIVE)
private String patternInfo;

@Size

介绍:带@Size注释的元素大小必须介于指定的边界(包括)之间。

参数类型:CharSequence 计算字符序列的长度;
Collection 计算集合大小;
Map 映射大小已计算;
Array 计算数组长度;

null 元素被认为是有效的;
min 最小值,默认为0;
max 最大值,默认为 0x7fffffff;

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Size(min = 0,max = 100)
private int sizeInfo;

@NotEmpty

介绍:带@NotEmpty注释的元素不能为 null或为空

参数类型:CharSequence 计算字符序列的长度;
Collection 计算集合大小;
Map 映射大小已计算;
Array 计算数组长度;

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @NotEmpty
private String phone;

@NotBlank

介绍:验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格。带注释的元素不能是null,并且必须至少包含一个非空白字符。

参数类型:Object

使用地方:方法、属性、注释类型、构造函数、参数

message:自定义返回的异常信息。

示例:

1
2
js复制代码   @NotBlank
private String nickName;

@Email

介绍:字符串必须是格式正确的电子邮件地址。构成有效数据的确切语义

参数类型:基础类型 CharSequence

使用地方:方法、属性、注释类型、构造函数、参数

null 元素被认为是有效的。

regexp:正则表达式

flags:结合使用以指定正则表达式

message:自定义返回的异常信息。

示例:

1
2
js复制代码    @Email
private String email;

结语

  以上就是Spring Boot集成@Valid注解的详细介绍,大家也可以查询接口文档去了解一下。

  好了,感谢您的阅读,希望您喜欢,如对您有帮助,欢迎点赞收藏。如有不足之处,欢迎评论指正。下次见。

  作者介绍:【小阿杰】一个爱鼓捣的程序猿,JAVA开发者和爱好者。公众号【Java全栈架构师】维护者,欢迎关注阅读交流。

本文转载自: 掘金

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

HashMap-数据结构&哈希算法

发表于 2021-11-03
2021年10月30日\color{#708090}{2021年10月30日}2021年10月30日 前言 -- 本文主要通过以下三部分来阐述HashMap的底层实现原理: > 1. 数据结构 & 哈希算法 > 2. 性能参数 & 扩容机制 > 3. 快速存取 & 时间复杂度 情景引入 ---- 工作的时候遇到了一个问题,同步过去一个商品集合,商品对象A包含了model和quantity,但是返回给我的集合对象B里只有model,没有quantity。我该怎么给返回来的对象B添加上型号对应的数量? 两种思路: 1. 嵌套循环 undefined 此方法,假设外层循环执行n次,内层循环执行m次,时间复杂度为O(n\*m)。 2. 使用HashMap undefined 此方法,List转Map时间复杂度为O(n),HashMap的get()方法时间复杂度是多少呢? 数据结构&哈希算法 --------- 1.数据结构\color{#A0522D}{1.数据结构}1.数据结构 > **JDK1.7及之前:数组+链表** > **JDK1.8及之后:数组+链表+红黑树** * **数组**:是一种**线性表**数据结构。它用一组**连续的**内存空间,来存储一组具有**相同类型**的数据。支持**随机访问**,计算元素的寻址地址: undefined * **链表**:和数组一样,也是一种**线性表**。是**不连续的**内存空间,将一组零散的内存块串联起来,从而进行数据存储的数据结构。 * **哈希表**:综合了两种特性,是一种寻址比较高效、插入删除也高效的数据结构。哈希表有不同的实现方法,其中最常用的一种方法——拉链法,可以理解为“链表的数组”,如图: ![image.png](https://gitee.com/songjianzaina/juejin_p9/raw/master/img/26b78c9e2c97ce3dfb746e9fcb4ead20bfcdb8378ec4bf9f37ba74ea692611c8) 从上图我们可以发现哈希表是由**数组+链表**组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢?一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。 * **红黑树**:当链表的长度过长时,其固有弊端就显现出来了,即**查询效率较低**,时间复杂度可能会达到O(n)级别,而数组的查询时间复杂度仅为O(1)。红黑树是一棵接近于**平衡的二叉查找树**,其查询时间复杂度为**O(logn)**,远远比链表的查询效率高。但如果链表长度不到一定的**阈值**,直接使用红黑树代替链表也是不行的,因为红黑树的自身维护的代价也是比较高的,每插入一个元素都可能打破红黑树的平衡性,这就需要每时每刻对红黑树再平衡(左旋、右旋、重新着色)。 **HashMap**是Java中用**哈希表**数据结构实现的**Map**。 2.Hash算法(通俗讲,将key值转化为数组下表)\color{#A0522D}{2.Hash算法(通俗讲,将key值转化为数组下表)}2.Hash算法(通俗讲,将key值转化为数组下表) 1. JDK 1.8中,hash方法如下: undefined 获取对象的hashCode()值,然后将hashCode值右移16位,然后将右移后的值与原来的hashCode做**异或**运算,返回结果。举个例子: undefined 2. 计算数组下标 通过hash方法计算得到Key的hash值后,怎么才能保证元素均匀分布到每个桶中呢?我们会想到取模,hash%len。但其实HashMap是通过**hash&(n-1)**计算index的。**因为桶数组的长度总是2的n次幂**,所以**hash&(n-1)**就**相当于**对hash对len取模,而**位运算**要比**取模运算**快得多,即两者是等价不等效的,这是HashMap在效率上的一个优化。 undefined > **为什么计算出key的hashCode后,还要位移16位再跟自身做异或运算?** 对于初始容量1<<<4来说,如果直接用key的hashcode和数组容量做&运算,会导致只有前四位进行运算,高位没有进行运算。因为容量的高位都是0,而key的hashcode的高位都是有值的。所以为了避免key的高位的**信息丢失**,首先将高十六位右移16位与低十六做异或运算,这样混合得到的新的数值中,高位和低位的信息都保留了。这样做的目的是为了使得高位也能参与哈希,从而进一步降低**hash碰撞**的概率。下面举例说明: ![image.png](https: gitee.com songjianzaina juejin_p9 raw master img dcc66bcb67b478a72f855f407e162c458000ccd4ae66ff2c5f9ec9f9bc0df955)> **为什么计算出来hash后,还需要做取模运算?** > 为了让HashMap存取更加高效,需要尽量减少碰撞,也就是要尽量把数据分配均匀。Hash值的范围前后加起来大概40亿的映射空间,只要哈希算法映射得足够均匀,一般很难出现碰撞,问题是一个40亿长度的数组,内存是放不下的,所以hash值不能直接拿来用,用之前还要对数组长度做取模运算,得到的余数才是对应的数组的下标。这个数组下标的计算方法就是**hash&(n-1)** > **为什么HashMap的数组长度一定是2的幂次方?** > 1.使用位运算来实现取模运算\color{#A0522D}{1.使用位运算来实现取模运算}1.使用位运算来实现取模运算 **当 array.ength长度是2的次幂时,** `key.hashcode % array.length`**等于**`key.hashcode & (array.length - 1)`。 X % 2^n = X & (2^n – 1) 假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 - 1 = 7, 表示成2进制是0111。 此时X & (2^3 - 1) 就相当于取X的二进制的最后三位数。为什么,因为&运算规则是:两者相同是1得1;否则得0。因为7的最后三位是1,所以最后三位数的值由X决定,X是1,则为1,X是0,则为0。 ![image.png](https://gitee.com/songjianzaina/juejin_p9/raw/master/img/59a1e3f2af7b388302060cb31237c6762eb8d788851459175fa1d1ff528e99ae) 从2进制角度来看,X / 8 相当于X >> 3, 即把X右移3位,此时得到的是 X / 8 的商,而被移掉的部分(后3位),则是 X % 8,也就是余数。 所以,只要保证数组的长度是2^n的话,就可以实现用&来代替%。 2.减少哈希碰撞,提高效率\color{#A0522D}{2.减少哈希碰撞,提高效率}2.减少哈希碰撞,提高效率 基于hash & (n - 1)的策略,假设hash从0-15。 先来看length为奇数的情况,假设length = 15的情况: ![image.png](https://gitee.com/songjianzaina/juejin_p9/raw/master/img/233554259066e56ecec75c770ef63e06fc5cf63865efcd880a96ced38246d17a) 从上面的图表中我们可以看到,当 length 为15时总共发生了8次碰撞,同时发现空间浪费非常大,因为在 1、3、5、7、9、11、13、15 这八处没有存放数据。 这是因为hash值在与14(即 1110)进行&运算时,得到的结果最后一位永远都是0,那么最后一位为1的位置即 0001、0011、0101、0111、1001、1011、1101、1111位置处是不可能存储数据的。这样,空间的减少会导致碰撞几率的进一步增加,从而就会导致查询速度慢。 再来看length为偶数的情况,假设length = 16的情况: ![image.png](https://gitee.com/songjianzaina/juejin_p9/raw/master/img/6c8a9fa78b3f89a54140ad50b24616af6b737c3b698def7e1e1f0c2cfc7cd36e) 当length为16时,length – 1 = 15, 即 1111,那么,在进行&运算时,值总是与原来hash值相同。所以,当 length=2^n 时,不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,查询速度也较快。 多试一些数,就可以发现规律:当length为奇数时,length-1为偶数,而偶数二进制的最后一位永远为0,那么与其进行 & 运算,得到的二进制数最后一位永远为0,那么结果一定是偶数,那么就会导致下标为奇数的桶永远不会放置数据,这就不符合我们均匀放置,减少冲突的要求了。 然而,保证length是偶数还不够,为什么一定是2^n,这就又回到第一点的原因了。 性能参数&扩容机制 --------- 1.HashMap在JDK中的定义\color{#A0522D}{1.HashMap在JDK中的定义}1.HashMap在JDK中的定义 undefined 2.重要的类的属性\color{#A0522D}{2.重要的类的属性}2.重要的类的属性 undefined 3.HashMap的构造函数\color{#A0522D}{3.HashMap的构造函数}3.HashMap的构造函数 **1. HashMap(int, float)** undefined *tableSizeFor(initialCapacity)* 方法返回大于initialCapacity的最小的二次幂数值。 undefined **2. HashMap(int)** undefined **3. HashMap()** undefined **4. HashMap(Map)** undefined `未完待续...`

本文转载自: 掘金

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

实战小技巧20:jar包执行传参使用小结 jar包执行时传参

发表于 2021-11-03

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」。

jar包执行时传参的使用姿势

虽说我们现在大多不太直接使用jar包运行方式,目前比较主流的是将自己的服务丢在某个容器中(如tomcat,jetty等)运行,比如我之前所属的电商公司,就是将项目打包为war包,丢到tomcat容器中运行的

在使用SpringBoot时,可能会出现直接打包一个可执行的jar,然后运行,这种时候,通过java命令执行时,时可以传参的,那么问题来了,main方法可以如何优雅的解析这些传参呢?

I. 简陋版本

最容易想到的,无非是自己直接解析main方法的传参,如我们知道的main方法的一般写法为

1
2
java复制代码public static void main(String[] args) {
}

看到上面的写法,很容易就可以猜到,传入的参数最终都放到了args数组中,那么该怎么用就怎么用,一个hello world的实例如下

1
2
3
java复制代码public static void main(String[] args) {
System.out.println("hello " + args[0]);
}

测试如下:

image.png

看到这里,真心感觉没有什么干货,上面这些过于小白了吧,估计连入门都算不上,那么参数处理仅止于此么?

II. 进阶版本

玩过shell的同学应该都知道man命令,可以用来查看很多shell命令的帮助,里面介绍了很多的shell命令的参数说明,而且这些参数一般有缩写和全拼,而且有些参数可以带传值,有些并不需要,可以说shell命令的传参方式,已经拥有自己独立的一套规范了,而且用起来非常的爽

那么我们的jar包,能否支持这种传参方式呢?

举一个简单的例子,上面的HelloWord接收一个简单用户名参数

  • 不传入时,默认输出 hello world
  • 短参方式: -n xxx
  • 长参方式: --name=xxx

仅仅支持这一个场景,需要自己来解析的话,就得写一长串的代码,好在这种需求已经有轮子了

1. commons-cli

首先引入依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
</dependency>

开始使用,官网已经给出了例子,完整的doc可以参考

  • commons-cli Usage Scenarios

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
java复制代码@Slf4j
public class AppLaunch {
private static final String SOURCE_PATH = "./task-core/src/test/java/com/git/hui/task";
private static final String TASK_ARG_LONG = "task";
private static final String TASK_ARG_SHORT = "t";
private static final String ARG_HELP_LONG = "help";
private static final String ARG_HELP_SHORT = "h";
private static volatile boolean run = true;


private static void printHelp() {
Options options = buildOptions();
HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.printHelp("java -jar ${jar} [options]", options);
}

private static Options buildOptions() {
Options options = new Options();
options.addOption(
Option.builder(TASK_ARG_SHORT).argName(TASK_ARG_LONG).hasArg().longOpt(TASK_ARG_LONG).required(false)
.desc("choose task path, default [" + SOURCE_PATH + "]").build());
options.addOption(Option.builder(ARG_HELP_SHORT).longOpt(ARG_HELP_LONG).desc("show command help").build());
return options;
}

private static CommandLine parseArguments(String[] arguments) {
Options options = buildOptions();
CommandLine commandLine = null;
try {
commandLine = new DefaultParser().parse(options, arguments);
} catch (ParseException e) {
e.printStackTrace();
System.exit(1);
}

if (commandLine.hasOption(ARG_HELP_LONG)) {
printHelp();
System.exit(0);
}
return commandLine;
}


public static void main(String[] args) throws InterruptedException {
CommandLine commandLine = parseArguments(args);
String scriptSource = commandLine.getOptionValue(TASK_ARG_LONG, SOURCE_PATH);
System.out.println("script source: {}" + scriptSource);
// ....
}
}

对上面的使用姿势进行简单的说明,从逻辑上划分,可以分为下面几块

  • 定义传参,包括参数说明,缩写和全拼,是否有参数值,描述等
  • 解析传参数组,将具体的传参解析为CommandLine对象
  • 获取参数,执行相应的业务逻辑

从源码角度来看,没什么复杂或者难以理解的地方,稍稍提一点,参数的定义,即buildOption方法中,上面指定了两个参数 help, task, 其中一个要求有参数值,一个不需要参数值,下面实际演示如下

show

III. 其他

0. 相关信息

  • 文档: commons-cli 使用手册
  • 实际项目:github.com/liuyueyi/qu…

1.一灰灰的联系方式

尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 个人站点:blog.hhui.top
  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840
  • 微信公众号:一灰灰blog

本文转载自: 掘金

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

设计模式 -- 工厂模式

发表于 2021-11-03

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」

工厂模式主要有三种,先介绍一下简单工厂模式,顾名思义比较简单

含义: 利用单独的类来进行创造实例的过程,定义⼀个创建对象的接⼝,让接⼝的实现类决定创建哪种对象,让类的实例化推迟到⼦类中进行。

就像工厂一样,在工厂先建好机器,然后等待一系列物品进行生产,就拿咖啡举例吧

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
typescript复制代码//首先创建一个咖啡的规范类
public interface Coffee{
   public String getName();
}
​
//然后定义工厂中生产的咖啡种类
public Americano interface Coffee{
   @Override
   public String getName(){
       return "Americano";
  }
}
​
public Latte interface Coffee{
   @Override
   public String getName(){
       return "Latte";
  }
}
   
public MoCha interface Coffee{
   @Override
   public String getName(){
       return "MoCha";
  }
}
​
//然后建立创建咖啡的工厂
public class CoffeeFactory{
   public Coffee createCoffee(String coffeeType){
       if (coffeeType.equals("Americano")){
           return new Americano();
      }else if (coffeeType.equals("Latte")){
           return new Latte();
      }else if(coffeeType.equals("MoCha"){
           return new MoCha();
      }
       return null;
  }
}
               
//现在我想喝咖啡了,我只需要告诉工厂我想喝什么,并不需要知道它的生产过程。
public static void main(String[] args){
   CoffeeFactory coffeeFactory = new CoffeeFactory();
   coffeeFactory.createCoffee("Americano");
}

接下来了解一下他的优缺点吧,优点显而易见,当想要创建对象时候,仅仅知道它的名称就可以了,并且屏蔽具体的实现过程,很好的体现了封装性;但是现在如果有很多种类咖啡的话,那么就要在工厂中增加大量的条件判断,不符合开闭原则,会使工厂类变得非常繁琐,也不符合单一职责原则,这就是它的缺点。

为了应对简单工厂模式的缺点,我们可以把生产各个产品的工厂也做抽象分离,即每个产品都有自己专门的工厂,顾名思义它叫工厂方法模式

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
typescript复制代码//首先创建一个咖啡工厂的规范类
public interface CoffeeFactory{
   public Coffee createCoffee();
}
​
public class AmericanoFactory implements CoffeeFactory{
   @Override
   public Coffee createCoffee(){
       return new Americano();
  }
}
​
public class LatteFactory implements CoffeeFactory{
   @Override
   public Coffee createCoffee(){
       return new Latte();
  }
}
​
public class MoChaFactory implements CoffeeFactory{
   @Override
   public Coffee createCoffee(){
       return new MoCha();
  }
}
​
public static void main(String[] args){
   AmericanoFactory americanoFactory = new AmericanoFactory();
   AmericanoFactory.createCoffee();
}

这样一来,当我们想要新的种类的时候,只需要新建工厂就行了,同时能够满足开闭原则和单一职责原则。但是它虽然进一步优化了简单工厂模式,可它仍然需要创建大量的类,增加函数的复杂度。

可以看出上述两种模式都是对同一种类的物品进行生产的工厂,那么我们要生产汽车,衣服怎么办呢?总不能还用咖啡的工厂来生产吧。就算像奶茶一样有相似之处,复制修改相同的代码虽然也是可以,但是如果后期维护和扩展功能你就知道这种做法是多么愚蠢了。因此就发明出了随时改变的抽象工厂模式。

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
scala复制代码//首先创建抽象工厂
abstract class AbstractFactory{
   public abstract void createProduct(String product);
}
​
//奶茶工厂
class BeerFactory extends AbstractFactory{
   @Override
   public void createProduct(String product){
       switch(product){
           case "Bubble Tea":
               return new BubbleTea();
               break;
           case "milk":
               return new Milk();
               break;
           default:
               return new othersTea();
               break;
      }
       return null;
  }
}
​
//咖啡工厂
class CoffeeFactory extends AbstractFactory{
   @Override
   public void createProduct(String product){
       switch(product){
           case "Americano":
               return new Americano();
               break;
           case "Latte":
               return new Latte();
               break;
           default:
               return new othersCoffee();
               break;
      }
       return null;
  }
}
​
public class AbstractFactoryTest{
   public static void main(String[] args) {
      // 抽象工厂
      new CoffeeFactory()).createProduct("Latte");
  }
}

虽然完善了简单工厂模式和工厂方法模式不方便扩展产品族,并且增加了系统的抽象性和理解难度。

虽然看起来这三种工厂模式越来越好,但是并没有所谓的高级模式,只有适合使用的场景才是最好的,大家应该按照自己的需求找到最合适的模式。

本文转载自: 掘金

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

SpringBoot 过期属性报错问题分析及排查

发表于 2021-11-03

这是我参与11月更文挑战的第 3 天,活动详情查看:2021最后一次更文挑战

背景

近期发布项目时经常出现一个问题,大体报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码The use of configuration keys that are no longer supported was found in the environment:

Property source 'systemEnvironment':
Key: spring.cloud.nacos.config.shared-dataids
Reason: replaced to NacosConfigProperties#sharedConfigs and not use it at the same time.

Property source 'bootstrapProperties-login-service.yaml,login-service':
Key: spring.mvc.favicon.enabled
Reason: none

Property source 'bootstrapProperties-application.yaml,DEFAULT_GROUP':
Key: server.connection-timeout
Reason: Each server behaves differently.


Please refer to the release notes or reference guide for potential alternatives.

从日志报错有一个直观的判断,就是这些被抛出来的 key 是不被支持的(过期或者已经移除的)。这里先给出项目的基本信息:

  • 基于 spring boot 2.4.2 +
  • 配置中心使用的是 nacos ( cloud alibaba nacos)

此外,这个报错不会导致我项目启动直接阻断,而且存在一定的随机性,并不是每次都会出现。针对这个报错,有两个问题:

  • 1、这个报错产生的具体原因是什么?
  • 2、为什么会有随机性,不是必现

下面是分析及排查的过程。

分析及排查过程

首先明确一点,任何报错日志,都会有其抛出堆栈或者日志的地方;找到它就可以现确定它是那个组件或者框架产生的,对于一些简单的异常,通过阅读源码是可以直接发现问题原因的,对于复杂的异常情况,可以通过简化项目结构进行场景复现,通过 debug 找到问题原因。

报错日志产生原因

针对上面的error 日志,现进行过滤:

  • source 'xxxx' :可以忽略,这里指的是下面 key 的 source。
  • key:配置的 key ,可以缩小我们的排查范围
  • Reason:产生原因

此外如上所述,找到异常产生的源码 org.springframework.boot.context.properties.migrator.PropertiesMigrationReport#getErrorReport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* Return a report for all the properties that are no longer supported. If no such
* properties were found, return {@code null}.
* @return a report with the configurations keys that are no longer supported
*/
String getErrorReport() {
Map<String, List<PropertyMigration>> content = getContent(LegacyProperties::getUnsupported);
if (content.isEmpty()) {
return null;
}
StringBuilder report = new StringBuilder();
report.append(String.format(
"%nThe use of configuration keys that are no longer supported was found in the environment:%n%n"));
append(report, content);
report.append(String.format("%n"));
report.append("Please refer to the release notes or reference guide for potential alternatives.");
report.append(String.format("%n"));
return report.toString();
}

通过分析执行堆栈发现,这里被执行到,在 springboot 中并不是通过 ApplicationFailsEvent 事件发布出去,而是通过 ApplicationReadyEvent 事件,所以这里就解释了为什么这个报错不会阻断程序运行的原因。

这里先给出一个小结论:报错的key 在相关的 ConfigProperties 中都是被标记 @Deprecated 或者 @Deprecated 和 @DeprecatedConfigurationProperty 注解的。

这里衍生一个新问题,被打了 @Deprecated / @DeprecatedConfigurationProperty 注解的这些配置属性和被 report 之间的关系是逻辑是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码// filter 从上层过来条件是 ConfigurationMetadataProperty::isDeprecated ,即过期的
private Map<String, List<PropertyMigration>> getMatchingProperties(
Predicate<ConfigurationMetadataProperty> filter) {
MultiValueMap<String, PropertyMigration> result = new LinkedMultiValueMap<>();
// 从 allProperties 中筛选出所有过期的配置
List<ConfigurationMetadataProperty> candidates = this.allProperties.values().stream().filter(filter)
.collect(Collectors.toList());
// 从当前项目配置中去匹配,如果有匹配到的 key ,则放到 result 中
getPropertySourcesAsMap().forEach((name, source) -> candidates.forEach((metadata) -> {
ConfigurationProperty configurationProperty = source
.getConfigurationProperty(ConfigurationPropertyName.of(metadata.getId()));
if (configurationProperty != null) {
result.add(name,
new PropertyMigration(configurationProperty, metadata, determineReplacementMetadata(metadata)));
}
}));
return result;
}

上面这段代码的核心就是,先将当前 class path 下所有的 properties 项枚举出来,然后和当前项目中的配置进行比对,大体策略如下:

假设工程所有可提供的 ConfigProperties 的配置项是 :a.b.c a.b.b a.b.d a.b.e a.b.f ,启动 a.b.d a.b.e a.b.f 是过期的,所以第一遍拿到的 candidates 中就包括 a.b.d a.b.e a.b.f 三项。

此时项目中配置了a.b.d a.b.e 两项,所以在 result 中就是 a.b.d a.b.e 两个 item,这两个 item 会在 error 日志中体现出来。

关于 ConfigurationMetadataProperty

对于 springboot 工程的 properties 文件,在使用时会有自动补全的机制,这主要依赖于 spring-boot-configuration-processor , spring-boot-configuration-processor 还有一个作用是,如果你的项目中引入了它,在 build 之后,会产生一个 json 文件:

以为例 spring.mvc.favicon.enabled 为例:

1
2
3
4
5
6
7
8
9
json复制代码{
"name": "spring.mvc.favicon.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable resolution of favicon.ico.",
"deprecated": true,
"deprecation": {
"level": "error"
}
},

原始代码如下:

1
2
3
4
5
6
java复制代码@DeprecatedConfigurationProperty(
reason = "Use of path extensions for request mapping and for content negotiation is discouraged.")
@Deprecated
public boolean isFavorPathExtension() {
return this.favorPathExtension;
}

偶现的原因

这里其实在一开始大概就能猜到和依赖版本有关,这里主要取决于 maven 编译构建机制,maven 在构建 fatjar 时,依赖的引入和 jar 版本生效机制是个复杂的问题,这里没有直接去关注 maven 相关的东西,而是通过选择降低版本、高低版本同时存在两个场景从测试验证了猜想。

PS:依赖管理对于任何公司、任何工程、任何项目来说,都是头疼的问题。

解决方案

当明确问题产生的具体原因之后,解决它就是非常容易的事情了。这里有两个考虑点,第一这个报错不会影响项目的启动,是否有必要关注它?如果你不确定,那么你尽量不要动。第二在你非常清楚这个配置项并且确定项目中不使用它或者你知道它的替代方案时,那么请直接将这个配置项干掉或者替换它。

在我的工程中,是选择直接删除掉的,属于历史遗留问题

关于依赖问题,请不要随意指定任何依赖的版本,项目中已有 >> dependencyManager 管控版本 >> dependency 指定版本。

本文转载自: 掘金

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

Java复制 Java随笔记

发表于 2021-11-03

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」


相关文章

Java随笔记:随笔记


前言

  • 将一个对象的引用复制给另外一个对象,一共有三种方式。
+ 直接赋值
+ 浅拷贝
+ 深拷贝
+ 这三种概念实际上都是为了拷贝对象
  • 那么这三种拷贝的区别是什么?

一、直接赋值

  • 在 Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。
  • 感觉这个没啥好说的。。不做案例分析。

二、浅拷贝

  • 创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typescript复制代码class Resume implements Cloneable {
       public Object clone() {
           try {
               return (Resume) super.clone();
          } catch (Exception e) {
               e.printStackTrace();
               return null;
          }
      }
    }
  • 自己可以实现Cloneable接口。点进去看下这个super.clone();是啥玩意。

  • image-20211103193352225.png

  • 原来用的是native方法。

+ native方法大家都知道,这玩意不是java写的,只是供Java调用而已。
+ 因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
  • 因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
  • 要想对一个对象进行复制,就需要对clone方法覆盖。

三、深拷贝

  • 深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
  • 如果我们一个对象里面的属性是另一个对象怎么办?
+ 
1
2
3
4
5
6
arduino复制代码@Data
public class teacher {
   private String name;
   private int age;
   private Student student;
}
+
1
2
3
4
5
java复制代码@Data
class Student implements Cloneable {
   private String name;
   private int age;
}
  • 这种情况下如何进行复制?其实通过重写clone()方法也可以做到深度克隆,但是较麻烦。在此不做介绍。
  • 通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
+ 序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。
+ 通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象。
+ 因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    java复制代码@Data
    public class Teacher implements Serializable {
       private String name;
       private int age;
       private Student student;
    ​
       public Teacher(String name, int age, Student student) {
           this.name = name;
           this.age = age;
           this.student = student;
      }
    ​
       // 深克隆
       public Object deepClone() throws IOException, ClassNotFoundException {
           // 序列化
           ByteArrayOutputStream bos = new ByteArrayOutputStream();
           ObjectOutputStream oos = new ObjectOutputStream(bos);
           oos.writeObject(this);
           // 反序列化
           ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
           ObjectInputStream ois = new ObjectInputStream(bis);
           return ois.readObject();
      }
    }
  • 1
    2
    3
    4
    5
    6
    less复制代码@Data
    @AllArgsConstructor
    public class Student implements Serializable {
       private String name;
       private int age;
    }
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    java复制代码public class test {
       public static void main(String[] args) {
           try {
               Student s = new Student("大鱼", 24);
               Teacher origin = new Teacher("丁老师", 32, s);
               System.out.println("克隆前的学生姓名:" + origin.getStudent().getName());
               Teacher clone = (Teacher) origin.deepClone();
               // 更改克隆后的d学生信息 更改了姓名
               clone.getStudent().setName("我是克隆对象更改后的大鱼2");
               System.out.println("克隆后的学生姓名:" + clone.getStudent().getName());
          }catch (Exception e){
               e.printStackTrace();
          }
      }
    }
  • 执行结果:

+ ![image-20211103195458492.png](https://gitee.com/songjianzaina/juejin_p17/raw/master/img/eb56f15a084f88db0ae427366ff8ea98aa7bcdd3e1dd427972f6dbb9ed9a408f)

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

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

学习设计模式——策略模式

发表于 2021-11-03

概述

策略模式:(Strategy Design Pattern)定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。

策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。

何时使用:

  • 一个系统有许多许多类,而区分它们的只是他们直接的行为。

UML 类图:

image.png

角色组成:

  1. Context上下文: 也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
  2. 抽象策略角色:抽象策略角色,是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
  3. 具体策略角色: 用于实现抽象策略中的操作,即实现具体的算法。

通用代码

Context上下文:

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

private Strategy strategy;

public Context(Strategy strategy) {
this.strategy = strategy;
}

// 上下文接口
public void contextInterface() {
strategy.algorithmInterface();
}

}

抽象策略角色:

1
2
3
4
5
6
java复制代码public interface Strategy {

// 算法方法
void algorithmInterface();

}

具体策略角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class ConcreteStrategyA implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法A实现");
}
}


public class ConcreteStrategyB implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法B实现");
}
}


public class ConcreteStrategyC implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法C实现");
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public class Test {
public static void main(String[] args) {

Context context;

context = new Context(new ConcreteStrategyA());
context.contextInterface();

context = new Context(new ConcreteStrategyB());
context.contextInterface();

context = new Context(new ConcreteStrategyC());
context.contextInterface();

}
}

结果:

算法A实现

算法B实现

算法C实现

如何利用策略模式避免分支判断

看这样一个案例:给不同的订单不同的折扣策略。不使用策略模式时,我们是这样写代码的。

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
java复制代码public class OrderService {
public double discount(Order order) {
double discount = 0.0;
OrderType type = order.getType();
if (type.equals(OrderType.NORMAL)) { // 普通订单
// 省略折扣计算算法代码
} else if (type.equals(OrderType.GROUPON)) { // 团购订单
// 省略折扣计算算法代码
} else if (type.equals(OrderType.PROMOTION)) { // 促销订单
// 省略折扣计算算法代码
}
return discount;
}
}


public enum OrderType {

NORMAL, // 普通订单

GROUPON, // 团购订单

PROMOTION // 促销订单

}


public class Order {
OrderType type;

public Order(OrderType type) {
this.type = type;
}

public OrderType getType() {
return type;
}

public void setType(OrderType type) {
this.type = type;
}
}

如何来移除掉分支判断逻辑呢?那策略模式就派上用场了。我们使用策略模式对上面的代码重构,将不同类型订单的打折策略设计成策略类,并由工厂类来负责创建策略对象。具体的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码public interface DiscountStrategy {

double calDiscount(Order order);

}


public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单不打折!");
return 1;
}
}


public class GrouponDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单打八折!");
return 0.8;
}
}


public class PromotionDiscountStrategy implements DiscountStrategy {
@Override
public double calDiscount(Order order) {
System.out.println("经过计算,该订单打七折!");
return 0.7;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class DiscountStrategyFactory {
private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();

static {
strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
}

public static DiscountStrategy getDiscountStrategy(OrderType type) {
return strategies.get(type);
}
}
1
2
3
4
5
6
7
java复制代码public class OrderService {
public double discount(Order order) {
OrderType type = order.getType();
DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
return discountStrategy.calDiscount(order);
}
}

测试:

1
2
3
4
5
6
7
8
java复制代码public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.discount(new Order(OrderType.NORMAL));
orderService.discount(new Order(OrderType.GROUPON));
orderService.discount(new Order(OrderType.PROMOTION));
}
}

测试结果:

经过计算,该订单不打折!

经过计算,该订单打八折!

经过计算,该订单打七折!

测试用例的两种写法结果都是一样的

总结

策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。

  • 策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
  • 策略的创建由工厂类来完成,封装策略创建的细节。
  • 策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。

本文转载自: 掘金

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

手把手教你通过pycharm直接连接远程服务器写代码 前情提

发表于 2021-11-03

前情提要

说起pycharm,对于大多数使用python的人来说,应该并不陌生。而近日又正好赶上了双11来临,有很多新用户入手了云服务器,但是不知道应该如何快速上手使用,所以,本期瞎老弟我决定做一期连接远程服务器写代码的教程。

需要准备的东西是:

远程服务器

pycharm专业版

注意!专业版是必须的,社区版中没有提供这一功能!

使用指南

1,首先,我们要找到设置,通过file-settings得到

1.png

2,打开设置以后,通过Build, Execution, Deployment找到Deployment

2.png

3,添加选择选项sftp

3.png

4,之前如果没有已经设置好的服务器,那么就点击…新建一个

4.png

5,在host处填写你服务器的ip地址,在username处填写你的用户名,然后再写上服务器的密码

5.png

6,上图中有一个Test Connection按钮,配置完以后可以点击该按钮测试一下。如果配置没有问题,那么会显示测试成功。

6.png

7,确定以后,就返回图4的Root Path步骤,这个是设置服务器的根路径,你可以选择一个根路径,也可以只写/

7.png

8,设置映射,Local Path需要填写本地的项目路径,Deployment Pat需要填写的服务器的路径,

注意,服务器的路径是从之前设置的服务器根路径开始写的相对路径。

例如:

root path的路径是/user/xia,

Deployment Pat 是/laodi

那么与本地路径同步的服务器最终路径就是/user/xia/laodi

8.png

9,设置好之后,应当来说,就已经配置成功了。你在本地写的文件就会被同步上传到服务器上。如果没有及时自动上传,你也可以右键点击你要上传的文件,通过Deployment手动选择upload

9.png

如果以上步骤你都已经照着一模一样做好了,那么,恭喜你,本地和服务器的连接已经建立成功了。此时你写代码就可以同步到服务器上了。

代码运行

注意,此时只是设置了代码的同步上传,但并没有选择代码在服务器上运行。也就是说,虽然代码会被同步上传到服务器上,但是此时点击运行代码,仍然是在本地运行。

如果你觉得,你非常有必要让代码要在服务器上运行,而非本地的话,那么还需要设置一下python的解释器。

a,同正常设置python解释器一样,找到添加python interpreter,并且,选择ssh interpreter

10.png

b,新添加或者选择一个已有的服务器端的python解释器,此时,就可以完成点击运行,在服务器上运行代码。

11.png

注意:要正确的设置服务器端的python解释器的路径地址,以及下边的映射地址,也就是本地地址和服务器上的地址之间的对应关系。

总结

实际上,pycharm同步代码的这一功能并非是必须的。如果你不想使用这一功能,完全可以手动选择使用scp或者sftp自行进行文件的上传。但是相对而言,上传和修改都没有直接通过pycharm远程连接方便快捷。尤其是体现在你使用flask或者django进行网站的部署上时。

因此,pycharm专业版的这一功能还是非常强大的。如果你正好有远程服务器可以使用的话,建议把这篇文章好好学习一下。

我是瞎老弟,喜欢一定记得关注我,你们的支持对我来说,真的非常重要!

本文转载自: 掘金

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

设计模式-代理模式及应用

发表于 2021-11-03

“代理”反应到生活中,有很多类似的案例。例如之前互联网没有那么发达的时候我们买火车票,除了去火车站买,往往每个区域或者街道会有火车票代售点。各个代售点,就类似于我们的代理模式中的代理对象。完成被代理对象的功能操作(这里就是买车票),代理点的剩余车票数量和火车站买的都是事实的。我从代理售卖窗口也能买到我需要的车票。

在程序的设计过程中,我们往往需要一些额外的功能来统计或者做一些其他的业务操作。比如我们记录每个方法的执行时间,如果在每个方法的开始和执行完业务操作的末尾,统计时间并记录下来。这里的冗余代码会很多,而且都是重复的。所以代理模式就可以解决我们类似的问题。后面会通过具体的案例来说明,在实际代码中的使用场景。

代码模式的定义与结构

1.代理模式的定义:

  • (Proxy Design Pattern)代理模式的使得在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

2.代理模式结构
代理模式的主要角色如下。

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式结构图:
在这里插入图片描述

此外代理模式有动态代理和静态代理之分;

  • 静态代理:在设计程序代码时,代理对象就已经将逻辑写完;
  • 动态代理:在程序运行时,运用反射机制动态创建而成,无需关注代理对象的内部实现;

就Java开发而言,JDK默认已经提供了动态代理,还有第三方框架cglib提供的动态代理,其实现方式有些差异,下面通过案例来说明一下静态代理、动态代理(JDK、Cglib)的使用。

代码案例:
所有的代码案例,都模拟看电影,代理对象对看电影前后所有准备的事情做操作。
1.静态代理

  • 抽象主题 这里定义一个看电影的方法
1
2
3
4
java复制代码public interface Movie {
// 看电影
void watchMovie();
}
  • 真实主题
1
2
3
4
5
6
7
java复制代码public class ConcreteMovie implements Movie{

@Override
public void watchMovie() {
System.out.println("正在观影《阿凡达》");
}
}
  • 代理对象 (静态代理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class ProxyConcreteMovie implements Movie{

private Movie movie;

public ProxyConcreteMovie(Movie movie) {
this.movie = movie;
}

@Override
public void watchMovie() {
preRequest();
movie.watchMovie();
postRequest();
}

public void preRequest() {
System.out.println("看电影之前,买票买3D眼镜");
}
public void postRequest() {
System.out.println("看完之后,归还眼镜");
}
}
  • 使用方
1
2
3
4
5
6
7
8
java复制代码public class Client {
public static void main(String[] args) {
Movie movie = new ConcreteMovie();
Movie proxy = new ProxyConcreteMovie(movie);
proxy.watchMovie();
}

}

执行结果:

1
2
3
java复制代码看电影之前,买票买3D眼镜
正在观影《阿凡达》
看完之后,归还眼镜

这个案例中的ProxyConcreteMovie为代理类,实现和真实对象一致的接口,利用对被代理对象的引用,在调用目标对象时的前后做相关的业务操作。 静态代理的缺点是,如果我的目标对象很多,抽象主体的方法也很多的情况下,代理对象就需要实现全部的接口,而且每个方法,都要去加相同的重复代码,对程序的侵入性也很大。

并不推荐对于整个系统设计过程中,大量业务需要使用的非业务功能代码,通过静态代理来实现。那么解决这个问题,最好的方式就是下面要说的动态代理方式。

2.动态代理(JDK)

  • 抽象主题 这里定义一个看电影的方法
  • 真实主题

抽象对象和真实对象这俩个构件的代码与静态代码相同,这里就不重复写了;着重看下代理对象的实现。

  • 代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码public class JdkProxy implements InvocationHandler{

private Object object;

public JdkProxy(Object object) {
this.object = object;
}

public Object getProxyInstance(){
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preRequest();
method.invoke(object, args);
postRequest();
return null;
}

public void preRequest() {
System.out.println("看电影之前,买票买3D眼镜");
}
public void postRequest() {
System.out.println("看完之后,归还眼镜");
}
}
  • 使用方
1
2
3
4
5
6
7
8
java复制代码public class JdkClient {
public static void main(String[] args) {
ConcreteMovie movie = new ConcreteMovie();
JdkProxy jdkProxy = new JdkProxy(movie);
Movie newProxyInstance = (Movie)jdkProxy.getProxyInstance();
newProxyInstance.watchMovie();
}
}

执行结果:

1
2
3
java复制代码看电影之前,买票买3D眼镜
正在观影《阿凡达》
看完之后,归还眼镜

我们看完JDK动态代理的方式和静态代理的区别,无需代理类与真实真实主题同时实现相同的接口,只需要代理类实现InvocationHandler接口,并实现invoker()方法,而且附加的功能我们可以只写一次,如果定义了其他方法,只需要通过代理对象,调用方法附件的模块会添加到每个方法上。

JDK动态代理的案例结构图:
在这里插入图片描述

3.动态代理(cglib)
和Jdk代理抽象对象和真实对象这俩个构件的代码与静态代码相同,不重复写。

  • 代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码public class CglibProxy implements MethodInterceptor{

private Object object;

public CglibProxy(Object object) {
this.object = object;
}

public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(object.getClass());
//设置回调
enhancer.setCallback(this);
return enhancer.create();
}

@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
preRequest();
arg1.invoke(object, arg2);
postRequest();
return null;
}

public void preRequest() {
System.out.println("看电影之前,买票买3D眼镜");
}
public void postRequest() {
System.out.println("看完之后,归还眼镜");
}
}
  • 使用方
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class CglibClient {

public static void main(String[] args) {
ConcreteMovie concreteMovie = new ConcreteMovie();
//设置被代理对象
CglibProxy cglibProxy = new CglibProxy(concreteMovie);
//获取代理对象
Movie proxyInstance = (Movie)cglibProxy.getProxyInstance();
//调用目标方法
proxyInstance.watchMovie();
}
}

执行结果:

1
2
3
java复制代码看电影之前,买票买3D眼镜
正在观影
看完之后,归还眼镜

cglib实现代理模式的方式与JDK提供的方式差不多,这里的代理类需实现cglib提供的接口MethodInterceptor,并且实现intercept()方法。另外一个区别是创建代理对象的方式不同。

根据不同的使用场景,这两种动态代理的使用场景也有些区别。1.JDK动态代理,需要目标对象实现接口,如果目标对象是一个类,并非接口JDK动态代理就无法使用那就需要使用到cglib代理;
2.cglib也叫子类代理,cglib是在内存中构建一个子类对象从而实现目标对象的拓展功能。
3.spring框架的AOP,实现对目标方法的拦截,就是基于动态代理的方式。那它在底层实现是两种方式都会用到,如果目标对象需要实现接口,用JDK代理,反之用cglib代理。

cglib案例结构图:
在这里插入图片描述

代理模式的优缺点及应用场景

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

缺点:

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

使用场景:
1.一般我们使用spring aop的场景,通常是一些非功能业务模块。一般使用鉴权、限流、事务、监控、日志等;
2.RPC框架,通过远程代理的方式,使客户端在使用远程方法时,只需要知道其接口定义,无需知道具体实现;

代理模式在源码中的使用

1.spring代理模式的使用
spring框架中的AOP实现,代理对象的获取都是通过ProxyFactoryBean类中的getObject() 方法,看下这块的源码

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码   public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}

return this.newPrototypeInstance();
}
}

返回有单例对象和原型对象两种方式,spring代理默认生成单例对象。这里就以单例对象为例,首先往下更getSingletonInstance()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = this.freshTargetSource();
if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {
Class targetClass = this.getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}

this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}

super.setFrozen(this.freezeProxy);
this.singletonInstance =
//获取代理对象 this.getProxy(this.createAopProxy());
}

return this.singletonInstance;
}

看下this.createAopProxy()这个方法,是ProxyFactoryBean的父类ProxyCreatorSupport中createAopProxy()方法,

1
2
3
4
5
6
7
8
java复制代码protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
this.activate();
}

return
// 获取代理类 this.getAopProxyFactory().createAopProxy(this);
}

ProxyCreatorSupport类中的getAopProxyFactory,AOP代理工程,职责就是创建代理类对象,返回AopProxy接口对象,再往下跟看下AopProxy接口的实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FpdnvkeR-1619572486476)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1460)]
说明spring实现动态代理有JDK和Cglib两种方式。
具体使用哪一种方式,我们可以看下,AopProxyFactory子类DefaultAopProxyFactory,方法createAopProxy()的实现

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(targetClass.isInterface() ? new JdkDynamicAopProxy(config) : DefaultAopProxyFactory.CglibProxyFactory.createCglibProxy(config));
}
}
}

可以通过配置指定使用何种方式

ProxyTargetClass (是否强制使用CGLIB来实现代理)
(true : 强制使用CGLIB来实现代理)
(false : 不强制使用CGLIB来实现代理,首选JDK来实现代理)(默认值)

isOptimize (是否对生成代理策略进行优化)
(true : 进行优化,如果有接口就代理接口(使用JDK动态代理),没有接口代理类(CGLIB代理))
(false : 不进行优化) (默认值)

可以通过配置来指定使用的代理方式。

获取具体代理类后,回到ProxyFactoryBean类中的
this.singletonInstance = this.getProxy(this.createAopProxy());方法,看下getProxy()操作,
这里就是通过前面获取的具体代理方式,获取代理对象

1
2
3
java复制代码protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}

以上案例分析结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3FyHPKf-1619572486544)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1459)]

本文转载自: 掘金

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

1…426427428…956

开发者博客

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