macbook-pro安装adobe-flash-player后仍然不能看视频

想用刚买的mac pro看看优酷,结果提示需要安装flash player插件,然后我按照提示安装了adobe flash player,重启了safari,结果还是看不了视频,仍然提示未安装flash player。重启了机器后仍然是不行。

各种百度google搜索都没有找到办法。后来自己摸索,整好久才发现,原来默认插件安装是PPAPI Plug-in,它不是针对safari浏览器的,而是针对opera和chrome的。而NPAPI Plug-in这个才是真对safari、firefox、遨游的。要检查你安装的flash player插件是否支持safari可以这样看:系统偏好(System Preference)> Flash Player > Updates里面,效果如下:

Flash Play设置
Flash Play设置

如图所示,如果你的两个插件中NPAPI插件为安装,那么你就需要安装这个插件,安装方法如下:

首先几点上图中的install now,然后会自动打开一个adobe的网站,

安装flash player
安装flash player

 

不要点install now,因为那样会再次安装PPAPI插件,而这个插件对safari没用。应该点击左边的一个链接“Need Flash Player for a different computer”,点了以后看到的效果应该是这样的:

安装支持safari和遨游的flash player
安装支持safari和遨游的flash player

如上图的方式选择NPAPI,然后点击Downloading Now,后续按照提示一步步进行即可,然后重启浏览器就可以看视频了。

说实话,mac系统真心不如windows来得简单直接,不知道是转型mac不习惯,还是真的不好用~~

 

sun.misc.Unsafe

Java是个安全的编程语言,它可以防止程序员犯一些低级错误,往往这些错误都是基于内存管理的。但是在Java中仍然有办法故意的犯这些错误——使用Unsafe类。 这篇文章将快速的阐述sun.misc.Unsafe类的public API和少许有意思的用法。

Unsafe类的初始化

在使用前,我们都需要创建一个Unsafe类的实例对象,但不能通过Unsafe unsafe = new Unsafe()方式来实现, 因为Unsafe类的构造函数是private的。Unsafe类有一个getUnsafe()的静态方法,但你如果尝试调用Unsafe.getUnsafe()的话,可能会抛出SecurityException异常,因为只有信任的代码才能使用这个静态方法。

上面的代码说明了java是如何校验代码是否是可信任的。它仅校验了我们的代码是否被BootClassloader加载。 要让我们都代码被信任,可以在运行你的程序时使用bootclasspath参数来指定系统类和你要使用Unsafe的类路径。

但是这个方法太麻烦了。 Unsafe类里面有个属性就是一个它的实例对象,即theUnsafe,这个属性是private的。我们可以通过反射方式把这个属性取出来。

注意:忽略你的IDE的error提示。比如:eclipse会提示“Access restriction…”,但你要是运行代码,会发现一切正常。如果你觉得这个提示很烦,可以这样设置来忽略这个提示:

 Unsafe API

sun.misc.Unsafe有105个方法,可以分为多组重要的操作对象属性的方法,这里列举一些:

  • Info. 返回一些底层的内存信息.
    • addressSize
    • pageSize
  • Objects. 提供了操作对象和其属性的方法
    • allocateInstance
    • objectFieldOffset
  • Classes. 提供了操作类和静态属性的方法
    • staticFieldOffset
    • defineClass
    • defineAnonymousClass
    • ensureClassInitialized
  • Arrays. 数组操作
    • arrayBaseOffset
    • arrayIndexScale
  • Synchronization. 底层原始的同步管理
    • monitorEnter
    • tryMonitorEnter
    • monitorExit
    • compareAndSwapInt
    • putOrderedInt
  • Memory. 直接内存访问的方法
    • allocateMemory
    • copyMemory
    • freeMemory
    • getAddress
    • getInt
    • putInt

一些有意思的案例

避免实例化

allocateInstance方法在这几个场景下很有用:跳过对象的实例化阶段(通过构造函数)、忽略构造函数的安全检查(反射newInstance()时)、你需要某类的实例但该类没有public的构造函数。比如下面的类:

通过构造函数、反射方式创建实例与unsafe方式创建实例得到的效果不同:

由此想想你的单例真的能保证单例吗。

内存变更

这对C语言开发者很常用,他同时也是一种常见的跳过安全检查的方法,举个校验访问权限的例子:

客户端代码通过调用giveAccess()来校验访问权限,这种方式是很安全。不幸的是,不论谁调用这个方法,它都返回false。只有通过特殊手段来改变ACCESS_ALLOWED常量的值,从而使客户端获得访问权限。 下面是实例代码:

现在所有的客户端都可以获得无限制的访问权限。 实际上,我们可以通过反射的方式达到相同的效果,但有意思的是,使用unsafe可以让我们修改任意对象,甚至我们没有获取到对象的引用。 比如:在内存中有另外一个Guard对象在当前这个guard对象的下一个内存区块中,我们可以用下面的方式来修改它的ACCESS_ALLOWED属性:

注意,我们并没有这个新的Guard对象的引用,16是Guard对象在32位机器中占用的空间大小。我们可以通过马上要讲到的sizeOf方法来获取对象占用内存的大小。

sizeOf

使用obectFieldOffset方法可以使我们实现C语言的sizeOf功能。下面的实现就可以返回对象占用内存的大小(不包含子对象占用内存大小,暂且把这种内存大小称作“浅内存大小”):

算法是这样的:遍历所有当前类和所有父类的非静态属性,获得每个属性的偏移量,找到最大的偏移量,加上字节对齐所需的内存大小。可能不一定全,但思路是清晰的。 有个更简单的方式实现sizeOf,如果我们仅根据类的结构来读取对象占用的内存大小(该对象在JVM 1.7,32位机器上)分配的偏移量为12:

为了违反内存地址,normalize方法用来把int转成无符号的long:

很巧,这个方法返回的结果和前面的sizeof函数是相同的。 当然,要用更好更安全更精确的sizeof方法,最好使用java.lang.instrument包,但它需要在JVM上挂一个agent。

浅拷贝

既然已经实现了对象浅内存大小的计算功能,我们可以很容易的添加一点功能实现对象拷贝。标准的做法是让你的对象类实现Cloneable接口,或者在你的对象中自定义拷贝功能,但它都不具有通用性。 浅拷贝:

toAddress把对象转换成内存地址,而fromAddress则正好相反:

这种拷贝的方式可以用于任意类型的对象,对象的大小会自动的计算。值得注意的是,在拷贝以后,你需要把返回的对象强制转换为你需要的类型。

隐藏密码

一个更有意思的使用Unsafe直接访问内存的用法是:从内存中移除不想要的对象。 大部分获取用户密码的API都使用byte[]或者char[]类型,为什么是数组类型? 其实原因主要是为了安全,因为我们可以在我们不需要他们时把数组元素置为null。如果我们以String形式获取密码,他将以对象形式存储在内存中,这时把这个对象置为null相当于是使该对象解除引用。但这个对象仍然在内存中,除非它被GC回收掉。 下面的这个坑爹的例子用相同内存大小的String对象替换内存中的原始对象:

这样做,密码就安全了吗? 注意:这个方式仍不安全。因为安全的做法是需要通过反射的方式把String中的char数组中的每个元素置为”?”:

多重继承

要知道,在java中是不能多重继承的。 但是如果我们可以把任意类型转换为其他任意类型时,就另当别论了,比如下面的例子:

这个代码片段给String类添加了Integer作为其父类,所以我们可以做下面的类型转换,而不抛出运行时 异常:

这里为了欺骗编译器,需要先把Integer对象转换为Object类型。

动态类

我可以在运行时创建类,比如根据编译后得到.class文件来创建类。其机制是把.class的内容读取到byte数组中,并传给defineClass方法。

从文件中读取到byte数组中:

当你必须动态的创建类时,这个很管用,比如:一些代理或者基于已有的代码进行面向切面的编程。

抛异常

你是否不喜欢catch异常?没问题,这么干吧:

这个方法抛出一个需要被主动catch的异常,但是你的代码可以不去catch或者重新抛出异常,它就行抛出了一个运行时异常一样。(把IOException()当做运行时一样抛出,但调用方不需要catch)

快速的序列化

这个更有用些。 每个人知道java标准的Serializable功能性能很差,它还需要你的类必须要有一个无参的构造函数。 Externalizable 要好一点,但他需要为类的序列化过程进行定义。 热门高性能的库,例如kryo有依赖,这可能不适合低内存的场景。 但是完整的序列化和反序列过程可以很容易用unsafe来实现。

序列化:

  • 通过反射方式构建对象的schema,只需构架你一次就行了
  • 使用Unsafe的getLong、getInt、getObject等方法来获取实际的属性值
  • 添加一个类用来存储这个对象
  • 把对象写入到文件或者其他输出中

你也可以添加压缩功能来减少空间消耗。

反序列化:

  • 创建一个序列化类的实例。这时候allocateInstance方法就很有用了,因为它不需要任何构造函数
  • 构建schema,和序列化中的步骤一样
  • 从文件或者其他输入中读取所有的属性
  • 使用Unsafe的putLong、putInt、putObject方法等来填充前面创建的对象实例

实际上,有很多细节没有一一展开,但这个思路就是这样的。这个序列化反序列确实很快。顺便说一下,在kryo中有一些使用Unsafe的尝试:http://code.google.com/p/kryo/issues/detail?id=75

大数组

你可能知道Integer.MAX_VALUE是java数组的最大长度。通过直接的内存配分,我们可以创建出很大的java数组,其上限是堆空间的大小。

下面是SuperArray的实现:

可以这样使用SuperArray:

事实上,这种方式使用的是堆外内存空间(off heap memory),在java.nio包中有部分使用。

它并不是分配在java堆中,也不受java gc的管理,要小心使用Unsafe.freeMemory(),因为它不做任何边界检查,任何非法访问都会导致jvm crash。

