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

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


  • 首页

  • 归档

  • 搜索

Spring Cloud / Alibaba 微服务架构

发表于 2021-11-05

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

代码同样会贴在后续非参加活动的文章中及gitee上~(由于GitHub经常不稳定,所以先上传到gitee上)

之前的文章我们已经搭建了通用和配置两大模块,这篇文章将介绍Alibaba Nacos:服务注册与配置中心。

Alibaba Nacos

目前的微服务解决方案中都将Nacos列为首选,所以我们也将Nacos作为本项目的基本组件之一。

Nacos Console是Nacos为方便用户使用,给用户提供的web ui,我们在完成Nacos的安装并启动之后就可以在浏览器打开这个console页面,以可视化的方式去查看配置信息等。

1、Alibaba Nacos 的基本架构

1)服务

指一个或一组软件功能,例如特定信息的检索或一组操作的执行,使不同的客户端可以为不同的目的去重用,例如通过跨进程的网络调用。Nacos 支持主流的服务生态,如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service等。对应到我们的工程,就是用Spring Cloud RESTful Service去集成Nacos。

2)配置服务

工程在运行过程中可能需要一些动态的配置信息,例如服务的元数据、数据库地址等,此时就可以提供给Nacos存储并管理这些配置信息。Nacos提供的配置服务对标Spring Cloud Config且比它更优秀。

3)名字服务

“名字服务”这个词可能不是很好理解,但是“服务注册”与“服务发现”你一定不陌生,这是微服务架构下不可缺少的一个功能。

服务注册是指我们的微服务把自身的信息,例如IP地址、端口号、服务名称注册到Nacos上。服务发现就是指其他的微服务提供一些信息,通过Nacos去寻找已经注册的微服务,以此来实现微服务之间的互通互联。

2、Alibaba Nacos 概念解读

(此处只介绍Nacos部分最核心最常用的一些概念)

1)服务注册中心

它是服务、实例及元数据的数据库,服务注册中心可能会调用服务实例的健康检查API来验证它是否能够处理请求。

注:我们部署的微服务会将自身信息汇报给Nacos,Nacos会存储这些数据,所以Nacos对于微服务的基本信息来说类似于数据库。同时Nacos为了知道注册的服务实例是否还在存活,会调用微服务的健康检查接口。

2)服务元数据

包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据。

注:即描述服务自身的数据。

3)服务提供/消费方

提供可以复用和可调用服务的应用方,会发起对某个服务调用的应用方。

注:Nacos自身是微服务提供方,提供给微服务去使用,微服务需要知道其他微服务的信息就去Nacos中获取,所以微服务自然就是消费方,消费Nacos提供的服务。

4)配置

在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件形式存在。

注:但其实动态的配置还是比较少的,我们通常只会把限流配置、路由配置这些信息存储到Nacos中,像数据库地址这些信息可能一整个项目都不会改变,那么放到配置中的意义就不大了。

3、Alibaba Nacos 单机版本部署步骤

1)下载所需的版本

github.com/alibaba/nac…

1.0、2.0版本以上都可以,API方面是完全兼容的。本项目使用的是2.0.0。

2)解压

Mac: 执行 tar -xzvf nacos-server-2.0.0.tar.gz

Windows: 直接解压即可。

3)单机模式启动(默认配置就可以)

Mac: 执行 ./startup.sh -m standalone

Windows: 直接启动即可。

4)新建命名空间

服务默认端口号:8848,直接访问127.0.0.1:8848/nacos查看Nacos提供的web页面。默认账号密码都是nacos。

在命名空间菜单下新建一个命名空间给我们的电商项目使用。

image.png

5)给Nacos配置自定义的MySQL持久化(选做)

修改配置,指定MySQL地址、用户名、端口号。

image.png

本文转载自: 掘金

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

