精华内容
下载资源
问答
  • 目录 一、Spring Cloud Hytrix概述和设计目标 (一)Spring Cloud Hytrix基本概述 (二)Spring Cloud Hytrix概述设计目标 二、Spring Cloud Hytrix解决的主要内容 (一)隔离(线程池隔离和信号量隔离) ...

    目录

    一、Spring Cloud Hystrix概述和设计目标

    (一)Spring Cloud Hystrix基本概述

    (二)Spring Cloud Hystrix概述设计目标

    二、Spring Cloud Hytrix解决的主要内容

    (一)隔离(线程池隔离和信号量隔离)

    1.线程和线程池

    线程隔离的好处:

    线程隔离的缺点

    2.信号量隔离(Semaphores)

    (二)优雅的降级机制

    (三)熔断

    (四)缓存

    1.Hystrix缓存策略的命令执行流程

    2.请求合并实现

    (五)支持实时监控、报警、控制(修改配置)

    三、Spring Cloud Hystrix工作流程介绍

    四、仪表盘讲解

    (一)监控单体应用

    (二)Turbine集群监控

    五、Spring Cloud Hystrix配置说明

    1.Execution相关的属性的配置:

    2.Fallback相关的属性

    3.Circuit Breaker相关的属性

    4.Metrics相关参数

    5.Request Context 相关参数

    6.Collapser Properties 相关参数

    7.ThreadPool 相关参数

    六、Spring Cloud Hystrix线程调整和计算

    七、Spring Cloud Hystrix源码分析

    注:以上所有只做理论性的总结与分析,相关实战代码会在后面的博客中和github中逐步增加。

    参考书籍、文献和资料:


    注:以上所有只做理论性的总结与分析,相关实战代码会在后面的博客中和github中逐步增加。

    一、Spring Cloud Hystrix概述和设计目标

    (一)Spring Cloud Hystrix基本概述

    在软件架构领域,容错特指容忍并防范局部错误,不让这种局部错误不断扩大。我们在识别风险领域,风险可以分为已知风险和未知风险,容错直接应对的就是已知风险,这就要求针对的场景是:系统之间调用延时超时、线程的数量急剧飙升导致CPU使用率升高、集群服务器磁盘被打满等等。面对容错,我们一般都会要求提供降级方案,而且强调不可进行暴力降级(比如把整个论坛功能降掉直接给用户展现一个大白板),而是将一部分数据缓存起来,也就是要有拖底数据。

    在一个分布式系统中,必然会有部分系统的调用会失败。Hystrix是一个通过添加超时容错和失败容错逻辑来帮助你控制这些分布式系统的交互。Hystrix通过隔离服务之间的访问,阻止他们之间的级联故障以及提供后背选项来实现进行丢底方案,从而提高系统的整体弹性。

    Hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。

    查看Hytrix官网有如下描述:

    Introduction:
    Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, 
    services and 3rd party libraries, 
    stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

    大意是:Hystrix是一个延迟和容错库,旨在隔离远程系统、服务和第三方库,阻止级联故障,在复杂的分布式系统中实现恢复能力。

    (二)Spring Cloud Hystrix概述设计目标

    查看Hystrix官网https://github.com/Netflix/Hystrix/wiki中对设计目标有如下描述:

    Hystrix is designed to do the following:
    1.Give protection from and control over latency and failure from dependencies accessed 
    (typically over the network) via third-party client libraries.
    2.Stop cascading failures in a complex distributed system.
    3.Fail fast and rapidly recover.
    4.Fallback and gracefully degrade when possible.
    5.Enable near real-time monitoring, alerting, and operational control.

    翻译如下:

    • 1.通过客户端库对延迟和故障进行保护和控制。
    • 2.在一个复杂分布式系统中防止级联故障。
    • 3.快速失败和迅速恢复。
    • 4.在合理情况下回退和优雅降级。
    • 5.开启近实时监控、告警和操作控制。

    二、Spring Cloud Hystrix解决的主要内容

    (一)隔离(线程池隔离和信号量隔离)

    在微服务架构中,要求服务的容错隔离,以及对应的回退降级限流方案理论,我们在以下博客中已经初步提到:

    https://blog.csdn.net/xiaofeng10330111/article/details/86772740.

    隔离的基本要求是:限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。Spring Cloud Hystrix下针对隔离主要指的是线程池隔离和信号量隔离。如下图所示,可以很明显的说明信号量隔离和线程池隔离的主要区别:线程池方式下,业务请求线程和执行依赖的服务线程不是同一个线程;信号量方式下业务请求线程和执行依赖的线程是同一个线程。

    1.线程和线程池

    线程隔离指每个服务都为一个个独立的线程组,当某个服务出现问题时,不会导致整个服务瘫痪。由于线程隔离会带来线程开销,有些场景(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离,当服务的并发数大于信号量阈值时将进入fallback。

    如下图,客户端(第三方,网络调用等)依赖和请求线程运行在不同的线程上,可以将他们从调用线程隔离开来,这样调用者就可以从一个耗时太长的依赖中隔离,也可以为不同的请求开启不同的线程池,彼此之间不相互干扰。

    传统上,我们在实现线程池隔离的手段上,一般有以下两种方式:

    • 方式一:使用一个大的线程池,固定线程池的大小,比如1000,通过权重的思路为某个方法分配一个固定大小的线程数,比如为某个方法请求分配了10个线程,按照实现方式有“保守型”和“限制性”,具体见以后博客中和github中的代码举例。
    • 方式二:利用ConcurrentHashMap来存储线程池,key是方法名,值是每个方法对应的一个ThreadPool。当请求到来的时候,我们获取方法名,然后直接从Map对象中取到响应的线程池去处理。

    对于方法一而言,严格意义上讲,它并不属于线程池隔离,因为它只有一个公共的线程池,然后来让大家瓜分,不过也达到了隔离的效果。在Spring Cloud Hystrix的线程池隔离上,我们使用的是方式二。对于以上两种方式,线程池的粒度都是作用在方法上,我们可以结合实际情况也可以按组来分。

    线程隔离的好处

    整个应用在客户端调用失效的情况下也能健康的运行,线程池能够保证这个线程的失效不会影响应用的运行。当失效的客户端调用回复的时候,这个线程池也会被清理并且应用会立马回复健康,比tomcat那种长时间的恢复要好很多。简而言之,线程隔离能够允许在不引起中断的情况下优雅的处理第三方调用的各种问题。

    线程隔离的缺点

    主要缺点是增加了上下文切换的开销,每个执行都涉及到队列,调度和上下文切换。不过NetFix在设计这个系统的时候,已经决定接受这笔开销,以换取他的好处。对于不依赖网络访问的服务,比如只依赖内存缓存,就不适合用线程池隔离技术,而是采用信号量隔离。

    2.信号量隔离(Semaphores)

    可以使用信号量(或者计数器)来限制当前依赖调用的并发数,而不是使用线程池或者队列。如果客户端是可信的,且能快速返回,可以使用信号量来代替线程隔离,降低开销。信号量的大小可以动态调节,线程池却不行。

    HystrixCommand和HystrixObserverCommand提供信号量隔离在下面两个地方:

    • Fallback:当Hystrix检索fallback的时候,他心总是调用tomcat线程上执行此操作
    • 如果你设置execution.isolation.strategy为SEMAPHORE的时候,Hystrix会使用信号量代替线程池去限制当前调用Command的并发数。

    对于不依赖网络访问的服务,就不适合使用线程池隔离,而是采用信号量隔离。

    (二)优雅的降级机制

    超时降级、资源不足时(线程或信号量)降级,降级总之是一种退而求其次的方式,所谓降级,就是指在Hystrix执行费核心链路功能失败的情况下,我们应该如何返回的问题,根据业务场景的不同,一般采用以下两种模式进行降级:

    • 第一种:(最常用)如果服务失败,则我们通过fallback进行降级,返回静态值。
    • 第二种:调用备选方案

    降级会退的方案有很多,以上只是初步的建议,具体降级回退方案如:Fail Fast(快速失败)、Fail Silent(无声失败)、Fallback:Static(返回默认值)、Fallback:Stubbed(自己组装一个值返回)、Fallback:Cache via Network(利用远程缓存)、Primary + Secondary with fallback(主次方式回退),其基本的实现在相关功能由介绍,建议进行扩展学习。

    这里需要注意回退处理方式不适合的场景,如以下三种场景我们不可以使用回退处理:

    • 写操作
    • 批处理
    • 计算

    (三)熔断器

    当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复,类似于电闸。下图是基本源码中的一个处理流程图:

    断路器打开还是关闭的步骤如下

    • 1.假定请求的量超过预定的阈值(circuitBreakerRequestVolumeThreshold)
    • 2.再假定错误百分比超过了设定的百分比(circuitBreakerErrorThresholdPercentage)
    • 3.断路器会从close状态到open状态
    • 4.当打开的状态,会短路所有针对该断路器的请求
    • 5.过了一定时间(circuitBreakerSleepWindowInMilliseconds(短路超过一定时间会重新去请求)),下一个请求将通过,不会被短路(当前是half-open状态)。如果这个请求失败了,则断路器在睡眠窗口期间返回open状态,如果请求成功,则断路器返回close状态,并重新回到第一步逻辑判断。

    (四)缓存

    提供了请求缓存、请求合并实现。

    1.Hystrix缓存策略的命令执行流程                       

    2.请求合并实现

    为什么要进行请求合并?举个例子,有个矿山,每过一段时间都会生产一批矿产出来(质量为卡车载重量的1/100),卡车可以一等到矿产生产出来就马上运走矿产,也可以等到卡车装满再运走矿产,

    前者一次生产对应卡车一次往返,卡车需要往返100次,而后者只需要往返一次,可以大大减少卡车往返次数。显而易见,利用请求合并可以减少线程和网络连接,开发人员不必单独提供一个批量请求接口就可以完成批量请求。

     在Hystrix中进行请求合并也是要付出一定代价的,请求合并会导致依赖服务的请求延迟增高,延迟的最大值是合并时间窗口的大小,默认为10ms,当然我们也可以通过hystrix.collapser.default.timerDelayInMilliseconds属性进行修改,如果请求一次依赖服务的平均响应时间是20ms,那么最坏情况下(合并窗口开始是请求加入等待队列)这次请求响应时间就会变成30ms。在Hystrix中对请求进行合并是否值得主要取决于Command本身,高并发度的接口通过请求合并可以极大提高系统吞吐量,从而基本可以忽略合并时间窗口的开销,反之,并发量较低,对延迟敏感的接口不建议使用请求合并。

    请求合并的流程图如下:                  

    可以看出Hystrix会把多个Command放入Request队列中,一旦满足合并时间窗口周期大小,Hystrix会进行一次批量提交,进行一次依赖服务的调用,通过充写HystrixCollapser父类的mapResponseToRequests方法,将批量返回的请求分发到具体的每次请求中。

    (五)支持实时监控、报警、控制(修改配置)

    通过仪表盘等进行统计后,从而进行实时监控、报警、控制,最终依据这些来修改配置,得到最佳的选择。

    三、Spring Cloud Hystrix工作流程介绍

    基本流程图如下:

    1.创建HystrixCommand或者HystrixObservableCommand对象。用来表示对以来服务的请求操作。从命名就能看的出来,使用的是命令模式,其中HystrixCommand返回的是单个操作结果,HystrixObservableCommand返回多个结果
    2.命令执行,共有4中方法执行命令:

    • execute():用户执行,从依赖的服务里返回单个结果或抛出异常
    • queue():异步执行,直接返回一个Future对象
    • observe():放回observable对象,代表了多个结果,是一个Hot Observable
    • toObservable():返回Observable对象,但是是一个 Cold Observable(Hystrix大量的使用了RxJava,想更容易的理解Hystrix的,请自行百度RxJava进行阅读。)

    3.结果是否被缓存。如果已经启用了缓存功能,且被命中,那么缓存就会直接以Observable对象返回
    4.断路器是否已打开,没有命中缓存,在执行命令前会检查断路器是否已打开:

    • 断路器已打开,直接执行fallback
    • 断路器关闭,继续往下执行

    5.线程池And信号量Or请求队列是否已被占满    如果与该命令有关的线程池和请求队列,或者信号量已经被占满,就直接执行fallback
    6.执行HystrixObservableCommand.construct () 或 HystrixCommand.run()  方法。如果设置了当前执行时间超过了设置的timeout,则当前处理线程会抛出一个TimeoutyException,如果命令不在自身线程里执行,就会通过单独的计时线程来抛出异常,Hystrix会直接执行fallback逻辑,并忽略run或construct的返回值。
    7.计算断路器的健康值。
    8.fallback处理。
    9.返回成功的响应。

    四、仪表盘讲解

    Spring Cloud Hystrix Dashboard是一个可以监控HystrixCommand的可视化图形界面,由于某种原因,如网络延迟、服务故障等,这时候可以借助dashboard提供的可视化界面监控各个Hystrix执行的成功率、调用成功数、失败数量、最近十分钟的流量图等等,根据这些数据我们就可以进行错误排查以及进行服务的优化等。Hystrix Dashboard只能对单个服务进行监控,实际项目中,服务通常集群部署,这时候可以借助Turbine进行多个服务的监控。

    (一)监控单体应用

    不管是监控单体应用还是Turbine集群监控,我们都需要一个Hystrix Dashboard,当然我们可以在要监控的单体应用上继续添加功能,让它也具备仪表盘的功能,但是这样并不符合我们微服务的思想,所以,Hystrix仪表盘我还是单独创建一个新的工程专门用来做Hystrix Dashboard。Hystrix Dashboard仪表盘是根据系统一段时间内发生的请求情况来展示的可视化面板,这些信息时每个HystrixCommand执行过程中的信息,这些信息是一个指标集合和具体的系统运行情况。如Hystrix Dashboard界面图:       

    输入相关数据,得到如下仪表盘:    

    (二)Turbine集群监控

    在实际应用中,我们要监控的应用往往是一个集群,这个时候我们就得采取Turbine集群监控了。Turbine有一个重要的功能就是汇聚监控信息,并将汇聚到的监控信息提供给Hystrix Dashboard来集中展示和监控。

    在实际项目中,这种实时监控有点耗性能,通常采用消息中间件如RabbitMQ等,我们接口调用把Hystrix的一些信息收集到RabbitMQ中,然后Turbine从RabbitMQ中获取监控的数据。

    五、Spring Cloud Hystrix配置说明

    以下的属性都是spring cloud 1.5.9版本的。

    1.Execution相关的属性的配置:

    • hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore

    • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms

    • hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
    • hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
    • hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
      semaphore应该占整个容器(tomcat)的线程池的一小部分。

    2.Fallback相关的属性

    这些参数可以应用于Hystrix的THREAD和SEMAPHORE策略

    • hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10
    • hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true

    3.Circuit Breaker相关的属性

    • hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true。
    • hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20。这个参数非常重要,熔断器是否打开首先要满足这个条件。
    • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
    • hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
    • hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
    • hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage

    4.Metrics相关参数

    • hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
    • hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
    • hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
    • hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
    • hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
    • hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
    • hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms

    5.Request Context 相关参数

    hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
    hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true

    6.Collapser Properties 相关参数

    hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
    hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
    hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true

    7.ThreadPool 相关参数

    线程数默认值10适用于大部分情况(有时可以设置得更小),如果需要设置得更大,那有个基本得公式可以follow:
    requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
    每秒最大支撑的请求数 (99%平均响应时间 + 缓存值)
    比如:每秒能处理1000个请求,99%的请求响应时间是60ms,那么公式是:
    1000 (0.060+0.012)

    基本得原则时保持线程池尽可能小,他主要是为了释放压力,防止资源被阻塞。
    当一切都是正常的时候,线程池一般仅会有1到2个线程激活来提供服务

    • hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
    • hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
    • hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
    • hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
    • hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默认10000
    • hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个buckets,默认10

    六、Spring Cloud Hystrix线程调整和计算

    在实际使用过程中会涉及多个微服务,可能有些微服务使用的线程池过大,有些服务使用的线程池小,有些服务的超时时间长,有的短,所以Hystrix官方也提供了一些方法供我们来计算和调整这些配置,总的宗旨是,通过自我预判的配置先发布到生产或测试,然后查看它具体的运行情况,在调整为更符合业务的配置,通常做法有:

    1.超过时间默认为1000ms,如果业务明显超过1000ms,则根据自己的业务进行修改。

    2.线程池默认10,如果知道确实要使用更多时可以调整。

    3.金丝雀发布,如果成功则保持。

    4.在生产环境中运行超过24小时。

    5.如果系统有告警和监控,那么可以依靠他们捕捉问题。

    6.运行24小时后,通过延时百分位和流量来计算有意义的最低满足值。

    7.在生产或测试环境中实时修改值,然后用仪表盘监控。

    8.如果断路器产生变化和影响,则需要再次确认这个配置。                             

    官方例子如图,Threadpool的大小为10,如下计算公式:

    每秒请求的峰值 X 99%的延迟百分比(请求响应的时间)+ 预留缓存的值

    即 30 x 0.2s  = 6 + 预留缓存的值 = 10 ,这里预留了4个线程数。

    Thread Timeout:预留了一个足够的时间,250ms,然后加上重试一次的中位数值。

    Connect Timeout & Read Timeout:100ms和250ms,这两个值的设置方法远高于中位数值,以适应大多数请求。

    在实际的生产测试过程中,配置每个服务时可以根据官方推荐的这些方法来测试自己的业务需要的数值,这样产生最合适的配置。

    七、Spring Cloud Hystrix源码分析

    Spring Cloud Hystrix的使用: 

    • 1.启动类添加@EnableHystrix注解。 
    • 2.方法上添加@HystrixCommand注解,并指定fallback的方法。

    查看@EnableHystrix注解

    /**
     * Convenience annotation for clients to enable Hystrix circuit breakers (specifically).
     * Use this (optionally) in case you want discovery and know for sure that it is Hystrix
     * you want. All it does is turn on circuit breakers and let the autoconfiguration find
     * the Hystrix classes if they are available (i.e. you need Hystrix on the classpath as
     * well).
     *
     * @author Dave Syer
     * @author Spencer Gibb
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @EnableCircuitBreaker
    public @interface EnableHystrix {
    }

    这个注解的功能就是开启Hystrix。这个注解还引入了@EnableCircuitBreaker注解。

    在代码同一级目录下,还可以看到两个配置类:HystrixAutoConfiguration和HystrixCircuitBreakerConfiguration。 
    下面是HystrixAutoConfiguration配置类的配置:

    @Configuration
    @ConditionalOnClass({ Hystrix.class, HealthIndicator.class })
    @AutoConfigureAfter({ HealthIndicatorAutoConfiguration.class })
    public class HystrixAutoConfiguration {
    
        @Bean
        @ConditionalOnEnabledHealthIndicator("hystrix")
        public HystrixHealthIndicator hystrixHealthIndicator() {
            return new HystrixHealthIndicator();
        }
    }

    从代码中可以看到,HystrixAutoConfiguration这个配置类主要是hystrix的健康检查的配置。再看下HystrixCircuitBreakerConfiguration这个类,这个类里面就配置了很多内容。

    @Bean
    public HystrixCommandAspect hystrixCommandAspect() {
        return new HystrixCommandAspect();
    }

    这里返回了HystrixCommandAspect的bean,这个切面中定义了Pointcut:

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
    public void hystrixCommandAnnotationPointcut() {
    }
    
    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
    public void hystrixCollapserAnnotationPointcut() {
    }

    所以,这个Aspect就是利用AOP切面对 HystrixCommand 、 HystrixCollapser 两种注解的方法进行扩展处理。 
    我们在方法上添加@HystrixCommand注解,就会经过这个切面,这个切面中定义了@Around(…)拦截所有请求。

    下面看下这个方法:

    @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
        if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
            throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
                    "annotations at the same time");
        }
        MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
        MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
        HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
        ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
        Object result;
        try {
            result = CommandExecutor.execute(invokable, executionType, metaHolder);
        } catch (HystrixBadRequestException e) {
            throw e.getCause();
        }
        return result;
    }

    这个方法中,一开始先获取拦截的Method,然后判断,如果方法上同时加了@HystrixCommand和@HystrixCollapser两个注解的话,就抛异常。 
    在创建MetaHolder的时候,调用了MetaHolderFactory的create方法,MetaHolderFactory有两个子类,CollapserMetaHolderFactory和CommandMetaHolderFactory,最终执行的是子类的create方法,下面是CommandMetaHolderFactory中的create方法:

    private static class CommandMetaHolderFactory extends MetaHolderFactory {
        @Override
        public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
            HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class);
            ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType());
            MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint);
            return builder.defaultCommandKey(method.getName())
                            .hystrixCommand(hystrixCommand)
                            .observableExecutionMode(hystrixCommand.observableExecutionMode())
                            .executionType(executionType)
                            .observable(ExecutionType.OBSERVABLE == executionType)
                            .build();
        }
    }
    MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
        MetaHolder.Builder builder = MetaHolder.builder()
                .args(args).method(method).obj(obj).proxyObj(proxy)
                .defaultGroupKey(obj.getClass().getSimpleName())
                .joinPoint(joinPoint);
        if (isCompileWeaving()) {
            builder.ajcMethod(getAjcMethodFromTarget(joinPoint));
        }
    
        FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(obj.getClass(), method);
        if (fallbackMethod.isPresent()) {
            fallbackMethod.validateReturnType(method);
            builder
                    .fallbackMethod(fallbackMethod.getMethod())
                    .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType()));
        }
        return builder;
    }

    在创建MetaHolder的过程中,就会指定fallback方法。 
    创建完MetaHolder之后,就会根据MetaHolder创建HystrixInvokable。

    public HystrixInvokable create(MetaHolder metaHolder) {
        HystrixInvokable executable;
        if (metaHolder.isCollapserAnnotationPresent()) {
            executable = new CommandCollapser(metaHolder);
        } else if (metaHolder.isObservable()) {
            executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
        } else {
            executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
        }
        return executable;
    }

    这段代码里定义了后续真正执行HystrixCommand的GenericCommand实例

    方法最终会去执行CommandExecutor.execute方法:

    public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {
        Validate.notNull(invokable);
        Validate.notNull(metaHolder);
    
        switch (executionType) {
            case SYNCHRONOUS: {
                return castToExecutable(invokable, executionType).execute();
            }
            case ASYNCHRONOUS: {
                HystrixExecutable executable = castToExecutable(invokable, executionType);
                if (metaHolder.hasFallbackMethodCommand()
                        && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {
                    return new FutureDecorator(executable.queue());
                }
                return executable.queue();
            }
            case OBSERVABLE: {
                HystrixObservable observable = castToObservable(invokable);
                return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();
            }
            default:
                throw new RuntimeException("unsupported execution type: " + executionType);
        }
    }

    这里会分成同步和异步的场景,进入execute方法看下:

    /**
     * Used for synchronous execution of command.
     * 
     * @return R
     *         Result of {@link #run()} execution or a fallback from {@link #getFallback()} if the command fails for any reason.
     * @throws HystrixRuntimeException
     *             if a failure occurs and a fallback cannot be retrieved
     * @throws HystrixBadRequestException
     *             if invalid arguments or state were used representing a user failure, not a system failure
     * @throws IllegalStateException
     *             if invoked more than once
     */
    public R execute() {
        try {
            return queue().get();
        } catch (Exception e) {
            throw decomposeException(e);
        }
    }

    这个方法的注释中说明了返回值,可以返回请求的结果,当失败的时候,则会通过getFallback()方法来执行一个回退操作,由于是GenericCommand实例,那就看下这个实例中的getFallback()方法:

    @Override
    protected Object getFallback() {
        if (getFallbackAction() != null) {
            final CommandAction commandAction = getFallbackAction();
            try {
                return process(new Action() {
                    @Override
                    Object execute() {
                        MetaHolder metaHolder = commandAction.getMetaHolder();
                        Object[] args = createArgsForFallback(metaHolder, getExecutionException());
                        return commandAction.executeWithArgs(commandAction.getMetaHolder().getFallbackExecutionType(), args);
                    }
                });
            } catch (Throwable e) {
                LOGGER.error(FallbackErrorMessageBuilder.create()
                        .append(commandAction, e).build());
                throw new FallbackInvocationException(e.getCause());
            }
        } else {
            return super.getFallback();
        }
    }

    大体的一个流程也就知道了,就是通过HystrixCommandAspect,请求成功返回接口的结果,请求失败执行fallback的逻辑。

    参考书籍、文献和资料:

    【1】徐进,叶志远,钟尊发,蔡波斯等. 重新定义Spring Cloud. 北京:机械工业出版社. 2018.

    【2】郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.

    【3】王新栋.架构修炼之道.北京:电子工业出版社,2019.

    【4】https://blog.csdn.net/hzq472583006/article/details/81110443.

    【5】https://www.cnblogs.com/niechen/p/8513597.html.

    【6】https://blog.csdn.net/lij231/article/details/82956455.

    【7】https://blog.csdn.net/chayangdz/article/details/82561158.

    【8】https://segmentfault.com/a/1190000011478978?utm_source=tag-newest.

    【9】https://blog.csdn.net/Weixiaohuai/article/details/82699125.

    【10】https://www.cnblogs.com/huangjuncong/p/9043844.html.

    【11】https://blog.csdn.net/chengqiuming/article/details/81151734.

    【12】https://www.cnblogs.com/huangjuncong/p/9043844.html.

    展开全文
  • SpringMVC框架理解

    万次阅读 多人点赞 2018-01-26 10:05:24
    Struts和SpringMVC是Web层的框架,Spring是业务层的框架,Hibernate和MyBatis是持久层的框架。 为什么要使用SpringMVC? 很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常...

    JavaEE体系结构包括四层,从上到下分别是应用层、Web层、业务层、持久层。Struts和SpringMVC是Web层的框架,Spring是业务层的框架,Hibernate和MyBatis是持久层的框架。

    为什么要使用SpringMVC?

    很多应用程序的问题在于处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖于同一个业务对象时是没有灵活性的。

    SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。

    MVC设计模式

    MVC设计模式的任务是将包含业务数据的模块与显示模块的视图解耦。这是怎样发生的?在模型和视图之间引入重定向层可以解决问题。此重定向层是控制器,控制器将接收请求,执行更新模型的操作,然后通知视图关于模型更改的消息。
    在这里插入图片描述

    在这里插入图片描述

    SpringMVC架构

    SpringMVC是Spring的一部分,如图:
    在这里插入图片描述

    SpringMVC的核心架构:
    在这里插入图片描述

    具体流程:

    (1)首先浏览器发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

    (2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;

    (3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

    (4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

    (5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;

    (6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;

    (7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

    SpringMVC入门程序

    (1)web.xml

    <web-app>
      <servlet>
      <!-- 加载前端控制器 -->
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <!-- 
      	   加载配置文件
      	   默认加载规范:
      	   * 文件命名:servlet-name-servlet.xml====springmvc-servlet.xml
      	   * 路径规范:必须在WEB-INF目录下面
      	   修改加载路径:
       -->
       <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:springmvc.xml</param-value>   
       </init-param>
      </servlet>
      
      <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>*.do</url-pattern>
      </servlet-mapping>
    </web-app>
    

    (2)springmvc.xml

    <beans>
    	<!-- 配置映射处理器:根据bean(自定义Controller)的name属性的url去寻找handler;springmvc默认的映射处理器是
    	BeanNameUrlHandlerMapping
    	 -->
    	<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
    	
    	
    	<!-- 配置处理器适配器来执行Controlelr ,springmvc默认的是
    	SimpleControllerHandlerAdapter
    	-->
    	<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
    	
    	<!-- 配置自定义Controller -->
    	<bean id="myController" name="/hello.do" class="org.controller.MyController"></bean>
    	
    	<!-- 配置sprigmvc视图解析器:解析逻辑试图; 
    		后台返回逻辑试图:index
    		视图解析器解析出真正物理视图:前缀+逻辑试图+后缀====/WEB-INF/jsps/index.jsp
    	-->
    	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/WEB-INF/jsps/"></property>
    		<property name="suffix" value=".jsp"></property>		
    	</bean>
    </beans>
    

    (3)自定义处理器

    public class MyController implements Controller{
    
    	public ModelAndView handleRequest(HttpServletRequest arg0,
    			HttpServletResponse arg1) throws Exception {
    		ModelAndView mv = new ModelAndView();
    		//设置页面回显数据
    		mv.addObject("hello", "欢迎学习springmvc!");
    		
    		//返回物理视图
    		//mv.setViewName("/WEB-INF/jsps/index.jsp");
    		
    		//返回逻辑视图
    		mv.setViewName("index");
    		return mv;
    	}
    }
    

    (4)index页面

    <html>
    <body>
    <h1>${hello}</h1>
    </body>
    </html>
    

    (5)测试地址

    http://localhost:8080/springmvc/hello.do
    

    HandlerMapping

    处理器映射器将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器对象、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略。

    处理器映射器有三种,三种可以共存,相互不影响,分别是BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping和ControllerClassNameHandlerMapping;

    BeanNameUrlHandlerMapping

    默认映射器,即使不配置,默认就使用这个来映射请求。

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
    //映射器把hello.do请求映射到该处理器
    <bean id="testController" name="/hello.do" class="org.controller.TestController"></bean>
    

    SimpleUrlHandlerMapping

    该处理器映射器可以配置多个映射对应一个处理器.

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    	<property name="mappings">
    		<props>
    			<prop key="/ss.do">testController</prop>
    			<prop key="/abc.do">testController</prop>
    		</props>
    	</property>
    </bean>
    //上面的这个映射配置表示多个*.do文件可以访问同一个Controller。	
    <bean id="testController" name="/hello.do" class="org.controller.TestController"></bean>
    

    ControllerClassNameHandlerMapping

    该处理器映射器可以不用手动配置映射, 通过[类名.do]来访问对应的处理器.

    //这个Mapping一配置, 我们就可以使用Controller的 [类名.do]来访问这个Controller.
    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"></bean>
    

    HandlerAdapter

    处理器适配器有两种,可以共存,分别是SimpleControllerHandlerAdapter和HttpRequestHandlerAdapter。

    SimpleControllerHandlerAdapter
    SimpleControllerHandlerAdapter是默认的适配器,所有实现了org.springframework.web.servlet.mvc.Controller 接口的处理器都是通过此适配器适配, 执行的。

    HttpRequestHandlerAdapter
    该适配器将http请求封装成HttpServletResquest 和HttpServletResponse对象. 所有实现了 org.springframework.web.HttpRequestHandler 接口的处理器都是通过此适配器适配, 执行的. 实例如下所示:

    (1)配置HttpRequestHandlerAdapter适配器

    <!-- 配置HttpRequestHandlerAdapter适配器 -->
    <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
    

    (2)编写处理器

    public class HttpController implements HttpRequestHandler{
    
    	public void handleRequest(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		//给Request设置值,在页面进行回显
    		request.setAttribute("hello", "这是HttpRequestHandler!");
    		//跳转页面
    		request.getRequestDispatcher("/WEB-INF/jsps/index.jsp").forward(request, response);
    	}
    }
    

    (3)index页面

    <html>
    <body>
    <h1>${hello}</h1>
    </body>
    </html>
    

    Adapter源码分析
    模拟场景:前端控制器(DispatcherServlet)接收到Handler对象后,传递给对应的处理器适配器(HandlerAdapter),处理器适配器调用相应的Handler方法。

    (1)模拟处理器

    //以下是Controller接口和它的是三种实现 
    public interface Controller {
    }
    
    public class SimpleController implements Controller{
    	public void doSimpleHandler() {
    		System.out.println("Simple...");
    	}
    }
    
    public class HttpController implements Controller{
    	public void doHttpHandler() {
    		System.out.println("Http...");
    	}
    }
    
    public class AnnotationController implements Controller{
    	public void doAnnotationHandler() {
    		System.out.println("Annotation..");
    	}
    } 
    

    (2)模拟处理器适配器

    //以下是HandlerAdapter接口和它的三种实现
    public interface HandlerAdapter {
    	public boolean supports(Object handler);
    	public void handle(Object handler);
    }
    
    public class SimpleHandlerAdapter implements HandlerAdapter{
    	public boolean supports(Object handler) {
    		return (handler instanceof SimpleController);
    	}
    
    	public void handle(Object handler) {
    		((SimpleController)handler).doSimpleHandler();
    	}
    }
    
    public class HttpHandlerAdapter implements HandlerAdapter{
    	public boolean supports(Object handler) {
    		return (handler instanceof HttpController);
    	}
    
    	public void handle(Object handler) {
    		((HttpController)handler).doHttpHandler();
    	}
    }
    
    public class AnnotationHandlerAdapter implements HandlerAdapter{
    	public boolean supports(Object handler) {
    		return (handler instanceof AnnotationController);
    	}
    
    	public void handle(Object handler) {
    		((AnnotationController)handler).doAnnotationHandler();
    	}
    }
    

    (3)模拟DispatcherServlet

    public class Dispatcher {
    	public static List<HandlerAdapter> handlerAdapter = new ArrayList<HandlerAdapter>();
    	
    	public Dispatcher(){
    		handlerAdapter.add(new SimpleHandlerAdapter());
    		handlerAdapter.add(new HttpHandlerAdapter());
    		handlerAdapter.add(new AnnotationHandlerAdapter());
    	}
    	
    	//核心功能
    	public void doDispatch() {
    		//前端控制器(DispatcherServlet)接收到Handler对象后
    		//SimpleController handler = new SimpleController();
    		//HttpController handler = new HttpController();
    		AnnotationController handler = new AnnotationController();
    		
    		//传递给对应的处理器适配器(HandlerAdapter)
    		HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
    		
    		//处理器适配器调用相应的Handler方法
    		handlerAdapter.handle(handler);
    	}
    	
    	//通过Handler找到对应的处理器适配器(HandlerAdapter)
    	public HandlerAdapter getHandlerAdapter(Controller handler) {
    		for(HandlerAdapter adapter : handlerAdapter){
    			if(adapter.supports(handler)){
    				return adapter;
    			}
    		}
    		return null;
    	}
    }
    

    (4)测试

    public class Test {
    	public static void main(String[] args) {
    		Dispatcher dispather = new Dispatcher();
    		dispather.doDispatch();
    	}
    }
    

    Handler

    这里只介绍上文提到的两种处理器, 除此之外还有很多适用于各种应用场景的处理器, 尤其是Controller接口还有很多实现类, 大家可以自行去了解.

    Controller

    org.springframework.web.servlet.mvc.Controller, 该处理器对应的适配器是 SimpleControllerHandlerAdapter.

    public interface Controller {
    
    	/**
    	 * Process the request and return a ModelAndView object which the DispatcherServlet
    	 * will render. A {@code null} return value is not an error: it indicates that
    	 * this object completed request processing itself and that there is therefore no
    	 * ModelAndView to render.
    	 * @param request current HTTP request
    	 * @param response current HTTP response
    	 * @return a ModelAndView to render, or {@code null} if handled directly
    	 * @throws Exception in case of errors
    	 */
    	ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
    
    }
    

    该处理器方法用于处理用户提交的请求, 通过调用service层代码, 实现对用户请求的计算响应, 并最终将计算所得数据及要响应的页面封装为一个ModelAndView 对象, 返回给前端控制器(DispatcherServlet).

    Controller接口的实现类:
    在这里插入图片描述

    HttpRequestHandler

    org.springframework.web.HttpRequestHandler, 该处理器对应的适配器是 HttpRequestHandlerAdapter.

    public interface HttpRequestHandler {
        void handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException;
    }
    

    该处理器方法没有返回值, 不能像 ModelAndView 一样, 将数据及目标视图封装为一个对象, 但可以将数据放入Request, Session等域属性中, 并由Request 或 Response完成目标页面的跳转.

    ViewResolver

    视图解析器负责将处理结果生成View视图. 这里介绍两种常用的视图解析器:

    InternalResourceViewResolver

    该视图解析器用于完成对当前Web应用内部资源的封装和跳转. 而对于内部资源的查找规则是, 将ModelAndView中指定的视图名称与视图解析器配置的前缀与后缀想结合, 拼接成一个Web应用内部资源路径. 内部资源路径 = 前缀 + 视图名称 + 后缀.

    InternalResourceViewResolver解析器会把处理器方法返回的模型属性都存放到对应的request中, 然后将请求转发到目标URL.

    (1) 处理器

    public class MyController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            ModelAndView mv = new ModelAndView();
            mv.addObject("hello", "hello world!");
            mv.setViewName("index");
            return mv;
        }
    }
    

    (2) 视图解析器配置

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"></property>
            <property name="suffix" value=".jsp"></property>
       </bean>
    

    当然, 若不指定前缀与后缀, 直接将内部资源路径写到 setViewName()中也可以, 相当于前缀与后缀均为空串.

    mv.setViewName("/WEB-INF/jsp/index.jsp");
    

    BeanNameViewResolver

    InternalResourceViewResolver视图解析器存在一个问题, 就是只可以完成将内部资源封装后的跳转, 无法跳转向外部资源, 如外部网页.

    BeanNameViewResolver 视图解析器将资源(内部资源和外部资源)封装为bean实例, 然后在 ModelAndView 中通过设置bean实例的id值来指定资源. 在配置文件中可以同时配置多个资源bean.

    (1) 处理器

    public class MyController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            return new ModelAndView("myInternalView");
    //        return new ModelAndView("baidu");
        }
    }
    

    (2) 视图解析器配置

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
    <!--内部资源view-->
    <bean id="myInternalView" class="org.springframework.web.servlet.view.JstlView">
        <property name="url" value="/jsp/show.jsp"/>
    </bean>
    <!--外部资源view-->
    <bean id="baidu" class="org.springframework.web.servlet.view.RedirectView">
        <property name="url" value="https://www.baidu.com/"/>
    </bean>
    

    了解完这两种视图解析器后是不是有种熟悉感, 是的, 就是请求转发和重定向.

    中文乱码解决

    Get请求乱码

    Tomcat8已经解决了Get请求乱码, 如果是Tomcat8以下的版本, 可以使用以下两种方法:

    • 更改Tomcat的配置文件server.xml

      在这里插入图片描述

    • 对参数进行重新编码

      String userName =new
      String(request.getParamter(“userName”).getBytes(“ISO-8859-1”),“UTF-8”); //ISO-8859-1是Tomcat8以下版本的默认编码

    Post请求乱码
    在web.xml中加入:

    <filter>
        <filter-name>characterEncoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    展开全文
  • Java集合框架总结

    万次阅读 多人点赞 2019-08-08 09:13:16
    集合框架:用于存储数据的容器。 集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。 接口:表示集合的抽象数据类型。接口...

    简介

    集合框架:用于存储数据的容器。

    集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。
    任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

    接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。

    实现:集合接口的具体实现,是重用性很高的数据结构。

    算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。
    它减少了程序设计的辛劳。

    集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。
    通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。

    特点

    • 对象封装数据,对象多了也需要存储。集合用于存储对象。

    • 对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。

    集合和数组的区别

    • 数组是固定长度的;集合可变长度的。

    • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

    • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

    数据结构:就是容器中存储数据的方式。

    对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。

    集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参阅顶层内容。建立底层对象。

    使用集合框架的好处

    1. 容量自增长;
    2. 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
    3. 允许不同 API 之间的互操作,API之间可以来回传递集合;
    4. 可以方便地扩展或改写集合,提高代码复用性和可操作性。
    5. 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

    Iterator接口

    Iterator接口,用于遍历集合元素的接口。

    在Iterator接口中定义了三个方法:

    修饰与类型 方法与描述
    boolean hasNext() 如果仍有元素可以迭代,则返回true。
    E next() 返回迭代的下一个元素。
    void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。

    每一个集合都有自己的数据结构(就是容器中存储数据的方式),都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口,使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果。

    也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。

    使用迭代器遍历集合元素

    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("abc0");
        list1.add("abc1");
        list1.add("abc2");
    
        // while循环方式遍历
        Iterator it1 = list1.iterator();
        while (it1.hasNext()) {
            System.out.println(it1.next());
        }
    
        // for循环方式遍历
        for (Iterator it2 = list1.iterator(); it2.hasNext(); ) {
            System.out.println(it2.next());
        }
    
    }
    

    使用Iterator迭代器进行删除集合元素,则不会出现并发修改异常。

    因为:在执行remove操作时,同样先执行checkForComodification(),然后会执行ArrayList的remove()方法,该方法会将modCount值加1,这里我们将expectedModCount=modCount,使之保持统一。

    ListIterator接口

    ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。

    特点

    1. 允许我们向前、向后两个方向遍历 List;
    2. 在遍历时修改 List 的元素;
    3. 遍历时获取迭代器当前游标所在位置。

    常用API

    修饰与类型 方法与描述
    void add(E e) 将指定的元素插入到列表 (可选操作)。
    boolean hasNext() 如果此列表迭代器在前进方向还有更多的元素时,返回 true
    boolean hasPrevious() 如果此列表迭代器在相反方向还有更多的元素时,返回 true
    E next() 返回列表中的下一个元素和光标的位置向后推进。
    int nextIndex() 返回调用 next()后返回的元素索引。
    E previous() 返回列表中的上一个元素和光标的位置向前移动。
    int previousIndex() 返回调用previous() 后返回的元素索引 。
    void remove() 删除列表中调用next()previous()的返回最后一个元素。
    void set(E e) 用指定元素替换列表中调用next()previous()的返回最后一个元素。

    Collection接口

    所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:CollectionMap,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

    • Collection一次存一个元素,是单列集合;

    • Map一次存一对元素,是双列集合。Map存储的一对元素:键–值,键(key)与值(value)间有对应(映射)关系。

    单列集合继承关系图

    单列集合

    Collection集合主要有List和Set两大接口

    • List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

    • Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

    List集合

    List是元素有序并且可以重复的集合。

    List的主要实现:ArrayList, LinkedList, Vector。

    List常用方法

    List常用方法

    ArrayList、LinkedList、Vector 的区别
    ArrayList LinkedList Vector
    底层实现 数组 双向链表 数组
    同步性及效率 不同步,非线程安全,效率高,支持随机访问 不同步,非线程安全,效率高 同步,线程安全,效率低
    特点 查询快,增删慢 查询慢,增删快 查询快,增删慢
    默认容量 10 / 10
    扩容机制 int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5 倍 / 2 倍

    总结

    • ArrayList 和 Vector 基于数组实现,对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
    • LinkedList 不会出现扩容的问题,所以比较适合随机位置增、删。但是其基于链表实现,所以在定位时需要线性扫描,效率比较低。
    • 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;
    • 当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
    遍历时操作元素

    遍历集合时,同时操作集合中的元素(增删等)

    /**
      * Description: for循环遍历
      * 输出结果:
      * [a, b, c, d, e]
      * 由结果可知,第二个元素b并未删除,原因是当第一个元素b被删除后,它后面所有的元素都向前移动了一个单位,循环时导致第二个元素b漏掉了
      */
    public static void remove(List<String> list) {
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            if (s.equals("b")) {
                list.remove(s);
            }
        }
    }
    
    /**
      * Description: foreach循环遍历
      *
      * 会报错:java.util.ConcurrentModificationException。这是因为在这里,foreach循环遍历容器本质上是使用迭代器进行遍历的,会对修改次数modCount进行检查,不允许集合进行更改操作
         */
    public static void remove2(List<String> list) {
        for (String s : list) {
            if (s.equals("b")) {
                list.remove(s);
            }
            System.out.println(s);
        }
    }
    
    /**
      * Description: 使用迭代器遍历
      */
    public static void remove3(List<String> list) {
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            if (s.equals("b")) {
                it.remove();
            }
        }
    }
    

    使用迭代器遍历删除时,能够避免方法二中出现的问题。这是因为:在ArrayList中,modCount是指集合的修改次数,当进行add或者delete时,modCount会+1;expectedModCount是指集合的迭代器的版本号,初始值是modCount,但是当集合进行add或者delete操作时,modCount会+1,而expectedModCount不会改变,所以方法二中会抛出异常。但是it.remove操作时,会同步expectedModCount的值,把modCount的值赋予expectedModCount。所以不会抛出异常。

    测试方法

    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
        arrayList.add("e");
        // remove(arrayList);
        // remove2(arrayList);
        remove3(arrayList);
        System.out.println(arrayList);
    
    }
    

    总结:如果想正确的循环遍历删除(增加)元素,需要使用方法三,也就是迭代器遍历删除(增加)的方法。

    Set集合

    Set集合元素无序(存入和取出的顺序不一定一致),并且没有重复对象。
    Set的主要实现类:HashSet, TreeSet。

    Set常用方法

    Set常用方法

    HashSet、TreeSet、LinkedHashSet的区别
    HashSet TreeSet LinkedHashSet
    底层实现 HashMap 红黑树 LinkedHashMap
    重复性 不允许重复 不允许重复 不允许重复
    有无序 无序 有序,支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。 有序,以元素插入的顺序来维护集合的链接表
    时间复杂度 add(),remove(),contains()方法的时间复杂度是O(1) add(),remove(),contains()方法的时间复杂度是O(logn) LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet,时间复杂度是 O(1)。
    同步性 不同步,线程不安全 不同步,线程不安全 不同步,线程不安全
    null值 允许null值 不支持null值,会抛出 java.lang.NullPointerException 异常。因为TreeSet应用 compareTo() 方法于各个元素来比较他们,当比较null值时会抛出 NullPointerException异常。 允许null值
    比较 equals() compareTo() equals()
    HashSet如何检查重复

    当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。
    hashCode()与equals()的相关规定:

    • 如果两个对象相等,则hashcode一定也是相同的
    • 两个对象相等,equals方法返回true
    • 两个对象有相同的hashcode值,它们也不一定是相等的
    • 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
      hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

    总结:
    HashSet是一个通用功能的Set,而LinkedHashSet 提供元素插入顺序保证,TreeSet是一个SortedSet实现,由Comparator 或者 Comparable指定的元素顺序存储元素。

    Map接口

    Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
    Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

    双列集合继承关系图

    双列集合

    Map常用方法

    Map常用方法

    HashMap、HashTable、TreeMap的区别

    • TreeMap:基于红黑树实现。
    • HashMap:基于哈希表实现。
    • HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
    • LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
    HashMap HashTable TreeMap
    底层实现 哈希表(数组+链表) 哈希表(数组+链表) 红黑树
    同步性 线程不同步 同步 线程不同步
    null值 允许 key 和 Vale 是 null,但是只允许一个 key 为 null,且这个元素存放在哈希表 0 角标位置 不允许key、value 是 null value允许为null。
    当未实现 Comparator 接口时,key 不可以为null
    当实现 Comparator 接口时,若未对 null 情况进行判断,则可能抛 NullPointerException 异常。如果针对null情况实现了,可以存入,但是却不能正常使用get()访问,只能通过遍历去访问。
    hash 使用hash(Object key)扰动函数对 key 的 hashCode 进行扰动后作为 hash 值 直接使用 key 的 hashCode() 返回值作为 hash 值
    容量 容量为 2^4 且容量一定是 2^n 默认容量是11,不一定是 2^n
    扩容 两倍,且哈希桶的下标使用 &运算代替了取模 2倍+1,取哈希桶下标是直接用模运算

    HashMap在JDK1.7和JDK1.8中有哪些不同

    不同 JDK 1.7 JDK 1.8
    存储结构 数组 + 链表 数组 + 链表 + 红黑树
    初始化方式 单独函数:inflateTable() 直接集成到了扩容函数resize()
    hash值计算方式 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
    存放数据的规则 无冲突时,存放数组;冲突时,存放链表 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
    插入数据方式 头插法(先讲原位置的数据移到后1位,再插入数据到该位置) 尾插法(直接插入到链表尾部/红黑树)
    扩容后存储位置的计算方式 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

    集合工具类Collections

    Collections:集合工具类,方便对集合的操作。这个类不需要创建对象,内部提供的都是静态方法。

    静态方法:

    Collections.sort(list);//list集合进行元素的自然顺序排序。
    Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。
    class ComparatorByLen implements Comparator<String>{
      public int compare(String s1,String s2){
         int temp = s1.length()-s2.length();
         return temp==0?s1.compareTo(s2):temp;
      }
    }
    Collections.max(list);//返回list中字典顺序最大的元素。
    int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。
    Collections.reverseOrder();//逆向反转排序。
    Collections.shuffle(list);//随机对list中的元素进行位置的置换。
    
    //将非同步集合转成同步集合的方法:Collections中的  XXX synchronizedXXX(XXX);  
    //原理:定义一个类,将集合所有的方法加同一把锁后返回。
    List synchronizedList(list);
    Map synchronizedMap(map);
    

    Collection 和 Collections的区别

      Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
    
      Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
    

    数组工具类 Arrays

    用于操作数组对象的工具类,里面都是静态方法。

    数组 -> 集合:asList方法,将数组转换成list集合。

    String[] arr ={"abc","kk","qq"};
    List<String> list =Arrays.asList(arr);//将arr数组转成list集合。
    

    将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合;

    可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set;
    
    注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);
    
    如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。
    
    如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。
    

    集合 -> 数组:用的是Collection接口中的toArray()方法;

    如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。
    
    如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。
    
    所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。
    

    将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。

    用基本数据类型的数组转换ArrayList,ArrayList的size有问题

    public static void main(String[] args) {
        int[] arr1 = { 1, 2, 3, 4, 5 };
        List<int[]> intList = Arrays.asList(arr1);
        // intList size: 1
        System.out.println(String.format("intList size: %s", intList.size()));
    
        Integer[] arr2 = { 1, 2, 3, 4, 5 };
        List<Integer> integerList = Arrays.asList(arr2);
        // integerList size: 5
        System.out.println(String.format("integerList size:%s", integerList.size()));
    }
    

    asList方法接受的参数是一个泛型的变长参数,我们知道基本数据类型是无法泛型化的,也就是说基本类型是无法作为asList方法的参数的, 要想作为泛型参数就必须使用其所对应的包装类型。但是这个这个实例中为什么没有出错呢?因为该实例是将int 类型的数组当做其参数,而在Java中数组是一个对象,它是可以泛型化的。所以该例子是不会产生错误的。既然例子是将整个int 类型的数组当做泛型参数,那么经过asList转换就只有一个int 的列表了.

    结论:

    在使用asList()时尽量不要将基本数据类型数组转List.

    asList转换得到的ArrayList不是java.util.ArrayList

    public static void main(String[] args) {
        String[] arr = {"abc", "kk", "qq"};
        List<String> list = Arrays.asList(arr);
        // 添加一个元素,抛出异常UnsupportedOperationException
        list.add("bb");
    }
    

    原因:

    此处ArrayList是Arrays的内部类,并没有add方法,add方法是父类AbstractList的,但是没有具体实现,
    而是直接抛出UnsupportedOperationException异常.

    Arrays内部类ArrayList

    正确操作

    public static void main(String[] args) {
        String[] arr = {"abc", "kk", "qq"};
    	// 使用new ArrayList包裹一层
        List<String> list = new ArrayList<>(Arrays.asList(arr));
        list.add("bb");
    }
    

    如何选用集合?

    主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。

    展开全文
  • class BookServiceImpl{ //之前开发:接口 = 实现类 (service和dao耦合) //private BookDao ... //spring之后 (解耦:service实现类使用dao接口,不知道具体的实现类) private BookDao bookDao; setter方法
  • 2018几大主流的UI/JS框架——前端框架

    万次阅读 多人点赞 2018-07-28 11:37:07
    这一年中有很多热门的前端开发框架,下面源码时代web小编为大家总结2016年至今最受欢迎的几款前端框架。 在这互联网飞速发展的信息时代,技术的更新迭代也在加快。目前看来,火了十几年的Java技术现在仍然是棵常青...

    2016年开始应该是互联网飞速发展的几年,同时也是Web前端开发非常火爆的一年,Web 前端技术发展速度让人感觉几乎不是继承式的迭代,而是一次次的变革和创造。这一年中有很多热门的前端开发框架,下面源码时代web小编为大家总结2016年至今最受欢迎的几款前端框架。

    在这互联网飞速发展的信息时代,技术的更新迭代也在加快。目前看来,火了十几年的Java技术现在仍然是棵常青树。回想两年前初来咋到,也是想好好当一名java程序员,五年计划都行想好了,最后还是阴差阳错搞了前端。前端目前来看还是非常火的,随着IT技术的百花齐放,新的前端框架不断推出,但大多都还在狂吼的阶段。其实一直以来对技术的理解是技术服务于业务和产品,产品又在不同程度的推进着技术的演进。Web、无线、物联网、VR、PC从不同方向推进着技术的融合与微创新。程序员在不同业务场景下的角色互换。而随着Node.js的出现语言的角色也在发生着转变,js扮演了越来越重要的角色。也就有了茶余饭后也把了解到的知识整理一下。

    前端UI框架组件库:

    说到前端框架我第一印象中想起React、Vue和Angular,不知道你是否与我一样想到这些,现在常用的有:Bootstrap、jQuery UI、BootMetro、AUI常用的还有很多、就不一一跟大家举例出来了,因为很多朋友认为在不同项目开发中用到的前端框架不一样,其实也有一款可以适用于多种项目开发的前端框架,只是没发现。

    用前端框架开发项目的原因?

    这个应该是最好解决的问题,首先就是减少造轮子的想法,能够快速的开发一款web应用对于公司来说都是非常愿意开到的,在时间和成本之间就能够节约很多的时间,这是其中一点,另外一点就是使用前端框架的组件功能,只要组件功能强大,什么样的项目都能够开发(前提是:要熟悉前端框架的功能!),时间成本问题就能够轻松解决。

    没有设计师也能做出精美页面效果的前端框架

    虽然市场中有很多的前端框架,但部分UI框架是属于组件库,然而QUICK UI跟当下流行的三大底层框架React、Vue和Angular不同,QUICK UI提供了一整套前端解决方案,包括前后端分离的开发框架、100多种功能强大的UI控件、几十套精美的皮肤模板和近16万字的开发文档,满足你所以开发项目都不是问题。

     

    前端框架库:

    1.Node.Js

    • 地址:http://www.runoob.com/nodejs/nodejs-tutorial.html (中文网)
    • 描述:Node.js是一个Javascript运行环境(runtime)。实际上它是对Google V8引擎进行了封装。V8引 擎执行Javascript的速度非常快,性能非常好。Node.js对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。

      Node.js是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。

      简单的说 node.js 就是运行在服务端的 JavaScript

      Node.js 是一个基于Chrome javascript 运行时建立的一个平台。

      Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

    • 用途:

      1. RESTful API(目前比较流行的接口开发风格)

      这是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的API需求。

      2. 统一Web应用的UI层

      目前MVC的架构,在某种意义上来说,Web开发有两个UI层,一个是在浏览器里面我们最终看到的,另一个在server端,负责生成和拼接页面。

    不讨论这种架构是好是坏,但是有另外一种实践,面向服务的架构,更好的做前后端的依赖分离。如果所有的关键业务逻辑都封装成REST调用,就意味着在上层只需要考虑如何用这些REST接口构建具体的应用。那些后端程序员们根本不操心具体数据是如何从一个页面传递到另一个页面的,他们也不用管用户数据更新是通过Ajax异步获取的还是通过刷新页面。

      3. 大量Ajax请求的应用

    例如个性化应用,每个用户看到的页面都不一样,缓存失效,需要在页面加载的时候发起Ajax请求,NodeJS能响应大量的并发请求。  总而言之,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景。

     

    2.angular.Js(比较厉害,github排名也比较高)

    • 地址:http://www.runoob.com/angularjs/angularjs-tutorial.html (中文网)
    • 描述:AngularJS[1]  诞生于2009年,由Misko Hevery 等人创建,后为Google所收购。是一款优秀的前端JS框架,已经被用于Google的多款产品当中。AngularJS有着诸多特性,最为核心的是:MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入等等。
    • 用途:通过描述我们应该就能很好的明白AngularJS的真实用途了,MVVM,模块化,自动化双向数据绑定等等。除了简单的dom操作外,更能体现Js编程的强大。当然应用应该视场合而定。
    • 它的出现比较早,也是曾经比较流行的前端js框架,但是今年来随着reactJS与VueJS的出现,它的热度在慢慢降低。

     

    3.JQuery Mobile

    • 地址:http://www.w3school.com.cn/jquerymobile/    (中文网)
    • 描述:Query Mobile是jQuery 在手机上和平板设备上的版本。jQuery Mobile 不仅会给主流移动平台带来jQuery核心库,而且会发布一个完整统一的jQuery移动UI框架。支持全球主流的移动平台。jQuery Mobile开发团队说:能开发这个项目,我们非常兴奋。移动Web太需要一个跨浏览器的框架,让开发人员开发出真正的移动Web网站。
    • 用途:jQuery Mobile 是创建移动 web 应用程序的框架。

          jQuery Mobile 适用于所有流行的智能手机和平板电脑。

          jquery Mobile 使用 HTML5 和 CSS3 通过尽可能少的脚本对页面进行布局。

     

    4.requirejs

    • 地址:http://www.requirejs.cn/
    • 描述:RequireJS的目标是鼓励代码的模块化,它使用了不同于传统<script>标签的脚本加载步骤。可以用它来加速、优化代码,但其主要目的还是为了代码的模块化。它鼓励在使用脚本时以module ID替代URL地址。

    RequireJS以一个相对于baseUrl的地址来加载所有的代码。 页面顶层<script>标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl一般设置到与该属性相一致的目录。

    • 用途:模块化动态加载。

     

    5.Vue.js(目前市场上的主流)

    • 地址:http://cn.vuejs.org/
    • 描述:Vue.js 是用于构建交互式的 Web  界面的库。它提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API。从技术上讲, Vue.js 集中在 MVVM 模式上的视图模型层,并通过双向数据绑定连接视图和模型。实际的 DOM 操作和输出格式被抽象出来成指令和过滤器。相比其它的 MVVM 框架,Vue.js 更容易上手。
    • 目前市场上比较流行的前后端分离的开发模式,大多前端都是vueJS做的,具体的优点请大家看官方文档。

     

    6.backbone.js

    • 地址:http://www.css88.com/doc/backbone/
    • 描述:Backbone 为复杂Javascript应用程序提供模型(models)、集合(collections)、视图(views)的结构。其中模型用于绑定键值数据和自定义事件;集合附有可枚举函数的丰富API; 视图可以声明事件处理函数,并通过RESTful JSON接口连接到应用程序。

     

    7.React.js(gihub排名仅次于vue.js)

    • 地址:http://reactjs.cn/react/docs/why-react.html
    • 描述:React 是一个 Facebook 和 Instagram 用来创建用户界面的 JavaScript 库。很多人认为 React 是 MVC 中的 V(视图)。我们创造 React 是为了解决一个问题:构建随着时间数据不断变化的大规模应用程序。为了达到这个目标,React 采用下面两个主要的思想。

    8.Amaze UI

    Amaze UI是轻量级的前端应用框架,是国内比较流行的框架,比较适用于移动端响应式开发框架,可以按照项目要求生成专属的UI框架库进行使用,组件非常丰富,可以构建出漂亮的web页面。

    官网地址:http://amazeui.org/

    三、可视化组件

    1.Echarts

    • 地址:http://echarts.baidu.com/
    • 描述:ECharts,一个纯 Javascript 的图表库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖轻量级的 Canvas 类库ZRender,提供直观,生动,可交互,可高度个性化定制的数据可视化图表。

     

    2.tableau(收费)

    • 地址:http://www.yuandingit.com/special/tableau/index.html
    • 描述:Tableau 是桌面系统中最简单的商业智能工具软件,Tableau 没有强迫用户编写自定义代码,新的控制台也可完全自定义配置。在控制台上,不仅能够监测信息,而且还提供完整的分析能力。Tableau控制台灵活,具有高度的动态性。

     

    四、前端构建工具

    1.gulp

    • 地址:http://www.gulpjs.com.cn/
    • 描述:易于使用

          通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。

          构建快速

          利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作。

          插件高质

          Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作。

          易于学习

          通过最少的 API,掌握 Gulp 毫不费力,构建工作尽在掌握:如同一系列流管道。

    展开全文
  • 白盒框架与黑盒框架

    千次阅读 2018-05-31 20:04:54
    介绍 与设计模式的区别 ...前一篇文章介绍了java中的委派和继承机制,今天介绍一下利用这两种代码复用的方式组装的两种框架——白盒框架与黑盒框架 介绍 为了增加代码的复用性,可以使用委派和继承机制。...
  • 《SSH框架》---SSH框架框架

    千次阅读 多人点赞 2017-03-12 10:14:35
    SSH是 struts+spring+hibernate的一个集成框架,是目前比较流行的一种Web应用程序开源框架。 那么,我们为什么要学习框架呢?这些框架的本质到底是什么呢?
  • Java SSM框架简介

    万次阅读 多人点赞 2019-10-03 22:39:54
    前言:在学习Java SSM框架前,我提前学习了反射、注解和MVC模式,因为它们频繁在SSM框架中被用到,建议大家先弄明白了这些基础知识,再学习SSM框架就很简单了。 Java进阶知识1:反射机制 Java进阶知识2:注解 ...
  • 简述SSM框架与SSH框架

    千次阅读 2019-06-27 17:21:43
    简述SSM框架与SSH框架 一. 简介SSM框架 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架。 Spring  Spring就...
  • Hadoop与Spark等大数据框架介绍

    万次阅读 多人点赞 2018-08-09 17:06:40
    即数据集中的内容会被归约为一个具体的数值(Scala标量、集合类型的数据或存储)。 RDD拥有的操作比MR丰富的多,不仅仅包括Map、Reduce操作,还包括右图的filter、sort、join、save、count等操作,所以Spark比MR更...
  • Ajax的Javascript框架:Prototype

    千次阅读 2006-01-24 17:53:00
    Prototype是Sam Stephenson写的一个JavaScript的包...没有具体框架介绍,只是把资源贴过来。 官方网站:http://prototype.conio.net/高亮版代码:http://pik.cinbs.com/rc/?file=./src/prototype/prototype.js.html中
  • ssm框架 进行excel表格导入数据库的具体代码怎么写!!跪求
  • Yii2.0 PHP框架

    万人学习 2015-11-14 21:38:46
    具体实例讲解Yii框架的核心内容。
  • SSH框架总结(框架分析+环境搭建+实例源码下载)

    万次阅读 多人点赞 2013-04-25 10:00:28
    首先,SSH不是一个框架,而是多个框架(struts+spring+hibernate)的集成,是目前较流行的一种Web应用程序开源集成框架,用于构建灵活、易于扩展的多层Web应用程序。   集成SSH框架的系统从职责上分为四层:表示层...
  • 什么是框架
  • 框架简述

    千次阅读 2019-01-29 17:20:14
    框架是一种结构化软件 框架将我们开发中的重复性操作以及具体问题抽象化,提供一个平台,基于这个平台可以更快的开发程序。 框架主要做了两件事: 自动化常见事务 提供成熟的架构方案...
  • Unity战斗框架 技能框架

    万次阅读 多人点赞 2019-08-05 18:00:12
    Unity战斗框架 技能框架1.简介2.技能对象2.1 技能触发2.2 技能执行2.2.1 技能单元处理器2.3 技能接受2.技能流程2.1 初始化流程 1.简介 该框架基本可以概括为以下这句话: 释放某技能后,先左斩一下(播个动画放个...
  • 以前是一名php开发人员,最近公司开始一个java开发的erp项目,从新学起的感觉倍爽,各种问题各种遇到,从通过ajax异步提交数据具体实现方法这里开始,以后在博客上慢慢呈现,话不多说,下面就是我通过springMvc和...
  • 设计框架

    千次阅读 2017-07-01 15:49:13
    可复用面向对象软件系统一般划分为两大类:应用程序工具箱和框架(Framework),我们平时开发的具体软件都是应用程序,Java的API属于工具箱;而框架是构成一类特定软件可复用设计的一组相互协作的类,EJB...
  • SSM框架

    万次阅读 多人点赞 2018-05-27 19:00:17
    一.SSM框架简单介绍 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、SpringMVC、MyBatis三个开源框架整合而成,常作为数据源较简单的web项目的框架。 其中spring是一个轻量级的控制反转(IoC)和面向切面(AOP)...
  • from flask import Flask app = Flask(__name__,template_folder='static/templates') app.debug=True # 导入app1/views.py中创建的蓝图 from app1.views import app1 ...app.register_blueprint(app1, url_prefix='/...
  • 基于哈希的图像检索框架

    千次阅读 2017-09-08 09:36:01
    基于哈希的图像检索技术其具体框架如图1.4所示,按步骤可以分为特征提取、哈希编码、汉明距离排序以及重排四个步骤:(1) 特征提取。对图像数据库中的图像逐一进行特征提取,并将其以图像文件名和图像特征一一对应的...
  • 前端框架Ext学习之框架搭建

    万次阅读 2017-08-02 10:12:56
    学无止境啊,当你在工作时就会发现,只恨自己少长了几个脑袋。...既然要学习这个Ext的框架了,那么首先就要先了解一下这个框架具体情况啊:  前言 Ext: Ext是一个强大的js类库,以前是基于YAHO
  • HTML中框架框架,浮动框架简述

    千次阅读 2019-05-05 06:11:56
    最简单的框架集合框架 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <!-- 默认根据内容需要显示是否...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 980,600
精华内容 392,240
关键字:

具体框架