执着的慢行者

执着的慢行者 查看完整档案

上海编辑西南财经大学  |  计算机技术 编辑金融互联网企业  |  架构 编辑 segmentfault.com/u/yesdata 编辑
编辑

大型电子商务系统架构师、项目管理专家

个人动态

执着的慢行者 发布了文章 · 2019-01-22

关于公司架构管控的思考

假想背景:
现状是,各子系统的新建及重大迭代都会形式化地走架构审批流程,但应用架构是否设计以及是否合理,信息技术部门不能掌握。而架构规划部门的架构师人屈指可数,面对总人数达数百人的开发团队所负责的几十子系统、每个月数十个迭代特性,无法做到直接帮助开发团队详尽的进行架构设计。由此提出:架构审批流程不代表架构设计、架构规划部门要加强架构管控。

要做好架构管控,需要能够回答几个问题:架构管控的目的是什么?架构管控的目标是什么?架构管控需要管控什么内容?如何进行管控?

一、目的

一个稳定发展、创新发展的企业,支援业务发展的信息系统的稳定与效率同等重要。架构管控的目的,要指导各团队技术负责人设计出合理的系统,能够满足稳定与开发效率的要求,能够满足功能与非功能的需求,能够考虑到各级相关者的意见;并且还要有考察开发过程及运营过程的机制,以确定最终交付的软件系统复合架构设计及开展架构设计的持续改善工作。

二、制定目标

为了达到这些我们架构管控的目标又是什么呢?发布一份指导意见?发布一份制度说明?发布一份评分表?
我认为这些应该归属于如何进行管控,而不是目标。
我们需要梳理现状,找出主要矛盾,量化主要矛盾,形成目标(SMART原则别忘记~~)

(一)、主要生产问题、客户投诉及分类

从客户反馈中可以抓住主要矛盾。

(二)、信息技术方面的主要矛盾

  1. 外购产品 vs 二次改造
    早期从外部采购的产品一般会使用比较老旧的开发框架,进行二次改造困难,常带来较多故障,并且开发效率低下。
  2. 系统解耦 vs 互相影响
    分布式带来较好的伸缩性等非功能特性的同时,也带来了复杂度 —— 系统间相互影响较难控制,给设计和开发增大了难度。
  3. 开发团队对科技公共平台的不熟悉 vs 强制使用
  4. 业务数据模型混乱 vs 新需求

5、控制 vs 创新

(三)、形成架构管控目标

  1. 控制新增外购系统的架构方案的合理性、梳理既存外购系统的架构、提升开发效率。
  2. 降低分布式系统的开发难度、提升分布式系统的交付质量。
  3. 降低科技公共平台的使用难度。
  4. 引导、规范新技术的引入。
查看原文

赞 0 收藏 0 评论 0

执着的慢行者 发布了文章 · 2018-11-30

Spring WebFlux是如何映射请求到控制器的?

一、前言

Spring Flux中的核心DispatcherHandler的处理过程分为三步,其中首步就是通过HandlerMapping接口查找Request所对应的Handler。本文就是通过阅读源码的方式,分析一下HandlerMapping接口的实现者之一——RequestMappingHandlerMapping类,用于处理基于注解的路由策略,把所有用@Controller和@RequestMapping标记的类中的Handler识别出来,以便DispatcherHandler调用的。

HandlerMapping接口的另外两种实现类:1、RouterFunctionMapping用于函数式端点的路由;2、SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler配对。

<!-- more -->

文章系列

二、对基于注解的路由控制器的抽象

Spring中基于注解的控制器的使用方法大致如下:

@Controller
public class MyHandler{
    @RequestMapping("/")
    public String handlerMethod(){

    }
}

在Spring WebFlux中,对上述使用方式进行了三层抽象模型。

  1. Mapping

    • 用户定义的基于annotation的映射关系
    • 该抽象对应的类是:org.springframework.web.reactive.result.method.RequestMappingInfo
    • 比如上述例子中的 @RequestMapping("/")所代表的映射关系
  2. Handler

    • 代表控制器的类
    • 该抽象对应的类是:java.lang.Class
    • 比如上述例子中的MyHandler类
  3. Method

    • 具体处理映射的方法
    • 该抽象对应的类是:java.lang.reflect.Method
    • 比如上述例子中的String handlerMethod()方法

基于上述三层抽象模型,进而可以作一些组合。

  1. HandlerMethod

    • Handler与Method的结合体,Handler(类)与Method(方法)搭配后就成为一个可执行的单元了
  2. Mapping vs HandlerMethod

    • 把Mapping与HandlerMethod作为字典存起来,就可以根据请求中的关键信息(路径、头信息等)来匹配到Mapping,再根据Mapping找到HandlerMethod,然后执行HandlerMethod,并传递随请求而来的参数。

理解了这个抽象模型后,接下来分析源码来理解Spring WebFlux如何处理请求与Handler之间的Mapping关系时,就非常容易了。

HandlerMapping接口及其各实现类负责上述模型的构建与运作。

三、HandlerMapping接口实现的设计模式

HandlerMapping接口实现,采用了"模版方法"这种设计模式。

1层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware

  ^  
  |  

2层:AbstractHandlerMethodMapping implements InitializingBean

  ^  
  |  

3层:RequestMappingInfoHandlerMapping

  ^  
  |  