捋一捋Python中的Dict(下) dict 的元素定位(

发表于 2021-11-05

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

正式的Python专栏第29篇,同学站住,别错过这个从0开始的文章!

学委前面展示dict的概念和增删查改!这次我们把其他函数介绍补充了。

dict 的元素定位(键/值定位)和遍历

dict这种对象我们可以通过 keys() 获取所有的键的列表;

dict这种对象我们可以通过 values() 获取所有的值的列表.

这样有列表我们可以遍历整个dict对象。

但是更多使用下面的风格:

1
2
3
4
5
6
7
8
perl复制代码mydict = {
'name': 'leixuewei',
'date': '20211104'
}
print("遍历字典-----开始")
for k, v in mydict.items():
print("key[%s] = value[%s]" % (k, v)) # 直接打印
print("遍历字典-----结束")

这几行代码是可以直接复制运行。

下面学委准备了更加全面的代码,展示了获取key/value和遍历操作等:

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
perl复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/3 11:34 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : dict_demo3.py
# @Project : hello


# 比如学委的信息作为一个字典
info_dict = dict()
info_dict['name'] = 'LEI_XUE_WEI' # 或者改为 'levin'
info_dict['blogs'] = '93' # 目前发表了93个博客文字。
info_dict['gongzhong_hao'] = '【雷学委】' # 欢迎过来关注支持
info_dict['meta-data'] = dict()

print("包含特定key ?%s" % ('gongzhong_hao' in info_dict))

print('所有键的列表:%s ' % info_dict.keys())
print('所有值的列表:%s ' % info_dict.values())

print('获取key对应的值:', info_dict['name'])

# 获取但是不设置到info_dict内
print('获取key DemoKey 对应的值:', info_dict.get('DemoKey', '匿名用户雷学委'))
# print('获取key对应的值:', info_dict['DemoKey']) #这句会抛出异常:KeyError: 'DemoKey'

# 如果没有某个key,进行设置默认值并返回
print('获取key name对应的值:', info_dict.setdefault('name', '匿名用户雷学委'))
print('获取key DemoKey 对应的值:', info_dict.setdefault('DemoKey', '匿名用户雷学委'))
print('获取key DemoKey 对应的值:', info_dict['DemoKey'])

print("遍历字典-----开始")
for key, value in info_dict.items():
print("\tkey[%s] = value[%s]" % (key, value))
print("遍历字典-----结束")

info_dict.clear() # 清空整个字典对象

效果如下:

屏幕快照 2021-11-05 上午12.30.35.png

这些操作都是比较基础,但是必须随手就来的,所以必须敲熟了。

dict的复制(拷贝)

字典的复制可以通过copy函数来实现,但是它是浅拷贝,也就是拷贝了但不完全拷贝

简单来说就是拷贝key,但是新字典的值还是沿用旧字典的值的引用。

这里也顺带提一提fromkeys函数,这个创建的新字典,只照搬了key列表,新字典内所有值都是空的。

说这么多,我们看看字典的代码示例:

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
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/3 11:34 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : dict_demo4.py
# @Project : hello

'''dict的拷贝问题'''
# 比如学委的信息作为一个字典
info_dict = dict()
info_dict['name'] = 'LEI_XUE_WEI' # 或者改为 'levin'
info_dict['blogs'] = '93' # 目前发表了93个博客文字。
info_dict['gongzhong_hao'] = '【雷学委】' # 欢迎过来关注支持
info_dict['meta-data'] = dict()

# 选择指定key列表局部拷贝
keys_copied_dict = info_dict.fromkeys(['name', 'blogs', 'zone'])
print("keys_copied_dict :", keys_copied_dict)

# 浅拷贝//Shadow Copy: 全拷贝但不完全拷贝
copied_dict = info_dict.copy()
print("copied_dict :", copied_dict)

shadow_copied_dict = info_dict.copy()
print('shadow_copied_dict:', shadow_copied_dict)

## 雷学委提示:下面一行代码修改了‘meta-data'这个key对应的值,我们并没有直接修改shadow_copied_dict
info_dict['meta-data']['date'] = '04 Nov 2021'

print('info_dict:', info_dict)
print('shadow_copied_dict:', shadow_copied_dict) # 这里输出看到变化也传递给了 shadow_copied_dict


print("*"*16 + "清空info_dict")
info_dict.clear()
print('info_dict:', info_dict) # clear 清空元素后 打印info_dict
print('shadow_copied_dict:', shadow_copied_dict) # clear 清空元素后打印 copied_dict

效果如下:

屏幕快照 2021-11-05 上午12.58.14.png

总结

dict总体使用是非常流畅的除了拷贝的问题,下一篇学委会谈谈拷贝的问题。

希望初学者们把dict熟练掌握。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

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

MySQL表连接为何不走索引

发表于 2021-11-04

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

  1. 关联查询语句

项目开发中的业务需要对多张表进行关联查询,并返回查询结果,其中使用到了客户表和认证记录表,客户表数据大概有十几万、认证记录有几万条,具体的查询语句如下:

1
2
3
4
5
6
7
8
9
sql复制代码SELECT
cr.*,
cc.*,
cu.*
FROM
retailer r
LEFT JOIN customer c1 ON r.CustomerCode = c1.Code
LEFT JOIN customer c2 ON r.parenCustomertCode = c2.EnCode
WHERE r.DeleteMark = 0

实际执行中,却发现数据量并不太大的关联查询持续了几十秒。

  1. 问题分析

2.1 检查分析流程

  1. 针对查询语句过慢的问题,首先使用explain关键字对sql的执行计划进行分析,发现整个查询过程中均没有使用索引,每个表的数据不大,但是三张表联合,数据量直接乘积量级了;
  2. 既然定位到索引问题,就去数据库查看表的索引信息,却发现相关字段已经建立了索引,但是查询过程中却未使用到索引;
  3. 使用左外连接时,数据库会以左表为驱动,右表被驱动,考虑使用inner join替换left join来观察数据库查询是否会进行优化,替换后使用explain发现retailer表作为了被驱动表,且使用了索引,但是customer两个表仍然未走索引,且耗时过长;
  4. 经过对关联表的结构进行深入对比,且对字段类型、长度、编码等信息对比后发现数据表的编码方式存在差异,retailer表中字段字符集格式为utf8mb4,而customer表中字符集格式为utf8;
  5. 将关联字段字符集统一后,使用left join左外连接仍然可以很好的命中索引,查询时间直接0.1s。

2.2 问题总结

  1. 对多张表进行关联查询时,如果被驱动表的连接条件字段添加了索引,且满足使用索引的条件,但是sql语句却没有使用索引,仍然进行了全表查询,则有可能是:
    • 两个表的关联字段类型、长度不一致
    • 两个表的关联字段字符集类型不一致
  2. 对于业务逻辑过于复杂的查询,建议查询关联表数量不要超过3个,可以在代码逻辑层面进行处理,减少数据量过大对数据库服务器造成的压力。

2.3 官方说明

MySQL官方针对关联查询也进行了说明,即在进行表关联时,关联字段需要使用相同的类型、长度以及字符集,否则无法使用索引。

To retrieve rows from other tables when performing joins. MySQL can use indexes on columns more efficiently if they are declared as the same type and size.

For comparisons between nonbinary string columns, both columns should use the same character set. For example, comparing a utf8 column with a latin1 column precludes use of an index.

  1. MySQL的utf8和utf8mb4

既然碰到了字符集不一致的问题,那么就顺便学习一下这俩字符集都是什么。

3.1 UTF是什么

由于对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,英文叫做Unicode Transformation Format,即UTF。

  • UTF-8其中的8表示最小单位是1字节(即8 bits),因此使用时有1、2、3字节等进行编码;
  • 如果是UTF-16则说明字符集最小单位是2个字节,因此只能出现2、4、6字节的编码。

3.2 utf8和utf8mb4

utf8mb4是MySQL5.5.3版本之后增加的字符编码

标准的UTF-8字符集编码是可以使用1-4个字节去编码21位字符,MySQL里面实现的utf8最长使用3个字符,包含了大多数字符,但是像一些不常用汉字或者表情字符就无法满足使用;

utf8mb4,mb4 即 most bytes 4,是utf8的超集并完全兼容utf8,能够用四个字节存储更多的字符,如emoji表情和一些不常用的汉字需要使用四个字符编码,使用utf8mb4字符集可以更好的存储,性能更高。

本文转载自: 掘金

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

dart系列之 dart语言中的变量 简介 dart中的变量

发表于 2021-11-04

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

简介

flutter是google在2015年dart开发者峰会上推出的一种开源的移动UI构建框架,使用flutter可以非常方便的编译成运行在原始android,ios,web等移动平台上的移动应用。

flutter是使用dart来编写的,最新的flutter版本是2.5.3,而最新的Dart语言版本是2.14。

本系列将会深入谈谈dart语言的用法和最佳实践,希望大家能够喜欢。

dart中的变量

Dart语言吸取了java和javascript的精华,如果你是上述语言的开发者,那么会很容易迁移到dart语言上。我们先从一个语言最基本的变量开始,探讨dart语言的奥秘。

定义变量

Dart中定义变量和java,javascript中定义变量是一致的,如下所示:

1
ini复制代码var name = 'jack';

上面我们使用var表示name的类型是可以通过推断得到。在程序编写过程中,如果我们遇到某些变量并不知道其类型的时候,可以尝试使用var,让dart自行推断。

当然,我们也可以指定变量的类型,如上所示,可以指定name为String类型:

1
ini复制代码String name = 'jack';

在dart中,所有的变量都是Object,而每个对象都是一个Class的实例。包括数字、函数、null都是Object。所有的对象都继承自Object类。

所以上面的赋值也可以这样写:

1
ini复制代码Object name = 'jack';

变量的默认值

在dart中,未初始化的变量都有一个nullable类型的值,这个值的初始值是null。

和java中不一样的是,dart中所有的number的初始值也是null。这是因为dart中的number也是一个对象。

如果一个变量可以为null,那么在赋值的时候可以在变量类型后面加上?, 如下所示:

1
arduino复制代码int? age;

对于类变量来说,只会在使用的时候进行初始化,这种初始化策略叫做延时初始化。

Late变量

Late修饰符是在Dart 2.12引入的新特性。他可以表示变量需要被延时加载,或者表示一个不为空的变量会在后续被初始化。

我们可以这样使用:

1
2
3
4
5
6
ini复制代码late int age;

void main() {
age = 18;
print(age);
}

为什么用late呢?因为有时候Dart无法检查某些变量在使用之前是否被初始化了,但是如果你非常确定的话,使用late来修饰它即可。

另外,late修饰的变量只有在使用的时候才会被初始化,所以我们可以使用late来定义一些耗时、耗资源的操作。

常量

如果变量是不会变化的,那么这就不是变量了,而是常量。

常量可以用final或者const来修饰,final变量表示变量只会被赋值一次。

而const变量表示变量会在编译的时候被赋值,默认const也是final的。

如下所示:

1
2
ini复制代码final age = 18; 
final int age = 18;
1
ini复制代码const age = 20;

如果const变量是class变量,那么将其设置为static。

constant还可以用来赋值,如下所示:

1
2
3
ini复制代码var age = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

上面的代码中,虽然age的值是const的,但是age本身并不是const,所以age是可以重新被赋值的:

1
ini复制代码foo = [18, 21, 23];

但是bar是final的,所以bar并不能被重新赋值。

总结

以上就是dart语言的变量使用了。

本文已收录于 <www.flydean.com>

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

本文转载自: 掘金

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

【mongo 系列】mongodb 学习十,MongoDB

发表于 2021-11-04

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

之前说到了主从集群,关于主从集群的搭建以及细节后面会再次分享,这次我们先初步来看看 分片集群

举个例子

例如我们有几百G甚至更多的数据,可是我们只有单个副本集,数据量这么大,网络 IO ,CPU ,内存占用 消耗都会非常的大,一个集群可能吃不消

那么我们可以分片 , 分片也就是分而治之

分片

分片,分为

  • 垂直分布

可以提高集群节点的性能,但是这个方式是有瓶颈的,例如我们一台服务器,内存占用较高,我们可以通过增加服务器的内存在处理

  • 水平分布

例如我们的服务器,内存占用较高,我们可以增加服务器的数量,增加集群的数量,这样服务器一多,维护成本也就线性增加,性能也是线性提升的

实际情况下,如果我们的数据增长非常大,那么肯定会有冷热数据的区分,对于冷数据,我们可以将数据放到差一些的服务器,很少会用到查询操作或者写操作,对于热点高频数据,我们就可以放到高性能的服务器上面,且要做好扩展

为什么要用分片集群?

我们为什么要使用分片集群呢?还是因为数据量太大,需要我们分成一小块一小块的分别来处理,这样才得以解决数据量大的问题

分片集群就是 Mongdb 在多台计算机之间分配数据的集群部署方式

通过上面的例子我们可以知道,就是可以将大型的数据,进行区分成相对更小并且易于管理的小片,将这些数据片分给不同的 mongodb 节点,这些节点,就组成了分片集群

对于分片集群,我们需要熟悉一下如下角色:

Router 路由器:

路由器,这里是 mongos 服务,当做是一个路由器,在客户端程序需要分片的时候可以提供接口

Shard 分片:

每个 Shard 分片包含共享数据的子集,每个Shard 分片是可以部署主从集群的,所以,分片集群,其实就是多个主从集群

config server 配置服务:

配置服务存储集群的元数据和具体的配置设置,mongodb 逐步发展,必须要将配置服务部署为副本集了,这个是 mongodb 3.4 版本开始的

为什么要把 mongos 放到客户端?

  • 可以较少网络开销,例如分片在别的网络上面的时候,**若客户端想要做路由,mongos 就在本地,那么在本地就可以完成路由,**如果 mongos 在服务端,那么就多出相互通信的数据包,增加网络开销
  • 如果 config server 的配置发生改变了,一般的情况下,是需要通过我们的 mongos 来进行修改的,修改后需要重新加载数据

而不是我们登陆到某一个 config server 上修改数据,这样会造成数据混乱,不能这样干

今天暂时了解一下基本的知识点,下一次我们一起来搭建一下 mongodb 的集群,以及部署中可能会遇到的坑

学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

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

spring boot 基础知识

发表于 2021-11-04

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

spring boot 的特性

  • EnableAutoConfigration 自动装配
  • Starter 启动依赖,依赖自动装配的技术
  • Actuator 监控,提供了endpoint,通过http,jmx形式进行访问,health,metries..
  • Spring Boot CLI(命令操作的功能,grovey脚本)spring boot 客户端

spring 注解驱动的发展过程

  • spring 1.x

+ IOC
+ application.xml
  • spring 2.x

+ @Required @Repository /@Aspect
+ spring 2.5
    - @Service
    - @Controller
    - @Compoment
    - @RequestMapping
  • spring 3.x

+ @Configuration 去xml化
+ > 核心目的是:如何将Bean更加快捷的加载到spring ioc容器中
+ @ComponmentScan
+ @Import
+ @Enable驱动模块
  • spring 4.x

+ @Condition 加载条件
  • spring 5.x

+ @Indexed
  • spring IOC bean 的装载
+ xml
+ configration
+ Enable
  • spring 动态 bean的装载

当我们的SpringBoot项目启动的时候,会先导入AutoConfigurationImportSelector,这个类会帮我们选择所有候选的配置,我们需要导入的配置都是SpringBoot帮我们写好的一个一个的配置类,那么这些配置类的位置,存在与META-INF/spring.factories文件中,通过这个文件,Spring可以找到这些配置类的位置,于是去加载其中的配置。

+ ImportSelector:DeferredImportSelector
+ Registotar:ImportBeanDefinitionRegistrar
1
2
3
4
5
6
7
8
9
10
11
public复制代码
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// configuration 或者 beanName
//
return new String[]{RedisConfiguration.class.getName()};
}
}

