Spring Boot:AbstractMethodError

场景

这个异常由虚拟机在运行时抛出。其原因是程序调用了抽象方法,这说明有一些方法没有被实现。在 Java 编译器编译时,会告知类的抽象方法没有实现。一些可能的情况如下:
  • 使用了版本不正确的包(新版本提供了额外的抽象方法)

实际例子

在使用 Jadira Usertype Core 时,在 Spring Boot 2.1.x 与 JPA、Jadira 3.2.0.GA、Joda Money 0.10.0 配合下,出现了 AbstractMethodError 。

解决过程

开始发现这个问题后,我呆滞了半响。花费很多的时间寻求问题无果后,实施排除法。 在排除之前,我先使用官方的 JPA 例子运行一个 JPA 程序,结果发现这个例子能够正确地运行。这说明Spring Boot JPA 本身没有什么问题。加入 自行编写部分 如实体类与 Repository 以及 POM 依赖,执行出现 AbstractMethodError 异常。 逐步减少自行编写部分,在减少 POM 中依赖时发现这个问题发生的原因:Jadira Usertype 使用了不正确的版本。更换更新的的版本后解决了问题。

参考

Oracle Java SE Docs:
https://docs.oracle.com/javase/8/docs/api/java/lang/AbstractMethodError.html

给予帮助何须问这里问那里

纠结之人莫纠结。无意义的纠结不会给你带来任何有意义的结果。

《我给自己写的名言》——codimiralce

为啥要问来问去?

问来问去,对于一些重要的决策而言,的确是需要的。但对于举手之劳而言,又那里需要问来问去,就像慈善机构募捐赈灾过程中,不断问捐款的人会不会捐款一样,多此一举,毫无必要,人家都来捐款了还要你问捐不捐款。。。。这样问来问去不过是对能够判断的事情极度不自信,极度消极,不清楚自己的位置,既毫无作为,又毫无担当的做法而已,总是在心里问我这么做真的好吗?说得有多么善解人意,多么征求他人的见解。实际上,这不过是另外一种披上了“我征求过别人同意才做,大家都好。”的华丽的个人正确的外衣的逃避罢了。

我该这么做吗?

在你这么想的时候,可能你已经想着要做了。如果公正客观来说的确是好事不妨去做啊?想什么乱七八糟的事情,八竿子打不着。这时候就应该问自己这么做会给自己和他人带来痛苦吗?想做的事情多数并不会给自己和他人带来任何痛苦,不过是有抵触心里而已。这比起精神上的痛苦有什么可比的呢?倘若你没做这件事情,你会感到无力的痛苦,感到无动于衷的失败感,落魄感。那你还不如做了这件事情。至少这让你减少消极感。

这么做好吗?

好个**,先回到开头,问这个不过是重蹈覆辙。事实上,自己无法确定,但可以确定的是——每个人做一件事是通过别人评价来确定做的好不好的。但这样是否是好呢?不该是听从内心吗?自己的内心告诉你,你可以这么做,为何要呆呆傻傻的坐着站着不动,仅仅是怕引发后续的事情,达不到别人的期盼吗?到这里还是傻傻的,不愿意做,不会做,拒绝不就好了吗?搞什么,拒绝多不好意思,毕竟是认识的,哪里帮助过自己。这还是傻,你都没办法做,不拒绝干啥子飞机呀,这只会给别人添麻烦而已嘛。反观过来,如果这件事情对于自己,对于他人没有什么坏处,为什么不做呢?不知道有什么值得纠结来纠结去的,为了征求他人同意你这么做吗?这么做除了不愿承担结果还有什么。不要给自己找“我只是想要确定好,再做而已,这样不会有问题。”的借口,明明错误只能避免,不管这么个修补法都会有不可磨灭的印记。

不要问那么多

不要问那么多,问自己会不会因此痛苦。许多的事情根本不需要问这问那,往往迈开步伐,行动就好。2019/9/17 这天的珠海,傍晚的时候下了一场雨,我的两位朋友在食堂等着雨停。在我回到宿舍后,我问她们需不需送伞,在日常生活中的这明明不过是常见的举手之劳而已。可能是觉得我刚刚回到宿舍不好让我送伞,也许是当时雨水也蛮大的所以也就说先等雨变小。 结果雨水变小,我却没有去送伞,让一位好友淋雨去拿伞,自己心情也不好。何必呢?今日看来,回想错过友人的生日,我却也这么问需不需补偿,作为朋友当然会不好意思呀。今日之言,莫忘,谨记。

第一期 编程我建议你定好概念

为什么要注意概念

