java常见工具库培训

目前项目中常见的工具库有apache commons,google guava,再算上spring的话,需要自己从头开始写工具类的情况大大减少。 为了给广大童鞋普及一下工具库用法,减少无用功(还可能因为实现的不好留后遗症的),这里简单的介绍一下相关工具类。google guava大家应该比较陌生,这里先不介绍,:)

apache commons

官方地址: http://commons.apache.org/

apache commons历史悠久,涉及范围也是最广的,在官网上分了数十个模块,但有些模块是新开发的,就不要贸然使用啦。

这里只是介绍最最常用的commons库,排名不分先后,如下:

commons-codec

包括常见的编码、解码算法,例如MD5,Base64,举例如下:

  • Base64#encode 加密成base64串
  • Base64#decode 解密base64串
  • DigestUtils.md5Hex 进行MD5加密,注意得到的是小写的MD5(MD5标准不区分大小写),在比较的时候需要注意
  • DigestUtils.shaHex 进行SHA1加密 SHA256,512之类也是支持的,可以自行查阅

commons-collections

包括一堆增强的集合类(我了解不多,大家可以自行学习),各种和集合类相关的工具类,举例如下:

  • CollectionUtils.isEmpty 是否null或空集合,这一类的方法很多,看看有个大概印象
  • MapUtils.isEmpty 是否null或空Map
  • ListUtils.removeAll 从某个列表中删除存在于另外个列表的元素

同类型的还有SetUtils、IteratorUtils等,大体上是集合相关的操作,如过滤、是否相等、交集、差集、转换(变同步、变不可变)等,其实这个用到的机会也不是很大。

commons-net

实现了一些常见的网络协议,可能关系最大的要数ftp、smtp的实现了。而jdk带的sun.net.ftp,这个尽量就少用拉。

这套api的实现用法得google一下了,看官方文档的例子, 又或者别人的经验代码,例如这个http://my.oschina.net/hly3825/blog/33657

commons-httpclient

http客户端实现,貌似已经从commons独立出去了。3.x版本和4.x版本变化比较大,大家要使用的时候自行查阅资料。 尽量避免使用HttpURLConnection去直接搞。

commons-io

io方面的工具类,主要包括文件处理、流处理,常见的类有IOUtils、FileUtils、FilenameUtils。举例如下:

  • IOUtils.closeQuietly 安静关闭输出输出流,常用于finally关闭流的时候
  • IOUtils.copy 把某个输入流拷贝到某个输出流中去
  • IOUtils.toString 把某个输入流、URI的内容转换成字符串
  • IOUtils.readLines 按行读取流
  • Charset.UTF_8 有一些常见的、系统都会支持的字符集,已经定义成常量
  • FileUtils.readLines 按行读取文件
  • FileUtils.readFileToString 读取文件保存在一个字符串中

IOUtils针对的是stream,FileUtils针对的是File对象,相应的有文件拷贝、删除等操作。
注意的是,使用字符流格式的时候,务必指定编码

commons-lang

这个是使用最多的库了,有lang2.x和3.x版本,尽量使用3.x版本。

常见的有StringUtils、SystemUtils、RandomStringUtils、DateFormatUtils、DateUtils、各种Builder、Validate,举例如下:

  • StringUitls.isEmpty 判空,和isBlank的区别在于它不进行trim
  • StringUtils.join 按分隔符合并,这个很常用
  • StringUtils.repeat 重复某个字符或字符串,有些需要格式化的是会用到
  • StringUtils.startsWith 和endsWith那样,是增强版本,还有endsWithAny、endsWithIgnoreCase等
  • SystemUtils 主要是一些常见系统环境变量,如临时目录、用户目录、分隔符等
  • RandomStringUtils 用来生成各种随即字符串,例如全字母、全数字或混合型的
  • DateFormatUtils、DateUtils 一个是字符串变日期,一个是日期相关的操作
  • 各种Builder 主要用实现常见的toString、compareTo、equals、hashcode等常见类,例如ReflectionToStringBuilder就很方便实现toString方法。同理,CompareToBuilder、EqualsBuilder、HashCodeBuilder都很好理解。
  • Validate 实现一些assert,例如Validate.notNull可以用来做前置校验,和spring的Assert类是类似的。

