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

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


  • 首页

  • 归档

  • 搜索

还在用Autowired注入?试试这个最佳实践

发表于 2021-11-03

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

在这篇文章中,我将向您展示如何使用Project Lombok作为Spring框架依赖性注入的最佳实践。

前言

Spring框架本身有各种不同的方式来执行依赖项的注入。灵活的选择是Spring框架的强项。然而,并非所有依赖注入方式都被认为是最佳实践。

依赖注入

接下来我们通过一些代码示例,来分别实现Spring的多种依赖注入的方式。首先我们有一个MyService,这个service中有一个sayHi()的服务,我们在Controller中尝试用不同的方式进行注入。

1
2
3
4
5
6
java复制代码@Service
public class MyService{
public String sayHi() {
return "hello Spring.";
}
}

属性注入

1
2
3
4
5
6
7
8
9
10
java复制代码@Controller
public class FieldController {

@Autowired
private MyService service;

public String saySomething() {
return service.sayHi();
}
}

通过属性注入的方式,Spring会在转配Bean时通过反射注入。这种方式虽然可以满足依赖的注入,但是在测试时,要么需要启动Spring Context,要么使用一些Spring实用程序执行依赖项注入以进行测试。

我们可以通过为私有属性提供setter来改进这一点。getter和setter通常被认为是面向对象编程中的最佳实践。通过注解setter方法来指导Spring使用setter进行依赖项注入是很简单的。

方法注入

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

private MyService service;

@Autowired
public void setService(MyService service) {
this.service = service;
}

public String saySomething() {
return service.sayHi();
}
}

这是对使用私有属性注入的一种改进方式,但是还是会有人认为代码太多。

构造器注入

当使用构造函数注入属性时,必须要提供@Autowired注解。这是一个很好的功能,它为我们减少了一些代码。从Spring Framework 4.2版本开始,用于依赖注入的构造函数注解已经是可选的。

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

private MyService service;

public FieldController(MyService service) {
this.service = service;
}
public String saySomething() {
return service.sayHi();
}
}

基于构造器的依赖注入通常被认为是最佳实践。有一段时间,我个人喜欢基于setter的注入,但后来我转而支持基于构造器的注入。

基于构造器的注入方式目前主要还有两个问题。

  • 我们服务的类型是具体类型。硬类型的依赖项注入并不被认为是最佳实践。
  • 我们要注入的属性没有声明为final,因此,从理论上讲,可以在实例化后修改注入的属性。

依赖注入最佳实践

依赖项注入的最佳实践是利用接口、构造函数和final属性。

最佳实践服务接口

1
2
3
java复制代码public interface BpService {
String getHello();
}

最佳实践服务实现

1
2
3
4
5
6
7
java复制代码@Service
public class BpServiceImpl implements BpService{
@Override
public String getHello() {
return "The Best Hello!";
}
}

使用Lombok

接下来,使用Lombok项目在依赖注入方面的原因是:

  • 声明接口类型的final属性
  • 使用Lombok所需的args构造函数注解

Lombok Controller

1
2
3
4
5
6
7
8
java复制代码@Controller
@AllArgsConstructor
public class BpController {
private final BpService bpService;
public String saySomething() {
return bpService.getHello();
}
}

这是一个很好的方式,代码会保持的非常干净。在使用Spring时,经常需要几个自动注入的属性。当需要添加一个bean时,只需声明为final的属性就可以。不需要再添加 @Autowired注解,也不需要添加setter方法或者构造器。

我在日常编码中使用这种方式已经有一段时间了。这绝对是一件省时的事,并且代码也会变的清晰。

小结

本期我给大家分享了Spring依赖注入的不同方式,主要有:

  • 通过私有属性Autowired注入;
  • 通过setter方法注入;
  • 通过构造器注入;

但是这三种方式都不是最佳实践,我推荐的最佳实践是使用Lombok的@AllArgsConstructor注解,并将需要注入的bean定义为final属性。

希望对你的开发有所帮助,点个赞是对我最大的支持。

本文转载自: 掘金

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

Go 与函数 函数

发表于 2021-11-03

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

函数

函数声明

函数由5部分组成:函数名、形参列表、返回列表、和函数体。func为定义函数的关键字

1
2
3
go复制代码func name(parameters-list) (result-list) {
body
}

形参列表的格式是参数名称+参数类型,相同类型的参数可以写在一起

1
2
3
go复制代码// 这两种个写法等价的
func f(x, y float64) float64 {}
func f(x float64, y float64) float64 {}

返回列表的格式是(返回值名称+返回值类型...)
返回值名称可以省,当函数存在返回列表时,必须显示地已return语句结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码// 方式一:返回值名称+类型
func sum(x, y int) (z int) {
z = x + y // 变量z已经被声明
return // 必须显示地以return结束,可以不用指出返回的变量,因为函数第一行已写
}
// 方式二:只有返回类型
func sum2(x, y int) int {
z := x + y // 注意 这里z要初始化
return z
}

// 如果有多个返回值,需要用括号包起来
func foo() (x, y int) {
x, y = 1, 1
return
}
func foo2() (int, int) {
x, y := 1, 1
return x, y
}

函数的类型称作函数签名,由函数的形参列表和返回列表确定,形参和返回值名称不会形象函数类型

1
2
3
4
5
go复制代码func add(x, y int) int { return x + y }
func sub(a int, b int) (c int) { c = a - b; return }

fmt.Printf("%T\n", add) // func(int, int) int
fmt.Printf("%T\n", sub) // func(int, int) int

我们可以只定义函数签名,函数实现放在其他地方或其他语言

1
go复制代码func Sin(x float64) float64

函数的形参

形参变量是函数的局部变量。通常情况下,调用函数时实参是按值传递的,因此函数内修改变量不会改变实参的值。

1
2
3
4
5
6
7
8
go复制代码func incr(x int) {
x++
}
func main() {
a := 1
incr(a)
fmt.Println(a) // 1
}

但是,如果实参是引用类型,比如:指针、slice、map、函数或者通道,那么就有可能改到实参的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码func updateSlice(s []int, index int, val int) {
s[index] = val
}

func updateMap(m map[int]int, k int, v int) {
m[k] = v
}
func main() {
s1 := []int{1, 1, 3, 4, 5}
updateSlice(s1, 1, 2)
fmt.Println(s1) // [1 2 3 4 5]

m1 := map[int]int{0: 1, 1: 1, 2: 3}
updateMap(m1, 0, 100)
fmt.Println(m1) // map[0:100 1:1 2:3]
}

函数的递归

递归实现斐波那契数列

1
2
3
4
5
6
7
8
9
10
go复制代码func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}

func main() {
fib(5) // 5
}

递归的实现使用栈结构来保存当前上下文信息。Go语言的实现使用了可变长的栈,栈的长度可以随着使用增加。

函数多指返回

Go语言支持函数的返回值不止一个,一般情况是一个期望计算得到的结果和一个错误值或者一个表示函数调用是否正确的布尔值。

1
2
3
4
5
6
7
go复制代码func calculate(expr string) (result float64, err error) {
...
}

func main() {
res, err := calculate("30*50")
}

函数变量

Go语言中,可以声明函数类型的变量,即函数变量。函数变量之间不能比较,只能和nil比较。

1
2
3
4
5
6
7
8
9
10
11
go复制代码var sum func(int, int) int
fmt.Printf("%T\n", sum) // func(int, int) int
sum = func(a, b int) int {
return a + b
}
fmt.Println(sum(1, 1)) // 2

var f func(int, int) int
if f != sum { // 编译错误 f != sum (func can only be compared to nil)
f = sum
}

函数变量可以作为参数传递

1
2
go复制代码func add1(r rune) rune { return r + 1 }  // 将字符的Unicode值加1
fmt.Println(strings.Map(add1, "HAL-9000")) // IBM.:111