它在数学计算中还是有用的,可以通过代码操作大数组数据。它对于实时系统开发的程序员来说很有意思,可以打破大数组gc上的限制。

并发

UnsafecompareAndSwap的并发简单来说就是,它说原子操作,可用于实现高并发的无锁数据结构。

例如,在多个线程间共享自增数据的问题:

首先,我们定义简单的接口定义:

然后,我们定义工作线程CounterClient,它使用到了Counter:

测试代码如下:

首先实现一个非同步的counter:

输出为:

运行很快,但是完全没有线程管理,所以结果说不准确的。

下一步,实现一个最简单的java同步counter:

输出为:

基础的同步功能还是起到了作用,但是耗时太长了。

下面让我们尝试使用ReentrantReadWriteLock:

出为:

仍然说正确的,耗时也好一些。如果使用原子操作会怎么样:

输出为:

AtomicCounter效果要更好一些。

最后,看看Unsafe原始的compareAndSwapLong方法是否真那么神奇:

输出为:

嗯,好像跟原子操作差不多,难道原子操作就是用这个这个方式?(答案:是)

实际上,这个例子很简单,但说明了Unsafe的强大之处。

如我所说,原始的CAS(CompareAndSwap)操作可以实现无锁的数据结构,其背后的机制是:

  • 有一定的状态
  • 拷贝一份
  • 修改它
  • 执行CAS
  • 如果执行CAS失败了,则这个过程

事实上,CAS比你想象的复杂得多,因为有很多问题,比如:ABA问题,指令重拍问题等。

如果你真得很感兴趣,可以看看这个牛x的ppt:无锁HashMap

注意:在counter变量上增加volatile,可以避免出现无限循环的风险。

彩蛋

Unsafe文档中park方法的注释有个我见过的最长的语句(。。。我也翻译不了了):

Block current thread, returning when a balancing unpark occurs, or a balancing unpark has already occurred, or the thread is interrupted, or, if not absolute and time is not zero, the given time nanoseconds have elapsed, or if absolute, the given deadline in milliseconds since Epoch has passed, or spuriously (i.e., returning for no “reason”). Note: This operation is in the Unsafe class only because unpark is, so it would be strange to place it elsewhere.

结论

虽然,Unsafe有一堆好用的用法,但永远不要使用它。

Reference

翻译自:http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

 

 

 

 

精品工具收集(二)

Clover:类似chrome标签的文件夹管理

Hbuilder:国内团队开发的,非常好用的html5、html5+、js、css的IDE,还能和android、ios的webview打通,开发非常便捷高效,强烈推荐

everything:极其快速的搜索软件,可以在几十秒内对整个磁盘的所有文件建索引

MobaXterm命令行终端:比xshell、破解的SecureCRT、putty都要好用的终端,功能介绍参考这里:http://jingxuan.io/?p=182。有免费版

xmind:思维导图,可以帮助你总结、规划、发散思维。工作时常用的软件,有免费版

Glances:一款跨平台的系统监控工具,在linux上使用最爽,相当于时top、df、free等常用命令的集合版,可以让你一眼看到系统的所有指标,觉得让你眼前一亮。

findjar:jar包查找

ZOC:免费的ssh terminal终端,可以很方便的使用复制粘贴功能,字体也不错

sourceTree:非常简单好用的git客户端,界面清晰直观

VS Code:非常轻便的编辑器IDE,相比sublime、notepad++、ultraEdit、editplus,功能更强大一些,更像IDE一点,好友的支持了git客户端、调试和丰富的扩展插件;相比eclipse、visual studio、xcode又更轻量级一些,打开速度非常快。至少可以取代sublime和notepad++这类的编辑器,而且特别适合前端同学,因为它就是用node写的,编写插件很方便。

eclipse全面提速

你是否经常在等待eclipse的一些操作完成?

eclipse loading
eclipse loading

如果你看到这里,说明答案是yes。如果你苦于eclipse中响应很慢的功能,并且想给eclipse提速让开发更舒服些,就请看看下面的内容。

注意:可能一般人都建议加大内存。如果可以,你可以买个cpu好点的机器。弄个SSD让你的文件操作更快。我们假设你买不起这些,你所能做的就是启动eclipse实例,所有ubuntu的设置都是基于eclipse 4.3.0版本,build id:I20121031-2000,当然其他平台的版本的设置都差不多。

Eclipse优化

插件

当我第一次找到强大的插件时,我非常高兴。我安装的越来越多后,eclipse就用起来不舒服了。所以你可以从众多的插件中禁用一些不常用的插件,禁用不代表删除,你仍然可以启用他们。

 

禁用不常用的eclipse启动插件
禁用不常用的eclipse启动插件

一些插件可能在尝试体验时用一用,但是后来可能在也不用了,这种情况可以把它删掉。

卸载eclipse插件
卸载eclipse插件

eclipse.ini

