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

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


  • 首页

  • 归档

  • 搜索

OkHttp4 RequestBodycreate()弃用

发表于 2021-11-18

OKhttp3已升级到Okhttp4 ,编写语言由java过渡到kotlin

okhttp3经常用到的post提交数据的,RequestBody.create() 已过时,并且换成了kotlin的新特性写法!

okhttp3 post请求的代码(4.0版本已过时) :

1
2
3
4
kotlin复制代码
val request:Request=Request.Builder()
.post(RequestBody.create(MediaType.parse("application/json;charset=utf8"),
"body参数")).build()

okhttp4 post最新请求的代码:

Kotlin版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kotlin复制代码import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody

//String转RequestBody String、ByteArray、ByteString都可以用toRequestBody()
val stringBody ="body参数".toRequestBody("application/json;charset=utf-8".toMediaType())
val request = Request
.Builder()
.post(stringBody)
.build()

//File转RequestBody
val file=File("")
val fileBody=file.asRequestBody("text/x-markdown; charset=utf-8".toMediaType())
val request = MultipartBody.Builder()
.addFormDataPart("file", file.name,fileBody)
.build()

Java版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码
import okhttp3.MediaType.Companion.*;
import okhttp3.RequestBody.Companion.*;

//String转RequestBody String、ByteArray、ByteString都可以用toRequestBody()
MediaType mediaType=MediaType.Companion.parse("application/json;charset=utf-8");
RequestBody stringBody=RequestBody.Companion.create("body参数",mediaType);
Request request=new Request
.Builder()
.post(stringBody)
.build();

//File转RequestBody
MediaType mediaType=MediaType.Companion.parse("text/x-markdown; charset=utf-8");
File file=new File("");
RequestBody fileBody=RequestBody.Companion.create(file,mediaType);
Request request=new MultipartBody.Builder()
.addFormDataPart("file", file.getName(),fileBody)
.build();

官方迁移指南

参考链接

本文转载自: 掘金

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

教你用SQL进行数据分析

发表于 2021-11-18

摘要:采用 SQL 作为数据查询和分析的入口是一种数据全栈的思路。

本文分享自华为云社区《如何使用 SQL 对数据进行分析?》,作者:zuozewei 。

前言

我们通过 OLTP(联机事务处理)系统实时处理用户数据,还需要在 OLAP(联机分析处理)系统中对它们进行分析,今天我们来看下如何使用 SQL 分析数据。

使用 SQL 进行数据分析的几种方式

在 DBMS(数据库管理系统) 中,有些数据库很好地集成了 BI 工具,可以方便我们对收集的数据进行商业分析。

比如在SQL Server 中提供了 BI 分析工具,我们可以通过使用 SQL Server中的 Analysis Services 完成数据挖掘任务。SQL Server 内置了多种数据挖掘算法,比如常用的 EM、K-Means 聚类算法、决策树、朴素贝叶斯和逻辑回归等分类算法,以及神经网络等模型。我们还可以对这些算法模型进行可视化效果呈现,帮我们优化和评估算法模型的好坏。

图片来源::docs.microsoft.com/en-us/analy…

另外 PostgreSQL 是一个免费开源的关系数据库(ORDBMS),它的稳定性非常强,功能强大,在 OLTP 和 OLAP 系统上表现都非常出色。同时在机器学习上,配合 Madlib 项目可以让 PostgreSQL 如虎添翼。Madlib 包括了多种机器学习算法,比如分类、聚类、文本分析、回归分析、关联规则挖掘和验证分析等功能。这样我们可以通过使用 SQL,在 PostgreSQL 中使用各种机器学习算法模型,帮我们进行数据挖掘和分析。

图片来源:cwiki.apache.org/confluence/…

2018 年 Google 将机器学习(Machine Learning)工具集成到了 BigQuery 中,发布了 BigQuery ML,这样开发者就可以在大型的结构化或半结构化的数据集上构建和使用机器学习模型。通过 BigQuery 控制台,开发者可以像使用 SQL 语句一样来完成机器学习模型的训练和预测。

SQLFlow 是蚂蚁金服于 2019 年开源的机器学习工具,我们可以通过使用 SQL 就可以完成机器学习算法的调用,你可以将 SQLFlow 理解为机器学习的翻译器。我们在 SELECT 之后加上 TRAIN 从句就可以完成机器学习模型的训练,在 SELECT 语句之后加上 PREDICT 就可以使用模型来进行预测。这些算法模型既包括了传统的机器学习模型,也包括了基于 Tensorflow、PyTorch 等框架的深度学习模型。

从上图中你能看出 SQLFlow 的使用过程,首先我们可以通过 Jupyter notebook 来完成 SQL 语句的交互。SQLFlow 支持了多种 SQL 引擎,包括 MySQL、Oracle、Hive、SparkSQL 和 Flink 等,这样我们就可以通过 SQL 语句从这些 DBMS 数据库中抽取数据,然后选择想要进行的机器学习算法(包括传统机器学习和深度学习模型)进行训练和预测。不过这个工具刚刚上线,工具、文档、社区还有很多需要完善的地方。

最后一个最常用方法是 SQL+Python,也是我们今天要重点讲解的内容。上面介绍的工具可以说既是 SQL 查询数据的入口,也是数据分析、机器学习的入口。不过这些模块耦合度高,也可能存在使用的问题。一方面工具会很大,比如在安装 SQLFlow 的时候,采用 Docker 方式进行安装,整体需要下载的文件会超过 2G。同时,在进行算法调参、优化的时候也存在灵活度差的情况。因此最直接的方式,还是将 SQL 与数据分析模块分开,采用 SQL 读取数据,然后通过 Python 来进行数据分析的处理。

案例:挖掘购物数据中的频繁项集与关联规则

下面我们通过一个案例来进行具体的讲解。

我们要分析的是购物问题,采用的技术为关联分析。它可以帮我们在大量的数据集中找到商品之间的关联关系,从而挖掘出经常被人们购买的商品组合,一个经典的例子就是“啤酒和尿布”的例子。

今天我们的数据集来自于一个购物样本数据,字段包括了 trans_id(交易 ID)以及 product(商品名称),具体的数据集参考下面的初始化 sql:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sql复制代码DROP TABLE IF EXISTS test_data;
CREATE TABLE test_data (
trans_id INT,
product TEXT
);
INSERT INTO test_data VALUES (1, 'beer');
INSERT INTO test_data VALUES (1, 'diapers');
INSERT INTO test_data VALUES (1, 'chips');
INSERT INTO test_data VALUES (2, 'beer');
INSERT INTO test_data VALUES (2, 'diapers');
INSERT INTO test_data VALUES (3, 'beer');
INSERT INTO test_data VALUES (3, 'diapers');
INSERT INTO test_data VALUES (4, 'beer');
INSERT INTO test_data VALUES (4, 'chips');
INSERT INTO test_data VALUES (5, 'beer');
INSERT INTO test_data VALUES (6, 'beer');
INSERT INTO test_data VALUES (6, 'diapers');
INSERT INTO test_data VALUES (6, 'chips');
INSERT INTO test_data VALUES (7, 'beer');
INSERT INTO test_data VALUES (7, 'diapers');

这里我们采用的关联分析算法是 Apriori 算法,它帮我们查找频繁项集,首先我们需要先明白什么是频繁项集。

频繁项集就是支持度大于等于最小支持度阈值的项集,小于这个最小值支持度的项目就是非频繁项集,而大于等于最小支持度的项集就是频繁项集。支持度是个百分比,指的是某个商品组合出现的次数与总次数之间的比例。支持度越高,代表这个组合出现的频率越大。

我们再来看下 Apriori 算法的基本原理。

Apriori 算法其实就是查找频繁项集 (frequent itemset) 的过程:0.设置一个最小支持度,1.从K=1开始,筛选频繁项集。2.在结果中,组合K+1项集,再次筛选3.循环1、2步。直到找不到结果为止,K-1项集的结果就是最终结果。

我们来看下数据理解一下,下面是所有的订单,以及每笔订单购买的商品:

在这个例子中,“啤酒”出现了 7 次,那么这 7 笔订单中“牛奶”的支持度就是 7/7=1。同样“啤酒 + 尿布”出现了 5 次,那么这 7 笔订单中的支持度就是 5/7=0.71。

同时,我们还需要理解一个概念叫做“置信度”,它表示的是当你购买了商品 A,会有多大的概率购买商品 B,在这个例子中,置信度(啤酒→尿布)=5/7=0.71,代表如果你购买了啤酒,会有 71% 的概率会购买尿布;置信度(啤酒→薯条)=3/7=0.43,代表如果你购买了啤酒,有 43% 的概率会购买薯条。