可以在函数内部声明递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码func main() {
var fib func(int) int
fib := func(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
fmt.Println(fib(10)) // 5
}
/*下面的写法是错误的
func main() {
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
fmt.Println(fib(10)) // 5
}
*/

变长函数

函数可以支持可变的参数数量,比如:fmt.Printf就是支持可变的数量。在参数列表最后的类型名称前使用...表示声明一个边长函数,下面我们来实现一个简易的Sprintf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
go复制代码func Sprintf(format string, params ...interface{}) string {
i, j := 0, 0
s := ""
for i < len(format)-1 {
if format[i] == '%' && format[i+1] == 'd' {
s = s + strconv.Itoa(params[j])
j++
i++
} else {
s = s + string(format[i])
}
i++
}
return s
}

func main() {
var s string
s = Sprintf("%d+%d=%d", 1, 2, 3)
fmt.Println(s) // 1+2=3
s = Sprintf("%d+%d+%d=%d", 1, 2, 3, 6)
fmt.Println(s) // 1+2+3=6
fmt.Printf("%T", Sprintf) // func(string, ...int) string
}

可变长度参数只能声明在最后,并且只能有一个,这样就限制了可变参数只能是一种类型,但是fmt.Printf可以这样写:

1
go复制代码fmt.Printf("%d %s", 1, "abc")  // 1 abc

这是因为它将可变长度参数的类型声明生成了interface{},将会在后面的章节研究。

延迟函数

在一个函数调用或者方法调用前加上defer关键字,就声明了这个函数(方法)延迟执行

  • 延迟到return语句后执行
  • 延迟到函数执行完毕后执行
  • 延迟到发生宕机时执行
    在一个函数作用域内,可以有声明多次延迟函数,执行的时候是以调用defer语句顺序的倒序进行。
    延迟函数一般用于声明函数正常或异常结束后释放资源。
1
2
3
go复制代码conn, err := Client.GetConn()
defer coon.Close()
...

此外,还可以结合闭包实现对一个函数执行时的监控

1
2
3
4
5
6
7
8
9
10
11
go复制代码func clock(msg string) func() {
start := time.Now()
fmt.Printf("enter %s\n", msg)
// 因为匿名函数可以得到其外层函数作用域内的变量(包括命名的结果)
return func() { fmt.Printf("exit %s (%s)\n", msg, time.Since(start)) }
}

func SlowFunc() {
defer clock("SlowFunc")()
time.Sleep(3 * time.Second)
}

函数的宕机和恢复

宕机发生在程序的运行时出现了严重的异常情况,比如:错误的输入、配置或者I/O失败等。此时程序执行会终止,goroutine中的所有延迟函数会执行,然后程序会异常退出。
一些标准库会对不可能发生的情况做宕机处理,我们自己也可以同宕机函数 panic来实现:

1
2
3
4
5
6
go复制代码switch isRight {
case true: //...
case false: // ...
default:
panic("invalid")
}

有些情况下,当程序发生宕机,我们也不期望程序退出,比如,当Web服务器遇到处理用户请求时遇到宕机情况,不能直接退出,而是要给用户返回当前遇到的错误:

  • 如果是用户查询的记录不存在,应该返回404
  • 如果是用户输入的参数有问题,应该返回400
    …

我们可以通过在函数的延迟函数中调用recover函数来终止当前的宕机状态并做一些逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go复制代码func RequestHandler(c *Context) (res Response) {
defer func() {
switch p := recover(); p {
case notFound{}:
res = NotFoundRes{}
case invalidParam{}:
res = InvalidParamRes{}
default:
res = InternalErrorRes{}
}
}
//具体处理逻辑
//...
return
}

需要注意的是,要合理评估当前情况是否需要对宕机进行恢复,恢复会有一定风险,比如导致资源泄露或使失败的处理函数处于未定义的状态从而导致其他问题。

本文转载自: 掘金

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

SpringBoot+Nacos实现配置中心——Nacos学

发表于 2021-11-03

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

概念

首先理解Nacos到底是干什么的?

  • 配置中心
  • 服务注册中心

配置中心

所谓配置中心:一般SpringBoot项目都使用在resources下创建类似application.yml之类的配置文件来管理整个项目的一些配置信息

  • mysql、redis、es、mq等地址账号密码
  • 业务场景信息
  • 主站ip前缀
  • 。。。。。。

那么就会有人想了,为啥要将这些本来是动态配置的信息写死在代码中呢。而且如果有人登陆上了服务器,获取了Jar文件,是不是就可以有了各种数据库的链接信息了呢。所以配置中心应运而生了。

通过一个配置平台来管理注册中心下所有的服务的配置文件信息,也就是说以后服务不需要去resource中获取相应需要的配置信息了,直接去这个服务中获取即可

优点:

  • 灵活配置
  • 统一管理
  • 权限控制
  • ……

Nacos-client使用配置中心

  1. 创建一个SpringBoot项目
  2. 导入相关的依赖
  3. 增加基本的配置信息
  4. 修改启动类
  5. 新增测试类

初始化项目

基于SpringBoot的所需依赖及注意事项

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
xml复制代码    <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>

<!--
注意:这里并没有列出SpringBoot-parent的版本信息,需要注意的是SpringBoot与这个Nacos的版本需要保持一致
(其实一般来说只要使用SpringBoot相关的包都尽量保持一致好了,因为很多相关的Service的注入在多个Jar文件都有注入,保持一致防止触发多处实例化Bean的问题)
-->

基础配置信息

bootstrap.yml,在存在该文件时,会优先加载改文件数据,bootstrap的意思是“引导程序”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yaml复制代码spring:
application:
name: nacos-config-client
profiles:
active: dev
cloud:
nacos:
discovery:
# Nacos服务注册中心地址
server-addr: http://yourIp:8848
config:
# 指定Nacos配置中心的地址
server-addr: http://yourIp:8848
file-extension: yaml # 指定yaml格式的配置 默认properties
server:
port: 8080

基础启动类

1
2
3
4
5
6
7
8
9
java复制代码@SpringBootApplication
@EnableDiscoveryClient
public class NacosClientApplication {

public static void main(String[] args) {
SpringApplication.run(NacosClientApplication.class, args);
}

}

基础测试验证类

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@RestController
//@RefreshScope用于动态刷新
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;

@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}

注意此时yml配置文件中并不能获取到${config.info}。所以为了启动不报错,需要提前在配置中心的页面中进行数据配置

配置中心进行配置1
配置中心进行配置2。

启动服务

在浏览器中调用localhost:8080/config/info接口,获取到配置中心配置的数据即表示无误

dataId

dataId=prefix−{prefix}-prefix−{spring.profile.active}.${file-extension}

其中profix默认为${spring.application.name}

file-extension表示配置的类型,默认为properties。与页面上相呼应!file-extension配置项

如果没有指定spring.profile.active,那么dataId就变成了profix.{profix}.profix.{file-extension}

  1. 启动时会发现启动日志中会打出多个,
    • ${prefix}
    • prefix−{prefix}-prefix−{spring.profile.active}
    • prefix−{prefix}-prefix−{spring.profile.active}.${file-extension}。说明其实是可以匹配配置中心中配置的多条配置名称
  2. 匹配优先级是:3>2>1,精确匹配

学习自:
大佬1号
大佬2号

遇到的问题:

  • Error creating bean with name ‘configurationPropertiesBeans‘ defined in class path resource
  • 「nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder ‘config.info’ in value “${config.info}”」配置中心指定的配置与测试中注入的配置信息不一致

本文转载自: 掘金

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

linux全网备份项目之项目实操(下)

发表于 2021-11-03

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

脚本第一个问题:需要进行不同服务器备份数据的区分管理
实现方式:分别创建不同的目录,备份不同服务器的数据信息

1
2
bash复制代码mkdir /backup/$ip_info -p
tar zchf /backup/$ip_info/www_backup_${date_info}.tar.gz ./var/html/www/

脚本第二个问题:需要保留每周一的数据信息
实现方式:将每周一的数据显示出来

1
bash复制代码$(date +%F_week%w -d -1day)

脚本第三个问题:需要验证数据完整性

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码    [root@backup 172.16.1.8]# md5sum -c finger.txt 
/backup/172.16.1.8/sysconfig_iptables_2020-10-31_week6.tar.gz: OK
/backup/172.16.1.8/sysconfig_spool_2020-10-31_week6.tar.gz: OK
/backup/172.16.1.8/www_backup_2020-10-31_week6.tar.gz: OK
/backup/172.16.1.8/sysconfig_scripts_2020-10-31_week6.tar.gz: OK
/backup/172.16.1.8/sysconfig_local_2020-10-31_week6.tar.gz: OK
#第一步:读取指纹文件信息
#第二步:根据已知的文件信息,对每一个文件生成MD5数值信息
#第三步:用本地生成md5数值信息和指纹文件中的md5数值信息进行比对
#第四步:比对结果一致,表示文件完整验证成功,否则,验证失败

find /backup/172.16.1.7/ -type f -name ".tar.gz"|xargs md5sum >/backup/172.16.1.7/finger.txt

脚本第四个问题:如何发送邮件告知相关运维人员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash复制代码ps:在备份服务端进行操作
vim /etc/mail.rc
set form=邮箱地址@163.com smtp=smtp.163.com <- 邮件发送邮件服务器域名,此处为163邮箱的发送服务器域名
set smtp-auth-user=邮箱账号 smtp-auth-password=邮箱授权密码 smtp-auth=login
说明:设置发送邮件是从哪个公共邮箱地址发送,采用的发送邮件服务器域名,公共邮箱账号和密码