// config类使用Imoport注解
@Import(LyDefineImportSelector.class)

spring boot 条件加载

AutoConfigurationImportSelector 中实现了了selectImports方法,此方法在加载时会去找到当前包路径的spring.factories文件和spring-autoconfigure-metadata.properties文件,可以在这两个文件中进行必要的条件配置,从而实现spring bean动态条件加载。

  • @ConditionClass
  • spring-autoconfigure-metadata.properties

实现自定义starter

  • 添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码 <dependencies>
<!-- 支持autoconfigure -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.3.1.RELEASE</version>
 </dependency>
<!-- 实现配置解析 -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <version>2.3.1.RELEASE</version>
    <optional>true</optional>
  </dependency>
</dependencies>
  • 实现starter配置文件自动提示
    可以通过配置additional-spring-configuration-metadata.properties配置属性的相关描述信息
    大家参考下面properties表格进行配置上的理解。
名称 类型 目的
name String 属性的全名。名称采用小写的周期分隔形式(例如server.address)。此属性是强制性的。
type String 属性的数据类型的完整签名(例如java.lang.String),但也是完整的泛型类型(例如java.util.Map<java.util.String,acme.MyEnum>)。您可以使用此属性来指导用户可以输入的值的类型。为了保持一致性,通过使用其包装对应项(例如,boolean变为java.lang.Boolean)来指定基元的类型。请注意,此类可能是一个复杂类型,它从Stringas绑定的值转换而来。如果类型未知,则可以省略。
description String 可以向用户显示的组的简短描述。如果没有可用的描述,则可以省略。建议描述为简短段落,第一行提供简明摘要。描述中的最后一行应以句点(.)结尾。
sourceType String 贡献此属性的源的类名称。例如,如果属性来自带注释的类@ConfigurationProperties,则此属性将包含该类的完全限定名称。如果源类型未知,则可以省略。
defaultValue Object 默认值,如果未指定属性,则使用该值。如果属性的类型是数组,则它可以是值数组。如果默认值未知,则可以省略。