所以说置信度是个条件概念,指的是在 A 发生的情况下,B 发生的概率是多少。

我们在计算关联关系的时候,往往需要规定最小支持度和最小置信度,这样才可以寻找大于等于最小支持度的频繁项集,以及在频繁项集的基础上,大于等于最小置信度的关联规则。

使用 MADlib+PostgreSQL 完成购物数据的关联分析

针对上面的购物数据关联分析的案例我们可以使用工具自带的关联规则进行分析,下面我们演示使用 PostgreSQL 数据库在 Madlib 工具中都可以找到相应的关联规则,通过写 SQL 的方式就可以完成关联规则的调用分析。

开发环境

  • Windows/MacOS
  • Navicat Premium 11.2.7及以上

服务器环境

  • Centos 7.6
  • Docker
  • PostgreSQL 9.6
  • MADlib 1.4及以上

使用 Docker 安装 MADlib+PostgreSQL

拉取 docker 镜像(这个镜像提供了需要的 postgres 等环境,并没有安装 madlib) :

1
bash复制代码docker pull madlib/postgres_9.6:latest

下载 MADlib github 源码. 假定下载的源码位置为 /home/git-repo/github/madlib:

1
bash复制代码cd /home/git-repo/github && git clone git@github.com:apache/madlib.git

启动容器,并建立本机目录与容器中系统的路径映射,共享的目录在容器和本机之间是读写共享的。

1
bash复制代码docker run -d -it --name madlib -v /home/git-repo/github/madlib:/incubator-madlib/ madlib/postgres_9.6

启动容器后,连接容器编译 MADlib 组件,编译用时约 30 分钟:

1
2
3
4
5
6
7
bash复制代码docker exec -it madlib bash
mkdir /incubator-madlib/build-docker
cd /incubator-madlib/build-docker
cmake ..
make
make doc
make install

在容器中安装 MADlib:

1
bash复制代码src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres install

运行 MADlib 测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash复制代码# Run install check, on all modules:
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres install-check

# Run install check, on a specific module, say svm:
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres install-check -t svm

# Run dev check, on all modules (more comprehensive than install check):
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres dev-check

# Run dev check, on a specific module, say svm:
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres dev-check -t svm

# 如果需要,重新安装 Reinstall MADlib:
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres reinstall

如果需要,先关掉并删除容器,删完再起新容器需要重新安装:

1
2
bash复制代码docker kill madlib
docker rm madlib

用配置好的容器制作新镜像,先查看容器 ID, 在用容器 ID 创建新镜像:

1
2
bash复制代码docker ps -a
docker commit <container id> my/madlib_pg9.6_dev

用新镜像创建新容器:

1
bash复制代码docker run -d -it -p 5432:5432 --name madlib_dev -v /home/my/git-repo/github/madlib:/incubator-madlib/ madlib/postgres_9.6

连接容器进行交互(发现新容器还是没有安装,但是不用编译了,安装也很快,装完测试一下)

1
2
3
4
bash复制代码docker exec -it madlib_dev bash
cd /incubator-madlib/build-docker
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres install
src/bin/madpack -p postgres -c postgres/postgres@localhost:5432/postgres install-check

使用 Navicat 远程连接 PostgreSQL(假定没有修改登录用户和密码,默认没有密码)

最后,新建表并初始化数据:

使用 SQL 完成关联规则的调用分析

最后使用 SQL + MADlib 进行关联分析,这里我们设定了参数最小支持度为 0.25,最小置信度为 0.5。根据条件生成 transactions 中的关联规则,如下所示:

1
2
3
4
5
6
7
8
sql复制代码SELECT * FROM madlib.assoc_rules( .25,            -- 支持度
.5, -- 置信度
'trans_id', -- Transaction id 字段
'product', -- Product 字段
'test_data', -- 输入数据
NULL, -- 输出模式
TRUE -- 详细输出
);

查询结果:

关联规则存储在 assoc_rules 表中:

1
2
sql复制代码SELECT * FROM assoc_rules
ORDER BY support DESC, confidence DESC;

注意:

关联规则会始终创建一个名为的表 assoc_rules。如果要保留多个关联规则表,请在再次运行之前复制该表。

使用 SQL+Python 完成购物数据的关联分析

除此以外,我们还可以直接使用 SQL 完成数据的查询,然后通过 Python 的机器学习工具包完成关联分析。

开发环境

  • Windows/MacOS
  • Navicat Premium 11.2.7及以上
  • Python 3.6

服务器环境

  • Centos 7.6
  • Docker
  • MySQL 5.7

使用 Docker 安装 MySQL

拉取官方镜像(我们这里选择5.7,如果不写后面的版本号则会自动拉取最新版):

1
复制代码docker pull mysql:5.7

检查是否拉取成功:

1
2
3
bash复制代码docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/mysql 5.7 db39680b63ac 2 days ago 437 MB

启动容器:

1
bash复制代码docker run -p 3306:3306 --name mymysql -v $PWD/conf:/etc/mysql/conf.d -v $PWD/logs:/logs -v $PWD/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
  • –name:容器名,此处命名为 mymysql;
  • -e:配置信息,此处配置 mysql 的 root 用户的登陆密码;
  • -p:端口映射,此处映射 主机 3306 端口到容器的 3306 端口;
  • -d:源镜像名,此处为 mysql:5.7;
  • -v:主机和容器的目录映射关系,”:”前为主机目录,之后为容器目录。

检查容器是否正常运行:

1
2
3
bash复制代码[root@VM_0_10_centos ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d1e682cfdf76 mysql:5.7 "docker-entrypoint..." 14 seconds ago Up 13 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mymysql

可以看到容器 ID、容器的源镜像、启动命令、创建时间、状态、端口映射信息、容器名字。

进入 docker 本地连接 MySQL 客户端:

1
2
bash复制代码sudo docker exec -it mymysql bash
mysql -u root -p

设置远程访问账号,并授权远程连接:

1
2
sql复制代码CREATE USER 'zuozewei'@'%' IDENTIFIED WITH mysql_native_password BY 'zuozewei';
GRANT ALL PRIVILEGES ON *.* TO 'zuozewei'@'%';

使用 Navicat 远程连接 MySQL,新建数据库并初始化数据。

编写 Python 脚本完成数据分析

首先我们通过 SQLAlchemy 来完成 SQL 查询,使用 efficient_apriori 工具包的 Apriori 算法。

整个工程一共包括 3 个部分:

  • 第一个部分为数据加载,首先我们通过 sql.create_engine 创建 SQL 连接,然后从数据集表中读取全部的数据加载到 data 中。这里需要配置 MySQL 账户名和密码;
  • 第二步为数据预处理。我们还需要得到一个 transactions 数组,里面包括了每笔订单的信息,其中每笔订单是以集合的形式进行存储的,这样相同的订单中 item 就不存在重复的情况,同时也可以使用 Apriori 工具包直接进行计算;
  • 最后一步,使用 Apriori 工具包进行关联分析,这里我们设定了参数 min_support=0.25,min_confidence=0.5,也就是最小支持度为 0.25,最小置信度为 0.5。根据条件找出 transactions 中的频繁项集 itemsets 和关联规则 rules。

下载依赖库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash复制代码#pip3 install 包名 -i 源的url 临时换源
#清华大学源:https://pypi.tuna.tsinghua.edu.cn/simple/

# 强大的数据结构库,用于数据分析,时间序列和统计等
pip3 install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple/

# python的orm程序
pip3 install SQLAlchemy -i https://pypi.tuna.tsinghua.edu.cn/simple/

# Apriori算法的高效纯Python实现
pip3 install efficient-apriori -i https://pypi.tuna.tsinghua.edu.cn/simple/

# MySQL驱动
pip3 install mysql-connector -i https://pypi.tuna.tsinghua.edu.cn/simple/

具体的代码如下:

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
python复制代码from efficient_apriori import apriori
import sqlalchemy as sql
import pandas as pd

'''
数据加载
'''

# 创建数据库连接
engine = sql.create_engine('mysql+mysqlconnector://zuozewei:zuozewei@server_ip/SQLApriori')
# 查询数据
query = 'SELECT * FROM test_data'
# 加载到 data 中
data = pd.read_sql_query(query, engine)

'''
数据预处理
'''

# 得到一维数组 orders_series,并且将 Transaction 作为 index, value 为 Item 取值
orders_series = data.set_index('trans_id')['product']
# 将数据集进行格式转换
transactions = []
temp_index = 0
for i, v in orders_series.items():
if i != temp_index:
temp_set = set()
temp_index = i
temp_set.add(v)
transactions.append(temp_set)
else:
temp_set.add(v)

'''
数据分析
'''

# 挖掘频繁项集和频繁规则
itemsets, rules = apriori(transactions, min_support=0.25, min_confidence=0.5)

print('频繁项集:', itemsets)
print('关联规则:', rules)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rust复制代码频繁项集: {
1: {('beer',): 7, ('chips',): 3, ('diapers',): 5},
2: {('beer', 'chips'): 3, ('beer', 'diapers'): 5, ('chips', 'diapers'): 2},
3: {('beer', 'chips', 'diapers'): 2}
}

关联规则: [
{chips} -> {beer},
{diapers} -> {beer},
{beer} -> {diapers},
{chips} -> {diapers},
{chips, diapers} -> {beer},
{beer, chips} -> {diapers},
{chips} -> {beer, diapers}
]

从结果中我们能看到购物组合中:

  • 商品个数为 1 的频繁项集有 3 种,分别为 beer(啤酒)、chips(薯条)、diapers(尿布) 等;
  • 商品个数为 2 的频繁项集有 3 种,包括{beer(啤酒), chips(薯条)},{beer(啤酒), diapers(尿布)},{chips(薯条), diapers(尿布)}等;
  • 其中关联规则有 7 种,包括了购买 chips(薯条) 的人也会购买 beer(啤酒),购买 diapers(尿布)的同时也会 beer(啤酒) 等。

总结

通过 SQL 完成数据分析、机器学习还是推荐使用到 Python,因为这是 Python 所擅长的。通过今天的例子我们应该能看到采用 SQL 作为数据查询和分析的入口是一种数据全栈的思路,对于数据开发人员来说降低了数据分析的技术门槛。相信在当今的 DT 时代,我们的业务增长会越来越依靠于 SQL 引擎 + AI 引擎。

参考文献:

[1]:madlib.apache.org/docs/latest…

[2]:sql-machine-learning.github.io/

[3]:www.jianshu.com/p/8e1e64c08…

[4]:《数据分析实战45讲》陈旸 清华大学计算机博士

点击关注,第一时间了解华为云新鲜技术~

本文转载自: 掘金

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

面试腾讯遇到这道题也是不容易呀

发表于 2021-11-18

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

问题描述

179. 最大数

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。

示例:

输入:nums = [10,2]

输出:”210”

分析问题

一开始拿到这个问题,第一印象不就是把整型数组排个序吗,这也太简单了吧,python一行代码搞定,点击提交,瞬间被打脸。连示例都没有跑通。

image.png

image-20211118115801221

对于 nums=[10,2],排序输出后的结果是102,而正确的结果是210。所以我们不能简单的比较。

根据观察,要想得到的数最大,就得保证生成的数的最高位尽可能的大才行,所以我们就需要把数值大的数放在高位。于是,我们通过比较数组的每个元素的最高位,最高位相同的时候比较次高位(这正好不就是字符串比较吗),以此类推,完成排序,然后把它们拼接起来。心里想,这下应该十拿九稳了,直接写代码,提交。

image-20211118115910035

额,再一次被打脸,这我。。。

要不算了吧,直接回去吧,卷不动了。

image.png

怎么能这么轻易放弃呢?搞起来。

我们来看一下,这种排序方式对于输入数组没有相同数字开头的时候是有效的,比如[10,2],但是对于数组中有相同数字开头的情况就不满足了。例如3,30和3,35。

  • 对于3,30 ,因为 330 > 303,所以需要把3放在前面。
  • 对于3,35,因为 353 > 335,所以需要把35放在前面。

因此我们需要比较两个数不同的拼接顺序的结果,进而决定它们在结果中的排列顺序。所以对于数组nums中的任意两个值a和b,我们只能根据拼接后的结果来决定他们的先后顺序。

所以通过上述排序规则,我们就可以求出最大数,证明过程去LeetCode官方解答查看。

下面我们来看一下代码的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码import functools
class Solution:
def solve(self , nums ):
# write code here
strs = map(str, nums)
#根据拼接后的结果判断谁应该放在前面
def cmp(a, b):
if a + b == b + a:
return 0
elif a + b > b + a:
return 1
else:
return -1

strs = sorted(strs, key=functools.cmp_to_key(cmp), reverse=True)
return ''.join(strs) if strs[0]!='0' else '0'

原创不易,如果喜欢,给个三连吧。

本文转载自: 掘金

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

netty(九)初识Netty-Future & Promi

发表于 2021-11-18

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

一、简介

在netty当中,我们需要进行异步处理的时候,经常会调用以下的两个方法:

Future & Promise

其实在我们使用JDK的时候,就知道有一个Future接口,用于异步时接收任务结果。

在Netty当中,基于JDK当中的Future接口,进行了扩展;后面又在Netty的Future基础之上,增加了Promise接口。

关于三者的关系请看以下的类图:

image.png

  • JDK Future:只能同步等待任务结束(无论成功还是失败)才能得到结果。
  • Netty Future:可以同步等待任务结束得到结果,也可以异步方式得到结果,前提是任务必须要结束。
  • Netty Promise:不仅有 netty Future 的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器。

二、扩展了哪些主要能力?

在这一章节,主要分析前面提到的netty新增的两个接口都新增了哪些功能。

功能/名称 jdk Future netty Future Promise
cancel 取消任务 - -
isCanceled 任务是否取消 - -
isDone 任务是否完成,不能区分成功失败 - -
get 获取任务结果,阻塞等待 - -
getNow - 获取任务结果,非阻塞,还未产生结果时返回 null -
await - 等待任务结束,如果任务失败,不会抛异常,而是通过 isSuccess 判断 -
sync - 等待任务结束,如果任务失败,抛出异常 -
isSuccess - 判断任务是否成功 -
cause - 获取失败信息,非阻塞,如果没有失败,返回null -
addLinstener - 添加回调,异步接收结果 -
setSuccess - - 设置成功结果
setFailure - - 设置失败结果

三、使用示例

下面针对Promise我们重点学习,看看针对不同场景下的使用。

例1 同步处理任务成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
csharp复制代码    public static void main(String[] args) throws ExecutionException, InterruptedException {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +", set success: " + 10);
promise.setSuccess(10);
});

System.out.println(Thread.currentThread().getName() +", start...");
//未产生结果,不阻塞,返回null
System.out.println(Thread.currentThread().getName() +", " + promise.getNow());
//阻塞等待结果
System.out.println(Thread.currentThread().getName() +", " + promise.get());
}

结果:

1
2
3
4
less复制代码main, start...
main, null
defaultEventLoop-1-1, set success: 10
main, 10

例2 异步处理任务成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
arduino复制代码    public static void main(String[] args) throws ExecutionException, InterruptedException {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +", set success: " + 10);
promise.setSuccess(10);
});

System.out.println(Thread.currentThread().getName() +", start...");

//添加异步监听,当有结果时调用getNow获取结果
promise.addListener(future->{
System.out.println(Thread.currentThread().getName() +", " + future.getNow());
});
}

结果:

1
2
3
arduino复制代码main, start...
defaultEventLoop-1-1, set success: 10
defaultEventLoop-1-1, 10

例3 同步处理任务失败 sync & get

sync或者get,区别是get会对异常信息再包一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
scss复制代码    public static void main(String[] args) throws ExecutionException, InterruptedException {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +", set failure");
promise.setFailure(new RuntimeException());
});

System.out.println(Thread.currentThread().getName() +", start...");
//未产生结果,不阻塞,返回null
System.out.println(Thread.currentThread().getName() +", " + promise.getNow());
//阻塞等待结果
System.out.println(Thread.currentThread().getName() +", " + promise.get());
//System.out.println(Thread.currentThread().getName() +", " + promise.get());
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码main, start...
main, null
defaultEventLoop-1-1, set failure
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException
at io.netty.util.concurrent.DefaultPromise.get(DefaultPromise.java:349)
at com.cloud.bssp.netty.promise.Test3.main(Test3.java:34)
Caused by: java.lang.RuntimeException
at com.cloud.bssp.netty.promise.Test3.lambda$main$0(Test3.java:27)
at io.netty.channel.DefaultEventLoop.run(DefaultEventLoop.java:54)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

例4 同步处理任务失败 await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
scss复制代码    public static void main(String[] args) throws ExecutionException, InterruptedException {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +", set failure");
promise.setFailure(new RuntimeException());
});

System.out.println(Thread.currentThread().getName() +", start...");
//未产生结果,不阻塞,返回null
System.out.println(Thread.currentThread().getName() +", " + promise.getNow());
//阻塞等待结果
promise.await();
//isSuccess判断任务是否成功
System.out.println(Thread.currentThread().getName() +", " + promise.isSuccess());
}

结果:

1
2
3
4
csharp复制代码main, start...
main, null
defaultEventLoop-1-1, set failure
main, false

例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
scss复制代码    public static void main(String[] args) {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ", set failure");
promise.setFailure(new RuntimeException());
});

System.out.println(Thread.currentThread().getName() + ", start...");

//添加异步监听,当有结果时调用getNow获取结果
promise.addListener(future -> {
if (!promise.isSuccess()) {
//失败查看结果
System.out.println(Thread.currentThread().getName() + ", " + promise.cause());
}
System.out.println(Thread.currentThread().getName() + ", " + future.getNow());
});
}

结果:

1
2
3
4
csharp复制代码main, start...
defaultEventLoop-1-1, set failure
defaultEventLoop-1-1, java.lang.RuntimeException
defaultEventLoop-1-1, null

例6 await 死锁检查

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
csharp复制代码    public static void main(String[] args) {
DefaultEventLoop eventExecutors = new DefaultEventLoop();
DefaultPromise<Integer> promise = new DefaultPromise<>(eventExecutors);

eventExecutors.submit(()->{
System.out.println("1");
try {
promise.await();
// 注意不能仅捕获 InterruptedException 异常
// 否则 死锁检查抛出的 BlockingOperationException 会继续向上传播
// 而提交的任务会被包装为 PromiseTask,它的 run 方法中会 catch 所有异常然后设置为 Promise 的失败结果而不会抛出
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("2");
});
eventExecutors.submit(()->{
System.out.println("3");
try {
promise.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("4");
});
}

结果:

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
less复制代码1
2
3
4
io.netty.util.concurrent.BlockingOperationException: DefaultPromise@6a77ffc2(incomplete)
at io.netty.util.concurrent.DefaultPromise.checkDeadLock(DefaultPromise.java:461)
at io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:246)
at com.cloud.bssp.netty.promise.Test6.lambda$main$0(Test6.java:21)
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
at io.netty.util.concurrent.PromiseTask.run(PromiseTask.java:106)
at io.netty.channel.DefaultEventLoop.run(DefaultEventLoop.java:54)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
io.netty.util.concurrent.BlockingOperationException: DefaultPromise@6a77ffc2(incomplete)
at io.netty.util.concurrent.DefaultPromise.checkDeadLock(DefaultPromise.java:461)
at io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:246)
at com.cloud.bssp.netty.promise.Test6.lambda$main$1(Test6.java:33)
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
at io.netty.util.concurrent.PromiseTask.run(PromiseTask.java:106)
at io.netty.channel.DefaultEventLoop.run(DefaultEventLoop.java:54)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

本文转载自: 掘金

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

Future & FutureTask 使用详解

发表于 2021-11-18

Future 的作用

Future最主要的作用是,比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过Future去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。

Callable 和 Future 的关系

接下来我们介绍下 Callable 和 Future 的关系,前面讲过,Callable 接口相比于 Runnable 的一大优势是可以有返回结果,那这个返回结果怎么获取呢?就可以用 Future 类的 get 方法来获取 。因此,Future 相当于一个存储器,它存储了 Callable 的 call 方法的任务结果。除此之外,我们还可以通过 Future 的 isDone 方法来判断任务是否已经执行完毕了,还可以通过 cancel 方法取消这个任务,或限时获取任务的结果等,总之 Future 的功能比较丰富。有了这样一个从宏观上的概念之后,我们就来具体看一下 Future 类的主要方法。

Future 的方法和用法

首先看一下 Future 接口的代码,一共有 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
java复制代码public interface Future<V> {



    boolean cancel(boolean mayInterruptIfRunning);



    boolean isCancelled();



    boolean isDone();



    V get() throws InterruptedException, ExecutionException;



    V get(long timeout, TimeUnit unit)

        throws InterruptedException, ExecutionException, TimeoutExceptio

}

其中,第 5 个方法是对第 4 个方法的重载,方法名一样,但是参数不一样。

get() 方法:获取结果

get 方法最主要的作用就是获取任务执行的结果,该方法在执行时的行为取决于 Callable 任务的状态,可能会发生以下 5 种情况。

(1)最常见的就是当执行 get 的时候,任务已经执行完毕了,可以立刻返回,获取到任务执行的结果。

(2)任务还没有结果,这是有可能的,比如我们往线程池中放一个任务,线程池中可能积压了很多任务,还没轮到我去执行的时候,就去 get 了,在这种情况下,相当于任务还没开始;还有一种情况是任务正在执行中,但是执行过程比较长,所以我去 get 的时候,它依然在执行的过程中。无论是任务还没开始或在进行中,我们去调用 get 的时候,都会把当前的线程阻塞,直到任务完成再把结果返回回来。

(3)任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出 ExecutionException 异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是 ExecutionException。

(4)任务被取消了,如果任务被取消,我们用 get 方法去获取结果时则会抛出 CancellationException。

(5)任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的 get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。

如图,更加便于理解;

image.png

在图中,右侧是一个线程池,线程池中有一些线程来执行任务。重点在图的左侧,可以看到有一个 submit 方法,该方法往线程池中提交了一个 Task,这个 Task 实现了 Callable 接口,当我们去给线程池提交这个任务的时候,调用 submit 方法会立刻返回一个 Future 类型的对象,这个对象目前内容是空的,其中还不包含计算结果,因为此时计算还没有完成。

当计算一旦完成时,也就是当我们可以获取结果的时候,线程池便会把这个结果填入到之前返回的 Future 中去(也就是 f 对象),而不是在此时新建一个新的 Future。这时就可以利用 Future 的 get 方法来获取到任务的执行结果了。

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复制代码/**

 * 描述:     演示一个 Future 的使用方法

 */

public class OneFuture {



    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        Future<Integer> future = service.submit(new CallableTask());

        try {

            System.out.println(future.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

        service.shutdown();

    }



    static class CallableTask implements Callable<Integer> {



        @Override

        public Integer call() throws Exception {

            Thread.sleep(3000);

            return new Random().nextInt();

        }

    }

}

这段代码中,main 方法新建了一个 10 个线程的线程池,并且用 submit 方法把一个任务提交进去。这个任务如代码的最下方所示,它实现了 Callable 接口,它所做的内容就是先休眠三秒钟,然后返回一个随机数。接下来我们就直接把 future.get 结果打印出来,其结果是正常打印出一个随机数,比如 100192 等。这段代码对应了我们刚才那个图示的讲解,这也是 Future 最常用的一种用法。

isDone() 方法:判断是否执行完毕

下面我们再接着看看 Future 的一些其他方法,比如说 isDone() 方法,该方法是用来判断当前这个任务是否执行完毕了。

需要注意的是,这个方法如果返回 true 则代表执行完成了;如果返回 false 则代表还没完成。但这里如果返回 true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个 isDone 方法而言,它其实也是会返回 true 的,因为对它来说,虽然有异常发生了,但是这个任务在未来也不会再被执行,它确实已经执行完毕了。所以 isDone 方法在返回 true 的时候,不代表这个任务是成功执行的,只代表它执行完毕了。

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复制代码public class GetException {



    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(20);

        Future<Integer> future = service.submit(new CallableTask());





        try {

            for (int i = 0; i < 5; i++) {

                System.out.println(i);

                Thread.sleep(500);

            }

            System.out.println(future.isDone());

            future.get();

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }





    static class CallableTask implements Callable<Integer> {



        @Override

        public Integer call() throws Exception {

            throw new IllegalArgumentException("Callable抛出异常");

        }

    }

}

这段代码中,可以看到有一个线程池,并且往线程池中去提交任务,这个任务会直接抛出一个异常。那么接下来我们就用一个 for 循环去休眠,同时让它慢慢打印出 0 ~ 4 这 5 个数字,这样做的目的是起到了一定的延迟作用。在这个执行完毕之后,再去调用 isDone() 方法,并且把这个结果打印出来,然后再去调用 future.get()。

这里知道这个异常实际上是在任务刚被执行的时候就抛出了,因为我们的计算任务中是没有其他逻辑的,只有抛出异常。我们再来看,控制台是什么时候打印出异常的呢?它是在 true 打印完毕后才打印出异常信息的,也就是说,在调用 get 方法时打印出的异常。

cancel 取消任务的执行

下面我们再来看一下 cancel 方法,如果不想执行某个任务了,则可以使用 cancel 方法,会有以下三种情况:

第一种情况最简单,那就是当任务还没有开始执行时,一旦调用 cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。

第二种情况也比较简单。如果任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。

第三种情况比较特殊,就是这个任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,而是会根据我们传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作 mayInterruptIfRunning,它是什么含义呢?如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,这个比较好理解。如果传入的是 false 则就代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。

那么如何选择传入 true 还是 false 呢?