4层:RequestMappingHandlerMappingimplements EmbeddedValueResolverAware

下面对各层的职责作简要说明:

  • 第1层主要实现了对外提供模型的接口

    • 即重载了HandlerMapping接口的"Mono<Object> getHandler(ServerWebExchange exchange) "方法,并定义了骨架代码。
  • 第2层有两个责任 —— 解析用户定义的HandlerMethod + 实现对外提供模型接口实现所需的抽象方法

    • 通过实现了InitializingBean接口的"void afterPropertiesSet()"方法,解析用户定义的Handler和Method。
    • 实现第1层对外提供模型接口实现所需的抽象方法:"Mono<?> getHandlerInternal(ServerWebExchange exchange)"
  • 第3层提供根据请求匹配Mapping模型实例的方法
  • 第4层实现一些高层次用到的抽象方法来创建具体的模型实例。

小结一下,就是HandlerMapping接口及其实现类,把用户定义的各Controller等,抽象为上述的Mapping、Handler及Method模型,并将Mapping与HandlerMethod作为字典关系存起来,还提供通过匹配请求来获得HandlerMethod的公共方法。

接下来的章节,将先分析解析用户定义的模型并缓存模型的过程,然后再分析一下匹配请求来获得HandlerMethod的公共方法的过程。

四、解析用户定义的模型并缓存模型的过程

4-1、实现InitializingBean接口

第2层AbstractHandlerMethodMapping抽象类中的一个重要方法——实现了InitializingBean接口的"void afterPropertiesSet()"方法,为Spring WebFlux带来了解析用户定义的模型并缓存模型的机会 —— Spring容器初初始化完成该类的具体类的Bean后,将会回调这个方法。
在该方法中,实现获取用户定义的Handler、Method、Mapping以及缓存Mapping与HandlerMethod映射关系的功能。

@Override
public void afterPropertiesSet() {

    initHandlerMethods();
    
    // Total includes detected mappings + explicit registrations via registerMapping..
    ...
}

4-2、找到用户定义的Handler

afterPropertiesSet方法中主要是调用了void initHandlerMethods()方法,具体如下:

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());
}

这儿首先获取Spring容器中所有Bean名字,然后循环处理每一个Bean。如果Bean名称不是以SCOPED_TARGET_NAME_PREFIX常量开头,则获取Bean的类型。如果获取到类型,并且类型是Handler,则继续加载Handler方法。

isHandler(beanType)调用,检查Bean的类型是否符合handler定义。
AbstractHandlerMethodMapping抽象类中定义的抽象方法"boolean isHandler(Class<?> beanType)",是由RequestMappingHandlerMapping类实现的。具体实现代码如下:

protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

不难看出,对于RequestMappingHandlerMapping这个实现类来说,只有拥有@Controller或者@RequestMapping注解的类,才是Handler。(言下之意对于其他实现类来说Handler的定义不同)。

具体handler的定义,在HandlerMapping各实现类来说是不同的,这也是isHandler抽象方法由具体实现类来实现的原因。

4-3、发现Handler的Method

接下来我们要重点看一下"detectHandlerMethods(beanName);"这个方法调用。

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);
        });
    }
}

首先将参数handler(即外部传入的BeanName或者BeanType)转换为Class<?>类型变量handlerType。如果转换成功,再将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)。接下来获取该用户类型里所有的方法(Method)。循环处理每个方法,如果是满足要求的方法,则注册到全局的MappingRegistry实例里。

4-4、解析Mapping信息

其中,以下代码片段有必要深入探究一下

 Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));

MethodIntrospector.selectMethods方法的调用,将会把用@RequestMapping标记的方法筛选出来,并交给第二个参数所定义的MetadataLookup回调函数将通过controller定义的mapping与手动定义的mapping合并起来。
第二个参数是用lambda表达式传入的,表达式中将method、userType传给getMappingForMethod(method, userType)方法。

getMappingForMethod方法在高层次中是抽象方法,具体的是现在第4层RequestMappingHandlerMapping类中实现。在具体实现getMappingForMethod时,会调用到RequestMappingHandlerMapping类的下面这个方法。从该方法中,我们可以看到,首先会获得参数element(即用户在Controller中定义的方法)的RequestMapping类型的类实例,然后构造代表Mapping抽象模型的RequestmappingInfo类型实例并返回。

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);
    }

构造代表Mapping抽象模型的RequestmappingInfo类型实例,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的信息,包括paths、methods、params、headers、consumers、produces、mappingName,即用户定义@RequestMapping注解时所设定的可能的参数,都被存在这儿了。拥有了这些信息,当请求来到时,RequestMappingInfo就可以测试自身是否是处理该请求的人选之一了。

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();
    }

4-5、缓存Mapping与HandlerMethod关系

最后,registerHandlerMethod(handler, invocableMethod, mapping)调用将缓存HandlerMethod,其中mapping参数是RequestMappingInfo类型的。。
内部调用的是MappingRegistry实例的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo类型。
MappingRegistry类维护所有指向Handler Methods的映射,并暴露方法用于查找映射,同时提供并发控制。

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();
            }
        }

五、匹配请求来获得HandlerMethod

AbstractHandlerMethodMapping类的“Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange)”方法,具体实现了根据请求查找HandlerMethod的逻辑。

    @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();
        }
    }

