Showing preview only (953K chars total). Download the full file or copy to clipboard to get everything.
Repository: mercyblitz/mercyblitz.github.io
Branch: master
Commit: a318a4fda94b
Files: 72
Total size: 813.8 KB
Directory structure:
gitextract_z0v7hnz7/
├── .gitignore
├── 404.html
├── Gruntfile.js
├── LICENSE
├── README.md
├── README.zh.md
├── _config.yml
├── _includes/
│ ├── about/
│ │ ├── en.md
│ │ └── zh.md
│ ├── featured-tags.html
│ ├── footer.html
│ ├── friends.html
│ ├── head.html
│ ├── intro-header.html
│ ├── mathjax_support.html
│ ├── nav.html
│ ├── short-about.html
│ └── sns-links.html
├── _layouts/
│ ├── default.html
│ ├── keynote.html
│ ├── page.html
│ └── post.html
├── _posts/
│ ├── 2018-01-01-Dubbo 注解驱动.md
│ ├── 2018-01-18-Dubbo 外部化配置.md
│ ├── 2018-05-26-Spring-Boot Web 应用加速.md
│ ├── 2018-06-28-Dubbo Cloud Native 实践与思考.md
│ ├── 2018-07-25-Reactive Programming 一种技术 各自表述.md
│ ├── 2019-03-05-《Java编程方法论 响应式之Rxjava篇》序.md
│ ├── 2019-04-26-Dubbo Spring Cloud 重塑微服务治理.md
│ ├── 2019-06-18-Service Mesh 时代,Dubbo 架构该怎么跟进?.md
│ ├── 2019-09-05-2019 Java 趋势报告 InfoQ 采访稿(小马哥部分).md
│ ├── 2020-05-11-Apache Dubbo 服务自省架构设计.md
│ └── 2020-10-28-《深入理解 Spring Cloud 与实战》序.md
├── about.html
├── archive.html
├── books/
│ └── thinking-in-spring-boot/
│ ├── README.md
│ ├── about.md
│ ├── conventions.md
│ ├── core/
│ │ └── preface.md
│ ├── donate.md
│ ├── overview.md
│ ├── revision.md
│ ├── samples.md
│ ├── version.md
│ └── videos.md
├── css/
│ ├── bootstrap.css
│ └── hux-blog.css
├── feed.xml
├── index.html
├── js/
│ ├── archive.js
│ ├── bootstrap.js
│ ├── hux-blog.js
│ ├── jquery.js
│ ├── jquery.nav.js
│ ├── jquery.tagcloud.js
│ ├── snackbar.js
│ └── sw-registration.js
├── less/
│ ├── highlight.less
│ ├── hux-blog.less
│ ├── mixins.less
│ ├── side-catalog.less
│ ├── sidebar.less
│ ├── snackbar.less
│ └── variables.less
├── my/
│ ├── books/
│ │ └── README.md
│ ├── lessons/
│ │ └── README.md
│ ├── open-sources/
│ │ └── README.md
│ ├── presentations/
│ │ └── README.md
│ └── training/
│ └── README.md
├── offline.html
├── package.json
└── sw.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
_site
.DS_Store
================================================
FILE: 404.html
================================================
---
layout: default
title: 404
hide-in-nav: true
description: "这里什么都没有 :("
header-img: "img/404-bg.jpg"
permalink: /404.html
---
<!-- Page Header -->
{% include intro-header.html type="page" short='true' %}
<script>
document.body.classList.add('page-fullscreen');
</script>
================================================
FILE: Gruntfile.js
================================================
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
main: {
src: 'js/<%= pkg.name %>.js',
dest: 'js/<%= pkg.name %>.min.js'
}
},
less: {
expanded: {
options: {
paths: ["css"]
},
files: {
"css/<%= pkg.name %>.css": "less/<%= pkg.name %>.less"
}
},
minified: {
options: {
paths: ["css"],
cleancss: true
},
files: {
"css/<%= pkg.name %>.min.css": "less/<%= pkg.name %>.less"
}
}
},
banner: '/*!\n' +
' * <%= pkg.title %> v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
' */\n',
usebanner: {
dist: {
options: {
position: 'top',
banner: '<%= banner %>'
},
files: {
src: ['css/<%= pkg.name %>.css', 'css/<%= pkg.name %>.min.css', 'js/<%= pkg.name %>.min.js']
}
}
},
watch: {
scripts: {
files: ['js/<%= pkg.name %>.js'],
tasks: ['uglify'],
options: {
spawn: false,
},
},
less: {
files: ['less/*.less'],
tasks: ['less'],
options: {
spawn: false,
}
},
},
});
// Load the plugins.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-banner');
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task(s).
grunt.registerTask('default', ['uglify', 'less', 'usebanner']);
};
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015-2016 Huxpro
https://github.com/Huxpro/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=======================================================================
Hux Blog Subcomponents:
The Hux Blog project contains subcomponents with separate copyright
notices and license terms. Your use of the source code for the these
subcomponents is subject to the terms and conditions of the following
licenses.
(MIT License) Clean Blog Jekyll Theme: https://github.com/BlackrockDigital/startbootstrap-clean-blog-jekyll/
https://github.com/BlackrockDigital/startbootstrap-clean-blog-jekyll/blob/master/LICENSE
Copyright (c) 2013-2016 Blackrock Digital LLC.
================================================
FILE: README.md
================================================
# 小马哥的技术博客 [https://mercyblitz.github.io/](https://mercyblitz.github.io/)
欢迎来到小马哥的技术博客,这里将深入探讨相关技术,包括行业动态,架构设计,设计模式,框架使用,源码分析等。
**博客将不定期更新,请小伙伴们随时关注哦!**
## [我的文章](https://mercyblitz.github.io/archive/)
知识星球:https://t.zsxq.com/72rj2rr
SF : https://segmentfault.com/u/mercyblitz
微信:**mercyblitz-1985**
微博:**mercyblitz**
## [我的课程](https://mercyblitz.github.io/lessons/)
## [《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)
## [关于我](https://mercyblitz.github.io/about/)
小马哥,十余年Java EE 从业经验,架构师、微服务布道师、Dubbo 维护者。目前主要负责阿里巴巴集团微服务技术实施、架构衍进、基础设施构建等。重点关注云计算、微服务以及软件架构等领域。通过SUN Java(SCJP、SCWCD、SCBCD)以及Oracle OCA 等的认证。
================================================
FILE: README.zh.md
================================================
# Hux blog 模板
### [我的博客在这里 →](http://huxpro.github.io)
### 关于收到"Page Build Warning"的email
由于jekyll升级到3.0.x,对原来的pygments代码高亮不再支持,现只支持一种-rouge,所以你需要在 `_config.yml`文件中修改`highlighter: rouge`.另外还需要在`_config.yml`文件中加上`gems: [jekyll-paginate]`.
同时,你需要更新你的本地jekyll环境.
使用`jekyll server`的同学需要这样:
1. `gem update jekyll` # 更新jekyll
2. `gem update github-pages` #更新依赖的包
使用`bundle exec jekyll server`的同学在更新jekyll后,需要输入`bundle update`来更新依赖的包.
参考文档:[using jekyll with pages](https://help.github.com/articles/using-jekyll-with-pages/) & [Upgrading from 2.x to 3.x](http://jekyllrb.com/docs/upgrading/2-to-3/)
## 关于模板(beta)
我的博客仓库——`huxpro.github.io`,是经常修改的,而且还会有人乱提交代码,因此给大家做了一个稳定版的模板。大家可以直接fork模板——`huxblog-boilerplate`,要改的地方我都说明了。或者可以直接下载zip到本地自己去修改。
```
$ git clone git@github.com:Huxpro/huxblog-boilerplate.git
```
**[在这里预览模板 →](http://huangxuan.me/huxblog-boilerplate/)**
## 各版本特性
##### New Feature (V1.5.2)
* 当你fork了我的仓库之后,还要删掉里面的关于我的文档是不是感到略烦躁呢?**Boilerplate** 模板将帮助你快速开始,方便合并与更新。
* `-apple-system`被添加到了字体规则里面了,这套字体格式能将iOS9默认的新字体**San Francisco**表现的非常漂亮。
* 解决了代码过长自动换行的bug,替换为横向滚动条。详情请见[issue#15](https://github.com/Huxpro/huxpro.github.io/issues/15)
###### 其他历史版本个人觉得没有必要了解,看看英文就行了。
## 支持
* 你可以自由的fork。如果你能将主题作者和 github 的地址保留在你的页面底部,我将非常感谢你。
* 如果你喜欢我的这个博客模板,请在`huxpro.github.io`这个repository点个赞——右上角**star**一下。
## 说明文档
* 开始
* [环境要求](#environment)
* [开始](#get-started)
* [写一篇博文](#write-posts)
* 组件
* [侧边栏](#sidebar)
* [迷你关于我](#mini-about-me)
* [推荐标签](#featured-tags)
* [好友链接](#friends)
* [HTML5 演示文档布局](#keynote-layout)
* 评论与 Google/Baidu Analytics
* [评论](#comment)
* [网站分析](#analytics)
* 高级部分
* [自定义](#customization)
* [标题底图](#header-image)
* [搜索展示标题-头文件](#seo-title)
#### Environment
如果你安装了jekyll,那你只需要在命令行输入`jekyll serve`就能在本地浏览器预览主题。你还可以输入`jekyll serve --watch`,这样可以边修改边自动运行修改后的文件。
经 [@BrucZhaoR](https://github.com/BruceZhaoR)的测试,好像两个命令都是可以的自动运行修改后的文件的,刷新后可以实时预览。官方文件是建议安装bundler,这样你在本地的效果就跟在github上面是一样的。详情请见这里:https://help.github.com/articles/using-jekyll-with-pages/#installing-jekyll
#### Get Started
你可以通用修改 `_config.yml`文件来轻松的开始搭建自己的博客:
```
# Site settings
title: Hux Blog # 你的博客网站标题
SEOTitle: Hux Blog # 在后面会详细谈到
description: "Cool Blog" # 随便说点,描述一下
# SNS settings
github_username: huxpro # 你的github账号
weibo_username: huxpro # 你的微博账号,底部链接会自动更新的。
# Build settings
# paginate: 10 # 一页你准备放几篇文章
```
Jekyll官方网站还有很多的参数可以调,比如设置文章的链接形式...网址在这里:[Jekyll - Official Site](http://jekyllrb.com/) 中文版的在这里:[Jekyll中文](http://jekyllcn.com/).
#### write-posts
要发表的文章一般以markdown的格式放在这里`_posts/`,你只要看看这篇模板里的文章你就立刻明白该如何设置。
yaml 头文件长这样:
```
---
layout: post
title: "Hello 2015"
subtitle: "Hello World, Hello Blog"
date: 2015-01-29 12:00:00
author: "Hux"
header-img: "img/post-bg-2015.jpg"
tags:
- Life
---
```
#### SideBar
看右边:

设置是在 `_config.yml`文件里面的`Sidebar settings`那块。
```
# Sidebar settings
sidebar: true #添加侧边栏
sidebar-about-description: "简单的描述一下你自己"
sidebar-avatar: /img/avatar-hux.jpg #你的大头贴,请使用绝对地址.
```
侧边栏是响应式布局的,当屏幕尺寸小于992px的时候,侧边栏就会移动到底部。具体请见bootstrap栅格系统 <http://v3.bootcss.com/css/>
#### Mini About Me
Mini-About-Me 这个模块将在你的头像下面,展示你所有的社交账号。这个也是响应式布局,当屏幕变小时候,会将其移动到页面底部,只不过会稍微有点小变化,具体请看代码。
#### Featured Tags
看到这个网站 [Medium](http://medium.com) 的推荐标签非常的炫酷,所以我将他加了进来。
这个模块现在是独立的,可以呈现在所有页面,包括主页和发表的每一篇文章标题的头上。
```
# Featured Tags
featured-tags: true
featured-condition-size: 1 # A tag will be featured if the size of it is more than this condition value
```
唯一需要注意的是`featured-condition-size`: 如果一个标签的 SIZE,也就是使用该标签的文章数大于上面设定的条件值,这个标签就会在首页上被推荐。
内部有一个条件模板 `{% if tag[1].size > {{site.featured-condition-size}} %}` 是用来做筛选过滤的.
#### Friends
好友链接部分。这会在全部页面显示。
设置是在 `_config.yml`文件里面的`Friends`那块,自己加吧。
```
# Friends
friends: [
{
title: "Foo Blog",
href: "http://foo.github.io/"
},
{
title: "Bar Blog",
href: "http://bar.github.io"
}
]
```
#### Keynote Layout
HTML5幻灯片的排版:

这部分是用于占用html格式的幻灯片的,一般用到的是 Reveal.js, Impress.js, Slides, Prezi 等等.我认为一个现代化的博客怎么能少了放html幻灯的功能呢~
其主要原理是添加一个 `iframe`,在里面加入外部链接。你可以直接写到头文件里面去,详情请见下面的yaml头文件的写法。
```
---
layout: keynote
iframe: "http://huangxuan.me/js-module-7day/"
---
```
iframe在不同的设备中,将会自动的调整大小。保留内边距是为了让手机用户可以向下滑动,以及添加更多的内容。
#### Comment
博客不仅支持多说[Duoshuo](http://duoshuo.com)评论系统,也支持[Disqus](http://disqus.com)评论系统。
`Disqus`优点是:国际比较流行,界面也很大气、简介,如果有人评论,还能实时通知,直接回复通知的邮件就行了;缺点是:评论必须要去注册一个disqus账号,分享一般只有Facebook和Twitter,另外在墙内加载速度略慢了一点。想要知道长啥样,可以看以前的版本点[这里](http://brucezhaor.github.io/about.html) 最下面就可以看到。
`多说` 优点是:支持国内各主流社交软件(微博,微信,豆瓣,QQ空间 ...)一键分享按钮功能,另外登陆比较方便,管理界面也是纯中文的,相对于disqus全英文的要容易操作一些;缺点是:就是界面丑了一点。
当然你是可以自定义界面的css的,详情请看多说开发者文档 http://dev.duoshuo.com/docs/5003ecd94cab3e7250000008 。
**首先**,你需要去注册一个账号,不管是disqus还是多说的。**不要直接使用我的啊!**
**其次**,你只需要在下面的yaml头文件中设置一下就可以了。
```
duoshuo_username: _你的用户名_
# 或者
disqus_username: _你的用户名_
```
**最后**多说是支持分享的,如果你不想分享,请这样设置:`duoshuo_share: false`。你可以同时使用两个评论系统,不过个人感觉怪怪的。
#### Analytics
网站分析,现在支持百度统计和Google Analytics。需要去官方网站注册一下,然后将返回的code贴在下面:
```
# Baidu Analytics
ba_track_id: 4cc1f2d8f3067386cc5cdb626a202900
# Google Analytics
ga_track_id: 'UA-49627206-1' # 你用Google账号去注册一个就会给你一个这样的id
ga_domain: huangxuan.me # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto。
```
#### Customization
如果你喜欢折腾,你可以去自定义我的这个模板的 code,[Grunt](gruntjs.com)已经为你准备好了。(感谢 Clean Blog)
JavaScript 的压缩混淆、Less 的编译、Apache 2.0 许可通告的添加与 watch 代码改动,这些任务都揽括其中。简单的在命令行中输入 `grunt` 就可以执行默认任务来帮你构建文件了。如果你想搞一搞 JavaScript 或 Less 的话,`grunt watch` 会帮助到你的。
**如果你可以理解 `_include/` 和 `_layouts/`文件夹下的代码(这里是整个界面布局的地方),你就可以使用 Jekyll 使用的模版引擎 [Liquid](https://github.com/Shopify/liquid/wiki)的语法直接修改/添加代码,来进行更有创意的自定义界面啦!**
#### Header Image
标题底图是可以自己选的,看看几篇示例post你就知道如何设置了。在
[issue #6 ](https://github.com/Huxpro/huxpro.github.io/issues/6) 中我被问到:怎么样才能让标题底图好看呢?
标题底图的选取完全是看个人的审美了,我也帮不了你。每一篇文章可以有不同的底图,你想放什么就放什么,最后宽度要够,大小不要太大,否则加载慢啊。
但是需要注意的是本模板的标题是**白色**的,所以背景色要设置为**灰色**或者**黑色**,总之深色系就对了。当然你还可以自定义修改字体颜色,总之,用github pages就是可以完全的个性定制自己的博客。
#### SEO Title
我的博客标题是 **“Hux Blog”** 但是我想要在搜索的时候显示 **“黄玄的博客 | Hux Blog”** ,这个就需要SEO Title来定义了。
其实这个SEO Title就是定义了<head><title>标题</title></head>这个里面的东西和多说分享的标题,你可以自行修改的。
## 致谢
1. 这个模板是从这里[IronSummitMedia/startbootstrap-clean-blog-jekyll](https://github.com/IronSummitMedia/startbootstrap-clean-blog-jekyll) fork 的。 感谢这个作者
2. 感谢[@BrucZhaoR](https://github.com/BruceZhaoR)的中文翻译
3. 感谢 Jekyll、Github Pages 和 Bootstrap!
================================================
FILE: _config.yml
================================================
# Site settings
title: Mercy Ma
SEOTitle: 小马哥的技术博客
header-img: img/home-bg.jpg
email: mercyblitz@gmail.com
description: "关于程序、设计以及架构 | 小马哥,Javaer ,Software Engineer"
keyword: "小马哥, mercyblitz, Mercy Ma, 小马哥的技术博客"
url: "https://weibo.com/mercyblitz" # your host, for absolute URL
baseurl: "" # for example, '/blog' if your blog hosted on 'host/blog'
post:
author: mercyblitz
# Publish posts or collection documents with a future date.
future: true
# SNS settings
RSS: true
weibo_username: mercyblitz
github_username: mercyblitz
twitter_username: mercyblitz
#facebook_username: mercyblitz
#linkedin_username: firstname-lastname-idxxxx
# Build settings
# from 2016, 'pygments' is unsupported on GitHub Pages. Use 'rouge' for highlighting instead.
highlighter: rouge
permalink: pretty
paginate: 10
exclude: ["less","node_modules","Gruntfile.js","package.json","README.md","README.zh.md"]
anchorjs: true # if you want to customize anchor. check out line:181 of `post.html`
# If you have timezone issue (e.g. #68) in China, uncomment to use this:
#timezone: CN
# Gems
# from PR#40, to support local preview for Jekyll 3.0
# make sure you have this gem installed
# `$ gem install jekyll-paginate`
plugins: [jekyll-paginate]
# Markdown settings
# replace redcarpet to kramdown,
# although redcarpet can auto highlight code, the lack of header-id make the catalog impossible, so I switch to kramdown
# document: http://jekyllrb.com/docs/configuration/#kramdown
markdown: kramdown
kramdown:
input: GFM # use Github Flavored Markdown !important
syntax_highlighter_opts:
span:
line_numbers: false
block:
line_numbers: true
start_line: 1
# Disqus settings
disqus_username: mercyblitz
# Netease settings
netease_comment: false
# Analytics settings
# Baidu Analytics
# ba_track_id: [your track id]
# Google Analytics
# ga_track_id: TODO # Format: UA-xxxxxx-xx
# ga_domain: TODO
# Sidebar settings
sidebar: true # whether or not using Sidebar.
sidebar-about-description: "大家好,我是小马哥"
sidebar-avatar: /img/avatar-mecyblitz.jpg # use absolute URL, seeing it's used in both `/` and `/about/`
# Featured Tags
featured-tags: true # whether or not using Feature-Tags
featured-condition-size: 1 # A tag will be featured if the size of it is more than this condition value
# Progressive Web Apps
chrome-tab-theme-color: "#000000"
service-worker: true
# MathJax rendering for layout:page (e.g. post preview)
page-mathjax: false
# Friends
friends: [
{}
]
================================================
FILE: _includes/about/en.md
================================================
> Hard Code in Internet,Think Big Data too much
Hi, I'm Mercy Ma, as a member in [Apache Dubbo](https://dubbo.apache.org/) PPMC, an achitecture of [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba), and working on Open Source projects and Micro Services infrastructure, certificated SUN Java(SCJP、SCWCD、SCBCD), Oracle OCA and so on.
## Books
- 《Thinking in Spring Boot》(Chinese)
## Online Courses
- imooc
- [Spring Boot 2.0深度实践之核心技术篇](https://coding.imooc.com/class/252.html)
- Spring Boot 2.0深度实践之生态整合篇(即将上线...)
- SegmentFault
- [Java 微服务实践 - Spring Boot / Spring Cloud](https://segmentfault.com/ls/1650000011387052)
## Open Source Projects
- Spring Cloud
- [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)
- Apache
- [Apache Dubbo](https://github.com/apache/incubator-dubbo)
- [Apache Dubbo Spring Boot](https://github.com/apache/incubator-dubbo-spring-boot-project)
- Alibaba
- [Alibaba Nacos](https://github.com/alibaba/nacos)
- [Nacos Spring](https://github.com/nacos-group/nacos-spring-project)
- [Nacos Spring Boot](https://github.com/nacos-group/nacos-spring-boot-project)
- [Alibaba Sentinel](https://github.com/alibaba/Sentinel)
- [Velocity Spring Boot Starter](https://github.com/alibaba/velocity-spring-boot-project)
## Talks
- Spring Cloud Alibaba 致力于提供分布式应用开发的一站式解决方法 · [CNCC 2018](http://cncc2018.ccf.org.cn/cms/show.action?code=publish_ff80808162f165f90163070bf87105de&siteid=100000&channelid=0000000002)
- Dubbo Cloud Native 之路的实践与思考 · [Dubbo开发者沙龙(上海站)- 2018 Aliware技术行](https://www.itdks.com/eventlist/detail/2307)
- Dubbo 的过去、现在以及未来 · [2017 国际互联网大会(上海)](http://2017.thegiac.com/)
- 微服务实践之路(二) · [2016 SegmentFault 开发者大(杭州站)](https://segmentfault.com/sfdc-2016/hz)
- 微服务实践之路(一) · [2016 厦门互联网技术峰会](https://www.bagevent.com/event/227489)
================================================
FILE: _includes/about/zh.md
================================================
> 手淫互联网,意淫大数据
大家好,我是小马哥(mercyblitz),[Java 劝退师](https://www.douyu.com/mercyblitz),[Apache Dubbo](https://dubbo.apache.org/) PMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba) 项目架构师,通过 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的认证。目前为自由职业者,承接企业培训、架构设计和咨询等业务。
### [书籍出版](/my/books)
### [线上课程](/my/lessons/)
### [开源项目](/my/open-sources/)
### [报告分享](/my/presentations/)
### [企业培训](/my/training/)
- 电子邮箱:mercyblitz@gmail.com
- 微信:mercyblitz-1985
- [微博](https://weibo.com/mercyblitz):[@mercyblitz](https://weibo.com/mercyblitz)
- [Github](http://github.com/mercyblitz):[http://github.com/mercyblitz](http://github.com/mercyblitz)
- [Twitter](https://twitter.com/mercyblitz):[@mercyblitz](https://twitter.com/mercyblitz)
- 微信公众号:**次灵均阁**

## 社区交流
- 小马哥和小伙伴1号群(已满)
- 小马哥和小伙伴2号群(已满)
- 小马哥和小伙伴3号群

================================================
FILE: _includes/featured-tags.html
================================================
{% comment %}
@param {boolean} bottom - bottom will render <hr>
{% endcomment %}
{% if site.featured-tags %}
<section>
{% if include.bottom %}
<hr class="hidden-sm hidden-xs">
{% endif %}
<h5><a href="{{'/archive/' | prepend: site.baseurl }}">FEATURED TAGS</a></h5>
<div class="tags">
{% capture tags %}
{% comment %}
there must be no space between for and if otherwise this tricky sort won't work.
url_encode/decode is for escaping otherwise extra <a> will get generated
but it will break sort...
{% endcomment %}
{% for tag in site.tags %}{% if tag[1].size > site.featured-condition-size %}
<a data-sort="{{ site.posts.size | minus: tag[1].size | prepend: '0000' | slice: -4, 4 }}"
href="{{ site.baseurl }}/archive/?tag={{ tag[0] | url_encode }}"
title="{{ tag[0] }}"
rel="{{ tag[1].size }}">{{ tag[0] }}</a>
{% endif %}{% endfor %}
{% endcapture %}
{{ tags | split:'</a>' | sort | join:'</a>' }}
</div>
</section>
{% endif %}
================================================
FILE: _includes/footer.html
================================================
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<!-- SNS Link -->
{% include sns-links.html center=true %}
<p class="copyright text-muted">
Copyright © {{ site.title }} {{ site.time | date: '%Y' }}
<br>
Powered by <a href="http://huangxuan.me">Hux Blog</a> |
<iframe
style="margin-left: 2px; margin-bottom:-5px;"
frameborder="0" scrolling="0" width="100px" height="20px"
src="https://ghbtns.com/github-btn.html?user=huxpro&repo=huxpro.github.io&type=star&count=true" >
</iframe>
</p>
</div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="{{ "/js/jquery.min.js " | prepend: site.baseurl }}"></script>
<!-- Bootstrap Core JavaScript -->
<!-- Currently, only navbar scroll-down effect at desktop still depends on this -->
<script src="{{ "/js/bootstrap.min.js " | prepend: site.baseurl }}"></script>
<!-- Custom Theme JavaScript -->
<script src="{{ "/js/hux-blog.min.js " | prepend: site.baseurl }}"></script>
<!-- Service Worker -->
{% if site.service-worker %}
<script src="{{ "/js/snackbar.js " | prepend: site.baseurl }}"></script>
<script src="{{ "/js/sw-registration.js " | prepend: site.baseurl }}"></script>
{% endif %}
<!-- async load function -->
<script>
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
</script>
<!--
Because of the native support for backtick-style fenced code blocks
right within the Markdown is landed in Github Pages,
From V1.6, There is no need for Highlight.js,
so Huxblog drops it officially.
- https://github.com/blog/2100-github-pages-now-faster-and-simpler-with-jekyll-3-0
- https://help.github.com/articles/creating-and-highlighting-code-blocks/
- https://github.com/jneen/rouge/wiki/list-of-supported-languages-and-lexers
-->
<!--
<script>
async("http://cdn.bootcss.com/highlight.js/8.6/highlight.min.js", function(){
hljs.initHighlightingOnLoad();
})
</script>
<link href="http://cdn.bootcss.com/highlight.js/8.6/styles/github.min.css" rel="stylesheet">
-->
{% if page.title == 'Archive' %}
<!-- jquery.tagcloud.js -->
<script>
async('{{ "/js/jquery.tagcloud.js" | prepend: site.baseurl }}',function(){
$.fn.tagcloud.defaults = {
//size: {start: 1, end: 1, unit: 'em'},
color: {start: '#bbbbee', end: '#2f93b4'},
};
$('#tag_cloud a').tagcloud();
})
</script>
<script src='{{ "/js/archive.js " | prepend: site.baseurl }}'></script>
{% endif %}
<!--fastClick.js -->
<script>
async("//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js", function(){
var $nav = document.querySelector("nav");
if($nav) FastClick.attach($nav);
})
</script>
<!-- Google Analytics -->
{% if site.ga_track_id %}
<script>
// dynamic User by Hux
var _gaId = '{{ site.ga_track_id }}';
var _gaDomain = '{{ site.ga_domain }}';
// Originial
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', _gaId, _gaDomain);
ga('send', 'pageview');
</script>
{% endif %}
<!-- Baidu Tongji -->
{% if site.ba_track_id %}
<script>
// dynamic User by Hux
var _baId = '{{ site.ba_track_id }}';
// Originial
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "//hm.baidu.com/hm.js?" + _baId;
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
{% endif %}
<!-- Side Catalog -->
{% if page.catalog %}
<script type="text/javascript">
function generateCatalog (selector) {
// interop with multilangual
if ('{{ page.multilingual }}' == 'true') {
_containerSelector = 'div.post-container.active'
} else {
_containerSelector = 'div.post-container'
}
// init
var P = $(_containerSelector),a,n,t,l,i,c;
a = P.find('h1,h2,h3,h4,h5,h6');
// clean
$(selector).html('')
// appending
a.each(function () {
n = $(this).prop('tagName').toLowerCase();
i = "#"+$(this).prop('id');
t = $(this).text();
c = $('<a href="'+i+'" rel="nofollow">'+t+'</a>');
l = $('<li class="'+n+'_nav"></li>').append(c);
$(selector).append(l);
});
return true;
}
generateCatalog(".catalog-body");
// toggle side catalog
$(".catalog-toggle").click((function(e){
e.preventDefault();
$('.side-catalog').toggleClass("fold")
}))
/*
* Doc: https://github.com/davist11/jQuery-One-Page-Nav
* Fork by Hux to support padding
*/
async("{{ '/js/jquery.nav.js' | prepend: site.baseurl }}", function () {
$('.catalog-body').onePageNav({
currentClass: "active",
changeHash: !1,
easing: "swing",
filter: "",
scrollSpeed: 700,
scrollOffset: 0,
scrollThreshold: .2,
begin: null,
end: null,
scrollChange: null,
padding: 80
});
});
</script>
{% endif %}
<!-- Multi-Lingual -->
{% if page.multilingual %}
<!-- Handle Language Change -->
<script type="text/javascript">
// get nodes
var $zh = document.querySelector(".zh");
var $en = document.querySelector(".en");
var $select = document.querySelector("select");
// bind hashchange event
// Changes at v1.7.2: change the language flag from hash-basing to in-memory basing
// due to its confliction with catalog anchors.
// window.addEventListener('hashchange', _render);
// handle render
function _render(_hash){
var _hash = _hash || window.location.hash;
// en
if(_hash == "#en"){
$select.selectedIndex = 1;
$en.style.display = "block";
$en.classList.add("active");
$zh.style.display = "none";
$zh.classList.remove("active");
// zh by default
}else{
// not trigger onChange, otherwise cause a loop call.
$select.selectedIndex = 0;
$zh.style.display = "block";
$zh.classList.add("active");
$en.style.display = "none";
$en.classList.remove("active");
}
// interop with catalog
if ("{{ page.catalog }}") generateCatalog(".catalog-body");
}
// handle select change
function onLanChange(index){
if(index == 0){
_hash = "#zh"
}else{
_hash = "#en"
}
_render(_hash)
}
// init
_render();
</script>
{% endif %}
================================================
FILE: _includes/friends.html
================================================
{% if site.friends %}
<hr>
<h5>FRIENDS</h5>
<ul class="list-inline">
{% for friend in site.friends %}
<li><a href="{{friend.href}}">{{friend.title}}</a></li>
{% endfor %}
</ul>
{% endif %}
================================================
FILE: _includes/head.html
================================================
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="description" content="{{ site.description }}">
<meta name="keywords" content="{{ site.keyword }}">
<meta name="theme-color" content="{{ site.chrome-tab-theme-color }}">
<!-- Open Graph -->
<meta property="og:title" content="{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}">
{% case page.layout %}
{% when 'post' %}
<meta property="og:type" content="article">
<meta property="og:description" content="{{ page.excerpt | strip_html | truncate:200 }}">
{% if page.date %}
<meta property="article:published_time" content="{{ page.date | date: "%Y-%m-%dT%H:%M:%SZ" }}">
{% endif %}
{% if page.author %}
<meta property="article:author" content="{{ page.author }}">
{% endif %}
{% for tag in page.tags %}
<meta property="article:tag" content="{{ tag }}">
{% endfor %}
{% else %}
<meta property="og:type" content="website">
<meta property="og:description" content="{% if page.description %}{{ page.description }}{% else %}{{ site.description }}{% endif %}">
{% endcase %}
<meta property="og:image" content="{{ site.url }}{{ site.sidebar-avatar }}">
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
<meta property="og:site_name" content="{{ site.SEOTitle }}">
<title>{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}</title>
<!-- Web App Manifest -->
<link rel="manifest" href="{{ site.baseurl }}/pwa/manifest.json">
<!-- Favicon -->
<link rel="shortcut icon" href="{{ site.baseurl }}/img/favicon.ico">
<!-- Canonical URL -->
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
<!-- Bootstrap Core CSS -->
<link rel="stylesheet" href="{{ "/css/bootstrap.min.css" | prepend: site.baseurl }}">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ "/css/hux-blog.min.css" | prepend: site.baseurl }}">
<!-- Custom Fonts -->
<!-- <link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
<!-- Hux change font-awesome CDN to qiniu -->
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- ga & ba script hoook -->
<script></script>
</head>
================================================
FILE: _includes/intro-header.html
================================================
{% comment %}
@param {string} type - 'page' | 'post' | 'keynote'
@param {boolean} short
{% endcomment %}
{% if include.type == 'post' %}
<style type="text/css">
header.intro-header{
position: relative;
background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}');
background: {{ page.header-bg-css }};
}
{% if page.header-mask %}
header.intro-header .header-mask{
width: 100%;
height: 100%;
position: absolute;
background: rgba(0,0,0, {{ page.header-mask }});
}
{% endif %}
</style>
{% if page.header-style == 'text' %}
<header class="intro-header style-text" >
{% else %}
<header class="intro-header" >
{% endif %}
<div class="header-mask"></div>
{% if page.header-img-credit %}
<div class="header-img-credit">
Image by <a href="//{{page.header-img-credit-href}}">{{page.header-img-credit}}</a>
</div>
{% endif %}
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<div class="tags">
{% for tag in page.tags %}
<a class="tag" href="{{ site.baseurl }}/archive/?tag={{ tag | url_encode }}" title="{{ tag }}">{{ tag }}</a>
{% endfor %}
</div>
<h1>{{ page.title }}</h1>
{% comment %} always create a h2 for keeping the margin {% endcomment %}
<h2 class="subheading">{{ page.subtitle }}</h2>
<span class="meta">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %} on {{ page.date | date: "%B %-d, %Y" }}</span>
</div>
</div>
</div>
</div>
</header>
{% endif %}
{% if include.type == 'keynote' %}
<style type="text/css">
header.intro-header{
height: 500px;
overflow: hidden;
}
header.intro-header .container{
visibility: hidden;
}
header iframe{
width: 100%;
height: 100%;
border: 0;
}
</style>
<header class="intro-header" >
<iframe src="{{page.iframe}}"></iframe>
<!-- keep for SEO -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<div class="tags">
{% for tag in page.tags %}
<a class="tag" href="{{ site.baseurl }}/archive/?tag={{ tag | url_encode }}" title="{{ tag }}">{{ tag }}</a>
{% endfor %}
</div>
<h1>{{ page.title }}</h1>
{% comment %} always create a h2 for keeping the margin {% endcomment %}
<h2 class="subheading">{{ page.subtitle }}</h2>
<span class="meta">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %}
on {{ page.date | date: "%B %-d, %Y" }}</span>
</div>
</div>
</div>
</div>
</header>
{% endif %}
{% if include.type == 'page' %}
<header class="intro-header" style="background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
{% if include.short %}
<div class="site-heading" id="tag-heading">
{% else %}
<div class="site-heading">
{% endif %}
<h1>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</h1>
<span class="subheading">{{ page.description }}</span>
</div>
</div>
</div>
</div>
</header>
{% endif %}
================================================
FILE: _includes/mathjax_support.html
================================================
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
TeX: {
equationNumbers: {
autoNumber: "AMS"
}
},
SVG: {
scale: 90
},
tex2jax: {
inlineMath: [ ['$','$'] ],
displayMath: [ ['$$','$$'] ],
processEscapes: true,
}
});
</script>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG">
</script>
================================================
FILE: _includes/nav.html
================================================
<!-- Navigation -->
{% if page.nav-style == "invert" or page.header-style == "text" %}
<nav class="navbar navbar-default navbar-custom navbar-fixed-top invert">
{% else %}
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
{% endif %}
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header page-scroll">
<button type="button" class="navbar-toggle">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ site.baseurl }}/">{{ site.title }}</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div id="huxblog_navbar">
<div class="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="{{ site.baseurl }}/">首页</a>
</li>
<li>
<a href="{{ site.baseurl }}/archive/">我的文档</a>
</li>
<li>
<a href="{{ site.baseurl }}/my/books/">我的书籍</a>
</li>
<li>
<a href="{{ site.baseurl }}/my/lessons/">我的课程</a>
</li>
<li>
<a href="{{ site.baseurl }}/my/presentations/">我的演讲</a>
</li>
<li>
<a href="{{ site.baseurl }}/my/open-sources/">开源项目</a>
</li>
<li>
<a href="{{ site.baseurl }}/about/">关于我</a>
</li>
</ul>
</div>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<script>
// Drop Bootstarp low-performance Navbar
// Use customize navbar with high-quality material design animation
// in high-perf jank-free CSS3 implementation
var $body = document.body;
var $toggle = document.querySelector('.navbar-toggle');
var $navbar = document.querySelector('#huxblog_navbar');
var $collapse = document.querySelector('.navbar-collapse');
var __HuxNav__ = {
close: function(){
$navbar.className = " ";
// wait until animation end.
setTimeout(function(){
// prevent frequently toggle
if($navbar.className.indexOf('in') < 0) {
$collapse.style.height = "0px"
}
},400)
},
open: function(){
$collapse.style.height = "auto"
$navbar.className += " in";
}
}
// Bind Event
$toggle.addEventListener('click', function(e){
if ($navbar.className.indexOf('in') > 0) {
__HuxNav__.close()
}else{
__HuxNav__.open()
}
})
/**
* Since Fastclick is used to delegate 'touchstart' globally
* to hack 300ms delay in iOS by performing a fake 'click',
* Using 'e.stopPropagation' to stop 'touchstart' event from
* $toggle/$collapse will break global delegation.
*
* Instead, we use a 'e.target' filter to prevent handler
* added to document close HuxNav.
*
* Also, we use 'click' instead of 'touchstart' as compromise
*/
document.addEventListener('click', function(e){
if(e.target == $toggle) return;
if(e.target.className == 'icon-bar') return;
__HuxNav__.close();
})
</script>
================================================
FILE: _includes/short-about.html
================================================
<section class="visible-md visible-lg">
<hr>
<h5><a href="{{'/about/' | prepend: site.baseurl }}">ABOUT ME</a></h5>
<div class="short-about">
{% if site.sidebar-avatar %}
<img src="{{site.sidebar-avatar}}" />
{% endif %}
{% if site.sidebar-about-description %}
<p>{{site.sidebar-about-description}}</p>
{% endif %}
<!-- SNS Link -->
{% include sns-links.html %}
</div>
</section>
================================================
FILE: _includes/sns-links.html
================================================
{% comment %}
@param {Boolean} center
{% endcomment %}
{% if include.center %}
<ul class="list-inline text-center">
{% else %}
<ul class="list-inline">
{% endif %}
{% if site.RSS %}
<li>
<a href="{{ "/feed.xml" | prepend: site.baseurl }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-rss fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
{% if site.twitter_username %}
<li>
<a href="https://twitter.com/{{ site.twitter_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
{% if site.zhihu_username %}
<li>
<a target="_blank" href="https://www.zhihu.com/people/{{ site.zhihu_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-stack-1x fa-inverse">知</i>
</span>
</a>
</li>
{% endif %}
{% if site.weibo_username %}
<li>
<a target="_blank" href="http://weibo.com/{{ site.weibo_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-weibo fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
{% if site.facebook_username %}
<li>
<a target="_blank" href="https://www.facebook.com/{{ site.facebook_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-facebook fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
{% if site.github_username %}
<li>
<a target="_blank" href="https://github.com/{{ site.github_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
{% if site.linkedin_username %}
<li>
<a target="_blank" href="https://www.linkedin.com/in/{{ site.linkedin_username }}">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-linkedin fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
{% endif %}
</ul>
================================================
FILE: _layouts/default.html
================================================
<!DOCTYPE html>
<html lang="en">
{% include head.html %}
<!-- hack iOS CSS :active style -->
<body ontouchstart="">
{% include nav.html %}
{{ content }}
{% include footer.html %}
<!-- Image to hack wechat -->
<!-- <img src="/img/icon_wechat.png" width="0" height="0" /> -->
<!-- Migrate from head to bottom, no longer block render and still work -->
</body>
</html>
================================================
FILE: _layouts/keynote.html
================================================
---
layout: default
---
<!-- Image to hack wechat -->
<!-- <img src="/img/icon_wechat.png" width="0" height="0"> -->
<!-- <img src="{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}" width="0" height="0"> -->
<!-- Post Header -->
{% include intro-header.html type='keynote' %}
<!-- Post Content -->
<article>
<div class="container">
<div class="row">
<!-- Post Container -->
<div class="post-container
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1 ">
{{ content }}
<hr style="visibility: hidden;">
<ul class="pager">
{% if page.previous.url %}
<li class="previous">
<a href="{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}" data-toggle="tooltip" data-placement="top" title="{{page.previous.title}}">
Previous<br>
<span>{{page.previous.title}}</span>
</a>
</li>
{% endif %}
{% if page.next.url %}
<li class="next">
<a href="{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}" data-toggle="tooltip" data-placement="top" title="{{page.next.title}}">
Next<br>
<span>{{page.next.title}}</span>
</a>
</li>
{% endif %}
</ul>
<hr style="visibility: hidden;">
{% if site.disqus_username %}
<!-- disqus 评论框 start -->
<div class="comment">
<div id="disqus_thread" class="disqus-thread">
</div>
</div>
<!-- disqus 评论框 end -->
{% endif %}
</div>
<!-- Sidebar Container -->
<div class="sidebar-container
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1 ">
<!-- Featured Tags -->
{% include featured-tags.html %}
<!-- Friends Blog -->
{% include friends.html %}
</div>
</div>
</div>
</article>
<!-- resize header to fullscreen keynotes -->
<script>
var $header = document.getElementsByTagName("header")[0];
function resize(){
/*
* leave 85px to both
* - told/imply users that there has more content below
* - let user can scroll in mobile device, seeing the keynote-view is unscrollable
*/
$header.style.height = (window.innerHeight-85) + 'px';
}
document.addEventListener('DOMContentLoaded', function(){
resize();
})
window.addEventListener('load', function(){
resize();
})
window.addEventListener('resize', function(){
resize();
})
resize();
</script>
{% if site.disqus_username %}
<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */
var disqus_shortname = "{{site.disqus_username}}";
var disqus_identifier = "{{page.id}}";
var disqus_url = "{{site.url}}{{page.url}}";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<!-- disqus 公共JS代码 end -->
{% endif %}
{% if site.anchorjs %}
<!-- async load function -->
<script>
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
async("//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js",function(){
anchors.options = {
visible: 'always',
placement: 'right',
icon: '#'
};
anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
})
</script>
<style>
/* place left on bigger screen */
@media all and (min-width: 800px) {
.anchorjs-link{
position: absolute;
left: -0.75em;
font-size: 1.1em;
margin-top : -0.1em;
}
}
</style>
{% endif %}
================================================
FILE: _layouts/page.html
================================================
---
layout: default
---
<!-- Page Header -->
{% include intro-header.html type='page' %}
<!-- Main Content -->
<div class="container">
<div class="row">
{% if site.sidebar == false %}
<!-- NO SIDEBAR -->
<!-- PostList Container -->
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 postlist-container">
{{ content }}
</div>
<!-- Sidebar Container -->
<div class="
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1
sidebar-container">
<!-- Featured Tags -->
{% include featured-tags.html %}
<!-- Friends Blog -->
{% include friends.html %}
</div>
{% else %}
<!-- USE SIDEBAR -->
<!-- PostList Container -->
<div class="
col-lg-8 col-lg-offset-1
col-md-8 col-md-offset-1
col-sm-12
col-xs-12
postlist-container
">
{{ content }}
</div>
<!-- Sidebar Container -->
<div class="
col-lg-3 col-lg-offset-0
col-md-3 col-md-offset-0
col-sm-12
col-xs-12
sidebar-container
">
<!-- Featured Tags -->
{% include featured-tags.html %}
<!-- Short About -->
{% include short-about.html %}
<!-- Friends Blog -->
{% include friends.html %}
</div>
{% endif %}
</div>
</div>
{% if site.page-mathjax %}
<!-- Add support for Mathjax by Voleking-->
<!-- If you want to see formulars well in post preview, Maybe you should add this.-->
<!-- However, most of the time formulars may not appear in the post preview, you can delete it.-->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
TeX: {
equationNumbers: {
autoNumber: "AMS"
}
},
tex2jax: {
inlineMath: [ ['$','$'] ],
displayMath: [ ['$$','$$'] ],
processEscapes: true,
}
});
</script>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
{% endif %}
================================================
FILE: _layouts/post.html
================================================
---
layout: default
---
<!-- Image to hack wechat -->
<!-- <img src="/img/icon_wechat.png" width="0" height="0"> -->
<!-- <img src="{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}" width="0" height="0"> -->
<!-- Post Header -->
{% include intro-header.html type='post' %}
<!-- Post Content -->
<article>
<div class="container">
<div class="row">
<!-- Post Container -->
<div class="
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1
post-container">
<!-- Multi-Lingual -->
{% if page.multilingual %}
<!-- Language Selector -->
<select class="sel-lang" onchange= "onLanChange(this.options[this.options.selectedIndex].value)">
<option value="0" selected> 中文 Chinese </option>
<option value="1"> 英文 English </option>
</select>
{% endif %}
{{ content }}
<hr style="visibility: hidden;">
<ul class="pager">
{% if page.previous.url %}
<li class="previous">
<a href="{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}" data-toggle="tooltip" data-placement="top" title="{{page.previous.title}}">
Previous<br>
<span>{{page.previous.title}}</span>
</a>
</li>
{% endif %}
{% if page.next.url %}
<li class="next">
<a href="{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}" data-toggle="tooltip" data-placement="top" title="{{page.next.title}}">
Next<br>
<span>{{page.next.title}}</span>
</a>
</li>
{% endif %}
</ul>
<hr style="visibility: hidden;">
{% if site.disqus_username %}
<!-- disqus 评论框 start -->
<div class="comment">
<div id="disqus_thread" class="disqus-thread"></div>
</div>
<!-- disqus 评论框 end -->
{% endif %}
{% if site.netease_comment %}
<!-- 网易云跟帖 评论框 start -->
<div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
<!-- 网易云跟帖 评论框 end -->
{% endif %}
</div>
<!-- Side Catalog Container -->
{% if page.catalog %}
<div class="
col-lg-2 col-lg-offset-0
visible-lg-block
sidebar-container
catalog-container">
<div class="side-catalog">
<hr class="hidden-sm hidden-xs">
<h5>
<a class="catalog-toggle" href="#">CATALOG</a>
</h5>
<ul class="catalog-body"></ul>
</div>
</div>
{% endif %}
<!-- Sidebar Container -->
<div class="
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1
sidebar-container">
<!-- Featured Tags -->
{% include featured-tags.html bottom=true %}
<!-- Friends Blog -->
{% include friends.html %}
</div>
</div>
</div>
</article>
<!-- add support for mathjax by voleking-->
{% if page.mathjax %}
{% include mathjax_support.html %}
{% endif %}
{% if site.netease_comment %}
<!-- 网易云跟帖JS代码 start -->
<script src="https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js"></script>
<script>
var cloudTieConfig = {
url: document.location.href,
sourceId: "",
productKey: "de25fc98a6fe48b3bc8a7ae765da99a0",
target: "cloud-tie-wrapper"
};
var yunManualLoad = true;
Tie.loader("aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s", true);
</script>
<!-- 网易云跟帖JS代码 end -->
{% endif %}
{% if site.disqus_username %}
<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */
var disqus_shortname = "{{site.disqus_username}}";
var disqus_identifier = "{{page.id}}";
var disqus_url = "{{site.url}}{{page.url}}";
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<!-- disqus 公共JS代码 end -->
{% endif %}
{% if site.anchorjs %}
<!-- async load function -->
<script>
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
async("//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js",function(){
anchors.options = {
visible: 'always',
placement: 'right',
icon: '#'
};
anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
})
</script>
<style>
/* place left on bigger screen */
@media all and (min-width: 800px) {
.anchorjs-link{
position: absolute;
left: -0.75em;
font-size: 1.1em;
margin-top : -0.1em;
}
}
</style>
{% endif %}
================================================
FILE: _posts/2018-01-01-Dubbo 注解驱动.md
================================================
# Dubbo 注解驱动(Annotation-Driven)
## 注解驱动(Annotation-Driven)
### `@DubboComponentScan`
#### 起始版本: `2.5.7`
#### `<dubbo:annotation> `历史遗留问题
##### 1. 注解支持不充分
在 Dubbo `2.5.7`之前的版本 ,Dubbo 提供了两个核心注解 `@Service` 以及 `@Reference`,分别用于Dubbo 服务提供和 Dubbo 服务引用。
其中,`@Service` 作为 XML 元素 `<dubbo:service>`的替代注解,与 Spring Framework `@org.springframework.stereotype.Service` 类似,用于服务提供方 Dubbo 服务暴露。与之相对应的`@Reference`,则是替代`<dubbo:reference` 元素,类似于 Spring 中的 `@Autowired`。
`2.5.7` 之前的Dubbo,与早期的 Spring Framework 2.5 存在类似的不足,即注解支持不够充分。注解需要和 XML 配置文件配合使用,如下所示:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="annotation-provider"/>
<dubbo:registry address="127.0.0.1:4548"/>
<dubbo:annotation package="com.alibaba.dubbo.config.spring.annotation.provider"/>
</beans>
```
##### 2. `@Service` Bean 不支持 Spring AOP
同时,使用 `<dubbo:annotation> ` 方式扫描后的Dubbo `@Service` ,在 Spring 代理方面存在问题,如 GitHub 上的 issue https://github.com/alibaba/dubbo/issues/794:
> 关于dubbo @Service注解生成ServiceBean时, interface获取成spring 的代理对象的bug
>
> >在项目里, 我使用了
> >
> >```java
> >@Service
> >@Transactional
> >@com.alibaba.dubbo.config.annotation.Service
> >public class SUserJpushServiceImp
> >```
> >
> >的形式, 来暴露服务。但是在发布服务的时候, interface class 是通过
> >``
> >serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
> >``
> >的形式获取, 刚好, 我的service都使用了@Transactional注解, 对象被代理了。所以获取到的interface是Spring的代理接口...
不少热心的小伙伴不仅发现这个历史遗留问题,而且提出了一些修复方案。同时,为了更好地适配 Spring 生命周期以及将 Dubbo 完全向注解驱动编程模型过渡,因此,引入了全新 Dubbo 组件扫描注解 - `@DubboComponentScan`。
> 注: `<dubbo:annotation> ` Spring AOP 问题将在 `2.5.9` 中修复:https://github.com/alibaba/dubbo/issues/1125
##### 3. @Reference 不支持字段继承性
假设有一个 Spring Bean `AnnotationAction` 直接通过字段`annotationService` 标记 `@Reference` 引用 `AnnotationService` :
```java
package com.alibaba.dubbo.examples.annotation.action;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.examples.annotation.api.AnnotationService;
import org.springframework.stereotype.Component;
@Component("annotationAction")
public class AnnotationAction {
@Reference
private AnnotationService annotationService;
public String doSayHello(String name) {
return annotationService.sayHello(name);
}
}
```
当`AnnotationAction` 被 XML 元素 `<dubbo:annotation>` 扫描后:
```xml
<dubbo:annotation package="com.alibaba.dubbo.examples.annotation.action"/>
```
字段 `annotationService` 能够引用到 `AnnotationService`,执行 `doSayHello` 方法能够正常返回。
如果将字段`annotationService` 抽取到`AnnotationAction` 的父类`BaseAction` 后,`AnnotationService` 无法再被引用,改造如下所示:
`AnnotationAction.java`
```java
@Component("annotationAction")
public class AnnotationAction extends BaseAction {
public String doSayHello(String name) {
return getAnnotationService().sayHello(name);
}
}
```
`BaseAction.java`
```java
public abstract class BaseAction {
@Reference
private AnnotationService annotationService;
protected AnnotationService getAnnotationService() {
return annotationService;
}
}
```
改造后,再次执行 `doSayHello` 方法,`NullPointerException` 将会被抛出。说明`<dubbo:annotation>` 并不支持`@Reference` 字段继承性。
了解了历史问题,集合整体愿景,下面介绍`@DubboComponentScan` 的设计原则。
#### 设计原则
Spring Framework 3.1 引入了新 Annotation - `@ComponentScan` , 完全替代了 XML 元素 ` <context:component-scan>` 。同样, `@DubboComponentScan` 作为 Dubbo `2.5.7` 新增的 Annotation,也是XML 元素 `<dubbo:annotation>` 的替代方案。
在命名上(类名以及属性方法),为了简化使用和关联记忆,Dubbo 组件扫描 Annotation `@DubboComponentScan`,借鉴了 Spring Boot 1.3 引入的 `@ServletComponentScan`。定义如下:
```java
public @interface DubboComponentScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @DubboComponentScan("org.my.pkg")} instead of
* {@code @DubboComponentScan(basePackages="org.my.pkg")}.
*
* @return the base packages to scan
*/
String[] value() default {};
/**
* Base packages to scan for annotated @Service classes. {@link #value()} is an
* alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the base packages to scan
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated @Service classes. The package of each class specified will be
* scanned.
*
* @return classes from the base packages to scan
*/
Class<?>[] basePackageClasses() default {};
}
```
> 注意:`basePackages()` 和 `value()` 均能支持占位符(placeholder)指定的包名
在职责上,`@DubboComponentScan` 相对于 Spring Boot `@ServletComponentScan` 更为繁重,原因在于处理 Dubbo `@Service` 类暴露 Dubbo 服务外,还有帮助 Spring Bean `@Reference`字段或者方法注入 Dubbo 服务代理。
在场景上,Spring Framework `@ComponentScan` 组件扫描逻辑更为复杂。而在 `@DubboComponentScan` 只需关注 `@Service` 和 `@Reference` 处理。
在功能上, `@DubboComponentScan` 不但需要提供完整 Spring AOP 支持的能力,而且还得具备`@Reference ` 字段可继承性的能力。
了解基本设计原则后,下面通过完整的示例,简介`@DubboComponentScan` 使用方法以及注意事项。
#### 使用方法
后续通过服务提供方(`@Serivce`)以及服务消费方(`@Reference`)两部分来介绍`@DubboComponentScan` 使用方法。
假设,服务提供方和服务消费分均依赖服务接口`DemoService`:
```java
package com.alibaba.dubbo.demo;
public interface DemoService {
String sayHello(String name);
}
```
##### 服务提供方(`@Serivce`)
###### 实现 `DemoService`
服务提供方实现`DemoService` - `AnnotationDemoService` ,同时标注 Dubbo `@Service` :
```java
package com.alibaba.dubbo.demo.provider;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation {@link DemoService} 实现
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Service
public class AnnotationDemoService implements DemoService {
@Override
public String sayHello(String name) {
return "Hello , " + name;
}
}
```
###### 服务提供方 Annotation 配置
将 `AnnotationDemoService` 暴露成Dubbo 服务,需要依赖 Spring Bean:`AplicationConfig`、`ProtocolConfig` 以及 `RegistryConfig` 。这三个 Spring Bean 过去可通过 XML 文件方式组装 Spring Bean:
```xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
<!-- 当前应用信息配置 -->
<dubbo:application name="dubbo-annotation-provider"/>
<!-- 连接注册中心配置 -->
<dubbo:registry id="my-registry" address="N/A"/>
<dubbo:protocol name="dubbo" port="12345"/>
</beans>
```
以上装配方式不予推荐,推荐使用 Annotation 配置,因此可以换成 Spring `@Configuration` Bean 的形式:
```java
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务提供方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 扫描 Dubbo 组件
public class ProviderConfiguration {
/**
* 当前应用配置
*/
@Bean("dubbo-annotation-provider")
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-provider");
return applicationConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean("my-registry")
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean("dubbo")
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(12345);
return protocolConfig;
}
}
```
###### 服务提供方引导类
```java
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服务提供方引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ProviderBootstrap {
public static void main(String[] args) {
// 创建 Annotation 配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册配置 Bean
context.register(ProviderConfiguration.class);
// 启动上下文
context.refresh();
// 获取 DemoService Bean
DemoService demoService = context.getBean(DemoService.class);
// 执行 sayHello 方法
String message = demoService.sayHello("World");
// 控制台输出信息
System.out.println(message);
}
}
```
`ProviderBootstrap` 启动并执行后,控制输出与预期一致:
```
Hello , World
```
以上直接结果说明 `@DubboComponentScan("com.alibaba.dubbo.demo.provider")` 扫描后,标注 Dubbo `@Service` 的 `AnnotationDemoService` 被注册成 Spring Bean,可从 Spring ApplicationContext 自由获取。
##### 服务消费方(`@Reference`)
###### 服务 `DemoService`
```java
package com.alibaba.dubbo.demo.consumer;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation 驱动 {@link DemoService} 消费方
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class AnnotationDemoServiceConsumer {
@Reference(url = "dubbo://127.0.0.1:12345")
private DemoService demoService;
public String doSayHell(String name) {
return demoService.sayHello(name);
}
}
```
###### 服务消费方 Annotation 配置
与服务提供方配置类似,服务消费方也许 Dubbo 相关配置 Bean - `ConsumerConfiguration`
```java
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务消费方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan
public class ConsumerConfiguration {
/**
* 当前应用配置
*/
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 注册 AnnotationDemoServiceConsumer,@DubboComponentScan 将处理其中 @Reference 字段。
* 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的话,
* 即使 @DubboComponentScan 指定 package 也不会进行处理,与 Spring @Autowired 同理
*/
@Bean
public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {
return new AnnotationDemoServiceConsumer();
}
}
```
###### 服务消费方引导类
服务消费方需要先引导服务提供方,下面的实例将会启动两个 Spring 应用上下文,首先引导服务提供方 Spring 应用上下文,同时,需要复用前面Annotation 配置 `ProviderConfiguration`:
```java
/**
* 启动服务提供方上下文
*/
private static void startProviderContext() {
// 创建 Annotation 配置上下文
AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
// 注册配置 Bean
providerContext.register(ProviderConfiguration.class);
// 启动服务提供方上下文
providerContext.refresh();
}
```
然后引导服务消费方Spring 应用上下文:
```java
/**
* 启动并且返回服务消费方上下文
*
* @return AnnotationConfigApplicationContext
*/
private static ApplicationContext startConsumerContext() {
// 创建服务消费方 Annotation 配置上下文
AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
// 注册服务消费方配置 Bean
consumerContext.register(ConsumerConfiguration.class);
// 启动服务消费方上下文
consumerContext.refresh();
// 返回服务消费方 Annotation 配置上下文
return consumerContext;
}
```
完整的引导类实现:
```java
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.config.ConsumerConfiguration;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服务消费端引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ConsumerBootstrap {
public static void main(String[] args) {
// 启动服务提供方上下文
startProviderContext();
// 启动并且返回服务消费方上下文
ApplicationContext consumerContext = startConsumerContext();
// 获取 AnnotationDemoServiceConsumer Bean
AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);
// 执行 doSayHello 方法
String message = consumer.doSayHello("World");
// 输出执行结果
System.out.println(message);
}
/**
* 启动并且返回服务消费方上下文
*
* @return AnnotationConfigApplicationContext
*/
private static ApplicationContext startConsumerContext() {
// 创建服务消费方 Annotation 配置上下文
AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
// 注册服务消费方配置 Bean
consumerContext.register(ConsumerConfiguration.class);
// 启动服务消费方上下文
consumerContext.refresh();
// 返回服务消费方 Annotation 配置上下文
return consumerContext;
}
/**
* 启动服务提供方上下文
*/
private static void startProviderContext() {
// 创建 Annotation 配置上下文
AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
// 注册配置 Bean
providerContext.register(ProviderConfiguration.class);
// 启动服务提供方上下文
providerContext.refresh();
}
}
```
运行`ConsumerBootstrap`结果,仍然符合期望,`AnnotationDemoServiceConsumer` 输出:
```
Hello , World
```
#### Spring AOP 支持
前面提到 `<dubbo:annotation> ` 注册 Dubbo `@Service` 组件后,在 Spring AOP 支持方面存在问题。事务作为 Spring AOP 的功能扩展,自然也会在 `<dubbo:annotation> `中不支持。
`@DubboComponentScan` 针对以上问题,实现了对 Spring AOP 是完全兼容。将上述服务提供方 Annotation 配置做出一定的调整,标注`@EnableTransactionManagement` 以及自定义实现`PlatformTransactionManager` :
```java
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 扫描 Dubbo 组件
@EnableTransactionManagement // 激活事务管理
public class ProviderConfiguration {
// 省略其他配置 Bean 定义
/**
* 自定义事务管理器
*/
@Bean
@Primary
public PlatformTransactionManager transactionManager() {
return new PlatformTransactionManager() {
@Override
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
System.out.println("get transaction ...");
return new SimpleTransactionStatus();
}
@Override
public void commit(TransactionStatus status) throws TransactionException {
System.out.println("commit transaction ...");
}
@Override
public void rollback(TransactionStatus status) throws TransactionException {
System.out.println("rollback transaction ...");
}
};
}
}
```
同时调整 `AnnotationDemoService` - 增加`@Transactional` 注解:
```java
@Service
@Transactional
public class AnnotationDemoService implements DemoService {
// 省略实现,保持不变
}
```
再次运行`ConsumerBootstrap` , 观察控制台输出内容:
```
get transaction ...
commit transaction ...
Hello , World
```
输入内容中多处了两行,说明自定义 `PlatformTransactionManager` `getTransaction(TransactionDefinition)` 以及 `commit(TransactionStatus) ` 方法被执行,进而说明 `AnnotationDemoService` 的`sayHello(String)` 方法执行时,事务也伴随执行。
#### 注意事项
`ConsumerConfiguration` 上的 `@DubboComponentScan` 并没有指定 `basePackages` 扫描,这种情况会将`ConsumerConfiguration` 当做 `basePackageClasses` ,即扫描`ConsumerConfiguration` 所属的 package `com.alibaba.dubbo.demo.config` 以及子 package。由于当前示例中,不存在标注 Dubbo `@Service`的类,因此在运行时日志(如果开启的话)会输出警告信息:
```
WARN : [DUBBO] No Spring Bean annotating Dubbo's @Service was found in Spring BeanFactory, dubbo version: 2.0.0, current host: 127.0.0.1
```
以上信息大可不必担忧,因为 `@DubboComponentScan` 除了扫描 Dubbo `@Service` 组件以外,还将处理 `@Reference`字段注入。然而读者特别关注`@Reference`字段注入的规则。
以上实现为例,`AnnotationDemoServiceConsumer` 必须申明为 Spring `@Bean` 或者 `@Component`(或者其派生注解),否则 `@DubboComponentScan` 不会主动将标注 `@Reference`字段所在的声明类提成为 Spring Bean,换句话说,如果 `@Reference`字段所在的声明类不是 Spring Bean 的话, `@DubboComponentScan` 不会处理`@Reference`注入,其原理与 Spring `@Autowired` 一致。
以上使用不当可能会导致相关问题,如 GitHub 上曾有小伙伴提问:https://github.com/alibaba/dubbo/issues/825
> **li362692680** 提问:
>
> > @DubboComponentScan注解在消费端扫描包时扫描的是 @Service注解??不是@Reference注解??
> > 启动时报
> > DubboComponentScanRegistrar-85]-[main]-[INFO] 0 annotated @Service Components { [] }
>
> 笔者(**mercyblitz**)回复:
>
> > `@Reference` 类似于 `@Autowired` 一样,首先其申明的类必须被 Spring 上下文当做一个Bean,因此,Dubbo 并没有直接将 `@Reference` 字段所在的类提升成 Bean。
> >
> > 综上所述,这并不是一个问题,而是用法不当!
#### 已知问题
最新发布的 Dubbo `2.5.8` 中,`@DubboComponentScan` 在以下特殊场景下存在 Spring `@Service` 不兼容情况:
> 假设有两个服务实现类 `A` 和 `B`,同时存放在`com.acme` 包下:
>
> * `A` 标注 Dubbo `@Service`
> * `B` 标注 Dubbo `@Service` 和 Spring `@Service`
>
> 当 Spring `@ComponentScan` 先扫描`com.acme` 包时,`B` 被当做 Spring Bean 的候选类。随后,`@DubboComponentScan` 也扫描相同的包。当应用启动时,`A` 和 `B` 虽然都是 Spring Bean,可仅 `A` 能够暴露 Dubbo 服务,`B` 则丢失。
问题版本:`2.5.7`、`2.5.8`
问题详情:https://github.com/alibaba/dubbo/issues/1120
修复版本:`2.5.9`(下个版本)
================================================
FILE: _posts/2018-01-18-Dubbo 外部化配置.md
================================================
# Dubbo 外部化配置(Externalized Configuration)
## 外部化配置(External Configuration)
在[Dubbo 注解驱动](Dubbo-Annotation-Driven.md)例子中,无论是服务提供方,还是服务消费方,均需要转配相关配置Bean:
```java
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
```
虽然实现类似于`ProviderConfiguration` 和 `ConsumerConfiguration` 这样的 Spring `@Configuration` Bean 成本并不高,不过通过 Java Code 的方式定义配置 Bean,或多或少是一种 Hard Code(硬编码)的行为,缺少弹性。
尽管在 Spring 应用中,可以通过 `@Value` 或者 `Environment` 的方式获取外部配置,其代码简洁性以及类型转换灵活性存在明显的不足。因此,Spring Boot 提出了外部化配置(External Configuration)的感念,即通过程序以外的配置源,动态地绑定指定类型。
随着 Spring Boot / Spring Cloud 应用的流行,开发人员逐渐地接受并且使用 Spring Boot 外部化配置(External Configuration),即通过 `application.properties` 或者 `bootstrap.properties` 装配配置 Bean。
下列表格记录了 Dubbo 内置配置类:
| 配置类 | 标签 | 用途 | 解释 |
| ------------------- | ---------------------- | ------ | ---------------------------------------- |
| `ProtocolConfig` | `<dubbo:protocol/>` | 协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
| `ApplicationConfig` | `<dubbo:application/>` | 应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
| `ModuleConfig` | `<dubbo:module/>` | 模块配置 | 用于配置当前模块信息,可选 |
| `RegistryConfig` | `<dubbo:registry/>` | 注册中心配置 | 用于配置连接注册中心相关信息 |
| `MonitorConfig` | `<dubbo:monitor/>` | 监控中心配置 | 用于配置连接监控中心相关信息,可选 |
| `ProviderConfig` | `<dubbo:provider/>` | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
| `ConsumerConfig` | `<dubbo:consumer/>` | 消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
| `MethodConfig` | `<dubbo:method/>` | 方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
| `ArgumentConfig` | `<dubbo:argument/>` | 参数配置 | 用于指定方法参数配置 |
通过申明对应的 Spring 扩展标签,在 Spring 应用上下文中将自动生成相应的配置 Bean。
在 Dubbo 官方用户手册的[“属性配置”](http://dubbo.io/books/dubbo-user-book/configuration/properties.html)章节中,`dubbo.properties` 配置属性能够映射到 `ApplicationConfig` 、`ProtocolConfig` 以及 `RegistryConfig` 的字段。从某种意义上来说,`dubbo.properties` 也是 Dubbo 的外部化配置。
其中,引用“映射规则”的内容:
>## 映射规则
>
>将 XML 配置的标签名,加属性名,用点分隔,多个属性拆成多行
>
>* 比如:`dubbo.application.name=foo`等价于`<dubbo:application name="foo" />`
>* 比如:`dubbo.registry.address=10.20.153.10:9090`等价于`<dubbo:registryaddress="10.20.153.10:9090" />`
>
>如果 XML 有多行同名标签配置,可用 id 号区分,如果没有 id 号将对所有同名标签生效
>
>* 比如:`dubbo.protocol.rmi.port=1234`等价于`<dubbo:protocol id="rmi" name="rmi" port="1099" />`[2](http://dubbo.io/books/dubbo-user-book/configuration/properties.html#fn_2)
>* 比如:`dubbo.registry.china.address=10.20.153.10:9090`等价于`<dubbo:registry id="china"address="10.20.153.10:9090" />`
>
>下面是 dubbo.properties 的一个典型配置:
>
>```
>dubbo.application.name=foo
>dubbo.application.owner=bar
>dubbo.registry.address=10.20.153.10:9090
>```
根据“映射规则”,Dubbo 即支持单配置 Bean 映射,也支持多 Bean 映射。综合以上需求,既要兼容 Dubbo 已有的一个或多个 Bean 字段映射绑定,也支持外部化配置。
> 特别提醒:外部化配置(External Configuration)并非 Spring Boot 特有,即使在 Spring Framework 场景下亦能支持。也就是说 Dubbo 外部化配置即可在 Spring Framework 中工作,也能在 Spring Boot 中运行。
Dubbo 外部化配置(External Configuration) 支持起始版本为:`2.5.8`
### `@EnableDubboConfig`
#### 起始版本:`2.5.8`
#### 使用说明
##### `@EnableDubboConfig` 定义
```java
public @interface EnableDubboConfig {
/**
* It indicates whether binding to multiple Spring Beans.
*
* @return the default value is <code>false</code>
* @revised 2.5.9
*/
boolean multiple() default false;
}
```
* `multiple` : 表示是否支持多Dubbo 配置 Bean 绑定。默认值为 `false` ,即单 Dubbo 配置 Bean 绑定
##### 单 Dubbo 配置 Bean 绑定
为了更好地向下兼容,`@EnableDubboConfig` 提供外部化配置属性与 Dubbo 配置类之间的绑定,其中映射关系如下:
| 配置类 | 外部化配置属性前缀 | 用途 |
| ------------------- | ------------------- | ------ |
| `ProtocolConfig` | `dubbo.protocol` | 协议配置 |
| `ApplicationConfig` | `dubbo.application` | 应用配置 |
| `ModuleConfig` | `dubbo.module` | 模块配置 |
| `RegistryConfig` | `dubbo.registry` | 注册中心配置 |
| `MonitorConfig` | `dubbo.monitor` | 监控中心配置 |
| `ProviderConfig` | `dubbo.provider` | 提供方配置 |
| `ConsumerConfig` | `dubbo.consumer` | 消费方配置 |
当标注 `@EnableDubboConfig` 的类被扫描注册后,同时 Spring(Spring Boot)应用配置(`PropertySources`)中存在`dubbo.application.*` 时,`ApplicationConfig` Bean 将被注册到在 Spring 上下文。否则,不会被注册。如果出现`dubbo.registry.*`的配置,那么,`RegistryConfig` Bean 将会创建,以此类推。即按需装配 Dubbo 配置 Bean。
如果需要指定配置 Bean的 id,可通过`**.id` 属性设置,以`dubbo.application` 为例:
```properties
## application
dubbo.application.id = applicationBean
dubbo.application.name = dubbo-demo-application
```
以上配置等同于以下 Java Config Bean:
```java
@Bean("applicationBean")
public ApplicationConfig applicationBean() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-demo-application");
return applicationConfig;
}
```
大致上配置属性与配置类绑定模式 - `dubbo.application.* ` 映射到 `ApplicationConfig` 中的字段。
> 注:当配置属性名称无法在配置类中找到字段时,将会忽略绑定
##### 多 Dubbo 配置 Bean 绑定
Dubbo `@Service` 和 `@Reference` 允许 Dubbo 应用关联`ApplicationConfig` Bean 或者指定多个`RegistryConfig` Bean 等能力。换句话说,Dubbo 应用上下文中可能存在多个`ApplicationConfig` 等 Bean定义。
为了适应以上需要,因此从Dubbo `2.5.9` 开始,`@EnableDubboConfig` 支持多 Dubbo 配置 Bean 绑定,同时按照业界规约标准,与单 Dubbo 配置 Bean 绑定约定不同,配置属性前缀均为英文复数形式:
> 详情请参考 :https://github.com/alibaba/dubbo/issues/1141
* `dubbo.applications`
* `dubbo.modules`
* `dubbo.registries`
* `dubbo.protocols`
* `dubbo.monitors`
* `dubbo.providers`
* `dubbo.consumers`
以`dubbo.applications` 为例,基本的模式如下:
```properties
dubbo.applications.${bean-name}.property-name = ${property-value}
```
请读者注意,在单 Dubbo 配置 Bean 绑定时,可以通过指定`id` 属性的方式,定义`ApplicationConfig` Bean 的ID,即`dubbo.application.id`。
而在多 Dubbo 配置 Bean 绑定时,Bean ID 则由`dubbo.applications.`与属性字段名称(`.property-name`)之间的字符来表达。
如下配置:
```properties
# multiple Bean definition
dubbo.applications.applicationBean.name = dubbo-demo-application
dubbo.applications.applicationBean2.name = dubbo-demo-application2
dubbo.applications.applicationBean3.name = dubbo-demo-application3
```
该配置内容中,绑定了三个`ApplicationConfig` Bean,分别是`applicationBean`、`applicationBean2`以及`applicationBean3`
#### 示例说明
`@EnableDubboConfig` 的使用方法很简答, 再次强调一点,当规约的外部配置存在时,相应的 Dubbo 配置类 才会提升为 Spring Bean。简言之,按需装配。
##### 单 Dubbo 配置 Bean 绑定
###### 外部化配置文件
将以下内容的外部化配置文件物理路径为:`classpath:/META-INF/config.properties`:
```properties
# 单 Dubbo 配置 Bean 绑定
## application
dubbo.application.id = applicationBean
dubbo.application.name = dubbo-demo-application
## module
dubbo.module.id = moduleBean
dubbo.module.name = dubbo-demo-module
## registry
dubbo.registry.address = zookeeper://192.168.99.100:32770
## protocol
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880
## monitor
dubbo.monitor.address = zookeeper://127.0.0.1:32770
## provider
dubbo.provider.host = 127.0.0.1
## consumer
dubbo.consumer.client = netty
```
###### `@EnableDubboConfig` 配置 Bean
```java
/**
* Dubbo 配置 Bean
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@EnableDubboConfig
@PropertySource("META-INF/config.properties")
@Configuration
public class DubboConfiguration {
}
```
###### 实现引导类
```java
/**
* Dubbo 配置引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboConfigurationBootstrap {
public static void main(String[] args) {
// 创建配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前配置 Bean
context.register(DubboConfiguration.class);
context.refresh();
// application
ApplicationConfig applicationConfig = context.getBean("applicationBean", ApplicationConfig.class);
System.out.printf("applicationBean.name = %s \n", applicationConfig.getName());
// module
ModuleConfig moduleConfig = context.getBean("moduleBean", ModuleConfig.class);
System.out.printf("moduleBean.name = %s \n", moduleConfig.getName());
// registry
RegistryConfig registryConfig = context.getBean(RegistryConfig.class);
System.out.printf("registryConfig.name = %s \n", registryConfig.getAddress());
// protocol
ProtocolConfig protocolConfig = context.getBean(ProtocolConfig.class);
System.out.printf("protocolConfig.name = %s \n", protocolConfig.getName());
System.out.printf("protocolConfig.port = %s \n", protocolConfig.getPort());
// monitor
MonitorConfig monitorConfig = context.getBean(MonitorConfig.class);
System.out.printf("monitorConfig.name = %s \n", monitorConfig.getAddress());
// provider
ProviderConfig providerConfig = context.getBean(ProviderConfig.class);
System.out.printf("providerConfig.name = %s \n", providerConfig.getHost());
// consumer
ConsumerConfig consumerConfig = context.getBean(ConsumerConfig.class);
System.out.printf("consumerConfig.name = %s \n", consumerConfig.getClient());
}
}
```
###### 执行结果
```
applicationBean.name = dubbo-demo-application
moduleBean.name = dubbo-demo-module
registryConfig.name = zookeeper://192.168.99.100:32770
protocolConfig.name = dubbo
protocolConfig.port = 20880
monitorConfig.name = zookeeper://127.0.0.1:32770
providerConfig.name = 127.0.0.1
consumerConfig.name = netty
```
不难发现,`@EnableDubboConfig` 配置 Bean 配合外部化文件 `classpath:/META-INF/config.properties`,与执行输出内容相同。
##### 多 Dubbo 配置 Bean 绑定
###### 外部化配置文件
将以下内容的外部化配置文件物理路径为:`classpath:/META-INF/multiple-config.properties`:
```properties
# 多 Dubbo 配置 Bean 绑定
## dubbo.applications
dubbo.applications.applicationBean.name = dubbo-demo-application
dubbo.applications.applicationBean2.name = dubbo-demo-application2
dubbo.applications.applicationBean3.name = dubbo-demo-application3
```
###### `@EnableDubboConfig` 配置 Bean(多)
```java
@EnableDubboConfig(multiple = true)
@PropertySource("META-INF/multiple-config.properties")
private static class DubboMultipleConfiguration {
}
```
###### 实现引导类
```java
/**
* Dubbo 配置引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboConfigurationBootstrap {
public static void main(String[] args) {
// 创建配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前配置 Bean
context.register(DubboMultipleConfiguration.class);
context.refresh();
// 获取 ApplicationConfig Bean:"applicationBean"、"applicationBean2" 和 "applicationBean3"
ApplicationConfig applicationBean = context.getBean("applicationBean", ApplicationConfig.class);
ApplicationConfig applicationBean2 = context.getBean("applicationBean2", ApplicationConfig.class);
ApplicationConfig applicationBean3 = context.getBean("applicationBean3", ApplicationConfig.class);
System.out.printf("applicationBean.name = %s \n", applicationBean.getName());
System.out.printf("applicationBean2.name = %s \n", applicationBean2.getName());
System.out.printf("applicationBean3.name = %s \n", applicationBean3.getName());
}
}
```
###### 执行结果
```
applicationBean.name = dubbo-demo-application
applicationBean2.name = dubbo-demo-application2
applicationBean3.name = dubbo-demo-application3
```
`@EnableDubboConfig(multiple = true)` 执行后,运行结果说明`ApplicationConfig` Bean 以及 ID 的定义方式。
### `@EnableDubboConfigBinding` & `@EnableDubboConfigBindings`
`@EnableDubboConfig`适合绝大多数外部化配置场景,然而无论是单 Bean 绑定,还是多 Bean 绑定,其**外部化配置属性前缀**是固化的,如`dubbo.application` 以及 `dubbo.applications` 。
当应用需要自定义**外部化配置属性前缀**,`@EnableDubboConfigBinding`能提供更大的弹性,支持单个外部化配置属性前缀(`prefix`) 与 Dubbo 配置 Bean 类型(`AbstractConfig` 子类)绑定,如果需要多次绑定时,可使用`@EnableDubboConfigBindings`。
> 尽管 Dubbo 推荐使用 Java 8 ,然而实际的情况,运行时的 JDK 的版本可能从 6到8 均有。因此,`@EnableDubboConfigBinding` 没有实现`java.lang.annotation.Repeatable`,即允许实现类不支持重复标注`@EnableDubboConfigBinding`。
`@EnableDubboConfigBinding` 在支持外部化配置属性与 Dubbo 配置类绑定时,与 Dubbo 过去的映射行为不同,被绑定的 Dubbo 配置类将会提升为 Spring Bean,无需提前装配 Dubbo 配置类。同时,支持多 Dubbo 配置Bean 装配。其 Bean 的绑定规则与`@EnableDubboConfig`一致。
#### 起始版本: `2.5.8`
#### 使用说明
##### `@EnableDubboConfigBinding` 定义
```java
public @interface EnableDubboConfigBinding {
/**
* The name prefix of the properties that are valid to bind to {@link AbstractConfig Dubbo Config}.
*
* @return the name prefix of the properties to bind
*/
String prefix();
/**
* @return The binding type of {@link AbstractConfig Dubbo Config}.
* @see AbstractConfig
* @see ApplicationConfig
* @see ModuleConfig
* @see RegistryConfig
*/
Class<? extends AbstractConfig> type();
/**
* It indicates whether {@link #prefix()} binding to multiple Spring Beans.
*
* @return the default value is <code>false</code>
*/
boolean multiple() default false;
}
```
* `prefix()` : 指定待绑定 Dubbo 配置类的外部化配置属性的前缀,比如`dubbo.application` 为 `ApplicationConfig` 的外部化配置属性的前缀。`prefix()` 支持占位符(Placeholder), 并且其关联前缀值是否以"." 作为结尾字符是可选的,即`prefix() = "dubbo.application"` 与 `prefix() = "dubbo.application."` 效果相同
* `type()` : 指定 Dubbo 配置类,所有 `AbstractConfig` 的实现子类即可,如`ApplicationConfig` 、`RegistryConfig` 以及 `ProtocolConfig` 等
* `multiple()` : 表明是否需要将`prefix()` 作为多个 `type()` 类型的 Spring Bean 外部化配置属性。默认值为`false`,即默认支持单个类型的 Spring 配置 Bean
假设标注 `@EnableDubboConfigBinding` 的实现类被 Spring 应用上下文扫描并且注册后,其中`prefix()` = `dubbo.app` 、 `type()` = `ApplicationConfig.class` ,且外部配置内容为:
```properties
dubbo.app.id = applicationBean
dubbo.app.name = dubbo-demo-application
```
Spring 应用上下文启动后,一个 ID 为 "applicationBean" 的 `ApplicationConfig` Bean 被初始化,其 `name` 字段被设置为 "dubbo-demo-application"。
##### `EnableDubboConfigBindings` 定义
```java
public @interface EnableDubboConfigBindings {
/**
* The value of {@link EnableDubboConfigBindings}
*
* @return non-null
*/
EnableDubboConfigBinding[] value();
}
```
* `value` : 指定多个`EnableDubboConfigBinding`,用于实现外部化配置属性前缀(`prefix`) 与 Dubbo 配置 Bean 类型(`AbstractConfig` 子类)绑定。
#### 示例说明
##### 外部化配置文件
将以下内容的外部化配置文件物理路径为:`classpath:/META-INF/bindings.properties`
```properties
# classpath:/META-INF/bindings.properties
## 占位符值 : ApplicationConfig 外部配置属性前缀
applications.prefix = dubbo.apps.
## 多 ApplicationConfig Bean 绑定
dubbo.apps.applicationBean.name = dubbo-demo-application
dubbo.apps.applicationBean2.name = dubbo-demo-application2
dubbo.apps.applicationBean3.name = dubbo-demo-application3
## 单 ModuleConfig Bean 绑定
dubbo.module.id = moduleBean
dubbo.module.name = dubbo-demo-module
## 单 RegistryConfig Bean 绑定
dubbo.registry.address = zookeeper://192.168.99.100:32770
```
##### `EnableDubboConfigBindings` 配置 Bean
`DubboConfiguration` 作为 Dubbo 配置 Bean,除通过 `@EnableDubboConfigBinding` 绑定之外,还需要 `@PropertySource` 指定外部化配置文件(`classpath:/META-INF/bindings.properties`):
```java
/**
* Dubbo 配置 Bean
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "${applications.prefix}",
type = ApplicationConfig.class, multiple = true), // 多 ApplicationConfig Bean 绑定
@EnableDubboConfigBinding(prefix = "dubbo.module", // 不带 "." 后缀
type = ModuleConfig.class), // 单 ModuleConfig Bean 绑定
@EnableDubboConfigBinding(prefix = "dubbo.registry.", // 带 "." 后缀
type = RegistryConfig.class) // 单 RegistryConfig Bean 绑定
})
@PropertySource("META-INF/bindings.properties")
@Configuration
public class DubboConfiguration {
}
```
##### 实现引导类
通过之前的使用说明,当 `EnableDubboConfigBinding` 将外部配置化文件`classpath:/META-INF/dubbo.properties` 绑定到 `ApplicationConfig`后,其中 Spring Bean "applicationBean" 的 name 字段被设置成 "dubbo-demo-application"。同时, `EnableDubboConfigBinding` 所标注的 `DubboConfiguration` 需要被 Sring 应用上下文注册:
```java
/**
* Dubbo 配置引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class DubboConfigurationBootstrap {
public static void main(String[] args) {
// 创建配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册当前配置 Bean
context.register(DubboConfiguration.class);
context.refresh();
// 获取 ApplicationConfig Bean:"applicationBean"、"applicationBean2" 和 "applicationBean3"
ApplicationConfig applicationBean = context.getBean("applicationBean", ApplicationConfig.class);
ApplicationConfig applicationBean2 = context.getBean("applicationBean2", ApplicationConfig.class);
ApplicationConfig applicationBean3 = context.getBean("applicationBean3", ApplicationConfig.class);
System.out.printf("applicationBean.name = %s \n", applicationBean.getName());
System.out.printf("applicationBean2.name = %s \n", applicationBean2.getName());
System.out.printf("applicationBean3.name = %s \n", applicationBean3.getName());
// 获取 ModuleConfig Bean:"moduleBean"
ModuleConfig moduleBean = context.getBean("moduleBean", ModuleConfig.class);
System.out.printf("moduleBean.name = %s \n", moduleBean.getName());
// 获取 RegistryConfig Bean
RegistryConfig registry = context.getBean(RegistryConfig.class);
System.out.printf("registry.address = %s \n", registry.getAddress());
}
}
```
##### 运行结果
`DubboConfigurationBootstrap` 运行后控制台输出:
```
applicationBean.name = dubbo-demo-application
applicationBean2.name = dubbo-demo-application2
applicationBean3.name = dubbo-demo-application3
moduleBean.name = dubbo-demo-module
registry.address = zookeeper://192.168.99.100:32770
```
输出的内容与`classpath:/META-INF/bindings.properties` 绑定的内容一致,符合期望。
================================================
FILE: _posts/2018-05-26-Spring-Boot Web 应用加速.md
================================================
# Spring Boot Web 应用加速
默认情况下,Spring Boot Web 应用会装配一些功能组件 Bean。
在大多数 Web 应用场景下,可以选择性地关闭一下自动装配的Spring 组件 Bean,以达到提升性能的目的。
## 配置项优化
### Spring Boot Web 应用加速 完整配置项
````properties
management.add-application-context-header = false
spring.mvc.formcontent.putfilter.enabled = false
spring.autoconfigure.exclude = org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration
````
### 配置项汇总
````properties
spring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration
````
### 关闭 Web 请求跟踪 自动装配
#### `org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration`
顾名思义,该自动装配用跟踪 Web 请求,通过Servlet Filter `org.springframework.boot.actuate.trace.WebRequestTraceFilter` 记录请求的信息(如:请求方法、请求头以及请求路径等),其计算的过程存在一定的开销,使用场景罕见,故可选择关闭。
* 配置项
````properties
spring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
````
#### `org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration`
当`org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration`关闭后,其请求信息存储介质`org.springframework.boot.actuate.trace.TraceRepository`没有存在的必要,故可选择关闭。
* 配置项
````properties
spring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration
````
### 关闭 Web 请求结果指标 自动装配
#### `org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration`
该组件将自动装配`org.springframework.boot.actuate.autoconfigure.MetricsFilter`,该 Filter
主要记录Web 请求结果指标(如:相应状态码、请求方法执行时间等),该信息一定程度上与反向代理服务器(nginx)功能重叠,故可选择关闭。
* 配置项
````properties
spring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration
````
### 可关闭 Servlet Web 组件
#### `org.springframework.web.filter.HttpPutFormContentFilter`
* 引入版本
`org.springframework.web.filter.HttpPutFormContentFilter` 由 Spring
Framework 3.1 版本引入,分发在 `org.springframework:spring-web` 中。
* 使用场景
通常 Web 场景中,浏览器通过 HTTP `GET` 或者 `POST` 请求 提交 Form 数据,而非浏览
器客户端(如应用程序)可能通过 HTTP `PUT` 请求来实现。
当 HTTP 请求头`Content-Type` 为 `application/x-www-form-urlencoded` 时
,Form 数据被 encoded。而 Servlet 规范中, `ServletRequest.getParameter*()`
方法仅对 HTTP `POST` 方法支持请求参数的获取,如:
````java
public intetfacce ServletRequest {
......
public String getParameter(String name);
public Enumeration<String> getParameterNames();
public String[] getParameterValues(String name);
public Map<String, String[]> getParameterMap();
......
}
````
故 以上方法无法支持 HTTP `PUT` 或 HTTP `PATCH` 请求方法(请求头`Content-Type`
为`application/x-www-form-urlencoded`)。
`org.springframework.web.filter.HttpPutFormContentFilter` 正是这种场景的解
决方案。
Spring Boot 默认场景下,将
`org.springframework.web.filter.HttpPutFormContentFilter` 被
`org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration` 自动
装配,以下为 Spring Boot 1.4.1.RELEASE 以及更好版本定义(可能存在一定的差异):
````java
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
@Bean
@ConditionalOnMissingBean(HttpPutFormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
return new OrderedHttpPutFormContentFilter();
}
......
}
````
综上所述,`org.springframework.web.filter.HttpPutFormContentFilter` 在绝大
多数 Web 使用场景下为非必须组件。
* 配置项
如果应用依赖 Spring Boot 版本 为 1.4.1.RELEASE 以及更高的版本,可通过如下配置,
进行将 `org.springframework.web.filter.HttpPutFormContentFilter` 关闭:
````properties
spring.mvc.formcontent.putfilter.enabled = false
````
#### `org.springframework.web.filter.HiddenHttpMethodFilter`
* 引入版本
`org.springframework.web.filter.HiddenHttpMethodFilter` 由 Spring
Framework 3.0 版本引入,分发在 `org.springframework:spring-web` 中。
* 使用场景
当 Web 服务端同一资源(URL)提供了多请求方法的实现,例如 URI :/update 提供了
HTTP `POST` 以及 HTTP `PUT` 实现),通常 Web 场景中,浏览器仅支持 HTTP `GET`
或者 `POST` 请求方法,这样的话,浏览器无法发起 HTTP `PUT` 请求。
为了浏览器可以消费 HTTP `PUT` 资源, 需要在服务端将 HTTP `POST` 转化成
HTTP `PUT` 请求,为了解决这类问题,Spring 引入
`org.springframework.web.filter.HiddenHttpMethodFilter` Web 组件。
当浏览器 发起 HTTP `POST` 请求时,可通过增加请求参数(默认参数名称:"_method")
的方式,进行HTTP 请求方法切换,
`org.springframework.web.filter.HiddenHttpMethodFilter` 获取参数"_method"
值后,将参数值作为 `HttpServletRequest#getMethod()`的返回值,给后续 `Servlet`
实现使用。
出于通用性的考虑,`org.springframework.web.filter.HiddenHttpMethodFilter`
通过调用 `#setMethodParam(String)` 方法,来修改转换请求方法的参数名称。
Spring Boot 默认场景下,将
`org.springframework.web.filter.HttpPutFormContentFilter` 被
`org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration` 自动
装配,以下为 Spring Boot 1.4.1.RELEASE 以及更好版本定义(可能存在一定的差异):
````java
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
......
}
````
综上所述,`org.springframework.web.filter.HiddenHttpMethodFilter` 也是特殊
场景下所需,故可以关闭之。
* 配置项
按目前最新的 Spring Boot 1.5.2.RELEASE 版本中实现,也没有提供类似
`spring.mvc.formcontent.putfilter.enabled` 这样的配置项关闭,无法关闭。
================================================
FILE: _posts/2018-06-28-Dubbo Cloud Native 实践与思考.md
================================================
# Dubbo Cloud Native 实践与思考
<!-- TOC -->
- [Dubbo Cloud Native 实践与思考](#dubbo-cloud-native-%E5%AE%9E%E8%B7%B5%E4%B8%8E%E6%80%9D%E8%80%83)
- [分享简介](#%E5%88%86%E4%BA%AB%E7%AE%80%E4%BB%8B)
- [自我介绍](#%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D)
- [主要议程](#%E4%B8%BB%E8%A6%81%E8%AE%AE%E7%A8%8B)
- [Cloud Native 基础设施](#cloud-native-%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD)
- [服务发现(Service Discovery )](#%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service-discovery)
- [如何选择](#%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9)
- [Eureka](#eureka)
- [Consul](#consul)
- [Zookeeper](#zookeeper)
- [负载均衡](#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1)
- [服务网关](#%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3)
- [分布式配置](#%E5%88%86%E5%B8%83%E5%BC%8F%E9%85%8D%E7%BD%AE)
- [服务熔断](#%E6%9C%8D%E5%8A%A1%E7%86%94%E6%96%AD)
- [链路跟踪](#%E9%93%BE%E8%B7%AF%E8%B7%9F%E8%B8%AA)
- [服务监控](#%E6%9C%8D%E5%8A%A1%E7%9B%91%E6%8E%A7)
- [Cloud Native 架构选型](#cloud-native-%E6%9E%B6%E6%9E%84%E9%80%89%E5%9E%8B)
- [CNCF 架构体系](#cncf-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)
- [Spring Cloud 架构体系](#spring-cloud-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)
- [Dubbo 架构体系](#dubbo-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)
- [Dubbo Cloud Native 准备](#dubbo-cloud-native-%E5%87%86%E5%A4%87)
- [Dubbo 注解驱动(Annotation-Driven)](#dubbo-%E6%B3%A8%E8%A7%A3%E9%A9%B1%E5%8A%A8annotation-driven)
- [`@DubboComponentScan` 服务端示例](#dubbocomponentscan-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%A4%BA%E4%BE%8B)
- [`@DubboComponentScan` 客户端示例](#dubbocomponentscan-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%A4%BA%E4%BE%8B)
- [Dubbo 外部化配置(Externalized Configuration)](#dubbo-%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AEexternalized-configuration)
- [现场演示环节](#%E7%8E%B0%E5%9C%BA%E6%BC%94%E7%A4%BA%E7%8E%AF%E8%8A%82)
- [Dubbo 整合 Hystrix 示例](#dubbo-%E6%95%B4%E5%90%88-hystrix-%E7%A4%BA%E4%BE%8B)
- [Dubbo 客户端实现](#dubbo-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AE%9E%E7%8E%B0)
- [Dubbo 服务端实现](#dubbo-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%AE%9E%E7%8E%B0)
- [测试客户端 REST 服务](#%E6%B5%8B%E8%AF%95%E5%AE%A2%E6%88%B7%E7%AB%AF-rest-%E6%9C%8D%E5%8A%A1)
- [参考资源](#%E5%8F%82%E8%80%83%E8%B5%84%E6%BA%90)
<!-- /TOC -->
## 分享简介
Cloud Native 应用架构随着云技术的发展受到业界特别重视和关注,尤其是 CNCF(Cloud Native Computing Foundation)项目蓬勃发展之际。Dubbo 作为服务治理的标志性项目,自然紧跟业界的潮流,拥抱技术的变化。本次分享的议题包括介绍 Apache 孵化项目Dubbo Spring Boot Project 以及汇报 Dubbo 与 Cloud Native 整合过程中的一些实践与思考,如适配 Spring Cloud 、服务发现、服务网关、服务跟踪以及监控等。
> 注:为了读者的阅读方便和习惯,本文字稿将在演讲内容的基础上做出适当的调整。
## 自我介绍
马昕曦(小马哥),阿里巴巴中间件技术专家,十余年 Java EE 从业经验,Dubbo 维护者、架构师以及微服务布道师。目前主要负责阿里巴巴集团微服务技术实施、架构衍进、基础设施构建等。重点关注云计算、微服务以及软件架构等领域。通过 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的认证。
## 主要议程
今天我非常荣幸地与大家一起讨论关于 Dubbo Cloud Native 相关议题,本次议题紧扣“实践与思考“两个关键字,主要的议程包括:
* **Cloud Native 基础设施**
* **Cloud Native 架构选型**
* **Dubbo Cloud Native 准备**
### Cloud Native 基础设施
关于 Cloud Native 的定义,不同的云平台可能给出的内容存在差异。此处,我向大家介绍目前最热门的 CNCF 的定义:
> ”[CNCF Cloud Native Definition v1.0](https://github.com/cncf/toc/blob/master/DEFINITION.md)“ 中的描述:
>
> > Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.
相对于其他学术流派,CNCF 的 Cloud Native 定义更为具体,偏向于软件技术。这一点我们从文中的一些关键字能够明显地体会到,如关键字 "Containers(容器)"、"service meshes"、”microservices(微服务)“等。通常,开发人员较为关注的 Cloud Native 基础设施为:“服务发现”、“负载均衡”、“服务网关”、“分布式配置”、“服务熔断”以及“跟踪监控”,如图所示:

由于 PPT 格式的限制,此处我将“链路跟踪”与“服务监控” 并陈为“跟踪监控”。接下来,我们进入“服务发现”的讨论。
#### 服务发现(Service Discovery )
随着微服务架构(MSA)受到不同规模企业的青睐,服务治理的实施逐渐被提上基础设施改造的议程。尽管这些概念在 SOA 时代已经提出,然而引起业界广泛关注应归功于微服务。服务发现(Service Discovery )作为服务治理的核心特性,通常也将服务注册(Service Registration)一并讨论。无论是服务发现,还是服务注册,在具体落地实施时,它们必须面对技术选型的问题。在座的各位,包括我,大多数是 Java 程序员,自然关心 Java 的技术方案。目前,Java 社区最为津津乐道的方案莫过于 Spring Cloud,搭配 Netflix OSS 组件 Eureka,帮助 Spring Boot 应用快速搭建服务发现体系。其中,Eureka Server 作为注册中心服务器,Spring Boot 应用整合 Eureka Client 向 Eureka Server 注册。实际上,Spring Cloud 除了整合 Netflix Eureka 作为服务发现之外,还提供了 Apache Zookeeper 和 HachiCorp Consul 的实现,所以这三种方案出现在当前页面:

其中还包括 Redis 和 Apache Curator,前者是 Dubbo 的服务发现实现方案之一,然而小马哥并不建议使用 Redis 作为注册中心,还是保持它缓存中间件的单纯性较好。而 Curator 作为 Zookeeper Java 客户端类库,它不但可用在 Dubbo,而且其扩展项目 Curator Service Discovery 也是 Spring Cloud 整合 Zookeeper 作为服务发现的关键基础设施。或许大家思考以上方案应该如何选型的问题。
##### 如何选择
###### Eureka
当服务发现选型时,Netflix Eureka 或许是在开发人员脑海中复现的首选方案。然而 Eureka 在阿里大规模实践时,它的表现并不理想,当 Eureka 客户端服务实例数量达到一定时,Eureka Server 时常会出现服务不可用的情况,主要的问题集中在更新(Update)机制、复制(Replication)机制以及内存型存储。由于时间的关系,此处我不加详细说明,部分答案在 Eureka Wiki [Eureka 2.0 Motivations](https://github.com/Netflix/eureka/wiki/Eureka-2.0-Motivations) 中也有描述:
> [Why Eureka 2.0?](https://github.com/Netflix/eureka/wiki/Eureka-2.0-Motivations#why-eureka-20)
>
> * Only support homogenous client views
> * Only supports scheduled updates
> * Replication algorithm limits scalability
>
> > 注:以上具体内容在分享现场并没有具体提及,此处特意为读者补充。
以上问题 Netflix 早在 2015 年已意识到,然而 Eureka 2.0 的发布遥遥无期。后来,我托朋友联系上了 Netflix 的工程师,咨询他们关于 Eureka 1 在自身生产环境的使用情况。他们的回复是部分场景在使用。这样的答复值得玩味,再细问其覆盖比重,对方三缄其口。这不得不让我对 Eureka 的成熟度产生了质疑,所以我不建议大家在数以千计的应用实例场景中使用。
###### Consul
Consul 同样作为 Spring Cloud 服务中心,基于 GO 语言开发,其数据一致性采用 Raft 算法,低内存,集群支持。曾一度成为我理想的替换 Eureka 的方案,不过本人并不具备 Consul 的大规模运用,为此还特意请教永辉云创的架构师翟永超(《Spring Cloud 微服务实战》的作者)。他告知 Consul 表现不错,并在跨 DC(数据中心)方面也比较稳定:
他的答复让我增强了 Consul 的信心,稍显遗憾的是其 Consul 应用节点略少。后来,我听说 B 站的哥们自研服务发现中间件 **[discovery](https://github.com/Bilibili/discovery/)**,他们应该也对 Consul 做过调研和评估,他们的看法是:

> Github 开源地址:https://github.com/Bilibili/discovery/
>
> **[discovery](https://github.com/Bilibili/discovery/)** 在 B 站 K8S 上的使用情况:
>
> 
综合两家公司的评估,尽管没有经过本人实际操作,并且两者没有提供具体的数据指标,然而在一定程度上说明 Consul 作为注册中心的实例节点规模大概在 2k 以内。换言之,它比较适合中小型企业。
###### Zookeeper
Zookeeper 即可是 Spring Cloud 注册中心,又能作为 Dubbo 注册中心,与 Eureka 不同,它属于 CP 分布式策略,而后者属于 AP。两者的共同点在于均属于内存型注册中心,在大规模集群场景,也会遇到 Eureka 类似的问题。不过从运维的角度,相较于 Eureka 而言,熟悉 Zookeeper 运维朋友更多。在生态性方面,Zookeeper 周边的生态更丰富,如 Zookeeper C API,尽管 Eureka 提供了语言无关性的 REST 接口。同时,Zookeeper 还从当配置服务器的角色,降低了学习的成本。综上结论,我推荐使用 Zookeeper 作为服务发现基础设施,无论您选择 Dubbo 方案,还是使用 Spring Cloud。尽管它在大规模集群时也出现 Zookeeper 间歇性卡顿等问题。
#### 负载均衡

负载均衡是第二个重要 Cloud Native 基础设施,熟悉 Spring Cloud 的朋友一定对右侧的蝴蝶结有印象,它就是 Netflix OSS 负载均衡组件 Ribbon,框架层面提供了多种负载均衡规则,如:
* **随机** - `RandomRule`
* **轮循** - `RoundRobinRule`
* **权重响应时间** - `WeightedResponseTimeRule`
`WeightedResponseTimeRule` 之外,其他的 Ribbon 负载均衡实现均没有提供权重因子,而权重因子对于蓝绿发布、服务预热等方面的帮助是至关重要的。因此,权重因子在 Dubbo “**随机**“、”**轮询**“ 以及 ”**最少活跃调用数**“ 负载均衡算法中均体现。
以上讨论的两种框架均属于 Java 实现,而中间的 Kong 则是更为通用的实现,通常它作为 API 服务网关,后面我们将继续讨论。可简单地认为它是 Nginx + Lua 的扩展,负载均衡自然成为不可或缺的特性。其默认的负载均衡算法为具备权重的轮询(weighted-round-robin),同时一致性 Hash 算法作为可选方案。
#### 服务网关

谈及服务网关,Java 工程师最容易想到的是 Spring Cloud Zuul。Zuul 是 Netflix 基于 Servlet API 开发的 Web 服务代理组件,在 Spring Cloud 使用场景中,它与 Eureka 和 Ribbon 整合,打造具备服务动态更新和负载均衡能力的服务网关。
最近,随着 Spring Cloud Finchley 的发布,Spring Cloud Zuul 的替代方案 Spring Cloud Gateway 孕育而生,不过官方的描述还是比较谦虚谨慎,并没有一刀切地引导开发人员从 Zuul 迁移到 Gateway 上来:
> API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
两者不同点在于,Zuul 运行在 Servlet 容器中,而 Gateway 并不像 Spring WebFlux 能够兼容 Servlet 3.1 运行时,而是必须依赖 Netty 的运行时,以及整合 Reactive 框架 Reactor,实现异步非阻塞网关。由于近期对于 Spring 5 WebFlux 能够大幅提升应用性能的观点甚嚣尘上,实际上,没有任何直接性能基准测试证明 WebFlux 能够加快程序执行速度,或许大家认为我的观点与主流格格不入,可是我要告诉大家的是,这个问题我在同事间验证过很多次,大多数情况,Reactive 并不没有提升性能。就连 Spring 官方也承认这个观点:
> [1.1.7. Performance vs scale](https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance)
>
> Performance has many characteristics and meanings. **Reactive and non-blocking generally do not make applications run faster**. They can, in some cases, for example if using the `WebClient` to execute remote calls in parallel. **On the whole it requires more work to do things the non-blocking way and that can increase slightly the required processing time**.
>
> > 资源地址:https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance
同时,这里提供一篇 [Spring 5 WebFlux: Performance tests](https://blog.ippon.tech/spring-5-webflux-performance-tests/) 的文章,在结尾部分给出了结论,作者坦言在速度上没有明显的提升,甚至从结果来看,速度稍微更糟糕:
- *No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).*
> 以上测试工程和结论是由开源项目 JHipster 的工程师给出,具备一定的客观性和可信度。
>
> > 资源地址:https://blog.ippon.tech/spring-5-webflux-performance-tests/
换言之,基于 Reactor 开发的 Gateway 在性能可能并没有明显的提升。因此,Zuul 和 Gateway 的性能对比则演变为 Servlet 容器和 Netty Web 容器的比较,感兴趣的朋友可以去网上寻找一些比较数据,两者的性能在伯仲间。
当然,我和在座的各位一样,对 Java 的实现方案自然是情有独钟。然而我想说的是,身为 Java 工程师,**眼中难免有 Java,但是眼中不要只有 Java**。Nginx 作为当年著名 “C10K” 问题的解决方案,无论从连接数量,还是资源消耗方面均优于 Java 实现。作为技术人,应该具有更为宽广的胸怀,接纳非我族类的气魄,该放手的时候就放手。Nginx 作为服务网关不失为一种好的方案,然而它的动态性略为不足,需要结合 Lua 脚本辅助完成,因此,OpenResty 和 Kong 这类方案脱颖而出。如果就 HTTP API 网关而言,个人认为 Kong 的方案更佳,因为它提供完整的解决方案,包括前面讨论的负载均衡(权重)、服务熔断以及服务发现等特性。类似的特性在 CNCF 项目 Envoy 也有体现,它是另一种高性能代理的方案,提供服务发现、健康和负载均衡。在协议上,天然支持 HTTP 和 HTTP/2,而通讯协议支持 gRPC,建议大家予以高度关注。
值得一提的是,HTTP API 网关通常需要支持 sidecar,换言之,支撑网关服务的基础设施必须提供服务发现的能力,就功能性而言,Zuul 和 Gateway 自身并不具备这样的特性,需要搭配 Eureka 这样组件,它们更像服务路由器的角色。
#### 分布式配置

左边和中间的四种技术均为 Spring Cloud 分布式配置的底层存储,其中 Git 为版本式配置,而 JDBC 是从 Spring Cloud Edgware 版本开始支持,提供更为通用和动态的配置源。这里我们又见到 Zookeeper 的声影,从简化运维的角度,可以利用 Zookeeper 即承担服务发现,也作为分布式配置的基础设施。而最右边的 etcd 是最近非常火的 Kubernetes 分布式配置的 key-value 存储,提供快速、简单、安全和可高的解决方案。
#### 服务熔断

服务熔断也非常让开发人员联想到 Spring Cloud Hystrix 技术,不过 Hystrix 并非与 Spring Cloud 强耦合,当然 Dubbo 也能结合 Netflix Hystrix 框架提供服务熔断的能力,后面部分将介绍 Dubbo 与 Hystrix 整合,提升 Dubbo 服务熔断的能力。确切地说,Dubbo 所提供的能力是集群容错,包括 Failover 等模式。 Kong 也天然地支持服务熔断的能力,所以它作为 API 网关的特性是全面的。
#### 链路跟踪

以上链路跟踪的基础设施从左至右,分别为 Zipkin、OpenTracing 以及 Jaeger,三者的灵感均来自于 [Google 论文 Dapper](https://ai.google/research/pubs/pub36356)。相对而言,Java 程序员可能更为熟悉 Zipkin,因为它是 Spring Cloud Sleuth 首选方案,提供客户端上报以及服务端聚合和 Dashboard 等功能。而 OpenTracing 和 Jaeger 是 CNCF 孵化项目,前者属于开放的标准,提供多语言的适配实现,后者则由 Uber(优步)公司开发并开源的链路跟踪项目,功能上与 Zipkin 类似,不过它基于 GO 语言开发,同时也提供 Java 客户端。
> OpenTracing 官网:http://opentracing.io/
> jaeger 官网:https://www.jaegertracing.io/
#### 服务监控

服务监控与链路跟踪有所区别,主要用于监控应用系统或业务的指标数据,可能是健康阈值,如 CPU 或 内存使用率,也可以是业务指标,如最近一小时的用户登录量。通常采用 Metrics 方式暴露,可使用客户端推送或服务端拉取的方式传输 Metrics 信息到数据中心。通常 Metrics 数据与时间是存在对应关系,因此,基本上采用时序型数据库来存储,如图中的 OpenTSDB。通常,Java 微服务应用会选择 Spring Boot 框架作为基础设施,如我之前设计的监控架构就采用了 Spring Boot + OpenTSDB ,后端存储基于 HBase。当时 Spring Boot Actuator Metrics 仅为简单的 Key Value 形式,自然 OpenTSDB 是理想的选择。随着 Spring Boot 2.0 开始支持 [Micrometer](https://micrometer.io/) 之后,使得 Spring Boot 的应用能够整合更多的 Micrometer 适配方案,其中名气较大的就是图中间的 [Prometheus](https://prometheus.io/),它同样也是 CNCF 的孵化项目。
当然服务监控不只是 Metrics 方式,我所知道国内不少的公司采用了日志收集的方案,并搭配 ELK(Elasticsearch, Logstash, Kibana) 架构,减少运维成本。假设您没有使用该方案,或者仅使用了 Elasticsearch 的话,无论哪种方案,图形化界面的监控是必不可少的,因此我推荐 Grafana,该项目能够支持多种数据源,包括前文提到的 OpenTSDB、Prometheus 以及 ElasticSearch 等。由此,从数据采集、上报、聚合以及展示的特性上,这些基础设施帮助 Cloud Native 应用构建服务监控的闭环。
本议程介绍了一些 Cloud Native 技术设施,接下里我们继续讨论 Cloud Native 架构选型。
### Cloud Native 架构选型
#### CNCF 架构体系

CNCF 体系作为目前最热门的架构选型之一,基本上围绕着 Kubernetes 为中心而构建。个人认为,Java 业界和 CNCF 体系并没有达成共识,如服务网关,CNCF 主打 Envoy,而 Java 主要的方案为 Zuul 和 Spring Cloud Gateway。因此,个人建议是密切的关注 CNCF 的发展,不过个别孵化项目可以先行,如 Prometheus 和 Jaeger 等。 至于 CNCF 与 Java 生态的整合和落地,还得有待时日。
#### Spring Cloud 架构体系

实际上,这个图片并非 Spring Cloud 组件架构,而是将其整合在 Pivotal Cloud Foundry (PCF) 架构中。基本上,Spring Cloud 功能组件均有所体现,包括 Eureka、Hystrix、Ribbon 等。不过值得注意的是,Spring Cloud Stream 是一套较为完整和抽象的流式编程框架,屏蔽了底层传输介质(不仅是消息服务),如 Kafka、RabbitMQ 等。除此之外,其他的组件可圈可点,如 Eureka 在大规模运用中的卡顿问题、Ribbon 缺少权重、Zuul 连接数限制和资源消耗、服务调用受限于 Feign REST 协议限制等。如果在小规模场景使用,以上限制或问题不明显,可以说 Spring Cloud 完全能够适任。
不过,差不多两年前,我曾在不同的公开场合讲过:”Spring Boot 易学难精,Spring Cloud 能用但不成熟“。当时很多人觉得我“离经叛道”,然而这句话并非空穴来风,是我这几年来 Java 微服务架构实施的心得。这两年来,深受 Spring Cloud “折磨”的小伙伴逐渐觉醒,慢慢地开始回到 Dubbo 等技术方案。如 Martin Fowler 在为“微服务”下定义时,提到通讯协议要用轻量级的 REST。假设微服务要做到服务无关的话,那么 Web Services 协议也是可以,尽管它看起来比较重,不过 Web Services 的结构化和强类型,可以省去不少的运行时校验逻辑。在我看来,微服务更大程度应该体现在服务粒度上,诚如 Netflix 前架构师 Adrian Cockcroft 说言:“Fine grain SOA”(微服务就是细粒度的 SOA),就这一点而言,比较容易地和业界达成共识。当我们把 Martin 的话视如圭臬时,我们是否要思考它是否经得起工程检验。这里,我没有兴趣贬低他人,来抬高自己(Dubbo),从而引导让大家放弃 Spring Cloud,而是我们需要给 Spring Cloud 时间,包括未来 Dubbo 也会向 Spring Cloud 靠拢并整合。在阿里的内部,基于 Nacos(马上开源的项目)和 Apache RocketMQ,实现了 Spring Cloud Service Discovery、Config 以及 Stream 等整合和适配,一旦时机成熟,可能会开源与大家共建。
既然谈到了 Dubbo,下面我们再来讨论 Dubbo 的架构体系。
#### Dubbo 架构体系

编程模型方面,不但支持传统的 Spring XML 配合方式,已经实现注解驱动以及外部化配置,并且全面支持最新的 Spring Boot 2.0,在不久的未来,大家会看到 Dubbo 与 Spring Cloud 的整合,使开发人员无缝地衔接已有的 Spring Cloud 应用。
> Dubbo Spring Boot 项目地址:https://github.com/apache/incubator-dubbo-spring-boot-project
注册中心方面,Dubbo 将整合 Eureka、etcd 以及 Consul 基础设施,深度与业界热门方案整合。
熔断机制方面,Dubbo 会在近期发布 Hystrix 整合实现,将编程友好性做得最大化。
通讯协议方面,Dubbo 将会支持 gRPC、Thrift 等热门通讯协议。
至于序列化协议,自然首先考虑的是 Protobuf,因其高层 gRPC 搭配 HTTP/2 快成或已经成为下一代通讯协议的事实标准,使得任何人无法忽视它们的存在。当然其他协议也会陆续支持。
其他方面,我这里就不一一介绍,总之,现在 Dubbo 已不再只是一个单一的 PRC 框架,而是要拥抱业界,形成完整的生态体系,与业界形成最大公约数。
### Dubbo Cloud Native 准备

在 Dubbo 架构体系时,我们曾提到编程模型的变化。从 Dubbo `2.5.8` 开始,注解驱动和外部化配置均已得到支持。同时,Dubbo 已经合并 Dubbox 代码,Java JAX-RS 标准得到了支持,目前业界事实的 REST 标准 Spring Web MVC 正在同步开发。Reactive 的支持也在同步进行,小马哥还得友好地提醒一下各位,对于 Reactive 的期望不应该过分的关注性能的提升。
#### Dubbo 注解驱动(Annotation-Driven)
在 Dubbo `2.5.7` 之前的版本 ,Dubbo 提供了两个核心注解 `@Service` 以及 `@Reference`,分别用于Dubbo 服务提供和 Dubbo 服务引用。
其中,`@Service` 作为 XML 元素 `<dubbo:service>` 的替代注解,与 Spring Framework `@org.springframework.stereotype.Service` 类似,用于服务提供方 Dubbo 服务暴露。与之相对应的 `@Reference`,则是替代`<dubbo:reference>` 元素,类似于 Spring 中的 `@Autowired`。
`2.5.7` 之前的Dubbo,与早期的 Spring Framework 2.5 存在类似的不足,即注解支持不够充分。注解需要和 XML 配置文件配合使用,如下所示:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="annotation-provider"/>
<dubbo:registry address="127.0.0.1:4548"/>
<dubbo:annotation package="com.alibaba.dubbo.config.spring.annotation.provider"/>
</beans>
```
不仅如此,当时的版本存在“ `@Service` Bean 不支持 Spring AOP” 以及 “`@Reference` 不支持字段继承性” 等问题。
从 `2.5.7` 开始,Dubbo 开始引入组件扫描 Annotation `@DubboComponentScan`,借鉴了 Spring Boot 1.3 引入的 `@ServletComponentScan`。
在职责上,`@DubboComponentScan` 相对于 Spring Boot `@ServletComponentScan` 更为繁重,原因在于处理 Dubbo `@Service` 类暴露 Dubbo 服务外,还有帮助 Spring Bean `@Reference`字段或者方法注入 Dubbo 服务代理。
在场景上,Spring Framework `@ComponentScan` 组件扫描逻辑更为复杂。而在 `@DubboComponentScan` 只需关注 `@Service` 和 `@Reference` 处理。
> 注:更多 Dubbo 注解驱动的详情,请参考[《Dubbo 注解驱动(Annotation-Driven)》](https://github.com/mercyblitz/blogs/blob/master/java/dubbo/Dubbo-Annotation-Driven.md)
##### `@DubboComponentScan` 服务端示例
假设,服务提供方和服务消费分均依赖服务接口`DemoService`:
```java
package com.alibaba.dubbo.demo;
public interface DemoService {
String sayHello(String name);
}
```
* 服务提供方实现`DemoService` - `AnnotationDemoService`
同时标注 Dubbo `@Service` :
```java
package com.alibaba.dubbo.demo.provider;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation {@link DemoService} 实现
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Service
public class AnnotationDemoService implements DemoService {
@Override
public String sayHello(String name) {
return "Hello , " + name;
}
}
```
* 服务端 `@Configuration` Class
```java
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务提供方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 扫描 Dubbo 组件
public class ProviderConfiguration {
/**
* 当前应用配置
*/
@Bean("dubbo-annotation-provider")
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-provider");
return applicationConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean("my-registry")
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean("dubbo")
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(12345);
return protocolConfig;
}
}
```
* 服务提供方引导类
```java
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服务提供方引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ProviderBootstrap {
public static void main(String[] args) {
// 创建 Annotation 配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册配置 Bean
context.register(ProviderConfiguration.class);
// 启动上下文
context.refresh();
// 获取 DemoService Bean
DemoService demoService = context.getBean(DemoService.class);
// 执行 sayHello 方法
String message = demoService.sayHello("World");
// 控制台输出信息
System.out.println(message);
}
}
```
##### `@DubboComponentScan` 客户端示例
* 消费服务 `DemoService`
```java
package com.alibaba.dubbo.demo.consumer;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation 驱动 {@link DemoService} 消费方
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class AnnotationDemoServiceConsumer {
@Reference(url = "dubbo://127.0.0.1:12345")
private DemoService demoService;
public String doSayHell(String name) {
return demoService.sayHello(name);
}
}
```
* 消费端 `@Configuration` Class
与服务提供方配置类似,服务消费方也许 Dubbo 相关配置 Bean - `ConsumerConfiguration`
```java
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服务消费方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan
public class ConsumerConfiguration {
/**
* 当前应用配置
*/
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
/**
* 当前连接注册中心配置
*/
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 注册 AnnotationDemoServiceConsumer,@DubboComponentScan 将处理其中 @Reference 字段。
* 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的话,
* 即使 @DubboComponentScan 指定 package 也不会进行处理,与 Spring @Autowired 同理
*/
@Bean
public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {
return new AnnotationDemoServiceConsumer();
}
}
```
* 服务消费方引导类
```java
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.config.ConsumerConfiguration;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服务消费端引导类
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ConsumerBootstrap {
public static void main(String[] args) {
// 启动服务提供方上下文
startProviderContext();
// 启动并且返回服务消费方上下文
ApplicationContext consumerContext = startConsumerContext();
// 获取 AnnotationDemoServiceConsumer Bean
AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);
// 执行 doSayHello 方法
String message = consumer.doSayHello("World");
// 输出执行结果
System.out.println(message);
}
/**
* 启动并且返回服务消费方上下文
*
* @return AnnotationConfigApplicationContext
*/
private static ApplicationContext startConsumerContext() {
// 创建服务消费方 Annotation 配置上下文
AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
// 注册服务消费方配置 Bean
consumerContext.register(ConsumerConfiguration.class);
// 启动服务消费方上下文
consumerContext.refresh();
// 返回服务消费方 Annotation 配置上下文
return consumerContext;
}
/**
* 启动服务提供方上下文
*/
private static void startProviderContext() {
// 创建 Annotation 配置上下文
AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
// 注册配置 Bean
providerContext.register(ProviderConfiguration.class);
// 启动服务提供方上下文
providerContext.refresh();
}
}
```
#### Dubbo 外部化配置(Externalized Configuration)
在Dubbo 注解驱动例子中,无论是服务提供方,还是服务消费方,均需要转配相关配置Bean:
```java
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
```
虽然实现类似于`ProviderConfiguration` 和 `ConsumerConfiguration` 这样的 Spring `@Configuration` Bean 成本并不高,不过通过 Java Code 的方式定义配置 Bean,或多或少是一种 Hard Code(硬编码)的行为,缺少弹性。
尽管在 Spring 应用中,可以通过 `@Value` 或者 `Environment` 的方式获取外部配置,其代码简洁性以及类型转换灵活性存在明显的不足。因此,Spring Boot 提出了外部化配置(External Configuration)的感念,即通过程序以外的配置源,动态地绑定指定类型。
随着 Spring Boot / Spring Cloud 应用的流行,开发人员逐渐地接受并且使用 Spring Boot 外部化配置(External Configuration),即通过 `application.properties` 或者 `bootstrap.properties` 装配配置 Bean。
下列表格记录了 Dubbo 内置配置类:
| 配置类 | 标签 | 用途 | 解释 |
| ------------------- | ---------------------- | ------------ | ----------------------------------------------------------------------- |
| `ProtocolConfig` | `<dubbo:protocol/>` | 协议配置 | 用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受 |
| `ApplicationConfig` | `<dubbo:application/>` | 应用配置 | 用于配置当前应用信息,不管该应用是提供者还是消费者 |
| `ModuleConfig` | `<dubbo:module/>` | 模块配置 | 用于配置当前模块信息,可选 |
| `RegistryConfig` | `<dubbo:registry/>` | 注册中心配置 | 用于配置连接注册中心相关信息 |
| `MonitorConfig` | `<dubbo:monitor/>` | 监控中心配置 | 用于配置连接监控中心相关信息,可选 |
| `ProviderConfig` | `<dubbo:provider/>` | 提供方配置 | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选 |
| `ConsumerConfig` | `<dubbo:consumer/>` | 消费方配置 | 当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选 |
| `MethodConfig` | `<dubbo:method/>` | 方法配置 | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |
| `ArgumentConfig` | `<dubbo:argument/>` | 参数配置 | 用于指定方法参数配置 |
通过申明对应的 Spring 扩展标签,在 Spring 应用上下文中将自动生成相应的配置 Bean。
在 Dubbo 官方用户手册的[“属性配置”](http://dubbo.io/books/dubbo-user-book/configuration/properties.html)章节中,`dubbo.properties` 配置属性能够映射到 `ApplicationConfig` 、`ProtocolConfig` 以及 `RegistryConfig` 的字段。从某种意义上来说,`dubbo.properties` 也是 Dubbo 的外部化配置。
> 注:更多外部化配置的详情,请参考[《Dubbo 外部化配置(Externalized Configuration)》](https://github.com/mercyblitz/blogs/blob/master/java/dubbo/Dubbo-Externalized-Configuration.md)
### 现场演示环节
> 本环境为分享后部分,现在编码 + 演示环境,当前文字仅提供代码实现。
#### Dubbo 整合 Hystrix 示例
> 本示例出现在分享议程的代码演示,将其放置此处,方便阅读理解
##### Dubbo 客户端实现
* 实现 `HystrixCommand`
```java
public class ResultHystrixCommand extends HystrixCommand<Result> {
private final Invoker<?> invoker;
private final Invocation invocation;
public ResultHystrixCommand(Invoker<?> invoker, Invocation invocation) {
super(HystrixCommandGroupKey.Factory.asKey(
"ResultHystrixCommand"),
100); // 设置超时时间
// 关联 Dubbo Invoker 和 Invocation
this.invoker = invoker;
this.invocation = invocation;
}
@Override
protected Result run() throws Exception {
// 远程方法调用执行
return invoker.invoke(invocation);
}
}
```
当目标方法执行时间超过 100 ms 时,触发熔断,并抛出 `new UnsupportedOperationException("No fallback available.")`。
* Dubbo `Filter` 整合 `ResultHystrixCommand`
```java
@Activate(group = Constants.CONSUMER, value = "hystrix") // 命名当前 Filter 为 "hystrix"
public class HystrixFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return new ResultHystrixCommand(invoker, invocation).execute();
}
}
```
* 创建并配置 `Filter` SPI 配置文件
在相对于 ClassPath 资源 `META-INF/dubbo/` 下创建 `com.alibaba.dubbo.rpc.Filter`,并配置如下:
```properties
hystrix=com.alibaba.boot.dubbo.demo.consumer.filter.HystrixFilter
```
* 配置 `@Reference` `filter` 属性
```java
@RestController
public class DemoConsumerController {
@Reference(
version = "${demo.service.version}",
application = "${dubbo.application.id}",
url = "dubbo://localhost:12345",
filter = "hystrix" // 指向 HystrixFilter 实现
)
private DemoService demoService;
@RequestMapping("/sayHello")
public String sayHello(@RequestParam String name) {
return demoService.sayHello(name);
}
}
```
###### Dubbo 服务端实现
* 服务提供者实现
```java
@Service(
version = "${demo.service.version}",
application = "${dubbo.application.id}",
protocol = "dubbo",
registry = "${dubbo.registry.id}"
)
public class DefaultDemoService implements DemoService {
private final Random random = new Random();
public String sayHello(String name) {
hold();
return "Say : Hello, " + name + " (from Spring Boot)";
}
private void hold() { // 随机等待 < 200 ms,当时间超过 100 ms 时,触发客户端熔断
long time = random.nextInt(200);
System.out.println("To hold " + time + " ms!");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
```
##### 测试客户端 REST 服务
依次启动服务端 和客户端 Spring Boot 应用,
* 执行 `curl` 命令
```
mercyblitz$ curl http://localhost:8080/sayHello?name=Hello
```
* 测试结果
```
{"timestamp":"2018-06-23T01:33:58.682+0000","status":500,"error":"Internal Server Error","message":"ResultHystrixCommand timed-out and no fallback available.","path":"/sayHello"}
```
运行结果说明服务端方法执行超过 100 ms,引起客户端熔断。
(EOF)
## 参考资源
* [Dubbo 官网](https://dubbo.apache.org/):https://dubbo.apache.org/
* [Dubbo 工程](https://github.com/apache/incubator-dubbo):https://github.com/apache/incubator-dubbo
* [Dubbo Spring Boot 工程](https://github.com/apache/incubator-dubbo-spring-boot-project):https://github.com/apache/incubator-dubbo-spring-boot-project
* [CNCF Landscape](https://landscape.cncf.io/):https://landscape.cncf.io/
* [Spring Cloud 官网](https://projects.spring.io/spring-cloud/):https://projects.spring.io/spring-cloud/
* [Kong 社区官网](https://konghq.com/kong-community-edition/):https://konghq.com/kong-community-edition/
* [Opentracing 官网](http://opentracing.io/):http://opentracing.io/
* [Jaeger 官网](https://www.jaegertracing.io/):https://www.jaegertracing.io/
* [Prometheus 官网](https://prometheus.io/):https://prometheus.io/
* [OpenTsdb 官网](http://opentsdb.net/):http://opentsdb.net/
* [Grafana 官网](https://grafana.com/):https://grafana.com/
* [小马哥 Github](https://github.com/mercyblitz):https://github.com/mercyblitz
================================================
FILE: _posts/2018-07-25-Reactive Programming 一种技术 各自表述.md
================================================
# Reactive Programming 一种技术,各自表述
## 前言
作为一名 Java 开发人员,尤其是 Java 服务端工程师,对于 Reactive Programming 的概念似乎相对陌生。随着 Java 9 以及 Spring Framework 5 的相继发布,Reactive 技术逐渐开始被广大从业人员所注意,笔者作为其中一员,更渴望如何理解 Reactive Programming,以及它所带来的哪些显著的编程变化,更为重要的是,怎么将其用于实际生产环境,解决当前面临的问题。然而,随着时间的推移和了解的深入,笔者对 Reactive Programming 的热情逐渐被浇息,对它的未来保持谨慎乐观的态度。
本文从理解 Reactive Programming 的角度出发,尽可能地保持理性和中立的态度,讨论 Reactive Programming 的实质。
## 初识 Reactive
笔者第一次接触 Reactive 技术的时间还要回溯到 2015年末,当时部分应用正使用 Hystrix 实现服务熔断,而 Hystrix 底层依赖是 RxJava 1.x,RxJava 是 Reactive 在 Java 编程语言的扩展框架。当时接触 Reactive 只能算上一种间接的接触,根据 Hystrix 特性来理解 Reactive 技术,感觉上,Hystrix 超时和信号量等特性与 Java 并发框架(J.U.C)的关系密切,进而认为 Reactive 是 J.U.C 的扩展。随后,笔者便参考了两本关于 Reactive Java 编程方面的书:《Reactive Java Programming》和《Reactive Programming with RxJava》。遗憾的是,两者尽管详细地描述 RxJava 的使用方法,然而却没有把 Reactive 使用场景讨论到要点上,如《Reactive Programming with RxJava》所给出的使用场景说明:
> When You Need Reactive Programming
>
> Reactive programming is useful in scenarios such as the following:
>
> * Processing user events such as mouse movement and clicks, keyboard typing,GPS signals changing over time as users move with their device, device gyroscope signals, touch events, and so on.
> * Responding to and processing any and all latency-bound IO events from disk or network, given that IO is inherently asynchronous ...
> * Handling events or data pushed at an application by a producer it cannot control ...
实际上,以上三种使用场景早已在 Java 生态中完全地实现并充分地实践,它们对应的技术分别是 Java AWT/Swing、NIO/AIO 以及 JMS(Java 消息服务)。那么,再谈 RxJava 的价值又在哪里呢?如果读者是初学者,或许还能蒙混过关。好奇心促使笔者重新开始踏上探索 Reactive 之旅。
## 理解 Reactive
2017年 Java 技术生态中,最有影响力的发布莫过于 Java 9 和 Spring 5,前者主要支持模块化,次要地提供了 Flow API 的支持,后者则将”身家性命“压在 Reactive 上面,认为 Reactive 是未来的趋势,它以 Reactive 框架 Reactor 为基础,逐步构建一套完整的 Reactive 技术栈,其中以 WebFlux 技术为最引人关注,作为替代 Servlet Web 技术栈的核心特性,承载了多年 Spring 逆转 Java EE 的初心。于是,业界开始大力地推广 Reactive 技术,于是笔者又接触到一些关于 Reactive 的讲法。
### 关于 Reactive 的一些讲法
其中笔者挑选了以下三种出镜率最高的讲法:
- Reactive 是异步非阻塞编程
- Reactive 能够提升程序性能
- Reactive 解决传统编程模型遇到的困境
第一种说法描述了功能特性,第二种说法表达了性能收效,第三种说法说明了终极目地。下面的讨论将围绕着这三种讲法而展开,深入地探讨 Reactive Programming 的实质,并且理解为什么说 Reactive Programming 是”一种技术,各自表述“。
同时,讨论的方式也一反常态,并不会直奔主题地解释什么 Reactive Programming,而是从问题的角度出发,从 Reactive 规范和框架的论点,了解传统编程模型中所遇到的困境,逐步地揭开 Reactive 神秘的面纱。其中 Reactive 规范是 JVM Reactive 扩展规范 [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm),而 Reactive 实现框架则是最典型的实现:
* Java 9 Flow API
* RxJava
* Reactor
### 传统编程模型中的某些困境
#### [Reactor](http://projectreactor.io/docs/core/release/reference/#_blocking_can_be_wasteful) 认为阻塞可能是浪费的
> ### 3.1. Blocking Can Be Wasteful
>
> Modern applications can reach huge numbers of concurrent users, and, even though the capabilities of modern hardware have continued to improve, performance of modern software is still a key concern.
>
> There are broadly two ways one can improve a program’s performance:
>
> 1. **parallelize**: use more threads and more hardware resources.
> 2. **seek more efficiency** in how current resources are used.
>
> Usually, Java developers write programs using blocking code. This practice is fine until there is a performance bottleneck, at which point the time comes to introduce additional threads, running similar blocking code. But this scaling in resource utilization can quickly introduce contention and concurrency problems.
>
> Worse still, blocking wastes resources.
>
> So the parallelization approach is not a silver bullet.
将以上 Reactor 观点归纳如下,它认为:
1. 阻塞导致性能瓶颈和浪费资源
2. 增加线程可能会引起资源竞争和并发问题
3. 并行的方式不是银弹(不能解决所有问题)
第三点基本上是废话,前面两点则较为容易理解,为了减少理解的偏差,以下讨论将结合示例说明。
##### 理解阻塞的弊端
假设有一个数据加载器,分别加载配置、用户信息以及订单信息,如下图所示:
* 图示

- Java 实现
```java
public class DataLoader {
public final void load() {
long startTime = System.currentTimeMillis(); // 开始时间
doLoad(); // 具体执行
long costTime = System.currentTimeMillis() - startTime; // 消耗时间
System.out.println("load() 总耗时:" + costTime + " 毫秒");
}
protected void doLoad() { // 串行计算
loadConfigurations(); // 耗时 1s
loadUsers(); // 耗时 2s
loadOrders(); // 耗时 3s
} // 总耗时 1s + 2s + 3s = 6s
protected final void loadConfigurations() {
loadMock("loadConfigurations()", 1);
}
protected final void loadUsers() {
loadMock("loadUsers()", 2);
}
protected final void loadOrders() {
loadMock("loadOrders()", 3);
}
private void loadMock(String source, int seconds) {
try {
long startTime = System.currentTimeMillis();
long milliseconds = TimeUnit.SECONDS.toMillis(seconds);
Thread.sleep(milliseconds);
long costTime = System.currentTimeMillis() - startTime;
System.out.printf("[线程 : %s] %s 耗时 : %d 毫秒\n",
Thread.currentThread().getName(), source, costTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
new DataLoader().load();
}
}
```
* 运行结果
```
[线程 : main] loadConfigurations() 耗时 : 1005 毫秒
[线程 : main] loadUsers() 耗时 : 2002 毫秒
[线程 : main] loadOrders() 耗时 : 3001 毫秒
load() 总耗时:6031 毫秒
```
* 结论
由于加载过程串行执行的关系,导致消耗实现线性累加。Blocking 模式即串行执行 。
不过 Reactor 也提到,以上问题可通过并行的方式来解决,不过编写并行程序较为复杂,那么其中难点在何处呢?
##### 理解并行的复杂
再将以上示例由串行调整为并行,如下图所示:
* 图示

- Java 代码
```java
public class ParallelDataLoader extends DataLoader {
protected void doLoad() { // 并行计算
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建线程池
CompletionService completionService = new ExecutorCompletionService(executorService);
completionService.submit(super::loadConfigurations, null); // 耗时 >= 1s
completionService.submit(super::loadUsers, null); // 耗时 >= 2s
completionService.submit(super::loadOrders, null); // 耗时 >= 3s
int count = 0;
while (count < 3) { // 等待三个任务完成
if (completionService.poll() != null) {
count++;
}
}
executorService.shutdown();
} // 总耗时 max(1s, 2s, 3s) >= 3s
public static void main(String[] args) {
new ParallelDataLoader().load();
}
}
```
* 运行结果
```
[线程 : pool-1-thread-1] loadConfigurations() 耗时 : 1003 毫秒
[线程 : pool-1-thread-2] loadUsers() 耗时 : 2005 毫秒
[线程 : pool-1-thread-3] loadOrders() 耗时 : 3005 毫秒
load() 总耗时:3068 毫秒
```
* 结论
明显地,程序改造为并行加载后,性能和资源利用率得到提升,消耗时间取最大者,即三秒,由于线程池操作的消耗,整体时间将略增一点。不过,以上实现为什么不直接使用 `Future#get()` 方法强制所有任务执行完毕,然后再统计总耗时?
Reactor 这方面的看法并没有向读者清晰地表达全秒,不过这还不是全部,听听它接下来的说法。
#### [Reactor](http://projectreactor.io/docs/core/release/reference/#_asynchronicity_to_the_rescue) 认为异步不一定能够救赎
> ### 3.2. Asynchronicity to the Rescue?
>
> The second approach (mentioned earlier), seeking more efficiency, can be a solution to the resource wasting problem. By writing *asynchronous*, *non-blocking* code, you let the execution switch to another active task **using the same underlying resources** and later come back to the current process when the asynchronous processing has finished.
>
> Java offers two models of asynchronous programming:
>
> - **Callbacks**: Asynchronous methods do not have a return value but take an extra `callback` parameter (a lambda or anonymous class) that gets called when the result is available. A well known example is Swing’s `EventListener`hierarchy.
> - **Futures**: Asynchronous methods return a `Future<T>` **immediately**. The asynchronous process computes a `T` value, but the `Future` object wraps access to it. The value is not immediately available, and the object can be polled until the value is available. For instance, `ExecutorService` running `Callable<T>` tasks use `Future` objects.
>
> Are these techniques good enough? Not for every use case, and both approaches have limitations.
>
> Callbacks are hard to compose together, quickly leading to code that is difficult to read and maintain (known as "Callback Hell").
>
> Futures are a bit better than callbacks, but they still do not do well at composition, despite the improvements brought in Java 8 by `CompletableFuture`.
再次将以上观点归纳,它认为:
- Callbacks 是解决非阻塞的方案,然而他们之间很难组合,并且快速地将代码引导至 "Callback Hell" 的不归路
- Futures 相对于 Callbacks 好一点,不过还是无法组合,不过 `CompletableFuture` 能够提升这方面的不足
以上 Reactor 的观点仅给出了结论,没有解释现象,其中场景设定也不再简单直白,从某种程度上,这也侧面地说明,Reactive Programming 实际上是”高端玩家“的游戏。接下来,本文仍通过示例的方式,试图解释"Callback Hell" 问题以及 `Future` 的限制。
##### 理解 "Callback Hell"
- Java GUI 示例
```java
public class JavaGUI {
public static void main(String[] args) {
JFrame jFrame = new JFrame("GUI 示例");
jFrame.setBounds(500, 300, 400, 300);
LayoutManager layoutManager = new BorderLayout(400, 300);
jFrame.setLayout(layoutManager);
jFrame.addMouseListener(new MouseAdapter() { // callback 1
@Override
public void mouseClicked(MouseEvent e) {
System.out.printf("[线程 : %s] 鼠标点击,坐标(X : %d, Y : %d)\n",
currentThreadName(), e.getX(), e.getY());
}
});
jFrame.addWindowListener(new WindowAdapter() { // callback 2
@Override
public void windowClosing(WindowEvent e) {
System.out.printf("[线程 : %s] 清除 jFrame... \n", currentThreadName());
jFrame.dispose(); // 清除 jFrame
}
@Override
public void windowClosed(WindowEvent e) {
System.out.printf("[线程 : %s] 退出程序... \n", currentThreadName());
System.exit(0); // 退出程序
}
});
System.out.println("当前线程:" + currentThreadName());
jFrame.setVisible(true);
}
private static String currentThreadName() { // 当前线程名称
return Thread.currentThread().getName();
}
}
```
* 运行结果
点击窗体并关闭窗口,控制台输出如下:
```
当前线程:main
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 121)
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 122)
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 122)
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 122)
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 122)
[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 201, Y : 102)
[线程 : AWT-EventQueue-0] 清除 jFrame...
[线程 : AWT-EventQueue-0] 退出程序...
```
* 结论
Java GUI 以及事件/监听模式基本采用匿名内置类实现,即回调实现。从本例可以得出,鼠标的点击确实没有被其他线程给阻塞。不过当监听的维度增多时,Callback 实现也随之增多。Java Swing 事件/监听是一种典型的既符合异步非阻塞,又属于 Callback 实现的场景,其并发模型可为同步或异步。不过,在 Java 8 之前,由于接口无法支持 `default` 方法,当接口方法过多时,通常采用 `Adapter` 模式作为缓冲方案,达到按需实现的目的。尤其在 Java GUI 场景中。即使将应用的 Java 版本升级到 8 ,由于这些 Adapter ”遗老遗少“实现的存在,使得开发人员仍不得不面对大量而繁琐的 Callback 折中方案。既然 Reactor 提出了这个问题,那么它或者 Reactive 能否解决这个问题呢?暂时存疑,下一步是如何理解 `Future` 的限制。
##### 理解 `Future` 的限制
Reactor 的观点仅罗列 `Future` 的一些限制,并没有将它们解释清楚,接下来用两个例子来说明其中原委。
###### 限制一:`Future` 的阻塞性
在前文示例中,`ParallelDataLoader` 利用 `CompletionService` API 实现 `load*()` 方法的并行加载,如果将其调整为 `Future` 的实现,可能的实现如下:
* Java `Future` 阻塞式加载示例
```java
public class FutureBlockingDataLoader extends DataLoader {
protected void doLoad() {
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建线程池
runCompletely(executorService.submit(super::loadConfigurations)); // 耗时 >= 1s
runCompletely(executorService.submit(super::loadUsers)); // 耗时 >= 2s
runCompletely(executorService.submit(super::loadOrders)); // 耗时 >= 3s
executorService.shutdown();
} // 总耗时 sum(>= 1s, >= 2s, >= 3s) >= 6s
private void runCompletely(Future<?> future) {
try {
future.get(); // 阻塞等待结果执行
} catch (Exception e) {
}
}
public static void main(String[] args) {
new FutureBlockingDataLoader().load();
}
}
```
* 运行结果
```
[线程 : pool-1-thread-1] loadConfigurations() 耗时 : 1003 毫秒
[线程 : pool-1-thread-2] loadUsers() 耗时 : 2004 毫秒
[线程 : pool-1-thread-3] loadOrders() 耗时 : 3002 毫秒
load() 总耗时:6100 毫秒
```
* 结论
`ParallelDataLoader` 加载耗时为”3068 毫秒“,调整后的 `FutureBlockingDataLoader` 则比串行的 `DataLoader` 加载耗时(“6031 毫秒”)还要长。说明`Future#get()` 方法不得不等待任务执行完成,换言之,如果多个任务提交后,返回的多个 Future 逐一调用 `get()` 方法时,将会依次 blocking,任务的执行从并行变为串行。这也是之前为什么 `ParallelDataLoader` 不采取 `Future` 的解决方案的原因。
###### 限制二:`Future` 不支持链式操作
由于 `Future` 无法实现异步执行结果链式处理,尽管 `FutureBlockingDataLoader` 能够解决方法数据依赖以及顺序执行的问题,不过它将并行执行带回了阻塞(串行)执行。所以,它不是一个理想实现。不过 `CompletableFuture` 可以帮助提升 `Future` 的限制:
* Java `CompletableFuture` 重构 `Future` 链式实现
```java
public class FutureChainDataLoader extends DataLoader {
protected void doLoad() {
CompletableFuture
.runAsync(super::loadConfigurations)
.thenRun(super::loadUsers)
.thenRun(super::loadOrders)
.whenComplete((result, throwable) -> { // 完成时回调
System.out.println("加载完成");
})
.join(); // 等待完成
}
public static void main(String[] args) {
new ChainDataLoader().load();
}
}
```
* 运行结果
```
[线程 : ForkJoinPool.commonPool-worker-1] loadConfigurations() 耗时 : 1000 毫秒
[线程 : ForkJoinPool.commonPool-worker-1] loadUsers() 耗时 : 2005 毫秒
[线程 : ForkJoinPool.commonPool-worker-1] loadOrders() 耗时 : 3001 毫秒
加载完成
load() 总耗时:6093 毫秒
```
* 结论
通过输出日志分析, `FutureChainDataLoader` 并没有像 `FutureBlockingDataLoader` 那样使用三个线程分别执行加载任务,仅使用了一个线程,换言之,这三次加载同一线程完成,并且异步于 main 线程,如下所示:

尽管 `CompletableFuture` 不仅是异步非阻塞操作,而且还能将 Callback 组合执行,也不存在所谓的 ”Callback Hell“ 等问题。如果强制等待结束的话,又回到了阻塞编程的方式。同时,相对于 `FutureBlockingDataLoader` 实现,重构后的 `FutureChainDataLoader` 不存在明显性能提升。
> 稍作解释,`CompletableFuture` 不仅可以支持 `Future` 链式操作,而且提供三种生命周期回调,即执行回调(Action)、完成时回调(Complete)、和异常回调(Exception),类似于 Spring 4 `ListenableFuture` 以及 Guava `ListenableFuture`。
至此,Reactor 的官方参考文档再没有出现其他有关”传统编程模型中的某些困境“的描述,或许读者老爷和我一样,对 Reactive 充满疑惑,它真能解决以上问题吗?当然,监听则明,偏听则暗,下面我们再来参考 [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 的观点。
#### [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope) 认为异步系统和资源消费需要特殊处理
> Handling streams of data—especially “live” data whose volume is not predetermined—requires special care in an asynchronous system. The most prominent issue is that resource consumption needs to be carefully controlled such that a fast data source does not overwhelm the stream destination. Asynchrony is needed in order to enable the parallel use of computing resources, on collaborating network hosts or multiple CPU cores within a single machine.
观点归纳:
- 流式数据容量难以预判
- 异步编程复杂
- 数据源和消费端之间资源消费难以平衡
此观点与 Reactor 相同的部分是,两者均认为异步编程复杂,而前者还提出了数据结构(流式数据)以及数据消费问题。
无论两者的观点孰优谁劣,至少说明一个现象,业界对于 Reactive 所解决的问题并非达到一致,几乎各说各话。那么,到底怎样才算 Reactive Programming 呢?
### 什么是 Reactive Programming
关于什么是 Reactive Programming,下面给出六种渠道的定义,尝试从不同的角度,了解 Reactive Programming 的意涵。首先了解的是 “[The Reactive Manifesto](https://www.reactivemanifesto.org/)” 中的定义
#### [The Reactive Manifesto](https://www.reactivemanifesto.org/) 中的定义
Reactive Systems are: Responsive, Resilient, Elastic and Message Driven.
> https://www.reactivemanifesto.org/
该组织对 Reactive 的定义非常简单,其特点体现在以下关键字:
- 响应的(Responsive)
- 适应性强的(Resilient)
- 弹性的(Elastic)
- 消息驱动的(Message Driven)
不过这样的定义侧重于 Reactive 系统,或者说是设计 Reactive 系统的原则。
#### [维基百科](https://en.wikipedia.org/wiki/Reactive_programming)中的定义
维基百科作为全世界的权威知识库,其定义的公允性能够得到保证:
> Reactive programming is a declarative programming paradigm concerned with **data streams** and the **propagation of change**. With this paradigm it is possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow.
>
> > 参考地址:https://en.wikipedia.org/wiki/Reactive_programming
维基百科认为 Reactive programming 是一种声明式的编程范式,其核心要素是**数据流(data streams )**与**其传播变化( propagation of change)**,前者是关于数据结构的描述,包括静态的数组(arrays)和动态的事件发射器(event emitters)。由此描述,在笔者脑海中浮现出以下技术视图:
- 数据流:Java 8 `Stream`
- 传播变化:Java `Observable`/`Observer`
- 事件/监听:Java `EventObject`/`EventListener`
这些技术能够很好地满足维基百科对于 Reactive 的定义,那么, Reactive 框架和规范的存在意义又在何方?或许以上定义过于抽象,还无法诠释 Reactive 的全貌。于是乎,笔者想到了去 Spring 官方找寻答案,正如所愿,在 Spring Framework 5 官方参考文档中找到其中定义。
#### [Spring](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-why-reactive) 5 中的定义
> The term "reactive" refers to programming models that are built around **reacting to change** — network component reacting to I/O events, UI controller reacting to mouse events, etc. In that sense **non-blocking** is reactive because instead of being blocked we are now in the mode of reacting to notifications as operations complete or data becomes available.
>
> > 参考地址:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-why-reactive
相对于维基百科的定义,Spring 5 WebFlux 章节同样也提到了变化响应(reacting to change ) ,并且还说明非阻塞(non-blocking)就是 Reactive。同时,其定义的侧重点在响应通知方面,包括操作完成(operations complete)和数据可用(data becomes available)。Spring WebFlux 作为 Reactive Web 框架,天然支持非阻塞,不过早在 Servlet 3.1 规范时代皆以实现以上需求,其中包括 Servlet 3.1 非阻塞 API `ReadListener` 和`WriteListener`,以及 Servlet 3.0 所提供的异步上下文 `AsyncContext` 和事件监听 `AsyncListener`。这些 Servlet 特性正是为 Spring WebFlux 提供适配的以及,所以 Spring WebFlux 能完全兼容 Servlet 3.1 容器。笔者不禁要怀疑,难道 Reactive 仅是新包装的概念吗?或许就此下结论还为时尚早,不妨在了解一下 ReactiveX 的定义。
#### [ReactiveX](http://reactivex.io/intro.html) 中的定义
广泛使用的 RxJava 作为 ReactiveX 的 Java 实现,对于 Reactive 的定义,ReactiveX 具备相当的权威性:
> ReactiveX extends the observer pattern to support sequences of data and/or events and adds operators that allow you to compose sequences together declaratively while abstracting away concerns about things like low-level threading, synchronization, thread-safety, concurrent data structures, and non-blocking I/O.
>
> > 参考地址:http://reactivex.io/intro.html
不过,ReactiveX 并没有直接给 Reactive 下定义,而是通过技术实现手段说明如何实现 Reactive。ReactiveX 作为观察者模式的扩展,通过操作符(Opeators)对数据/事件序列(Sequences of data and/or events )进行操作,并且屏蔽并发细节(abstracting away…),如线程 API(`Exectuor` 、`Future`、`Runnable`)、同步、线程安全、并发数据结构以及非阻塞 I/O。该定义的侧重点主要关注于实现,包括设计模式、数据结构、数据操作以及并发模型。除设计模式之外,Java 8 `Stream` API 具备不少的操作符,包括迭代操作 for-each、map/reduce 以及集合操作 `Collector`等,同时,通过 `parallel()` 和 `sequential()` 方法实现并行和串行操作间的切换,同样屏蔽并发的细节。至于数据结构,`Stream` 和数据流或集合序列可以画上等号。唯独在设计模式上,`Stream` 是迭代器(Iterator)模式实现,而 ReactiveX 则属于观察者(Observer)模式的实现。 对此,Reactor 做了进一步地解释。
#### [Reactor](http://projectreactor.io/docs/core/release/reference/#intro-reactive) 中的定义
> The reactive programming paradigm is often presented in object-oriented languages as an extension of the Observer design pattern. One can also compare the main reactive streams pattern with the familiar Iterator design pattern, as there is a duality to the Iterable-Iterator pair in all of these libraries. One major difference is that, while an Iterator is pull-based, reactive streams are push-based.
>
> > [http](http://projectreactor.io/docs/core/release/reference/)[://projectreactor.io/docs/core/release/reference/#](http://projectreactor.io/docs/core/release/reference/)[intro-reactive](http://projectreactor.io/docs/core/release/reference/)
同样地,Reactor 也提到了观察者模式(Observer pattern )和迭代器模式(Iterator pattern)。不过它将 Reactive 定义为响应流模式(Reactive streams pattern ),并解释了该模式和迭代器模式在数据读取上的差异,即前者属于推模式(push-based),后者属于拉模式(pull-based)。难道就因为这因素,就要使用 Reactive 吗?这或许有些牵强。个人认为,以上组织均没有坦诚或者简单地向用户表达,都采用一种模糊的描述,多少难免让人觉得故弄玄虚。幸运地是,我从 ReactiveX 官方找到一位前端牛人 [André Staltz](https://gist.github.com/staltz),他在学习 Reactive 过程中与笔者一样,吃了不少的苦头,在他博文[《The introduction to Reactive Programming you've been missing》](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)中,他给出了中肯的解释。
#### [André Staltz](https://gist.github.com/staltz) 给出的定义
> [Reactive programming is programming with **asynchronous data streams**.](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#reactive-programming-is-programming-with-asynchronous-data-streams)
>
> In a way, **this isn't anything new**. Event buses or your typical click events are really an asynchronous event stream, on which you can observe and do some side effects. Reactive is that **idea on steroids**. You are able to create data streams of anything, not just from click and hover events. Streams are cheap and ubiquitous, anything can be a stream: variables, user inputs, properties, caches, data structures, etc.
>
> >["What is Reactive Programming?"](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#what-is-reactive-programming)
他在文章指出,Reactive Programming 并不是新东西,而是司空见惯的混合物,比如事件总监、鼠标点击事件等。同时,文中也提到异步(asynchronous )以及数据流(data streams)等关键字。如果说因为 Java 8 Stream 是迭代器模式的缘故,它不属于Reactive Programming 范式的话,那么,Java GUI 事件/监听则就是 Reactive。那么,Java 开发人员学习 RxJava、Reactor、或者 Java 9 Flow API 的必要性又在哪里呢?因此,非常有必要深入探讨 Reactive Programming 的使用场景。
### Reactive Programming 使用场景
正如同 Reactive Programming 的定义那样,各个组织各执一词,下面仍采用多方引证的方式,寻求 Reactive Programming 使用场景的“最大公约数”。
#### [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 认为的使用场景
> The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary.
>
> > https://github.com/reactive-streams/reactive-streams-jvm
[Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 认为 Reactive Streams 用于在异步边界(asynchronous boundary)管理流式数据交换( govern the exchange of stream data)。异步说明其并发模型,流式数据则体现数据结构,管理则强调它们的它们之间的协调。
#### [Spring 5](https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance) 认为的使用场景
> Reactive and non-blocking generally do not make applications run faster. They can, in some cases, for example if using the `WebClient` to execute remote calls in parallel. On the whole it requires more work to do things the non-blocking way and that can increase slightly the required processing time.
>
> The key expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory. That makes applications more resilient under load because they scale in a more predictable way.
Spring 认为 Reactive 和非阻塞通常并非让应用运行更快速(generally do not make applications run faster),甚至会增加少量的处理时间,因此,它的使用场景则利用较少的资源,提升应用的伸缩性(scale with a small, fixed number of threads and less memory)。
#### [ReactiveX](http://reactivex.io/intro.html) 认为的使用场景
> The ReactiveX Observable model allows you to treat streams of asynchronous events with the same sort of simple, composable operations that you use for collections of data items like arrays. It frees you from tangled webs of callbacks, and thereby makes your code more readable and less prone to bugs.
ReactiveX 所描述的使用场景与 Spring 的不同,它没有从性能入手,而是代码可读性和减少 Bugs 的角度出发,解释了 Reactive Programming 的价值。同时,强调其框架的核心特性:异步(asynchronous)、同顺序(same sort)和组合操作(composable operations)。它也间接地说明了,Java 8 `Stream` 在组合操作的限制,以及操作符的不足。
#### [Reactor](http://projectreactor.io/docs/core/release/reference/#intro-reactive) 认为的使用场景
> Composability and readability
>
> Data as a flow manipulated with a rich vocabulary of operators
>
> Nothing happens until you subscribe
>
> Backpressure or the ability for the consumer to signal the producer that the rate of emission is too high
>
> High level but high value abstraction that is concurrency-agnostic
Reactor 同样强调结构性和可读性(Composability and readability)和高层次并发抽象(High level abstraction),并明确地表示它提供丰富的数据操作符( rich vocabulary of operators)弥补 `Stream` API 的短板,还支持背压(Backpressure)操作,提供数据生产者和消费者的消息机制,协调它们之间的产销失衡的情况。同时,Reactor 采用订阅式数据消费(Nothing happens until you subscribe)的机制,实现 `Stream` 所不具备的数据推送机制。
至此,讨论接近尾声,最后的部分将 Reactive Programming 内容加以总结。
## 总结 Reactive Programming
Reactive Programming 作为观察者模式([Observer](https://en.wikipedia.org/wiki/Observer_pattern)) 的延伸,不同于传统的命令编程方式( [Imperative programming](https://en.wikipedia.org/wiki/Imperative_programming))同步拉取数据的方式,如迭代器模式([Iterator](https://en.wikipedia.org/wiki/Iterator_pattern)) 。而是采用数据发布者同步或异步地推送到数据流(Data Streams)的方案。当该数据流(Data Steams)订阅者监听到传播变化时,立即作出响应动作。在实现层面上,Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性,屏蔽并发实现的复杂细节,提供数据流的有序操作,从而达到提升代码的可读性,以及减少 Bugs 出现的目的。同时,Reactive Programming 结合背压(Backpressure)的技术解决发布端生成数据的速率高于订阅端消费的问题。
## 后记
2005年,李敖大师曾在上海复旦大学做过一次演讲,他在讲到“放弃自由主义,注重务实”的部分时,引述一段故事:
> 美国有一个报纸,办报的人叫ABBOTT,他晚年的时候写回忆录,他爸爸是一个写儿童书的作家,他爸爸临死前告诉他说,感觉到人间所有的教会的争执90%都是名词之争。这个小ABBOTT老了以后,他回忆这段话,他说我回忆我爸爸告诉我所有人间宗教的争执90%都是名词之争,他说我发现我爸爸数学不好,原来最后那10%也是名词之争。
实际上,名词之争的战场不限于宗教教会,技术领域不也是如此吗?Reactive Programming 实际是一种技术,却被各自表达。有些定义含糊不清,有些定义则空洞无实,还有一些则是夸大其词,只有少数保持客观中立。不免让然唏嘘技术领域的非理性营销。当了解了 Reactive Programming 的本质后,您的热情还能像如初般地高涨吗?
================================================
FILE: _posts/2019-03-05-《Java编程方法论 响应式之Rxjava篇》序.md
================================================
# 《Java编程方法论 响应式之Rxjava篇》序
在《2019 一月的InfoQ 架构和设计趋势报告》[^1]中,响应式编程(Reactive Programming)和函数式(Functional Programing)仍旧编列在第一季度(Q1)的 Early Adopters(早期采纳者) 中。尽管这仅是一家之言,然而不少的开发人员逐渐意识到 Reactive 之风俨然吹起。也许您的生产系统尚未出现 Reactive 的身影,不过您可能听说过 Spring WebFlux 或 Netflix Hystrix 等开源框架。笔者曾请教过 Pivotal(Spring 母公司)布道师 Josh Long[^2]:”Spring 技术栈未来的重心是否要布局在 Reactive 之上?“。对方的答复是:”没错,Reactive 是未来趋势。“。同时,越来越多的开源项目开始签署 Reactive 宣言(The Reactive Manifesto)[^3],并喊出 ”Web Are Reactive“ 的口号。
或许开源界的种种举动无法说服您向 Reactive 的”港湾“中停靠,不过 Java 9 Flow API[^4] 的引入,又给业界注入了一剂强心针。不难看出,无论是 Java API,还是 Java 框架均走向了 Reactive 编程模型的道路,这并非是一种巧合。
通常,人们谈到的 Reactive 可与 Reactive 编程划上等号,以”非阻塞(Non-Blocking)“和”异步(Asynchronous)“的特性并述,数据结构与操作相辅相成。Reactive 涉及函数式和并发两种编程模型,前者关注语法特性,后者强调执行效率。简言之,Reactive 编程的意图在于 ”Less Code,More Efficient“。除此之外,个人认为 Reactive 更大的价值在于统一 Java 并发编程模型,使得同步和异步的实现代码无异,同时做到 Java 编程风格与其他编程语言更好地融合,或许您也发现 Java 与 JS 在 Reactive 方面并不存在本质区别。纵观 Java 在 Reactive 编程上的发展而看,其特性更新可谓是步步为营,如履薄冰。尽管 Java 线程 API `Thread` 与 `Runnable` 就已具备异步以及非阻塞的能力,然而同步和异步编程的模式并不统一,并且理解 `Thread` API 的细节和管理线程生命周期的成本均由开发人员概括承受。虽然 Java 5 引入 J.U.C 框架(Java 并发框架)之后, `ExecutorService` 实现减轻了以上负担。不过开发人员仍需关注 `ExecutorService` 实现细节,比如怎样合理地设置线程池空间以及阻塞队列又成为新的挑战。为此,Java 7 又引入 `ForkJoinPool` API,不过此时的J.U.C 框架与 Reactive 理念仍存在距离,即使是线程安全的数据结构,也并不具备并行计算的能力,如:集合并行排序,同时操作集合的手段也相当的贫瘠,缺少类似 Map/Reduce 等操作。不过这些困难只是暂时的,终究被 Java 8 ”救赎“。`Stream` API 的出现不但具备数据操作在串行和并行间自由切换的能力,如 `sequential()` 以及 `parallel()` 方法,而且淡化了并发的特性,如 `sorted()` 方法即可能是传统排序,亦或是并行排序。相同的设计哲学也体现在 Java Reactive 实现框架中,如同书中提及的 RxJava[^5] API `io.reactivex.Observable` 。统一编程模型只是 `Stream` 其中设计目标之一,它结合 Lambda 语法特性,虽然提供了数量可观的操作方法,如 `flatMap()` 等,然而无论对比 RxJava,还是 Reactor[^6] ,`Stream` 操作方法却又相形见绌。值得一提的是,这些操作方法在 Reactive 的术语中称之为操作符(Operators)。当然框架内建的操作符的多与寡,并非判断其是否为 Reactive 实现的依据。其中决定性因素在于数据必须来源于发布方(生产者)的”推送(Push)“,而非消费端的”拉取(Pull)“。显然,`Stream` 属于消费端已就绪(Ready)的数据集合,并不存在其他数据推送源。不过 JVM 语言早期的 Reactive 定义处于模糊地带,如 RxJava API 属于观察者模式(Observer Pattern)[^7]的扩展,而非迭代器(Iterator Pattern)模式[^8]的实现。而 Reactor 的实现则拥抱 Reactive Streams 规范[^9] ,该规范消费端对于数据的操作是被动的处理,而非主动的索。换言之,数据的到达存在着不确定性[^10]。当推送的数据无法得到消费端及时效应时,Reactive 框架必须提供背压(Backpressure)[^11]实现,确保消费端拥有”拒绝的权利(cancel)”。在此理论基础上,Reactive Streams 规范定义了一套抽象的 API,作为 Java 9 `java.util.concurrent.Flow` API 的顶层设计。不过关于操作符的部分,该规范似乎不太关心,这也是为什么 RxJava 和 Reactor 均称自身为 Reactive 扩展框架的原因,同时两者在 API 级别提供多种调度器(Schedulers)[^12]实现,适配不同并发场景提供。尽管 Reactive 定义在不同的阵营之间存在差异,援引本人在《Reactive-Programming-一种技术-各自表述》[^13]文中的总结:
> Reactive Programming 作为观察者模式([Observer](https://en.wikipedia.org/wiki/Observer_pattern)) 的延伸,不同于传统的命令编程方式( [Imperative programming](https://en.wikipedia.org/wiki/Imperative_programming))同步拉取数据的方式,如迭代器模式([Iterator](https://en.wikipedia.org/wiki/Iterator_pattern)) 。而是采用数据发布者同步或异步地推送到数据流(Data Streams)的方案。当该数据流(Data Steams)订阅者监听到传播变化时,立即作出响应动作。在实现层面上,Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性,屏蔽并发实现的复杂细节,提供数据流的有序操作,从而达到提升代码的可读性,以及减少 Bugs 出现的目的。同时,Reactive Programming 结合背压(Backpressure)的技术解决发布端生成数据的速率高于订阅端消费的问题。
不难看出,Reactive 是一门综合的编程艺术,在实现框架的加持下,相同的代码逻辑实现同步和异步非阻塞功能,从而达到提升应用整体性能的目的。不过现实的情况或许没有那么理想,Spring 官方文档在《Web on Reactive Stack》章节中提到,"Reactive 和非阻塞通常并不是让应用运行的更快"[^14]:
> *Reactive and non-blocking generally do not make applications run faster.*
为此,JHipster[^15] 给出了一份《 Spring 5 WebFlux 性能测试报告》[^16],其中一条结论是,”Reactive 应用并没有表现出速度提升(甚至是变得更差)“:
> *No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).*
数月后,看似相反的结论却在DZone[^17]一篇名为《Raw Performance Numbers - Spring Boot 2 Webflux vs. Spring Boot 1》[^18]的文中出现,测试结果是 Spring Boot 2 WebFlux在高并发下响应时间更为平稳。实际上,这个测试结论有些”关公战秦琼“的味道,毕竟 Spring Boot 2.0 下的 WebFlux 和 Spring Boot 1.0 中的 Servlet 容器所使用线程模型是不同的,并且 Servlet 3.0 异步以及非阻塞特性缺省是关闭的。不过以上两篇的结论并不矛盾,前者关注于响应速度,后者则强调吞吐量,都是性能的核心指标。遗憾的是,两篇文章均未对各自的测试用例做出调优,因此以上结论都存在一定的局限性,这也是本人对 Reactive 技术能否提升性能提出质疑的地方。
如果本人是国内提出 Reactive 问题的第一人的话,那么知秋[^19]就是国内第一个解决问题的人。作为国内为数不多的 Reactive 以及 NIO 方面的专家,在技术研究上,他追求格物致知,不轻忽技术细节。在知识分享上,他可谓是知无不言,言无不尽,不仅在社交群中答疑解惑,而且录制免费视频,发布在 B 站[^20]以及 YouTube 频道[^21],并得到 Josh Long 等大佬的推文(Twitter)。或许以上方式还不足以完整地讨论 Java Reactive 技术,知秋选择了漫长而又艰苦的著书之路,尽管他是本人的朋友,然而 ”内举不避亲“,笔者推荐给读者朋友,首先是因为这是大陆地区第一本全面解读 Java Reactive 技术的书籍,除作者的雄厚技术积累背书之外,书中的知识脉络是循序渐进的。同时,这也是一本引人深思的书,本书在导读源码的同时,也引导读者对于代码设计上的思考。再者,这又是一本知识苦旅的书,因为它涉及面较广,读者不仅需要具备一定的 Java 并发以及面向对象设计,而且需要读者付出较多的时间去反复推敲。正所谓”夫夷以近,则游者众;险以远,则至者少“[^22],笔者希望读者在购买此书后,不轻言放弃,当您面临挑战时,那才是成长的开始。同时,也期盼读者将 Reactive 技术付之于实践,提早触碰未来。
小马哥(mercyblitz)[^23]
2019 年 3 月 5 日
[^1]: Architecture and Design InfoQ Trends Report - January 2019 - https://www.infoq.com/articles/architecture-trends-2019
[^2]: Josh (@starbuxman) is the Spring Developer Advocate at Pivotal and a Java Champion - https://spring.io/team/jlong
[^3]: The Reactive Manifesto - https://www.reactivemanifesto.org
[^4]: https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html
[^5]: RxJava: Reactive Extensions for the JVM - https://github.com/ReactiveX/RxJava6
[^6]: Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient demand management (in the form of managing "backpressure"). - https://github.com/reactor/reactor-core
[^7]: Observer Pattern - http://en.wikipedia.org/wiki/observer_pattern
[^8]: Iterator Pattern - https://en.wikipedia.org/wiki/Iterator_pattern
[^9]: Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. - https://www.reactive-streams.org/
[^10]: Handling streams of data—especially “live” data whose volume is not predetermined - https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope
[^11]: Backpressure is an integral part of this model in order to allow the queues which mediate between threads to be bounded. The benefits of asynchronous processing would be negated if the backpressure signals were synchronous (see also the [Reactive Manifesto](http://reactivemanifesto.org/)), therefore care has been taken to mandate fully non-blocking and asynchronous behavior of all aspects of a Reactive Streams implementation. - https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope
[^12]: RxJava Scheduler - http://reactivex.io/documentation/scheduler.html, Reactor Schedulers : https://projectreactor.io/docs/core/release/reference/#schedulers
[^13]: 《Reactive Programming 一种技术,各自表述》:https://mercyblitz.github.io/2018/07/25/Reactive-Programming-一种技术-各自表述
[^14]: 1.1.6. Performance - https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-performance
[^15]:JHipster -https://www.jhipster.tech/
[^16]: 《Spring 5 WebFlux: Performance tests》- https://blog.ippon.tech/spring-5-webflux-performance-tests
[^17]: DZone : https://dzone.com
[^18]:《Raw Performance Numbers - Spring Boot 2 Webflux vs. Spring Boot 1》 - https://dzone.com/articles/raw-performance-numbers-spring-boot-2-webflux-vs-s
[^19]: 知秋,本书的作者笔名
[^20]: https://space.bilibili.com/2494318
[^21]: https://www.youtube.com/channel/UCmxVT2rkFwDX1tPpVDPJQaQ
[^22]:《游褒禅山记》,王安石
[^23]:小马哥,[Java 劝退师](https://www.douyu.com/mercyblitz),[Apache Dubbo](https://dubbo.apache.org/) PPMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba) 项目架构师 - https://mercyblitz.github.io/about/
================================================
FILE: _posts/2019-04-26-Dubbo Spring Cloud 重塑微服务治理.md
================================================
> 原文链接:[Dubbo Spring Cloud 重塑微服务治理](https://mp.weixin.qq.com/s/K60e1VkAWjwQXftmHw74NQ),来自于微信公众号:`次灵均阁`
## 摘要
在 Java 微服务生态中,Spring Cloud[^1] 成为了开发人员的首选技术栈,然而随着实践的深入和运用规模的扩大,大家逐渐意识到 Spring Cloud 的局限性。在服务治理方面,相较于 Dubbo[^2] 而言,Spring Cloud 并不成熟。遗憾的是,Dubbo 往往被部分开发者片面地视作服务治理的 RPC 框架,而非微服务基础设施。即使是那些有意将 Spring Cloud 迁移至 Dubbo 的小伙伴,当面对其中迁移和改造的成本时,难免望而却步。庆幸的是,Dubbo 生态体系已发生巨大变化,Dubbo Spring Cloud 作为 Spring Cloud Alibaba[^3] 的最核心组件,完全地拥抱 Spring Cloud 技术栈,不但无缝地整合 Spring Cloud 注册中心,包括 Nacos[^4]、Eureka[^5]、Zookeeper[^6] 以及 Consul[^7],而且完全地兼容 Spring Cloud Open Feign[^8] 以及 @LoadBalanced RestTemplate,本文将讨论 Dubbo Spring Cloud 对 Spring Cloud 技术栈所带来的革命性变化。
> 注:由于 Spring Cloud 技术栈涵盖的特性众多,因此本文讨论的范围仅限于服务治理部分。
## 简介
Dubbo Spring Cloud 基于 Dubbo Spring Boot 2.7.1[^9] 和 Spring Cloud 2.x 开发,无论开发人员是 Dubbo 用户还是 Spring Cloud 用户,都能轻松地驾驭,并以接近“零”成本的代价使应用向上迁移。Dubbo Spring Cloud 致力于简化 Cloud Native 开发成本,提高研发效能以及提升应用性能等目的。
2019年4月19日,Dubbo Spring Cloud 首个 Preview Release,随同 Spring Cloud Alibaba `0.2.2.RELEASE` 和 `0.9.0.RELEASE` 一同发布[^10],分别对应 Spring Cloud Finchley[^11] 与 Greenwich[^12] (下文分别简称为 “F” 版 和 “G” 版) 。
## 版本支持
由于 Spring 官方宣布 Spring Cloud Edgware(下文简称为 “E” 版) 将在 2019 年 8 月 1 号后停止维护13,因此,目前 Dubbo Spring Cloud 发布版本并未对 “E” 版提供支持,仅为 “F” 版 和 “G” 版开发,同时也建议和鼓励 Spring Cloud 用户更新至 “F” 版 或 “G” 版。
同时,Dubbo Spring Cloud 基于 Apache Dubbo Spring Boot 2.7.1 开发(最低 Java 版本为 1.8),提供完整的 Dubbo 注解驱动、外部化配置以及 Production-Ready 的特性,详情请参考:https://github.com/apache/incubator-dubbo-spring-boot-project
以下表格将说明 Dubbo Spring Cloud 版本关系映射关系:
| Spring Cloud | Spring Cloud Alibaba | Spring Boot | Dubbo Spring Boot |
| ------------ | -------------------- | ----------- | ---------------------------------- |
| Finchley | `0.2.2.RELEASE` | `2.0.x` | `2.7.1` |
| Greenwich | `0.9.0.RELEASE` | `2.1.x` | `2.7.1` |
| Edgware | `0.1.2.RELEASE` | `1.5.x` | :x: Dubbo Spring Cloud 不支持该版本 |
## 功能特性
由于 Dubbo Spring Cloud 构建在原生的 Spring Cloud 之上,其服务治理方面的能力可认为是 Spring Cloud Plus,不仅完全覆盖 Spring Cloud 原生特性[^13],而且提供更为稳定和成熟的实现,特性比对如下表所示:
| 功能组件 | Spring Cloud | Dubbo Spring Cloud |
| ---------------------------------------------------- | -------------------------------------- | ------------------------------------------------------ |
| 分布式配置(Distributed configuration) | Git、Zookeeper、Consul、JDBC | Spring Cloud 分布式配置 + Dubbo 配置中心[^14] |
| 服务注册与发现(Service registration and discovery) | Eureka、Zookeeper、Consul | Spring Cloud 原生注册中心[^15] + Dubbo 原生注册中心[^16] |
| 负载均衡(Load balancing) | Ribbon(随机、轮询等算法) | Dubbo 内建实现(随机、轮询等算法 + 权重等特性) |
| 服务熔断(Circuit Breakers) | Spring Cloud Hystrix | Spring Cloud Hystrix + Alibaba Sentinel[^17] 等 |
| 服务调用(Service-to-service calls) | Open Feign、`RestTemplate` | Spring Cloud 服务调用 + Dubbo `@Reference` |
| 链路跟踪(Tracing) | Spring Cloud Sleuth[^18] + Zipkin[^19] | Zipkin、opentracing 等 |
### 高亮特性
#### 1. Dubbo 使用 Spring Cloud 服务注册与发现
Dubbo Spring Cloud 基于 Spring Cloud Commons 抽象实现 Dubbo 服务注册与发现,应用只需增添外部化配置属性 “`dubbo.registry.address = spring-cloud://localhost`”,就能轻松地桥接到所有原生 Spring Cloud 注册中心,包括:
- Nacos
- Eureka
- Zookeeper
- Consul
> Dubbo Spring Cloud 将在下个版本支持 Spring Cloud 注册中心与 Dubbo 注册中心并存,提供双注册机制,实现无缝迁移。
#### 2. Dubbo 作为 Spring Cloud 服务调用
默认情况,Spring Cloud Open Feign 以及 `@LoadBalanced` `RestTemplate` 作为 Spring Cloud 的两种服务调用方式。Dubbo Spring Cloud 为其提供了第三种选择,即 Dubbo 服务将作为 Spring Cloud 服务调用的同等公民出现,应用可通过 Apache Dubbo 注解 `@Service`和 `@Reference` 暴露和引用 Dubbo 服务,实现服务间多种协议的通讯。同时,也可以利用 Dubbo 泛化接口轻松实现服务网关。
#### 3. Dubbo 服务自省
Dubbo Spring Cloud 引入了全新的服务治理特性 - 服务自省(Service Introspection),其设计目的在于最大化减轻注册中心负载,去 Dubbo 注册元信息中心化。假设一个 Spring Cloud 应用引入 Dubbo Spring Boot Starter,并暴露 N 个 Dubbo 服务,以 [Dubbo Nacos 注册中心](https://github.com/apache/incubator-dubbo/tree/master/dubbo-registry/dubbo-registry-nacos) 为例,当前应用将注册 N+1 个 Nacos 应用,除 Spring Cloud 应用本身之前,其余 N 个应用均来自于 Dubbo 服务,当 N 越大时,注册中心负载越重。因此,Dubbo Spring Cloud 应用对注册中心的负载相当于传统 Dubbo 的 N 分之一,在不增加基础设施投入的前提下,理论上,使其集群规模扩大 N 倍。当然,未来的 Dubbo 也将提供服务自省的能力。
#### 4. Dubbo 迁移 Spring Cloud 服务调用
尽管 Dubbo Spring Cloud 完全地保留了原生 Spring Cloud 服务调用特性,不过 Dubbo 服务治理的能力是 Spring Cloud Open Feign 所不及的,如高性能、高可用以及负载均衡稳定性等方面。因此,建议开发人员将 Spring Cloud Open Feign 或者 `@LoadBalanced` `RestTemplate` 迁移为 Dubbo 服务。考虑到迁移过程并非一蹴而就,因此,Dubbo Spring Cloud 提供了方案,即 `@DubboTransported` 注解。该注解能够帮助服务消费端的 Spring Cloud Open Feign 接口以及 `@LoadBalanced` `RestTemplate` Bean 底层走 Dubbo 调用(可切换 Dubbo 支持的协议),而服务提供方则只需在原有 `@RestController` 类上追加 Dubbo `@Servce` 注解(需要抽取接口)即可,换言之,在不调整 Feign 接口以及 `RestTemplate` URL 的前提下,实现无缝迁移。如果迁移时间充分的话,建议使用 Dubbo 服务重构系统中的原生 Spring Cloud 服务的定义。
## 简单示例
开发 Dubbo Spring Cloud 应用的方法与传统 Dubbo 或 Spring Cloud 应用类似,按照以下步骤就能完整地实现Dubbo 服务提供方和消费方的应用,完整的示例代码请访问一下资源:
- Dubbo 服务提供方应用 - <https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-server-sample>
- Dubbo 服务消费方应用 - <https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-client-sample>
### 定义 Dubbo 服务接口
Dubbo 服务接口是服务提供方与消费方的远程通讯契约,通常由普通的 Java 接口(interface)来声明,如 `EchoService` 接口:
```java
public interface EchoService {
String echo(String message);
}
```
为了确保契约的一致性,推荐的做法是将 Dubbo 服务接口打包在第二方或者第三方的 artifact(jar)中,如以上接口就存放在 artifact [spring-cloud-dubbo-sample-api](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-sample-api) 之中。
对于服务提供方而言,不仅通过依赖 artifact 的形式引入 Dubbo 服务接口,而且需要将其实现。对应的服务消费端,同样地需要依赖该 artifact,并以接口调用的方式执行远程方法。接下来进一步讨论怎样实现 Dubbo 服务提供方和消费方。
### 实现 Dubbo 服务提供方
#### 初始化 `spring-cloud-dubbo-server-sample` Maven 工程
首先,创建 `artifactId` 名为 `spring-cloud-dubbo-server-sample` 的 Maven 工程,并在其 `pom.xml` 文件中增添
Dubbo Spring Cloud 必要的依赖:
```xml
<dependencies>
<!-- Sample API -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dubbo-sample-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- Spring Cloud Nacos Service Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
```
以上依赖 artifact 说明如下:
- `spring-cloud-dubbo-sample-api` : 提供 `EchoService` 接口的 artifact
- `spring-boot-actuator` : Spring Boot Production-Ready artifact,间接引入 `spring-boot` artifact
- `spring-cloud-starter-dubbo` : Dubbo Spring Cloud Starter `artifact`,间接引入 `dubbo-spring-boot-starter` 等 artifact
- `spring-cloud-starter-alibaba-nacos-discovery` : Nacos Spring Cloud 服务注册与发现 `artifact`
值得注意的是,以上 artifact 未指定版本(version),因此,还需显示地声明 `<dependencyManagement>` :
```xml
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Alibaba dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```
> 以上完整的 Maven 依赖配置,请参考 `spring-cloud-dubbo-server-sample` [`pom.xml`](spring-cloud-dubbo-server-sample/pom.xml) 文件
完成以上步骤之后,下一步则是实现 Dubbo 服务
#### 实现 Dubbo 服务
`EchoService` 作为暴露的 Dubbo 服务接口,服务提供方 `spring-cloud-dubbo-server-sample` 需要将其实现:
```java
@org.apache.dubbo.config.annotation.Service
class EchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return "[echo] Hello, " + message;
}
}
```
其中,`@org.apache.dubbo.config.annotation.Service` 是 Dubbo 服务注解,仅声明该 Java 服务(本地)实现为 Dubbo 服务。
因此,下一步需要将其配置 Dubbo 服务(远程)。
#### 配置 Dubbo 服务提供方
在暴露 Dubbo 服务方面,推荐开发人员外部化配置的方式,即指定 Java 服务实现类的扫描基准包。
> Dubbo Spring Cloud 继承了 Dubbo Spring Boot 的外部化配置特性,也可以通过标注 `@DubboComponentScan` 来实现基准包扫描。
同时,Dubbo 远程服务需要暴露网络端口,并设定通讯协议,完整的 YAML 配置如下所示:
```yaml
dubbo:
scan:
# dubbo 服务扫描基准包
base-packages: org.springframework.cloud.alibaba.dubbo.bootstrap
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1
registry:
# 挂载到 Spring Cloud 注册中心
address: spring-cloud://localhost
spring:
application:
# Dubbo 应用名称
name: spring-cloud-alibaba-dubbo-server
main:
# Spring Boot 2.1 需要设定
allow-bean-definition-overriding: true
cloud:
nacos:
# Nacos 服务发现与注册配置
discovery:
server-addr: 127.0.0.1:8848
```
以上 YAML 内容,上半部分为 Dubbo 的配置:
- `dubbo.scan.base-packages` : 指定 Dubbo 服务实现类的扫描基准包
- `dubbo.protocol` : Dubbo 服务暴露的协议配置,其中子属性 `name` 为协议名称,`port` 为协议端口( -1 表示自增端口,从 20880 开始)
- `dubbo.registry` : Dubbo 服务注册中心配置,其中子属性 `address` 的值 "spring-cloud://localhost",说明挂载到 Spring Cloud 注册中心
> 当前 Dubbo Spring Cloud 实现必须配置 `dubbo.registry.address = spring-cloud://localhost`,下一个版本将其配置变为可选
> (参考 [issue #592](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/592)),
> 并且支持传统 Dubbo 协议的支持(参考 [issue #588](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/588))
下半部分则是 Spring Cloud 相关配置:
- `spring.application.name` : Spring 应用名称,用于 Spring Cloud 服务注册和发现。
> 该值在 Dubbo Spring Cloud 加持下被视作 `dubbo.application.name`,因此,无需再显示地配置 `dubbo.application.name`
- `spring.main.allow-bean-definition-overriding` : 在 Spring Boot 2.1 以及更高的版本增加该设定,
因为 Spring Boot 默认调整了 Bean 定义覆盖行为。(推荐一个好的 Dubbo 讨论 [issue #3193](https://github.com/apache/incubator-dubbo/issues/3193#issuecomment-474340165))
- `spring.cloud.nacos.discovery` : Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
> 以上完整的 YAML 配置文件,请参考 `spring-cloud-dubbo-server-sample` [`bootstrap.yaml`](spring-cloud-dubbo-server-sample/src/main/resources/bootstrap.yaml) 文件
完成以上步骤后,还需编写一个 Dubbo Spring Cloud 引导类。
#### 引导 Dubbo Spring Cloud 服务提供方应用
Dubbo Spring Cloud 引导类与普通 Spring Cloud 应用并无差别,如下所示:
```java
@EnableDiscoveryClient
@EnableAutoConfiguration
public class DubboSpringCloudServerBootstrap {
public static void main(String[] args) {
SpringApplication.run(DubboSpringCloudServerBootstrap.class);
}
}
```
在引导 `DubboSpringCloudServerBootstrap` 之前,请提前启动 Nacos 服务器。
当 `DubboSpringCloudServerBootstrap` 启动后,将应用 `spring-cloud-dubbo-server-sample` 将出现在 Nacos 控制台界面。
当 Dubbo 服务提供方启动后,下一步实现一个 Dubbo 服务消费方。
### 实现 Dubbo 服务消费方
由于 Java 服务就 `EchoService`、服务提供方应用 `spring-cloud-dubbo-server-sample` 以及 Nacos 服务器均已准备完毕。Dubbo 服务消费方
只需初始化服务消费方 Maven 工程 `spring-cloud-dubbo-client-sample` 以及消费 Dubbo 服务。
#### 初始化 `spring-cloud-dubbo-client-sample` Maven 工程
与服务提供方 Maven 工程类,需添加相关 Maven 依赖:
```xml
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Alibaba dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Sample API -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dubbo-sample-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- Spring Cloud Nacos Service Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
```
与应用 `spring-cloud-dubbo-server-sample` 不同的是,当前应用依赖 `spring-boot-starter-web`,表明它属于 Web Servlet 应用。
> 以上完整的 Maven 依赖配置,请参考 `spring-cloud-dubbo-client-sample` [`pom.xml`](spring-cloud-dubbo-client-sample/pom.xml) 文件
#### 配置 Dubbo 服务消费方
Dubbo 服务消费方配置与服务提供方类似,当前应用 `spring-cloud-dubbo-client-sample` 属于纯服务消费方,因此,所需的外部化配置更精简:
```yaml
dubbo:
registry:
# 挂载到 Spring Cloud 注册中心
address: spring-cloud://localhost
cloud:
subscribed-services: spring-cloud-alibaba-dubbo-server
spring:
application:
# Dubbo 应用名称
name: spring-cloud-alibaba-dubbo-client
main:
# Spring Boot 2.1 需要设定
allow-bean-definition-overriding: true
cloud:
nacos:
# Nacos 服务发现与注册配置
discovery:
server-addr: 127.0.0.1:8848
```
对比应用 `spring-cloud-dubbo-server-sample`,除应用名称 `spring.application.name` 存在差异外,`spring-cloud-dubbo-client-sample`
新增了属性 `dubbo.cloud.subscribed-services` 的设置。并且该值为服务提供方应用 "spring-cloud-dubbo-server-sample"。
- `dubbo.cloud.subscribed-services` : 用于服务消费方订阅服务提供方的应用名称的列表,若需订阅多应用,使用 "," 分割。
不推荐使用默认值为 "*",它将订阅所有应用。
> 当应用使用属性 `dubbo.cloud.subscribed-services` 默认值时,日志中将会输出一行警告:
> > Current application will subscribe all services(size:x) in registry, a lot of memory and CPU cycles may be used,
> > thus it's strongly recommend you using the externalized property 'dubbo.cloud.subscribed-services' to specify the services
由于当前应用属于 Web 应用,它会默认地使用 8080 作为 Web 服务端口,如果需要自定义,可通过属性 `server.port` 调整。
> 以上完整的 YAML 配置文件,请参考 `spring-cloud-dubbo-client-sample` [`bootstrap.yaml`](spring-cloud-dubbo-client-sample/src/main/resources/bootstrap.yaml) 文件
#### 引导 Dubbo Spring Cloud 服务消费方应用
为了减少实现步骤,以下引导类将 Dubbo 服务消费以及引导功能合二为一:
```java
@EnableDiscoveryClient
@EnableAutoConfiguration
@RestController
public class DubboSpringCloudClientBootstrap {
@Reference
private EchoService echoService;
@GetMapping("/echo")
public String echo(String message) {
return echoService.echo(message);
}
public static void main(String[] args) {
SpringApplication.run(DubboSpringCloudClientBootstrap.class);
}
}
```
不仅如此,`DubboSpringCloudClientBootstrap` 也作为 REST Endpoint,通过暴露 `/echo` Web 服务,消费 Dubbo `EchoService` 服务。因此,
可通过 `curl` 命令执行 HTTP GET 方法:
```
$ curl http://127.0.0.1:8080/echo?message=%E5%B0%8F%E9%A9%AC%E5%93%A5%EF%BC%88mercyblitz%EF%BC%89
```
HTTP 响应为:
```
[echo] Hello, 小马哥(mercyblitz)
```
以上结果说明应用 `spring-cloud-dubbo-client-sample` 通过消费 Dubbo 服务,返回服务提供方 `spring-cloud-dubbo-server-sample` 运算后的内容。
## 高阶示例
如果您需要进一步了解 Dubbo Spring Cloud 使用细节,可参考官方 Samples:https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples
其子模块说明如下:
- spring-cloud-dubbo-sample-api:API 模块,存放 Dubbo 服务接口和模型定义
- spring-cloud-dubbo-provider-web-sample:Dubbo Spring Cloud 服务提供方示例(Web 应用)
- spring-cloud-dubbo-provider-sample:Dubbo Spring Cloud 服务提供方示例(非 Web 应用)
- spring-cloud-dubbo-consumer-sample:Dubbo Spring Cloud 服务消费方示例
- spring-cloud-dubbo-servlet-gateway-sample:Dubbo Spring Cloud Servlet 网关简易实现示例
## 问题反馈
如果您在使用 Dubbo Spring Cloud 的过程中遇到任何问题,请内容反馈至 https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues
## 进阶阅读
关于更多的 Dubbo Spring Cloud 特性以及设计细节,请关注
- Spring Cloud Alibaba wiki - https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki
- Dubbo 的博客:http://dubbo.apache.org/zh-cn/blog/index.html
## 下篇预告
接下的文章将会介绍 Dubbo Spring Cloud 高阶示例的运用和实现,敬请关注小马哥微信公众号:`次灵均阁`
[](https://camo.githubusercontent.com/36671f72e171f0c95f33f781f294b3e6bdceef07/68747470733a2f2f6d65726379626c69747a2e6769746875622e696f2f626f6f6b732f7468696e6b696e672d696e2d737072696e672d626f6f742f6173736574732f6d795f6d705f7172636f64652e6a7067)
获得最新 Dubbo Spring Cloud 相关资讯。
## [新书推荐](https://item.jd.com/12570242.html)
本书全名为[《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/),是以 Spring Boot 2.0 为讨论的主线,讨论的范围将涵盖 Spring Boot 1.x 的所有版本,以及所关联的 Spring Framework 版本,致力于:
- 场景分析:掌握技术选型
- 系统学习:拒绝浅尝辄止
- 重视规范:了解发展趋势
- 源码解读:理解设计思想
- 实战演练:巩固学习成果

> 欢迎小伙伴在京东或当当订购
京东存有少量现货(随机发送签名版),可先睹为快:http://t.cn/ExjBU2M
当当价格优惠,需要五月初发货:http://t.cn/EX0QteF
## 分享推荐
### 免费分享
- [「小马哥技术周报」](https://www.douyu.com/mercyblitz)
- [斗鱼直播](https://www.douyu.com/mercyblitz)
- [B 站录播](http://space.bilibili.com/327910845/channel/detail?cid=52311)
- [「慕课网」](https://www.imooc.com/t/5387391)
- [Spring Boot 2.0深度实践-初遇Spring Boot](https://www.imooc.com/learn/933)
- [Spring Boot 2.0深度实践之系列总览](https://www.imooc.com/learn/1058)
- [「SegmentFault」](https://segmentfault.com/u/mercyblitz)
- [「小马哥 2019 跨年直播」一入 Java 深似海,从此“劝退”成必然](https://mp.weixin.qq.com/s?__biz=MzIxNDU4NjE1OQ==&mid=2247484085&idx=1&sn=5905f53e69bae9d48b3783a83bde40b3)
### 收费分享
- [「小马哥 Java 知识星球」](http://t.cn/RnxUYzd)
> 深入探讨 Java 相关技术,包括行业动态,架构设计,设计模式,框架使用,源码分析等。
- SegmentFault 直播
- [《Java 微服务实践 - Spring Boot / Spring Cloud》](http://t.cn/RoC0nNi)
- [《一入 Java 深似海》](http://t.cn/E6bTa9O)
- 慕课视频
- [《Spring Boot 2.0深度实践之核心技术篇》](http://t.cn/ReChCU9)
- 慕课网
- [Spring Boot 2.0深度实践之核心技术篇](https://coding.imooc.com/class/252.html)
- Spring Boot 2.0深度实践之生态整合篇(即将上线...)
## 关于作者
小马哥,Java 劝退师,Apache 和 Spring Cloud 等知名开源架构成员,[点击查看详情](https://mercyblitz.github.io/about/)。
---
[^1]: Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks, leadership election, distributed sessions, cluster state). - https://spring.io/projects/spring-cloud
[^2]: Apache Dubbo (incubating) |ˈdʌbəʊ| is a high-performance, light weight, java based RPC framework. - https://dubbo.apache.org
[^3]: Spring Cloud Alibaba provides a one-stop solution for distributed application development. - https://github.com/spring-cloud-incubator/spring-cloud-alibaba
[^4]: Nacos is an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications - https://nacos.io/
[^5]: Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. - https://github.com/Netflix/eureka
[^6]: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. - https://zookeeper.apache.org
[^7]: Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud - https://www.consul.io/
[^8]: Spring Cloud Open Feign - https://github.com/spring-cloud/spring-cloud-openfeign
[^9]: 从 2.7.0 开始,Dubbo Spring Boot 与 Dubbo 在版本上保持一致
[^10]: Preview releases of Spring Cloud Alibaba are available: 0.9.0, 0.2.2, and 0.1.2 - <https://spring.io/blog/2019/04/19/preview-releases-of-spring-cloud-alibaba-are-available-0-9-0-0-2-2-and-0-1-2>
[^11]: 目前最新的 Spring Cloud “F” 版的版本为:`Finchley.SR2` - <https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html>
[^12]: 当前Spring Cloud “G” 版为 `Greenwich.RELEASE`
[^13]: Spring Cloud 特性列表 - <https://cloud.spring.io/spring-cloud-static/Greenwich.RELEASE/single/spring-cloud.html#_features>
[^14]: Dubbo 2.7 开始支持配置中心,可自定义适配 - <http://dubbo.apache.org/zh-cn/docs/user/configuration/config-center.html>
[^15]: Spring Cloud 原生注册中心,除 Eureka、Zookeeper、Consul 之外,还包括 Spring Cloud Alibaba 中的 Nacos
[^16]: Dubbo 原生注册中心 - <http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html>
[^17]: Alibaba Sentinel:Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性 - <https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D>,目前 Sentinel 已被 Spring Cloud 项目纳为 Circuit Breaker 的候选实现 - <https://spring.io/blog/2019/04/16/introducing-spring-cloud-circuit-breaker>
[^18]: Spring Cloud Sleuth - <https://spring.io/projects/spring-cloud-sleuth>
[^19]: Zipkin - <https://github.com/apache/incubator-zipkin>
================================================
FILE: _posts/2019-06-18-Service Mesh 时代,Dubbo 架构该怎么跟进?.md
================================================
> 原文链接:[Service Mesh 时代,Dubbo 架构该怎么跟进?](https://mp.weixin.qq.com/s/-p06WsPQK7EmFpmUABluRA),来自于微信公众号:`次灵均阁`
导读:6月21-23日,2019 GIAC全球互联网架构大会将于深圳举行。GIAC是面向架构师、技术负责人及高端技术从业人员的年度技术架构大会,是中国地区规模最大的技术会议之一。今年GIAC邀请到了众多布道师、明星讲师以及105位来自Google、微软、Oracle、eBay、百度、阿里、腾讯、商汤、图森、字节跳动、新浪、美团点评等公司专家出席。
在大会前夕,高可用架构采访了本届 GIAC Java分论坛讲师[小马哥](https://mercyblitz.github.io/about/),就目前大家广泛关注的Dubbo/微服务相关的问题进行了访谈。
#### 作为 Duboo 核心开发者,请先简单介绍下自己
答:大家好,我是小马哥(mercyblitz),一名学习当爸爸的父亲,[Java 劝退师](https://www.douyu.com/mercyblitz),[Apache Dubbo](https://dubbo.apache.org/) PMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)项目架构师,《Spring Boot 编程思想》的作者。目前主要负责集团中间件开源项目、微服务技术实施、架构衍进、基础设施构建等。
#### Spring Cloud 和 Duboo 在微服务方面的优劣分别是什么?
答:在 Java 生态中,Spring Cloud 和 Dubbo 都是微服务框架。前者被业界常作为 Java 微服务的首选框架,而后者有时被错误地解读为服务治理的 RPC 框架。实际上,两者在微服务架构中并没有本质的差异,均是分布式应用服务治理的框架。
在开发体验方面,Spring Cloud 开箱即用的组件让人印象深刻。在 API 抽象和设计方面,流淌着 Spring 家族血液的 Spring Cloud 延续了父辈的荣耀。由此观之,Dubbo 与其存在差距。
然而随着实践的不断深入,Spring Cloud 功能的稳定性以及版本的兼容性等问题较为突出。当应用集群达到一定规模时,其分布式经验上的短板也随之暴露,尤其是 Spring Cloud Netflix 套件,比如 Eureka 与 Ribbon 之间的 90 秒延迟会影响服务调用的成功率,以及负载均衡算法缺少权重无法帮助 JVM 预热。简言之,在服务治理方面,Spring Cloud 相较于 Dubbo 而言,并不算太成熟。如果大家有兴趣了解更多的话,可参考[「小马哥技术周报」](https://github.com/mercyblitz/tech-weekly)。
总之,Spring Cloud 和 Dubbo 各有特色,过度地关注彼此优劣并不可取。为此,Spring Cloud Alibaba 项目综合两家之长,提供了一套名为 Dubbo Spring Cloud 的整容实现,使得 Dubbo 与 Spring Cloud 不再是互斥性选项。
#### 请介绍下 Duboo 的现状?
答:2019年5月16日,Apache 软件基金会董事会决议通过了 Apache Dubbo 的毕业申请,这意味着 Apache Dubbo 正式成为 Apache 的顶级项目。Apache Dubbo 项目在 Github 上的 star 数已超过 2.7 万,contributors 人数达到 202,Commiters 人数也升至 32 人,借此机会感谢所有关系和参与 Apache Dubbo 建设的小伙伴。目前,项目主要包含三大核心的分支,均在并行开发。其中,2.6.x 处于维护状态;2.7.x 聚焦云原生微服务方向,3.0.x 则指定未来标准和技术走向。简言之,Dubbo 不再是纯粹的 Java 服务治理 RPC 框架,已经逐渐成为多语种 Cloud Native 基础设施的中坚力量。
#### Duboo在成为Apache顶级项目的过程中,背后有哪些不为人知的故事?
答:Dubbo 在 Apache 从孵化到毕业,期间的确有太多不为人知的故事,这里我简单地介绍一下其中孵化过程:
- 筹备期(2017.12-2018.2):最主要的工作是准备进入孵化器相关的材料,比如寻找合适的导师,编写加入孵化器的提案等。
- 初始期(2018.2-2018.5):主要完成的工作主要是完成知识产权的清理,邮件列表的创建,代码迁移等工作。
- 首次 Release:Apache孵化项目第一个重要的里程碑,第一次Release非常关键,除了确保功能的稳定以外,最重要的就是需要确保引入的代码的许可证符合Apache的政策,Apache对于许可证有着明确的规定。
- 社区发展(Community Building):也是作为一个Apache项目非常看重的一环。最不愿意看到的就是一家公司独大,控制整个项目,对于Dubbo来说,经过这么多年的发展,在国内已经具备了一定的渗透率,有了不少用户,但是他们就像花粉一样散落在各个角落里面,需要做的事情就是把他们都聚集起来。
#### 在未来一年,Duboo的新特性路线图可以简单介绍下吗?
答:由于 Dubbo 2.6.x 处于维护状态,不会新增明显的功能特性。
本年度主要的发力点在 Dubbo 2.7 这个版本上,该版本致力于 Cloud Native 以及微服务领域,大致的路线计划为
- 2.7.2 - Metrics、etcd 元数据、nacos 配置与元数据以及 2.6 兼容
- 2.7.3 - Cloud Native 注册机制、服务自省以及 Dubbo Proxy
- 2.7.4 - K8s 原生支持(服务发现、元数据存储和配置推送)、Dubbo GO 以及 gRPC 集成
- 2.7.5 - 服务治理规则支持 Pilot CRD
- 2.7.6 - 控制面 xDS API 对接
Dubbo 3.0.0 M1 版本的核心特性围绕在 RSocket、Reactive 以及异步化上的支持。 M2 版本计划将在 8 月发布,主要提供对 HTTP/2 的支持和性能调优。接下来的 M3 版本将通过 HTTP/2 支持 gPRC 以及 Rocket 通讯协议。3.0.0 正式的发布将安排在 2020 年的 2 月。
#### Duboo 开源以来,代码贡献者中阿里本身的开发者占绝大多数,这是否意味着来自阿里的需求会起主导作用?在后续的发展过程中,计划如何引入阿里之外的开发者?
答:尽管目前 Apache Duboo 代码贡献者主要来自于阿里的开发人员,不过这个比重正在迅速地变小,一方面说明 Dubbo 用户人数在逐渐变大,专业程度在不断地变深,同时,也说明有意主导并且贡献的小伙伴越来越活跃。这无论对社区的发展,还是从业人员的职业技能均有裨益。因此,需求的来源不再已阿里为绝对主导,社区共建和共制的发展模式已成事实。
#### Service Mesh 时代,需要什么样的微服务框架?
答:哈哈,需要 Dubbo 这样的框架(玩笑)。首先,个人并不是 Service Mesh 方面的专家,就目前所得到掌握的信息,Service Mesh 并不算成熟的技术,换言之,目前还不是 Service Mesh 的时代,甚至我听到不少的朋友由于性能和稳定性方面的原因,从该架构中退化,这也是 Dubbo 在 Service Mesh 方面的衍进相对缓慢的原因之一。当然,技术的发展总会是在掌声伴随着嘘声中前进,因此,个人对于 Service Mesh 的看法是谨慎乐观的。
#### 对于公司内部定制Dubbo而言,你有什么建议吗?
答:流水不腐户枢不蠹,我希望这些公司能够积极参与 Dubbo 社区的共建,或许这些定制化的场景也可以服务其他场景。大家互通有无,实现共同进步。
#### 对于初级开发者而言,学习Duboo应该如何入手?对于资深开发者而言,怎样研究Dubbo的源代码更加高效?
答:对于初级开发者,我的建议是首先从 Apache Dubbo 官网([https://dubbo.apache.org](http://dubbo.apache.org/))学习《用户文档》,初步了解了 Dubbo 架构和特性后,再结合 Dubbo 官方样例(https://github.com/apache/dubbo-samples)全面掌握 Dubbo 功能和最佳实践。最后,参考官方博客([http://dubbo.apache.org/zh-cn/blog/index.html](http://dubbo.apache.org/zh-cn/blog/index.html)),深度理解 Dubbo。
对于资深开发者,尤其是那些致力于贡献的小伙伴,我建议参考《开发者文档》,掌握 Dubbo 设计和实现,并且结合 Dubbo 的源码巩固学习,最好直接贡献代码(在 GitHub Pull Request),战胜心中一切的畏惧。如果仍不满足于此,强烈推荐参考 Apache Dubbo PMC 商宗海(花名:诣极)编写并即将出版的书籍 - 《深入理解 Apache Dubbo 与实战》,从中本人也受益匪浅,建议小伙们入手。
#### 你作为讲师参加GIAC,对本次GIAC大会有何寄语?
答:非常感谢 GIAC 的主办方给本人这次机会分享 Dubbo 相关的的议程,这也是我本人第二次在 GIAC 分享该主题了。我衷心地祝福 GIAC 影响力越做越大,希望能够走出国门,成为具有国际化影响力的技术组织,向世界传播技术和力量。
#### 作为Duboo的开发者,你最喜欢的Java(Java8以后)特性是什么?你最希望加入的Java特性是什么?
答:Java 8 是 Dubbo 2.7 默认的语言级别,其中 Lambda 表达式以及 Stream API 被广泛地使用。除此之外,本人同样偏好使用 CompletableFuture 作为并行编程的 API。我最希望 Java 增加 JVM 级别的协程支持。
#### 简单介绍下你自己的从业经历?
答:今年是我从业的第十二个年头,这些年一直在从事 Java 研发。首个雇主是一家外企公司,为其服务了三年。外企的工作相对轻松,拥有充分的自主时间提升技能,同时也有机会提升英语水平。期间通过了 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的认证,尽管这些证书并没有受到国内雇主的重视,不过对我后续的职业产生了深远的影响。当然,事情并不是总是积极正面,东西方文化差异,以及部分外籍同事的傲慢与偏见着实让本人对西方的技术和文化重新开始审视。既然无法改变,那么离开并继续深造或许是必然的选择,希望有一天能够通过共同的努力,让世界看到中华的进步。于是,我的第一份工作就在 2010 年 10 月 1 号画上了句点。迎接我的是第二份工作,至今也快九个年头。这几年,我经历了很多、学到了很多,也成长了很多,岂能尽如人意,但求无愧我心。不可否认的是,儒家思想对我的影响最为深刻,它让我学会独立、理性以及辩证的思考,培养我处变不惊的人生态度,直接或间接地提升了专业素质。经过数年的沉寂,我也明确了自己的方向,辗转投入开源社区的建设。不过,纵使浑身是铁能打几根钉,开源社区的发展需要更多的能人参与,知之者不如好之者,好之者不如乐之者。然而现实的情况又有些残酷,不少的年轻人在经济的压力下,逐渐失去对技术的追求。于是从 2016 年开始,我便尝试做一些技术分享,希望能够帮助到部分年轻从业人员,使他们对技术产生兴趣。随后,我又着手编写《Spring Boot 编程思想》,希望读者能够理解规范和基础的重要性,如果读者从中能够培养自己系统化的知识体系或者思维方式,那就善莫大焉了。我也时常鼓励更多的小伙伴多多分享,无论是免费,还是收费。同时,注重知识产权的保护,树立良好的生态环境。当然,我的第二份职业尚未告一段落,或许等它结束之际,方可“盖棺定论“。总之,但行好事,莫问前程。
#### [书籍推荐](https://item.jd.com/12570242.html)
-《Spring Boot 编程思想(核⼼心篇)》 https://item.jd.com/12570242.html
> 本书全名为[《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/),是以 Spring Boot 2.0 为讨论的主线,讨论的范围将涵盖 Spring Boot 1.x 的所有版本,以及所关联的 Spring Framework 版本,致力于:
> - 场景分析:掌握技术选型
> - 系统学习:拒绝浅尝辄止
> - 重视规范:了解发展趋势
> - 源码解读:理解设计思想
> - 实战演练:巩固学习成果
- [《Spring Cloud 微服务实战》](https://item.jd.com/12172344.html)
- [《深⼊入理理解Kafka:核⼼心设计与实践原理理》](https://item.jd.com/12489649.html)
- [《未来架构 从服务化到云原⽣生》](https://item.jd.com/12498217.html)
- [《⾼高可⽤用可伸缩微服务架构:基于Dubbo、Spring Cloud和Service Mesh》](https://item.jd.com/12585284.html)
- [《Kubernetes权威指南:从Docker到Kubernetes实践全接触》](https://item.jd.com/12601558.html)
- [《Java编程⽅方法论:响应式RxJava与代码设计实战》](https://item.jd.com/12615848.html)
================================================
FILE: _posts/2019-09-05-2019 Java 趋势报告 InfoQ 采访稿(小马哥部分).md
================================================
### 趋势报告框架
#### 第一部分:Java的技术采用生命周期
这部分采用与英文站同样的标准划分:
- 创新者
- 早期采用者
- 早期大众
- 晚期大众
技术采用生命周期是美国高科技营销大师杰弗里·摩尔在自己的书《跨越鸿沟》里提出的概念。技术采用生命周期是一个用来衡量用户对某项新技术接受程度的模型,它认为一个新的技术,从一开始出现到最后走向成熟,必然会经历创新者、早期采用者、早期大众、晚期大众的阶段。
虽然每个人群间都会有裂缝,但是早期采用者和早期大众之间的那条裂缝最大,这条裂缝就是传说中的“鸿沟”,只有跨越过这条鸿沟,渗透到早期大众这个人群,产品才等于是进入了主流市场。
希望您结合国内使用和发展情况,把以下技术对应到技术采用生命周期相应的不同阶段中:
- Java/JVM
- Java版本(8~13);
-
gitextract_z0v7hnz7/ ├── .gitignore ├── 404.html ├── Gruntfile.js ├── LICENSE ├── README.md ├── README.zh.md ├── _config.yml ├── _includes/ │ ├── about/ │ │ ├── en.md │ │ └── zh.md │ ├── featured-tags.html │ ├── footer.html │ ├── friends.html │ ├── head.html │ ├── intro-header.html │ ├── mathjax_support.html │ ├── nav.html │ ├── short-about.html │ └── sns-links.html ├── _layouts/ │ ├── default.html │ ├── keynote.html │ ├── page.html │ └── post.html ├── _posts/ │ ├── 2018-01-01-Dubbo 注解驱动.md │ ├── 2018-01-18-Dubbo 外部化配置.md │ ├── 2018-05-26-Spring-Boot Web 应用加速.md │ ├── 2018-06-28-Dubbo Cloud Native 实践与思考.md │ ├── 2018-07-25-Reactive Programming 一种技术 各自表述.md │ ├── 2019-03-05-《Java编程方法论 响应式之Rxjava篇》序.md │ ├── 2019-04-26-Dubbo Spring Cloud 重塑微服务治理.md │ ├── 2019-06-18-Service Mesh 时代,Dubbo 架构该怎么跟进?.md │ ├── 2019-09-05-2019 Java 趋势报告 InfoQ 采访稿(小马哥部分).md │ ├── 2020-05-11-Apache Dubbo 服务自省架构设计.md │ └── 2020-10-28-《深入理解 Spring Cloud 与实战》序.md ├── about.html ├── archive.html ├── books/ │ └── thinking-in-spring-boot/ │ ├── README.md │ ├── about.md │ ├── conventions.md │ ├── core/ │ │ └── preface.md │ ├── donate.md │ ├── overview.md │ ├── revision.md │ ├── samples.md │ ├── version.md │ └── videos.md ├── css/ │ ├── bootstrap.css │ └── hux-blog.css ├── feed.xml ├── index.html ├── js/ │ ├── archive.js │ ├── bootstrap.js │ ├── hux-blog.js │ ├── jquery.js │ ├── jquery.nav.js │ ├── jquery.tagcloud.js │ ├── snackbar.js │ └── sw-registration.js ├── less/ │ ├── highlight.less │ ├── hux-blog.less │ ├── mixins.less │ ├── side-catalog.less │ ├── sidebar.less │ ├── snackbar.less │ └── variables.less ├── my/ │ ├── books/ │ │ └── README.md │ ├── lessons/ │ │ └── README.md │ ├── open-sources/ │ │ └── README.md │ ├── presentations/ │ │ └── README.md │ └── training/ │ └── README.md ├── offline.html ├── package.json └── sw.js
SYMBOL INDEX (99 symbols across 6 files)
FILE: js/archive.js
function queryString (line 6) | function queryString() {
function init (line 54) | function init() {
function searchButtonsByTag (line 63) | function searchButtonsByTag(_tag/*raw tag*/) {
function buttonFocus (line 74) | function buttonFocus(target) {
function tagSelect (line 82) | function tagSelect (tag/*raw tag*/, target) {
FILE: js/bootstrap.js
function transitionEnd (line 34) | function transitionEnd() {
function removeElement (line 126) | function removeElement() {
function Plugin (line 142) | function Plugin(option) {
function Plugin (line 247) | function Plugin(option) {
function Plugin (line 466) | function Plugin(option) {
function getTargetFromTrigger (line 685) | function getTargetFromTrigger($trigger) {
function Plugin (line 697) | function Plugin(option) {
function clearMenus (line 829) | function clearMenus(e) {
function getParent (line 848) | function getParent($this) {
function Plugin (line 865) | function Plugin(option) {
function Plugin (line 1179) | function Plugin(option, _relatedTarget) {
function complete (line 1521) | function complete() {
function Plugin (line 1673) | function Plugin(option) {
function Plugin (line 1787) | function Plugin(option) {
function ScrollSpy (line 1830) | function ScrollSpy(element, options) {
function Plugin (line 1953) | function Plugin(option) {
function next (line 2060) | function next() {
function Plugin (line 2106) | function Plugin(option) {
function Plugin (line 2263) | function Plugin(option) {
FILE: js/jquery.js
function isArraylike (line 533) | function isArraylike( obj ) {
function Sizzle (line 745) | function Sizzle( selector, context, results, seed ) {
function createCache (line 859) | function createCache() {
function markFunction (line 877) | function markFunction( fn ) {
function assert (line 886) | function assert( fn ) {
function addHandle (line 908) | function addHandle( attrs, handler ) {
function siblingCheck (line 923) | function siblingCheck( a, b ) {
function createInputPseudo (line 950) | function createInputPseudo( type ) {
function createButtonPseudo (line 961) | function createButtonPseudo( type ) {
function createPositionalPseudo (line 972) | function createPositionalPseudo( fn ) {
function testContext (line 995) | function testContext( context ) {
function setFilters (line 2004) | function setFilters() {}
function toSelector (line 2075) | function toSelector( tokens ) {
function addCombinator (line 2085) | function addCombinator( matcher, combinator, base ) {
function elementMatcher (line 2138) | function elementMatcher( matchers ) {
function multipleContexts (line 2152) | function multipleContexts( selector, contexts, results ) {
function condense (line 2161) | function condense( unmatched, map, filter, context, xml ) {
function setMatcher (line 2182) | function setMatcher( preFilter, selector, matcher, postFilter, postFinde...
function matcherFromTokens (line 2275) | function matcherFromTokens( tokens ) {
function matcherFromGroupMatchers (line 2333) | function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
function winnow (line 2629) | function winnow( elements, qualifier, not ) {
function sibling (line 2953) | function sibling( cur, dir ) {
function createOptions (line 3031) | function createOptions( options ) {
function completed (line 3425) | function completed() {
function Data (line 3530) | function Data() {
function dataAttr (line 3721) | function dataAttr( elem, key, data ) {
function returnTrue (line 4061) | function returnTrue() {
function returnFalse (line 4065) | function returnFalse() {
function safeActiveElement (line 4069) | function safeActiveElement() {
function manipulationTarget (line 4941) | function manipulationTarget( elem, content ) {
function disableScript (line 4951) | function disableScript( elem ) {
function restoreScript (line 4955) | function restoreScript( elem ) {
function setGlobalEval (line 4968) | function setGlobalEval( elems, refElements ) {
function cloneCopyEvent (line 4979) | function cloneCopyEvent( src, dest ) {
function getAll (line 5013) | function getAll( context, tag ) {
function fixInput (line 5024) | function fixInput( src, dest ) {
function actualDisplay (line 5479) | function actualDisplay( name, doc ) {
function defaultDisplay (line 5501) | function defaultDisplay( nodeName ) {
function curCSS (line 5548) | function curCSS( elem, name, computed ) {
function addGetHookIf (line 5596) | function addGetHookIf( conditionFn, hookFn ) {
function computePixelPositionAndBoxSizingReliable (line 5636) | function computePixelPositionAndBoxSizingReliable() {
function vendorPropName (line 5741) | function vendorPropName( style, name ) {
function setPositiveNumber (line 5763) | function setPositiveNumber( elem, value, subtract ) {
function augmentWidthOrHeight (line 5771) | function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
function getWidthOrHeight (line 5810) | function getWidthOrHeight( elem, name, extra ) {
function showHide (line 5854) | function showHide( elements, show ) {
function Tween (line 6152) | function Tween( elem, options, prop, end, easing ) {
function createFxNow (line 6321) | function createFxNow() {
function genFx (line 6329) | function genFx( type, includeWidth ) {
function createTween (line 6349) | function createTween( value, prop, animation ) {
function defaultPrefilter (line 6363) | function defaultPrefilter( elem, props, opts ) {
function propFilter (line 6496) | function propFilter( props, specialEasing ) {
function Animation (line 6533) | function Animation( elem, properties, options ) {
function addToPrefiltersOrTransports (line 7581) | function addToPrefiltersOrTransports( structure ) {
function inspectPrefiltersOrTransports (line 7613) | function inspectPrefiltersOrTransports( structure, options, originalOpti...
function ajaxExtend (line 7640) | function ajaxExtend( target, src ) {
function ajaxHandleResponses (line 7660) | function ajaxHandleResponses( s, jqXHR, responses ) {
function ajaxConvert (line 7716) | function ajaxConvert( s, response, jqXHR, isSuccess ) {
function done (line 8174) | function done( status, nativeStatusText, responses, headers ) {
function buildParams (line 8418) | function buildParams( prefix, obj, traditional, add ) {
function getWindow (line 8912) | function getWindow( elem ) {
FILE: js/jquery.tagcloud.js
function toRGB (line 38) | function toRGB (code) {
function toHex (line 47) | function toHex (ary) {
function colorIncrement (line 55) | function colorIncrement (color, range) {
function tagColor (line 61) | function tagColor (color, increment, weighting) {
function compareWeights (line 76) | function compareWeights(a, b)
FILE: js/sw-registration.js
function handleRegistration (line 11) | function handleRegistration(registration){
FILE: sw.js
constant CACHE_NAMESPACE (line 12) | const CACHE_NAMESPACE = 'main-'
constant CACHE (line 14) | const CACHE = CACHE_NAMESPACE + 'precache-then-runtime';
constant PRECACHE_LIST (line 15) | const PRECACHE_LIST = [
constant HOSTNAME_WHITELIST (line 32) | const HOSTNAME_WHITELIST = [
constant DEPRECATED_CACHES (line 38) | const DEPRECATED_CACHES = ['precache-v1', 'runtime', 'main-precache-v1',...
function sendMessageToAllClients (line 224) | function sendMessageToAllClients(msg) {
function sendMessageToClientsAsync (line 236) | function sendMessageToClientsAsync(msg) {
function revalidateContent (line 253) | function revalidateContent(cachedResp, fetchedResp) {
Condensed preview — 72 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,001K chars).
[
{
"path": ".gitignore",
"chars": 17,
"preview": "_site\n.DS_Store\n\n"
},
{
"path": "404.html",
"chars": 278,
"preview": "---\nlayout: default\ntitle: 404\nhide-in-nav: true\ndescription: \"这里什么都没有 :(\"\nheader-img: \"img/404-bg.jpg\"\npermalink: /404."
},
{
"path": "Gruntfile.js",
"chars": 2131,
"preview": "module.exports = function(grunt) {\n\n // Project configuration.\n grunt.initConfig({\n pkg: grunt.file.readJSO"
},
{
"path": "LICENSE",
"chars": 11937,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 662,
"preview": "# 小马哥的技术博客 [https://mercyblitz.github.io/](https://mercyblitz.github.io/)\n\n欢迎来到小马哥的技术博客,这里将深入探讨相关技术,包括行业动态,架构设计,设计模式,框架使"
},
{
"path": "README.zh.md",
"chars": 6531,
"preview": "# Hux blog 模板\n\n### [我的博客在这里 →](http://huxpro.github.io)\n\n\n### 关于收到\"Page Build Warning\"的email\n\n由于jekyll升级到3.0.x,对原来的"
},
{
"path": "_config.yml",
"chars": 2681,
"preview": "# Site settings\ntitle: Mercy Ma\nSEOTitle: 小马哥的技术博客\nheader-img: img/home-bg.jpg\nemail: mercyblitz@gmail.com\ndescription: "
},
{
"path": "_includes/about/en.md",
"chars": 1894,
"preview": "\n> Hard Code in Internet,Think Big Data too much\n\nHi, I'm Mercy Ma, as a member in [Apache Dubbo](https://dubbo.apache.o"
},
{
"path": "_includes/about/zh.md",
"chars": 960,
"preview": "> 手淫互联网,意淫大数据\n\n大家好,我是小马哥(mercyblitz),[Java 劝退师](https://www.douyu.com/mercyblitz),[Apache Dubbo](https://dubbo.apache.or"
},
{
"path": "_includes/featured-tags.html",
"chars": 1132,
"preview": "{% comment %}\n @param {boolean} bottom - bottom will render <hr> \n{% endcomment %}\n\n{% if site.featured-tags %}\n<sect"
},
{
"path": "_includes/footer.html",
"chars": 7482,
"preview": "<!-- Footer -->\n<footer>\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-lg-8 col-lg-o"
},
{
"path": "_includes/friends.html",
"chars": 194,
"preview": "{% if site.friends %}\n<hr>\n<h5>FRIENDS</h5>\n<ul class=\"list-inline\">\n {% for friend in site.friends %}\n <li><a href=\"{"
},
{
"path": "_includes/head.html",
"chars": 3116,
"preview": "<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"google-site-"
},
{
"path": "_includes/intro-header.html",
"chars": 3999,
"preview": "{% comment %}\n @param {string} type - 'page' | 'post' | 'keynote'\n @param {boolean} short\n{% endcomment %}\n\n{% if "
},
{
"path": "_includes/mathjax_support.html",
"chars": 448,
"preview": "<script type=\"text/x-mathjax-config\">\n MathJax.Hub.Config({\n TeX: {\n equationNumbers: {\n autoNumber: \"AM"
},
{
"path": "_includes/nav.html",
"chars": 3733,
"preview": "<!-- Navigation -->\n{% if page.nav-style == \"invert\" or page.header-style == \"text\" %}\n<nav class=\"navbar navbar-default"
},
{
"path": "_includes/short-about.html",
"chars": 419,
"preview": "<section class=\"visible-md visible-lg\">\n <hr>\n <h5><a href=\"{{'/about/' | prepend: site.baseurl }}\">ABOUT ME</a></h5>\n"
},
{
"path": "_includes/sns-links.html",
"chars": 2293,
"preview": "{% comment %}\n @param {Boolean} center \n{% endcomment %}\n\n{% if include.center %}\n<ul class=\"list-inline text-center\""
},
{
"path": "_layouts/default.html",
"chars": 387,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n{% include head.html %}\n\n<!-- hack iOS CSS :active style -->\n<body ontouchstart=\"\">\n\n "
},
{
"path": "_layouts/keynote.html",
"chars": 4729,
"preview": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!"
},
{
"path": "_layouts/page.html",
"chars": 2298,
"preview": "---\nlayout: default\n---\n\n<!-- Page Header -->\n{% include intro-header.html type='page' %}\n\n<!-- Main Content -->\n<div cl"
},
{
"path": "_layouts/post.html",
"chars": 5861,
"preview": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!"
},
{
"path": "_posts/2018-01-01-Dubbo 注解驱动.md",
"chars": 18569,
"preview": "# Dubbo 注解驱动(Annotation-Driven)\n\n\n\n\n## 注解驱动(Annotation-Driven)\n\n\n\n\n\n### `@DubboComponentScan`\n\n\n\n\n\n#### 起始版本: `2.5.7`\n\n\n"
},
{
"path": "_posts/2018-01-18-Dubbo 外部化配置.md",
"chars": 17897,
"preview": "# Dubbo 外部化配置(Externalized Configuration)\n\n\n\n\n## 外部化配置(External Configuration)\n\n\n\n在[Dubbo 注解驱动](Dubbo-Annotation-Driven."
},
{
"path": "_posts/2018-05-26-Spring-Boot Web 应用加速.md",
"chars": 7237,
"preview": "# Spring Boot Web 应用加速\n\n\n\n默认情况下,Spring Boot Web 应用会装配一些功能组件 Bean。\n\n\n在大多数 Web 应用场景下,可以选择性地关闭一下自动装配的Spring 组件 Bean,以达到提升性能"
},
{
"path": "_posts/2018-06-28-Dubbo Cloud Native 实践与思考.md",
"chars": 29553,
"preview": "# Dubbo Cloud Native 实践与思考\n\n<!-- TOC -->\n\n- [Dubbo Cloud Native 实践与思考](#dubbo-cloud-native-%E5%AE%9E%E8%B7%B5%E4%B8%8E%E"
},
{
"path": "_posts/2018-07-25-Reactive Programming 一种技术 各自表述.md",
"chars": 24706,
"preview": "# Reactive Programming 一种技术,各自表述\n\n\n\n## 前言\n\n作为一名 Java 开发人员,尤其是 Java 服务端工程师,对于 Reactive Programming 的概念似乎相对陌生。随着 Java 9 以及"
},
{
"path": "_posts/2019-03-05-《Java编程方法论 响应式之Rxjava篇》序.md",
"chars": 7199,
"preview": "# 《Java编程方法论 响应式之Rxjava篇》序\n\n在《2019 一月的InfoQ 架构和设计趋势报告》[^1]中,响应式编程(Reactive Programming)和函数式(Functional Programing)仍旧编列在第"
},
{
"path": "_posts/2019-04-26-Dubbo Spring Cloud 重塑微服务治理.md",
"chars": 21144,
"preview": "> 原文链接:[Dubbo Spring Cloud 重塑微服务治理](https://mp.weixin.qq.com/s/K60e1VkAWjwQXftmHw74NQ),来自于微信公众号:`次灵均阁`\n\n## 摘要\n\n在 Java 微服"
},
{
"path": "_posts/2019-06-18-Service Mesh 时代,Dubbo 架构该怎么跟进?.md",
"chars": 6017,
"preview": "> 原文链接:[Service Mesh 时代,Dubbo 架构该怎么跟进?](https://mp.weixin.qq.com/s/-p06WsPQK7EmFpmUABluRA),来自于微信公众号:`次灵均阁`\n\n\n\n导读:6月21-23"
},
{
"path": "_posts/2019-09-05-2019 Java 趋势报告 InfoQ 采访稿(小马哥部分).md",
"chars": 8389,
"preview": "### 趋势报告框架\n\n#### 第一部分:Java的技术采用生命周期\n\n这部分采用与英文站同样的标准划分:\n\n- 创新者\n\n- 早期采用者\n\n- 早期大众\n\n- 晚期大众\n\n技术采用生命周期是美国高科技营销大师杰弗里·摩尔在自己的书《跨越"
},
{
"path": "_posts/2020-05-11-Apache Dubbo 服务自省架构设计.md",
"chars": 24695,
"preview": "# 背景\n\n随着微服务架构的推广和普及,服务之间的耦合度在逐步降低。在演化的过程中,伴随着应用组织架构的变化以及基础设施的衍进,服务和应用之间的边界变得更为模糊。Java 作为一门面向对象的编程语言,Java 接口(interface)作为"
},
{
"path": "_posts/2020-10-28-《深入理解 Spring Cloud 与实战》序.md",
"chars": 1721,
"preview": "# 《深入理解 Spring Cloud 与实战》序\n\n2017 年,我与作者在国内开源社区相识,那时给人的印象是,他是一名乐于分享并且积极上进的青年。后来,得知他入职阿里巴巴中间件部门,并一同参与了 Spring Cloud 与阿里巴巴以"
},
{
"path": "about.html",
"chars": 1493,
"preview": "---\nlayout: page\ntitle: \"About\"\ndescription: \"关于我\"\nheader-img: \"img/about-bg.jpg\"\nmultilingual: true\n---\n\n<!-- Language "
},
{
"path": "archive.html",
"chars": 2504,
"preview": "---\ntitle: Archive\nlayout: default\ndescription: 归档文章\nheader-img: \"img/tag-bg.jpg\"\n---\n\n<!-- \nCredits: this page shameles"
},
{
"path": "books/thinking-in-spring-boot/README.md",
"chars": 1843,
"preview": "# 《Spring Boot 编程思想》\n\n> 谨以此书纪念已故外婆 - 解厚群\n\n本书全名为《Spring Boot 编程思想》,是以 Spring Boot 2.0 为讨论的主线,讨论的范围将涵盖 Spring Boot 1.x 的所有"
},
{
"path": "books/thinking-in-spring-boot/about.md",
"chars": 1325,
"preview": "# 关于我\n\n“我是谁?”,是个不错的哲学问题。\n\n在江湖上,大家亲切地称我 “小马哥“,我做公益,也做生意;在社区中,我又以 `mercyblitz` 的身份出没在众多开源项目,\"mercy\" 符合我的性格,\"blitz\" 说明我的风格。"
},
{
"path": "books/thinking-in-spring-boot/conventions.md",
"chars": 6511,
"preview": "# 《Spring Boot 编程思想》 - 相关约定\n\n\n本书在议题的讨论中,将在文档引用、示例代码、日志输出、源码版本、源码省略等方面做出约定。\n\n\n\n\n## 文档引用约定\n\n在文档引用方面,Spring Boot 官方文档的默认版本为"
},
{
"path": "books/thinking-in-spring-boot/core/preface.md",
"chars": 3259,
"preview": "# 自序\n\n非常感谢您阅读本书,在成长道路上,我们从此不再孤单。\n\n大约在三年前,鄙人有幸参与全集团微服务架构的衍进以及基础设施的构建,期间痛苦和受益并存。二零一六年十二月,经朋友引荐,作为 “SFDC 2016 杭州开发者大会” 的嘉宾,"
},
{
"path": "books/thinking-in-spring-boot/donate.md",
"chars": 799,
"preview": "> 更新时间:2019-11-12\n\n## 稿费明细\n\n| 打款日期 | **结算册数** | 税前稿费 | 税后稿费 | 税金 |\n| ---------- | ------------ | -------- | -------"
},
{
"path": "books/thinking-in-spring-boot/overview.md",
"chars": 2694,
"preview": "# 《Spring Boot 编程思想》 - 内容总览\n\n由于本书所讨论的内容跨度广泛,功能特性鲜明,由《核心篇》、《运维篇》以及《Web 篇》三册分别讨论之。《核心篇》开篇总览 Spring Boot 核心特性,逐一讨论 [Spring "
},
{
"path": "books/thinking-in-spring-boot/revision.md",
"chars": 653,
"preview": "# 《Spring Boot 编程思想》 - 勘误汇总\n\n如果您在阅读《Spring Boot 编程思想 - 核心篇》或示例练习的过程中发现了其中错误,请将错误内容提交至[【勘误汇】](https://github.com/mercybli"
},
{
"path": "books/thinking-in-spring-boot/samples.md",
"chars": 13406,
"preview": "# 《Spring Boot 编程思想》- 示例工程\n\n《Spring Boot 编程思想》所有的示例代码均存放在 GitHub 工程 [https://github.com/mercyblitz/thinking-in-spring-bo"
},
{
"path": "books/thinking-in-spring-boot/version.md",
"chars": 2323,
"preview": "# 《Spring Boot 编程思想》 - 版本范围\n\n为了系统性地讨论 Spring Boot 的发展脉络,会将 Spring Boot 2.0 与 1.x 的版本加以对比,探索从 1.0 到 2.0 版本之间的重要变化,便于读者后续架"
},
{
"path": "books/thinking-in-spring-boot/videos.md",
"chars": 769,
"preview": "# 《Spring Boot 编程思想》 - 配套视频\n\n尽管本书巨细靡遗地讨论 Spring Boot 以及 Spring Framework 相关特性,不过它并非快速上手或使用教程,如果读者具备三年以上的开发经验或者资深的 Spring"
},
{
"path": "css/bootstrap.css",
"chars": 141414,
"preview": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://gi"
},
{
"path": "css/hux-blog.css",
"chars": 36498,
"preview": "/*!\n * Hux Blog v1.7.0 (http://huxpro.github.io)\n * Copyright 2018 Hux <huxpro@gmail.com>\n */\n\n@media (min-width: 1200px"
},
{
"path": "feed.xml",
"chars": 1292,
"preview": "---\nlayout: null\n---\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">"
},
{
"path": "index.html",
"chars": 1406,
"preview": "---\nlayout: page\ntitle: 小马哥的技术博客\nauthor: mercyblitz\ndescription: \"这里将深入探讨相关技术,包括行业动态,架构设计,设计模式,框架使用,源码分析等。 :)\"\n---\n\n{% f"
},
{
"path": "js/archive.js",
"chars": 4197,
"preview": "/*\nCredits: this script is shamelessly borrowed from\nhttps://github.com/kitian616/jekyll-TeXt-theme\n*/\n(function() {\n f"
},
{
"path": "js/bootstrap.js",
"chars": 66732,
"preview": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://gi"
},
{
"path": "js/hux-blog.js",
"chars": 2937,
"preview": "/*!\n * Clean Blog v1.0.0 (http://startbootstrap.com)\n * Copyright 2015 Start Bootstrap\n * Licensed under Apache 2.0 (htt"
},
{
"path": "js/jquery.js",
"chars": 247387,
"preview": "/*!\n * jQuery JavaScript Library v2.1.3\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Cop"
},
{
"path": "js/jquery.nav.js",
"chars": 5175,
"preview": "/*\n * jQuery One Page Nav Plugin\n * http://github.com/davist11/jQuery-One-Page-Nav\n *\n * Copyright (c) 2010 Trevor Davis"
},
{
"path": "js/jquery.tagcloud.js",
"chars": 2145,
"preview": "(function($) {\n\n $.fn.tagcloud = function(options) {\n var opts = $.extend({}, $.fn.tagcloud.defaults, options);\n "
},
{
"path": "js/snackbar.js",
"chars": 1980,
"preview": "/**\n * SnackBar.js\n * \n * This small component is borrowed from \n * https://codepen.io/wibblymat/pen/avAjq\n */\n\n\nvar cre"
},
{
"path": "js/sw-registration.js",
"chars": 1962,
"preview": "/* ===========================================================\n * sw-registration.js\n * ================================"
},
{
"path": "less/highlight.less",
"chars": 3651,
"preview": "// Atom's One Dark theme for Jekyll\n// Credits: https://github.com/mgyongyosi/OneDarkJekyll/\n\n.highlight,\npre.highlight "
},
{
"path": "less/hux-blog.less",
"chars": 22375,
"preview": "@import \"variables.less\";\n@import \"mixins.less\";\n@import \"sidebar.less\";\n@import \"side-catalog.less\";\n@import \"snackbar."
},
{
"path": "less/mixins.less",
"chars": 1725,
"preview": "// Mixins\n\n.transition-all() {\n -webkit-transition: all 0.5s;\n -moz-transition: all 0.5s;\n transition: all 0.5s"
},
{
"path": "less/side-catalog.less",
"chars": 1755,
"preview": "// Directory Section\n\n.catalog-container{\n padding: 0px;\n}\n\n.side-catalog {\n &.fixed{\n position: fixed;\n "
},
{
"path": "less/sidebar.less",
"chars": 1121,
"preview": "@import \"variables.less\";\n\n// Sidebar Components\n\n// Large Screen\n@media (min-width: 1200px){\n .post-container, .side"
},
{
"path": "less/snackbar.less",
"chars": 4796,
"preview": "/*\n Please note this CSS is currently in prototype form. We'll implement a cleaned up version in Web Starter Kit.\n*/\n"
},
{
"path": "less/variables.less",
"chars": 166,
"preview": "// Variables\n\n@brand-primary: #0085A1;\n@gray-dark: lighten(black, 25%);\n@gray: gray;\n@gray-l: lighten(black, 75%);\n@whit"
},
{
"path": "my/books/README.md",
"chars": 362,
"preview": "# 书籍出版\n\n\n\n### [《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)\n- [《核心篇》](https://mercyb"
},
{
"path": "my/lessons/README.md",
"chars": 2076,
"preview": "# 线上课程\n\n\n\n### 公开课\n\n- 「[B 站](https://space.bilibili.com/327910845)」\n - [「小马哥 Java 分布式架构训练营」公开课](https://space.bilibili.c"
},
{
"path": "my/open-sources/README.md",
"chars": 1093,
"preview": "# 开源项目\n\n\n\nGithub:[https://github.com/mercyblitz](https://github.com/mercyblitz)\n\n\n\n- Spring Cloud\n - [Spring Cloud Alib"
},
{
"path": "my/presentations/README.md",
"chars": 1781,
"preview": "# 报告分享\n\n\n\n### 线上分享\n\n- 2020\n - [05.28 Apache Dubbo 服务自省架构设计与实现](https://www.bilibili.com/video/BV1wQ4y1P7TM)\n - [05.14 "
},
{
"path": "my/training/README.md",
"chars": 261,
"preview": "# 培训\n\n\n\n\n\n## 课程大纲\n\n\n\n\n\n## 合作企业\n\n\n\n### 金融企业\n\n- 花旗银行(上海、大连)\n\n- 招商银行(上海)\n- 宁波银行(总行)\n- 农商行(广州)\n- 平安金融(深圳)\n\n- 小雨点金融(重庆)\n\n\n\n##"
},
{
"path": "offline.html",
"chars": 281,
"preview": "---\nlayout: default\ntitle: Offline\nhide-in-nav: true\ndescription: \"阅读过的页面可以在离线时访问哦 ;)\"\nheader-img: \"img/404-bg.jpg\"\nperm"
},
{
"path": "package.json",
"chars": 1079,
"preview": "{\n \"name\": \"hux-blog\",\n \"title\": \"Hux Blog\",\n \"author\": \"Hux <huxpro@gmail.com>\",\n \"version\": \"1.7.0\",\n \""
},
{
"path": "sw.js",
"chars": 9780,
"preview": "/* ===========================================================\n * sw.js\n * ============================================="
}
]
About this extraction
This page contains the full source code of the mercyblitz/mercyblitz.github.io GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 72 files (813.8 KB), approximately 258.7k tokens, and a symbol index with 99 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.