Scala中使用Hystrix Command(Non-Blocking)

直接看代码吧

package commands

import javax.inject.Inject

import com.netflix.hystrix.HystrixObservableCommand.Setter
import com.netflix.hystrix._
import play.api.Logger
import play.api.cache.Cache
import play.api.libs.json.{Json, JsValue}
import rx.lang.scala.JavaConversions
import rx.{Subscriber, Observable}
import rx.Observable.OnSubscribe
import scala.collection.JavaConversions._
import scala.concurrent.duration._
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.Play.current

import scala.reflect.ClassTag

/**
  * Created by nealmi on 1/12/16.
  */

object HystrixBridge {

  /**
    * 通过Scala Promise 和 Future 以及 RxScala 桥接 RxJava 以实现no-blocking处理, 方便以Scala的方式使用Hystrix
    *
    * @param groupKey   用来分组,比如所有请求kanche-api的调用使用 kanche-api 分成一组
    * @param commandKey 通常用来描述实际操作,比如:retrieve opened cites
    * @param command    实际处理函数
    * @param fallback   在command()出现异常的时候调用
    * @tparam T 泛型参数
    * @return Future[T]
    */
  def async[T](groupKey: String, commandKey: String, command: () => Future[T], fallback: () => Future[T]) = {
    val setter = Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
      .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))

    val observable = new HystrixObservableCommand[T](setter) {
      override def construct(): Observable[T] = {
        Observable.create(new OnSubscribe[T] {
          override def call(observer: Subscriber[_ >: T]): Unit = {
            if (!observer.isUnsubscribed()) {
              val future = command()
              future onSuccess {
                case result =>
                  observer.onNext(result)
                  observer.onCompleted()
              }

              future onFailure {
                case t =>
                  observer.onError(t)
              }
            }
          }
        })
      }

      override def resumeWithFallback(): Observable[T] = {
        Observable.create(new OnSubscribe[T] {
          override def call(observer: Subscriber[_ >: T]): Unit = {
            if (!observer.isUnsubscribed()) {
              val future = fallback()
              future onSuccess {
                case result =>
                  observer.onNext(result)
                  observer.onCompleted()
              }

              future onFailure {
                case t =>
                  observer.onError(t)
              }
            }
          }
        })
      }
    }.observe()


    val scalaObservable = JavaConversions.toScalaObservable(observable)

    val promise = Promise[T]

    scalaObservable.subscribe(
      x => promise.success(x),
      e => promise.failure(e),
      () => ()
    )
    promise.future
  }
}

使用Maven管理项目(一)

我使用Maven来管理项目也有段时间,不过大部分都是在开始配置好,然后就修修补补,一直都是本着够用就好的原则,从来没有真正花太多时间去研究它。最近有点闲暇时间,重新思考了下。

假设问题:我们现在要开发一个简单在线购物的系统,基本想法是包括简单的后台管理(编辑商品,库存管理等等)和前端购物网站。

分析:按照大型系统来设计的话,估计需要把商品目录,库存管理,订单处理,促销活动管理,首页,搜索,商品详细全部分成子模块来管理,就像某东。但是很明显,本着够用就好的原则,这个不太适合起步做一个小型的系统,所以我决定采用如下组织模式:

domain – 领域对象模块
persistence – 持久化模块
service – 服务模块
admin-site – 后台管理
customer-site – 购物网站

我觉得我们可以开始了,首先建一个文件夹shop,打开终端,准备创建各个模块。

项目目录一个一个手工创建也太麻烦了,所以就需要Maven Archetype Plugin,一看文档一大堆参数真愁人,没关系先试试看再说。下面这个命令好像是生成啥的:

mvn archetype:generate

哈哈,这货各种的loading啊,之后进入了交互模式:

出来了一个项目模板列表,问你你用那个模板呢,默认是361 — quickstart 模板,直接回车

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 361: 
 

你用模板的那个版本呢?默认1.1,直接回车。

Choose org.apache.maven.archetypes:maven-archetype-quickstart version: 
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
Choose a number: 6: 

