直接使用Map作为属性时的陷井(在UMP项目中)-晋元

Map(或者说HashMap)可以说是Java中最常用的一个类,事实上,这个类的使用却存在很多陷井,比如下面的写法:
public class Demo {
private Map attributes=new HashMap();
/**
* @return the attributes
*/
public Map getAttributes() {
return attributes;
}
/**
* @param attributes the attributes to set
*/
public void setAttributes(Map attributes) {
this.attributes = attributes;
}

}

上面是最常见的写法,它最大的问题在于,直接将Map暴露出去,而且允许外面的代码直接修改。比如说:
Demo demo=new Demo();
demo.setAttributes(null);
这时候,demo.getAttributes()就会返回null,于是我们很多人的代码,就必须加上null判断。
这样的代码,犯了几个错误:
1、违反了如Effecitve Java中的第12条:使类和成员的可访问能力最小化。这样,通过Map map=demo.getAttributes(); map.put(…)就可以随意修改Map中的数据,完全不可控。
2、不必要地强迫很多人写上各种null判断的代码,而且因为程序执行路径的不同,可能引发NPE错误。
建议的写法如下:
private final Map<String, String> attributes = new HashMap<String, String>();
public String getAttribute(String key) {
return attributes.get(key);
}
public void setAttribute(String key, String value) {
this.attributes.put(key, value);
}
public void clearAttributes() {
this.attributes.clear();
}
public void addAllAttributes(Map<String, String> attributes) {
if(null!=attributes){
this.attributes.putAll(attributes);
}
}
public Map<String, String> getUnmodifiablsAttributes() {
return Collections.unmodifiableMap(this.attributes);

}

通过上述写法,对外封闭了内部的Map信息,同时明确地告诉外部,只能通过哪些方法来修改数据。

不罕的代码分享1

 

之前一个项目部署后访问抛出异常ConcurrentModifiedException ,看代码后发现有段逻辑中用到了一个处理集合的地方,逐个删除不符合条件的DO。

使用的是list.remove();

这个方法用在list的迭代中会抛出ConcurrentModificationException异常,随换用Iterator自带的iterator.remove()方法后安全执行.

   Iterator<ShopStreetDO> iterator = streetList.iterator();

iterator.remove();

 

 

虽然这个貌似很小的点而已,但往往在系统中却是个隐藏的大bug.

 

原因:

  • 某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。通常在这些情况下,迭代的结果是不明确的。如果检测到这种行为,一些迭代器实现(包括 JRE 提供的所有通用 collection 实现)可能选择抛出此异常。执行该操作的迭代器称为快速失败迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。
  • 注意,此异常不会始终指出对象已经由不同 线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常。例如,如果线程使用快速失败迭代器在 collection 迭代时直接修改该 collection,则迭代器将抛出此异常。
  • 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败操作会尽最大努力抛出 ConcurrentModificationException。因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。
  • 当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时,即使是在单线程下运行, java.util.ConcurrentModificationException 异常也将被抛出。
  • Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
  • 所以 Iterator 在工作的时候也是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性

有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs

 

 

针对项目中遇到的那种用法的性能差别:

remove方法由于牵涉到集合里单向列表索引的移动,所以性能很低。如若集合大小超过100个,迭代remove次数频繁的话就要考虑避开这种方式了。可以考虑用add,新建一个list,把符合条件的对象add进来。这样性能提高很多。随着remove频率的逐渐升高,两者的性能差别会是数量级的。集合大小100个之内,移动次数不频繁的话,add方式和remove方式性能差不多。

 

 

 

 

 

大家可以做个试验:

 

这段代码是个反例,不要学习。

      String str1 = new String(“test”);
String str2 = new String(“test”);
String str3 = new String(“test”);
String str4 = new String(“test”);
String str5 = new String(“test”);
List list = new ArrayList();
list.add(str1);
list.add(str2);
list.add(str3);
list.add(str4);
list.add(str5);
System.out.println(“list.size()=” + list.size());
for (int i = 0; i < list.size(); i++) {
list.remove(i);
System.out.println(“after list.size()=” + list.size());
}

System.out.println(“last list.size()=” + list.size());

 

解决的方法:

方法1:倒过来遍历list

  for (int i = list.size()-1; i > =0; i–) {

list.remove(i);

}

 

 

方法2:每移除一个元素以后再把“指针”i移回来