其他commons库

  • commons-fileupload 仅限于在文件上传的类中使用,虽然它也有一些工具类,但是就不要在其他地方使用啦。
  • commons-dbcp 一个数据库连接池,现在就比较少用了
  • commons-pool 一个java对象池实现,通常用来缓存一些耗时较大的对象,dbcp也是基于它的,一般也少直接用。
  • commons-logging 日志包装实现,在开源项目中使用广泛,项目中一般直接用log4j等。

使用org.json库进行xml和json转换存在的问题

org.json库中提供一个xml和json进行转换的工具类,XML.java

使用方式如下:

  • xmlstr = XML.toString(jsonstr)
  • jsonstr = XML.toJSONObject(xmlstr).toString()

中间层原有代码使用这种方式进行格式转换,不过存在一些问题:

  • json转换为xml的时候,对带content字段的节点,是直接生成文本,而不是xx
  • xml转换为json的时候,会对指为整形(还有true/false/null等)的字符串尝试进行转换,变成原生类型

为了避免这两个问题,对org.json库的XML.java进行了一些修改:

  • 去掉content字段的特殊处理
  • 去掉整形字符串尝试转换的逻辑

见https://github.com/mccxj/JSON-java

经验教训: 以后引用第三方库的时候,要小心呀,避免触碰到一些特殊开关。

结合状态机的开发风格

本文主要以XXX的html5版本为蓝本,讨论结合状态机开发的思路和实践方式。状态机选型使用statechart.js

起步知识

特别是状态机介绍,内容非常好,强烈推荐。

适用场景

  • 主要用于某个具体业务的复杂页面流控制
  • 简单的业务流程是不需要的。例如只有一两个页面(列表+详情)
  • 适用于多步骤多页面(包括弹出框)、各种跳转的场景

如何定义状态?

根据页面流、步骤来定义状态。可以参考以下步骤:

  • 对照保真、流程图,划分每个独立页面 以个人营销活动为例,主要页面包括活动页面、选档次页面、奖品页面、奖品包选择页面、缴费页面、发票页面。 那么可以考虑定义为list、level、reward、giftpack、charge、invoice

  • 对于有多种弹出窗口的情况,可以考虑定义子状态 以推荐业务为例,在菜单页面上,可能会弹出反馈窗口,或者产品订购窗口。 那么可以考虑定义为menu/index、menu/feedback、menu/prod,这样的话,通过下面的页面控制,就可以让在值状态的情况下,菜单页面一直显示。

<div ng-show="fsm.isCurrent('/menu')" ng-include="'app/partials/recommended/recommended_menu.html'"></div>
<div ng-show="fsm.isCurrent('/menu/prod')" ng-include="'app/partials/recommended/recommended_orderprod.html'"></div>
<div ng-show="fsm.isCurrent('/menu/feedback')" ng-include="'app/partials/recommended/recommended_feedback.html'"></div>
  • 对于页面显示,有较多共性的页面,可以考虑定义子状态,方便共享逻辑和事件处理 以上述的个人营销活动为例,奖品页面、奖品包选择页面的页面很类似,功能操作也比较实现,可以定义成子状态,如order/reward、order/giftpack
<div ng-show="fsm.isCurrent('/list')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_list.html'"></div>
<div ng-show="fsm.isCurrent('/level')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_level.html'"></div>
<div ng-show="fsm.isCurrent('/order/reward')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_reward.html'"></div>
<div ng-show="fsm.isCurrent('/order/giftpack')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_giftpack.html'"></div>
<div ng-show="fsm.isCurrent('/invoice')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_invoice.html'"></div>
<div ng-show="fsm.isCurrent('/charge')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_charge.html'"></div>

如何控制页面的显示、如何响应页面操作?

  • 页面显示与否,通过状态机的状态,而不是数据的状态。这里用的是isCurrent方法
  • 页面操作,通过状态机的事件发送,而不是直接使用绑定在$scope的方法。这里用的是send方法
  • 页面跳转,通过状态机的状态变化来驱动。这里用的是goto方法,是在send方法之后的event逻辑中处理的。

页面显示与否,例子上面已经说了。而对于ng-click这种事件触发,直接用send方法即可。

<div class="Feedback-btn">
  <a ng-click="fsm.send('feedback', 'hesitate')" class="accept-btn"><span>考虑</span></a>
  <a ng-click="fsm.send('feedback', 'refuse')" class="refuse-btn"><span>拒绝</span></a>
</div>

可以看到,事件也可以捎带参数的,这样可以在该状态的event中进行处理,如下:

# 反馈
@state 'feedback', ->
  # 进行反馈操作
  @event 'feedback', (operationtype) ->
    product = $scope.viewModel.product
    if product.opertype == '1'
      new Toast(
        context: $('body')
        message: "该产品不可推荐"
      ).show();
      return

    qryfeedbackService.event
      "userseq": $scope.viewModel.product.userseq
      "servnumber": $scope.telnum
      "operationtype": operationtype
    .then (ok) =>
        @goto '/menu/index'
      , (err) ->
        new Toast(
          context: $('body')
          message: err
        ).show()

需要注意的是,event是挂靠在某个状态下的,如果你是子状态的话的,event会先在子状态中找,如果没有找到会在父状态上找。 通过这种方式,就可以实现多个子状态共享event,例如奖品页面、奖品包页面都有选择功能,就可以把这个操作放到父状态的event中去。

更多状态机的细节

很多状态机都实现了某些特殊状态,如进入状态,退出状态这种事件。statechart也实现了,对应的是enter和exit,代码大体上是:

@state 'menu', ->
  @enter ->
    #TODO

  @exit ->
    #TODO

但是需要注意的是,重复进入这个状态的话,是会重复执行的。所以对于A -> B -> C这样的业务流程,从A到B和C回退到B,都会执行这个enter, 就无法区分这种情况了。因为通常,从A到B是进行初始化,而从C回到B得保留原来B的数据状态。所以实际上我很少使用这些特殊事件,除非:

  • 无需区分的情况,这样写会让代码风格更统一。
  • 没有接口交互,本地操作的话,因为这种消耗小很多。
  • 存在直接跳转到该状态的情况(例如由另外的业务跳转过来),这种特殊情况下前面的步骤都被忽略。而且这种情况下,需要通常需要接口交互(例如补充某些必要信息),而为了区分回退的情况,我通常会根据业务特性考虑一些数据缓存处理。

json格式须知

着重介绍与项目使用相关的json知识。如果没有特别说明,环境是指Javascript下的json。

区分类型

  • 首先需要区分json字符串和json对象,不过通常根据上下文可以区分。
  • 协议关注的是json字符串,而代码中处理的是json对象,两者通过序列化(JSON.stringify)和反序列化相互转换(JSON.parse)。

常见格式

  • 主要有数组和key/value形式的object
  • 数组是有顺序的,可以不同类型,常用于顺序遍历操作。
  • object是无顺序的,key只能是字符串,常用于快速随机查找。
  • null是可以被序列化的,而undefined不可以(会消失)。
  • 其他的一些特殊值,如Nan,Infinity,会被序列化为null。

关于数组

  • 对于数组对象,虽然支持key/value的操作,但是序列化的时候设置的值会丢失。
  • 数组序列化的长度是根据length属性来的,没有赋值的位置是null。
  • 对数组遍历不应该采用for in语句,因为通过key/value设置的值也会被输出。

关于Object

  • 规范上规定key是带双引号的字符串(),但实际上很多反序列化工具能够支持数值、单引号字符串、字符串字面量(没有引号的字符串)。
  • 如果是一普通浮点数值,可以通过相应的数值作为key获取,或者通过对应的字符串来获取。如用2.2的话,可以用2.2或”2.2”。
  • 如果是一整型数值,可以用数值,但用字符串只能用整型的,如用2.0的话,可以用2.0或2或”2”,但”2.0”就不可以。
  • 如果使用字符串字面量的话,需要避免一些关键字使用。如delete
  • key不应该重复,如果重复的话,通常结果是后面的会覆盖前面的。
  • 可以用.后面加key来取值,或者用[]这样的操作符来获取,第一种方式更推荐,但只能支持非数字开头的字符串,unicode也是可以。
  • 对object遍历可以采用for in语句。

协议转换

  • 协议传输的是json字符串,但通常里边的类型都是字符串,不区分数值,因此做数值运算需要先转换。
  • 和xml一样,需要注意特殊字符如引号、回车、unicode等,尽量避免手动拼接,采用序列化工具。
  • object类型的json序列化/序列化的时候,都不应该预期他是有顺序的,虽然很多库都有带默认顺序,应该使用数组。

angular开发总结

下面是XXX的HTML5版本开发总结。

开发思路

说好是MVVM了,当然和以前也页面为中心进行DOM操作的方式很不一样。这方面可以参考文章:StackOverFlow精彩问答赏析:有jQuery背景的开发者如何建立起AngularJS的思维模式?