  • 如果我们明确知道这个线程不能处理中断,那应该传入 false。
  • 我们不知道这个任务是否支持取消(是否能响应中断),因为在大多数情况下代码是多人协作的,对于这个任务是否支持中断,我们不一定有十足的把握,那么在这种情况下也应该传入 false。
  • 如果这个任务一旦开始运行,我们就希望它完全的执行完毕。在这种情况下,也应该传入 false。
    这就是传入 true 和 false 的不同含义和选择方法。

isCancelled() 方法:判断是否被取消

最后一个方法是 isCancelled 方法,判断是否被取消,它和 cancel 方法配合使用,比较简单。

以上就是关于 Future 的主要方法的介绍了。

用 FutureTask 来创建 Future

除了用线程池的 submit 方法会返回一个 future 对象之外,同样还可以用 FutureTask 来获取 Future 类和任务的结果。

FutureTask 首先是一个任务(Task),然后具有 Future 接口的语义,因为它可以在将来(Future)得到执行的结果。

FutureTask 的代码实现:

1
2
3
4
5
java复制代码public class FutureTask<V> implements RunnableFuture<V>{

 ...

}

它实现了一个接口,这个接口叫作 RunnableFuture。我们再来看一下 RunnableFuture 接口的代码实现:

1
2
3
4
5
java复制代码public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();

}

Runnable、Future、RunnableFuture、FutureTask关系图
image.png

既然 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 又实现了 RunnableFuture 接口,所以 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

典型用法是,把 Callable 实例当作 FutureTask 构造函数的参数,生成 FutureTask 的对象,然后把这个对象当作一个 Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过 FutureTask 获取任务执行的结果。

例:

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
java复制代码/**

 * 描述:     演示 FutureTask 的用法

 */

public class FutureTaskDemo {



    public static void main(String[] args) {

        Task task = new Task();

        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);

        new Thread(integerFutureTask).start();