handlerMethod = lookupHandlerMethod(exchange)调用,继续查找HandlerMethod。我们继续看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定义。为方便阅读,我把注释也写在了代码里。

    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);
        }
    }

六、总结

理解了Spring WebFlux在获取映射关系方面的抽象设计模型后,就很容易读懂代码,进而更加理解框架的具体处理方式,在使用框架时做到“知己知彼”。

原文:http://www.yesdata.net/2018/11/27/spring-flux-request-mapping/

查看原文

赞 0 收藏 0 评论 0

执着的慢行者 发布了文章 · 2018-11-26

快速上手Spring WebFlux框架

一、前言

本文主要介绍基于SpringBoot如何快速上手使用SpringFlux框架开发WEB网站。

Spring 5.0在原有的Spring MVC Stack(又称Servlet Stack)以外,又引入了新的WEB开发技术栈——Spring Flux Stack(又称Reactive Stack),以满足不同的应用程序及开发团队的需求。

开发者一直在寻找最适合他们的应用程序的运行时、编程框架及架构。比如,有些用例最适合采用基于同步阻塞IO架构的技术栈,而另一些用例可能更适合于基于Reactive Streams响应式编程原则构建的异步的、非阻塞的技术栈。

后续将有系列文章深入介绍SpringFlux所采用的响应式编程原则及其代表实现ProjectReactor,希望通过系列文章的介绍,让广大读者能够在逐步使用SpringFlux的过程中,理解响应式编程原理及实现,进而能够对项目应该选择SpringMVC还是SpringWebFlux形成自己的判断标准。

文章系列

<!-- more -->

二、快速上手

1、创建项目

打开 http://start.spring.io,来初始化一个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等)。

通过start.spring.io创建reactive栈的项目

2、增加一个Controller

如果你熟悉Spring MVC,你一定对@Controller注解不陌生。即使不熟悉也没关系,我会简单介绍一下。
Spring WebFlux带有两种特征,一种是函数式的(Functional),另一种是基于注解的(annotation-based)。函数式编程不太适合快速上手,我们先选择基于注解。类似于Spring MVC模型,Spring WebFlux模型也使用@Controller注解,以及@RestController注解。

用IDE打开项目后,追加一个类:SampleController
然后增加如下代码:

@RestController
@RequestMapping("/")
public class SampleController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

运行后,用浏览器访问:http://localhost:8080/hello,将会在浏览器上看到"hello"字样。

增加SampleController

SampleController执行结果

怎么样,是不是很简单。

3、增加一个WebFilter

增加一个Filter试试看。在Spring WebFlux框架下,增加Filter是通过实现WebFilter接口实现的。

@Component
public class FirstWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        serverWebExchange.getAttributes().put("User", "jerry");
        return webFilterChain.filter(serverWebExchange);
    }
}

三、深入了解一下SpringBoot启动Spring WebFlux的过程

对于已经笔记熟悉Spring MVC框架的开发人员来说,快速上手无法满足欲望。必须了解其背后的机制原理等,方能有知己知彼的感觉。接下来稍微探讨以下SpringBoot启动Spring WebFlux的过程。尚不太熟悉Spring MVC框架的开发人员也可以阅读一下本章内容,或许对更好地使用Spring WebFlux框架有帮助。

1、准备工作

你需要从Gitubhttps://github.com/spring-projects/spring-framework下载Spring WebFlux的源码,以便进行后续分析。

2、@EnableWebFlux

回顾一下通过http://start.spring.io创建项目中,DemoApplication启动类的注解中,有一个注解是:@EnableWebFlux。SpringBoot就是通过这个注解,来启动Spring WebFlux的。

@EnableWebFlux
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

再看一下EnableWebFlux的定义(可以到SpringWebFlux源码中找到),如下,我们注意到它引入了DelegatingWebFluxConfiguration类。看来秘密会藏在DelegatingWebFluxConfiguration类里呢。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebFluxConfiguration.class)
public @interface EnableWebFlux {
}

打开DelegatingWebFluxConfiguration.java文件(可以到SpringWebFlux源码中找到),然后你我们发现它的父类是WebFluxConfigurationSupport类。我们先来看看这个父类。

@Configuration
public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport {
......
}

下面是父类WebFluxConfigurationSupport实现的代码片段。我们注意到,它向Spring Bean容器中,注入了两个Bean:webHandler和requestMappingHandlerMapping(实际上还注入了其它若干个Bean,不过我们暂时只关注这两个吧)。

名为"webHandler"的Bean的类型是DispatcherHandler,顾名思义是分发器,分发请求给各个处理单元。
名为"requestMappingHandlerMapping"的Bean的类型是RequestMappingHandlerMapping,它的根本是实现了HandlerMapping接口。HandlerMapping的作用是将请求映射到Handler上,比如把某个请求路径映射到某个Controller上。

关于DispatcherHandler和HandlerMapping,后面章节继续深入讲解。

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;
    }
}

2、DispatcherHandler

DispatcherHandler将自身作为Bean注册到Spring的Bean容器中,它与WebFilter、WebExceptionHandler等一起,组成处理链。