下面的优化都需要修改eclipse所在目录下的eclipse.ini文件。

  • 给eclipse执行jvm。它可以让你使用自己的jdk,而不是系统环境变量所指定的jdk
  • 使用最新的jdk来运行eclipse。使用最新的jdk要好很多。
  • 使用sun的jdk来运行ecipse。原因同上。
  • 配置jvm虚拟机的启动参数。你可以自定义虚拟机参数,如果你觉得他们更合适(虚拟机参数介绍)。我使用下面的启动参数来增加堆的大小至768Mb,perm区设置为256Mb(内存总大小为3Gb)

你可以添加-Xverify:none参数来跳过jvm对class文件的校验,以此提升eclipse的启动速度,但这是很不安全的。

你还可以通过测试不同的垃圾回收器策略、server参数来测试eclipse的性能差异。以下为实验过程中使用的部分参数:

可以在这里查看所有的eclipse运行时参数,选择适合你的参数。

禁用动画

动画很酷,但如果可以的话,我总是在所有的工具中禁用动画。所以classic主题是我最常用的主题。

设置eclipse主题
设置eclipse主题

禁用label decoration

label decoration是项目、文件、类层级上的小图标,它可以有益于显性化文件的状态。比如:文件是否已经提交到git。很多插件都提供了这个功能,但很少有用。你可以仅留下你想要的,其他的禁用。

设置label decoration
设置label decoration

自动补全

有时在性能较差的机器上,或者当你有很多类的时,自动补全功能性能就会很差。一个很小的优化是减少自动补全的proposal。我仅保留了Java Proposals和Template Proposals:

eclipse Content Assist,eclipse自动补全设置
eclipse Content Assist,eclipse自动补全设置

取消验证器

如果你对自己的技术很自信,就可以暂停所有的校验器。就算出现问题,你也可以靠自己的能力定位问题,节省了你的开发时间。

取消eclipse校验器
取消eclipse校验器

关闭不相关的工程

如果你仅开发部分eclipse中的工程,那你最好把其他功能关闭掉。他们不会出现在eclipse索引中。你可以在workspace中手动关闭不相关的工程(Close unrelated projects)。但我推荐使用Working Set,你可以添加多个工程到一个Working Set中,这样就可以快速的在Working Set件切换。

关闭编辑器中不用的tab

编辑中太多的tab会导致eclipse性能下降,可以这样控制下tab的个数:

勾选 Close editors automatically 并设置 Number of opened tabs 为10。

控制eclipse编辑器中tab的个数
控制eclipse编辑器中tab的个数

禁用拼写检查

你还是个程序员吗?我觉得没有任何理由需要拼写检查功能。取消这个功能吧:

禁用auto build

如果你在意什么时候build你的工程,可以这样设置:

快捷键

仁者见仁,智者见智。就算你用超快的IDE功能,但如果你要花10个动作才能实现一个操作,那你的开发过程就不算快。把你最常用的动作配置成快捷键,并记住他们,几周的使用后,你的开发效率将由显著提升。

为了逼着自己使用所有的快捷键,我直接把工具栏给禁用了。

参考链接

 

本文译自:http://mishadoff.com/blog/eclipse-speedup/

 

java中坑爹的getter、setter方法的潜规则

众所周知的java中都会给类的属性写getter和setter方法,以getter方法为例,通常是get+属性的大写首字母+属性剩下的字符组成。比如:有个属性名为name,那么其getter方法名就是getName,如果name是boolean类型,那么getter方法就是isName,当然现在的getter和setter方法都不会自己手写了,一般是通过eclipse或者Intellij idea生成。

javaBean规范

javabean规范文档:http://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/,里面的有以下两个章节降到了具体的命名规则:

  • 8.3.1 Simple properties
  • 8.3.2 Boolean properties
  • 8.8 Capitalization of inferred names.

这个文档里面说明了,从getter和setter方法名如何推倒出propertyName:

  • 一般情况,把除去get或者is(如果是boolean类型)后的部分首字母转成小写即可,比如:getFoo –> foo
  • 如果除去get和is后端的部分,首字母和第二个字母都是大写,不作转换即可,比如:getXPath –> XPath

意想不到的getter和setter生成规则

现在大家都使用ide来帮忙生成getter和setter,那么ide的规则就算是我们的标准了吧,是否把javabean标准反过来推理就行了呢?经测试实际上还有一些场景可能都想不到,比如:首字母不用大写;多个不同的属性(字母大小写不同)的getter方法是相同的。看下面的例子(用eclipse生成getter测试):

看到这里,你是否有种隐隐的蛋疼感~~

什么时候你需要关注getter和setter方法的生成规则?

  1. 想要序列化为json对象时,如果你使用gson的话,基本没啥问题,但要是你使用了低版本的fastjson,那么可能会中枪了
  2. 要自己写代码拼装出getter和setter方法名,以此来通过反射查找Method时

个人建议

  1. 属性的前两个都以小写开头
  2. boolean类型不要用is开头

Flex布局学习笔记

flex布局
flex布局

本文是根据阮老师的博客《Flex布局教程:语法篇》汇总的,便于以后自己查阅。另外阮老师还有一篇讲解很经典的flex布局实战博文《Flex布局教程:实例篇》。