下面比较关键了,groupId这里用我们的顶级包名,模块名用domain,版本默认,源码包用com.nealmi.shop.domain

Define value for property 'groupId': : com.nealmi.shop
Define value for property 'artifactId': : domain
Define value for property 'version':  1.0-SNAPSHOT: : 
Define value for property 'package':  com.nealmi.shop: : com.nealmi.shop.domain

到这里,domain模块已经生成。其实上面的过程就是一条命令就能搞定:(看出来了把,-D参数名=参数值)

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.1 -DgroupId=com.nealmi.shop -DartifactId=domain -Dpackage=com.neami.shop.domain -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false

按照同样的方式,生成persistence和service模块。

为什么不用同样的方式生成admin-site和customer-site呢?这个嘛,内个,貌似这俩货是web项目把,所以呢,换个archetype模板:maven-archetype-webapp。

 mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.0 -DgroupId=com.nealmi.shop -DartifactId=admin-site  -Dpackage=com.neami.shop.admin -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false

看下生成之后记得目录结构(用了一个神奇shell语句)

$ ls -R | grep ":" | sed -e 's/://' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'
 |-admin-site
 |---src
 |-----main
 |-------resources
 |-------webapp
 |---------WEB-INF
 |-customer-site
 |---src
 |-----main
 |-------resources
 |-------webapp
 |---------WEB-INF
 |-domain
 |---src
 |-----main
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------domain
 |-----test
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------domain
 |-persistence
 |---src
 |-----main
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------persistence
 |-----test
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------persistence
 |-service
 |---src
 |-----main
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------service
 |-----test
 |-------java
 |---------com
 |-----------neami
 |-------------shop
 |---------------service

目录结构建立完成,试试编译打包。分别到每个模块目录下执行 mvn package,应该能将每个项目打包:
domain-1.0.0-SNAPSHOT.jar
persistentce-1.0.0-SNAPSHOT.jar
service-1.0.0-SNAPSHOT.jar
admin-site.war
customer-site.war

为什么有的是jar,有的是war呢,因为我们用了两种模板。生成的pom.xml里,quickstart模板对应jar,webapp模板对应了 war,当然你可以自己修改pom.xml.

但是现在每次要到各自模块下执行命令,比较麻烦,我希望执行一个命令将所有的模块都打包。这就需要用到maven模块的概念了,在所有shop下(也就是所有模块的父目录)创建一个pom.xml文件,packaging用pom,加入modules配置。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelversion>4.0.0</modelversion>
  <groupid>com.nealmi.shop</groupid>
  <artifactid>base</artifactid>
  <packaging>pom</packaging>
  <version>1.0.0-SNAPSHOT</version>
  <name>admin-site Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <modules>
      <module>domain</module>
      <module>persistence</module>
      <module>service</module>
      <module>admin-site</module>
      <module>customer-site</module>
  </modules>
  <build>
    <finalname>base</finalname>
  </build>
</project>

在shop下执行 mvn package,所有模块是不是都自动打好了包。现在新的问题来了,每个模块有独立的版本号,虽然在有些情况(通常大项目,每个模块不同的开发组的情况)下可能会用到,只是我们目前不需要,只需要所有模块一致版本号。既然我们刚刚写了顶层pom.xml,最好在它里面管理版本号,这就需要让每个子模块知道老爹是谁。
在每个子模块的pom.xml里加入,同时删掉子模块的版本号,这样所有子模块就继承了父pom的版本号。父子关系出现了,很多其他的东西也可以用同样的模式处理,比如properties,dependencies等等。

<parent>
     <groupid>com.nealmi.shop</groupid>
        <artifactid>base</artifactid>
        <version>1.0.0-SNAPSHOT</version>
        <relativepath>../</relativepath>
  </parent>

如果你细心观察输出的话,你会发现一行警告,大致是说,用系统的编码拷贝的文件,这个到别的平台上可能会有问题。
[note]
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[/note]
永远不要忽视警告,有时候会死的很惨的。要处理这个警告,就要涉及到插件Maven Resources Plugin ,这里我们引入两个新的概念,属性(properties)和插件管理(pluginManagement).