deprecation每个properties元素的属性中包含的JSON对象可以包含以下属性:

名称 类型 目的
level String 弃用级别,可以是warning(默认)或error。当属性具有warning弃用级别时,它仍应绑定在环境中。但是,当它具有error弃用级别时,该属性不再受管理且不受约束。
reason String 该属性被弃用的原因的简短描述。如果没有可用的原因,可以省略。建议描述为简短段落,第一行提供简明摘要。描述中的最后一行应以句点(.)结尾。
replacement String 替换此不推荐使用的属性的属性的全名。如果此属性没有替换,则可以省略。

本文转载自: 掘金

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

SpringMVC 基于 Jackson 的数据转换处理

发表于 2021-11-04

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

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

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

这一篇只关注一个小点 , 学习一下 SpringMVC 是如何进行数据转换.

二. 数据承接

2.1 数据转换常见用法

1
2
3
4
5
6
7
8
java复制代码@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date createDate;

@JsonIgnore
private String ignoreField;

@JSONField(name = "age")
private String testAge;

以 fasterxml 为例 , 它属于外包 , 但是 SpringMVC 对其进行了集成 , 那么该功能是如何进行处理的呢 ?

2.2 数据转换的源码梳理

JSON 的转换流程主要为 HttpMessageConverter 模块 , 先来看一下之前的流程图