编程本身是一个思维活动过程,思维的清晰程度体现在每个编程中创建或主观确定的概念上,思路清晰要求我们确立的概念是清楚明了的。每一个概念都是必须清楚而明确的,如果没有明确的概念,就无法让代码产生真正的力量。知识的作用在于其清楚明白,而不在于其“熵增”。在不断的概念定义后,软件便慢慢的集中了庞大的知识体系,这个知识体系慢慢地成为领域语言。这个领域语言的完善程度越高,意味着软件实现越稳定。邻域语言的合理程度越高,意味着软件实现越正确。 这些概念的作用体现在哪里呢?首先,顶层概念决定了编码的方式。例如,在使用 MVC 模式时,编码的方式就变成了先写一下 V,再写一下 C,最后再写一下M。实际上MVC实质上与分层区别不了多少,分层会引入相应的层次概念。例如,View 层的概念:视图,负责页面展示与数据输入。Controller 层:控制器层,负责数据处理与数据流转。Model:模型层,保存数据,提供数据。其次,中层概念决定了业务编码。例如,对于消息业务,我们大可能使用发布订阅模式,这样我们会引入主题,订阅者,消息这三个概念。再次,底层概念决定了数据流转。例如,如果你有使用消息队列,你可能会把消息队列作为一个桥梁,用以与其它应用程序进行通信、协作,这样相比与其它进程通信如管道,共享内存而言更加灵活。在软件世界,概念无处不在。

如何确立一个概念

通常我们会在编程中直接引入概念,例如:我们定义这个类完成文件操作。那么,什么是“文件操作”?可能的一种情况是我们在进行 Java IO 流的操作,从而定义了文件操作是指Java 中 IO流的操作。由此我们得到一个定义概念的一种形式:<什么概念>是指<什么地方>的<什么处理>。这个形式界定了概念的名称,概念有效的范围,概念的作用,这样可以让这个概念明确起来。那这时概念确立了吗?是的,这样一个简单的概念就确立起来了。另外一个可能的情况就是文件系统的操作,文件系统操作更多地是新建文件,编辑文件,删除文件,移动文件,复制文件等一系列操作。在这样看来,我们所定义的是一个二义性概念,二义性概念以及泛义性概念都是具有一个以上的定义,如果没有找好定义就会产生不一样的理解,让人对代码晦涩难懂。那我们如何确立一个概念呢?单义概念?还是多义概念?答案是:视情况而定。对于需要高稳定性的应用,它需要概念都清楚明确,没有二义性。这样该软件缺陷数量便会相应地少,每个处理都有着自身实际定义。对于高扩展的应用,它需要包容度高的概念作为基本概念作为入口。例如,在 Tomcat 的架构中,Tomcat 由Server到Acceptor层层递进的定义使得 Tomcat具有稳定的结构和易于配置。在Jetty 的架构中Handler 的核心概念让 Jetty 便于扩展形成链式扩展。

应用与遵循概念

       应用概念通常在确立时并已经正式要用上了。在编程中,概念的应用通常是一个类的定义或者方法的定义。例如对于一个文件IO操作的定义中,我们可能定义一个 FileIOOperation 类,这个类中我们定义一个 byte[] readFileTypeBytes() 方法用于读取文件前三、四个字节(通常文件会使用头四个字节作为文件类型的标识位)。接着可能是 String readContent() 方法,用于读取文件内容。这样我们就得到了一个自然而然的概念及相应类实现。遵循概念要求你时刻保证概念定义一致,如果我们在上述的文件IO操作类 FileIOOperation中定义一个 boolean move(File dir) 方法时,我们对文件IO操作的定义变得宽泛。那如果我们确实要对概念继续宽泛处理呢?通常是把概念变得更加包容。在上述的文件IO操作概念中,如果把IO去掉那么就成为文件操作这样一个包容性较好的概念,这样就能够和原有的概念相容,但这样会引入二义性概念,也不利于符合编程中“高内聚,低耦合”。现在,我想你已经知道泛化是什么意思了,泛化其实是概念逐渐变得多义的过程。

客观概念与主观概念

       在编写程序时,我们可以有自己的对程序的定义。这些定义在某种程度上说可以分为两种,一种是客观概念,另一种是主观概念。对于主观概念,我们可以进一步分为主体主观,和客体主观。主观概念在程序中会导致程序表现的语义并不能贴切现实的业务。通常在沟通时能够体会到这一点,往往就是你说A,用户却会说成B。那这时候我们会说,不如我们使用客体主观概念,这时对于用户来说是贴切的,却导致程序缺乏一定的抽象定义。这对程序编写会出现冗余的情况。例如,没有必要的数据保存,一些需求方会要求你对每一个过程的数据进行独立分开进行运算。对于客观概念,只要符合业务环境,往往能够开箱即用,例如在前面介绍到的MVC 模式作为一种一种客观概念,已经普遍地使用。客观概念往往简单,并且能够相容比较多的概念,这也突出了客观概念在编程中的重要作用。

实际示例

       在我一次外包中,急于需求方的修改意见,我原先考虑增加一个概念,在那时,我加入周期组概念来把几个不同周期的工作合在一起以方便汇总。在编码中,我逐渐把周期组的概念抽象成了工作会话概念,用于记录他们的业务处理过程。做了一天半发现我可以直接引入一个更加简单的统计范围,这样我只需要做简单的界面更新和后端接口更改便可完成需求方功能。结果,只花了一个晚上便完成了原定的功能。可见概念在泛化时带来的概念包容的力量是多么强大。