        try {

            System.out.println("task运行结果:"+integerFutureTask.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}



class Task implements Callable<Integer> {



    @Override

    public Integer call() throws Exception {

        System.out.println("子线程正在计算");

        int sum = 0;

        for (int i = 0; i < 100; i++) {

            sum += i;

        }

        return sum;

    }

}

这段代码中可以看出,首先创建了一个实现了 Callable 接口的 Task,然后把这个 Task 实例传入到 FutureTask 的构造函数中去,创建了一个 FutureTask 实例,并且把这个实例当作一个 Runnable 放到 new Thread() 中去执行,最后再用 FutureTask 的 get 得到结果,并打印出来。

本文转载自: 掘金

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

更改Apollo数据库为oracle

发表于 2021-11-18

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

根据公司领导安排现在需要系统设置 决定使用apollo 有以下两点要求

  1. 注册到自己eureka上面
  2. 因为公司使用的是oracle,所以数据库最好使用oracle 。

所以需要稍微修改以下源码,首先下载源码。

1.问题一

经过apollo文档,找到了解决办法。(1.5.0之前的版本)

1.修改源码注解

1
js复制代码修改com.ctrip.framework.apollo.configservice.ConfigServiceApplication,把@EnableEurekaServer改为@EnableEurekaClient

注意

  1. 博主这里的路径为E:\apollo-oracle-eureka\apollo-configservice\src\main\java\com\ctrip\framework\apollo\configservice\ConfigServiceApplication以供参考。
  2. 然后修改注解,但是这里有个坑,在更改注释时,需要把注解需要的包也加入,这里为import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

2.更改数据库

修改ApolloConfigDB.ServerConfig表中的eureka.service.url,指向自己的Eureka地址。
在这里插入图片描述

3.修改配置文件

在这里插入图片描述

2.问题三

这里我们百度了一下,有分支为oracle版本,这里我们就可以参考一下啦 。地址;www.cnblogs.com/skabyy/p/10…

以上文章有些地方有些模糊,这里补充一下。

  1. 经测试 ojdbc8也可以 博主这里使用的是Nexus进行处理这个jar包
  2. 源码下载好之后我们只需要一个操作,只需要修改 E:\apollo\scripts\build.sh,博主这里只搭建了pro环境,所以只修改连接方式和pro的地址 其他代码不变,修改之后为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码#!/bin/sh

# apollo config db info
apollo_config_db_url=jdbc:oracle:thin:@192.168.2.12:1521/orcl
apollo_config_db_username=APOLLOCONFIG
apollo_config_db_password=123

# apollo portal db info
apollo_portal_db_url=jdbc:oracle:thin:@192.168.2.12:1521/orcl
apollo_portal_db_username=APOLLOPORTAL
apollo_portal_db_password=123


# meta server url, different environments should have different meta server addresses
# dev_meta=http://fill-in-dev-meta-server:8080
# fat_meta=http://fill-in-fat-meta-server:8080
# uat_meta=http://fill-in-uat-meta-server:8080
pro_meta=http://127.0.0.1:8080

1.打包

  1. 如果是linux下,复制到linux服务器,运行scripts/bulid.sh文件,执行./bulid.sh即可。
  2. 如果是window那么直接双击E:\apollo\scripts\build.bat

执行完毕后获取到打好的zip包 。
在这里插入图片描述
运行
解压上文获取的zip包,分别进入进入apollo/apollo-adminservice/scripts/,apollo/apollo-configservice/scripts/,/apollo/apollo-portal/scripts/,运行命令:

1
js复制代码./startup.sh

注意:如果在打包时不修改build.sh/build.bat,直接修改修改每一个服务下的config\application-github.properties的文件也可以
例如修改apollo-configservice\config\application-github.properties连接,但是注意连接信息为oracle的驱动,语法为:

1
2
3
4
> spring.datasource.ur...复制代码spring.datasource.username={ApolloConfig|ApolloPortal}
> spring.datasource.password={password}
>
>

​

本文转载自: 掘金

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

他说:“只是单纯的想用Python收集一些素颜照,做机器学习

发表于 2021-11-18

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

今天,他爬取了 上千张 相亲素颜照。跟我说?刷相亲平台,收集素颜照,训练机器模型。这也能信?
她们明明化妆了。

阅读本文你将收获

  1. 近万张素颜头像;
  2. lxml 解析库初识;
  3. XPath 语法初识;
  4. Cooike 反爬;
  5. 女朋友(没准是意外收获)

Python 采集 19 楼相亲女生头像

从本篇博客开始,你将进入爬虫 120 例的第二个小阶段,requests + lxml 实现爬虫。

requests 相信通过前面 10 个案例,你已经比较熟悉了,接下来我们在其基础上,新增一款爬虫解析库 lxml。该库主要用于 XML,HTML 的解析,而且解析效率非常高,使用它之后,你就可以摆脱编写正则表达式的烦恼了。

目标数据源分析

爬取目标网站

本次抓取目标是 19 楼女生相亲频道,该分类频道截止 7 月 1 日还在持续更新中。

www.19lou.com/r/1/19lnsxq…

以下图片来自网页截图,如有侵权,及时联系橡皮擦哦~

他说:“只是单纯的想用Python收集一些素颜照,做机器学习使用”,“我信你个鬼!”

本次爬取的目标为上图头像图片,文件名保存为标题内容。

使用的 Python 模块

  • requests 模块
  • lxml 模块
  • fake_useragent 模块

重点学习内容

lxml 模块初识。

列表页分析

本次抓取围绕列表页即可完成任务,列表页排序规则如下:

1
2
3
txt复制代码https://www.19lou.com/r/1/19lnsxq.html
https://www.19lou.com/r/1/19lnsxq_2.html
https://www.19lou.com/r/1/19lnsxq-233.html

图片所在标签如下所示,提取工作交给 lxml 模块完成,当然为了联系熟练程度,你依旧可以使用 re 模块完成一版。

他说:“只是单纯的想用Python收集一些素颜照,做机器学习使用”,“我信你个鬼!”

lxml 基础知识

提前通过 pip install lxml 对该库完成安装。

导入该库与该库的基本使用。

1
2
3
4
5
6
python复制代码from lxml import etree
html = "一点HTML代码"
#生成一个 XPath 对象
html=etree.HTML(text)
# 提取数据
html.xpath('//li/a')

上述代码注释中提及的 XPath 对象,关于 XPath,是一门在 XML/HTML 文档中查找信息的语言,大意为通过特定语法在 HTML 中提取数据的语言,基础知识的学习,可以参考 www.w3school.com.cn/xpath/xpath…,最佳的学习技巧是边查边用。

整理需求如下

  1. 批量生成待抓取列表页;
  2. requests 请求目标数据;
  3. lxml 提取目标数据;
  4. 保存图片。

编码时间

在编码时,为了防止直接被反爬识别,所在爬取过程中,增加一个等待时间,限制爬取速度(当然在后续发现没有对 IP 的限制,直接移除即可)。

代码编写过程中,如果出现如下错误,更新 fake_useragent 即可。

1
python复制代码raise FakeUserAgentError('Maximum amount of retries reached')

更新脚本如下:

1
shell复制代码pip install -U fake-useragent

如果依旧失败,建议自己写随机生成 UserAgent 的函数。

一点点反爬

爬取该目标数据时,直接通过 requests 请求目标地址,会返回如下代码,该代码不是目标数据所在页面,即网站存在反爬技术。

直接请求目标网址,得到的响应代码如下图所示,注意红框位置。

他说:“只是单纯的想用Python收集一些素颜照,做机器学习使用”,“我信你个鬼!”

对 requests 请求到的数据进行分析,发现在返回的代码中设置了 Cookie,该值进行反复测试之后,发现为固定值,后续直接通过 requests 参数 headers 设置即可。

获取到目标页面源码之后,就可以通过 lxml 进行页面提取操作了,在前文已经进行了简单的描述。重点学习的分为两个部分内容:

  1. 首先通过 lxml 模块中的 etree 对象,将 HTML 源码进行序列化,即转化为 Element 对象;
  2. 然后对 Element 对象进行解析,这里用到的解析语法是 XPath,本文用到了路径解析,在完整代码部分有注释说明。

完整代码

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
python复制代码import requests
from lxml import etree
from fake_useragent import UserAgent
import time


def save(src, title):
try:
res = requests.get(src)
with open(f"imgs/{title}.jpg", "wb+") as f:
f.write(res.content)
except Exception as e:
print(e)


def run(url):
# ua = UserAgent(cache=False)
ua = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36"
headers = {
"User-Agent": ua,
"Host": "www.19lou.com",
"Referer": "https://www.19lou.com/r/1/19lnsxq-233.html",
"Cookie": "_Z3nY0d4C_=37XgPK9h" # 从反爬代码中获取到的值
}
try:
res = requests.get(url=url, headers=headers)
text = res.text
# 将 html 转换成 Element 对象
html = etree.HTML(text)
# xpath 路径提取 @class 为选取 class 属性
divs = html.xpath("//div[@class='pics']")
# print(len(divs))
# 遍历 Elements 节点
for div in divs:
# 提取地址,注意提取的属性为 data-src 而不是 src
src = div.xpath("./img/@data-src")[0]
# 提取标题
title = div.xpath("./img/@alt")[0]
save(src, title)
except Exception as e:
print(e)


if __name__ == '__main__':
urls = ["https://www.19lou.com/r/1/19lnsxq.html"]
for i in range(114, 243):
urls.append(f"https://www.19lou.com/r/1/19lnsxq-{i}.html")
for url in urls:
print(f"正在抓取{url}")
run(url)
# time.sleep(5)

print("全部爬取完毕")

为了提高效率,你可以取消 5 秒等待,也可以采用多线程,不过尝试几秒钟就好了,不要过度抓取哦,毕竟咱们只为学习。

上述代码还存在一个重要知识点,在获取到的源码中图片的 src 属性为 dot.gif(加载图片),data-src 属性存在值。

具体对比如下图所示,上图为直接查看页面源码,下图为服务器直接返回源码。

这部分给我们的爬取提示为,任何数据的解析提取,都要依据服务器直接返回的源码。

他说:“只是单纯的想用Python收集一些素颜照,做机器学习使用”,“我信你个鬼!”

抓取结果展示时间

爬虫 120 例,第 11 例完成,希望本篇博客能带给你不一样的惊喜与知识。相关资料可以在下面直接获取。

他说:“只是单纯的想用Python收集一些素颜照,做机器学习使用”,“我信你个鬼!”

完整代码下载地址:codechina.csdn.net/hihell/pyth…,NO11。

以下是爬取过程中产生的各种学习数据,如果只需要数据,可去下载频道下载~。

  • 绝密,上万张素颜照,一键拥有!!!

爬取资源仅供学习使用,侵权删。

本文转载自: 掘金

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

Active Database Duplication 一、

发表于 2021-11-18

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

一、介绍

当源数据库不存在备份集,并且磁盘空间不足的情况下,可以通过Active Database Duplication来实现对数据库的复制。

Active Database Duplication不需要源数据库的备份。 通过网络将数据库文件复制到辅助实例,它将实时源数据库复制到目标主机。 RMAN可以将所需文件复制为映像副本或备份集。

_Note:_For active database duplication, the source database must use a server parameter file(SPFILE).

在这种方法中,RMAN客户端作为TARGET连接到源数据库,而作为AUXILIARY连接到辅助实例。

有以下两种方式:

Active Database Duplication Using Image Copies

Active Database Duplication Using Backup Sets

Notes:12.1之后开始支持备份集(Backup Sets)复制。

在某些场景下,使用备份集(Backup Sets)进行主动数据库复制比映像副本(Image Copies)可能更为可取:

a.在复制数据库时想要使用并行多段备份,压缩或加密。

See Also:

  • About Compressing Backup Sets During Active Database Duplication
  • About Parallelizing Backup Set Creation During Active Database Duplication
  • About Encrypting Backup Sets During Active Database Duplication

b.源数据库没有足够的网络资源来传输所需的数据库文件到目标数据库。

c.在源数据库上使用的资源最少,最小化影响源数据库的性能。

二、Active Database Duplication Using Image Copies(push-based method)

源数据库通过网络将所需的数据库文件传输到辅助实例。

Description of Figure 25-4 follows

三、Active Database Duplication Using Backup Sets(pull-based method)

辅助实例通过Oracle Net Services连接到源数据库,并通过网络从源数据库检索所需的数据库文件。

Description of Figure 25-5 follows

满足以下任一条件时,RMAN**使用备份集****(Backup Sets)执行活动数据库复制,否则,RMAN使用映像副本****(Image Copies)**执行活动数据库复制。

1、Compressing Backup Sets During Active Database Duplication

1
2
3
4
bash复制代码DUPLICATE TARGET DATABASE TO dup_db
FROM ACTIVE DATABASE
PASSWORD FILE
USING COMPRESSED BACKUPSET;

2、Parallelizing Backup Set Creation During Active Database Duplication

1
2
3
4
bash复制代码DUPLICATE TARGET DATABASE TO dup_db
FROM ACTIVE DATABASE
PASSWORD FILE
SECTION SIZE 400M;

3、Encrypting Backup Sets During Active Database Duplication

1
2
bash复制代码##在DUPLICATE命令之前使用SET ENCRYPTION ALGORITHM命令来指定加密算法。
SET ENCRYPTION ON IDENTIFIED BY password;

四、注意点

1、需要复制后保持数据库文件名称一致

a.复制数据库配置为使用与源数据库相同的目录结构和文件名

b.如果源数据库使用ASM磁盘组,则重复的数据库必须使用具有相同名称的ASM磁盘组。

c.如果源数据库文件是Oracle Managed Files,则辅助实例必须将DB_CREATE_FILE_DEST参数设置为与源数据库相同的目录位置。

d.如果源数据库中数据库文件的名称包含路径,则该路径名称在重复数据库中必须相同。

e.对于Oracle Real Application Clusters(RAC)环境,请对源数据库和目标数据库的ORACLE_SID参数使用相同的值。

2、源库与复制库目录不一致的情况

a.SET NEWNAME命令

Variable Description
%b

|

Specifies the file name stripped of directory paths. For example, if a data file is named /oradata/prod/financial.dbf, then %b results in financial.dbf.

|
|

%f

|

Specifies the absolute file number of the data file for which the new name is generated. For example, if data file 2 is duplicated, then %f generates the value 2.

|
|

%I

|

Specifies the DBID.

|
|

%N

|

Specifies the tablespace name.

|
|

%U

|

Specifies the following format: data-D-%d_id-%I_TS-%N_FNO-%f

|

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
bash复制代码##按优先级排序
1.SET NEWNAME FOR DATAFILE and SET NEWNAME FOR TEMPFILE
2.SET NEWNAME FOR TABLESPACE
3.SET NEWNAME FOR DATABASE

##Example
##1.该脚本为数据文件1至5和临时文件1指定了新名称。该脚本未为数据文件6设置新名称,因为它位于TOOLS表空间中,该表空间已从重复数据库中排除。
RUN
{
SET NEWNAME FOR DATAFILE 1 TO '/oradata1/system01.dbf';
SET NEWNAME FOR DATAFILE 2 TO '/oradata2/sysaux01.dbf';
SET NEWNAME FOR DATAFILE 3 TO '/oradata3/undotbs01.dbf';
SET NEWNAME FOR DATAFILE 4 TO '/oradata4/users01.dbf';
SET NEWNAME FOR DATAFILE 5 TO '/oradata5/users02.dbf';
SET NEWNAME FOR TEMPFILE 1 TO '/oradatat/temp01.dbf';
DUPLICATE TARGET DATABASE TO dupdb
SKIP TABLESPACE tools
LOGFILE
GROUP 1 ('/duplogs/redo01a.log','/duplogs/redo01b.log') SIZE 4M REUSE,
GROUP 2 ('/duplogs/redo02a.log', '/duplogs/redo02b.log') SIZE 4M REUSE;
}

##2.并使用单个SET NEWNAME命令命名表空间用户中的所有数据文件。 示例完成后,表空间用户的文件名设置为:/oradata4/users01.dbf和/oradata5/users02.dbf。
{
SET NEWNAME FOR TABLESPACE users TO '/oradata%f/%b';
SET NEWNAME FOR DATAFILE 1 TO '/oradata1/system01.dbf';
SET NEWNAME FOR DATAFILE 2 TO '/oradata2/sysaux01.dbf';
SET NEWNAME FOR DATAFILE 3 TO '/oradata3/undotbs01.dbf';
SET NEWNAME FOR TEMPFILE 1 TO '/oradatat/temp01.dbf';
DUPLICATE TARGET DATABASE TO dupdb
SKIP TABLESPACE tools
LOGFILE
GROUP 1 ('/duplogs/redo01a.log','/duplogs/redo01b.log') SIZE 4M REUSE,
GROUP 2 ('/duplogs/redo02a.log','/duplogs/redo02b.log') SIZE 4M REUSE;
}

##3.使用单个SET命令命名数据库中的所有数据文件。
RUN
{
SET NEWNAME FOR DATABASE TO '/oradata/%U';
DUPLICATE TARGET DATABASE TO dupdb
SKIP TABLESPACE tools
LOGFILE
GROUP 1 ('/duplogs/redo01a.log','/duplogs/redo01b.log') SIZE 4M REUSE,
GROUP 2 ('/duplogs/redo02a.log','/duplogs/redo02b.log') SIZE 4M REUSE;
}

对于OMF和ASM数据库文件,必须使用SET NEWNAME … TO NEW命令,而不能显式提供数据库文件的名称。

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
bash复制代码##Example

##1.USER表空间的文件是用OMF托管,因此使用set newname to new
RUN
{
SET NEWNAME FOR TABLESPACE users TO NEW;
SET NEWNAME FOR DATAFILE 3 TO NEW;
SET NEWNAME FOR DATAFILE 1 TO '/oradata1/system01.dbf';
SET NEWNAME FOR DATAFILE 2 TO '/oradata2/sysaux01.dbf';
SET NEWNAME FOR TEMPFILE 1 TO '/oradatat/temp01';
DUPLICATE TARGET DATABASE TO dupdb
SKIP TABLESPACE tools
LOGFILE
GROUP 1 ('/duplogs/redo01a.log','/duplogs/redo01b.log') SIZE 4M REUSE,
GROUP 2 ('/duplogs/redo02a.log','/duplogs/redo02b.log') SIZE 4M REUSE;
}

##2.用SET NEWNAME在ASM创建文件
RUN
{
SET NEWNAME FOR DATAFILE 1 TO "+DGROUP1";
SET NEWNAME FOR DATAFILE 2 TO "+DGROUP2";
.
.
.
DUPLICATE TARGET DATABASE
TO dupdb
FROM ACTIVE DATABASE
SPFILE SET DB_CREATE_FILE_DEST +DGROUP3;
}

b.AUXNAME命令

为重复的数据库文件指定非OMF和非ASM替代名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash复制代码##Example:

##1.以指定文件数据文件1到5的名称
CONFIGURE AUXNAME FOR DATAFILE 1 TO '/oradata1/system01.dbf';
CONFIGURE AUXNAME FOR DATAFILE 2 TO '/oradata2/sysaux01.dbf';
CONFIGURE AUXNAME FOR DATAFILE 3 TO '/oradata3/undotbs01.dbf';
CONFIGURE AUXNAME FOR DATAFILE 4 TO '/oradata4/users01.dbf';
CONFIGURE AUXNAME FOR DATAFILE 5 TO '/oradata5/users02.dbf';
SET NEWNAME FOR TEMPFILE 1 TO '/oradatat/temp01.dbf';
DUPLICATE TARGET DATABASE
TO dupdb
SKIP TABLESPACE tools
LOGFILE
GROUP 1 ('/duplogs/redo01a.log','/duplogs/redo01b.log') SIZE 4M REUSE,
GROUP 2 ('/duplogs/redo02a.log','/duplogs/redo02b.log') SIZE 4M REUSE;

c.DUPLICATE命令的SPFILE子句

设置与重复的数据库文件名相关的所有必需的初始化参数,但DB_FILE_NAME_CONVERT参数除外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bash复制代码##Example

##1.假设源数据库prod在host1上,并将其数据文件存储在非ASM文件系统中。 prod的控制文件位于/ oracle / oradata / prod /中。 您要将源数据库复制到远程主机host2上的数据库dupdb中。 您要将重复的数据库文件存储在ASM磁盘组+ DISK1中。

DUPLICATE TARGET DATABASE TO dupdb
FROM ACTIVE DATABASE
SPFILE
PARAMETER_VALUE_CONVERT '/oracle/oradata/prod/', '+DISK1'
SET DB_CREATE_FILE_DEST +DISK1;

##DUPLICATE命令完成后,将创建重复数据库,其中包含ASM磁盘组+DISK1中的数据文件,联机重做日志文件和控制文件。


##2.假定源数据库prod在host1上,并将其数据文件存储在ASM磁盘组+DISK1中。 您要将目标复制到远程主机host2上的数据库dupdb。 您想要将dupdb的数据文件存储在ASM中。

DUPLICATE TARGET DATABASE
TO dupdb
FROM ACTIVE DATABASE
SPFILE PARAMETER_VALUE_CONVERT '+DISK1','+DISK2'
SET DB_RECOVERY_FILE_DEST_SIZE='750G';

##当DUPLICATE命令完成时,将创建重复的数据库,其中包含较大ASM磁盘组+DISK2中的数据文件,联机重做日志和控制文件。

d.DB_FILE_NAME_CONVERT和LOG_FILE_NAME_CONVERT初始化参数(_Non-MO_F)

限制:

1
2
3
4
bash复制代码1.不能使用DUPLICATE命令的DB_FILE_NAME_CONVERT选项来控制源数据库实例中OMF格式的辅助实例中文件的新名称的生成。
2.不能使用LOG_FILE_NAME_CONVERT初始化参数来控制源数据库实例中OMF格式的重复实例中文件的新名称的生成。
3.如果设置OMF初始化参数,请不要指定LOG_FILE_NAME_CONVERT参数。
4.LOG_FILE_NAME_CONVERT参数不能在DUPLICATE命令中指定为子句,而只能在辅助实例的初始化参数中指定。

_注意:_当复制到没有NOFILENAMECHECK子句的本地主机或远程主机时,请确保不使用源数据库当前正在使用的联机重做日志文件的名称。

3、NOFILENAMECHECK

CONFIGURE AUXNAME命令,SET NEWNAME命令或DB_FILE_NAME_CONVERT参数可能会生成已在目标数据库中使用的名称。 在这种情况下,RMAN在复制期间显示错误。 复制到远程主机时,请使用NOFILENAMECHECK选项来避免出现此错误消息。

_Notes:_复制到本地主机时使用NOFILENAMECHECK会覆盖目标数据库文件。

4、目标端Oracle安装

_Notes:_请确保在源主机和目标主机上都安装了具有相同补丁程序级别的相同版本的Oracle数据库软件。

5、NOOPEN

通过指定NOOPEN参数,防止打开复制数据库到resetlogs。

6、static listener

DG需要使用静态监听。

1
2
3
4
5
6
7
8
bash复制代码SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = orcl)
(ORACLE_HOME = /u01/app/oracle/product/11.2.0/db)
(SID_NAME = orcl)
)
)