DispatcherHandler会从Spring Configuration中探寻到它需要的组件,主要是以下三类:

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    ...
    private List<HandlerMapping> handlerMappings;
    private List<HandlerAdapter> handlerAdapters;
    private List<HandlerResultHandler> resultHandlers;
    ...
}
  • HandlerMapping

    • 映射Request到Handler
    • HandlerMapping的默认实现是RequestMappingHandlerMapping,用于@RequestMapping所标记的方法;RouterFunctionMapping用于函数式端点的路由;SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler
  • HandlerAdaptor

    • 帮助DispatcherHandler调用Request所映射的Handler
    • HandlerAdaptor的主要作用是将DispatcherHandler从调用具体Handler的细节中解放出来
  • HandlerResultHandler

    • 处理Handler的结果并终结Response
    • 调用具体的Handler的返回结果是包装在HandlerResult中的,HandlerResultHandler负责处理HandlerResult。比如ResponseBodyResultHandler负责处理@ResponseBody标记的方法的返回值

示意图如下:

DispatcherHandler
  |
  |-->HandlerMapping
  |-->HandlerAdaptor
  |-->HandlerResultHandler

DispatcherHandler的核心方法是:

@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));
}

该方法道出了DispatcherHandler处理请求的套路

  • 先通过HandlerMapping找到Request的专用Handler,具体的代码片段是
concatMap(mapping -> mapping.getHandler(exchange))
  • 再通过HandlerAdaptor执行这个具体的Handler,具体的代码片段是
flatMap(handler -> invokeHandler(exchange, handler))
  • 最后通过HandlerResultHandler处理Handler返回的结果,具体的代码片段是
flatMap(result -> handleResult(exchange, result))

可能你有疑问,这个方法里Request在哪呢?藏在"ServerWebExchange exchange"里了,关于ServerWebExchange又其它文章进一步介绍,这儿就不作详细说明了。

3、HandlerMapping

DispatcherHandler套路中,处理Request的第一环节就是使用HandlerMapping。常见的HandlerMapping有三种:RequestMappingHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping

RequestMappingHandlerMapping是默认的,是否还记得DispatcherHandler中有发布一个Bean名为"requestMappingHandlerMapping"?它承担了映射Request与annotation-based Handler之间的关系。

由于我们习惯于使用@Controller、@RestController、@RequestMapping之类的注解来开发WEB项目,所以非常依赖RequestMappingHandlerMapping。我们接下来就谈谈RequestMappingHandlerMapping

3-1、RequestMappingHandlerMapping的类层级

1层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware

  ^
  |

2层:AbstractHandlerMethodMapping implements InitializingBean

  ^
  |

3层:RequestMappingInfoHandlerMapping

  ^
  |

4层:RequestMappingHandlerMappingimplements EmbeddedValueResolverAware

3-2、扫描可用handler

大家注意到,第2层实现了InitializingBean接口,实现了该接口的类有机会在BeanFactory设置好它的所有属性后通过调用

void afterPropertiesSet()

方法通知它。我们来看看第2层的对该方法的实现吧。

@Override
public void afterPropertiesSet() {

    initHandlerMethods();
    
    // Total includes detected mappings + explicit registrations via registerMapping..
    ...
}

篇幅原因,这儿不细究方法中所调用的

initHandlerMethods();

的细节了 —— 实际上这里面是扫描所有@Controller注解的类中的@RequestMapping及其变体所修饰的方法,即最终会处理Request的Handler,并缓存起来,以便后续进一步执行。

3-3、与DispatcherHandler相互配合

DispatcherHandler会调用MappingHandler的

Mono<Object> getHandler(ServerWebExchange exchange)

方法,选择处理这个请求交的具体的Handler。

四、总结

Spring WebFlux模型的使用非常简单,尤其是对于熟悉Spring MVC模型的开发人员来说,无缝切换。使用Spring Boot框架开发时,使用@Controller、@RestController、@RequestMapping等注解,实现处理Request的Handler尤其方便。

原文:http://www.yesdata.net/2018/1...

查看原文

赞 8 收藏 5 评论 0

执着的慢行者 发布了文章 · 2018-11-13

从时间碎片角度理解阻塞IO模型及非阻塞模型

阻塞模型限制了服务器的并发处理能力(伸缩性或同时处理的客户端连接数)

传统的网络服务器只支持阻塞模型,该模型下,针对每个客户端连接,服务器都必须创建一个线程来处理这个连接上的请求,服务器必须维持着这些线程直到线程中的处理工作结束。

服务器上所能创建的线程数量是有限的,WHY?

  • 进程上下文切换是耗时的过程
  • 创建的进程本身占用资源,比如每个进程或线程占用一定容量的内存
  • 等待数据准备和内核缓存复制,导致IO阻塞,占用着线程

所以当连接到服务器上的客户端的数量很大时,把服务器上所能创建的线程都占据了时,服务器就无法接受更多的连接了。这限制了服务器处理请求的伸缩性。

并非所有客户端都是持续活跃的

存在这样一个事实,就是虽然连接到服务器上的客户端很多,但并非所有客户端都是持续活跃着的。它们占据着阻塞式服务器的线程资源——即使它们处于非工作状态。这些线程占据了资源,却不工作。

这会造成什么现象呢?
就是线程时间的碎片化——一个线程大部分时间是在等待IO操作的结果。

为了让服务器能接受更多客户端的连接,非阻塞模型就出现了。

如何提升服务器的并发处理能力?

消灭碎片化时间,可以提升服务器的并发处理能力。