设置完成样子
set form=fby1725052115@163.com smtp=smtp.163.com
set smtp-auth-user=fby1725052115@163.com smtp-auth-password=AHPYEWCFLZDOXSPK smtp-auth=login

/etc/init.d/postfix start 开启邮件服务

邮件发送的方法:
echo "系统有异常问题,请检查系统"|mail -s"异常告警" fby1725052115@163.com
说明:echo后面接上要发送的邮件信息,mail -s后面接上邮件标题,最后表示要将邮件发给谁

mail -s "异常警告" fby1725052115@163.com </etc/hosts

客户端脚本信息:

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
bash复制代码#!/bin/bash
date_info=$(date +%F_week%w -d -1day)
ip_info=$(hostname -i)

mkdir /backup/$ip_info -p

cd /
#tar zchf /backup/sysconfigfile_backup_${date_info}tar.gz ./var/spool/cron/root ./etc/rc.local ./etc/sysconfig/iptables ./server/scripts/
# -h 打包链接文件指定的源文件信息,不会打包源文件信息
tar zchf /backup/$ip_info/sysconfig_spool_${date_info}.tar.gz ./var/spool/cron/root
tar zchf /backup/$ip_info/sysconfig_local_${date_info}.tar.gz ./etc/rc.local
tar zchf /backup/$ip_info/sysconfig_iptables_${date_info}.tar.gz ./etc/sysconfig/iptables
tar zchf /backup/$ip_info/sysconfig_scripts_${date_info}.tar.gz ./server/scripts/
mkdir /var/htmL/www /app/Logs -p
tar zchf /backup/$ip_info/www_backup_${date_info}.tar.gz ./var/html/www/
tar zchf /backup/$ip_info/www_backup_${date_info}.tar.gz ./app/log/

find /backup/172.16.1.8/ -type f -name ".tar.gz"|xargs md5sum >/backup/$ip_info/finger.txt

rsync -az /backup/$ip_info rsync_backup@172.16.1.41::backup --password-file=/etc/rsync.password

find /backup/ -type f -name "*.tar.gz" -mtime +7|xargs rm &>/dev/null

[root@web01 ~]# cd /server/scripts/ cd到备份目录下
[root@web01 scripts]# ll /backup 查看备份目录
[root@web01 scripts]# sh backup_data.sh 执行脚本文件
[root@web01 scripts]# vim backup_data.sh 编写脚本文件

本文转载自: 掘金

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

Rust变量与可变性 数据类型介绍 标量类型 结语

发表于 2021-11-03

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

数据类型介绍

Rust 语言的数据类型可以分成两类,分别是标量类型和复合类型,本文首先介绍标量类型。

Rust 是一门静态的编译型语言,在编译时必须知道所有变量的具体类型,在大部分情况下,基于变量的值,编译器能够推断出变量值的具体类型,但是如果某一个变量对应的值的可能的类型比较多(例如将string 转换成整数的parse方法),就必须为这个变量添加类型标注,否则在编译时就会报错。

1
2
3
4
rust复制代码fn main() {
let a: u32 = "40".parse().expect("not a number"); // 指定a变量的具体类型
// let a = "40".parse().expect("not a number"); 因为字符串40能够解析出来的类型较多,比如i32 u32 i8等,如果不标注a的类型,在编译时就会提示type error的错误
}

标量类型

一个标量类型代表一个单个的值,Rust 有四种主要的标量类型,分别是整数类型、浮点类型、布尔类型、字符类型。

整数类型

整数类型没有效数部分,例如上面例子中的u32 就是一个无符号的整数类型,占据32位的空间,无符号整数类型以u(unsize)开头,有符号整数类型以i(integer)开头,Rust的整数类型列表如下,每种类型都分i 和 u, 有符号的范围是[(−2n−1),(2n−)][(-2^n-1), (2^n-)][(−2n−1),(2n−)], 无符号的范围是[0,2n−1][0, 2^n-1][0,2n−1]。而isize 和usize 类型的位数是由程序运行的计算机的架构(arch(architecture))所决定的,如果计算机是64位的,那么就是64位(i64/u64)的,实际上使用这两种类型的场景不多。

位长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

整数的表述方法有以下几种,十进制、十六进制、八进制、二进制和字节,很显然,有的整数中间存在下划线,这种设计可以可读性。

下述除了byte 类型外,所有的数值都允许使用类型后缀,比如57u8,表示整数57,类型是u8,如果不太清楚使用哪种类型,可以使用Rust 相应的默认类型,整数的默认类型就是i32 ,i32 类型总体上来说速度很快,即使是在64位的系统中。

进制 例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字节(只能表示 u8 型) b’A’

由于不同的整数类型对应的范围不同,因此在开发程序的过程中可能会出现整数溢出的现响,比如说u8 的范围是0-255,如果把一个u8 的变量值设为256,那么可能会出现两种情况:

  • 调试模式下:在调试模式下进行编译,Rust 会检查整数溢出,如果发生溢出,程序在运行时就会panic
  • 发布模式下(–release):在发布模式下进行编译,Rust 不会检查整数溢出,如果发生溢出,Rust 会执行环绕操作,比如u8 类型的变量发生整数溢出时,256会变成0,257会变成1…,但是程序不会panic

浮点类型

Rust 有两种基础的浮点类型,就是含有小数部分的类型,分别是:

  • f32:32位,单精度
  • f64:64位,双精度

f64 是默认类型,因为在现代CPU上f64 和f32的速度差不多,但是精度更高。

1
2
3
4
rust复制代码fn main() {
let a = 3.0; // 此时a是f64
let a: f32 = 3.0; // 此时a是f32
}

数值操作

Rust 的数值操作主要就是加减乘除余,和其他编程语言差不多,但是需要注意的是,浮点型和整数类型之间不能直接进行运算,Rust支持+= -=的操作:

1
2
3
4
5
6
7
rust复制代码fn main() {
let sum = 1 + 2; // 加
let diff = 3 - 2; // 减
let pro = 3 * 2; // 乘
let quo = 3.0 / 1.5; // 除
let rem = 3 % 2; // 取余
}

布尔类型

Rust 和其他编程语言一样,布尔类型用bool 表示,值只能是true 或者 false。占用一个字节位置。

1
2
3
4
rust复制代码fn main() {
let b1 = true;
let b2: bool = flase; // 显示标注类型
}

字符类型

字符型用 char 表示。

Rust*的 *char 类型大小为 4 个字节,代表 Unicode标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至emoji*和零宽度空格在 *Rust 中都是有效的 char 值。

char字面量是单引号包围的任意单个字符,例如'a'、'我'。注意:char和单字符的字符串String是不同的类型。

1
2
3
4
5
6
rust复制代码fn main() {
let a = 'a';
let b = 'あ';
let c = '😂';
let d = "h"; // d的类型是&str
}

结语

文章首发于微信公众号程序媛小庄,同步于掘金。

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)

本文转载自: 掘金

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

领导让我研究 Eureka 源码 注册过程 Eureka

发表于 2021-11-03

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

本文首发公众号:悟空聊架构

原文链接:点击这里

Eureka 源码之客户端注册

上一讲我们讲到 Eureka 的启动过程,这次我们来分析客户端是如何注册的。

领导让我研究 Eureka 源码 | 启动过程

本文主要内容如下:

目录

Eureka Client 就是客户端,可以是 Eureka Server 自身,也可以是要注册的服务实例,比如订单服务、商品服务等。

后续讲到 @EnableEurekaClient 注解时,其实是将当前 Application 当作一个 eureka client,注册到 eureka 服务上。

那么 Eureka Client 是如何注册的呢?

我们可以通过 Eureka 源码提供的示例类 ExampleEurekaClient 来看下 Eureka Client 的构造和注册过程。

首先从 main 方法方法看起,只从 main 方法看,看不出来注册的代码在哪,那我们就来研究下底层的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public static void main(String[] args) throws UnknownHostException {
​
   injectEurekaConfiguration();
​
   ExampleEurekaClient sampleClient = new ExampleEurekaClient();
​
   // create the client
   ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
   EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());
​
   // shutdown the client
   eurekaClient.shutdown();
}

接着我们来一步一步分析 main 里面做了什么事情。

一、初始化配置

1.1 初始化变量