flex容器有6个属性

  • flex-direction:决定主轴的方向(即项目的排列方向)。
    • row(默认值):主轴为水平方向,起点在左端。
    • row-reverse:主轴为水平方向,起点在右端。
    • column:主轴为垂直方向,起点在上沿。
    • column-reverse:主轴为垂直方向,起点在下沿。
  • flex-wrap:定义,如果一条轴线排不下,如何换行。
    • nowrap(默认):不换行。
    • wrap:换行,第一行在上方。
    • wrap-reverse:换行,第一行在下方。
  • flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content:定义了项目在主轴上的对齐方式。
    • flex-start(默认值):左对齐
    • flex-end:右对齐
    • center: 居中
    • space-between:两端对齐,项目之间的间隔都相等。
    • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
  • align-items:定义项目在交叉轴上如何对齐。
    • flex-start:交叉轴的起点对齐。
    • flex-end:交叉轴的终点对齐。
    • center:交叉轴的中点对齐。
    • baseline: 项目的第一行文字的基线对齐。
    • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
  • align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
    • flex-start:与交叉轴的起点对齐。
    • flex-end:与交叉轴的终点对齐。
    • center:与交叉轴的中点对齐。
    • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
    • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
    • stretch(默认值):轴线占满整个交叉轴。

flex中项目有6个属性(项目:item)

  • order:定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow:定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
  • flex-shrink:定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。
  • flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。如果值为100%,表示整个轴线长度空间
  • flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
  • align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

Reference

【译】用maven使java web应用运行在内嵌的Jetty或Tomcat容器中

apache maven
apache maven

开发java web应用时,能在“真实”的环境中有快速的反馈是非常实用的。本文将探寻如何使用maven使java web应用运行在内嵌的Jetty或Tomcat容器中。我将展示如何配置他们来开发Podcastpedia.org网站的工程podcastpedia。

环境准备

你需要有Maven,至少安装了Java 7。正常情况你应该可以自行部署和启动podcastpedia工程并看到效果

Jetty Maven Plugin

插件配置

注意:

  • jettyConfig指定Jetty的配置文件,下一部分将有该配置文件的具体内容
  • scanTargets指定了Jetty监控文件变化的文件夹
  • 指定连接数据库和发邮件的依赖包

Jetty.xml配置文件

Jetty配置文件中,你需要配置一下内容:

  • Server类(或者子类)以及全局的可选项
  • 一个线程池(最小、最大线程数)
  • Connectors连接器(端口号、超时时间、缓冲区大小、协议)
  • 处理器结构(handler structure)(默认的处理器或者一个contextHandlerCollections)
  • 扫描部署的webapps和容器上下文的部署管理器
  • 提供认证的登录服务
  • 请求日志

Apache Tomcat Maven插件

apache tomcat maven插件的配置

注意:

  • 指定tomcat的端口号
  • 指定contextFile,用来告诉tomcat配置文件在哪里
  • 指定连接数据库和发邮件的依赖包

context.xml

在context.xml中,定义了数据库和邮件资源

就这样,使用了spring框架的Java web应用可以运行轻量级的servlet容器,显然这种方式可以代替JavaEE服务器及其带来的所有成本。

参考

  1. Jetty Maven Plugin
  2. Apache Tomcat Maven Plugin

翻译:http://www.javacodegeeks.com/2015/06/run-java-web-apps-in-embedded-containers-with-maven-jetty-and-tomcat.html

 

 

【译】怎样开发一个高度可定制的产品

你可曾听过:“我真的很喜欢你的产品,除了一些小的细节点”?然后CIO(首席信息官)推出一个必须有的需求清单,这清单中的数百个需求点需要添加到你的了不起的产品中。你可曾听过或者甚至说过:“同志们,我们将签署一个高利润的合同,但是。。。”?然后客户期望的额外的功能变成了开发工程师的头痛点。

所以,尽管现在你的产品能满足顾客的需求,但怎样才能让你的产品远离顾客潜在的有风险的想法?怎样才能在已有无数加载项的情况下,维系一个产品技术设计的最高水平,来让其在特定的方式下起作用?这些为成熟的解决方案提供可靠且突出支持的基础性需求,将面临多少挑战?

在商业世界,产品定制是一种越来越可取的要求和一些共通的做法,这些做法已经演变为响应这一客户需求。下面你将找到一个大致的方法,如果你已经对它们比较了解了,你可以直接拖到到“方法五:扩展法”章节,来了解我们怎样以一种我们自认为有效的方式克服这些挑战。

方法一:在一个产品中包含所有定制功能(All in One)

最直接最容易想到的定制化的办法就是在一个产品核心中实现所有的需求,然后通过“属性开关”的技术来匹配不同客户的需求。

这种方法的主要优点是始终保持一个单一产品,对于不需要太多扩展就能覆盖大多数业务需求的场景是比较管用的。