如何消灭碎片化时间? 让线程分工协作各司其职,是一个很好的手段。
原来的阻塞模型下,一个线程要干所有的事情。分工协作机制下,一部分线程专门用于接受客户端的连接、一部分专门用于获取请求的数据、一部分专门执行计算工作、还有一部分线程专门用于响应客户端。
接受客户端连接的线程在接收到客户端连接后,立即把连接交给后续工序的线程处理,而它自己则继续接受下一个连接。如此类推,各个线程无须等待,不存在碎片化时间,全负荷工作。
这样一来,整体上需要的较少的线程,就可以完成以前需要较多线程才能达到的工作时间了。

阻塞模型下的实现方式

在阻塞模型下,利用异步处理的方式对线程进行分工协作。接收请求的线程可以满负荷工作,但处理IO操作的线程仍然是阻塞着的,仍然存在线程工作不饱和的现象。

非阻塞模型彻底消灭线程工作不饱和

非阻塞模型下,IO操作不再是阻塞的了,而是立即返回。这样的话,处理IO操作的线程,可以在空闲时对所有请求进行轮询,以便判断哪些IO操作已完成。比如判断某个请求是否可以进行“写”操作,如果还不可以,无须等待,继续判断下一个请求是否可以进行“读”操作,如果可以则立即读取数据,然后把数据转交给专职计算的线程。这样就让线程工作不饱和现象消失了。

这是所谓的“同步非阻塞”。

轮询的耗时如何消灭?

这就要请出“IO复用”这尊大神了。

IO复用模型下,线程一次性从操作系统那儿获得一批可以进行IO操作的请求,处理完毕后,再此获得新的一批。线程无须与操作系统交互多次以便轮询每个请求的状态,而是与操作系统交互一次即可获得批量信息。效率进一步提高啦。

查看原文

赞 7 收藏 6 评论 0

执着的慢行者 发布了文章 · 2018-11-02

从JDK11新增HttpClient谈谈非阻塞模型

北京时间 9 月 26 日,Oracle 官方宣布 Java 11 正式发布

一、JDK HTTP Client介绍

JDK11中的17个新特性

JDK11中的17个新特性

JDK11中引入HTTP Client的动机

既有的HttpURLConnection存在许多问题

  • 其基类URLConnection当初是设计为支持多协议,但其中大多已经成为非主流(ftp, gopher…)
  • API的设计早于HTTP/1.1,过度抽象
  • 难以使用,存在许多没有文档化的行为
  • 它只支持阻塞模式(每个请求/响应占用一个线程)

<!--more-->

HTTP Client发展史

HTTP Client发展史

在JDK11 HTTP Client出现之前

在此之前,可以使用以下工具作为Http客户端

  • JDK HttpURLConnection
  • Apache HttpClient
  • Okhttp
  • Spring Rest Template
  • Spring Cloud Feign
  • 将Jetty用作客户端
  • 使用Netty库。还

初探JDK HTTP Client

我们来看一段HTTP Client的常规用法的样例 ——
执行GET请求,然后输出响应体(Response Body)。

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();

第一步:创建HttpClient

一般使用JDK 11中的HttpClient的第一步是创建HttpClient对象并进行配置。

  • 指定协议(http/1.1或者http/2)
  • 转发(redirect)
  • 代理(proxy)
  • 认证(authenticator)
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();

第二步:创建HttpRequest

从HttpRequest的builder组建request

  • 请求URI
  • 请求method(GET, PUT, POST)
  • 请求体(request body)
  • Timeout
  • 请求头(request header)
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()

第三步:send

  • http client可以用来发送多个http request
  • 请求可以被以同步或异步方式发送

1. 同步发送

同步发送API阻塞直到HttpResponse返回

HttpResponse<String> response =
      client.send(request, BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());

2. 异步发送

  • 异步发送API立即返回一个CompletableFuture
  • 当它完成的时候会获得一个HttpResponse
client.sendAsync(request, BodyHandler.asString())
      .thenApply(response -> { System.out.println(response.statusCode());
                               return response; } )
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println);

※CompletableFuture是在java8中加入的,支持组合式异步编程

二、从提升单机并发处理能力的技术来看HttpClient的实现细节

提升单机并发处理能力的技术

  • 多CPU/多核
  • 多线程
  • 非阻塞(non-blocking)

Java NIO

Java NIO为Java带来了非阻塞模型。
NIO

Lambda表达式

  • Lambda表达式可以方便地利用多CPU。
  • Lambda表达式让代码更加具有可读性

回调

假设以下代码是一个聊天应用服务器的一部分,该应用以Vert.x框架实现。
(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)
向connectHandler方法输入一个Lambda表达式,每当有用户连接到聊天应用时,都会调用该Lambda表达式。这就是一个回调。
这种方式的好处是,应用不必控制线程模型——Vert.x框架为我们管理线程,打理好一切相关复杂性,程序员只考虑和回调就够了。

vertx.createServer()
    .connectHandler(socket -> {
        socket.dataHandler(new User(socket, this));
    }).listen(10_000);

注意,这种设计里,不共享任何状态。对象之间通过向事件总线发送消息通信,根本不需要在代码中添加锁或使用synchronized关键字。并发编程变得更加简单。

Future

大量的回调会怎样?请看以下伪代码

(1)->{
    (2)->{
        (3)->{
            (4)->{}
        }
    }
}

大量回调会形成“末日金字塔”。

如何破解? 使用Future