injectEurekaConfiguration() 方法初始化了 Eureka 的一些变量,比如端口号、当前服务的访问路径、是否需要抓取注册表信息等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码private static void injectEurekaConfiguration() throws UnknownHostException {
   String myHostName = InetAddress.getLocalHost().getHostName();
   String myServiceUrl = "http://" + myHostName + ":8080/v2/";
​
   System.setProperty("eureka.region", "default");
   System.setProperty("eureka.name", "eureka");
   System.setProperty("eureka.vipAddress", "eureka.mydomain.net");
   System.setProperty("eureka.port", "8080");
   System.setProperty("eureka.preferSameZone", "false");
   System.setProperty("eureka.shouldUseDns", "false");
   System.setProperty("eureka.shouldFetchRegistry", "false");
   System.setProperty("eureka.serviceUrl.defaultZone", myServiceUrl);
   System.setProperty("eureka.serviceUrl.default.defaultZone", myServiceUrl);
   System.setProperty("eureka.awsAccessId", "fake_aws_access_id");
   System.setProperty("eureka.awsSecretKey", "fake_aws_secret_key");
   System.setProperty("eureka.numberRegistrySyncRetries", "0");
}

1.2 获取配置文件配置

在这一行代码中,将配置文件 eureka-client.properties 中的配置读取后,放到了 EurekaInstanceConfig 中。这个 EurekaInstanceConfig 是用来初始化 applicationInfoManager 信息管理器的。

看下面代码,创建了一个 MyDataCenterInstanceConfig,其实就是创建了 EurekaInstanceConfig。

1
scss复制代码new MyDataCenterInstanceConfig()

那 MyDataCenterInstanceConfig 和 EurekaInstanceConfig 是什么关系呢?

InstanceConfig 类图

从类图关系中可以看到 MyDataCenterInstanceConfig 继承 PropertiesInstanceConfig 类,实现了 EurekaInstanceConfig 接口。这种接口之前专门讲过,通过接口来获取配置信息,类似这种方法 getXX()。

然后在 PropertiesInstanceConfig 类的构造函数调用了一个工具类,读取了配置文件 eureka-client.properties 中的值。这个隐藏的有点深啊!

1
ini复制代码Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);

1.3 初始实例信息

主要就是构造出 instanceInfo 实例信息。这个里面的信息包含了第一步初始化变量中的配置信息。

1
ini复制代码InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();

1.4 初始化实例信息管理器

就是将 instanceConfig 和 instanceInfo 交给实例信息管理器来管理。

1
ini复制代码applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);

二、构造 EurekaClient

2.1 构造流程

构造 eurekaClient 的代码

1
ini复制代码eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);

DiscoveryClient 是 EurekaClient 的子类,构造 DiscoveryClient做了以下几件事:

构造 EurekaClient 流程

  • 加载配置文件
  • 初始化网络传输组件
  • 将服务实例配置、配置文件配置、网络传输组件都赋值给了 DiscoveryClient。
  • 初始化两个线程,一个用来心跳检测,一个用来刷新缓存。
  • 初始化网络通信组件 EurekaTransport
  • 尝试抓取注册表信息,如果没有抓取到,则从备用的注册表中获取。
  • 初始化调度任务的方法中,启动了定时调度任务:心跳检测 heartbeat、缓存刷新 cacheRefresh
  • 初始化调度任务的方法中,初始化了一个 InstanceInfoReplicator,用来向 eureka server 注册的。
  • 初始化调度任务的方法中,初始化了一个状态变更的监听器 StatusChangeListener,这个里面也有注册的逻辑。

注意:在初始化调度任务的方法,会根据是否设置了抓取注册表信息和是否注册将 eureka-client 注册到 eureka-server 来执行上面的初始化操作。如下代码所示:

三、Eureka Client 注册

3.1 注册流程

Eureka Client 向 Server 注册的代码隐藏的比较深,很难找到,不是直接调用注册的方法,而是通过一个后台线程去做的,而且调用注册方法的类的名字起得也有争议,叫做 InstanceInfoReplicator,“Replicator” 是拷贝副本的意思,而注册其实不是拷贝副本,而是将新的注册信息发送到 eureka server 上去的,所以这个类的名字起得不太好,这也是容易造成找不到注册代码的一个问题。

下面来看下 eureka client 是怎么向 eureka server 注册的。

(1)注册是通过 InstanceInfoReplicator 类来注册的。它是在构造 DiscoveryClient 时创建出来的。

启动了一个延时 40 s 的线程,

1
scss复制代码instanceInfoReplicator.start(40); // 40 s后执行

(2)然后将一个标志位设置为 true,用来标记是否注册过了。

1
ini复制代码instanceInfo.setIsDirty();

(3)然后调用注册的方法

1
c复制代码discoveryClient.register();

register() 里面的核心代码就是

1
ini复制代码httpResponse = eurekaTransport.registrationClient.register(instanceInfo);

返回的 httpResponse 大家可以想到这是一个 HTTP 请求,eureka client 注册时就是发送的 http 请求。

eurekaTransport:底层的传输组件,在初始化 DiscoveryClient 时初始化出来的。

registrationClient:它是一个抽象类,在初始化 DiscoveryClient 时,通过调用 scheduleServerEndpointTask() 初始化了专门用于注册的 registrationClient,这里就是 SessionedEurekaHttpClient。

instanceInfo:就是要发送给 eureka server 的当前实例信息,用来注册的信息。

(4)发送 post 注册请求

执行 register() 方法,发送注册请求的类是 AbstractJerseyEurekaHttpClient,这个类在工程 eureka-client-jersey2 里面,用到的是 Jersey 框架,国内用这个框架的不多,不用深究。请求的 url 为

http://localhost:8080/v2/apps/EUREKA

注册的方法里面发送了 post 请求。至此,Client 就注册到 Server 那边了。

1
2
3
4
ini复制代码response = resourceBuilder
.accept(MediaType.APPLICATION_JSON)
.acceptEncoding("gzip")
.post(Entity.json(info));

那么 Server 是如何将注册信息保存到自己注册表里面的呢? 下篇我们再来讲解。

四、总结

Eureka Client 向 Eureka Server 注册的过程:

(1)Eureka Client 初始化了一个 DiscoveryClient,抓取注册表,执行调度任务。

(2)InstanceInfoReplicator 对象启动了一个延迟 40 s 的后台线程,执行注册。

(3)然后使用 AbstractJersey2EurekaHttpClient 发送 post 请求,将 instanceInfo 实例信息发送给 Eureka Server。

时序图如下:

留个问题

我们使用 Eureka 时,Service 启动后,Eureka 很快就发现了 Service 的存在,如下图所示的控制台界面:

并不需要等待 40 s 才能注册到 Eureka,那这又是为什们呢?

下一篇,我们来看下 Eureka Server 是如何将 Eureka Client 发送过来的注册信息保存起来的。

本文转载自: 掘金

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

java8(五)并行流之分支/合并(fork/join)框架

发表于 2021-11-03

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

并行流背后使用的基础架构是Java 7中引入的分支/合并框架。我们会在本文仔细研究分支/合并框架。

分支/合并框架的目的是以递归方式将可以并行的任务拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果。它是 ExecutorService 接口的一个实现,它把子任务分配给线程池(称为 ForkJoinPool )中的工作线程。

image.png

一、RecursiveTask

要把任务提交到这个池,必须创建 RecursiveTask 的一个子类,其中 R 是并行化任务(以及所有子任务)产生的结果类型,或者如果任务不返回结果,则是 RecursiveAction 类型。

要定义 RecursiveTask, 只需实现它唯一的抽象方法compute :

1
java复制代码protected abstract R compute();

在我们实现这个方法时,需要同时定义将任务拆分成子任务的逻辑,以及无法再拆分或不方便再拆分时,生成
单个子任务结果的逻辑。

这个方法的实现类似于下面的伪代码:

1
2
3
4
5
6
7
java复制代码if (任务足够小或不可分) {
顺序计算该任务
} else {
将任务分成两个子任务
递归调用本方法,拆分每个子任务,等待所有子任务完成
合并每个子任务的结果
}

递归任务拆分过程如下所示:

image.png

分支/合并框架实例:为一个数字范围Long[]求和

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
java复制代码import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

import static com.cloud.bssp.java8.stream.TestStreamParallel.measureSumPerf;