本文转载自: 掘金

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

【设计模式系列】工厂模式

发表于 2021-11-18

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

前言

面向对象设计模式分为三类:创建型、结构型和行为型,工厂设计模式是面向对象设计模式中的创建型设计模式之一。

创建型设计模式主要包括:

  • 单例模式
  • 工厂模式
  • 抽象工厂模式
  • 构建者模式
  • 原型模式

工厂设计模式主要用于某个超类有多个子类,需要通过输入条件创建其中一个子类对象时使用。我们可以在工厂类上应用Singleton模式,也可以将工厂方法定义为静态方法。

使用工厂设计模式会将对象的创建责任从调用方(客户端)转移到工厂类中。

工厂模式的结构

在工厂模式中,主要包含以下元素:

  • 超类(可以是一个接口,抽象类或一个具体的类)
  • 一个或多个子类
  • 工厂类

我们使用一个具体的例子来实现,比如根据条件创建计算机Computer,Computer是一个抽象概念,它有两个具体的实现PC机和笔记本Laptop。

首先定义出Computerc抽象类(也可以是一个接口):

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复制代码/**
* @author 小黑说Java
* @ClassName Computer
* @date 2021/11/17
**/
public abstract class Computer {

/**
* 内存
*/
public abstract String getRAM();

/**
* 硬盘
*/
public abstract String getHDD();

/**
* CPU
*/
public abstract String getCPU();

@Override
public String toString() {
return "RAM= " + this.getRAM() + ", HDD=" + this.getHDD() + ", CPU=" + this.getCPU();
}
}