Future1=(1)->{}
Future2=(Future1.get())->{}
Future3=(Future2.get())->{}
Future4=(Future3.get())->{}

但这会造成原本期望的并行处理,变成了串行处理,带来了性能问题。
我们真正需要的是将Future和回调联合起来使用。下面将要讲的CompletableFuture就是结合了Future和回调,其要点是组合不同实例而无需担心末日金字塔问题。

CompletableFuture

(new CompletableFuture()).thenCompose((1)->{})
    .thenCompose((2)->{})
    .thenCompose((3)->{})
    .thenCompose((4)->{})
    .join()

Reactive Streams

Reactive Streams是一个倡议,它提倡提供一种带有非阻塞背压的异步流处理的标准(Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure)。

JDK 9中的java.util.concurrent.Flow中的概念,与Reactive Streams是一对一对等的。java.util.concurrent.Flow是Reactive Streams标准的实现之一。

多CPU的并行机制让处理海量数据的速度更快,消息传递和响应式编程让有限的并行运行的线程执行更多的I/O操作。

HttpClient的实现细节

请求响应的body与reactive streams

  • 请求响应的body暴露为reactive streams
  • http client是请求的body的消费者
  • http client是响应的body的生产者

请求响应的body与reactive streams

HttpRequest内部

public abstract class HttpRequest {
    ...
    public interface BodyPublisher
                extends Flow.Publisher<ByteBuffer> { ... }
}

HttpResponse的内部

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>> { ... }
}
查看原文

赞 3 收藏 2 评论 0

执着的慢行者 发布了文章 · 2018-04-01

做好云平台架构需要哪些能力

这儿讲的平台,是指计算平台(conputing platform),这是一种环境,软件在这种环境里运行。更进一步,云平台,我们把它定义为企业级的云计算化的平台,直接面向业务的软件运行在这个平台上,并且利用这个平台环境,新的业务软件可以迅速被开发出来。云平台涵盖了硬件、运行时代码库、框架、服务及服务管理系统等。

架构师

做好云平台,做出一个安全、稳定、高效的,能支持企业级大规模业务处理的云平台,需要哪些基本能力呢?

  • 熟悉平台所服务的业务环境
  • 掌握软件系统的工作原理
  • 拥有架构理论及经验
  • 项目管理
  • 文档及表达

<!--more-->

熟悉平台所服务的业务环境

业务是信息系统的灵魂,只有能够支撑业务运营、业务发展的企业信息系统,才是最适合企业的,也是最值得企业投入资源去开发和维护的。云平台作为企业信息系统的底层支撑系统,更是要着眼于业务。核心业务有哪些、非核心业务有哪些、企业着力开拓的新兴业务有哪些,业务规模都有多大,业务跨越的地理范围有多大等等,在了解了这些的基础上,你所架构的平台系统,才更适合企业。

比如企业业务是跨国的,那么平台系统如果不支持跨语言、跨文化的话,那么很可能会成为业务发展的掣肘,反之则助力企业发展

掌握软件系统的工作原理

其实掌握软件系统的工作原理更多地是指选型的能力,这涉及到你的工具箱的广度。
在你根据业务进行一番比较抽象的思考后,你决定了一个基本架构的结构图,然后你需要选型,或许你需要选择一个分布式缓存系统,那么你至少需要知道有哪些已经存在了的分布式缓存系统,并且它们各自的工作原理是什么,然后才能判断它们是否适用,或者是否有必要自己动手对其进行包装或创造一种全新的缓存系统。

这儿列举一些基本的你可能需要掌握或了解的软件系统的工作原理

  • 开发语言(比如Java)
  • 操作系统(Linux或Windows)
  • DNS(域名解析)
  • CDN(内容分发网络)
  • WEB服务器(基于互联网提供的服务将越来越多)
  • 负载均衡(比如Nginx)
  • 消息队列(比如ActiveMQ)
  • 缓存系统(比如Redis)
  • 数据库(比如mysql)及ORM框架(比如MyBatis)

如果还可以掌握以下软件系统工作原理的话,你设计的云平台或许能够应对更大规模的企业业务

  • 分布式资源协调
  • 大数据框架(比如Storm)
  • 人工智能框架(比如)

拥有架构理论及经验

架构理论可以指导你开展架构工作,可以指导你如何对业务系统进行分解以得到子系统、子系统之间的交互关系,指导你考虑系统的运行环境及制约因素、指导你考虑运维需求等。而经验可以让你更快地作出判断,更快地在各种需求之间找到平衡点。

架构理论有许多种,常见的系统架构理论有三层架构、JavaEE架构、SOA架构、微服务架构、CAP理论,常见的企业架构理论有开放组体系结构框架(TOGAF)、联邦体系架构框架(FEA)、美国国防部架构框架(DODAF)。

项目管理

项目管理知识可以指导你的架构的实现更加成功。在企业文化及上下文环境的制约下,充分考虑范围、时间、成本、进度、采购、质量、团队、风险,然后作出的架构决定,更加真实,距离成功的实现更进一步了。 一般如果你是既担任架构师又担任项目经理的话,你就不得不考虑这些了:)

文档及表达

【英】Simon Brown著的《程序员必读之软件架构》一书(邓钢译 ISBN:987-115-37107-2),是这么解释架构的。