/**
* @description: 使用ForkJoinPool
* @author:weirx
* @date:2021/10/25 14:10
* @version:3.0
*/
public class TestRecursiveTask extends RecursiveTask<Long> {

/**
* 要求和的数组
*/
private final long[] numbers;

/**
* 子任务求和的数组的开始位置
*/
private int start;

/**
* 子任务求和的数组的结束位置
*/
private int end;

/**
* 私有构造,用于以递归方式为主任务创建子任务
*
* @param numbers
* @param start
* @param end
*/
private TestRecursiveTask(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}

/**
* 公共函数用于构建主任务
*
* @param numbers
*/
public TestRecursiveTask(long[] numbers) {
this.numbers = numbers;
}

/**
* 任务拆分的数组最大值
*/
public static final long THRESHOLD = 10000L;

@Override
protected Long compute() {
int length = end - start;
if (length <= THRESHOLD) {
// 如果大小小于等于阈值,则顺序计算
return computeSequentially();
} else {
//创建一个子任务,为数组的前一半求和
TestRecursiveTask left = new TestRecursiveTask(numbers, start, start + length / 2);
//利用另一个ForkJoinPool线程异步执行新创建的子任务
left.fork();
//创建一个子任务,为数组的后一半求和
TestRecursiveTask right = new TestRecursiveTask(numbers, start + length / 2, end);
// 同步执行第二个子任务
Long compute = right.compute();
//读取第一个子任务的结果,没有完成则等待
Long join = left.join();
//结果合并
return compute + join;
}
}

/**
* 当子任务不可拆分时计算结果的简单算法
*
* @return
*/
private Long computeSequentially() {
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}

/**
* 并行对前n个自然数求和
*
* @param n
* @return
*/
public static long forkJoinSum(long n) {
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinTask<Long> task = new TestRecursiveTask(numbers);
return new ForkJoinPool().invoke(task);
}

public static void main(String[] args) {
System.out.println("ForkJoin sum done in: " + measureSumPerf(
TestRecursiveTask::forkJoinSum, 10000000) + " msecs");
}

}

输出结果:

1
bash复制代码ForkJoin sum done in: 64 msecs

这个性能看起来比用并行流的版本要差,但这只是因为必须先要把整个数字流都放进一个long[] ,之后才能在任务中使用它。

二、Fork/join的最佳用法

虽然分支/合并框架还算简单易用,不幸的是它也很容易被误用。以下是几个有效使用它的最佳做法:

1)对一个任务调用 join 方法会阻塞调用方,直到该任务做出结果。因此,有必要在两个子任务的计算都开始之后再调用它。否则,你得到的版本会比原始的顺序算法更慢更复杂,因为每个子任务都必须等待另一个子任务完成才能启动。

2)不应该在 RecursiveTask 内部使用 ForkJoinPool 的 invoke 方法。相反,你应该始终直接调用 compute 或 fork 方法,只有顺序代码才应该用 invoke 来启动并行计算。

3) 对子任务调用 fork 方法可以把它排进 ForkJoinPool 。同时对左边和右边的子任务调用fork()似乎很自然,但这样做的效率要比直接对其中一个调用 compute 低。调用compute你可以为其中一个子任务重用同一线程,从而避免在线程池中多分配一个任务造成的开销。

4)调试分支/合并框架的并行计算代码可能有点棘手。特别是你平常都在你喜欢的IDE里面看栈跟踪(stack trace)来找问题,但放在分支/合并计算上就不行了,因为调用 compute的线程并不是概念上的调用方,后者是调用 fork 的那个。

5)和并行流一样,你不应理所当然地认为在多核处理器上使用分支/合并框架就比顺序计算快。一个任务可以分解成多个独立的子任务,才能让性能在并行化时有所提升。所有这些子任务的运行时间都应该比分出新任务所花的时长。

三、工作窃取

工作窃取为何被提出?

如前面的例子,我们指定数组的大小是10000L,即允许任务被拆分为每个数组大小为10000,共1000个任务。

在理想的情况下,每个任务完成的时间应该是相同的,这样在多核cpu的前提下,我们能保证每个核处理的时间都是相同的。

实际情况中,每个子任务花费的时间可以说是天差地别,磁盘,网络,或等等很多的因素导致。

Fork/Join框架为了解决这个提出,提出了工作窃取(work stealing)的概念。

在实际应用中,这意味着这些任务差不多被平均分配到 ForkJoinPool 中的所有线程上。每个线程都为分配给它的任务保存一个双向链式队列,每完成一个任务,就会从队列头上取出下一个任务开始执行。

基于前面所述的原因,某个线程可能早早完成了分配给它的所有任务,也就是它的队列已经空了,而其他的线程还很忙。这时,这个线程并没有闲下来,而是随机选了一个别的线程,从队列的尾巴上“偷走”一个任务。这个过程一直继续下去,直到所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务,这有助于更好地在工作线程之间平衡负载。

一般来说,这种工作窃取算法用于在池中的工作线程之间重新分配和平衡任务。如下图展示了这个过程。当工作线程队列中有一个任务被分成两个子任务时,一个子任务就被闲置的工作线程“偷走”了。如前所述,这个过程可以不断递归,直到规定子任务应顺序执行的条件为真。

image.png

四、Spliterator

那么Stream是如何实现并行的呢?我们并不需要手动去实现Fork/join,这就意味着,肯定有一种自动机制来为你拆分流。这种新的自动机制称为 Spliterator。

Spliterator 是Java 8中加入的另一个新接口;这个名字代表“可分迭代器”(splitableiterator)。和 Iterator 一样, Spliterator 也用于遍历数据源中的元素,但它是为了并行执行而设计的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public interface Spliterator<T> {

/**
* tryAdvance 方法的行为类似于普通的Iterator ,因为它会按顺序一个一个使用 Spliterator 中的元素,
* 并且如果有其他元素要遍历就返回 true
*/
boolean tryAdvance(Consumer<? super T> action);

/**
* 专为 Spliterator 接口设计的,因为它可以把一些元素划出去分
* 给第二个 Spliterator (由该方法返回),让它们两个并行处理。
*/
Spliterator<T> trySplit();

/**
* estimateSize 方法估计还剩下多少元素要遍历
*/
long estimateSize();

int characteristics();
}

4.1 拆分过程

将 Stream 拆分成多个部分的算法是一个递归过程,这个框架不断对 Spliterator 调用 trySplit直到它返回 null ,表明它处理的数据结构不能再分割,流程如下描述。

1)第一步是对第一个Spliterator 调用 trySplit ,生成第二个 Spliterator 。

2)第二步对这两个 Spliterator 调用trysplit ,这样总共就有了四个 Spliterator 。

3)第三步,对当前所有的Spliterator 调用trysplit ,当所有的trysplit 都返回null,则表示拆分结束。

4.2 Spliterator特性

Spliterator的拆分过程也收到其本身的特性所影响,特性是通过characteristics()方法来声明的。

Spliterator 接口声明的最后一个抽象方法是 characteristics ,它将返回一个 int ,代表 Spliterator 本身特性集的编码。

有如下特性:

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
arduino复制代码    /**
* 元素有既定的顺序(例如 List ),因此 Spliterator 在遍历和划分时也会遵循这一顺序
*/
public static final int ORDERED = 0x00000010;

/**
* 对于任意一对遍历过的元素 x 和 y , x.equals(y) 返回 false
*/
public static final int DISTINCT = 0x00000001;

/**
* 遍历的元素按照一个预定义的顺序排序
*/
public static final int SORTED = 0x00000004;

/**
* 该 Spliterator 由一个已知大小的源建立(例如 Set ),因此 estimatedSize() 返回的是准确值
*/
public static final int SIZED = 0x00000040;

/**
* 保证遍历的元素不会为 null
*/
public static final int NONNULL = 0x00000100;

/**
* Spliterator 的数据源不能修改。这意味着在遍历时不能添加、删除或修改任何元素
*/
public static final int IMMUTABLE = 0x00000400;

/**
* 该 Spliterator 的数据源可以被其他线程同时修改而无需同步
*/
public static final int CONCURRENT = 0x00001000;

/**
* 该 Spliterator 和所有从它拆分出来的 Spliterator 都是 SIZED
*/
public static final int SUBSIZED = 0x00004000;

这里做个简单了解就好了,限于篇幅暂时不深入了。


同学们看到这,觉得有学到一丁点知识的给个赞吧~

本文转载自: 掘金

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

《从0学MySQL》之最详细MySQL安装教程(Window

发表于 2021-11-03

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


  • 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)、有任何问题欢迎私信,看到会及时回复!

  • 大家好,我是小诚,这段时间在网上进行了一些面试,发现无论什么公司,数据库的面试题都是不可避免的,甚至一些前端工程师面试的时候都避免不了被询问到和数据库有关的一些问题。
  • 通过面试,也发现了一些现象,网上的很多数据库教程都是讲得比较浅或者只讲解了片面,比较好的教程要么找不到要么就是收费昂贵,为了方便自己复习以及帮助到一些想从全面了解数据库的小伙伴,这段时间在不断恶补数据库的知识,打算出一个关于《从0到1-全面深刻理解MySQL》的教程,教程是以小白视角出发,从最简单的安装数据库到深入理解数据库如何执行SQL语句到数据库如何实现数据存储和查询的全方位讲解。
  • 争取让所有对MySQL库感兴趣的小伙伴都能够从中学习到一些知识,无论是用于面试或者拓展自己的知识广度方面起到一些帮助,当我们对一个知识从”知其然”到”知其所以然”时,涨薪和升职自然也随之而来。
  • 《从0到1-全面深刻理解MySQL系列》第一篇就从最基本的安装MySQL环境开始,感兴趣的小伙伴可以关注我,系列文章会持续更新,一起加油,一起进步!

