<!–
/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
/* Author: Nicolas Hery – http://nicolashery.com */
/* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */
/* Source: https://github.com/nicolahery/markdownpad-github */
/* RESET
=============================================================================*/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
}
/* BODY
=============================================================================*/
body {
font-family: Helvetica, arial, freesans, clean, sans-serif;
font-size: 14px;
line-height: 1.6;
color: #333;
background-color: #fff;
margin: 0 auto;
}
body>–>*:first-child {
margin-top: 0 !important;
}
body>*:last-child {
margin-bottom: 0 !important;
}
/* BLOCKS
=============================================================================*/
p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}
/* HEADERS
=============================================================================*/
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
color: #000;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: #777;
font-size: 14px;
}
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}
/* LINKS
=============================================================================*/
a {
color: #4183C4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* LISTS
=============================================================================*/
ul, ol {
padding-left: 30px;
}
ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}
ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}
dl {
padding: 0;
}
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}
dl dt:first-child {
padding: 0;
}
dl dt>:first-child {
margin-top: 0px;
}
dl dt>:last-child {
margin-bottom: 0px;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd>:first-child {
margin-top: 0px;
}
dl dd>:last-child {
margin-bottom: 0px;
}
/* CODE
=============================================================================*/
pre, code, tt {
font-size: 12px;
font-family: Consolas, “Liberation Mono”, Courier, monospace;
}
code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}
pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}
pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: “Helvetica Neue”,Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}
/* QUOTES
=============================================================================*/
blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
blockquote>:first-child {
margin-top: 0px;
}
blockquote>:last-child {
margin-bottom: 0px;
}
/* HORIZONTAL RULES
=============================================================================*/
hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}
/* IMAGES
=============================================================================*/
img {
max-width: 100%
}
–>
<!–
.highlight { background: #ffffff; }
.highlight .c { color: #999988; font-style: italic } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { font-weight: bold } /* Keyword */
.highlight .o { font-weight: bold } /* Operator */
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #999999 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { font-weight: bold } /* Keyword.Constant */
.highlight .kd { font-weight: bold } /* Keyword.Declaration */
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
.highlight .kr { font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #009999 } /* Literal.Number */
.highlight .s { color: #d14 } /* Literal.String */
.highlight .na { color: #008080 } /* Name.Attribute */
.highlight .nb { color: #0086B3 } /* Name.Builtin */
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
.highlight .no { color: #008080 } /* Name.Constant */
.highlight .ni { color: #800080 } /* Name.Entity */
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
.highlight .nn { color: #555555 } /* Name.Namespace */
.highlight .nt { color: #000080 } /* Name.Tag */
.highlight .nv { color: #008080 } /* Name.Variable */
.highlight .ow { font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #009999 } /* Literal.Number.Float */
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
.highlight .sb { color: #d14 } /* Literal.String.Backtick */
.highlight .sc { color: #d14 } /* Literal.String.Char */
.highlight .sd { color: #d14 } /* Literal.String.Doc */
.highlight .s2 { color: #d14 } /* Literal.String.Double */
.highlight .se { color: #d14 } /* Literal.String.Escape */
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
.highlight .si { color: #d14 } /* Literal.String.Interpol */
.highlight .sx { color: #d14 } /* Literal.String.Other */
.highlight .sr { color: #009926 } /* Literal.String.Regex */
.highlight .s1 { color: #d14 } /* Literal.String.Single */
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #008080 } /* Name.Variable.Class */
.highlight .vg { color: #008080 } /* Name.Variable.Global */
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
.pl-c {
color: #969896;
}
.pl-c1,.pl-mdh,.pl-mm,.pl-mp,.pl-mr,.pl-s1 .pl-v,.pl-s3,.pl-sc,.pl-sv {
color: #0086b3;
}
.pl-e,.pl-en {
color: #795da3;
}
.pl-s1 .pl-s2,.pl-smi,.pl-smp,.pl-stj,.pl-vo,.pl-vpf {
color: #333;
}
.pl-ent {
color: #63a35c;
}
.pl-k,.pl-s,.pl-st {
color: #a71d5d;
}
.pl-pds,.pl-s1,.pl-s1 .pl-pse .pl-s2,.pl-sr,.pl-sr .pl-cce,.pl-sr .pl-sra,.pl-sr .pl-sre,.pl-src,.pl-v {
color: #df5000;
}
.pl-id {
color: #b52a1d;
}
.pl-ii {
background-color: #b52a1d;
color: #f8f8f8;
}
.pl-sr .pl-cce {
color: #63a35c;
font-weight: bold;
}
.pl-ml {
color: #693a17;
}
.pl-mh,.pl-mh .pl-en,.pl-ms {
color: #1d3e81;
font-weight: bold;
}
.pl-mq {
color: #008080;
}
.pl-mi {
color: #333;
font-style: italic;
}
.pl-mb {
color: #333;
font-weight: bold;
}
.pl-md,.pl-mdhf {
background-color: #ffecec;
color: #bd2c00;
}
.pl-mdht,.pl-mi1 {
background-color: #eaffea;
color: #55a532;
}
.pl-mdr {
color: #795da3;
font-weight: bold;
}
.pl-mo {
color: #1d3e81;
}
.task-list {
padding-left:10px;
margin-bottom:0;
}
.task-list li {
margin-left: 20px;
}
.task-list-item {
list-style-type:none;
padding-left:10px;
}
.task-list-item label {
font-weight:400;
}
.task-list-item.enabled label {
cursor:pointer;
}
.task-list-item+.task-list-item {
margin-top:3px;
}
.task-list-item-checkbox {
display:inline-block;
margin-left:-20px;
margin-right:3px;
vertical-align:1px;
}
–>
上下文
记的学英语的时候,总是不记的某个词是什么意思,然后就看不下去了,只能问周围的同学或者老师或者去查词典,他们的建议是通过上下文去推测这个词大概的意思,反正我那会上学时没理解,所以英文一直比较差。
现在英语水平也没提高多少,尽管有点领会。
后来慢慢理解了一些,因为有些词有很多种意思,放在某个场景下可能是一个意思,放在另外一个场景下又是其它的意思,这里不举例子了,上文有一定的相似度。
RPC客户端上下文
客户端由于是一个独立的环境,所以可以认为它处于一个属于自己的上下文中,与其它的端隔离。在上下文中可以指定一些公共的参数来提供给接口使用,比如:
RPC版本号
客户端请求ID
各类自定义的公共参数
RPC服务端上下文
服务端同理,也处于一个属于自己的上下文中,与其它端隔离。
RPC上下文的作用
基本线程级别的访问,让客户端或者服务端能够像访问本地变量一样访问RPC框架级别的变量。比如我们想将客户端的一个请求ID传递给服务端,这个请求ID作用于所有接口,比如RPC的调用链追踪,有两种方式:
接口中增加请求ID参数
这个方案显然是不能接受的,因为需要改的接口过多。
接口不改的情况下,在RPC框架中提供一个上下文,其中包含请求ID
这个方案显然成本最小,比如这种样调用:RpcContext.getRequestId();
- 上图中的Context是指RPC框架级变量的一个本地副本,为了简化调用复杂度。
- 上图中的Invocation是RPC框架级的变量,它与上面提到的Context相互配合,做到调用端与框架本身的解耦合
实现
定义上下文对象
在RpcContext对象中增加一个map类型的参数对象,可以存放任意扩展的参数。
public class RpcContext { private Map<String,Object> contextParameters; public Map<String, Object> getContextParameters() {
return contextParameters;
} public void setContextParameters(Map<String, Object> contextParameters) {
this.contextParameters = contextParameters;
} private static final ThreadLocal<RpcContext> rpcContextThreadLocal=new ThreadLocal<RpcContext>(){
@Override
protected RpcContext initialValue() {
RpcContext context= new RpcContext();
context.setContextParameters(Maps.newHashMap());
return context;
}
}; public static RpcContext getContext() {
return rpcContextThreadLocal.get();
} public static void removeContext() {
rpcContextThreadLocal.remove();
} private RpcContext() {
}
}
RPC请求对象中增加上下文参数
RpcRequest增加如下字段,用于服务端调用。
private Map<String,Object> contextParameters;
RpcInvocation接口中增加上下文参数
在后续新增加的过滤器使用。
private Map<String,Object> contextParameters;
客户端代理
RpcProxy在组装RpcRequest对象时,从RpcContext中获取最新的参数传递给RpcReuest,从而传递给服务端。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RpcRequest request = new RpcRequest();
//... request.setContextParameters(RpcContext.getContext().getContextParameters()); //...
}
客户端上下文过滤器
主要作用就是从本线线程变量中获取参数传递给RpcInvocation。
注解上的order属性文章后面详细介绍
@ActiveFilter(group = {ConstantConfig.CONSUMER},order = -1000)
public class ClientContextFilter implements RpcFilter { private final static Logger logger = LoggerFactory.getLogger(ClientContextFilter.class); @Override
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
Map<String,Object> contextParameters=invocation.getContextParameters();
if(null==contextParameters){
contextParameters= Maps.newHashMap();
}
Map<String,Object> contextParametersFromRpcContext= RpcContext.getContext().getContextParameters();
if(null!=contextParametersFromRpcContext) {
contextParameters.putAll(contextParametersFromRpcContext);
}
Object rpcResponse=invoker.invoke(invocation);
logger.info("ClientContextFilter.invoke end");
return rpcResponse;
}
}
服务端上下文过滤器
服务端上下文过滤器与客户端的作用相反,是从RpcInvocation中获取参数传递给本地线程变量RpcContext,后面在执行服务端方法时就可以方便的通过RpcContext获取指定变量。
@ActiveFilter(group = {ConstantConfig.PROVIDER},order = -1000)
public class ServerContextFilter implements RpcFilter { private final static Logger logger = LoggerFactory.getLogger(ServerContextFilter.class); @Override
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
Map<String,Object> contextParameters=invocation.getContextParameters();
RpcContext.getContext().setContextParameters(contextParameters);
Object rpcResponse=invoker.invoke(invocation);
logger.info("ServerContextFilter.invoke end");
return rpcResponse;
}
}
过滤器排序
因为我们的RpcContext是个本地线程变量,而且Rpc服务端是多线程处理业务,所以需要在请求结束后及时的清理掉相关本地线程变量信息。这就需要清理上下文的过滤动作在最后执行,否则有会出现服务端方法还没有执行就被清空了参数。创建一个工具类,专门用来处理获取客户端以及服务端过滤器。
过滤器注解增加排序属性
增加order字段,升级排列。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ActiveFilter {
String[] group() default {};
String[] value() default {};
int order() default 999999999;
}
过滤器工具类
创建ActiveFilterUtil,包含下面两个函数。
过滤器排序函数
获取特定注解的类,然后根据注解上的排序属性升序排序。
private static List<Object> getActiveFilter(){
List<Object> rpcFilterList= Lists.newArrayList();
Map<String, Object> rpcFilterMapObject = ApplicationContextUtils.getApplicationContext().getBeansWithAnnotation(ActiveFilter.class);
if (null!=rpcFilterMapObject) {
rpcFilterList = Lists.newArrayList(rpcFilterMapObject.values());
Collections.sort(rpcFilterList, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
ActiveFilter activeFilterO1 = o1.getClass().getAnnotation(ActiveFilter.class);
ActiveFilter activeFilterO2 = o2.getClass().getAnnotation(ActiveFilter.class);
return activeFilterO1.order() > activeFilterO2.order() ? 1 : -1;
}
});
}
return rpcFilterList;
}
获取RPC过滤器列表
提供给客户端以及服务端的一个协助方法,便于客户端以及服务构建过滤器职责链。
public static Map<String,RpcFilter> getFilterMap(boolean isServer){
List<Object> rpcFilterList=getActiveFilter();
Map<String,RpcFilter> filterMap=new HashMap<>();
for (Object filterBean : rpcFilterList) {
Class<?>[] interfaces = filterBean.getClass().getInterfaces();
ActiveFilter activeFilter=filterBean.getClass().getAnnotation(ActiveFilter.class);
String includeFilterGroupName=!isServer?ConstantConfig.CONSUMER:ConstantConfig.PROVIDER;
if(null!=activeFilter.group()&& Arrays.stream(activeFilter.group()).filter(p->p.contains(includeFilterGroupName)).count()==0){
continue;
}
for(Class<?> clazz:interfaces) {
if(clazz.isAssignableFrom(RpcFilter.class)){
filterMap.put(filterBean.getClass().getName(),(RpcFilter) filterBean);
}
}
}
return filterMap;
}
客户端以及服务端初始化
获取过滤器map的逻辑改为调用上面ActiveFilterUtil.getFilterMap方法。
使用示例
客户端设置参数
设置一个RPC版本号的参数。
@RequestMapping("/{productId}")
public Product getById(@PathVariable final long productId) throws UnknownHostException {
RpcContext.getContext().addContextParameter("rpc-version","1.0");
//...
}
服务端获取参数
简单的在服务端打印出RPC版本号。
logger.info("get context parameter from server,rpc-version={}",String.valueOf(RpcContext.getContext().getContextParameter("rpc-version")));
输出日志如下:
本文源码
https://github.com/jiangmin168168/jim-framework
文中代码是依赖上述项目的,如果有不明白的可下载源码