总结

       总地来说,本篇文章强调了以下几点:
  1. 概念在编程中大致分为顶层概念、中层概念和底层概念,可以说软件设计,概念无处不在。设计概念对于程序设计及其重要,没有概念将没有正确的程序。
  2. 如果确立单义性概念,那么这个概念将会稳定,在编码中也将始终不变。如果确立泛义性概念,那个这个概念将会变得灵活,但也会给编码带来风险。
  3. 视情况决定需要确立单义性的概念还是泛义性概念,这样会给程序带来合理的扩展,也带来了一定不稳定,平衡这两者的之间的矛盾,以指导自己进行开发。
  4. 概念从客观上分为主观概念,客观概念。客观概念构成应用程序是我们追求的目标。
概念是我们用以编写程序中必不可少的自然语言词汇。这些词汇表达了我们对实际业务的理解。一个良好的概念会给程序带来确定性和准确性。

理想和现实

无论我有多么愚蠢,我都知道我需要有一个目标来让我达到我想达到样子。那个我需要达到的地方一定不会让我轻易地到达,到达的那个地方并不美好,或许没有自己真真切切能够触及到的东西,又或许只是平凡无奇的一片草地。但是,我想达到我理想的样子。
理想
C/C++ 指针指向着一个雪花,我想表达什么?雪花在自然界中是不会相同的,然而在画图中我们总能得到相同的雪花。C/C++ “->”(指针成员运算符)总是得到一个类型的内容(或许这个类型是你定义的)。背景百搭黑(我朋友说黑色是百搭的)可以无限的延伸,或许它代表世界?或许它代表我自己?不管这样, 
我想说明的是我要表达的东西是:编码出独一无二(哪来的独一无二?黑盒技术给我们带来的任意代码实现重复的可能性,这大概是无法完成的)。我想我会继续做下去吧?
现实
然后呀,愚蠢的我用 CSS 写出了这样的东西(好吧,做成这样让我萌生放弃的念头)。我想我没有做到现实中我要编码的东西呀,而且在现实中我也没有我想成为的那样子啊。那个理想的自己总是在旁边提醒着,引导着。而我却无耻地担心着,害怕着选择了最为容易的方式。正确的方式在理想的自己一声又一声的叹息中埋没,那个懈怠的自己不断地鼓励着自己走向不好的地方。一次接着一次,愚蠢地重蹈覆辙。不断重复地说出没经过脑子的的话语,不断地想回到起初的时间点。但是,我依然想达到自己想要达到的样子。
实现理想只需完成
我可以达到自己想要的样子吗?我用 CSS 做出了与刚开始不一样的东西,这个和刚开始完完全全不一样,风格也许大概是在哪里看过的吧,有我自己的东西吗?有我自己的印记吗?加以自己的 CSS 能力,发挥自己的创造力了吗?理想的那个自己会不会满意地给我一个答复了呢?那个懈怠的自己会不会嘲笑我:这个傻子做出了一个奇怪的,又不是自己的东西呢?那我自己会想什么?会是——我可以做的更好,达到我想要达到的样子吗?

实现理想是一个及其艰难的过程,也许你会为自己寻找着借口,让自己不去达到旁边那个理想的自己。但是你只需要付出一点点的努力便会让自己有改变的地方。改变自己总是对自己有着强大的帮助。如果你不知道理想的自己,我想你会在某本书、某句话语、某个人或者自己的内心深处得到正确的答案吧。
最后,我想达到我想达到的样子。

爬虫服务化

Web 应用中,一个重要的话题就是服务化,服务化意味着 Web 服务的体积得到减小,并提高了 Web 服务的内聚和解藕程度,并给用户提供了更加可扩展的服务。本文将会带大家了解如何服务化爬虫。

原因

在基于爬虫的 Web 服务中,爬虫关乎整个系统的正常运作。在以爬虫为核心的Web 服务中,我们面临着爬虫进程的管理、爬取数据的管理和爬取数据的分析等问题。在处理爬虫运行状态管理时,爬虫的运行状态需要记录起来以方便找出爬虫中止的原因,排除爬虫的故障。在处理爬虫数据时,根据爬虫的数据进而结构化存储的对应操作需要具备一定的灵活性和纠错能力。分析爬虫爬取的数据时,数据的分析和处理需要额处的处理,如使用其它的服务等等。同时,为了给用户或者开发者带来开放能力,又必须给用户提供爬虫服务的接口,方便进行二次开发,大大提高服务的可重用性。

做法

通常一个应用的服务化,总是离不开操作系统,在处理服务化时离不开以下几种形式:

命令行模式