但这种方法其实隐藏了一个假设,即“没有太多了定制需求”。通常,产品的开发都以这个信念开发,但在几伦迭代交付之后,你会看到客户真正的定制需求量。陷入一个左右为难的困境并不罕见:拒绝定制或者潜在地丢失客户,或者把代码变成了一个“垃圾桶”,因为为一个客户定制太多的个性化需求对其他主要客户都是无用的。

你会选择哪种?很显然,从一个岩石和一个坚硬的东西中选择一个都不会成功。

总结:在一个产品中包含所有定制功能,只在定制需求很少或者有限时才算是合理的选择。否则,你将在可维护性和客户满意度之间做出选择。在此引用Jerry Garcia的话:“从两个不太坏的恶魔中选择一个,不还是恶魔吗”。

方法二:分支开发(Branching)

如果重要的定制是交付中必须要有的部分,那么All in One的技术方案就行不通了。有另外的更直接了当的方法——分支开发。你可以很容易的创建一个独立的产品分支,随意修改。

与All in One相比,分支开发最大的优势是没有定制范围的限制,通过分支的方式来分隔不同客户的特定需求,避免了在同一套代码中混杂所有的功能。

但是,这个优势的另一面可能让产品演化走向死路。显而易见的,产品分支是主要的开发领域:大部分的bugfix、改进、新功能首先添加到产品中,那么,要保持定制的分支和主产品保持同步,就需要频繁的合并代码,合并代码时要是和主干产品没有冲突,那合并代码是很容易的操作;要是有冲突,合并操作将非常耗时,且不可避免的需要回归测试。

如果定制分支很少时,这种分支开发的方式是可行的,但是当交付的产品数量增加以后,面临痛苦合并的可能性将迫在眉睫。

总结:分支开发无疑是非常灵活和直接的方法——产品的任意部分都可以修改。但是在交付的后期将很费力,随着时间的推移,将变得更困难,也不太可能有太多的定制分支。

方法三:实体-属性-值模型(EAV)

实体-属性-值 模型(Entity-Attribute-Value model又名:Object-Attribute-Value model,垂直数据库模型和开放schema)是一种熟知的广泛使用的数据模型。EAV可以支持动态的实体属性,它适合用于平行的标准关系模型。

从产品角度来看EAV的优势是:你可以交付你的产品“as is”,然后通过在运行时添加需要的属性来调整数据模型,而不需要修改源码。

还是一样的有点不足:

  • 适用性有限:该模型仅适用于这样的场景,基于已经编写好的逻辑,使用添加属性的方式,使其自动的嵌入到界面中
  • 额外的DB服务器负担:垂直数据库的设计通常会变成企业级应用的瓶颈,因为它通常操作大量的与应用相关的实体和属性。
  • 最后,企业级系统不可能没有复杂的报表引擎。EAV模型将因为它的“垂直”数据库结构带来许多潜在的并发症。

总结:EAV模型在某些场景下很有有用,比如:提供一种附加信息数据即可获得灵活性的需求,这种附加的信息数据并不显示地在业务逻辑中使用。换句话说,EAV适度是好的,除了标准的关系模型和插件架构。

方法四:插件架构(The Plugin Architecture)

插件架构是最常用最强大的一种方式之一:功能逻辑分别放在名为插件的东西里面。要想覆盖现有的盒子外的行为,并运行插件,就必须在产品的源码中定义“定制点”(又名扩展点)。一个“定制点”是某些源码中的片段,应用会扫描所有的插件,来检查是否有插件重写了指定的实现,如果实现了就执行该插件;另一种插件架构方式是外部脚本,功能实现被实现和存储在外部的脚本中,脚本的调用被预定义的“定制点”控制。

使用插件方式可以保持产品代码的纯净,可以不做修改的交付核心产品,而把定制的行为放在插件或者脚本中。另一个优点是可以很好的管理更新的过程。产品和插件的完全分离使他们可以相互独立的升级。

当然,不足之处在于:原则上,你不可能知道客户在未来提出的需求。因此,只可能猜测“定制点”要嵌在哪里。当然,你可以把“定制点”分散在所有可能的地方以缓解“万一需要的场景”,但这将导致代码可读性很差,代码很难调试,增添了复杂性。

总结:插件模式确实在“定制点”可预测的场景下工作,但要注意的是:在“定制点”间的定制是不可能的。

方法五:扩展法(The Extensions Approach)

在我们的企业级软件开发平台CUBA中,我们已经实现了一个独特的方法。正如我们之前的文章,CUBA是非常实用的,在一套开发驱动的演化中创造出来的活的有机体。所以,基于我们丰富的已有产品的经验,我们提出了两个终极需求:

  • 为客户定制的代码必须和核心产品代码完全分离
  • 每个产品的代码部分必须可以被修改

我们设法满足这些需求,甚至从这种扩展机制中获得了更多好处。

CUBA的扩展

一个扩展就是一个独立的CUBA工程,它把底层工程当作依赖库来使用,继承了所有底层工程的特性(比如:你的核心产品),这很显然允许开发者实现新功能,而不影响父工程,但得益于开放继承模式和专门的CUBA设施,你也可以重写任何父工程的部分功能。总之,一个扩展就是你可以实现成千上万个文章开头提到的“小细节点”的地方。