image.png

可以看到 , 会先通过 HandlerMethodArgumentResolverComposite 对属性进行解析 , 通过 HandlerMethodReturnValueHandlerComposite 对返回进行解析 , 他们都会通过 AbstractMessageConverterMethodArgumentResolver 进行统一的处理.

2.2.1 MessageConverter 的加载和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
java复制代码// 注意 , 该类在 Spring AutoConfigure 中 , 并不是 MVC 专属
private static class MessageConverterInitializer implements Runnable {

@Override
public void run() {
// 此处创建了一个对应的 FormHttpMessageConverter 扩展 ,增加对XML和基于json的部件的支持
new AllEncompassingFormHttpMessageConverter();
}

}

// 其中预加载了多种解析的类
public AllEncompassingFormHttpMessageConverter() {

addPartConverter(new SourceHttpMessageConverter<>());

// JAXB是Java Architecture for XML Binding的缩写,可以将一个Java对象转变成为XML格式,反之亦然
if (jaxb2Present && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
// 处理JSON和XML格式化的类库
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}

// Gson是谷歌官方推出的支持 `JSON -- Java Object` 相互转换的 Java`序列化/反序列化` 库
else if (gsonPresent) {
addPartConverter(new GsonHttpMessageConverter());
}
// 与 JSON 不同 , jsonb是保存为二进制格式的
//- jsonb通常比json占用更多的磁盘空间(有些情况不是)
//- jsonb比json的写入更耗时间
//- json的操作比jsonb的操作明显更耗时间(在操作一个json类型值时需要每次都去解析)
else if (jsonbPresent) {
addPartConverter(new JsonbHttpMessageConverter());
}
// jackson 并不是值处理 JSON 的 , 这个转换器用于读写XML编码数据的扩展组件
if (jackson2XmlPresent) {
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
}
// 可以读写Smile数据格式
if (jackson2SmilePresent) {
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
}
}

- jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
- jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
- jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
- jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
- gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
- jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);

2.2.2 转化的入口

MessageConvert 转换的核心入口为 AbstractMessageConverterMethodArgumentResolver , 来看一下处理逻辑 (之前已经看过相关的代码 , 这里只截取一部分 -> juejin.cn/post/696584…)

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
JAVA复制代码// C- AbstractMessageConverterMethodArgumentResolver # readWithMessageConverters

// 补充一 : messageConverters 列表
// - org.springframework.http.converter.ByteArrayHttpMessageConverter
// - org.springframework.http.converter.StringHttpMessageConverter
// - org.springframework.http.converter.ResourceHttpMessageConverter
// - org.springframework.http.converter.ResourceRegionHttpMessageConverter
// - org.springframework.http.converter.xml.SourceHttpMessageConverter
// - org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
// - org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
// - org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

for (HttpMessageConverter<?> converter : this.messageConverters) {

// 获取 Converter 类
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();

// 如果是 GenericHttpMessageConverter 需要进行转换
// GenericHttpMessageConverter 接口继承了 HttpMessageConverter 接口
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);

// 判断该 converter 是否可以处理该数据
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {

// converter 主要对 RequestBody 和 ResponseBody 的数据进行处理
if (message.hasBody()) {

// 前置处理操作 , 主要是 Advice -> JsonViewRequestBodyAdvice
HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);

// 核心处理 , 进行转换解析操作
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));

// 后置处理
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}

Converter 有很多种 , 此处以 AbstractJackson2HttpMessageConverter 为例 , 其向下的处理流程为 :

  • Step 1 : AbstractJackson2HttpMessageConverter # read : 进入 Converter 解析操作
  • Step 2 : AbstractJackson2HttpMessageConverter # readJavaType : 获取 Java 类型
  • Step 3 : CollectionDeserializer # deserialize : 进入转码解析
  • Step 4 : BeanDeserializer # deserializeFromObject : 循环处理 Object Param
  • Step 5 : deserializeAndSet : 解析并且设置数据

Step 4 : 循环处理 Object Param

最主要的操作就是从 deserializeFromObject 开始 ,此处将value 解析为 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
java复制代码public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{

// ObjectIdReader 对象知道如何反序列化对象id
// 如果 TokenId 表示为属性名 , 则直接处理该参数 , 因为其不需要其他序列化处理
if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
&& _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
return deserializeFromObjectId(p, ctxt);
}
}

// 如果JVM并未给该实体类添加默认无参构造函数 , 此处即为true , 进入如下逻辑
if (_nonStandardCreation) {
// 如果其中一个属性具有“unwrapped”值,则需要单独的helper对象
// PS :
if (_unwrappedPropertyHandler != null) {
return deserializeWithUnwrapped(p, ctxt);
}

// 属性使用外部类型id
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt);
}
Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
if (_injectables != null) {
injectValues(ctxt, bean);
}

return bean;
}

// 使用默认构造器创建一个当前 Body 对应的 Bean
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// 将 Bean 设置到容器中 , 用于后续的序列化处理
p.setCurrentValue(bean);
if (p.canReadObjectId()) {
Object id = p.getObjectId();
if (id != null) {
_handleTypedObjectId(p, ctxt, bean, id);
}
}
if (_injectables != null) {
injectValues(ctxt, bean);
}