for (int i = 0; i < list.size(); i++) {

list.remove(i);

i=i-1;

}

 

 

方法3:使用iterator.remove()方法

for (Iterator it = list.iterator(); it.hasNext();) {

String str = (String)it.next();

it.remove();

}

 

 

杭州西溪之地玩

梅竹山庄→西溪梅墅→西溪水阁→秋雪庵
最美妙的当然还是坐在摇橹船上赏景,这里的船老大会自豪地告诉你:葛优坐的是哪艘船,舒淇和方中信坐的又是哪艘船。由着船工的性子从内河道进发,周家码头——梅竹山庄——西溪梅墅——西溪水阁——秋雪庵——周家码头,游玩西溪,不可太贪心,想一次游个遍,需水陆结合,慢慢晃荡。
掩映在茂林深处的西溪水阁是文人雅士们隐居、读书的地方,由两组建筑组成,东为“拥书楼”,是文人居所;西为“蓝溪书屋”,是藏书读书之地。
位于中心水域的秋雪庵是西溪最美的地方,四面环水,只有靠划桨小船才能进入。“西溪芦”曾是清代西湖18景之一,而秋雪庵就是西溪观芦的最佳处。坐船靠近秋雪庵时,一群群野鸭会神气地从芦苇丛中钻出,横穿过“河路”,消失在另一片芦苇荡中。
顺便说一句,湿地公园附近有一个免费的观赏区,那里有座小塔,登上塔顶便能一览湿地风光,别有一番滋味。

 

如果选择步行,可以沿这条线行走:周家村(园区主入口)——梅竹山庄——泊庵草堂——烟水渔庄——小姑桥——深潭口——深潭港西侧游步道——虾龙滩保护区——西溪水阁——西溪梅墅,全程约8公里,需时3个半小时以上。

如何让UED同学更喜欢的跟你合作

如何让一个UED前端同学跟你快乐的合作,方式有很多种,我先分享其中一种:其实后端同学跟他们一起完成功能需求的时候,尤其是时间非常短的紧急需求,你只要多花点时间帮他们理一下需求和写一些文档给他们。 比如拿我工作中的列子:

第一种场景,   打开活动投票页面的时候:

实现方式:前端ued同学采用JSONP的方式发起请求后端300条数据,然后在每个分类中展示对应的100条数据。

请求连接:隐私

服务器返回数据JSON格式:darenCallback(约定好的函数名)({

“meifu”:[ {"type":"meifu","nick":"可爱的小娃娃",...},{"type":"meifu","nick":"齐B小短裙",...},.... ],

“meiru”:[ {"type":"meiru","nick":"可爱的小娃娃2",...},{"type":"meiru","nick":"齐B小短裙2",...},.... ] ,

“meiti”:[  {"type":"meiti","nick":"可爱的小娃娃3",...},{"type":"meiti","nick":"齐B小短裙3",...},.... ]

})

 

PS:

1,后端直接返回数据给前端时,把300条数据先分类好。比如json格式”美肤”:100条数据,”美汝”:100条数据,”美体”:100条数据。方便前端分类tab切换数据。

 

2,在返回的对象中我用省略号省去了一些熟悉,具体的字段属性和字段含义如下:

type:达人所属分类

nick:达人报名的昵称(这个不是达人的旺旺nick,防止达人被骚扰)

userId:达人的旺旺数字id(此属性在页面上不展示,前端同学会放在对应达人的隐藏域中,后续的观察投票和关注达人组件需要用到)

source:达人来源 (如新浪推荐)

num: 达人编号

peoPicurl: 达人图片

expertScore:评委对达人的评分数

vote:达人的得票数

followers:达人的粉丝数

itemPicurl:达人推荐的商品大图

itemTitle:达人推荐的商品名字

itemReason:达人推荐的商品理由

spulist:达人推荐的该商品对应的SPU list

 

 

第二种场景,  进入投票页面的观众选择给某达人投票的时候:

实现方式:前端ued同学采用JSONP的方式发起请求美容后端服务器

请求连接:隐私

服务器返回数据JSON格式:

1,正常返回:voteCallback(约定好的函数名)){

“message”:”投票成功”,”vote”,11111}其中vote为当前达人的最新得票数

或者voteCallback{

“message”:”抱歉!您今天已经投过票了,请明天再来吧!”}

或者voteCallback{

“message”:”抱歉!经由您的计算机今天发起的投票已经超过上限,请明天再来吧!”}(这个后台技术实现还未定)