命令行模式在处理爬虫服务故障时,是最简单,把日志重定向到文件就可以方便日后的故障排查。但数据的处理和分析需要单独对输出的数据文件进行处理,中间有一定的烦琐,如果数据量多的话,很考验服务器的 IO 处理能力和Web 服务读取数据的内存优化能力。通常命令行模式的做法与以下步骤相似:
  1. 执行爬虫命令,爬虫日志与数据单独保存
  2. 记录爬虫运行状态和退出值,如创建,运行,终止。保存爬虫的日志文件和数据文件。
  3. Web 服务在爬虫进程结束后,读取爬虫数据,和日志数据(如果需要)。

RESTful API/RPC

RESTful API 或者远程过程调用的形式在第一种形式的基础上,减少了服务器的 IO 处理能力。但无法优化内存,一但传输结构确定下来便难以提高结构与算法相适应的改进。

第三方托管(PaaS)

以 Scrapy 为例,Scrapy Hub 提供了 Scrapy Cloud ,可以替我们管理和运行爬虫。优点与 RESTful/RPC 形式相似。极大地减轻服务器负担(别人帮我们完成了!)并以 RESTful API 形式开放接口。
可以前往 Scrapy Cloud 了解更多信息。

示例

本示例以命令行模式进行构建爬虫服务,在开始之前,需要读者具备一定的 Python 编程能力,大致上能够想起怎样写类就差不多了。另外,本示例使用 Scrapy 作为爬虫框架。
我们首先需要一个 Pyhton 编译器,我使用的是 Python 3.x 可以在这里下载:https://python.org/ 。 安装好后,接着安装 Scrapy 爬虫框架:
pip install scrapy
接下来,讲述如何简单地创建一个爬虫项目,让大家熟悉一下。
可以直接把 loveletter-spider (https://github.com/codimiracle/loveletter-spider) 克隆下来这样可以节约不少的时间。 安装完 Scrapy 后,执行scrapy会得到以下结果:
$ scrapy
Scrapy 1.5.2 - no active project
Usage:
scrapy [options] [args]
Available commands:
bench Run quick benchmark test
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy -h" to see more info about a command
接着,运行 scrapy startproject [project_name] 创建一个爬虫项目。 进入 loveletter-spider 目录,再次执行 scrapy 你应该可以看到以下内容
$ scrapy
Scrapy 1.5.2 - project: loveletter
Usage:
scrapy [options] [args]
Available commands:
bench Run quick benchmark test
check Check spider contracts
crawl Run a spider
edit Edit spider
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
list List available spiders
parse Parse URL (using its spider) and print the results
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
Use "scrapy -h" to see more info about a command
接着,我们可以使用 scrapy list 显示当前可以运行的 spider。使用 scrapy crawl [spider_name] 可以运行一个爬虫。不管怎样,您可以在上面给出的爬虫链接中得到提示。 我们需要的只是爬虫给我们产生的日志和数据。在上边给出的 loveletter-spider 中是 *.json 和 *.log 在这个爬虫运行结束后会产生这两个文件,我们的服务化过程就有了输入点。然后,我们需要做的就是运行 spider 和处理 json 和 log 文件。在 Java 中运行一条命令使用的是 Runtime#exec() 方法。具体要如做呢?
@Async
public ListenableFuture<CrawlingResult> crawlFully() {
String runId = getRunId();
String logfile = "crawled/" + runId + ".log";
String outfile = "crawled/" + runId + ".json";
String command = getCommand(outfile, logfile);
//插入爬取结果
CrawlingResult result = getCrawlingResult(runId, outfile, logfile);
crawlingResultRepository.insert(result);
try {
//执行爬虫
Process process = Runtime.getRuntime().exec(command);
result.setStatus(CrawlingResult.RUNNING);
crawlingResultRepository.updateIdempotently(result, CrawlingResult.CREATED);
//等待爬虫执行完成
while (process.isAlive()) {
Thread.sleep(3000);
}
//处理爬虫进程结束退出值
result.setExitValue(process.exitValue());
if (result.getExitValue() == 0) {
result.setStatus(CrawlingResult.FINISHED);
//载入爬取的数据
loadCrawledData(runId);
//分析爬取的数据
analysisCrawledData(runId);
} else {
result.setStatus(CrawlingResult.SPIDER_ERROR);
}
crawlingResultRepository.updateIdempotently(result, CrawlingResult.RUNNING);
return AsyncResult.forValue(result);
} catch (Exception e) {
log.error("spider service terminated:", e);
int previousStatus = result.getStatus();
result.setStatus(CrawlingResult.SYSTEM_ERROR);
crawlingResultRepository.updateIdempotently(result, previousStatus);
return AsyncResult.forExecutionException(e);
}
}
这样可以记录好爬虫执行状态。处理好爬虫的运行接着我们就需要处理爬取到的数据了,这时候我们可以这样处理例如:
try {
//读取爬取的数据
InputStream inputStream = Files.newInputStream(Paths.get(result.getOutfile()));
String rawData = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
List<CrawledData> crawledDataList = JSON.parseArray(rawData, CrawledData.class);
//主题摘要
String[] rawThemeData = null;
Map<String, Theme> map = new HashMap<>();
for (CrawledData crawlData : crawledDataList) {
//处理爬虫数据,结构化地存入数据库
Theme theme = map.get(crawlData.title);
if (Objects.isNull(theme)) {
theme = new Theme();
BeanUtils.copyProperties(crawlData, theme);
rawThemeData = crawlData.title.split("\\|");
theme.setEpisode(rawThemeData[0]);
theme.setId(getThemeId(crawlData.title, crawlData.getPublishTime()));
theme.setRunId(runId);
map.put(crawlData.title, theme);
log.debug("read title data of crawled data: [{}]", theme);
themeRepository.insert(theme);
}
Letter letter = new Letter();
BeanUtils.copyProperties(crawlData, letter);
letter.setTheme(theme);
letter.setRunId(runId);
letter.setId(getLetterId(theme.getId(), crawlData.letterIndex));
log.debug("read letter data of crawled data: [{}]", letter);
letterRepository.insert(letter);
}
} catch (IOException e) {
log.error("can not load the data of crawled data by runId [{}], it throws [{}]", runId, e);
throw e;
}
这样如果中途出错应该怎么办呢?我们可以使用事务来帮助处理。在 Spring Boot 中我们使用 @Transaction(rollbackFor = IOException.class) 注解处理,若发生问题回滚操作。
@Async
@Transactional(rollbackFor = IOException.class)
public void loadCrawledData(String runId) throws IOException {
CrawlingResult result = crawlingResultRepository.findByRunId(runId);
if (Objects.nonNull(result)) {
// 与上一致
}
}
如果需要控制爬虫本身呢?我们需要把处理这个爬虫的线程Thread.currentThread()保存下来,并对其进行相应的结束,暂停等操作,不过需要注意的是一旦服务重启这个就会失去效用。

具体的爬虫和该示例的源代码:
爬虫:https://github.com/codimiracle/loveletter-spider
服务:https://github.com/codimiracle/loveletter-service

注意:Java 程序在运行时,PATH 路径要包含 Python 解析器。

总结

我们通过命令行模式进行爬虫的服务化,了解了 Web 应用中如何爬虫的服务化设计。在本次学习中,我们可以得到爬虫服务化的过程中需要注意以下几点:
  • 爬虫运行的方式,其运行方式决定了爬虫自身服务化的方式。
  • 爬虫状态管理,通常服务需要管理爬虫的状态,以方便进行爬虫的管理。这里笔者只是视作一项成功或失败的任务。
  • 爬取数据的处理与分析,爬取数据是需要处理和分析的。没有处理爬虫的数据,没有分析爬虫的数据,就得不到这次爬虫的成果。

Java 虚拟机 – 结构

数据类型

Java 虚拟机中有两种数据类型:原始类型和引用类型。这两种类型的值可以存进变量,作为参数传递,被方法返回,并通过原始值或者引用值进行操作。虚拟机不进行类型检查,所有的检查都在运行时之前处理完成。虚拟机通过虚拟机指令来确定参数类型如 iadd,ladd,fadd,dadd 都是两个数值操作类型返回一个数值结果的指令,分别对 integer, long, float, double 进行操作。
原始类型byteshortintlongchar
字节数816326416
默认值0000‘\u000000’
数值范围[-128,
127]
[-32768,
32767]

[-2147483648,
-2147483647]

[-9223372036854775808
, 9223372036854775807]
[0,
65535]
原始类型 float IEEE 754 double IEEE 754
字节数 32 64
默认值
+0+0
returnAddress 类型被虚拟机 jsr,ret,jsr_w 指令进行操作,returnAddress 的值指向一条操作码(opcode), returnAddress 不会作为 Java 语言中特定类型并且不能通过编程语言进行修改。 boolean 类型虽然在虚拟机中进行了定义,但实际中是使用 int 来表示的,虚拟机也是使用 byte 相关的指令对其进行操作,不存在 boolean 相关的操作指令, boolean 数组使用 newarray 指令创建,并使用 byte 数组指令 baload,bastore 进行访问。 引用类型有三种,分别是 class 类型,array 类型,interface 类型,null 没有具体的运行时类型,但可以强制转换为任意的引用类型。虚拟机规范没有规定 null 的具体值。

运行时数据区域

Swagger

入门

Swagger 给我们提供了两个有用的工具: Swagger Editor 和 Swagger Inspector 和一个 API 托管平台 SwaggerHub。其中 Swagger Hub 是一个可以让个人拥有 3个公共 API 文档仓库和一个私有仓库的在线 API 文档生成以及Mock API Server的一整套服务,使用 YAML 以及 Open API Specification 编写出能够使 Swagger 自动生成文档的 API。下面,笔者将会带大家学习 Swagger 这个极好的 API 编写平台。

我们打开 Swagger 后,我们就会来到 Swagger 的首页,点击 “Try Swagger Hub” 就可以来到 “Swagger Hub”的首页,再接着 “Sign Up Free” 便可以进入注册界面,当然可以直接进入注册界面如下所示:
SwaggerHub 注册界面
接下来我们分别输入“User Name”, “Email”, 以及 “Password” 点下 “SIGN UP” 静候佳音即可,完成后可以看到下面的界面,输入组织名称 “Name your Organization”点下按钮即可,当然你可以不管它直接进入 Swagger Hub
创建组织
接着来到 Swagger Hub 界面,新建一个 Swagger API 。点击 “Create New API”
Swagger Hub 界面
创建 API 对话框
OpenAPI version 默认为 2.0 如果有兴趣也可以使用 3.0,Template 为一些基础的 API 文档,这里选择的是宠物商店(Petstore)模板,在 Name 中输入一个“test”,可见性(Visibility)选择“Public”,Public 可以直接让别人看到你的API,通常不告诉别人几乎不大可能看到,看到也没多大关系。而 Private 就相反了,只有你和协作者才可以看到。现在可以点击“Create API”继续下去了。 成功后会来到 Design View 如下图所示:
Design View
绿色区域是导航视图、编辑器视图、文档视图三个视图的显示,隐藏,编辑器与导航可以单独隐藏,但至少显示一个。蓝色区域便是导航视图,可以看到由一个可以展开和折叠的“Tags”做页标并以HTTP请求方法和请求路径的项目构成。点击导航的项目可以很方便地导航到该项目在编辑器中定义的位置。紫色区域为文档区域,在文档区域可以查看 Open API 给我们解释并生成的 API 文档。深蓝色区域与一般高亮的编辑器没什么特别之处。编辑器编写的 Open API 代码会实时更新到那里文档区域和导航区域。橘色区域可以调整编辑器的文字大小,白天、夜晚背景色和评论。评论可以帮助组织或者团队之间的协作和沟通。粉红色区域则是编辑器所编写的 Open API 代码能否顺利通过验证,可以点击它展开来看到不正确的地方。
Open API 验证
API 文档
文档区域中比较简明易懂,参数列表,响应码以及相应描述和响应结果一应俱全。左上方的“Try it out”还可以直接使用 Mock API Server 进行返回结果。
Model 文档
Model 文档直接显示了这份 Open API 所用到的实体,对数据库设计很方便啊!显示的方式与 JSON 格式相差不大。 接下来,笔者将会讲述一些必要的 YAML 知识和 Open API 知识。

YAML 结构

YAML 使用缩进(这里使用两个空格)作为嵌套层,类似与 Python 使用缩进界定代码块。与 JSON 类似可以有 number, string, object, array 类似的概念,故这里使用来简单地介绍 YAML。这里给出 YAML 的官网以及会用到的一些写法帮助大家简单地了解一下 YAML 。 YAML 的基础写法
key: value
key:
  key1: value
  key2: value
YAML number 的写法
grade: 100
YAML string 的写法,使用第二种允许预先换行。
title: Hello world
title: |
  Hello world
  this is a good example
YAML object 的写法
person:
  name: hello
  age: 18
  gender: male
YAML array 的写法
persons:
- name: hello
  age: 18
  gender: male
- name: mike
  age: 20
  gender: male

Open API 结构

Open API 2.0 组成

简述

Open API 组成很直观,顶头注明Open API 的版本,使用一个 info object,介绍这份 Open API, 并使用 tags array,描述这份 Open API 中用到的标签,使用 paths object,描述各部分 REST API 中的 HTTP 链接 。securityDefinitions object 定义了有关登录和权限的限定。definitions object 定义了各个 Model,externalDocs object 则定义这份 Open API 的额外信息,后面的是 Swagger Hub 给我们提供的 Mock Server。

info object

info object 的内容
info object 的内容很直观,这份API 相关的描述,版本,标题, 联系方式,开发协议等等。

tags array

如同前面所言,数组元素使用“- ”开头然后写key: value 对构成,每一个“- ”及其缩进的内容为一个 YAML object。

paths object

paths object
paths object的写法很明显,先是一个API 请求路径 “/pet” object 接着是一个 “请求方法” object,然后写明这个请求 object 的 tags array,写上“summary”说明这个 API 是拿来干什么的,operationId 用来标识整个 API ,consumes (消耗),意味着该 API 可以接受怎样的数据(accept header)。produces (产出) 意味着API会产生什么类型的响应(content-type)。parameters,很显然是该 API 接受的参数。responses 则是定义响应。security 则是相应的权限。Open API 允许我们在一个请求路径 object 下接着写其它 请求方法 object 的 API。如上面没有显示完全的“put”。
安全认证定义
在 securityDefinitions object 中,petstore_auth 用于 oauth2 的认证,api_key 用于 API 调用的认证(类似与 Web 中经常放在地址后面的 Token )。如果是 cookie 的认证则简单的由 api_key auth 的 i n 改为 “basic” (2.0) 或者 “cookie ” (3.0)。
definitions
在 definitions object 中,每个像 Order, Category 一样层级的都是Model, 这些 Model 在编写 API 的参数,输出结果中在 schema 属性中进行引用。引用方式一般是以下的方式:
schema:
  $ref: '#/definitions/ModelName'
写法如如英文所言,先指定好 type, 接着写属性 properties,properties 中为一个Model 的属性名 object 指定该属性的类型,在这里依然可以使用 schema 进行应用。更多 Open API 的说明可以查看 Open API 规范

JVM 运行时数据区域

简介

要去了解 Java 虚拟机,我们必须要先了解它的组成。那么 Java 虚拟机是如何定义运行时数据区域的呢?在运行时数据区域中在《Java 虚拟机规范(Java SE 7)》规定下主要为:
1. 程序计数器
2. Java 虚拟机栈
3. 本地方法栈
4. Java 堆
5. 方法区
6. 运行时常量池
7. 直接内存

程序计数器

程序计数器(Program Counter Register)是一块很小的内存,计算机科学中,计数器作为指令位置的一个行号指示器,取出当前的指令后,计数器会加一指向下一条指令。Java 的指令,分支,循环,跳转,异常处理,线程恢复等都依赖于程序计数器。多线程时,每个线程都会有各自独立的程序计数器。

当执行 Java 方法时,如果为一个 Native 修饰的方法,则计数器的值为空(Undefined)

Java 虚拟机栈

Java 虚拟机栈(Java Virtual Marchine Stacks)又被粗糙地称为栈内存是线程私有的,它的生命周期与线程相同。JVM 栈描述了 Java 方法执行的内存模型,每一次方法调用都会创建一个栈帧(Stack Frame)用于保存局部变量表、操作数栈、动态链接、方法出口等信息。方法执行与结束的过程对应着JVM 栈的入栈和出栈过程。

局部变量表存放了Java的基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型, 一个对象句柄或者对象指针又或者对象位置)、returnAddress 类型 (一条字节码的地址)。一个局部变量空间大致为32位。局部变量表的空间不会动态分配。