下载前需要了解的一些概念

  在进入到官网下载的时候,我们会发现官网上提供了很多类型的版本,它们到底是什么意思,哪个才是我们需要的呢?下面就就来简单介绍下常见的,如下:

  1、MySQL Enterprise Edition: Mysql企业版本,包含了最新的特性和管理工具,以及可以提供技术支持(但是是要收费)。

  2、MySQL Cluster CGE: 一个用于高吞吐量快速、稳定的访问数据的开源事务数据库,它包含了MySQL Cluster、MySQL Enterprise Edition、MySQL Cluster Manager的功能。

  3、MySQL Community (GPL):遵循GPL开源协议的MySQL版本,平常我们使用的大多数遵循这个协议下的社区版(它是免费的)

  4、MySQL Installer: 是一个安装管理程序,因为MySQL家族包括了许多产品,所以提供了一个统一管理下载的工具。

  5、MySQL Community Server: MySQL Community (GPL)下的开源社区版本,是使用的数据库开源版本(免费的,盘它)。

  6、Mysql Workbench: 类似navicat是个图形界面UI工具,可以实现远程Mysql数据库访问(一开始不建议直接使用图形化管理工具,建议先通过命令行了解,这样能够更快的认识Mysql,高手都是用命令行的(PS: 如有需要图形化工具的可以私我))。

选择自己需要的版本

  到官网下载自己需要的版本(www.mysql.com/)或者直接到云盘下载(私信我,这种方式更快),下载步骤如下:

  1、进入MySQL官网 =》选择“DOWNLOADS”选项 =》 点击MySQL Community (GPL) Downloads »

  2、根据自己的需要选择“MySQL Community Server(不带图形化界面-推荐)”或者MySQL Workbench(带图形化界面的)

  3、下载安装包(32位电脑下载32位的安装包,64位电脑下载64位的安装包)


  4、解压下载好的安装包

  5、配置环境变量

   环境变量: 指的是当你在命令行属于任意一个值时,win系统会去环境变量池中匹配,如果有匹配到可执行的路径,则直接去对应的路径下进行执行。

  如你输入mysql,然后你在环境变量中配置了你安装的MySQL路径为:D:\mysql8.x\bin,则系统会匹配成D:\mysql8.x\bin\mysql,如果这个目录下有这个可执行文件,则运行,具体步骤如下:。




连接MySQL服务

   1、启动MySQL服务器,并进行登录

   2、如果发现MySQL服务无法启动,则进行下面的配置即可

   (1)、在mysql压缩的路径中添加以下my.ini文件,内容如下(把其中的两处工作路径改为自己的按照路径即可):

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复制代码[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=D:\Mylargeprogram\Mysql\mysql-8.0.12-winx64
# 设置mysql数据库的数据的存放目录
datadir=D:\Mylargeprogram\Mysql\mysql-8.0.12-winx64\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8

   (2)、执行执行 mysqld –initialize-insecure 指令进行配置,安装路径会默认生成一个data文件夹,如下:

   3、输入mysqld –install将mysql注册到Window服务中,如果提示已经存在则跳过

   4、启动mysql服务:net start mysql

   5、连接Mysql服务: mysql -u root -p 回车(默认密码为空,输入密码时直接回车即可,为了安全性,记得修改密码哦),到此Mysql安装完成,可以随便操作了!


   6、修改密码,分为MySQL5.x版本和8.x版本,步骤如下:

  (1)、选中mysql数据库: use mysql

  (2)、修改root用户的密码

1
2
3
4
5
6
java复制代码// 5.x版本的修改
UPDATE USER SET PASSWORD=PASSWORD('你的密码') WHERE USER='root';

// 8.x版本的修改
// 格式: alter 表名 用户名@user表中用户名对应的Host字段值 IDENTIFIED WITH 指定使用哪种加密技术 BY ‘修改后的密码’
ALTER USER root@localhost IDENTIFIED WITH caching_sha2_password BY '123456';

小结

   不积跬步,无以至千里;不积小流,无以成江海。今天播种努力的种子,总会有一天发芽!

   欢迎大家关注,如果觉得文章对你有帮助,不要忘记一键三连哦,你的支持是我创作更加优质文章的动力,有任何问题可以私信我,看到会及时给你答复!。

本文转载自: 掘金

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

干掉 BeanUtils!试试这款 Bean 自动映射工具,

发表于 2021-11-03

平时做项目的时候,经常需要做PO、VO、DTO之间的转换。简单的对象转换,使用BeanUtils基本上是够了,但是复杂的转换,如果使用它的话又得写一堆Getter、Setter方法了。今天给大家推荐一款对象自动映射工具MapStruct,功能真心强大!

SpringBoot实战电商项目mall(50k+star)地址:github.com/macrozheng/…

关于BeanUtils

平时我经常使用Hutool中的BeanUtil类来实现对象转换,用多了之后就发现有些缺点:

  • 对象属性映射使用反射来实现,性能比较低;
  • 对于不同名称或不同类型的属性无法转换,还得单独写Getter、Setter方法;
  • 对于嵌套的子对象也需要转换的情况,也得自行处理;
  • 集合对象转换时,得使用循环,一个个拷贝。

对于这些不足,MapStruct都能解决,不愧为一款功能强大的对象映射工具!

MapStruct简介

MapStruct是一款基于Java注解的对象属性映射工具,在Github上已经有4.5K+Star。使用的时候我们只要在接口中定义好对象属性映射规则,它就能自动生成映射实现类,不使用反射,性能优秀,能实现各种复杂映射。

IDEA插件支持

作为一款非常流行的对象映射工具,MapStruct还提供了专门的IDEA插件,我们在使用之前可以先安装好插件。

项目集成

在SpingBoot中集成MapStruct非常简单,仅续添加如下两个依赖即可,这里使用的是1.4.2.Final版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xml复制代码<dependency>
<!--MapStruct相关依赖-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

基本使用

集成完MapStruct之后,我们来体验下它的功能吧,看看它有何神奇之处!

基本映射

我们先来个快速入门,体验一下MapStruct的基本功能,并聊聊它的实现原理。

  • 首先我们准备好要使用的会员PO对象Member;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码/**
* 购物会员
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {
private Long id;
private String username;
private String password;
private String nickname;
private Date birthday;
private String phone;
private String icon;
private Integer gender;
}
  • 然后再准备好会员的DTO对象MemberDto,我们需要将Member对象转换为MemberDto对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 购物会员Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberDto {
private Long id;
private String username;
private String password;
private String nickname;
//与PO类型不同的属性
private String birthday;
//与PO名称不同的属性
private String phoneNumber;
private String icon;
private Integer gender;
}
  • 然后创建一个映射接口MemberMapper,实现同名同类型属性、不同名称属性、不同类型属性的映射;
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}
  • 接下来在Controller中创建测试接口,直接通过接口中的INSTANCE实例调用转换方法toDto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@ApiOperation(value = "基本映射")
@GetMapping("/baseMapping")
public CommonResult baseTest() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
MemberDto memberDto = MemberMapper.INSTANCE.toDto(memberList.get(0));
return CommonResult.success(memberDto);
}
}
  • 运行项目后在Swagger中测试接口,发现PO所有属性已经成功转换到DTO中去了,Swagger访问地址:http://localhost:8088/swagger-ui

  • 其实MapStruct的实现原理很简单,就是根据我们在Mapper接口中使用的@Mapper和@Mapping等注解,在运行时生成接口的实现类,我们可以打开项目的target目录看下;

  • 下面是MapStruct为MemberMapper生成好的对象映射代码,可以和手写Getter、Setter说再见了!
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复制代码public class MemberMapperImpl implements MemberMapper {
public MemberMapperImpl() {
}

public MemberDto toDto(Member member) {
if (member == null) {
return null;
} else {
MemberDto memberDto = new MemberDto();
memberDto.setPhoneNumber(member.getPhone());
if (member.getBirthday() != null) {
memberDto.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(member.getBirthday()));
}

memberDto.setId(member.getId());
memberDto.setUsername(member.getUsername());
memberDto.setPassword(member.getPassword());
memberDto.setNickname(member.getNickname());
memberDto.setIcon(member.getIcon());
memberDto.setGender(member.getGender());
return memberDto;
}
}
}

集合映射

MapStruct也提供了集合映射的功能,可以直接将一个PO列表转换为一个DTO列表,再也不用一个个对象转换了!

  • 在MemberMapper接口中添加toDtoList方法用于列表转换;
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
List<MemberDto> toDtoList(List<Member> list);
}
  • 在Controller中创建测试接口,直接通过Mapper接口中的INSTANCE实例调用转换方法toDtoList;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@ApiOperation(value = "集合映射")
@GetMapping("/collectionMapping")
public CommonResult collectionMapping() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
List<MemberDto> memberDtoList = MemberMapper.INSTANCE.toDtoList(memberList);
return CommonResult.success(memberDtoList);
}
}
  • 在Swagger中调用接口测试下,PO列表已经转换为DTO列表了。

子对象映射

MapStruct对于对象中包含子对象也需要转换的情况也是有所支持的。

  • 例如我们有一个订单PO对象Order,嵌套有Member和Product对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码/**
* 订单
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Order {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
private Member member;
private List<Product> productList;
}
  • 我们需要转换为OrderDto对象,OrderDto中包含MemberDto和ProductDto两个子对象同样需要转换;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码/**
* 订单Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderDto {
private Long id;
private String orderSn;
private Date createTime;
private String receiverAddress;
//子对象映射Dto
private MemberDto memberDto;
//子对象数组映射Dto
private List<ProductDto> productDtoList;
}
  • 我们只需要创建一个Mapper接口,然后通过使用uses将子对象的转换Mapper注入进来,然后通过@Mapping设置好属性映射规则即可;
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 订单对象映射
* Created by macro on 2021/10/21.
*/
@Mapper(uses = {MemberMapper.class,ProductMapper.class})
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

@Mapping(source = "member",target = "memberDto")
@Mapping(source = "productList",target = "productDtoList")
OrderDto toDto(Order order);
}
  • 接下来在Controller中创建测试接口,直接通过Mapper中的INSTANCE实例调用转换方法toDto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@ApiOperation(value = "子对象映射")