// 指示反序列化的某些方面取决于所使用的活动视图
if (_needViewProcesing) {
// 获取激活的视图TODO : 这个地方好像很有趣
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {

// 获取属性名
String propName = p.getCurrentName();

// 核心 : 对属性进行循环操作
do {
// 迭代 token
p.nextToken();

// 获取当前属性对应的元数据参数
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
// 解析并且设置
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}

// 处理无法解析的对象
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}

// PS : TokenID 是什么 ?
// TokenID 是用于返回结果的基本标记类型的枚举 , 因为 JSON 中的数据是无格式的 ,
// 所以需要通过 Token 表示其格式 , JsonTokenId 中共包含了如下类型 :
public final static int ID_NO_TOKEN = 0;
public final static int ID_START_OBJECT = 1;
public final static int ID_END_OBJECT = 2;
public final static int ID_START_ARRAY = 3;
public final static int ID_END_ARRAY = 4;
// 属性名
public final static int ID_FIELD_NAME = 5;
public final static int ID_STRING = 6; // 字符串
public final static int ID_NUMBER_INT = 7; // INT
public final static int ID_NUMBER_FLOAT = 8; // Float
public final static int ID_TRUE = 9;
public final static int ID_FALSE = 10;
public final static int ID_NULL = 11;
public final static int ID_EMBEDDED_OBJECT = 12;

image.png

Step 5 : 数据转换和设置

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
java复制代码public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,Object instance) throws IOException{

Object value;

// null 值得处理
if (p.hasToken(JsonToken.VALUE_NULL)) {
if (_skipNulls) {
return;
}
value = _nullProvider.getNullValue(ctxt);
} else if (_valueTypeDeserializer == null) {
// 此处值已经转换完成 , 此处会有对应的序列化类
// - DateDeserializers
value = _valueDeserializer.deserialize(p, ctxt);

// 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
if (value == null) {
if (_skipNulls) {
return;
}
// 如果处理完还是 null ,则进入 null 值得相关处理
value = _nullProvider.getNullValue(ctxt);
}
} else {
value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
try {

// 最终 : 反射设置到 setter 方法中
// 这也是为什么 setter 不存在时值不会设置得原因
_setter.invoke(instance, value);
} catch (Exception e) {
_throwAsIOE(p, e, value);
}
}

Step 6 : 具体类进行序列化

此处以 DateDeserializers 为例 , 对应得序列化类还有好多 , 有兴趣得可以进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码// C- DateDeserializers
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)throws IOException
{
if (_customFormat != null) {
if (p.hasToken(JsonToken.VALUE_STRING)) {
// 2021-10-21 14:20:55
String str = p.getText().trim();
if (str.length() == 0) {
return (Date) getEmptyValue(ctxt);
}
// 还上锁进行处理了
synchronized (_customFormat) {
try {
// 核心 : format 对时间进行处理
return _customFormat.parse(str);
} catch (ParseException e) {
return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
"expected format "%s"", _formatString);
}
}
}
}
// 如果无法处理, 则交给父类处理 , 很常见得使用方式 , 类似于双亲委派得思路, 棒啊
return super._parseDate(p, ctxt);
}
}

image.png

这里再来回顾下 , DateFormat 是什么时候注入的 ?

在属性进入得时候 , 会进行 createContextual 操作 ,为对象创建一个容器上下文进行处理 (容器的处理很有意思 , 有机会也要看看 , 理解这种思想)

  • CollectionDeserializer # createContextual : 创建容器
  • DeserializationContext # findContextualValueDeserializer : 查找当前 value 对应的序列化类
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 static JsonDeserializer<?> find(Class<?> rawType, String clsName)
{
if (_classNames.contains(clsName)) {
// Start with the most common type
if (rawType == Calendar.class) {
return new CalendarDeserializer();
}
// 最终根据时间类型选择对应的时间序列化类
// 默认构造器中就会设置 -> public DateDeserializer() { super(Date.class); }
if (rawType == java.util.Date.class) {
return DateDeserializer.instance;
}
if (rawType == java.sql.Date.class) {
return new SqlDateDeserializer();
}
if (rawType == Timestamp.class) {
return new TimestampDeserializer();
}
if (rawType == GregorianCalendar.class) {
return new CalendarDeserializer(GregorianCalendar.class);
}
}
return null;
}

容器就像一个小车库, 当准备买车后 , 为他准备各种工具 , 用于自己的保养和维护 , 来解决各种问题 ,为其进行改装 ,不过这种思想 , 在单调的系统中其实很难实现

三 .数据导出

那么数据导出时是如何进行转换处理的呢 ?

write 数据同样通过 for 循环 messageConverters 来进行处理 , 其调用流程如下 :

  • RequestMappingHandlerAdapter # handleInternal : 此时在其中进行 invokeAndHandle 方法进行处理
  • RequestMappingHandlerAdapter # invokeHandlerMethod :
  • ServletInvocableHandlerMethod # invokeAndHandle : 准备 Return Value
  • HandlerMethodReturnValueHandlerComposite # handleReturnValue :
  • RequestResponseBodyMethodProcessor # handleReturnValue :
  • AbstractMessageConverterMethodProcessor # writeWithMessageConverters : 处理 converter 列表

在 writeWithMessageConverters 中核心流程如下 ,我们重点关注一下 :

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
java复制代码for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {

// Step 1 : 与 read 不同得一大点就是没有 afterWrite
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);

if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {

// Step 2 :
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {

}
return;
}
}

可以看到 , 使用的 converter 还是那些 , 处理的逻辑也大致相同 , 其主流程中分为2大部分 :

  • Step 1 : 前置处理 , 调用 RequestResponseBodyAdviceChain 链式处理
  • Step 2 : 调用具体的 converter 进行 write 操作

Advice 主要分为 RequestBodyAdvice 和 ResponseBodyAdvice 两种

mvc-bodyadvice.png

总结