本地方法栈

本地方法栈(Native Method Stack)Java 虚拟机栈通常是一致的,不过为 Native Method 服务,在 Sun HotSpot VM 中,与Java 虚拟机栈合二为一

Java 堆

Java堆(Java Heap)是 JVM 管理的最大的,被所有线程共享的内存区域,用来保存大多数的对象实例,但这不是“绝对”的。Java 的垃圾回收集中在 Java 堆上,因此又被称为 GC 堆(Garbage Collected Heap)。

从内存回收的角度来看,由于现在收集器基本都采用分代回收算法,所以 Java 堆中分为:新生代和老年代。细致地可以分为:Eden 空间、From Survivor 空间、To Survivor 空间。
从内存分配角度来看,Java堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。

可以通过 -Xmx -Xms 来控制 Java 堆的大小。

方法区

方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,用于储存已经被虚拟机加载的类信息、常量、静态变量、即时翻译器编译后的代码等数据。为了区别方法区和堆通常,方法区有 Non-Heap 的别名。

通常不会对方法区进行内存回收因此又被称为(Java 堆的永久代),但这个区域是会被回收的。对这个区域的 GC 一般是常量池的回收和类型的卸载。

方法区空间的大小会受到 -XX:MaxPermSize 的影响。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table)用于存放编译期生成的各种字面量和符号引用,这些存放在类加载后进入方法区的运行时常量池中存放。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,但也是一块重要的内存区域,避免了使用 NIO(New Input/Output)时,在Java 堆与Native 堆之间的数据交换。DirectByteBuffer 对象作为这块内存的引用。