然后定义PC和Laptop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码/**
* @author 小黑说Java
* @ClassName PC
* @date 2021/11/17
**/
public class PC extends Computer {

private String ram;
private String hdd;
private String cpu;

public PC(String ram, String hdd, String cpu) {
this.ram = ram;
this.hdd = hdd;
this.cpu = cpu;
}

@Override
public String getRAM() {
return ram;
}

@Override
public String getHDD() {
return hdd;
}

@Override
public String getCPU() {
return cpu;
}
}

代表笔记本电脑的Laptop:

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复制代码/**
* @author 小黑说Java
* @ClassName Laptop
* @date 2021/11/17
**/
public class Laptop extends Computer{
private String ram;
private String hdd;
private String cpu;

public Laptop(String ram, String hdd, String cpu) {
this.ram = ram;
this.hdd = hdd;
this.cpu = cpu;
}
@Override
public String getRAM() {
return ram;
}

@Override
public String getHDD() {
return hdd;
}

@Override
public String getCPU() {
return cpu;
}
}

有了需要创建的实体类之后,我们接下来定义工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* @author 小黑说Java
* @ClassName ComputerFactory
* @date 2021/11/17
**/
public class ComputerFactory {

public static Computer getComputer(String type, String ram, String hdd, String cpu) {
if ("PC".equalsIgnoreCase(type)) {
return new PC(ram, hdd, cpu);
}
if ("Laptop".equalsIgnoreCase(type)) {
return new Laptop(ram, hdd, cpu);
}
return null;
}
}

这样对于客户端只需要根据需要的类型,调用工厂类的getComputer()方法获取想要的实例了。

接下来我们来通过一个简单的测试模拟工厂类的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码/**
* @author 小黑说Java
* @ClassName FactoryTest
* @date 2021/11/17
**/
public class FactoryTest {

public static void main(String[] args) {
Computer pc = ComputerFactory.getComputer("PC", "16GB", "500GB", "2.4GHz");

Computer laptop = ComputerFactory.getComputer("Laptop", "16GB", "500GB", "2.4GHz");

System.out.println("PC:"+pc);
System.out.println("laptop:"+laptop);
}
}

运行结果:

工厂模式的优势

使用工厂模式具有以下优势:

  • 对具体要创建的类形成接口或抽象类,只需要提供方法定义,不用提供具体实现;
  • 从客户端移除了对实际类的实例化;
  • 代码健壮性更高,降低耦合,扩展性更好;

例如要修改PC的具体实现逻辑,只需要在PC类中修改,对于客户端来说透明。

JDK中的工厂模式

在JDK中有大量的工厂模式,比如:

  • java.util.Calendar类创建实例时,使用getInstance(),对于客户端来讲,不用关注Calendar创建的具体实现;
  • NumberFormat类创建实例时,也是使用getInstance()。

以上就是工厂模式的全部内容,工厂模式相对比较简单,下期内容是抽象工厂模式。

本文转载自: 掘金

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

Spring--手写一个简易的IoC容器,附思路原理

发表于 2021-11-18

依赖注入 DI(Dependecy Injection)

一个Java对象依赖别的对象,一种依赖关系,如service依赖dao,只声明对象不赋值均为null,通过注入自动装配对象实例

控制反转 IoC(Inversion of Control)

不需要控制所有依赖和装配的进行,有容器会自动进行装配

下面的案例,简单的实现IoC容器,可帮助理解依赖注入和控制反转

(在这个demo中是基于maven搭建的,使用了springframework的Autowired,但不重要,自己生成一个注解替换即可)

简单的讲讲实现的流程

  1. 在resource目录下新建一个properties配置文件,用来配置需要自动装配的bean服务和对应限定全类名,它可能长这样:
    image.png
  2. 在需要自动装配的对象使用对应的注解,
    如在UserService类中依赖了UserDao对象,添加一个@Autowired注解(是不是Autowired不重要,注解可自行替换)
1
2
3
4
5
6
7
8
java复制代码public class UserService {
@Autowired
private UserDao userDao;

public User getCurrentLoginUser() {
return userDao.getUserById(1);
}
}
  1. (以下代码贴在最后)启动容器时,用new Properties("[你的配置文件路径]")读取配置文件的内容,获取到的是配置的服务名和其限定全类名
  2. 声明一个Map用于存储即将要通过反射生成的对象实例,遍历properties对象,通过Class.forName("全类名").getConstructor().newInstance()获取对象实例,再通过map.put(服务名,对象实例)存储
  3. 遍历Map,获取反射生成的对象的class对象,调用其getDeclaredFields()方法拿到全部字段,筛选出字段含有@Autowired注解的字段,并返回一个Field对象数组
  4. 遍历这个Field对象数组,拿到字段的名字,并将private的属性设置成可访问状态,调用field.setAccessible(true)后,调用field.set()传入字段对应的对象实例和要赋值的对象,要赋值的对象从Map.get(fieldName)获取
  5. 依赖注入的过程到此结束
  6. 可以通过暴漏一个Api叫getBean的方法,传入bean的名字,返回Map.get([name])来获取Bean实例

(需要通过约束属性名的方式进行注入,即属性名要和配置文件中的一致,否则注入时会找不到Map中对应的实例,属性也会为null)

核心代码

UserService中依赖UserDao,使用注解标识

1
2
3
4
5
6
7
8
9
java复制代码public class UserService {
@Autowired
private UserDao userDao;

public User getCurrentLoginUser() {
// getUserById模拟的返回用户对象
return userDao.getUserById(1);
}
}

UserDao

1
2
3
4
5
6
java复制代码public class UserDao {
public User getUserById(Integer id) {
System.out.println("返回了一个用户");
return new User(id, "user" + id);
}
}

容器代码

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
java复制代码import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

public class MyIoCContainer {
private Map<String, Object> beansCache = new HashMap<>();

public static void main(String[] args) {
// 创建一个容器并启动
MyIoCContainer container = new MyIoCContainer();
container.start();
// 通过getBean获取实例,并调用其方法
UserService userService = (UserService) container.getBean("userService");
userService.getCurrentLoginUser();
}

// 启动该容器
public void start() {
Properties properties = new Properties();
try {
properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
properties.forEach((propertyName, propertyClassName) -> {
try {
beansCache.put((String) propertyName,
Class.forName((String) propertyClassName).getConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
// 使用lambdab表达式简化代码
beansCache.forEach((beanName, beanInstance) -> dependencyInjection(beanInstance, beansCache));
}

// 实现依赖注入
private void dependencyInjection(Object beanInstance, Map<String, Object> beansCache) {
// Stream结合lambda表达式
// 得到所有带@Autowired注解的字段
List<Field> fieldList = Arrays.stream(beanInstance.getClass().getDeclaredFields())
.filter(field -> field.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());

// 自动装配对应的对象实例
fieldList.forEach(field -> {
String fieldName = field.getName();
field.setAccessible(true);
try {
field.set(beanInstance, beansCache.get(fieldName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}

// 从容器中获取一个bean
public Object getBean(String beanName) {
return beansCache.get(beanName);
}
}
UserService中,只声明了userDao属性而没赋值,通过打断点,可以观察到userDao对象成功注入了,如果有多个地方的userDao使用注解注入,则可在调试中观察到得到的是同一个对象实例

image.png

userDao的方法也正常执行,打印输出并会返回一个mock的User对象

image.png

Spring的核心实现会比这个复杂的多,但本质上都是通过配置-反射-自动装配的流程处理程序,并不是要手写出一个Spring框架,而是通过手写类似的模式来加深对Spring的理解。

本文转载自: 掘金

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

1…294295296…956

开发者博客

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