事实上,每个CUBA工程是一个CUBA平台自身的扩展,所以它能重写任意的平台功能。我们自己采用这种方式来区分出核心平台之外的功能。所以如果在你的工程中需要他们,你只需要把他们当作父工程添加到你的工程中即可,类似多重继承!

用相同的方法,你可以构建一个可继承的自定义模型。这可能听起来比较复杂,但它很管用。让我给个真实的例子:SherlockHaulmont’s的完备的出租车管理方案,支持每个运行出租车业务的各个方面,从预定和分派到应用和结算。这个方案涵盖了需要不同的客户业务,并且这些业务中很多是和本地化相关的。比如:所有英国的出租车公司有相同的法规,但有的法规在美国却行不通,反之亦然。很显然,我们不想在核心产品中实现所有这些法规,因为:

  • 这是一个“操作层面的具体的”功能
  • 在不同的国家,本地化的法规可能对出租车队管理的影响完全不同
  • 有的客户根本不需要法规

所以,我们组织一下多层扩展:

  1. 核心产品包含通用出租车业务功能
  2. 第一层定制实现区域特殊化
  3. 第二层定制覆盖客户的需求清单(如果有的话!)
cuba-platform_product-schema
CUBA的扩展架构图

非常干净。

如你所见,使用扩展的方式,保持了代码的干净和可维护性,你既不需要以分支方式开发你的产品,也不需要迁移所有的需求到你的核心产品中。看起来真的很好,所以让我们看看它在代码实践中如何工作!

给已有的实体添加新的属性

让我们假设我们有一个User实体定义,它由两个属性组成:login和password:

现在我们的客户有个额外的需求,要添加一个“home address”属性到User实体中,要做到这个,我们在扩展中继承User实体:

你可能已经发现,所有的注解,除了@Extend,都是JPA的注解。@Extend注解是CUBA引擎的一部分,它会用ExtUser全局替换User实体,甚至跨产品功能。

使用@Extend注解,我们必须让平台:

  1. 总是创建继承结构中叶子类型的实体
  2. 在执行前,转换所有JPQL的查询语句,以便我们总是返回叶子类型的集合
  3. 在关联的实体中总是使用叶子类型

换句话说,如果声明了一个继承的实体,父实体就在整个产品和扩展中被遗弃,并且被继承的实体全局替换。

界面定制

我们已经通过添加address属性来继承了User实体,现在我们想要让这个继承效果作用到用户界面中。首先,让我们看看原始界面是什么样子:

可以看到,一个CUBA的界面描述是以XML的方式呈现。显然,我们可以简单的在扩展中重新声明整个界面描述,但是这意味着复制-粘贴大部分的描述代码,它导致如果以后产品界面有点变化,我们就需要手动地把这些变化点复制到扩展的产品界面中。为了避免这个,CUBA引入了界面继承机制,你所需要做的仅是描述不同的界面部分:

使用extends属性来定义祖先界面描述,然后只需要描述扩展需求中不同的部分即可。

走你,让我们最后看看界面效果

cuba-platform_admin
CUBA扩展后的界面效果

业务逻辑修改

为了修改CUBA平台的业务逻辑,CUBA平台使用了Spring框架作为其平台基础的核心部分。

比如:你有一个中间件来执行价格计算:

要重写价格计算的实现,我们仅需要两个步骤:

第一步:继承产品类,并重写相应的方法:

第二部:在spring中配置这个产品bean:

现在PriceCalculator注入将总是返回继承的类的实例,这样,继承的实现将作用到整个产品中。

在扩展中升级基础产品的版本

随着核心产品的演进和新版本的发布,你终究会决定把你的扩展升级到最新的产品版本。这个过程非常简单:

  1. 在扩展中指定底层产品的新版本
  2. 重新构建扩展:
    1. 如果扩展扩展是构建在稳定的产品API基础上,那么它应该可以跑起来了
    2. 如果产品API有些重要的修改发生,且这些修改与定制的实现有冲突,那就要在扩展中支持新的API

大多数时候,产品API不会再更新中有太多的变更,尤其是小版本的发布。但是就算API的大改发生了,一个产品通常也会在未来的几个版本中保持向下兼容,并在老的实现中标记“deprecated”,允许所有的扩展迁移到新的API上。

总结

简单的汇总一下,我比较喜欢用对比分析的表格形式来阐述:

/ All in One Branching EAV Plugins CUBA Extensions
Architecture independent + +
Dynamic customization + +/-
Business logic customization + + +/- +
Data model customization + + + +/- +
UI customization + + +/- +/- +
Code quality and readability +/- +/- +/- +
Doesn’t affect performance + + + +
Risk of software regression High High Low Medium Medium
The complexity of long term support Extremal Extremal Low Medium Medium
Scalability Low Low High High High

 

如你所见,扩展方式是很强大的,但它有一条不足,就是在运行中动态微调的能力不够。为了克服这个问题,CUBA也提供了全面的EAV模型和插件/脚本方式的支持。