有点偏题了 , 不算 MVC 的核心内容 , 主要是日常使用中出现了未生效的问题, 排查了一下原因 . 顺便出了一篇文档 , 以备以后使用.

Jackson 的底层看的很过瘾 , 很多地方想深入但是精力有限 , 它展现了序列化的核心流程 , 有机会一定要深入的看看

参考文档

developer.aliyun.com/article/769…

本文转载自: 掘金

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

Java 主函数 main 中的关键字 static

发表于 2021-11-04

相信很多人在运行第一个可以运行的 Java 程序的时候都会要求写一个主函数。

然后很多人都会照葫芦画瓢的写一个下面的函数:

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

}

IJ 甚至非常贴心的给你 main 的代码提示:

IJ-main-01

这个函数里面有几个定义和关键字,估计可能会有些绕,和为什么这么写?

近期在学习的时候才翻出来再看看,其中可能比较难理解的是关键字 static

public

这个关键字在这个函数中很好理解,就是表示这个函数是可以被其他类访问到。

void

表示这个函数不返回任何数据。

这个也比较好理解,因为这个函数是主程序的入口,通常是不需要返回结果的。使用 void 也没关系。

String[] args

这个表示的是这个函数可以从执行的时候获得的输入参数。

就是告诉这个函数在执行的时候,你可以在命令行中定义一些参数,然后这个函数通过获得这些参数来对运行进行调整。

通常我们对输入参数的控制会使用。

Apache 的 commons-cli-1.4.jar 来进行处理,因为这个 API 帮我解决了对出入参数进行处理的常用方法,能够简化我们的操作。

这个内容我们在其他地方再单独拿出来说。

static

这个就是我们需要说明一下的重点了。

我们都知道 static 是表示静态的意思,为什么在 main 函数里面需要静态关键字修饰?

static 主要用途是告诉编译器 main 函数是一个静态函数。同时也就是说main 函数中的代码是存储在静态存储区的,即当定义了类以后这段代码就已经存在了。

这个所谓的静态存储区,就是我们常说 JVM 中的堆(Heap),这个 Heap 就是 JVM 垃圾回收管理的区域,也是你经常看到内存溢出的区域。

static关键字主要用于内存管理。我们可以应用 ava static关键字在变量,方法,块和嵌套类中。 static关键字属于类,而不是类的实例。

被 static 关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问。

在

main()

方法中使用静态(static)这个关键字,JVM 将会为这个方法开辟内存空间,你不需要对这个方法进行实例化,因此能够节省不必要的开销。

如果 main 方法不声明为静态的,JVM 就必须创建main类的实例,因为构造器可以被重载,JVM就没法确定调用哪个 main 方法。

因此,在这里这个 static 关键字是必须要有的,否则你的程序可以编译,但是无法运行。

如果使用上面的代码:

1
2
3
typescript复制代码public void main(String[] args) {
System.out.println("RUN");
}

运行的时候将会有错误:

1
2
3
4
vbnet复制代码Error: Main method is not static in class com.ossez.codebank.algorithm.Main, please define the main method as:
public static void main(String[] args)

Process finished with exit code 1

但是编译是不会有错误的。

IJ-main-02

www.ossez.com/t/java-main…

本文转载自: 掘金

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

SpringBoot 操作 ElasticSearch 详解

发表于 2021-11-04

1.ElasticSearch简介

Elasticsearch是位于Elastic Stack核心的分布式搜索和分析引擎。Logstash和Beats有助于收集,聚合和丰富您的数据并将其存储在Elasticsearch中。使用Kibana,您可以交互式地探索,可视化和共享对数据的见解,并管理和监视堆栈。

2.Maven 引入相关依赖

pom.xml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.james</groupId>
<artifactId>elasticsearch_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elasticsearch_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.0</version>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.7</version>
</dependency>

<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.4.0</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.2</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.2</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

3.ElasticSearch 连接配置

(1) application.properties配置
1
2
3
properties复制代码elasticsearch.address=localhost:9200
elasticsearch.username=elastic
elasticsearch.password=elastic
(2) java 连接配置类
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
java复制代码@Configuration
public class ElasticsearchConfig {

@Value("${elasticsearch.address}")
private String address;

@Value("${elasticsearch.username}")
private String userName;

@Value("${elasticsearch.password}")
private String password;

@Bean(destroyMethod = "close")
public RestHighLevelClient restClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(userName, password));


// 拆分地址
List<HttpHost> hostLists = new ArrayList<>();
String[] hostList = address.split(",");
for (String addr : hostList) {
String host = addr.split(":")[0];
String port = addr.split(":")[1];
hostLists.add(new HttpHost(host, Integer.parseInt(port), "http"));
}
// 转换成 HttpHost 数组
HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});

RestClientBuilder builder = RestClient.builder(httpHost).
setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setMaxConnTotal(100)
.setMaxConnPerRoute(100)
.setDefaultCredentialsProvider(credentialsProvider)).
setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder.setConnectTimeout(10000)
.setSocketTimeout(60000));
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;

}
}