属性定义如下,这里的project.build.sourceEncoding指定是Maven Complier Plugin的源码编码,就是你的.java是用的什么编码。基本等价于:mvn compile -Dproject.build.sourceEncoding=UTF-8.
我们这里图省事,就直接用它作为拷贝资源文件的编码来用,在pom.xml里可以通过${project.build.sourceEncoding}来引用这个值。

<properties>
    <project .build.sourceEncoding>UTF-8</project>
</properties>

指定拷贝资源文件(各种配置文件)的编码,这里要说一下pluginManagement,这个是用来共享插件配置信息的一个功能,我们这里配置了resources插件,所有子模块只要引用的这个插件就会使用这个配置信息,当然子模块也可以覆盖这个配置。至于子模块为什么没有通过plugin配置引用这个插件,也能工作,是因为这个插件属于核心插件,所有项目默认引用(注意:这个描述可能不是很精确,但是大意类似)。可以到http://maven.apache.org/plugins/index.html去看一下核心插件(core plugins)

<build>
    <finalname>base</finalname>
    <pluginmanagement>
        <plugins>
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-resources-plugin</artifactid>
                <version>2.6</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>          
        </plugins>
    </pluginmanagement>
  </build>

现在执行一下,是不是没有警告了。

如果你能耐着性子看到这里,你应该能知道,Maven是靠各种插件来完成工作的,比如:编译,打包,测试,部署等。

下面引入Maven一个强大功能:透明依赖管理。如果你做了很长时间java的项目,尤其是规模稍大的,你肯定遇到过jar包版本冲突的问题,估计说起来都是泪啊。
在生成domain模块的时候,你如果看了pom.xml,你会发现一行如下配置:

<dependencies>
    <dependency>
      <groupid>junit</groupid>
      <artifactid>junit</artifactid>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

按照字面上来理解,应该也能大致猜出,domain模块依赖junit 3.8.1。等等,scope是干嘛的?这个就是字面的意思,依赖的使用范围,这里是test,也就是测试的时候用这个,打包啥的就不用了。
常用的scope就下面这四个:
compile – 编译发布都用,比如你用spring的类库,apache的commons类库
provided – 容器提供了,比如servlet类库,不打包
runtime – 编译不用,打包发布必须得用,比如:数据库驱动(一般是通过classloader动态加载的类库)
test – 测试过程使用

这里你打开domain,persistence和service的pom.xml,你会发现每个里面都是配置了junit的依赖,根据DRY(不用重复你自己)原则明显是不对的。我们要的是所有子模块使用同样的junit版本。这里就要用 pluginManagement的配置了,我们不想用junit 3.8.1这个过时的版本(好吧,我的错,我用了老版本的archetype模板生成的项目)。
打开shop下的pom.xml,加入如下配置(与build配置平级),然后去掉所有子模块里面junit的version和scope配置:

<dependencymanagement>
    <dependencies>
      <dependency>
      <groupid>junit</groupid>
      <artifactid>junit</artifactid>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    </dependencies>
  </dependencymanagement>

这样,所有子项目继承了父pom.xml里junit的version和scope配置。执行mvn test试试看,Maven就自动给你去下载junit相关的类库了。这里你肯定要问了,这不还是每个子模块都要引用junit么,没解决多大问题呢。是的,仔细想想,父子继承什么的,dependencyManagement 和 dependencies的区别。

打开shop下的pom.xml,加入如下配置(与build配置平级),然后删除所有子模块中junit依赖配置:

<dependencies>
    <dependency>
      <groupid>junit</groupid>
      <artifactid>junit</artifactid>
    </dependency>
</dependencies>

这里组织原则是,如果所有子模块都共用的依赖(就像Junit)就是下载父pom的dependencies下,但是Junit的version和scope通过dependencyManagement统一管理。举个例子:domain,persistence和service模块不需要Spring MVC的类库,但是admin-site和customer-site需要,Spring类库就不应该配置在父pom的dependencies里。

