SegmentFault 数据价值最新的文章
2019-01-22T20:11:52+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
关于公司架构管控的思考
https://segmentfault.com/a/1190000017985230
2019-01-22T20:11:52+08:00
2019-01-22T20:11:52+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
0
<p>假想背景:<br>现状是,各子系统的新建及重大迭代都会形式化地走架构审批流程,但应用架构是否设计以及是否合理,信息技术部门不能掌握。而架构规划部门的架构师人屈指可数,面对总人数达数百人的开发团队所负责的几十子系统、每个月数十个迭代特性,无法做到直接帮助开发团队详尽的进行架构设计。由此提出:架构审批流程不代表架构设计、架构规划部门要加强架构管控。</p>
<p>要做好架构管控,需要能够回答几个问题:架构管控的目的是什么?架构管控的目标是什么?架构管控需要管控什么内容?如何进行管控?</p>
<h2>一、目的</h2>
<p>一个稳定发展、创新发展的企业,支援业务发展的信息系统的稳定与效率同等重要。架构管控的目的,要指导各团队技术负责人设计出合理的系统,能够满足稳定与开发效率的要求,能够满足功能与非功能的需求,能够考虑到各级相关者的意见;并且还要有考察开发过程及运营过程的机制,以确定最终交付的软件系统复合架构设计及开展架构设计的持续改善工作。</p>
<h2>二、制定目标</h2>
<p>为了达到这些我们架构管控的目标又是什么呢?发布一份指导意见?发布一份制度说明?发布一份评分表?<br>我认为这些应该归属于如何进行管控,而不是目标。<br>我们需要梳理现状,找出主要矛盾,量化主要矛盾,形成目标(SMART原则别忘记~~)</p>
<h3>(一)、主要生产问题、客户投诉及分类</h3>
<p>从客户反馈中可以抓住主要矛盾。</p>
<h3>(二)、信息技术方面的主要矛盾</h3>
<ol>
<li>外购产品 vs 二次改造<br>早期从外部采购的产品一般会使用比较老旧的开发框架,进行二次改造困难,常带来较多故障,并且开发效率低下。</li>
<li>系统解耦 vs 互相影响<br>分布式带来较好的伸缩性等非功能特性的同时,也带来了复杂度 —— 系统间相互影响较难控制,给设计和开发增大了难度。</li>
<li>开发团队对科技公共平台的不熟悉 vs 强制使用</li>
<li>业务数据模型混乱 vs 新需求</li>
</ol>
<h4>5、控制 vs 创新</h4>
<h3>(三)、形成架构管控目标</h3>
<ol>
<li>控制新增外购系统的架构方案的合理性、梳理既存外购系统的架构、提升开发效率。</li>
<li>降低分布式系统的开发难度、提升分布式系统的交付质量。</li>
<li>降低科技公共平台的使用难度。</li>
<li>引导、规范新技术的引入。</li>
</ol>
Spring WebFlux是如何映射请求到控制器的?
https://segmentfault.com/a/1190000017203341
2018-11-30T10:35:43+08:00
2018-11-30T10:35:43+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
1
<h2>一、前言</h2>
<p>Spring Flux中的核心DispatcherHandler的处理过程分为三步,其中首步就是通过HandlerMapping接口查找Request所对应的Handler。本文就是通过阅读源码的方式,分析一下HandlerMapping接口的实现者之一——RequestMappingHandlerMapping类,用于处理基于注解的路由策略,把所有用@Controller和@RequestMapping标记的类中的Handler识别出来,以便DispatcherHandler调用的。</p>
<blockquote>HandlerMapping接口的另外两种实现类:1、RouterFunctionMapping用于函数式端点的路由;2、SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler配对。</blockquote>
<p><!-- more --></p>
<p>文章系列</p>
<ul>
<li>关于非阻塞IO:<a href="https://segmentfault.com/a/1190000017000841">《从时间碎片角度理解阻塞IO模型及非阻塞模型》</a>
</li>
<li>关于Spring WebFlux新手入门:<a href="https://segmentfault.com/a/1190000017150640">《快速上手Spring Flux框架》</a>
</li>
<li>
<a href="#">为什么Spring要引入SpringFlux框架</a> 尚未完成</li>
<li>
<a href="https://segmentfault.com/a/1190000017203341">Spring WebFlux是如何映射请求到控制器的?</a> 本文</li>
<li>
<a href="#">Spring WebFlux中执行HandlerMapping的过程</a> 尚未完成</li>
<li>
<a href="#">Spring WebFlux中是如何处理HandlerResult的</a> 尚未完成</li>
<li>
<a href="#">Spring WebFlux与WEB服务器之Servlet 3.1+</a> 尚未完成</li>
<li>
<a href="#">Spring WebFlux与WEB服务器之Netty</a> 尚未完成</li>
</ul>
<h2>二、对基于注解的路由控制器的抽象</h2>
<p>Spring中基于注解的控制器的使用方法大致如下:</p>
<pre><code>@Controller
public class MyHandler{
@RequestMapping("/")
public String handlerMethod(){
}
}</code></pre>
<p>在Spring WebFlux中,对上述使用方式进行了三层抽象模型。</p>
<ol>
<li>
<p>Mapping</p>
<ul>
<li>用户定义的基于annotation的映射关系</li>
<li>该抽象对应的类是:org.springframework.web.reactive.result.method.RequestMappingInfo</li>
<li>比如上述例子中的 @RequestMapping("/")所代表的映射关系</li>
</ul>
</li>
<li>
<p>Handler</p>
<ul>
<li>代表控制器的类</li>
<li>该抽象对应的类是:java.lang.Class</li>
<li>比如上述例子中的MyHandler类</li>
</ul>
</li>
<li>
<p>Method</p>
<ul>
<li>具体处理映射的方法</li>
<li>该抽象对应的类是:java.lang.reflect.Method</li>
<li>比如上述例子中的String handlerMethod()方法</li>
</ul>
</li>
</ol>
<p>基于上述三层抽象模型,进而可以作一些组合。</p>
<ol>
<li>
<p>HandlerMethod</p>
<ul><li>Handler与Method的结合体,Handler(类)与Method(方法)搭配后就成为一个可执行的单元了</li></ul>
</li>
<li>
<p>Mapping vs HandlerMethod</p>
<ul><li>把Mapping与HandlerMethod作为字典存起来,就可以根据请求中的关键信息(路径、头信息等)来匹配到Mapping,再根据Mapping找到HandlerMethod,然后执行HandlerMethod,并传递随请求而来的参数。</li></ul>
</li>
</ol>
<p><em>理解了这个抽象模型后,接下来分析源码来理解Spring WebFlux如何处理请求与Handler之间的Mapping关系时,就非常容易了。</em></p>
<p>HandlerMapping接口及其各实现类负责上述模型的构建与运作。</p>
<h2>三、HandlerMapping接口实现的设计模式</h2>
<p>HandlerMapping接口实现,采用了"模版方法"这种设计模式。</p>
<p>1层:AbstractHandlerMapping <strong>implements HandlerMapping</strong>, <em>Ordered, BeanNameAware</em></p>
<pre><code> ^
| </code></pre>
<p>2层:AbstractHandlerMethodMapping <strong>implements InitializingBean</strong></p>
<pre><code> ^
| </code></pre>
<p>3层:RequestMappingInfoHandlerMapping</p>
<pre><code> ^
| </code></pre>
<p>4层:<strong>RequestMappingHandlerMapping</strong> <em>implements EmbeddedValueResolverAware</em> </p>
<p>下面对各层的职责作简要说明:</p>
<ul>
<li>
<p>第1层主要实现了对外提供模型的接口</p>
<ul><li>即重载了HandlerMapping接口的"Mono<Object> getHandler(ServerWebExchange exchange) "方法,并定义了骨架代码。</li></ul>
</li>
<li>
<p>第2层有两个责任 —— 解析用户定义的HandlerMethod + 实现对外提供模型接口实现所需的抽象方法</p>
<ul>
<li>通过实现了InitializingBean接口的"void afterPropertiesSet()"方法,解析用户定义的Handler和Method。</li>
<li>实现第1层对外提供模型接口实现所需的抽象方法:"Mono<?> getHandlerInternal(ServerWebExchange exchange)"</li>
</ul>
</li>
<li>第3层提供根据请求匹配Mapping模型实例的方法</li>
<li>第4层实现一些高层次用到的抽象方法来创建具体的模型实例。</li>
</ul>
<p>小结一下,就是HandlerMapping接口及其实现类,把用户定义的各Controller等,抽象为上述的Mapping、Handler及Method模型,并将Mapping与HandlerMethod作为字典关系存起来,还提供通过匹配请求来获得HandlerMethod的公共方法。</p>
<p>接下来的章节,将先分析解析用户定义的模型并缓存模型的过程,然后再分析一下匹配请求来获得HandlerMethod的公共方法的过程。</p>
<h2>四、解析用户定义的模型并缓存模型的过程</h2>
<h3>4-1、实现InitializingBean接口</h3>
<p>第2层AbstractHandlerMethodMapping抽象类中的一个重要方法——实现了InitializingBean接口的"void afterPropertiesSet()"方法,为Spring WebFlux带来了解析用户定义的模型并缓存模型的机会 —— Spring容器初初始化完成该类的具体类的Bean后,将会回调这个方法。<br>在该方法中,实现获取用户定义的Handler、Method、Mapping以及缓存Mapping与HandlerMethod映射关系的功能。</p>
<pre><code>@Override
public void afterPropertiesSet() {
initHandlerMethods();
// Total includes detected mappings + explicit registrations via registerMapping..
...
}</code></pre>
<h3>4-2、找到用户定义的Handler</h3>
<p>afterPropertiesSet方法中主要是调用了void initHandlerMethods()方法,具体如下:</p>
<pre><code>protected void initHandlerMethods() {
//获取Spring容器中所有Bean名字
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
//获取Bean的类型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//如果获取到类型,并且类型是Handler,则继续加载Handler方法。
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
//初始化后收尾工作
handlerMethodsInitialized(getHandlerMethods());
}</code></pre>
<p>这儿首先获取Spring容器中所有Bean名字,然后循环处理每一个Bean。如果Bean名称不是以SCOPED_TARGET_NAME_PREFIX常量开头,则获取Bean的类型。如果获取到类型,并且类型是Handler,则继续加载Handler方法。</p>
<p>isHandler(beanType)调用,检查Bean的类型是否符合handler定义。<br>AbstractHandlerMethodMapping抽象类中定义的抽象方法"boolean isHandler(Class<?> beanType)",是由RequestMappingHandlerMapping类实现的。具体实现代码如下:</p>
<pre><code>protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}</code></pre>
<p>不难看出,对于RequestMappingHandlerMapping这个实现类来说,<strong>只有拥有@Controller或者@RequestMapping注解的类,才是Handler</strong>。(言下之意对于其他实现类来说Handler的定义不同)。</p>
<p>具体handler的定义,在HandlerMapping各实现类来说是不同的,这也是isHandler抽象方法由具体实现类来实现的原因。</p>
<h3>4-3、发现Handler的Method</h3>
<p>接下来我们要重点看一下"detectHandlerMethods(beanName);"这个方法调用。</p>
<pre><code>protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//寻找目标类型userType中的Methods,selectMethods方法的第二个参数是lambda表达式,即感兴趣的方法的过滤规则
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
//回调函数metadatalookup将通过controller定义的mapping与手动定义的mapping合并起来
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isTraceEnabled()) {
logger.trace("Mapped " + methods.size() + " handler method(s) for " + userType + ": " + methods);
}
methods.forEach((key, mapping) -> {
//再次核查方法与类型是否匹配
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
//如果是满足要求的方法,则注册到全局的MappingRegistry实例里
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}</code></pre>
<p>首先将参数handler(即外部传入的BeanName或者BeanType)转换为Class<?>类型变量handlerType。如果转换成功,再将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)。接下来获取该用户类型里所有的方法(Method)。循环处理每个方法,如果是满足要求的方法,则注册到全局的MappingRegistry实例里。</p>
<h3>4-4、解析Mapping信息</h3>
<p>其中,以下代码片段有必要深入探究一下</p>
<pre><code> Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));</code></pre>
<p>MethodIntrospector.selectMethods方法的调用,将会把用@RequestMapping标记的方法筛选出来,并交给第二个参数所定义的MetadataLookup回调函数将通过controller定义的mapping与手动定义的mapping合并起来。<br>第二个参数是用lambda表达式传入的,表达式中将method、userType传给getMappingForMethod(method, userType)方法。</p>
<p>getMappingForMethod方法在高层次中是抽象方法,具体的是现在第4层RequestMappingHandlerMapping类中实现。在具体实现getMappingForMethod时,会调用到RequestMappingHandlerMapping类的下面这个方法。从该方法中,我们可以看到,首先会获得参数element(即用户在Controller中定义的方法)的RequestMapping类型的类实例,然后构造代表Mapping抽象模型的RequestmappingInfo类型实例并返回。</p>
<pre><code>private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}</code></pre>
<p>构造代表Mapping抽象模型的RequestmappingInfo类型实例,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的信息,包括paths、methods、params、headers、consumers、produces、mappingName,即用户定义@RequestMapping注解时所设定的可能的参数,都被存在这儿了。拥有了这些信息,当请求来到时,RequestMappingInfo就可以测试自身是否是处理该请求的人选之一了。</p>
<pre><code>protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}</code></pre>
<h3>4-5、缓存Mapping与HandlerMethod关系</h3>
<p>最后,registerHandlerMethod(handler, invocableMethod, mapping)调用将缓存HandlerMethod,其中mapping参数是RequestMappingInfo类型的。。<br>内部调用的是MappingRegistry实例的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo类型。<br>MappingRegistry类维护所有指向Handler Methods的映射,并暴露方法用于查找映射,同时提供并发控制。</p>
<pre><code>public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
......
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}</code></pre>
<h2>五、匹配请求来获得HandlerMethod</h2>
<p>AbstractHandlerMethodMapping类的“Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange)”方法,具体实现了根据请求查找HandlerMethod的逻辑。</p>
<pre><code> @Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
//获取读锁
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod;
try {
//调用其它方法继续查找HandlerMethod
handlerMethod = lookupHandlerMethod(exchange);
}
catch (Exception ex) {
return Mono.error(ex);
}
if (handlerMethod != null) {
handlerMethod = handlerMethod.createWithResolvedBean();
}
return Mono.justOrEmpty(handlerMethod);
}
//释放读锁
finally {
this.mappingRegistry.releaseReadLock();
}
}</code></pre>
<p>handlerMethod = lookupHandlerMethod(exchange)调用,继续查找HandlerMethod。我们继续看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定义。为方便阅读,我把注释也写在了代码里。</p>
<pre><code> protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
List<Match> matches = new ArrayList<>();
//查找所有满足请求的Mapping,并放入列表mathes
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);
if (!matches.isEmpty()) {
//获取比较器comparator
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
//使用比较器将列表matches排序
matches.sort(comparator);
//将排在第1位的作为最佳匹配项
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
//将排在第2位的作为次佳匹配项
Match secondBestMatch = matches.get(1);
}
handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange);
}
}</code></pre>
<h2>六、总结</h2>
<p>理解了Spring WebFlux在获取映射关系方面的抽象设计模型后,就很容易读懂代码,进而更加理解框架的具体处理方式,在使用框架时做到“知己知彼”。</p>
<p>原文:<a href="https://link.segmentfault.com/?enc=GhvWdN0TqEjLRpDjJEUpiQ%3D%3D.QHtwW3Kkv%2FhxjygfMeoXdOEbLYpyia5ppZyXo14NQAtvkJwIAv7r4xfX1QRVejfBpmv05AIuDKxMu5TWUfYCgg%3D%3D" rel="nofollow">http://www.yesdata.net/2018/11/27/spring-flux-request-mapping/</a></p>
快速上手Spring WebFlux框架
https://segmentfault.com/a/1190000017150640
2018-11-26T20:50:17+08:00
2018-11-26T20:50:17+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
8
<h2>一、前言</h2>
<p>本文主要介绍基于SpringBoot如何快速上手使用SpringFlux框架开发WEB网站。</p>
<p>Spring 5.0在原有的Spring MVC Stack(又称Servlet Stack)以外,又引入了新的WEB开发技术栈——Spring Flux Stack(又称Reactive Stack),以满足不同的应用程序及开发团队的需求。</p>
<blockquote>开发者一直在寻找最适合他们的应用程序的运行时、编程框架及架构。比如,有些用例最适合采用基于同步阻塞IO架构的技术栈,而<a href="https://link.segmentfault.com/?enc=JZqE9%2BAvm3AuMTAUHMetbw%3D%3D.15N7eheXYVeoO7H5DjJCmbCI4KnelJK2KmGTKrx33wKivKNAvyEcAL9%2BHhYmYN6o7jMSa194P1tm3pmUEouI8CzEqXrQp%2FvWvHbmKqUsY8E4mcSa4Qsxn45jjuxS9U2K0%2F4I9z9wr8riZMNeMW8zMJ96WutSVjY16V%2F%2F6Mpss2c%3D" rel="nofollow">另一些用例</a>可能更适合于基于<a href="https://link.segmentfault.com/?enc=qwQDDQ6Idz0e8B5o63%2FMag%3D%3D.Yf3EDGACii9AUHFDoq%2FMOCJ2zubh4tDxK%2B7W4QP9t8gbrgHDETkhMuEeAbsnEVmo" rel="nofollow">Reactive Streams</a>响应式编程原则构建的异步的、非阻塞的技术栈。</blockquote>
<p>后续将有系列文章深入介绍SpringFlux所采用的响应式编程原则及其代表实现ProjectReactor,希望通过系列文章的介绍,让广大读者能够在逐步使用SpringFlux的过程中,理解响应式编程原理及实现,进而能够对项目应该选择SpringMVC还是SpringWebFlux形成自己的判断标准。</p>
<p>文章系列</p>
<ul>
<li>关于非阻塞IO:<a href="https://segmentfault.com/a/1190000017000841">《从时间碎片角度理解阻塞IO模型及非阻塞模型》</a>
</li>
<li>关于SpringFlux新手入门:<a href="#">《快速上手Spring Flux框架》</a>
</li>
<li>
<a href="#">为什么Spring要引入SpringFlux框架</a> 尚未完成</li>
<li>
<a href="#">Spring Flux中Request与HandlerMapping关系的形成过程</a> 尚未完成</li>
<li>
<a href="#">Spring Flux中执行HandlerMapping的过程</a> 尚未完成</li>
<li>
<a href="#">Spring Flux中是如何处理HandlerResult的</a> 尚未完成</li>
<li>
<a href="#">Spring Flux与WEB服务器之Servlet 3.1+</a> 尚未完成</li>
<li>
<a href="#">Spring Flux与WEB服务器之Netty</a> 尚未完成</li>
</ul>
<p><!-- more --></p>
<h2>二、快速上手</h2>
<h3>1、创建项目</h3>
<p>打开 <a href="https://link.segmentfault.com/?enc=Umwc5BybXQR3KMylTmZ8dA%3D%3D.Xia5uWKYObMUNvzzmNVU4TDx18HdiTLtSdVlVvGOzXM%3D" rel="nofollow">http://start.spring.io</a>,来初始化一个Spring WebFlux项目吧。左侧一列是Project Metadata,填上你的group名(我们使用net.yesdata吧),还有Artifact名(默认是demo);然后右侧一列是Dependencies(依赖),我们输入"reactive web",在得到的下拉框中选择"Reacive Web",然后下方"Selected Dependencies"处会显示我们选中的"Reactive Web"。然后点击"Generate Project",浏览器会下载创建好的Spring Boot项目。加压后用你喜欢的IDE(Eclipse或者IntelliJ IDEA等)。</p>
<p><img src="/img/remote/1460000017150643?w=2338&h=1184" alt="通过start.spring.io创建reactive栈的项目" title="通过start.spring.io创建reactive栈的项目"></p>
<h3>2、增加一个Controller</h3>
<p>如果你熟悉Spring MVC,你一定对@Controller注解不陌生。即使不熟悉也没关系,我会简单介绍一下。<br>Spring WebFlux带有两种特征,一种是函数式的(Functional),另一种是基于注解的(annotation-based)。函数式编程不太适合快速上手,我们先选择基于注解。类似于Spring MVC模型,Spring WebFlux模型也使用@Controller注解,以及@RestController注解。</p>
<p>用IDE打开项目后,追加一个类:SampleController <br>然后增加如下代码:</p>
<pre><code>@RestController
@RequestMapping("/")
public class SampleController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}</code></pre>
<p>运行后,用浏览器访问:<a href="https://link.segmentfault.com/?enc=jCQlVQ6IAC2sGBF%2B26i1zg%3D%3D.xQ8EwJHg%2B5pZNfikgAWlEQWA%2F%2BHNguAB1XpQEZrPteI%3D" rel="nofollow">http://localhost:8080/hello</a>,将会在浏览器上看到"hello"字样。</p>
<p><img src="/img/remote/1460000017150644?w=1824&h=750" alt="增加SampleController" title="增加SampleController"></p>
<p><img src="/img/remote/1460000017150645?w=898&h=388" alt="SampleController执行结果" title="SampleController执行结果"></p>
<p>怎么样,是不是很简单。</p>
<h3>3、增加一个WebFilter</h3>
<p>增加一个Filter试试看。在Spring WebFlux框架下,增加Filter是通过实现WebFilter接口实现的。</p>
<pre><code>@Component
public class FirstWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
serverWebExchange.getAttributes().put("User", "jerry");
return webFilterChain.filter(serverWebExchange);
}
}</code></pre>
<h2>三、深入了解一下SpringBoot启动Spring WebFlux的过程</h2>
<p>对于已经笔记熟悉Spring MVC框架的开发人员来说,快速上手无法满足欲望。必须了解其背后的机制原理等,方能有知己知彼的感觉。接下来稍微探讨以下SpringBoot启动Spring WebFlux的过程。尚不太熟悉Spring MVC框架的开发人员也可以阅读一下本章内容,或许对更好地使用Spring WebFlux框架有帮助。</p>
<h3>1、准备工作</h3>
<p>你需要从Gitub<a href="https://link.segmentfault.com/?enc=HokayXdT0eoJvOdND%2BQC7Q%3D%3D.GCEAoyNRrG8cq3JCnnwAS9K%2F8mmzW8sfNgnxdf6JQXp8s21HSNQ3RStyinFQIRD9d7bvXq%2BnnW9O56iLjXcDgA%3D%3D" rel="nofollow">https://github.com/spring-projects/spring-framework</a>下载Spring WebFlux的源码,以便进行后续分析。</p>
<h3>2、@EnableWebFlux</h3>
<p>回顾一下通过<a href="https://link.segmentfault.com/?enc=YnHnwFSw3SENqP3Ls9b5EQ%3D%3D.zrMqIvmsQWtNEihgLwQ4ju6YYIVwk47bqUKu71bU4ns%3D" rel="nofollow">http://start.spring.io</a>创建项目中,DemoApplication启动类的注解中,有一个注解是:@EnableWebFlux。SpringBoot就是通过这个注解,来启动Spring WebFlux的。</p>
<pre><code>@EnableWebFlux
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
</code></pre>
<p>再看一下EnableWebFlux的定义(可以到SpringWebFlux源码中找到),如下,我们注意到它引入了DelegatingWebFluxConfiguration类。看来秘密会藏在DelegatingWebFluxConfiguration类里呢。</p>
<pre><code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebFluxConfiguration.class)
public @interface EnableWebFlux {
}</code></pre>
<p>打开DelegatingWebFluxConfiguration.java文件(可以到SpringWebFlux源码中找到),然后你我们发现它的父类是WebFluxConfigurationSupport类。我们先来看看这个父类。</p>
<pre><code>@Configuration
public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport {
......
}</code></pre>
<p>下面是父类WebFluxConfigurationSupport实现的代码片段。我们注意到,它向Spring Bean容器中,注入了两个Bean:webHandler和requestMappingHandlerMapping(实际上还注入了其它若干个Bean,不过我们暂时只关注这两个吧)。</p>
<p>名为"webHandler"的Bean的类型是DispatcherHandler,顾名思义是分发器,分发请求给各个处理单元。 <br>名为"requestMappingHandlerMapping"的Bean的类型是RequestMappingHandlerMapping,它的根本是实现了HandlerMapping接口。HandlerMapping的作用是将请求映射到Handler上,比如把某个请求路径映射到某个Controller上。</p>
<p>关于DispatcherHandler和HandlerMapping,后面章节继续深入讲解。</p>
<pre><code>public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Bean
public DispatcherHandler webHandler() {
return new DispatcherHandler();
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setContentTypeResolver(webFluxContentTypeResolver());
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useCaseSensitiveMatch != null) {
mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
}</code></pre>
<h3>2、DispatcherHandler</h3>
<p>DispatcherHandler将自身作为Bean注册到Spring的Bean容器中,它与WebFilter、WebExceptionHandler等一起,组成处理链。</p>
<p>DispatcherHandler会从Spring Configuration中探寻到它需要的组件,主要是以下三类:</p>
<pre><code>public class DispatcherHandler implements WebHandler, ApplicationContextAware {
...
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;
private List<HandlerResultHandler> resultHandlers;
...
}</code></pre>
<ul>
<li>
<p>HandlerMapping</p>
<ul>
<li>映射Request到Handler</li>
<li>HandlerMapping的默认实现是RequestMappingHandlerMapping,用于@RequestMapping所标记的方法;RouterFunctionMapping用于函数式端点的路由;SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler</li>
</ul>
</li>
<li>
<p>HandlerAdaptor</p>
<ul>
<li>帮助DispatcherHandler调用Request所映射的Handler</li>
<li>HandlerAdaptor的主要作用是将DispatcherHandler从调用具体Handler的细节中解放出来</li>
</ul>
</li>
<li>
<p>HandlerResultHandler</p>
<ul>
<li>处理Handler的结果并终结Response</li>
<li>调用具体的Handler的返回结果是包装在HandlerResult中的,HandlerResultHandler负责处理HandlerResult。比如ResponseBodyResultHandler负责处理@ResponseBody标记的方法的返回值</li>
</ul>
</li>
</ul>
<p>示意图如下:</p>
<pre><code>DispatcherHandler
|
|-->HandlerMapping
|-->HandlerAdaptor
|-->HandlerResultHandler</code></pre>
<p>DispatcherHandler的核心方法是:</p>
<pre><code>@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}</code></pre>
<p>该方法道出了DispatcherHandler处理请求的套路</p>
<ul><li>先通过HandlerMapping找到Request的专用Handler,具体的代码片段是</li></ul>
<pre><code>concatMap(mapping -> mapping.getHandler(exchange))</code></pre>
<ul><li>再通过HandlerAdaptor执行这个具体的Handler,具体的代码片段是</li></ul>
<pre><code>flatMap(handler -> invokeHandler(exchange, handler))</code></pre>
<ul><li>最后通过HandlerResultHandler处理Handler返回的结果,具体的代码片段是</li></ul>
<pre><code>flatMap(result -> handleResult(exchange, result))</code></pre>
<p><em>可能你有疑问,这个方法里Request在哪呢?藏在"ServerWebExchange exchange"里了,关于ServerWebExchange又其它文章进一步介绍,这儿就不作详细说明了。</em></p>
<h3>3、HandlerMapping</h3>
<p>DispatcherHandler套路中,处理Request的第一环节就是使用HandlerMapping。常见的HandlerMapping有三种:RequestMappingHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping</p>
<p>RequestMappingHandlerMapping是默认的,是否还记得DispatcherHandler中有发布一个Bean名为"requestMappingHandlerMapping"?它承担了映射Request与annotation-based Handler之间的关系。</p>
<p>由于我们习惯于使用@Controller、@RestController、@RequestMapping之类的注解来开发WEB项目,所以非常依赖RequestMappingHandlerMapping。我们接下来就谈谈<strong>RequestMappingHandlerMapping</strong>。</p>
<h4>3-1、RequestMappingHandlerMapping的类层级</h4>
<p>1层:AbstractHandlerMapping <strong>implements HandlerMapping</strong>, <em>Ordered, BeanNameAware</em></p>
<pre><code> ^
|</code></pre>
<p>2层:AbstractHandlerMethodMapping <strong>implements InitializingBean</strong></p>
<pre><code> ^
|</code></pre>
<p>3层:RequestMappingInfoHandlerMapping</p>
<pre><code> ^
|</code></pre>
<p>4层:<strong>RequestMappingHandlerMapping</strong> <em>implements EmbeddedValueResolverAware</em></p>
<h4>3-2、扫描可用handler</h4>
<p>大家注意到,第2层实现了InitializingBean接口,实现了该接口的类有机会在BeanFactory设置好它的所有属性后通过调用</p>
<pre><code>void afterPropertiesSet()</code></pre>
<p>方法通知它。我们来看看第2层的对该方法的实现吧。</p>
<pre><code>@Override
public void afterPropertiesSet() {
initHandlerMethods();
// Total includes detected mappings + explicit registrations via registerMapping..
...
}</code></pre>
<p>篇幅原因,这儿不细究方法中所调用的</p>
<pre><code>initHandlerMethods();</code></pre>
<p>的细节了 —— 实际上这里面是扫描所有@Controller注解的类中的@RequestMapping及其变体所修饰的方法,即最终会处理Request的Handler,并缓存起来,以便后续进一步执行。</p>
<h4>3-3、与DispatcherHandler相互配合</h4>
<p>DispatcherHandler会调用MappingHandler的</p>
<pre><code>Mono<Object> getHandler(ServerWebExchange exchange)</code></pre>
<p>方法,选择处理这个请求交的具体的Handler。</p>
<h2>四、总结</h2>
<p>Spring WebFlux模型的使用非常简单,尤其是对于熟悉Spring MVC模型的开发人员来说,无缝切换。使用Spring Boot框架开发时,使用@Controller、@RestController、@RequestMapping等注解,实现处理Request的Handler尤其方便。</p>
<p>原文:<a href="https://link.segmentfault.com/?enc=BuCBOIy0N02GPCcUuJAAcQ%3D%3D.6AnsDUX4%2Fl6cdQxJTQS63hzzV%2BvhoVRrECGFKGYky02Bnm81RGyeW6lhU97dhs8A" rel="nofollow">http://www.yesdata.net/2018/1...</a></p>
从时间碎片角度理解阻塞IO模型及非阻塞模型
https://segmentfault.com/a/1190000017000841
2018-11-13T20:13:01+08:00
2018-11-13T20:13:01+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
7
<h2>阻塞模型限制了服务器的并发处理能力(伸缩性或同时处理的客户端连接数)</h2>
<p>传统的网络服务器只支持阻塞模型,该模型下,针对每个客户端连接,服务器都必须创建一个线程来处理这个连接上的请求,服务器必须维持着这些线程直到线程中的处理工作结束。</p>
<p>服务器上所能创建的线程数量是有限的,WHY?</p>
<ul>
<li>进程上下文切换是耗时的过程</li>
<li>创建的进程本身占用资源,比如每个进程或线程占用一定容量的内存</li>
<li>等待数据准备和内核缓存复制,导致IO阻塞,占用着线程</li>
</ul>
<p>所以当连接到服务器上的客户端的数量很大时,把服务器上所能创建的线程都占据了时,<strong>服务器就无法接受更多的连接</strong>了。这限制了服务器处理请求的伸缩性。</p>
<h2>并非所有客户端都是持续活跃的</h2>
<p>存在这样一个事实,就是虽然连接到服务器上的客户端很多,但并非所有客户端都是持续活跃着的。它们占据着阻塞式服务器的线程资源——即使它们处于非工作状态。这些线程占据了资源,却不工作。</p>
<p>这会造成什么现象呢?<br>就是<strong>线程时间的碎片化</strong>——一个线程大部分时间是在等待IO操作的结果。</p>
<p>为了让<strong>服务器能接受更多客户端的连接</strong>,非阻塞模型就出现了。</p>
<h2>如何提升服务器的并发处理能力?</h2>
<p>消灭碎片化时间,可以提升服务器的并发处理能力。</p>
<p>如何消灭碎片化时间? 让线程分工协作各司其职,是一个很好的手段。<br>原来的阻塞模型下,一个线程要干所有的事情。分工协作机制下,一部分线程专门用于接受客户端的连接、一部分专门用于获取请求的数据、一部分专门执行计算工作、还有一部分线程专门用于响应客户端。<br>接受客户端连接的线程在接收到客户端连接后,立即把连接交给后续工序的线程处理,而它自己则继续接受下一个连接。如此类推,各个线程无须等待,不存在碎片化时间,全负荷工作。<br>这样一来,整体上需要的较少的线程,就可以完成以前需要较多线程才能达到的工作时间了。</p>
<h2>阻塞模型下的实现方式</h2>
<p>在阻塞模型下,利用<strong>异步处理</strong>的方式对线程进行分工协作。接收请求的线程可以满负荷工作,但处理IO操作的线程仍然是阻塞着的,仍然存在线程工作不饱和的现象。</p>
<h2>非阻塞模型彻底消灭线程工作不饱和</h2>
<p>非阻塞模型下,IO操作不再是阻塞的了,而是立即返回。这样的话,处理IO操作的线程,可以在空闲时对所有请求进行轮询,以便判断哪些IO操作已完成。比如判断某个请求是否可以进行“写”操作,如果还不可以,无须等待,继续判断下一个请求是否可以进行“读”操作,如果可以则立即读取数据,然后把数据转交给专职计算的线程。这样就让线程工作不饱和现象消失了。</p>
<p>这是所谓的“同步非阻塞”。</p>
<h2>轮询的耗时如何消灭?</h2>
<p>这就要请出“IO复用”这尊大神了。</p>
<p>IO复用模型下,线程一次性从操作系统那儿获得一批可以进行IO操作的请求,处理完毕后,再此获得新的一批。线程无须与操作系统交互多次以便轮询每个请求的状态,而是与操作系统交互一次即可获得批量信息。效率进一步提高啦。</p>
从JDK11新增HttpClient谈谈非阻塞模型
https://segmentfault.com/a/1190000016886420
2018-11-02T13:31:51+08:00
2018-11-02T13:31:51+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
3
<h2>北京时间 9 月 26 日,Oracle 官方宣布 Java 11 正式发布</h2>
<h2>一、JDK HTTP Client介绍</h2>
<h3>JDK11中的17个新特性</h3>
<p><img src="/img/remote/1460000016886423?w=1052&h=864" alt="JDK11中的17个新特性" title="JDK11中的17个新特性"></p>
<h3>JDK11中引入HTTP Client的动机</h3>
<p>既有的HttpURLConnection存在许多问题</p>
<ul>
<li>其基类URLConnection当初是设计为支持多协议,但其中大多已经成为非主流(ftp, gopher…)</li>
<li>API的设计早于HTTP/1.1,过度抽象</li>
<li>难以使用,存在许多没有文档化的行为</li>
<li>它只支持阻塞模式(每个请求/响应占用一个线程)</li>
</ul>
<p><!--more--></p>
<h3>HTTP Client发展史</h3>
<p><img src="/img/remote/1460000016886424" alt="HTTP Client发展史" title="HTTP Client发展史"></p>
<h3>在JDK11 HTTP Client出现之前</h3>
<p>在此之前,可以使用以下工具作为Http客户端</p>
<ul>
<li>JDK HttpURLConnection</li>
<li>Apache HttpClient</li>
<li>Okhttp</li>
<li>Spring Rest Template</li>
<li>Spring Cloud Feign</li>
<li>将Jetty用作客户端</li>
<li>使用Netty库。还</li>
</ul>
<h3>初探JDK HTTP Client</h3>
<p>我们来看一段HTTP Client的常规用法的样例 ——<br>执行GET请求,然后输出响应体(Response Body)。</p>
<pre><code>HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, asString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();</code></pre>
<h3>第一步:创建HttpClient</h3>
<p>一般使用JDK 11中的HttpClient的第一步是创建HttpClient对象并进行配置。</p>
<ul>
<li>指定协议(http/1.1或者http/2)</li>
<li>转发(redirect)</li>
<li>代理(proxy)</li>
<li>认证(authenticator)</li>
</ul>
<pre><code>HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_2)
.followRedirects(Redirect.SAME_PROTOCOL)
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.authenticator(Authenticator.getDefault())
.build();</code></pre>
<h3>第二步:创建HttpRequest</h3>
<p>从HttpRequest的builder组建request</p>
<ul>
<li>请求URI</li>
<li>请求method(GET, PUT, POST)</li>
<li>请求体(request body)</li>
<li>Timeout</li>
<li>请求头(request header)</li>
</ul>
<pre><code>HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(BodyPublisher.fromFile(Paths.get("file.json")))
.build()</code></pre>
<h3>第三步:send</h3>
<ul>
<li>http client可以用来发送多个http request</li>
<li>请求可以被以同步或异步方式发送</li>
</ul>
<h4>1. 同步发送</h4>
<p>同步发送API阻塞直到HttpResponse返回</p>
<pre><code>HttpResponse<String> response =
client.send(request, BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());</code></pre>
<h4>2. 异步发送</h4>
<ul>
<li>异步发送API立即返回一个CompletableFuture</li>
<li>当它完成的时候会获得一个HttpResponse</li>
</ul>
<pre><code>client.sendAsync(request, BodyHandler.asString())
.thenApply(response -> { System.out.println(response.statusCode());
return response; } )
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);</code></pre>
<p><em>※CompletableFuture是在java8中加入的,支持组合式异步编程</em></p>
<h2>二、从提升单机并发处理能力的技术来看HttpClient的实现细节</h2>
<h2>提升单机并发处理能力的技术</h2>
<ul>
<li>多CPU/多核</li>
<li>多线程</li>
<li>非阻塞(non-blocking)</li>
</ul>
<h3>Java NIO</h3>
<p>Java NIO为Java带来了非阻塞模型。<br><img src="/img/remote/1460000016886425" alt="NIO" title="NIO"></p>
<h3>Lambda表达式</h3>
<ul>
<li>Lambda表达式可以方便地利用多CPU。</li>
<li>Lambda表达式让代码更加具有可读性</li>
</ul>
<h3>回调</h3>
<p>假设以下代码是一个聊天应用服务器的一部分,该应用以Vert.x框架实现。<br><em>(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)</em><br>向connectHandler方法输入一个Lambda表达式,每当有用户连接到聊天应用时,都会调用该Lambda表达式。这就是一个回调。<br>这种方式的好处是,应用不必控制线程模型——Vert.x框架为我们管理线程,打理好一切相关复杂性,程序员只考虑和回调就够了。</p>
<pre><code>vertx.createServer()
.connectHandler(socket -> {
socket.dataHandler(new User(socket, this));
}).listen(10_000);</code></pre>
<p>注意,这种设计里,不共享任何状态。对象之间通过向事件总线发送消息通信,根本不需要在代码中添加锁或使用synchronized关键字。并发编程变得更加简单。</p>
<h3>Future</h3>
<p>大量的回调会怎样?请看以下伪代码</p>
<pre><code>(1)->{
(2)->{
(3)->{
(4)->{}
}
}
}</code></pre>
<p>大量回调会形成“末日金字塔”。</p>
<p>如何破解? 使用Future</p>
<pre><code>Future1=(1)->{}
Future2=(Future1.get())->{}
Future3=(Future2.get())->{}
Future4=(Future3.get())->{}</code></pre>
<p>但这会造成原本期望的并行处理,变成了串行处理,带来了性能问题。<br>我们真正需要的是将Future和回调联合起来使用。下面将要讲的CompletableFuture就是结合了Future和回调,其要点是组合不同实例而无需担心末日金字塔问题。</p>
<h3>CompletableFuture</h3>
<pre><code>(new CompletableFuture()).thenCompose((1)->{})
.thenCompose((2)->{})
.thenCompose((3)->{})
.thenCompose((4)->{})
.join()</code></pre>
<h3>Reactive Streams</h3>
<p>Reactive Streams是一个倡议,它提倡提供一种带有<strong>非阻塞背压</strong>的异步流处理的标准(<em>Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure</em>)。</p>
<p>JDK 9中的java.util.concurrent.Flow中的概念,与Reactive Streams是一对一对等的。java.util.concurrent.Flow是Reactive Streams标准的实现之一。</p>
<h3>多CPU的并行机制让处理海量数据的速度更快,消息传递和响应式编程让有限的并行运行的线程执行更多的I/O操作。</h3>
<h2>HttpClient的实现细节</h2>
<h3>请求响应的body与reactive streams</h3>
<ul>
<li>请求响应的body暴露为reactive streams</li>
<li>http client是请求的body的消费者</li>
<li>http client是响应的body的生产者</li>
</ul>
<p><img src="/img/remote/1460000016886426" alt="请求响应的body与reactive streams" title="请求响应的body与reactive streams"></p>
<h3>HttpRequest内部</h3>
<pre><code>public abstract class HttpRequest {
...
public interface BodyPublisher
extends Flow.Publisher<ByteBuffer> { ... }
}</code></pre>
<h3>HttpResponse的内部</h3>
<pre><code>public abstract class HttpResponse<T> {
...
public interface BodyHandler<T> {
BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
}
public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> { ... }
}</code></pre>
做好云平台架构需要哪些能力
https://segmentfault.com/a/1190000014119688
2018-04-01T17:57:38+08:00
2018-04-01T17:57:38+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
1
<p>这儿讲的平台,是指<a href="https://link.segmentfault.com/?enc=pl6ARHQ6w3zCAAOEIrohwg%3D%3D.rjP0Pn%2Be3iQ7eS2dO5QzYBKR9zn8VrY2LF7f1%2F7jk3ytTfPezbuSycr1fz4RsKeN%2FVPhOQGE%2BALn3TsfyeGMvQ%3D%3D" rel="nofollow">计算平台</a>(conputing platform),这是一种环境,软件在这种环境里运行。更进一步,云平台,我们把它定义为企业级的云计算化的平台,直接面向业务的软件运行在这个平台上,并且利用这个平台环境,新的业务软件可以迅速被开发出来。云平台涵盖了硬件、运行时代码库、框架、服务及服务管理系统等。</p>
<p><img src="/img/remote/1460000014119699" alt="架构师" title="架构师"></p>
<p>做好云平台,做出一个安全、稳定、高效的,能支持企业级大规模业务处理的云平台,需要哪些基本能力呢?</p>
<ul>
<li>熟悉平台所服务的业务环境</li>
<li>掌握软件系统的工作原理</li>
<li>拥有架构理论及经验</li>
<li>项目管理</li>
<li>文档及表达</li>
</ul>
<p><!--more--></p>
<h2>熟悉平台所服务的业务环境</h2>
<p>业务是信息系统的灵魂,只有能够支撑业务运营、业务发展的企业信息系统,才是最适合企业的,也是最值得企业投入资源去开发和维护的。云平台作为企业信息系统的底层支撑系统,更是要着眼于业务。核心业务有哪些、非核心业务有哪些、企业着力开拓的新兴业务有哪些,业务规模都有多大,业务跨越的地理范围有多大等等,在了解了这些的基础上,你所架构的平台系统,才更适合企业。</p>
<p>比如企业业务是跨国的,那么平台系统如果不支持跨语言、跨文化的话,那么很可能会成为业务发展的掣肘,反之则助力企业发展</p>
<h2>掌握软件系统的工作原理</h2>
<p>其实掌握软件系统的工作原理更多地是指选型的能力,这涉及到你的工具箱的广度。 <br>在你根据业务进行一番比较抽象的思考后,你决定了一个基本架构的结构图,然后你需要选型,或许你需要选择一个分布式缓存系统,那么你至少需要知道有哪些已经存在了的分布式缓存系统,并且它们各自的工作原理是什么,然后才能判断它们是否适用,或者是否有必要自己动手对其进行包装或创造一种全新的缓存系统。</p>
<p>这儿列举一些基本的你可能需要掌握或了解的软件系统的工作原理</p>
<ul>
<li>开发语言(比如Java)</li>
<li>操作系统(Linux或Windows)</li>
<li>DNS(域名解析)</li>
<li>CDN(内容分发网络)</li>
<li>WEB服务器(基于互联网提供的服务将越来越多)</li>
<li>负载均衡(比如Nginx)</li>
<li>消息队列(比如ActiveMQ)</li>
<li>缓存系统(比如Redis)</li>
<li>数据库(比如mysql)及ORM框架(比如MyBatis)</li>
</ul>
<p>如果还可以掌握以下软件系统工作原理的话,你设计的云平台或许能够应对更大规模的企业业务</p>
<ul>
<li>分布式资源协调</li>
<li>大数据框架(比如Storm)</li>
<li>人工智能框架(比如)</li>
</ul>
<h2>拥有架构理论及经验</h2>
<p>架构理论可以指导你开展架构工作,可以指导你如何对业务系统进行分解以得到子系统、子系统之间的交互关系,指导你考虑系统的运行环境及制约因素、指导你考虑运维需求等。而经验可以让你更快地作出判断,更快地在各种需求之间找到平衡点。</p>
<p>架构理论有许多种,常见的系统架构理论有三层架构、JavaEE架构、SOA架构、微服务架构、CAP理论,常见的企业架构理论有开放组体系结构框架(TOGAF)、联邦体系架构框架(FEA)、美国国防部架构框架(DODAF)。</p>
<h2>项目管理</h2>
<p>项目管理知识可以指导你的架构的实现更加成功。在企业文化及上下文环境的制约下,充分考虑范围、时间、成本、进度、采购、质量、团队、风险,然后作出的架构决定,更加真实,距离成功的实现更进一步了。 一般如果你是既担任架构师又担任项目经理的话,你就不得不考虑这些了:)</p>
<h2>文档及表达</h2>
<p>【英】Simon Brown著的《程序员必读之软件架构》一书(邓钢译 ISBN:987-115-37107-2),是这么解释架构的。</p>
<blockquote>架构作为名词来解释时,概括起来都与结构有关:将产品分解为一系列组件、模块和交互。这需要考虑整个产品.. <br>架构作为动词来解释时,包括了理解你需要构建什么、设定愿景以便进行构建和做出恰当的涉及决策...关键在于,架构是关于交流愿景和引入技术领导力的,这样参与构建产品的每个人都能理解这个愿景,并为产品的成功做出积极贡献</blockquote>
<p>你对产品的分解、你所设定的愿景、你作出的决策,都需要很好地记述下来,表达出来,以便每个人都能准确理解,并参与、贡献。</p>
微服务与Spring Cloud
https://segmentfault.com/a/1190000013803444
2018-03-17T20:38:07+08:00
2018-03-17T20:38:07+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
1
<p>微服务是继SOA之后流行起来的一种系统架构模式。因它紧随SOA之后,所以有必要对他们先作个比较。</p>
<h2>SOA vs 微服务</h2>
<p>关于二者的比较表格,我在谷歌上搜索的<a href="https://link.segmentfault.com/?enc=PYhk%2BTkVxPrRObwvt0Br5g%3D%3D.lh4olxaQe2UTSMmy2gPoflidXwnrIUzSt6ZWdAvvnWP3ooqI9Kj1wuWRbttn%2Bj3OBNjHw9Mln5%2BhwRKgOhxN6DeL4j%2FEOQqvTDXnkNT%2BJ4s%3D" rel="nofollow">一篇文章</a>分析的挺好,现引用如下。</p>
<table>
<thead><tr>
<th>面向服务架构</th>
<th>微服务架构</th>
</tr></thead>
<tbody>
<tr>
<td>出现于1990's年代</td>
<td>出现于2000's年代</td>
</tr>
<tr>
<td>最大化应用服务的重用性</td>
<td>关注解耦</td>
</tr>
<tr>
<td>系统变化需要修改整体</td>
<td>系统变化是创建新服务</td>
</tr>
<tr>
<td>DevOps和持续发布开始变得流行但不是主流</td>
<td>重点关注DevOps和持续发布</td>
</tr>
<tr>
<td>聚焦于业务系统重用</td>
<td>“边界上下文”越发重要</td>
</tr>
<tr>
<td>使用ESB通信</td>
<td>使用简单消息系统通信</td>
</tr>
<tr>
<td>支持多种消息协议</td>
<td>使用轻量级协议诸如:HTTP, REST等</td>
</tr>
<tr>
<td>对部署在其上的所有服务使用通用平台</td>
<td>通常使用云平台而非应用服务器</td>
</tr>
<tr>
<td>Docker不太流行</td>
<td>容器与微服务工作的非常协调</td>
</tr>
<tr>
<td>SOA服务共享数据存储</td>
<td>每个微服务可以拥有独立的存储服务</td>
</tr>
<tr>
<td>通用的治理和标准</td>
<td>松散治理,关注团队协作与自由选择</td>
</tr>
</tbody>
</table>
<p><img src="/img/remote/1460000013803449" alt="SOA vs MicroService" title="SOA vs MicroService"></p>
<p><!--more--></p>
<h2>微服务</h2>
<p>微服务这么流行,肯定有其优势所在。不过也不能不看到它的劣势噢。</p>
<h3>优势</h3>
<ul>
<li>每个模块都是独立的,所以你可以选择不同的开发语言以获得语言级别的好处</li>
<li>每个模块都可以有自己的数据库,NoSQL或者关系型</li>
<li>开发人员构建和维护微服务所以他们对模块非常熟悉。跨功能的成员一些协作完成服务</li>
</ul>
<h3>劣势</h3>
<ul>
<li>服务调用其它服务,所以对于大型项目来说,难以跟踪调用过程以及监控服务</li>
<li>服务之间通过RESTful API调用所以性能可能没有整体的进程内通信的系统高</li>
<li>难以调试,尤其是一个服务调用了一系列其它服务</li>
</ul>
<h2>SpringCloud</h2>
<p>如何扬长避短,更好更方便地利用微服务呢。</p>
<h3>SpringCloud提供以下特性来强化微服务的优势、弥补劣势</h3>
<ul>
<li>分布式/版本化的配置</li>
<li>服务注册和发现</li>
<li>路由</li>
<li>服务到服务的调用</li>
<li>负载均衡</li>
<li>熔断(Circuit Breaker)</li>
<li>全局锁</li>
<li>leader选举及集群状态</li>
<li>分布式消息</li>
</ul>
<h2>SpringCloud主要项目</h2>
<h3>Spring Cloud Config</h3>
<p>统一配置中心。</p>
<h3>Spring Cloud Netflix</h3>
<p>服务发现</p>
<h3>Spring Cloud Bus</h3>
<p>一个事件总线,利用分布式消息系统将服务和服务实例连到一起。可用于在集群内传播状态变化(如配置变化事件)</p>
<h3>Spring Cloud for Cloud Foundry</h3>
<p>集成你的应用到Pivotal Cloud Foundry,提供并且让实现SSO和OAuth2来保护资源变得更加容易。</p>
<h3>Spring Cloud Open Service Broker</h3>
<p>提供一个用于构建实现了Open Service Broker API的服务的Broker的起始点</p>
<p>Open Service Broker API连接开发者到一个全球的服务生态环境,该项目给开发者、ISVs、SaaS厂商提供一个单一的、简单的和完美的方式去发布服务到原生云平台上(诸如Cloud Foundry, OpenShift, Kubernetes)</p>
<p><a href="https://link.segmentfault.com/?enc=ZXHIQJ3mvFMNk%2BddOxySYg%3D%3D.xGtvrCoOT7T6vPDe9EloSoJgNlyNadmXnxaW4Saje7mBPhKzZPER1sYzAebSyMHS" rel="nofollow">Open Service Broker 项目</a></p>
<p><a href="https://link.segmentfault.com/?enc=l227WyS08QNDipBuXybVWg%3D%3D.0zR81GjbqK7wrywuOj2ABNOU4Fvzoa%2FgwnoOJA3KxFM35gt4FUPFXM%2F6%2FgNMRWAWhVdYaS786P0Lh756UHT0Ig%3D%3D" rel="nofollow">Open Service Broker API定义</a></p>
<h3>Spring Cloud Cluster</h3>
<p>提供基于Zookeeper、Redis、Hazelcast、Consul的领头选举、通用状态机模式的抽象及实现。</p>
<h3>Spring Cloud Consul</h3>
<p>基于Hashicorp Consul的服务发现及配置管理。</p>
<h3>Spring Cloud Security</h3>
<p>提供对基于负载均衡的OAuth2 rest client和authentication header的支持,依赖了Zuul proxy。</p>
<h3>Spring Cloud Sleuth</h3>
<p>应用于Spring Cloud的分布式追踪功能,与Zipkin, HTrace and log-based (e.g. ELK) tracing 相兼容。</p>
<h3>Spring Cloud Data Flow</h3>
<p>一个cloud-native的服务编排,易用的DSL、drag-and-drop GUI,REST-APIs 一起全面简化了基于服务编排的数据管道。</p>
<h3>Spring Cloud Stream</h3>
<p>一个轻量级的事件驱动微服务框架,便于快速构建连接到外部系统的应用。简单的声明模型可以使用Apache Kafka或RabbitMQ在Spring Boot应用间收发消息。</p>
<h3>Spring Cloud Stream App Starters</h3>
<p>一系列基于Spring Boot的Spring集成应用程序,提供与外部应用的集成。</p>
<h3>Spring Cloud Task</h3>
<p>一个短期的微服务框架,快速构建执行有限数量的数据处理的应用。简单的声明就可以增加功能性或非功能性特性到Spring Boot中。</p>
<h3>Spring Cloud Task App Starters</h3>
<p>一系列单机版的可执行应用程序,可以拥有按需用例:比如数据库迁移、机器学习、定时操作。这些应用可以运行于各种独立平台,比如:Cloud Foundry、Apache Yarn、 ApacheMesos、Kubernetes、Docker甚至你的笔记本上。</p>
<h3>Spring Cloud Zookeeper</h3>
<p>基于Apache Zookeeper的服务发现及配置管理。</p>
<h3>Spring Cloud Connectors</h3>
<p>使得运行于各种平台上的PaaS应用能够方便地连接到后端服务,比如数据库、消息Broker。(这个项目原先叫作Spring Cloud)</p>
<h3>Spring Cloud Starters</h3>
<p>Spring Boot风格的启动项目,简化服务消费方Spring Cloud的依赖管理。</p>
<h3>Spring Cloud CLI</h3>
<p>Spring Boot CLI插件,用来快速使用Groovy语言创建Spring Cloud组件应用</p>
<h3>Spring Cloud Contract</h3>
<p>是一揽子有关帮助用户成功地实现“消费端驱动契约”方式解决方案</p>
<h3>Spring Cloud Gateway</h3>
<p>是一款基于智能的、可编程的路由的<a href="https://link.segmentfault.com/?enc=iOOeGAm4pYxzp3Ghr7HhJg%3D%3D.lh1MJ4iOwEeFO%2Fs6nbAkjCizdl1ykQKTj0IwTIN%2Bba8%3D" rel="nofollow">Project Reactor</a></p>
<h3>Spring Cloud OpenFeign</h3>
<p>为Spring Boot应用提供通过自动化配置和绑定到Spring Environment和其它编程模型风格的集成。</p>
<p><a href="https://link.segmentfault.com/?enc=4bxh6a%2BpH78l%2FBBGeMAk8Q%3D%3D.1ryhaR8BN8CqIUurZAjhZTJLSThyS1iBj6iixW5GtEDsiWv0Kei7Q7wxQl8yNyTZQS%2FbQNeSWCeUNhcY46Lk9oKSAz1AXgiidT42E8J6aok%3D" rel="nofollow">原文发表于http://www.yesdata.net/2018/03/15/microservice-and-spring-cloud/</a></p>
<p>·</p>
分布式系统开发工具包 —— 基于Hessian的HTTP RPC调用技术
https://segmentfault.com/a/1190000013797902
2018-03-17T13:49:16+08:00
2018-03-17T13:49:16+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
0
<p><img src="/img/remote/1460000013797908" alt="caucho" title="caucho"></p>
<p>Hessian官网:<a href="https://link.segmentfault.com/?enc=FANl83FgtMvNFKNQ0icROQ%3D%3D.xUB0D4LvipgMRBScvpvITgoXngL6i2NZ7xy2exN3f3s%3D" rel="nofollow">http://hessian.caucho.com/</a> </p>
<p>hessian是二进制web service协议。</p>
<h2>Hessian介绍</h2>
<p>创建Hessian服务包括四个步骤:</p>
<ol>
<li>创建Java接口,用于提供公开服务</li>
<li>使用HessianProxyFactory创建客户端</li>
<li>创建服务实现类</li>
<li>在servlet引擎中配置服务</li>
</ol>
<p><!--more--></p>
<h5>HelloWorld服务</h5>
<pre><code>public interface BasicAPI {
public String hello();
}</code></pre>
<h5>服务实现</h5>
<pre><code>public class BasicService extends HessianServlet implements BasicAPI {
private String _greeting = "Hello, world";
public void setGreeting(String greeting)
{
_greeting = greeting;
}
public String hello()
{
return _greeting;
}
}</code></pre>
<h5>客户端实现</h5>
<pre><code>String url = "http://hessian.caucho.com/test/test";
HessianProxyFactory factory = new HessianProxyFactory();
BasicAPI basic = (BasicAPI) factory.create(BasicAPI.class, url);
System.out.println("hello(): " + basic.hello());</code></pre>
<h4>部署标准web.xml</h4>
<pre><code><web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
<init-param>
<param-name>home-class</param-name>
<param-value>example.BasicService</param-value>
</init-param>
<init-param>
<param-name>home-api</param-name>
<param-value>example.Basic</param-value>
</init-param>
</servlet>
<servlet-mapping>
<url-pattern>/hello</url-pattern>
<servlet-name>hello</servlet-name>
</servlet-mapping>
</web-app></code></pre>
<h2>Hessian序列化</h2>
<p>Hessian类可以用来做序列化与反序列化</p>
<h5>序列化</h5>
<pre><code>Object obj = ...;
OutputStream os = new FileOutputStream("test.xml");
Hessian2Output out = new Hessian2Output(os);
out.writeObject(obj);
os.close();</code></pre>
<h5>反序列化</h5>
<pre><code>InputStream is = new FileInputStream("test.xml");
Hessian2Input in = new Hessian2Input(is);
Object obj = in.readObject(null);
is.close();</code></pre>
<p>如果要序列化比基础类型或String类型更加复杂的java对象,务必确保对象实现了java.io.Serializable接口。</p>
<h2>Hessian处理大量数据</h2>
<p>分布式应用需要发送大量二进制数据时,使用InputStream会更加有效率,因为它避免了分配大量byte数组。方法<br>参数中只有最后一个参数可能是InputStream,因为数据是在调用过程中读的。</p>
<p>下面是一个上传文件的API的例子</p>
<pre><code>package example;
public interface Upload {
public void upload(String filename, InputStream data);
}</code></pre>
<p>如果返回结果是InputStream,客户端必须在finally块中调用InputStream.close()方法,因为Hessian不会关闭<br>底层HTTP流,直到所有数据被读取并且input stream被关闭。</p>
<pre><code>文件下载API:
package example;
public interface Download {
public InputStream download(String filename, InputStream data);
}
文件下载实现:
InputStream is = fileProxy.download("test.xml");
try {
... // read data here
} finally {
is.close();
}</code></pre>
<p><a href="https://link.segmentfault.com/?enc=UoCP%2FTA7P%2BQO%2F%2FgrTQq5dA%3D%3D.LmM8qbGTYAj71JrxP3Cy%2BtnGc8ypQyEKjiEUKD6pIKIlzCI5Xef2Vsk%2BN8iL1%2B23" rel="nofollow">原文发布于:http://www.yesdata.net/2018/03/11/hessian/</a></p>
深入理解volatile类型——从Java虚拟机内存模型角度
https://segmentfault.com/a/1190000011212090
2017-09-17T22:07:57+08:00
2017-09-17T22:07:57+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
0
<h2>一、前言</h2>
<p>在java多线程编程中,volatile可以用来定义轻量级的共享变量,它比synchronized的使用成本更低,因为它不会引起线程上下文的切换和调度。所谓知己知彼、百战不殆。本文从JVM内存模型角度,探讨volatile的实现原理。在明白了volatile的实现原理后,再回过头来使用它,会有一种一览众山小的感觉吧,使用起来会更加得心应手。</p>
<h2>二、Java内存模型(JMM)</h2>
<h3>1、并发编程关键问题</h3>
<p>多线程编程涉及到两个关键问题,线程之间的通信与同步。通信是指线程之间传递信息,同步是指控制线程操作的执行顺序。通过共享内存或者消息通知这两种方法,可以实现通信或同步。基于共享内存的线程通信是隐式的,线程同步是显式的;而基于消息通知的线程通信是显式的,线程同步是隐式的。JAVA是前者,即基于共享内存的隐式线程通信、显式线程同步。</p>
<h3>2、happens-before模型</h3>
<p>JMM呈现给程序员的模型是happens-before模型,即:</p>
<ul>
<li>顺序规则:(单)线程中的写操作的结果,happens before于于任意后续操作。</li>
<li>锁规则: 锁的解锁,happens before于于锁的获取或加锁。</li>
<li>volatile变量规则:volatile写操作,happens before于后续该变量的读操作是可见的。</li>
<li>传递性:A操作happens before于B操作,B操作happens before于C操作,则A操作happens before于C操作</li>
</ul>
<p>这儿的happens-before,并不是指操作先于后续操作执行,而是指操作结果对于后续结果是可见的。</p>
<h3>3、可见性</h3>
<p>在JMM中,每个线程的内存由两层构成:线程的“本地内存”、“主内存”。“本地内存”是JMM的一个抽象,本身是不存在的,它包括缓存、寄存器、写缓冲区、编译器及CPU的优化等。共享变量存放在“主内存”中,“本地内存”中存放的是共享变量的副本。当线程中发生对共享变量的写操作时,并不是直接写到“主内存”中的,而是先写到“本地内存”的写缓冲区中,只有当刷新(flush)到主内存后,才可能被其它线程加载到其“本地内存”中,此时我们说该共享变量是对其它线程“可见”的,反之如果没有刷新(flush)到主内存,就是对于其它线程“不可见”的。</p>
<blockquote><p>JMM就是通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供可见性的保证的</p></blockquote>
<h2>三、volatile类型的内存语义</h2>
<p>happens-before规则提供了对volatile变量可见性的保证,即volatile变量的写操作,对后续任意对该变量的读操作是可见的。</p>
<h2>四、volatile类型的JMM实现</h2>
<p>再深入一些,JMM是如何实现happens-before模型中的volatile规则的呢?是通过限制这两种操作的重排序实现的。</p>
<table>
<thead><tr>
<th>重排序规则</th>
<th colspan="3">第二个操作</th>
</tr></thead>
<tbody>
<tr>
<td>第一个操作</td>
<td>volatile读</td>
<td>volatile写</td>
<td>普通读/写</td>
</tr>
<tr>
<td>volatile读</td>
<td>禁止重排序(1)</td>
<td>禁止重排序(2)</td>
<td>禁止重排序(3)</td>
</tr>
<tr>
<td>volatile写</td>
<td>禁止重排序(4)</td>
<td>禁止重排序(5)</td>
<td>-</td>
</tr>
<tr>
<td>普通读/写</td>
<td>-</td>
<td>禁止重排序(6)</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>(1) 第一个操作是volatile读时,不可以重排序,否则读出来的结果,可能是被修改过了的。<br>(2)(5)(6),第二个是volatile写时,所有操作都不可以被重排序于其后面,因为要确保其写的结果对于后续操作可见。</p>
<h2>五、volatile类型使用的注意事项</h2>
<ul>
<li>多个volatile操作或复合操作,整体上不具有有序性</li>
<li>适用于对volatile变量的写操作很少而读操作很多的环境</li>
</ul>
从URI创建artemis core的ServerLocator实例过程的思考
https://segmentfault.com/a/1190000008472933
2017-02-24T15:49:16+08:00
2017-02-24T15:49:16+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
0
<h2>背景</h2>
<p>broker.xml中有这么一段:</p>
<pre><code><connectors>
<!-- Connector used to be announced through cluster connections and notifications -->
<connector name="artemis">tcp://localhost:61618</connector>
</connectors></code></pre>
<p>那么,artemis中是如何将URI“tcp://localhost:61618”解析成ServerLocator的呢?</p>
<h2>URI工厂</h2>
<p>在artemis中,URI的处理,是通过URIFactory类进行加工的。<br>URIFactory类位于artemis-commons包中的org.apache.activemq.artemis.utils.uri命名空间下,它的主要功能是注册URISchema、委托URISchema创建相应的对象。URIFactory类是一个模板类,用户可以指定需要通过URI创建的对象的类型T,以及创建对象时需要传入的参数类型P。比如我们想要通过URI创建ServerLocator类型的对象,则T传入ServerLocator接口类型,P传入String类型。如其名称,它采用的是工厂方法设计模式。同时它还隐藏了策略模式,通过注册URISchema方法,引入包含通过URI创建相应对象的策略的URISchema,将创建对象的策略委托给了URISchema。</p>
<h2>URI工厂中的策略</h2>
<p>具体注册URISchema策略的执行者是ServerLocatorParser类。该类位于artemis-core-client模块中的org.apache.activemq.artemis.uri命名空间下。该类的构造函数,直接注册了4中策略:InVM、TCP、UDP、JGroup四种SchemaURI解析及策略。</p>
<pre><code> public ServerLocatorParser() {
registerSchema(new InVMServerLocatorSchema());
registerSchema(new TCPServerLocatorSchema());
registerSchema(new UDPServerLocatorSchema());
registerSchema(new JGroupsServerLocatorSchema());
}</code></pre>
<p>策略的载体——URISchema类,位于artemis-commons包中的org.apache.activemq.artemis.utils.uri命名空间下。它也是一个模板类,,用户可以指定需要通过URI创建的对象的类型T,以及创建对象时需要传入的参数类型P。它的职责是提供通过URI创建相应对象的具体策略。</p>
<p>URISchema类是策略接口的定义,其实现类InVMServerLocatorSchema、TCPServerLocatorSchema、UDPServerLocatorSchema、JGroupsServerLocatorSchema,是具体策略的实现。四个具体策略的实现类,位于artemis-commons包中的org.apache.activemq.artemis.uri.schema.serverLocator命名空间下。</p>
<h2>URI策略-TCP策略的实现</h2>
<p>URISchema类采用了模板方法模式,其中要求子类必须实现internalNewObject抽象方法。我们以TCPServerLocatorSchema这个子类为例来看看它是如何实现internalNewObject抽象方法的。代码如下:</p>
<pre><code> ConnectionOptions options = newConnectionOptions(uri, query);
List<TransportConfiguration> configurations = TCPTransportConfigurationSchema.getTransportConfigurations(uri, query, TransportConstants.ALLOWABLE_CONNECTOR_KEYS, name, NettyConnectorFactory.class.getName());
TransportConfiguration[] tcs = new TransportConfiguration[configurations.size()];
configurations.toArray(tcs);
if (options.isHa()) {
return ActiveMQClient.createServerLocatorWithHA(tcs);
} else {
return ActiveMQClient.createServerLocatorWithoutHA(tcs);
}</code></pre>
<p>可见它主要是利用了TCPTransportConfigurationSchema的静态方法getTransportConfigurations,来根据uri解析得到TransportConfiguration对象列表,然后使用ActiveMQClient工具类的createServerLocatorWithHA或createServerLocatorWithoutHA类创建ServerLocator接口实例的。</p>
<h2>后记</h2>
<p>本文主要是讲解从URI创建artemis core的ServerLocator实例过程,所以不对ServerLocator作过多讲解,这儿只给出ServerLocator所处位置:<br>ServerLocator接口位于artemis-core-client模块中的org.apache.activemq.artemis.api.core.client命名空间下。<br>两个实现类,ServerLocaorInternal类和ServerLocatorImpl类,位于artemis-core-client模块中的org.apache.activemq.artemis.api.core.client.impl命名空间下。</p>
<h2>相关模块、命名空间和类的汇总参考</h2>
<p>artemis-commons</p>
<pre><code>org.apache.activemq.artemis.utils.uri
URIFactory
URISchema
org.apache.activemq.artemis.uri.schema.serverLocator
InVMServerLocatorSchema
TCPServerLocatorSchema
UDPServerLocatorSchema
JGroupsServerLocatorSchema</code></pre>
<p>artemis-core-client</p>
<pre><code>org.apache.activemq.artemis.uri
ServerLocatorParser
org.apache.activemq.artemis.api.core
TransportConfiguration
org.apache.activemq.artemis.api.core.client
ServerLocator
ActiveMQClient
org.apache.activemq.artemis.api.core.client.impl
ServerLocaorInternal
ServerLocatorImpl
</code></pre>
手把手教你编写入门级redis客户端
https://segmentfault.com/a/1190000007824903
2016-12-16T12:53:09+08:00
2016-12-16T12:53:09+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
1
<p>Redis是开源的、基于内存的数据结构存储系统,可用作数据库、缓存以及消息代理方面。Redis支持许多种数据结构,并内置了丰富的诸如冗余、脚本、事务、持久化等功能,深受业界喜爱,被各种业务系统广泛使用。为了方便使用,Redis官网推荐了针对各种编程语言的多种客户端,支持java、c#、python、c++等主流编程语言。那么大家会问,既然Redis客户端已经这么丰富了,为什么还要尝试自己编写客户端?我的看法是,知己知彼,自己尝试制作Redis客户端,不仅可以加深对Redis的了解,而且可以通晓Redis客户端的原理,为今后的更好地使用、乃至定制改造Redis作好充分准备。</p>
<h2>知识准备</h2>
<p>要想亲自开发Redis客户端,需要以下知识:<br>1、网络编程基础<br>2、熟悉Redis协议<br>3、了解Redis的基本操作<br>另外文中的例子将会采用java编写,因此最好有基本的java编程知识。</p>
<h2>面向读者</h2>
<p>本文面向Redis各层次使用者。</p>
<h2>Redis Protocal</h2>
<p>Redis协议被称为:RESP (REdis Serialization Protocol),客户端通过TCP协议连接到客户端的6379端口(默认端口)。<br>RESP协议是在Redis1.2中引入的,不过现在已经是Redis2.0中的标准协议了。所以你应该再Redis客户端中实现这个协议。</p>
<h3>RESP描述</h3>
<p>RESP其实是一个序列化协议,支持简单字符串、错误、整数、整块字符串和数组。数据类型依赖头文字,分别表示如下:<br><strong>简单字符串</strong>的头文字是“+”<br><strong>错误</strong>的头文字是“-”<br><strong>整数</strong>的头文字是“:”<br><strong>整块字符串</strong>的头文字是“$”<br><strong>数组</strong>的头文字是“*”</p>
<h3>RESP在请求-响应模型中的用法</h3>
<ul>
<li><p>客户端向Redis服务器发送命令,以RESP整块字符串数组的形式。</p></li>
<li><p>服务器端根据命令的结果,选择适宜的一种RESP类型返回</p></li>
</ul>
<h4>简单字符串</h4>
<p>简单字符串是以半角加号开头,后跟随着不含回车换行的字符串,然后以回车换行结尾。<br>举例如下:+OK\r\n<br>简单字符串是非二进制安全的,如果需要二进制安全,可使用“整块字符串”。</p>
<h4>错误</h4>
<p>错误和简单字符串类似,但头文字换成半角减号了。后面跟随的文字,可以视为错误消息内容。<br>举例如下:<br>-ERR unknown command 'foobar'<br>-WRONGTYPE Operation against a key holding the wrong kind of value</p>
<h4>整数</h4>
<p>整数与简单字符串类似,头文字为半角冒号。<br>举例如下:<br>:0\r\n<br>:1000\r\n</p>
<h4>整块字符串</h4>
<p>整块字符串可以用来标示二进制安全的、最大512MB长度的字符串。它以$符号开头,后跟随实际字符串长度,以回车换行结尾,后跟随实际字符串,再最终以回车换行结尾。<br>举例如下:<br>$6\r\nfoobar\r\n<br>空字符串表现形式如下:$0\r\n\r\n<br>nil表现形式如下:$-1\r\n\r\n</p>
<h4>数组</h4>
<p>数组以半角星号开头,后接数组中元素个数,然后以回车换行结尾,然后后接各个元素。<br>举例如下:<br>空数组:*0\r\n<br>包含两个整块字符串的数组:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n<br>包含三个整数的数组:*3\r\n:1\r\n:2\r\n:3\r\n<br>数组还支持嵌套。</p>
<h2>Redis客户端原理</h2>
<p>要实现和Redis服务端通信,首先需要与Redis服务端建立TCP通信连接,然后使用上述的RESP协议,将想要执行的Redis命令发送至服务端,并等待服务端响应,然后接收到响应结果,展示给用户。</p>
<p>以下代码实现了一个简单的获取info的操作。</p>
<pre><code>package my_redis_client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.CharBuffer;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
//定义redis服务端默认端口
int port = 6379;
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
//创建tcp连接
socket = new Socket("localhost", port);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
//传送info命令
//客户端向Redis服务器发送命令,以RESP整块字符串数组的形式
out.println("*1\r\n$4\r\ninfo\r\n");
System.out.println("Redis command wat sent successfully.");
//接收服务器的回复
CharBuffer response = CharBuffer.allocate(1024);
int readedLen = in.read(response);
String responseBody = response.flip().toString();
//输出服务器的回复
System.out.println(responseBody);
}
catch(Exception e) {
e.printStackTrace();
}
finally {
//最后关闭相关的流
if (out != null){
out.close();
out = null;
}
if (in != null) {
try {
in.close();
}
catch(IOException e){
e.printStackTrace();
}
in = null;
}
if (socket != null) {
try {
socket.close();
}
catch(IOException e){
e.printStackTrace();
}
socket = null;
}
}
}
}
</code></pre>
<p>运行后,系统将会在命令行界面输出info的执行结果。</p>
<h2>结尾</h2>
<p>根据上述代码所描述的方法,就可以继续扩展客户端的功能,实现Redis各种命令了。</p>
<h2>笔者实现</h2>
<h4>源码请参考</h4>
<p><a href="https://link.segmentfault.com/?enc=HrNFHiAfVfokTJLONqNNUQ%3D%3D.HwSGD8wRDpSxUreYvlG0I%2FR7gb1RfeJs8YSV%2F2dSXEYRvvjNhSjRJ2LsnzFmEhqyrqEoyNjHva77oC1hGfwN%2Fw%3D%3D" rel="nofollow">https://github.com/yourcaptai...</a></p>
<h4>maven中央仓库</h4>
<pre><code><dependency>
<groupId>net.yesdata</groupId>
<artifactId>dudu-RESP-interpreter</artifactId>
<version>1.0.4</version>
</dependency></code></pre>
<h4>maven中央仓库地址</h4>
<p><a href="https://link.segmentfault.com/?enc=YsA4UQMdq5ayGLUZmKzEtw%3D%3D.N9yT%2BKylsUxwyLvFWbvVhOefyx9sS4%2FbjpgH7CHBXW4LLTi8fm7ZfBvGEzcd%2FiqcxLfQt0%2F1XX0pAUaFGZVA%2Bg%3D%3D" rel="nofollow"></a><a href="https://link.segmentfault.com/?enc=UGRkVoLshk5b9SvfiRMkTw%3D%3D.lfGlDZBLDtU3J1R7soxewI%2FbWhL7D%2Fc0rzlmOYAH2Gw%3D" rel="nofollow">https://oss.sonatype.org</a></p>
利用阿里云的VPC+ECS+负载均衡搭建安全的WEB服务
https://segmentfault.com/a/1190000007812038
2016-12-15T12:51:32+08:00
2016-12-15T12:51:32+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
1
<p>随着云计算时代的到来,各公有云平台都提供了各种丰富的产品供消费者使用,商品也达到了“琳琅满目”的程度了。以阿里云为例,它就提供了10多种分类、五六十种具体的服务产品。那么,中小企业在搭建WEB服务时,利用公有云的哪些产品就能快速、便捷、低成本地搭建出高安全、高可用的、可伸缩的服务呢?我就自身经验,给出如下的实践过程,供有同类需求的业内人士参考。</p>
<p>打开阿里云的首页,鼠标放到“产品”这个菜单上,默认出来的子菜单的第一个,就是“弹性计算”。既然“弹性计算”被放在默认的位置,那么我们可以猜测,这个分类下所提供的产品,应该是为广大消费者所经常使用的,也是最基本的吧。事实上,我接下来介绍的实践经验,就是利用这个分类下的“云服务器 ECS”、“专有网络 VPC”、“负载均衡”三个产品实现的。</p>
<h2>项目背景</h2>
<p>介绍一下我们接下来要实现的WEB服务的功能和构成。这是一个向企业内部1千多名员工提供按关键字搜索专业文章的服务,由2台搜索引擎、2台服务接收器组成,其中服务接收器接收来自用户的请求,通过调用搜索引擎获取数据并包装成用户需要的格式返回给用户。考虑到阿里本身是服务于许多中小企业的,那么自然而然就选择将该服务部署到阿里云上作为生产环境。</p>
<h2>专有网络 VPC</h2>
<p>强烈推荐中小企业使用VPC,尽量不使用经典网络。更多的经典网络与VPC的区别请戳这里。</p>
<p>随着业务的发展,中小企业将会租赁更多的云服务,如果使用的是经典网络的话,那么网络带宽需要根据每台ECS购买,且处于一个非隔离的网络中。而使用VPC的话,VPC内部的所有ECS可以设置为共享公网流量,且默认不对外开放,安全性极大提高。不用担心使用VPC该如何设置交换机等,参照阿里云提供的教程可轻松搞定,所花费的精力,要比设置经典网络下的ECS的防火墙等等要少得多。</p>
<blockquote><p>专有网络( Virtual Private Cloud ),基于阿里云构建出一个隔离的网络环境。您可以自定义IP 地址范围、网段、路由表和网关等。此外您也可以通过专线/VPN/GRE等连接方式实现VPC与传统数据中心互联,构建混合云业务。</p></blockquote>
<h2>负载均衡</h2>
<p>阿里云提供的负载均衡服务,非常好用。不仅有易用的配置管理界面,而且免费提供5Gbps以下的四层DDoS攻击防护,同时支持删除和添加后端云服务器,实现无缝伸缩。计费模式也具有弹性,支持按流量或按带宽计费。我提及的这个项目,虽然使用人数较多,但传输的信息量较小,因而选择按流量计费。(还有一个秘密,内网间的负载均衡是免费的。)</p>
<h2>项目生产环境结构</h2>
<p>购买4台ECS,网络环境均为VPC,然后购买1台按流量计费的公网负载均衡,购买1台免费的内网负载均衡,构成如下的拓扑图。将应用部署上去,就可以提供服务啦。</p>
<h2>项目生产环境结构</h2>
<p><img src="/img/bVGWqG?w=363&h=526" alt="图片描述" title="图片描述"></p>
<h2>如何远程访问处于VPC中的ECS?</h2>
<p>大家肯定已经知道了,可以通过公网负载均衡,将某个端口指向其中一台ECS的远程访问端口(Windows远程桌面默认端口3389,SSH默认端口22),然后远程登录到这台ECS上,再进一步远程登录到其它ECS上。</p>
<h2>VPC中的ECS需要主动访问外网的其它服务,怎么办?</h2>
<p>有两个办法:1、为ECS绑定EIP,即弹性公网IP。 2、再购买阿里云的“NAT网关服务”,通过NAT网管的端口映射实现ECS访问外网。</p>
<p>相比较而言,使用NAT网关服务,更加安全,因为弹性IP会导致你好容易布设的VPC出现“裂缝”——外部的任何人都可以通过你绑定的弹性IP扫描攻击你的VPC中的那台ECS。</p>
<h2>后记</h2>
<p>即使是小型企业,应用服务的安全性也不容忽视,否则多多少少会带来不断的麻烦,影响工作效率影响操作人员心情。如果有方便又便宜的方法,可以实现高安全的服务部署,何乐而不为呢?</p>
使用artTemplate模板开发网站(node.js + express环境)
https://segmentfault.com/a/1190000003766092
2015-09-19T09:09:35+08:00
2015-09-19T09:09:35+08:00
执着的慢行者
https://segmentfault.com/u/yesdata
5
<p>本文详细说明了如何利用artTemplate模板引擎开发网站,主要是搭配node.js、express环境进行讲解。同时在文章开头会简单介绍了模板、模板引擎概念,以及artTemplate模板引擎的发展史,比较熟悉模板、模板引擎的读者可以跳过这部分。artTemplate的语法将放在文章最后稍作说明,因为语法不是本文的重点所在,可以参考其它文章详细了解语法知识。</p>
<h2>为什么要写作这篇文章?</h2>
<p>在学习artTemplate模板时,大家自然而然地想到参考artTemplate官方公布的文档,也就是托管在GitHub中的项目的README。然而README中的讲解对于初次接触artTemplate,尤其是对初次接触模板引擎的初学者来说,略显深奥。本文的宗旨,就是详细地描述出使用artTemplate的具体步骤和做法,使初学者可以迅速地看到使用artTemplate所能达到的效果,为进一步学习和使用模板引擎作铺垫。</p>
<p>本文只是将许多参考资料加以整理而已,并不包含全新的思想等。本文中的许多例子,包括文字说明、代码等,也都是引用网络上的现成的。引用较多,在文章最后有列出主要的参考文章。</p>
<h2>目录</h2>
<ul>
<li><p>模板与模板引擎的概念</p></li>
<li><p>artTemplate模板引擎</p></li>
<li><p>作为前端引擎的artTemplate的使用方法</p></li>
<li><p>作为后端引擎的artTempalte的使用方法(搭配node.js)</p></li>
<li><p>artTemplate搭配express使用</p></li>
<li><p>artTemplate的语法</p></li>
</ul>
<h2>模板与模板引擎的概念</h2>
<p>模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎会生成一个标准的HTML文档。模板引擎不属于特定技术领域,它是跨领域跨平台的概念。在Asp下有模板引擎,在PHP下也有模板引擎,在C#下也有,甚至JavaScript、WinForm开发都会用到模板引擎技术。</p>
<p>模板引擎按实现方式,可以分类“置换型引擎 ”、“解释型引擎 ”、“编译型引擎 ”三类。<br>置换型引擎只是将指定模板内容(字符串)中的特定标记(子字符串)替换一下便生成了最终需要的业务数据,它实现简单,但其效率低下,无法满足高负载的应用需求。<br>模板引擎按运行在客户端还是运行在服务器端,可以分为“前端引擎”、“后端引擎”两类。</p>
<p>模板引擎可以让(网站)程序实现界面与数据分离,这就大大提升了开发效率,良好的设计也使得代码重用变得更加容易。我们司空见惯的模板安装卸载等概念,基本上都和模板引擎有着千丝万缕的联系。模板引擎不只是可以让你实现代码分离(业务逻辑代码和用户界面代码),也可以实现数据分离(动态数据与静态数据),还可以实现代码单元共享(代码重用),甚至是多语言、动态页面与静态页面自动均衡(SDE)等等与用户界面可能没有关系的功能。</p>
<h2>artTemplate模板引擎</h2>
<p>artTemplate是一款性能卓越的 javascript 模板引擎。它采用预编译方式让性能有了质的飞跃,并且充分利用 javascript 引擎特性,使得其性能无论在前端还是后端都有极其出色的表现。除了性能优势外,调试功能也值得一提。模板调试器可以精确定位到引发渲染错误的模板语句,解决了编写模板过程中无法调试的痛苦,让开发变得高效,也避免了因为单个模板出错导致整个应用崩溃的情况发生。</p>
<p>artTemplate引擎 原本是这原本是腾讯内部公用组件之一,现已在MIT、BSD、GPL协议下开源,广大群众都可以使用了。</p>
<p>artTemplate引擎属于“前端引擎”同时又属于“后端引擎”,还支持预编译。</p>
<p>目前artTemplate的版本是3.0.3,可以从<a href="https://link.segmentfault.com/?enc=pIkYDNhG7r%2FafnfPZNw44A%3D%3D.D7xBQ%2BKZTVLPMJ4133O%2FzbYwEBZ%2FlFieabx1haVVZSpEgZzp%2Byg3ut3d0f%2B2VpSy" rel="nofollow">https://github.com/yourcaptai...</a> 获得源码(forked from aui/artTemplate)。</p>
<h2>作为前端引擎的artTemplate的使用方法</h2>
<p>artTemplate作为前端引擎使用,上手特别快。<br>主要步骤有,下载template.js,制作模板,渲染模板。接下来一一详细说明。</p>
<p><strong>首先需要下载</strong>:<br><a href="https://link.segmentfault.com/?enc=dpqoLlwsR48%2BROMeprV2yQ%3D%3D.TpkChZL%2BO0v4eoR%2BZOVINSWtiwr1r0JjPRxkMvpxQAe0s3pNtchcKoyT73HvhcjGKT3CoAOMSJLoP2qokmxkkw%3D%3D" rel="nofollow">template.js (简洁语法版, 2.7kb)</a></p>
<p><strong>然后是创建一个html文件</strong>,比如创建一个名为sample.html的文件。<br>我们将在其中嵌入模板。<br>该html文件可以比较简单如下,在头文件中引用刚刚下载的template.js(注意引用路径须根据你的环境下该文件所存放的路径进行适当调整),然后再正文中放置一个id为content的div标签(也可以是其它块标签),用来存放接下来根据模板动态生成的html内容。</p>
<pre><code class="html"><html>
<head>
<meta charset=”UTF-8″>
<title>basic-demo</title>
<script src=”template.js”></script>
</head>
<body>
<div id=”content”></div>
</body>
</html></code></pre>
<p><strong>然后是制作模板:</strong><br>本例使用一个type=”text/html”的script标签存放模板,&lt;script&gt;</script&gt;标签中包含的部分是模板本身,&lt;script&gt;</script&gt;标签只是用来存放模板的容器。</p>
<p>将模板内容放到&lt;script&gt;</script&gt;标签只是为了容易引用这些模板内容,<br>后面你将看到不将模板内容放到&lt;script&gt;</script&gt;标签中而是在javascript中以字符串的形式存放模板。</p>
<pre><code class="html"><!– 模板 –>
<script id=”test” type=”text/html”>
<h1>{{title}}</h1>
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} :{{value}}</li>
{{/each}}
</ul>
</script></code></pre>
<p>这样sample.html将变为如下样子</p>
<pre><code class="html"><html>
<head>
<meta charset=”UTF-8″>
<title>basic-demo</title>
<script src=”template.js”></script>
</head>
<body>
<div id=”content”></div>
<!– 模板 –>
<script id="test" type="text/html">
<h1>{{title}}</h1>
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} :{{value}}</li>
{{/each}}
</ul>
</script>
</body>
</html></code></pre>
<p><strong>然后是渲染模板的工作</strong><br>即将数据等应用到模板上,以得到最终显示的效果(html文档)。 </p>
<p>渲染模板的代码如下:</p>
<pre><code class="html">var data = {
title: '标签',
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById(‘content’).innerHTML = html;</code></pre>
<p>渲染代码中用到了template(id, data)方法,该方法是在template.js文件中定义的。<br>template(id, data) 方法根据 id 渲染模板。内部会根据document.getElementById(id)查找模板。<br>如果没有 data 参数,那么将返回一渲染函数。<br>此时知道为什么要把模板放在&lt;script&gt;标签中了吧,因为渲染模板时,template可以根据&lt;script&gt;标签的id来查找模板啦。</p>
<p>这样sample.html将变为如下样子</p>
<pre><code class="html"><html>
<head>
<meta charset=”UTF-8″>
<title>basic-demo</title>
<script src=”template.js”></script>
</head>
<body>
<div id=”content”></div>
<!– 模板 –>
<script id="test" type="text/html">
<h1>{{title}}</h1>
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} :{{value}}</li>
{{/each}}
</ul>
</script>
<!– 渲染模板 –>
<script>
var data = {
title: ‘标签’,
list: [‘文艺’, ‘博客’, ‘摄影’, ‘电影’, ‘民谣’, ‘旅行’, ‘吉他’]
};
var html = template(‘test’, data);
document.getElementById(‘content’).innerHTML = html;
</script>
</body>
</html></code></pre>
<p>然后在浏览器中打开sample.html,就会看到由artTemplate根据给定的数据将模板渲染成功的样子了。<br><a href="https://link.segmentfault.com/?enc=fDYmTEmgV%2B8%2BC480X3dPaQ%3D%3D.6%2Ft23ws920LB4uHRdtiSfnbFKh%2FACvZ7wHf66M0pticfYkPH2sDKIbw9o4CIoj3Z0SubICer1V%2Bwq4pvpAHO1g%3D%3D" rel="nofollow">http://aui.github.io/artTempl...</a></p>
<h2>作为后端引擎的artTempalte的使用方法(搭配node.js)</h2>
<p>artTemplate不仅可以作为前端引擎,还可以作为后端引擎使用。<br>artTemplate是出色的javascript引擎,搭配出色的javascript运行时环境node.js一起使用,将会非常顺畅。<br>所以要求读者需掌握node.js的基础知识。<br>本小节将详细说明node.js环境下artTemplate的使用方法,包括安装node.js、创建工作区、初始化工作区、使用npm安装NodeJS版artTemplate、创建http服务器框架、编写artTemplate模板、渲染模板</p>
<p><strong>安装node.js 和 npm</strong></p>
<pre><code>可参考官方文档:https://nodejs.org/en/download/
安装最新版的node.js时会自动同时安装npm。
此处读者需掌握node.js和npm基础知识
</code></pre>
<p><strong>创建工作区</strong><br>创建工作区,就是指定一个空文件夹,作为工作区。 假定我们的工作区的目录名叫“myWork”,MS windows操作系统下,可以放到公共文档目录下“C:UsersPublicDocumentsmyWork”,Linux等操作系统下请自行在可用的目录下创建“myWork”文件夹。<br>然后进入工作区,即进入“myWork”文件夹。 </p>
<p><strong>初始化工作区</strong></p>
<p>进入工作区,即进入“myWork”文件夹,然后执行以下命令,依据提示填入信息即可。MS Windows下可在命令行工具中进行。Linux下请注意确认是否需要root权限。<br><em>此处读者需掌握node.js和npm基础知识</em></p>
<pre><code class="npm">npm init</code></pre>
<p>执行完毕后,工作区下会出现一个package.json文件。</p>
<p>使用npm安装NodeJS版artTemplate<br><em>此处读者需掌握node.js和npm基础知识</em><br>进入工作区,即进入“myWork”文件夹,然后执行以下命令。MS Windows下可在命令行工具中进行。Linux下请注意确认是否需要root权限。</p>
<pre><code class="npm">npm install art-template --save</code></pre>
<p>执行完毕后,工作区下会出现一个node_modules文件夹。</p>
<p><strong>创建http服务器框架</strong><br>然后我们创建一个最简单的http服务器框架。<br><em>此处读者需掌握node.js和npm基础知识</em></p>
<p>进入工作区,即进入“myWork”文件夹,然后执行以下命令。MS Windows下可在命令行工具中进行。<br>然后新建文件:server.js<br>编写代码如下:</p>
<pre><code class="nodejs">var http = require(“http”);
var os = require(“os”);
http.createServer(function(request, response) {
console.log(“New request arrived.”);
response.end();
}).listen(3000);
console.log(“Server is running, listening on port 3000….”);</code></pre>
<p>在Windows命令行下,进入工作区,执行 node server.js ,服务器就启动了。<br> 此时在本地机器上使用浏览器访问<a href="https://link.segmentfault.com/?enc=ii2GpcEtvTaqg6n6YqjUqQ%3D%3D.gDsJ1X5WlvB6wEtZs31%2BSyY2p6LU4k9ksZbgnxn8T8k%3D" rel="nofollow">http://localhost:3000</a>将不会收到任何返回信息,不过服务器端的命令行工具上会显示“New request arrived.”字样。<br> 按<kbd>Ctl<kbd>+<kbd>C<kbd>退出服务器</kbd></kbd></kbd></kbd></p>
<p><strong>编写artTemplate模板</strong></p>
<p>在工作区下,创建index.html,并将如下代码输入index.html,并保存。<br>其中&lt;ul&gt;标签中的内容,就是模板代码。</p>
<pre><code class="html"><html>
<head>
<meta charset=”utf-8″>
<title>模板</title>
</head>
<body>
<div id=”main”>
<ul>
{{each list}}
<li>编号:{{$value.id}} &nbsp;&nbsp;姓名:{{$value.name}}</a></li>
{{/each}}
</ul>
</div>
</body>
</html></code></pre>
<p><strong>渲染模板</strong></p>
<p>现在,我们要改造刚才创建的创建http服务器框架。在response.end()调用之前,加上渲染模板的代码,使得服务器框架代码变为如下的样子。</p>
<pre><code class="nodejs">var http = require(“http”);
var os = require(“os”);
http.createServer(function(request, response) {
console.log(“New request arrived.”);var template = require(‘art-template’);
//数据
var data = {list: [{id:’1′, name:’张三’}, {id:’2′, name:’李四’}]};
//渲染模板
var html = template(‘./index’, data);
response.writeHead(200, {“Content-Type”: “text/html”});
response.write(html);
response.end();
}).listen(3000);
console.log(“Server is running, listening on port 3000….”);</code></pre>
<p>还记得template(id, data)方法吗?这是在前端DOM环境下的用法。 其实在NodeJS环境下,它就相当于template(filename, data)了,可以将需要渲染的模板文件名当作路径传给它的第一个参数。</p>
<p>在Windows命令行下,进入工作区,执行 node server.js ,服务器就启动了。<br>此时在本地机器上使用浏览器访问<a href="https://link.segmentfault.com/?enc=ikjMoXcmCipRO3PFuSBnEA%3D%3D.BzXTjIFZPMvryxkLIj73TpeCsTIruGEsLfhs054MgcM%3D" rel="nofollow">http://localhost:3000</a>将会看到Html输出了,服务器端的命令行工具上同时也会显示“New request arrived.”字样。</p>
<p>按<kbd>Ctrl<kbd>+<kbd>C<kbd>退出服务器</kbd></kbd></kbd></kbd></p>
<h2>artTemplate搭配express使用</h2>
<p>express框架是由javascript语言开发的,基于Node.js平台的,快速、开放、极简的web开发框架。</p>
<p><strong>安装express</strong></p>
<p>express中文网上有详尽的安装手册,可以参考:<a href="https://link.segmentfault.com/?enc=DP%2F2XQBWcOGhgSIuIPv2Zw%3D%3D.fk4E7JREKGZJoCfpAOjV4Mrsnuc61ZQ3UIydC4mFEdZqsSmtKt0CUmwzE6sEkax7hEvEyPAD%2B6h%2BHP0q5ZFELw%3D%3D" rel="nofollow">http://www.expressjs.com.cn/s...</a></p>
<p><strong>改造app.js</strong></p>
<p>app.js是express的主要文件,这个文件里包含了指定模板引擎、指定视图文件默认路径的代码。需要将指定模板引擎的代码改为指定用art-template引擎。视图文件默认路径保持不变,因此无需改动。<br>以</p>
<pre><code> //——————————————start
//——————————————end </code></pre>
<p>围起来的区域,是新增的内容。<br> 代码app.set(‘view engine’, ‘jade’); 这一行是app.js默认的内容,需要注释掉,因此在前面加上了//注释符。<br>代码最后的“app.listen(3000);”必须加上,否则服务启动后将不会监听任何端口。<br><em>express3.0以上版本默认框架中不再包含监听端口的代码了。</em></p>
<pre><code class="nodejs">//app.js
var express = require(‘express’);
var path = require(‘path’);
var favicon = require(‘serve-favicon’);
var logger = require(‘morgan’);
var cookieParser = require(‘cookie-parser’);
var bodyParser = require(‘body-parser’);//——————————————start
//引用artTemplate模块
var template = require(‘art-template’);
//——————————————end
var routes = require(‘./routes/index’);
var users = require(‘./routes/users’);var app = express();
// view engine setup
app.set(‘views’, path.join(__dirname, ‘views’));
//——————————————start
//用art-template引擎替换默认的jade引擎
//app.set(‘view engine’, ‘jade’);
template.config(‘base’, ”);
template.config(‘extname’, ‘.html’);
app.engine(‘.html’, template.__express);
app.set(‘view engine’, ‘html’);
//——————————————end
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, ‘public’, ‘favicon.ico’)));
app.use(logger(‘dev’));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, ‘public’)));
app.use(‘/’, routes);
app.use(‘/users’, users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error(‘Not Found’);
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get(‘env’) === ‘development’) {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render(‘error’, {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render(‘error’, {
message: err.message,
error: {}
});
});
module.exports = app;
app.listen(3000);</code></pre>
<p><strong>编写artTemplate模板</strong></p>
<p>在工作区下,进入express文件夹中的views子文件夹,创建index.html,并将如下代码输入index.html,并保存。</p>
<p>注意,views文件夹下会有index.jade等三个后缀是.jade的文件存在,可以忽视它们。因为express默认支持的模板引擎是jade,所以初始化的框架中的模板是以.jade结尾的文件。也可以删除它们。</p>
<p>其中&lt;ul&gt;标签中的内容,就是模板代码。</p>
<pre><code class="html"><html>
<head>
<meta charset=”utf-8″>
<title>模板</title>
</head>
<body>
<div id=”main”>
<ul>
{{each list}}
<li>编号:{{$value.id}} &nbsp;&nbsp;姓名:{{$value.name}}</a></li>
{{/each}}
</ul>
</div>
</body>
</html></code></pre>
<p><strong>渲染模板</strong></p>
<p>express默认访问index路由。我们需要在index路由方法中,渲染模板。</p>
<p>进入routes文件夹,打开index.js,增加渲染模板的代码,如下:</p>
<pre><code class="nodejs">var express = require(‘express’);
var router = express.Router();
router.get(‘/’, function(req, res, next) {
//数据
var data = {
title: ‘国内要闻’,
time: (new Date).toString(),
list: [
{
id: ‘1’,
name: ‘张三’
},
{
id: ‘2’,
name: ‘李四’
}
]
};
//渲染模板
res.render(‘index’, data);
});
module.exports = router;</code></pre>
<p>代码中的res.render(‘index’, data)调用,会调用artTemple模块中的template.__express方法,并传入模板文件名、数据。<br>template.__express方法是在app.js中注册给express框架的。</p>
<p>在Windows命令行下,进入工作区,执行 node app.js ,服务器就启动了。<br>此时在本地机器上使用浏览器访问<a href="https://link.segmentfault.com/?enc=5q8g0L8ZV80wSG6ukMyhjA%3D%3D.hR%2FUsz50I0xOAQVhLAJ1HDdEWGtVodhW1PlALm3mJzQ%3D" rel="nofollow">http://localhost:3000</a>将会看到Html输出了,服务器端的命令行工具上同时也会显示“New request arrived.”字样。</p>
<p>按<kbd>Ctl<kbd>+<kbd>C<kbd>退出服务器</kbd></kbd></kbd></kbd></p>
<h2>artTemplate的语法</h2>
<p>以下是语法的简单说明。<br><strong>简洁语法</strong> —— artTemplate的开发者推荐使用简洁语法,简单实用,利于读写。<br>template.js (简洁语法版, 2.7kb)<br>{{if admin}}<br>{{include 'admin_content'}}{{each list}}<br><div>{{$index}}. {{$value.user}}</div><br>{{/each}}<br>{{/if}}</p>
<p>简洁语法详细说明:<a href="https://link.segmentfault.com/?enc=YoUZsOYomx2qNG7UoJdLGw%3D%3D.a6UmRgBPHrkscSKZuZVYUNA8XU%2Bd8xcDSP5bAzuH4jMjcu3AKBWZE2ADjWhMuXKzZ7pffkiq2whHoDLAcf4HgA%3D%3D" rel="nofollow">https://github.com/aui/artTem...</a></p>
<p><strong>原生语法</strong><br>template-native.js (原生语法版, 2.3kb)<br><%if (admin){%><br><%include('admin_content')%><%for (var i=0;i<list.length;i++) {%><br><div><%=i%>. <%=list[i].user%></div><br><%}%><br><%}%></p>
<p>原生语法详细说明:<a href="https://link.segmentfault.com/?enc=D3J23193WO7B1iTqo7HPtg%3D%3D.fE04SlY7aFxoK4SUnNc5wAET8wVnCqbEfqBSFhvtnc7rzRKmgCIcqcjZHGkZL20MbmuF5Nao2bPlcyIySBQBqA%3D%3D" rel="nofollow">https://github.com/aui/artTem...</a></p>
<h2>后记</h2>
<p>本文是依据作者的实践经验整理而成。<br>作者的开发环境如下</p>
<ul>
<li><p>windows 7 64bit 中文版</p></li>
<li><p>node.js 0.12.7</p></li>
<li><p>npm 2.11.3</p></li>
<li><p>express 4.13.1</p></li>
</ul>
<p>文中的代码如在读者本地无法运行,请检查运行环境与作者的是否一致,不一致的话,请参考各工具版本间的变化,对代码加以修正。</p>
<p>如发现文中错误还请不吝赐教。</p>
<p>作者联系方式:yuan_aiqing@outlook.com 袁艾青</p>
<h2>参考资料</h2>
<p><a href="https://link.segmentfault.com/?enc=0uyAl5Y1Y%2BZwAJGXBYX20g%3D%3D.m2egHRUC%2FEycTQg0EeHSxe8m4LMyEx4S3gZVkxo5V%2B%2BBRx48NoV9PM9SzNb4c3LB" rel="nofollow">artTemplate的Github托管</a><br><a href="https://link.segmentfault.com/?enc=4jpsARtKN9Ta%2B6GnsjvV8w%3D%3D.KJhKQ6lxsnREnoepa7MzD7praaau7NKs8q0Kt2lqew6pkAhlHymwvaGcMnTezRAVh%2BOvOeGEUd33eGISo36NDA%3D%3D" rel="nofollow">JS模板引擎介绍搜集</a><br><a href="https://link.segmentfault.com/?enc=mz%2FvTPR9wn9gUT14ySM4Dg%3D%3D.tIJIjudgw5hSSK%2FLCwU0266MILDDCewKigEL9rgWBoo%3D" rel="nofollow">高性能JavaScript模板引擎原理解析</a><br><a href="https://link.segmentfault.com/?enc=mCzy66GCg3dExfelpUgGdQ%3D%3D.%2Fsp2wm4FONuuAwqguX75QhVtGmafswNIz9i9E2K3PmF4bZyFLDVyB%2F3vR3xSEGe7N99J3osGKaBqQ6oCsjR93%2BgR38jQ5Ep6Kdff%2FeiNZJFHQyR68jxR5fe0f3twwAYDPe%2FIpYFOjaiQqN7aWjSNGCry2QRYk4dsNOX25VeBh8g%3D" rel="nofollow">百度百科-模板引擎</a><br><a href="https://link.segmentfault.com/?enc=ItDUpLrtSk2uqy9IixLE6g%3D%3D.A42pbnj3tANsOhAu8V494wPqPIcCcwusYA6uHqQln65X1dZEdT0pZI1gG9hclDZdvXDxoyEpqFcVCF2IjzGnXfDe15sGWqkrKNN%2BkYFB%2Bbs%3D" rel="nofollow">为Express开发模板引擎</a></p>