开发经验就是,以数据为中心来考虑。我通常是对着高保真,先把所需要的数据结构先构思了,看看接口是如何和数据进行对接的。

不过,即使是以数据为中心的开发模式,逻辑与视图的完全分开几乎是不可能完成的任务,只能尽量的分离。我们需要操作的数据模型,不是真正意义的业务模型,而是视图模型(业务数据和视图数据的混合体)。如果实现上需要更偏重业务模型的话,可能要更多借助于resource的概念(ng也有提供这个),不过对于我们这种过程化的接口,意义不大。

关于Service

因为接口的原因,现在有的Service文件,按规范是一个接口对应一个,而且文件内容除了接口参数和响应结果获取,其他基本上是一样的。

根据接口数据的格式,我有时候会预先在service里边进行格式调整,例如一个带父子关系的列表数据,需要先整理为两层的数据结构,这个我会优先考虑在Service里处理。基本原则就是,如何让数据更贴近业务模型。

关于Controller

因为Service是一个很薄的一层,所以大多的页面流程控制,都是在Controller里边完成的。对于Controller如何组织视图模型,我通常是定义一个总的viewModel,来挂载所有的视图模型,大体上就是:

# 定义视图的数据模型
$scope.viewModel =
  acttypes: null #活动类型
  acts: {} #可选的活动列表数据
  act: null #选择的活动
  levels: [] #可选的档次
  level: null #选择的档次
  rewards: [] #可选的赠品、赠品包

这个做法,有个最明显的好处,就是不容易踩到ng-model的scope的坑,关于angular的scope,请参考文章 Understanding Scopes

关于ngmodel

像上面提到的,使用ngmodel,nginit这种会操作model的指令,需要注意scope的范围,一不小心就可能操作到不同的scope。具体原因请参考上述文章。有几个办法可以处理:

  • 使用$parent,但是对于多层次的scope无效。
  • 采用非原型类型的obj,可以避免scope的意外。如上述的viewModel

大多数情况在我们都是用的单向绑定,如果涉及ng-model这种双向绑定的话,有另外一个文章推荐 Using NgModelController With Custom Directives, 因为这篇文章很好的描述了angular对双向绑定的处理流程。

另外,如果使用html5的验证或新类型,也会有些问题,例如你使用number类型,输入非number类型的内容的话,model获取到的值就无效了。

关于性能

可以参考一下这位UC童鞋的文章 angular性能优化心得

基本上该说的都在上面的了。这里补充一下:

  • bindonce可以减少一些无谓的watch,主要用于ng-repeat上,目前还没使用上,但试过是可用的。
  • 在watch方法里边不能操作dom,并及时unwatch。操作dom对性能影响很大。
  • 像$interval,$timeout虽然有个invokeApply这个参数,却是无效的,这个问题在1.3.0beta14才修复,如果不需要变更模型,建议用原生的。
  • filter因为经常计算,所以要尽量的快,选择合适的数据结构很重要,例如对大列表进行筛选不如key/value的查找快。曾经做过字典结构的调整。
  • 目前项目没有lazy loading的问题,这迟早是需要考虑的,网上已有一些方案,借助了route的resolve特性来实现的。

复杂页面流程

  • 是分单个route还是多个route,主要考虑是否需要独立入口,因为一个route对应一个url。这个主要参考URL的设计原则。
  • 跨页面参数传递,如果参数少建议用路径参数,如果共享数据多,可以使用服务来数据共享。

复杂页面的处理,我主要考虑使用状态机来处理。现在使用statechart.js

关于状态机的介绍,请参考Statecharts and Angular.js

开发工具支持

项目涉及多种工具、概念,需要掌握他们的应用场景:

  • 基础设施 node
  • 包管理 npm
  • 模块化,require,sea,amd,cmd
  • 资源依赖,bower
  • 打包自动化,grunt
  • angular相关 插件batarang
  • 语言改进,coffeescript

特别说明关于coffeescript的使用,上述关于viewModel的代码就是coffeescript,大体上可以减少1/3的代码行。

调试的支持

chrome的调试工具很强大,能够模拟手机操作,基本可以解决大多数问题。

不过真机、webview之类的调试仍然是个大问题。当然方案也是有的,如google官方的android+chrome方案、weinre的方案。 不过,我页面做的少,实在没什么经验可以发表的。

其他

自己对css并不熟悉,主要关注逻辑编写,个人认为和web打交道,还是得多理解http协议,浏览器原理。