架构作为名词来解释时,概括起来都与结构有关:将产品分解为一系列组件、模块和交互。这需要考虑整个产品..
架构作为动词来解释时,包括了理解你需要构建什么、设定愿景以便进行构建和做出恰当的涉及决策...关键在于,架构是关于交流愿景和引入技术领导力的,这样参与构建产品的每个人都能理解这个愿景,并为产品的成功做出积极贡献

你对产品的分解、你所设定的愿景、你作出的决策,都需要很好地记述下来,表达出来,以便每个人都能准确理解,并参与、贡献。

查看原文

赞 1 收藏 3 评论 0

执着的慢行者 关注了专栏 · 2018-03-23

区块链技术

分享区块链前沿技术,翻译国外区块链技术文章。

关注 13

执着的慢行者 发布了文章 · 2018-03-17

微服务与Spring Cloud

微服务是继SOA之后流行起来的一种系统架构模式。因它紧随SOA之后,所以有必要对他们先作个比较。

SOA vs 微服务

关于二者的比较表格,我在谷歌上搜索的一篇文章分析的挺好,现引用如下。

面向服务架构微服务架构
出现于1990's年代出现于2000's年代
最大化应用服务的重用性关注解耦
系统变化需要修改整体系统变化是创建新服务
DevOps和持续发布开始变得流行但不是主流重点关注DevOps和持续发布
聚焦于业务系统重用“边界上下文”越发重要
使用ESB通信使用简单消息系统通信
支持多种消息协议使用轻量级协议诸如:HTTP, REST等
对部署在其上的所有服务使用通用平台通常使用云平台而非应用服务器
Docker不太流行容器与微服务工作的非常协调
SOA服务共享数据存储每个微服务可以拥有独立的存储服务
通用的治理和标准松散治理,关注团队协作与自由选择

SOA vs MicroService

<!--more-->

微服务

微服务这么流行,肯定有其优势所在。不过也不能不看到它的劣势噢。

优势

  • 每个模块都是独立的,所以你可以选择不同的开发语言以获得语言级别的好处
  • 每个模块都可以有自己的数据库,NoSQL或者关系型
  • 开发人员构建和维护微服务所以他们对模块非常熟悉。跨功能的成员一些协作完成服务

劣势

  • 服务调用其它服务,所以对于大型项目来说,难以跟踪调用过程以及监控服务
  • 服务之间通过RESTful API调用所以性能可能没有整体的进程内通信的系统高
  • 难以调试,尤其是一个服务调用了一系列其它服务

SpringCloud

如何扬长避短,更好更方便地利用微服务呢。

SpringCloud提供以下特性来强化微服务的优势、弥补劣势

  • 分布式/版本化的配置
  • 服务注册和发现
  • 路由
  • 服务到服务的调用
  • 负载均衡
  • 熔断(Circuit Breaker)
  • 全局锁
  • leader选举及集群状态
  • 分布式消息

SpringCloud主要项目

Spring Cloud Config

统一配置中心。

Spring Cloud Netflix

服务发现

Spring Cloud Bus

一个事件总线,利用分布式消息系统将服务和服务实例连到一起。可用于在集群内传播状态变化(如配置变化事件)

Spring Cloud for Cloud Foundry

集成你的应用到Pivotal Cloud Foundry,提供并且让实现SSO和OAuth2来保护资源变得更加容易。

Spring Cloud Open Service Broker

提供一个用于构建实现了Open Service Broker API的服务的Broker的起始点

Open Service Broker API连接开发者到一个全球的服务生态环境,该项目给开发者、ISVs、SaaS厂商提供一个单一的、简单的和完美的方式去发布服务到原生云平台上(诸如Cloud Foundry, OpenShift, Kubernetes)

Open Service Broker 项目

Open Service Broker API定义

Spring Cloud Cluster

提供基于Zookeeper、Redis、Hazelcast、Consul的领头选举、通用状态机模式的抽象及实现。

Spring Cloud Consul

基于Hashicorp Consul的服务发现及配置管理。

Spring Cloud Security

提供对基于负载均衡的OAuth2 rest client和authentication header的支持,依赖了Zuul proxy。

Spring Cloud Sleuth

应用于Spring Cloud的分布式追踪功能,与Zipkin, HTrace and log-based (e.g. ELK) tracing 相兼容。

Spring Cloud Data Flow

一个cloud-native的服务编排,易用的DSL、drag-and-drop GUI,REST-APIs 一起全面简化了基于服务编排的数据管道。

Spring Cloud Stream

一个轻量级的事件驱动微服务框架,便于快速构建连接到外部系统的应用。简单的声明模型可以使用Apache Kafka或RabbitMQ在Spring Boot应用间收发消息。

Spring Cloud Stream App Starters

一系列基于Spring Boot的Spring集成应用程序,提供与外部应用的集成。

Spring Cloud Task

一个短期的微服务框架,快速构建执行有限数量的数据处理的应用。简单的声明就可以增加功能性或非功能性特性到Spring Boot中。

Spring Cloud Task App Starters

一系列单机版的可执行应用程序,可以拥有按需用例:比如数据库迁移、机器学习、定时操作。这些应用可以运行于各种独立平台,比如:Cloud Foundry、Apache Yarn、 ApacheMesos、Kubernetes、Docker甚至你的笔记本上。

Spring Cloud Zookeeper

基于Apache Zookeeper的服务发现及配置管理。

Spring Cloud Connectors