了解完 JVM 运行时的内存区域,我们能够更加深入地去了解 JVM 中各区域的运行细节!

什么是框架?

引言

框架这个词在许多的地方都能见到。在给出框架是什么的定义之前,我们会讨论骨架 、框架 和架构 这三个常见的且容易混淆但又相似的术语。在实际中,我们通常会用模式或者框架又或者架构来概括这三个术语。
回溯到尚未出现软件工程之前 ,我们能够想到当时的编程环境是“天堂”又是“地狱”。软件作为一种著作物,附带版权的保护减少了软件代码被他人修改使用的可能,阻碍了软件代码的传播,也阻碍了软件行业的发展。软件作为一种思维产物,必然带有主观性。软件作者的思维直接影响到了软件自身的质量。同时,为了复用积累下来的代码,软件作者才会对复用的代码作一定的改进。这在一定程度上对常用的代码进行了维护和复用,虽然这不是框架出现的标志,但这是框架出现的起点。

复用为本

复用是框架的基础和目的。若一个框架没有达到复用的目的,那么,我们不能称之为框架。代码的复用阶段达到相当的规模时则可以简而言之框架或伪框架。通常复用会构成库和实用性工具。“库”作为一种思维抽象而存在,描述了软件中某一部分需要完成的工作。实用性工具对库的使用进行了简要但又是实用的复用,通常把这种行为称为封装。当这些库和实用工具在经过一次又一次的重复迭代下得到稳定性的保证后,软件作者很大程度上会对其所做的软件共同的流程逻辑作一次更高层面的复用。这时候骨架就诞生了。骨架的诞生使得软件作者在项目接下来的开发中能够更加充分地集中于软件开发本身。