@GetMapping("/subMapping")
public CommonResult subMapping() {
List<Order> orderList = getOrderList();
OrderDto orderDto = OrderMapper.INSTANCE.toDto(orderList.get(0));
return CommonResult.success(orderDto);
}
}
  • 在Swagger中调用接口测试下,可以发现子对象属性已经被转换了。

合并映射

MapStruct也支持把多个对象属性映射到一个对象中去。

  • 例如这里把Member和Order的部分属性映射到MemberOrderDto中去;
1
2
3
4
5
6
7
8
9
10
java复制代码/**
* 会员商品信息组合Dto
* Created by macro on 2021/10/21.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MemberOrderDto extends MemberDto{
private String orderSn;
private String receiverAddress;
}
  • 然后在Mapper中添加toMemberOrderDto方法,这里需要注意的是由于参数中具有两个属性,需要通过参数名称.属性的名称来指定source来防止冲突(这两个参数中都有id属性);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码/**
* 会员对象映射
* Created by macro on 2021/10/21.
*/
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);

@Mapping(source = "member.phone",target = "phoneNumber")
@Mapping(source = "member.birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
@Mapping(source = "member.id",target = "id")
@Mapping(source = "order.orderSn", target = "orderSn")
@Mapping(source = "order.receiverAddress", target = "receiverAddress")
MemberOrderDto toMemberOrderDto(Member member, Order order);
}
  • 接下来在Controller中创建测试接口,直接通过Mapper中的INSTANCE实例调用转换方法toMemberOrderDto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@ApiOperation(value = "组合映射")
@GetMapping("/compositeMapping")
public CommonResult compositeMapping() {
List<Order> orderList = LocalJsonUtil.getListFromJson("json/orders.json", Order.class);
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
Member member = memberList.get(0);
Order order = orderList.get(0);
MemberOrderDto memberOrderDto = MemberMapper.INSTANCE.toMemberOrderDto(member,order);
return CommonResult.success(memberOrderDto);
}
}
  • 在Swagger中调用接口测试下,可以发现Member和Order中的属性已经被映射到MemberOrderDto中去了。

进阶使用

通过上面的基本使用,大家已经可以玩转MapStruct了,下面我们再来介绍一些进阶的用法。

使用依赖注入

上面我们都是通过Mapper接口中的INSTANCE实例来调用方法的,在Spring中我们也是可以使用依赖注入的。

  • 想要使用依赖注入,我们只要将@Mapper注解的componentModel参数设置为spring即可,这样在生成接口实现类时,MapperStruct会为其添加@Component注解;
1
2
3
4
5
6
7
8
9
10
java复制代码/**
* 会员对象映射(依赖注入)
* Created by macro on 2021/10/21.
*/
@Mapper(componentModel = "spring")
public interface MemberSpringMapper {
@Mapping(source = "phone",target = "phoneNumber")
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
MemberDto toDto(Member member);
}
  • 接下来在Controller中使用@Autowired注解注入即可使用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@Autowired
private MemberSpringMapper memberSpringMapper;

@ApiOperation(value = "使用依赖注入")
@GetMapping("/springMapping")
public CommonResult springMapping() {
List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
MemberDto memberDto = memberSpringMapper.toDto(memberList.get(0));
return CommonResult.success(memberDto);
}
}
  • 在Swagger中调用接口测试下,可以发现与之前一样可以正常使用。

使用常量、默认值和表达式

使用MapStruct映射属性时,我们可以设置属性为常量或者默认值,也可以通过Java中的方法编写表达式来自动生成属性。

  • 例如下面这个商品类Product对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码/**
* 商品
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class Product {
private Long id;
private String productSn;
private String name;
private String subTitle;
private String brandName;
private BigDecimal price;
private Integer count;
private Date createTime;
}
  • 我们想把Product转换为ProductDto对象,id属性设置为常量,count设置默认值为1,productSn设置为UUID生成;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* 商品Dto
* Created by macro on 2021/10/12.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductDto {
//使用常量
private Long id;
//使用表达式生成属性
private String productSn;
private String name;
private String subTitle;
private String brandName;
private BigDecimal price;
//使用默认值
private Integer count;
private Date createTime;
}
  • 创建ProductMapper接口,通过@Mapping注解中的constant、defaultValue、expression设置好映射规则;
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码/**
* 商品对象映射
* Created by macro on 2021/10/21.
*/
@Mapper(imports = {UUID.class})
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);

@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product);
}
  • 接下来在Controller中创建测试接口,直接通过接口中的INSTANCE实例调用转换方法toDto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "使用常量、默认值和表达式")
@GetMapping("/defaultMapping")
public CommonResult defaultMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setId(100L);
product.setCount(null);
ProductDto productDto = ProductMapper.INSTANCE.toDto(product);
return CommonResult.success(productDto);
}
}
  • 在Swagger中调用接口测试下,对象已经成功转换。

在映射前后进行自定义处理

MapStruct也支持在映射前后做一些自定义操作,类似AOP中的切面。

  • 由于此时我们需要创建自定义处理方法,创建一个抽象类ProductRoundMapper,通过@BeforeMapping注解自定义映射前操作,通过@AfterMapping注解自定义映射后操作;
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复制代码/**
* 商品对象映射(自定义处理)
* Created by macro on 2021/10/21.
*/
@Mapper(imports = {UUID.class})
public abstract class ProductRoundMapper {
public static ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);

@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
public abstract ProductDto toDto(Product product);

@BeforeMapping
public void beforeMapping(Product product){
//映射前当price<0时设置为0
if(product.getPrice().compareTo(BigDecimal.ZERO)<0){
product.setPrice(BigDecimal.ZERO);
}
}

@AfterMapping
public void afterMapping(@MappingTarget ProductDto productDto){
//映射后设置当前时间为createTime
productDto.setCreateTime(new Date());
}
}
  • 接下来在Controller中创建测试接口,直接通过Mapper中的INSTANCE实例调用转换方法toDto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {

@ApiOperation(value = "在映射前后进行自定义处理")
@GetMapping("/customRoundMapping")
public CommonResult customRoundMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setPrice(new BigDecimal(-1));
ProductDto productDto = ProductRoundMapper.INSTANCE.toDto(product);
return CommonResult.success(productDto);
}
}
  • 在Swagger中调用接口测试下,可以发现已经应用了自定义操作。

处理映射异常

代码运行难免会出现异常,MapStruct也支持处理映射异常。

  • 我们需要先创建一个自定义异常类;