使得运行于各种平台上的PaaS应用能够方便地连接到后端服务,比如数据库、消息Broker。(这个项目原先叫作Spring Cloud)

Spring Cloud Starters

Spring Boot风格的启动项目,简化服务消费方Spring Cloud的依赖管理。

Spring Cloud CLI

Spring Boot CLI插件,用来快速使用Groovy语言创建Spring Cloud组件应用

Spring Cloud Contract

是一揽子有关帮助用户成功地实现“消费端驱动契约”方式解决方案

Spring Cloud Gateway

是一款基于智能的、可编程的路由的Project Reactor

Spring Cloud OpenFeign

为Spring Boot应用提供通过自动化配置和绑定到Spring Environment和其它编程模型风格的集成。

原文发表于http://www.yesdata.net/2018/03/15/microservice-and-spring-cloud/

·

查看原文

赞 1 收藏 3 评论 0

执着的慢行者 发布了文章 · 2018-03-17

分布式系统开发工具包 —— 基于Kryo的Java对象序列化

Kryo是用于Java语言的一个快速和高效的对象图序列化框架。Kryo项目的目的是快速、高效、方便地使用API。当需要持久化对象的时候,不论是持久化到文件、数据库还是网络,都可以使用Kryo。

目前Kryo已经到了4.0.1版本以上了。本文的介绍适用于V2.0+以上版本。

安装Kryo

一般适用maven来Kryo包。

使用官方版的Kryo的话可以引用下述配置代码

<dependency>
    <groupId>com.esotericsoftware</groupId>
        <artifactId>kryo</artifactId>
    <version>4.0.1</version>
</dependency>

<!--more-->

如果你已经在你的classpath下有了不同版本的asm了的话,上述依赖可能会碰到问题。这时你可以使用kyro-shaded jar包,它自身包含了它所需版本的asm,并且是位于在不同包里的。

<dependency>
    <groupId>com.esotericsoftware</groupId>
        <artifactId>kryo-shaded</artifactId>
    <version>4.0.1</version>
</dependency>

如果你想试用最新的特性

<repository>
   <id>sonatype-snapshots</id>
   <name>sonatype snapshots repo</name>
   <url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
    
<dependency>
   <groupId>com.esotericsoftware</groupId>
       <artifactId>kryo</artifactId>
   <version>4.0.1-SNAPSHOT</version>
</dependency>

开始使用Kryo库

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.io.Input;
// ...
Kryo kryo = new Kryo();
// ...
Output output = new Output(new FileOutputStream("file.bin"));
SomeClass someObject = ...
kryo.writeObject(output, someObject);
output.close();
// ...
Input input = new Input(new FileInputStream("file.bin"));
SomeClass someObject = kryo.readObject(input, SomeClass.class);
input.close();

Kryo类编排序列化,Output和Input类处理缓存字节和刷新到流中。

本文为摘录,原文发布于:http://www.yesdata.net/2018/03/17/kyro/

查看原文

赞 0 收藏 0 评论 0

执着的慢行者 发布了文章 · 2018-03-17

分布式系统开发工具包 —— 基于Hessian的HTTP RPC调用技术

caucho

Hessian官网:http://hessian.caucho.com/

hessian是二进制web service协议。

Hessian介绍

创建Hessian服务包括四个步骤:

  1. 创建Java接口,用于提供公开服务
  2. 使用HessianProxyFactory创建客户端
  3. 创建服务实现类
  4. 在servlet引擎中配置服务

<!--more-->

HelloWorld服务

public interface BasicAPI {
  public String hello();
}

服务实现

public class BasicService extends HessianServlet implements BasicAPI {
  private String _greeting = "Hello, world";

  public void setGreeting(String greeting)
  {
    _greeting = greeting;
  }

  public String hello()
  {
    return _greeting;
  }
}

客户端实现

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());

部署标准web.xml

<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>

Hessian序列化

Hessian类可以用来做序列化与反序列化

序列化

Object obj = ...;

OutputStream os = new FileOutputStream("test.xml");
Hessian2Output out = new Hessian2Output(os);

out.writeObject(obj);
os.close();

反序列化

InputStream is = new FileInputStream("test.xml");
Hessian2Input in = new Hessian2Input(is);

Object obj = in.readObject(null);
is.close();

如果要序列化比基础类型或String类型更加复杂的java对象,务必确保对象实现了java.io.Serializable接口。

Hessian处理大量数据

分布式应用需要发送大量二进制数据时,使用InputStream会更加有效率,因为它避免了分配大量byte数组。方法
参数中只有最后一个参数可能是InputStream,因为数据是在调用过程中读的。

下面是一个上传文件的API的例子

package example;

public interface Upload {
  public void upload(String filename, InputStream data);
}

如果返回结果是InputStream,客户端必须在finally块中调用InputStream.close()方法,因为Hessian不会关闭
底层HTTP流,直到所有数据被读取并且input stream被关闭。

文件下载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();
}

原文发布于:http://www.yesdata.net/2018/03/11/hessian/

查看原文

赞 0 收藏 1 评论 0

认证与成就

  • 获得 34 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • Redis协议实现

    该项目是Redis协议RESP 2.0的开源实现。目的是用于帮助Redis学习者深入学习Redis。(RESP:REdis Serialization Protocol)

注册于 2014-01-24
个人主页被 1.2k 人浏览