4. Java 代码示例

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
java复制代码@Service
@Slf4j
public class ProfileService {


private RestHighLevelClient restClient;
private ObjectMapper objectMapper;

@Autowired
public ProfileService(RestHighLevelClient restClient, ObjectMapper objectMapper) {
this.restClient = restClient;
this.objectMapper = objectMapper;
}

public String createProfileDocument(ProfileDocument document) throws Exception {
UUID uuid = UUID.randomUUID();
document.setId(uuid.toString());
IndexRequest indexRequest = new IndexRequest(INDEX, TYPE, document.getId())
.source(convertProfileDocumentToMap(document));
IndexResponse indexResponse = restClient.index(indexRequest, RequestOptions.DEFAULT);
return indexResponse.getResult().name();
}

public List<ProfileDocument> findAll() throws Exception {
SearchRequest searchRequest = buildSearchRequest(INDEX, TYPE);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse =
restClient.search(searchRequest, RequestOptions.DEFAULT);
return getSearchResult(searchResponse);
}

private SearchRequest buildSearchRequest(String index, String type) {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(index);
searchRequest.types(type);
return searchRequest;
}

private List<ProfileDocument> getSearchResult(SearchResponse response) {
SearchHit[] searchHit = response.getHits().getHits();
List<ProfileDocument> profileDocuments = new ArrayList<>();
for (SearchHit hit : searchHit) {
profileDocuments
.add(objectMapper
.convertValue(hit
.getSourceAsMap(), ProfileDocument.class));
}
return profileDocuments;
}

private Map<String, Object> convertProfileDocumentToMap(ProfileDocument profileDocument) {
return objectMapper.convertValue(profileDocument, Map.class);
}

private ProfileDocument convertMapToProfileDocument(Map<String, Object> map) {
return objectMapper.convertValue(map, ProfileDocument.class);
}

}

本文转载自: 掘金

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

Java基础学习—day03 Java基础学习 1、priv

发表于 2021-11-04

Java基础学习

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

7548c63f4ac26d3ebc8d4aa15945cc12.gif

1、private实现封装处理

如果像想要知道封装,首先必须清楚如果没有封装会怎么样?

没有封装方法中的属性,在所有方法被调用后都可以进行无权限的访问。而当进行了封装操作之后,在实例化对象访问该方法的时候会出现无法访问的问题。

1
2
3
4
5
6
7
java复制代码TestDemo1.java:11: 错误: name 在 Person 中是 private 访问控制
               per.name = "张三";
                  ^
TestDemo1.java:12: 错误: age 在 Person 中是 private 访问控制
               per.age = 13;
                  ^
2 个错误

解决该问题就需要通过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
25
26
27
28
java复制代码class Person{
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void info(){
System.out.println("name = "+ name +" \nage = " + age);
}
}
​
public class TestDemo1{
public static void main(String args[]){
Person per = new Person();
per.setName("张三");
per.setAge(13);
per.info();
}
}

image-20210727174228951

private实现封装的最大特征在于:只允许本类,而不允许外类访问

private只是封装的第一步,如果要学明白,还需要学习多态、继承。

2、构造方法与匿名对象

构造方法就是在使用关键字new实例化新对象的时候进行调用,但是对于构造方法需要遵守以下原则

  • 方法名称必须与类名称相同,并且构造的方法没有返回值,每个类中一定至少有一个构造方法,如果类中没有明确的定义任何一个构造方法,如果类中没有定义任何一个构造方法,那么将自动生成一个无参的构造方法
  • 构造方法是在new对象的时候才执行

疑问:既然构造方法中没有返回数据,为什么不使用void囊?

现在类中的组成:属性、普通方法、构造方法

属性是在对象开辟堆内存的时候开辟空间

构造方法是在使用关键字new的同时调用

普通方法是在new实例化对象完成之后进行调用的

构造方法的作用:

  • 构造方法的调用和对象的内存分配几乎是同步完成,所以可以利用构造方法设置类中的属性内容,构造帆帆噶可以给类中的属性进行初始化
  • 通过构造方法设置内容,实际上可以避免重复的setter调用了
  • 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
25
26
27
28
29
30
31
32
33
34
35
36
java复制代码class Person{
private String name;
private int age;
public Person(){}
public Person(String name){
name = name;
}
public Person(String name, int age){
name = name;
age = age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void info(){
System.out.println("name = "+ name +" \nage = " + age);
}
}
​
public class TestDemo1{
public static void main(String args[]){
Person per = new Person("李四",80);
per.setName("张三");
per.setAge(13);
per.info();
}
}

image-20210727194138758

3、简单java类

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
java复制代码class Emp{
private int empno;
private String ename;
private String job;
private double sal;
private double comm;
public Emp(int no,String name,String j,double s,double c){
setEmpno(no);
setEname(name);
setJob(j);
setSal(s);
setComm(c);
}
public void setEmpno(int no){
empno = no;
}
public void setEname(String name){
ename = name;
}
public void setJob(String job){
job = job;
}
public void setSal(double s){
sal = s;
}
public void setComm(double c ){
comm = c;
}
public int getEmpnoP(){
return empno;
}
public String getEname(){
return ename;
}
public String getJob(){
return job;
}
public double getSal(){
return sal;
}

public double getComm(){
return comm;
}
public String getInfo(){
return "empno = " + empno + "\n" +
"ename = " +ename + "\n" +
"job = " +job + "\n" +
"sal = " +sal + "\n" +
"comm = " +comm ;
}
}
​
public class TestDemo2{
public static void main(String args[]){
//测试类
      System.out.printf(new Emp(2021,"Joker","CEO",2000.0,0.0).getInfo());
}
}

4、数组

数组属于引用类型,所以在使用前一定要开辟空间,本身除了声明并开辟空间之外还有另一种开辟模式,采用我们的分布模式开辟数组空间。如果使用数组不开辟空间,会出现NullPointerException异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public class TestDemo3{
public static void main(String args[]){
//第一种声明的方式
int [] num = new int [3];
//第二种声明数组的方式
int [] num = null;
num = new int [3];
num[0] = 0;
num[1] = 1;
num[2] = 2;
for(int i = 0;i < num.length ; i++){
System.out.println(num[i]);
}
}
}

数组在开发之中一定会使用,但是像一些讲解的时候这么用的数组少了,在以后实际开发,会更多使用数组概念,而直接使用数组99%的情况下只是做for循环数组。

本文转载自: 掘金

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

1…418419420…956

开发者博客

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