可是你又会问,那不是就还是不符合DRY原则了么,两个site模块都要直接引用Spring MVC类库。再问这个问题就有点招人烦了,现在shop是老爹,所有子模块都是儿子,如果admin-site和customer-site这两个儿子编程site的儿子(也就是shop的孙子)的话,问题不就解决了.具体的配置我就不写了,留作你的练习吧。

shop源码下载

写累了,就到这里吧,总结一下:
Maven Archetype Plugin
Maven Resources Plugin
Maven Complier Plugin
多模块项目组织
插件管理
依赖管理
属性定义
mvn命令参数传递

Javascript 获得设备时区

前端时间做 Mobile Website 的时候有个需求是显示的日期要和设备时区一样,基本思路是通过 Javascript 获得设备的时区,写到本地 Cookie,然后再传到日期的格式化标签里。代码如下:


function setCookie(c_name, c_value, c_expiredays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + c_expiredays);
document.cookie = c_name + “=” + c_value +
((c_expiredays == null) ? “” : “;expires=” + exdate.toGMTString()) +
“;path=/”;
}

//Write current timzone to cookie for date format later.
(function () {
var now = new Date();

var offsetInMinutes = now.getTimezoneOffset();
var timezonePrefix = “GMT”;
if (offsetInMinutes < 0) timezonePrefix += "+"; else timezonePrefix += "-"; offsetInMinutes = Math.abs(offsetInMinutes); var hours = parseInt(offsetInMinutes / 60); if (isNaN(hours)) { hours = 0 } var minutes = parseInt(offsetInMinutes % 60); if (isNaN(minutes)) { minutes = 0 } //alert(hours + ":" +minutes); var h = hours < 10 ? "0" + hours : "" + hours; var m = minutes < 10 ? "0" + minutes : "" + minutes; var timezone = timezonePrefix + h + m; setCookie("timezone", timezone); })(); [/javascript] 在JSP里面获得时区: [html]
[/html]

Ruby Square

少搬些砖头 – 使用 IDEA 的模板

最近在写一些项目要用的 Restful Web Service,步骤基本就是,定义接口-写实现,在写到第三个接口的时候,就已经恶心了,大部分时间都是在点鼠标,新建一个类,实现接口,实现方法,而且都是固定的模式(Restlet 的模式)。于是就萌生了写一个简单的生成器,可是一想到一大堆的事儿,目前还没有时间,所以就研究一些简单快速的方法,后来发现了IDEA的File Templates功能。

IDEA的File Template是使用Velocity实现,语法可以自行Google,当然你如果喜欢Baidu也可以使用它搜索。

在新建菜单里有一个 Edit Templates 菜单,点击之后就可以打开如下界面。

剩下的就自行研究吧,如何灵活使用就要靠自己的想象力了。我只是用了一个很简单的功能,如图所示,建立一个标准Resource实现的模板,每次不用写那些方法。

这个肯定不能完全解决我的问题,不过目前能提升多少算多少啦。将来有空写个代码生成起,一次生成一个标准(我自己定的标准)的Restful Web Service骨架。

 

Burst.net VPS 优惠码

最近收到Burst的邮件,VPS促销:VPS全系列产品优惠10%,仅限新客户,优惠码见下面邮件正文粗体。

BurstNET® SUMMER VPS CLEARANCE SALE! ALL EXISTING LOCATIONS!

The following discount code is available for our global VPS services:

10% OFF ALL VPS SERVICES: USE PROMO CODE “SUMMER10” **
VALID THRU JULY 31ST 2012

SERVICE AVAILABILITY: ALL LOCATIONS
Scranton, PA – Los Angeles, CA – Miami, FL – Chicago, IL – Manchester, UK/EU

US: PRODUCT DETAILS – ORDER LINK
UK/EU: PRODUCT DETAILS – ORDER LINK

Regards,
BURSTNET®
SALES DEPT

** AVAILABILITY – Sale valid for NEW CLIENTS or VPS ONLY. Existing service non-transferable.