什么是骨架

骨架是一个软件的基本构成部分。在软件中,骨架更普遍存在在软件执行流程中。因此,骨架更像是软件共同逻辑(例如用户登录动作,用户注册动作被抽象为动作处理骨架)的抽象复用。骨架的作用使得我们能够较为轻易地构造一些传统上较为复杂的软件。例如,管理系统。管理系统通常是由较为固定的步骤构成的(例如CRUD),这些步骤抽象成一个整体的骨架能够提高代码的可维护性。同时由于骨架的存在我们可以关注在管理系统的自身,如用户数据,被管理数据,以及相关的业务操作流程。这些操作流程也可以继续进一步以一个特定的骨架而存在。这样一个个骨架能够以描述性的方式把软件从脑海中描述出来。我们可以使用流程图达到这样的目的。另外,由于骨架的简单性,我们可以在需要的时候进行较多的扩展。在这个时候软件作者更多地通过钩子(Hook)的形式来提供,即使用模板模式 。骨架在这个时候可以被看作一个单骨架的框架。了解一个单独的骨架总比多个骨架简单得多,这是一些大项目使用小框架的原因之一。总而言之,骨架具有简单性,骨架的诞生是为了对特定流程的复用;骨架具有可扩展性,可扩展性和简单性一般是关联而存在的。通过极为简单的钩子,我们可以定义一些具有特别用途的操作例如事件发布,外置插件(Plugin)等等。当一个骨架能够以一种更为通用的形式给软件带来一定的简化时,框架就出现了。