2,异常返回:voteCallback{

“message”:”抱歉!本次操作由于系统繁忙或者未知错误,没有成功 , 请稍后重试!”}

 

PS:

请求连接中的?号后面参数需要UED同学从页面中带过来,其中viewUserId为进入该投票页面的观众淘宝账号(即旺旺账号)数字id

(前端同学可以从cookie中获取);userId为当前达人的淘宝账号数字id(前端同学可以从当前达人的隐藏域中获取,这个在第一种场景中有特意提到)

 

 

 

第三种场景,  观众关注达人的时候:

 

实现方式:前端ued同学采用JSONP的方式发起请求美容后端服务器获取达人的最新粉丝数

请求连接:隐私

服务器返回数据JSON格式:

1,正常返回:guanzhuCallback(约定好的函数名)){

“followers”,11111}其中followers为当前达人的最新得票数

或者guanzhuCallback(约定好的函数名)){

“followers”,”保密”}由于达人的安全设置等因素无法取到粉丝数,这个运营同学们会告诉达人们都放开

2,异常返回:guanzhuCallback{

“message”:”抱歉!本次操作由于系统繁忙或者未知错误,没有成功 , 请稍后重试!”}

 

 

PS:

1,第三种场景其实有2个异步交互,首先是用SNS关注组件请求SNS后端服务器(这个组件作者已经封装好逻辑)返回一些数据;

其次是在异步请求美容后端服务器获得达人粉丝数来动态更新

 

2,第三种关注达人的异步交互场景主要是保证加关注达人这个SNS组件功能OK,如果在其次请求美容后端服务器隐私

,假设服务器异常返回,那么不要在页面上提示这个异常信息来迷糊观众,造成用户体验很差。优先保证正常关注功能变成已关注状态(至于在异常特殊情况粉丝数下不能加1相比不是很重要)

埋点后续跟踪

链接

埋点方案http://wiki.ued.taobao.net/doku.php?id=tms:spm%E5%9F%8B%E7%82%B9%E6%96%B9%E6%A1%88

埋点功能和指标介绍http://dw.taobao.ali.com/klc/baike/baikeInfo.htm?id=30&ticket=53fde5ba-78b6-413f-a883-4d4327eb7837

埋点技术接口人

对埋点原理的理解:

1线上Apache新增了一个模块会生产埋点的JS(atp.js)(daily环境下需通知联系系统同学告知SCM配置和修改taobaoBeacon.cfg,要不然不会加载),当浏览器渲染完后会去发请求这个JS,这个JS处理逻辑把标签中的meta子标签的a(表是站点).b(表示站点下的某页面)值推送到埋点服务器来统计当前a站点下的b页面PV

2当浏览器加载完整个DOM结构后,该apt.js把<body>体里面的容器根节点(div,p等)的能发起http请求的子节点(a,img等)的c(模块).d(模块中的某位置)的值推送到埋点服务器来采集点击和引导效果。

对这个后续的安排:上述apt.js的原理目前还不适合我们很多系统后端java对应的VM文件。我们写的大部分screen 主题页面VM一般都布局在layout下的default.vm中,而meta元信息只能写在default.vm中head中,那么原理1中介绍的就不能写a.b属性值,有一种解决方式就是为每一个主题VM页面都写一个对应的
default.vm(这个不太现实,要改动已有页面的分类同时要担心其他系统对这个页面的引用改动,而且
以后开发新页面都要建一个新的default.vm,这个约束太大放弃。)

把VM的这个情况跟负责埋点的同学一起当面沟通了一下和讨论技术后,目前达成的意见是:

1,他们那边第一步尽快修改atp.js,兼容VM的情况:在meta中我们只写a(站点)的值,剩下的
b.c.d的值写在body里的容器中,这样对埋点原理理解中的第二个同样支持,同时去掉原来spm.js的引用

2,由于第一步的解决方案不能满足window onload后当前页面PV的统计,打算第二步调整
atp.js把当前页面的http地址(不包括参数)推送到埋点服务器,采集a+URL的方式来统计当前站点下的页面PV统计.(这种方案唯一担心的事,后端系统的域名和页面名字会不要变化太频繁)。

3,了解系统已有旧埋点方式,等他们至少第一步完成后,我们开始修改已有系统埋点旧方式而采用他们这个统一埋点方式。