1
2
3
4
5
6
7
8
9
java复制代码/**
* 商品验证异常类
* Created by macro on 2021/10/22.
*/
public class ProductValidatorException extends Exception{
public ProductValidatorException(String message) {
super(message);
}
}
  • 然后创建一个验证类,当price设置小于0时抛出我们自定义的异常;
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 商品验证异常处理器
* Created by macro on 2021/10/22.
*/
public class ProductValidator {
public BigDecimal validatePrice(BigDecimal price) throws ProductValidatorException {
if(price.compareTo(BigDecimal.ZERO)<0){
throw new ProductValidatorException("价格不能小于0!");
}
return price;
}
}
  • 之后我们通过@Mapper注解的uses属性运用验证类;
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码/**
* 商品对象映射(处理映射异常)
* Created by macro on 2021/10/21.
*/
@Mapper(uses = {ProductValidator.class},imports = {UUID.class})
public interface ProductExceptionMapper {
ProductExceptionMapper INSTANCE = Mappers.getMapper(ProductExceptionMapper.class);

@Mapping(target = "id",constant = "-1L")
@Mapping(source = "count",target = "count",defaultValue = "1")
@Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
ProductDto toDto(Product product) throws ProductValidatorException;
}
  • 然后在Controller中添加测试接口,设置price为-1,此时在进行映射时会抛出异常;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码/**
* MapStruct对象转换测试Controller
* Created by macro on 2021/10/21.
*/
@RestController
@Api(tags = "MapStructController", description = "MapStruct对象转换测试")
@RequestMapping("/mapStruct")
public class MapStructController {
@ApiOperation(value = "处理映射异常")
@GetMapping("/exceptionMapping")
public CommonResult exceptionMapping() {
List<Product> productList = LocalJsonUtil.getListFromJson("json/products.json", Product.class);
Product product = productList.get(0);
product.setPrice(new BigDecimal(-1));
ProductDto productDto = null;
try {
productDto = ProductExceptionMapper.INSTANCE.toDto(product);
} catch (ProductValidatorException e) {
e.printStackTrace();
}
return CommonResult.success(productDto);
}
}
  • 在Swagger中调用接口测试下,发现运行日志中已经打印了自定义异常信息。

总结

通过上面对MapStruct的使用体验,我们可以发现MapStruct远比BeanUtils要强大。当我们想实现比较复杂的对象映射时,通过它可以省去写Getter、Setter方法的过程。 当然上面只是介绍了MapStruct的一些常用功能,它的功能远不止于此,感兴趣的朋友可以查看下官方文档。

参考资料

官方文档:mapstruct.org/documentati…

项目源码地址

github.com/macrozheng/…

本文 GitHub github.com/macrozheng/… 已经收录,欢迎大家Star!

本文转载自: 掘金

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

Golang环境安装

发表于 2021-11-03

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

Go语言简介

什么是Go语言

  Go语言是谷歌2009发布的第二款开源编程语言。

  Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

  Go 语言具有很强的表达能力,它简洁、清晰而高效。得益于其并发机制, 用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。

  Go 代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制。 它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。

Go语言起源

  Go 语 言 起 源 2007 年, 并 于 2009 年 正 式 对 外 发 布。 它 从 2009 年 9 月 21 日 开 始 作 为 谷 歌 公 司 20% 兼 职 项 目, 即 相 关 员 工 利 用 20% 的 空 余 时 间 来 参 与 Go 语 言 的 研 发 工 作。 该 项 目 的 三 位 领 导 者 均 是 著 名 的 IT 工 程 师: Robert Griesemer, 参 与 开 发 Java HotSpot 虚 拟 机; Rob Pike, Go 语 言 项 目 总 负 责 人, 贝 尔 实 验 室 Unix 团 队 成 员, 参 与 的 项 目 包 括 Plan 9, Inferno 操 作 系 统 和 Limbo 编 程 语 言; Ken Thompson, 贝 尔 实 验 室 Unix 团 队 成 员, C 语 言、 Unix 和 Plan 9 的 创 始 人 之 一, 与 Rob Pike 共 同 开 发 了 UTF-8 字 符 集 规 范。 自 2008 年 1 月 起, Ken Thompson 就 开 始 研 发 一 款 以 C 语 言 为 目 标 结 果 的 编 译 器 来 拓 展 Go 语 言 的 设 计 思 想。

Ivo Balbaert. Go入门指南——The Way to Go(中文版) (Kindle 位置 112-117). 人民邮电出版社.

Go语言特点

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、数组安全、编译迅速

GO语言应用场景

  • 服务器编程, 如处理日志、数据打包、虚拟机处理、文件系统等
  • 分布式系统 (数据库代理、中间件)
  • 网络编程 (web应用、api应用)
  • 云平台 例如 docker、k8s都是go语言开发的

Go语言官网

1
arduino复制代码https://golang.org/

如果访问速度慢或者无法访问请访问谷歌中国官网

1
arduino复制代码https://golang.google.cn/

Go语言windows/.macOS/linux/raspberryPi开发环境安装是编程的第一步,也是最简单的. 现在我们就就开始安装最新的go语言开发环境.这篇文章的时候go版本号1.12.9 最新的下载页面

从Go 1.13开始,go命令默认使用Go模块镜像和Go运行的校验和数据库来下载和验证模块. 有关这些服务的隐私信息,请参阅proxy.golang.org/privacy ; 有关 配置详细信息, 请参阅 go命令文档,包括如何禁用这些服务器或使用不同的服务器.

windows和mac安装最为简单

windows msi安装Go语言环境

  • 下载安装包msi文件 https://dl.google.com/go/go1.12.9.windows-amd64.msi, 1.12.9 您可以替换成您想要的版本
  • 下载完成之后双击go1.12.9.windows-amd64.msi安装文件,同意协议,选择安装路径,勾选自动给您添加环境变量
  • 检查go是否安装成功, 在git-bash或者powershell或者CMD中敲入 go env
  • 墙内用户设置环境变量goproxy 为https://goproxy.io 或者 https://mirrors.aliyun.com/goproxy/

macOS pkg安装Go语言环境

  • 下载安装包pkg文件 https://dl.google.com/go/go1.12.9.darwin-amd64.pkg, 1.12.9 您可以替换成您想要的版本
  • 下载完成之后双击go1.12.9.darwin-amd64.pkg安装文件,同意协议,选择安装路径,勾选自动给您添加环境变量
  • 检查go是否安装成功, 在terminal中敲入 go env
  • 墙内用户设置环境变量goproxy 为https://goproxy.io 或者 https://mirrors.aliyun.com/goproxy/ vim ~/.bashrc 或者 vim /etc/profile 在文件最后添加 export GOPROXY=https://goproxy.io 或者 export GOPROXY=https://mirrors.aliyun.com/goproxy/ :wq 命令保持文件, 在终端中执行 source /etc/profile 或者 source ~/.bashrc

linux/centos/ubuntu 二进制装Go语言环境

下载安装二进制安装包

1
2
3
4
5
6
7
bash复制代码VERSION='1.12.9';#设置go语言版本 指定您的版本
OS=linux # 可选值 linux darwin
ARCH=amd64 #可选值 但是必须与您的操作系统匹配 amd64, 386, arm (树莓派raspberryPi3), arm64, s390x, ppc64le
GZFILE="go$VERSION.$OS-$ARCH.tar.gz" # 下载的安装名称包含arch
wget "https://dl.google.com/go/${GZFILE}";
rm -rf /usr/local/go; # 删除旧的go安装文件
tar -C /usr/local -xzf $GZFILE; #解压文件到 /usr/local/go目录中

墙内用户设置环境变量goproxy 为https://goproxy.io 或者 https://mirrors.aliyun.com/goproxy/ vim ~/.bashrc 或者 vim /etc/profile 在文件最后添加 export GOPROXY=https://goproxy.io 或者 export GOPROXY=https://mirrors.aliyun.com/goproxy/ :wq 命令保持文件, 在终端中执行 source /etc/profile 或者 source ~/.bashrc

当然以上命令也使用于macOS系统但是要选择适合的arch 操作系统

windows 设置goproxy教程,下载墙外的go依赖包

在Windows Powershell中,您可以执行以下命令.

1
2
3
4
5
ruby复制代码# Enable the go modules feature
$env:GO111MODULE=on
# Set the GOPROXY environment variable
# 或者设置成阿里云的镜像 https://mirrors.aliyun.com/goproxy/
$env:GOPROXY=https://goproxy.io

现在,当您构建并运行应用程序时,go将通过goproxy代理获取依赖项.

如果您的Go版本> = 1.13,则GOPRIVATE环境变量控制go命令认为哪些模块是私有的(不公开),因此不应使用代理或校验和数据库.例如: go version > = 1.13

1
2
3
4
ini复制代码go env -w GOPROXY=https://goproxy.io,direct
# Set environment variable allow bypassing the proxy for selected modules
# 设置.corp.example.com 包名的依赖不使用goproxy代理
go env -w GOPRIVATE=*.corp.example.com

The End

线上交流工具: 在你的terminal中输入 ssh $用户@mojotv.cn

在你的terminal中输入 ssh mojotv.cn hn 查看最新 hacknews

本文转载自: 掘金

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

1…430431432…956

开发者博客

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