什么是框架

框架是一个软件的蓝图轮廓或者蓝图的一部分。蓝图中具体的内容能够通过骨架提供较为普遍的方案。这样能够使得“蓝图的内容”是能够被维护的。和骨架相同,框架也是为了让软件作者关注软件作者需要关注的地方,而无需对关注点以外的事情付出精力。和骨架不同,框架致力于简化软件开发,使得软件开更像一个搭积木的过程。而骨架是为了降低软件开发的复杂性把流程进行复用。框架在骨架的基础上对软件构建进行了简化,这些简化通常包含骨架层面上的简化,第三方扩展支持的简化,编码规范上的简化等等。骨架作为流程逻辑的重用,在框架中作为主要部分。这里值得说明的是,框架的大小在某些程度上并没有大小之分,仅仅是骨架和扩展涉及的范围的宽广问题。
由于涉及的范围过大而导致框架必须发生改变。这些连带作用导致了框架变得复杂和难以看到全貌。就是这样,有了小项目用大框架,大项目用小框架这个说法。对于小型项目来说,小项目并不需要在深层次了解和全局控制框架自身。因此常常为了节约不必要的框架和插件维护而选择经起考验的大框架和第三方扩展。这样一来项目只需要更加关注在自身的开发。这样节约了时间,节约了维护成本,但这些小项目通常是不考虑软件依赖的风险。而对于大项目来说,框架的本身是需要的,但框架的绝大多数骨架和扩展又是不必要的。这种情况通常会被称为冗余。为什么是不必要的呢?大项目必须要考虑到后续的开发。
骨架越多,框架就越复杂。扩展越多,需要处理的依赖风险就越多。在这个复杂的情形下,越是需要维护的软件,依赖的风险越是不可控,例如依赖被废弃对项目会有不必要的危急影响,这个影响犹如四年前的软件依赖突然需要升级最新的版本到现在一样。涉及范围广的框架所提供的骨架和扩展功能与项目自身无关,浪费了项目实际关注点的精力。这些精力对于大型项目来说是极其关键的。灵感是不稳固的,不要要求灵感能够一直留在脑海中。常常会有过度设计的事情发生,不管是笔者还是其他人。我们很难避免过度设计,这些过度设计在某些程度上是软件作者对软件发展的预测。对于小型项目,它更加需要快速开发,后续维护较少。而对于大型项目需要各个部件的密切配合甚至给扩展预留空间,往后又是需要频繁的维护和需求变更。这就要求大型项目需要承受更多的挑战。想要项目承受得起挑战,软件作者必须要确保每一部分都是简单的。

架构与框架

架构是一个用于描述整体的宏图。架构一般描述了整个应用的大体部分,如在Web 中的Frontend-Middle-Backend架构中有C/S 架构和B/S架构两种形式。这两个架构中把服务端作为后端,把HTTP 作为中间件,把客户端和浏览器作为前端。这就是架构的作用,无需注意到Web应用的具体。而集中在相应的架构层。通常我们也会在框架中使用架构来描述框架自身是如何设计的。

总结

那么框架应该是什么?框架是一个以复用为基础和目的,以骨架为主要内容的,以简化应用开发为核心的设计基础。