翻译自:http://www.javacodegeeks.com/2015/07/how-to-develop-a-highly-customizable-product.html

javascript的apply()和call()

作为业务的前端开发,有时候看一些代码的时候碰到apply和call方法会觉得很纳闷,好好的一个函数执行,非要写成这样,看了网上的一些事例代码还是没真正理解其不得不用的场景。直到最近自己碰到一个不得不用apply方法的场景,才真正理解了apply和call的作用。

直接看代码:

在这段代码里面,我达到的目的是:通过传递worker对象的make方法的方式来让boss选择生产出那种产品,boss的chooseWorker方法的参数是make方法。我本来期望传给chooseWorker方法的参数如果是foodWorker的make方法就生产food,如果是carWorker的make方法就生产carWorker。期望的结果是这样:

但是结果并不是这样,而是:

那为什么我传递的是指定对象的方法,里面的this.product确实“house”呢?

原因是JavaScript对象的函数本身并不包含对象的信息的信息,如果直接调用makeFunction,其调用方默认是window对象,也就是说this==window,所以this.product就是“house”了。要想达到预想的效果,就得用到apply方法了。比如这样:

其输出结果就打到预期了,对于一些设计模式和继承使用的时候,apply和call会常被使用到。主要是解决this对象的指向问题,当然也可以通过使用apply和call来替换方法的调用者。

apply和call的作用基本相同,唯一的区别是apply有两个参数,而call只有一个参数。

  • apply的第一个参数是函数的调用对象,第二个参数是函数的参数列表(参数数组),上面的代码就是个例子。当然如果函数没有参数,可以给apply方法传空数组([]);
  • call的参数只有一个,就是函数的调用对象。

apply包含了call的功能,所以一般用apply就行了。这是我目前的理解,抛砖引玉。

 

【译】使用 Lombok 减少你的样板代码

Lomok是一个用来简化java代码开发的java库。这篇文章主要解决getters/setters、重写equals、hashCode、toString以及构造函数的编写问题。如果用普通的java编写方式,一定很繁琐。当你看到Lombok项目时,它会让你觉得一身轻松。

使用 Lombok 减少你的样板代码

1. 不用再写getters\setters方法了

比如下面的pojo类,用普通的方式写会是这样:

当写这些getters/setters方法时,我开始慢慢讨厌java了。

使用Lomok的方式来写会是这样:

是不是很容易?

除了这些,Lombok API还提供了其他很棒的特性:

  1. 不用重写toString方法:可以在pojo类上使用@ToString注解,lombok会把重写后的toString方法放到类中
  2. 不用重写equals和hashCode方法:在pojo类上使用@EqualsAndHashCode注解,在编译的时候很容易生成equal射hashCode方法
  3. 用注解来生成构造函数:
    • @NoArgsConstructor:用来创建无参的构造函数
    • @RequiredArgsConstructor:用来创建构造函数,其参数由所有未初始化的final字段和标记了@NonNull注解的字段
    • @AllArgsConstructor:用所有的字段作为参数,来创建构造函数

这些都是很常用的特性,其他特性可以在这里找到:project lombok site

使用了Lombok和未使用Lombok对比的例子

比如我们需要一个类,这个类需要包含equals、hashCode、toString、getters\setters、构造函数。

如果用Lombok来写:

这样写后Lombok会把这段代码转成常规的java pojo类代码

如果不用Lombok来写:

此时可能很多java开发会争辩了:“IDE可以帮我们生成这些繁琐的代码,速度比写注解来得更快把!”

如果你这样想,那么你可能遗漏了一点:人们不喜欢java,因为控制和编写所有这些代码其实是一种坏味道。相同的代码在Ruby、Groovy、Perl或者其他脚本语言中都很简单,为啥在java中如此冗长呢。简单才是好,Lombok正是为了java的简单而存在。(java开发应该把更多的时间放在其他的业务逻辑中,不应该在这些地方浪费时间)。

详细内容请看Lombok的官网:https://projectlombok.org/,里面有一段视频介绍,能很清晰介绍如何在eclipse中使用Lombok来简化你的开发。

Lombok的在eclipse中的安装和使用

  • 官网上有视频:https://projectlombok.org/。
  • 安装和使用的文档:http://jnb.ociweb.com/jnb/jnbJan2010.html
  • Lombok其实是通过agent机制在eclipse的启动参数中添加了一个agent(-javaagent:lombok.jar)

Lombok在maven中的使用

在maven中依赖上Lombok:https://projectlombok.org/mavenrepo/index.html

如果需要在maven中使用其打包,请看这里:http://awhitford.github.io/lombok.maven/lombok-maven-plugin/usage.html

Reference

  • 原文:http://keaplogik.blogspot.com/2013/04/java-developers-need-to-be-using-lombok.html
  • http://www.oschina.net/translate/lombok-reduces-your-boilerplate-code?cmp
  • http://www.ibm.com/developerworks/cn/java/j-lombok/