[翻译]CSS 的 background 属性

原文The Background Properties
(译文发布在伯乐在线,感谢刘唱校稿)

正如我之前所说,文档树中的元素都是一个方盒。每个盒都有背景层,它是透明的、有颜色的或一张图片。背景层由 8 个 CSS 属性(和 1 个简写属性)控制

background-color

background-color 属性设置元素背景颜色。它的值可以是一个合法的颜色值或 transparent 关键字。

.left { background-color: #ffdb3a; }
.middle { background-color: #67b3dd; }
.right { background-color: transparent; }

在由 background-clip 关键字指定的盒模型区域内填充背景颜色。如果也设置了背景图片,颜色层会在它们后面。图片层可以设置多个,每个元素只拥有一个颜色层。

background-image

background-image 属性为元素定义一个(或多个)背景图片。它的值是用 url() 符号定义的图片 url。none 也是允许的,它会被当做空的一层。

.left { background-image: url('ire.png'); }
.right { background-image: none; }

我们也可以指定多个背景图片,用逗号隔开。沿着 z 轴从前向后依次绘制每个图片。

.middle { 
  background-image: url('khaled.png'), url('ire.png');

  /* Other styles */
  background-repeat: no-repeat; 
  background-size: 100px;
}

background-repeat

background-size 指定大小和 background-position 指定位置后,background-repeat 属性控制如何平铺背景图片。

属性值可以是下面关键字之一 repeat-xrepeat-yrepeatspaceroundno-repeat。除了前两个(repeat-xrepeat-y)之外,其他关键字可以只写一次来同时定义 x 轴和 y 轴,或分开定义两个维度。

.top-outer-left { background-repeat: repeat-x; }
.top-inner-left { background-repeat: repeat-y; }
.top-inner-right { background-repeat: repeat; }
.top-outer-right { background-repeat: space; }

.bottom-outer-left { background-repeat: round; }
.bottom-inner-left { background-repeat: no-repeat; }
.bottom-inner-right { background-repeat: space repeat; }
.bottom-outer-right { background-repeat: round space; }

background-size

background-size 属性定义背景图片的大小。它的值可以是一个关键字、一个长度或一个百分比。

属性可用的关键字是 containcovercontain 会按比例将图片放大直到宽高完全适应区域。cover 会将其调整至能够完全覆盖该区域的最小尺寸。

.left { 
  background-size: contain;
  background-image: url('ire.png'); 
  background-repeat: no-repeat;
}
.right { background-size: cover; /* Other styles same as .left */ }

对于长度值和百分比,我们可以用来定义背景图片的宽高。百分比值通过元素的尺寸来计算。

.left { background-size: 50px; /* Other styles same as .left */ }
.right { background-size: 50% 80%; /* Other styles same as .left */ }

background-attachment

background-attachment 属性控制背景图片在可视区和元素中如何滚动。它有三个可能的值。

fixed 意思是背景图片相对于可视区固定,即使用户滚动可视区时也不移动。local 意思是背景在元素中位置固定。如果元素有滚动机制,背景图片会相对于顶端定位,当用户滚动元素时,背景图片会离开视野。最后,scroll 意思是背景图片固定,不会随着元素内容滚动。

.left { 
  background-attachment: fixed;
  background-size: 50%;
  background-image: url('ire.png'); 
  background-repeat: no-repeat;
  overflow: scroll;
}
.middle { background-attachment: local; /* Other styles same as .left */ }
.right { background-attachment: scroll; /* Other styles same as .left */ }

background-position

这个属性,结合 background-origin 属性,定义了背景图片起始位置。它的值可以是一个关键字、一个长度或一个百分比,我们可以依次定义 x 轴和 y 轴的位置。

可用的关键字有toprightbottomleftcenter。我们可以任意组合使用,如果只指定了一个关键字,另一个默认是 center

.top-left { 
  background-position: top;
  background-size: 50%;
  background-image: url('ire.png'); 
  background-repeat: no-repeat;
}
.top-middle { background-position: right;  /* Other styles same as .top-left */ }
.top-right { background-position: bottom;  /* Other styles same as .top-left */ }
.bottom-left { background-position: left;  /* Other styles same as .top-left */ }
.bottom-right { background-position: center;  /* Other styles same as .top-left */ }

对于长度值和百分比值,我们也可以依次定义 x 轴和 y 轴的位置。百分比值相对于容器元素。

.left { background-position: 20px 70px; /* Others same as .top-left */ }
.right { background-position: 50%; /* Others same as .top-left */ }

background-origin

background-origin 属性定义背景图片根据盒模型的哪个区域来定位。

可用的值有 border-box基于边框(Border)区域定位图片,padding-box 基于填充(Padding)区域,content-box 基于内容(Content)区域。

.left { 
  background-origin: border-box;
  background-size: 50%;
  background-image: url('ire.png'); 
  background-repeat: no-repeat;
  background-position: top left; 
  border: 10px dotted black; 
  padding: 20px;
}
.middle { background-origin: padding-box;  /* Other styles same as .left*/ }
.right { background-origin: content-box;  /* Other styles same as .left*/ }

background-clip

background-clip 属性决定背景绘制区域,也就是背景可以被绘制的区域。像 background-origin 属性一样,它也基于盒模型。

.left{ 
  background-clip: border-box;
  background-size: 50%;
  background-color: #ffdb3a; 
  background-repeat: no-repeat;
  background-position: top left; 
  border: 10px dotted black; 
  padding: 20px;
}
.middle { background-clip: padding-box;  /* Other styles same as .left*/ }
.right { background-clip: content-box;  /* Other styles same as .left*/ }

background

最后,background 属性是其他背景相关属性的简写。子属性的顺序并没有影响,因为每个属性的数据类型不同。然而,对于 background-originbackground-clip 属性,如果只指定了一个盒模型区域,会应用到两个属性。如果指定了两个,第一个设置为background-origin 属性。

[翻译]防御性编程的艺术

原文The Art of Defensive Programming
(译文发布在伯乐在线,感谢刘唱校稿)

为什么开发者不编写安全的代码?我们在这并不是要再一次讨论「整洁代码」。我们要从纯粹的实用观点出发,讨论其他东西——软件的安全性和保密性。是的,因为一个不安全的软件是无用的。我们来看看什么是不安全的软件。

  • 欧洲航天局的 Ariane 5 Flight 501 在起飞后 40 秒毁坏(1996年6月4日)。价值 10 亿美金的原型火箭因为搭载的导航软件里的一个 bug 而自毁。

  • 20 世纪 80 年代,在 Therac-25 radiation 医疗机器的控制代码里的一个 bug 使得 X光强度过大,直接导致至少 5 名病人死亡。

  • MIM-104 Patriot(爱国者)里的一个软件错误使它的系统每一百小时有三分之一秒的时钟偏移——导致定位拦截入侵导弹失败。伊拉克导弹飞入在达兰(沙特阿拉伯东北部城市)的一个军营(1991年2月25日),杀害了28名美国人。

这应该能够说明编写安全软件的重要性了,尤其在特定的环境中。当然也包括其他用例中,我们也应该意识到我们的软件 bug 会导致什么。

防御式编程初窥

为什么我认为在特定种类工程中,防御式编程是解决这些问题好的方式。

抵御那些不可能的事,因为看似不可能的事也会发生。

防御式编程中有很多防御方式,这也取决于你的软件项目所需的「安全」的级别和资源级别。

防御式编程是防御式设计的一种形式,用来确保软件在未预料的环境中能继续运行。防御式编程的实践往往用于需要高可靠性、安全性、保密性的地方。——Wikipedia

我个人相信这种实现适合很多人调用的大型、长期的项目。例如,一个需要大量维护的开源项目。

我们来探索一下我提出的关键点,来完成一个防御式编程的实现。

##永远不要相信用户输入

设想你总是将获取到你不期待的东西。这将使你成为防御式程序员,针对用户输入或传入你的系统的一般东西。因为我们说过我们期待异常情况。试着尽可能严谨。断言输入值是你所期待的。

进攻是最好的防守

设置白名单而不是黑名单。举个例子,当你验证图像扩展名时,不要检查非法的类型,而是检查合法的类型并排除其他类型。在 PHP 有无数的开源校验库让你的工作变得简单。

进攻是最好的防守。共勉

使用数据库抽象

OWASP Top 10 Security Vulnerabilities 排首位的是注入攻击。这意味着有些人(很多人)还没有使用安全的工具来查询数据库。请使用数据库抽象包或库。在 PHP 里你可以使用 PDO确保防御基本注入

不要重复发明轮子

你不用框架(或微框架)吗?好吧你喜欢毫无理由地做额外的工作。这并不仅仅有关框架,也意味着你可以方便的使用已经存在的、测试过的、受万千开发者信任的、稳定的新特性,而不是你自己仅为了从中受益而制作的东西。你自己创建东西的唯一原因是你需要的东西不存在,或存在但不符合你的需求(性能差、缺失特性等等)。

这就是所谓的智能代码重用。拥抱它吧。

不要相信开发者

防御式编程与防御驱动相关联。在防御驱动中,我们假设我们周围的每个人都可能犯错。所以我们要注意别人的行为。相同观念也适用于防御式编程,我们作为开发者不要相信其他开发者的代码。我们同样也不要相信我们的代码。

在很多人调用的大型项目中,我们有许多方式编写并组织代码。这也导致混乱甚至更多的 bug。这也是为什么我们需要规范代码风格并做代码检查,让生活更轻松。

##编写符合 SOLID 原则的代码

这是(防御式)编程最困难的部分——编写不糟糕的代码。这也是很多人知道并讨论的,但没有人关心或注意并致力于实现符合 SOLID 原则的代码。

让我们看一些糟糕的例子

避免:未初始化的属性

<?php

class BankAccount
{
    protected $currency = null;
    public function setCurrency($currency) { ... }
    public function payTo(Account $to, $amount)
    {
        // sorry for this silly example
        $this->transaction->process($to, $amount, $this->currency);
    }
}

// I forgot to call $bankAccount->setCurrency('GBP');
$bankAccount->payTo($joe, 100);

在这个例子中,我们需要牢记签发付款前要先调用 setCurrency。这是很糟糕的事情,一个像这样的改变状态的操作(签发付款)不应该分两步,使用两个公开的方法。我们可以拥有许多方法付款,但我们必须只有一个公开的方法来改变状态(类不应该存在不一致的状态)。

在这个例子中,我们把它改进,将未初始化的属性封装进 Money 类。

<?php

class BankAccount
{
    public function payTo(Account $to, Money $money) { ... }
}

$bankAccount->payTo($joe, new Money(100, new Currency('GBP')));

它变得极其简单和安全。不要使用未初始化的对象属性。

避免:类的作用域外泄露状态

<?php

class Message
{
    protected $content;
    public function setContent($content)
    {
        $this->content = $content;
    }
}

class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(
    {
        var_dump($this->message);
    }
}

$message = new Message();
$message->setContent("bob message");
$joeMailer = new Mailer($message);

$message->setContent("joe message");
$bobMailer = new Mailer($message);

$joeMailer->sendMessage();
$bobMailer->sendMessage();

在上述代码中,Message 通过引用传递“joe message”到每个例子中。一个解决方案是克隆 message 对象到 Mailer 构造函数。但是我们应该做的是试着使用(不变的)值对象,而不是简单易变的 Message 对象。尽可能使用不变的对象。

<?php

class Message
{
    protected $content;
    public function __construct($content)
    {
        $this->content = $content;
    }
}

class Mailer 
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage()
    {
        var_dump($this->message);
    }
}

$joeMailer = new Mailer(new Message("bob message"));
$bobMailer = new Mailer(new Message("joe message"));

$joeMailer->sendMessage();
$bobMailer->sendMessage();

编写测试

这点我们很还需要再说吗?编写单元测试可以帮助你秉承一般的原则,比如高内聚、单一职责、低耦合和正确的对象组合。它帮助你不仅仅测试小的单元用例,也测试你组织对象方式。确实,当测试你的小功能时,你会清晰的看到你需要测试多少情况和需要模拟多少对象,来达到 100% 的覆盖率。

结论

希望你喜欢这篇文章。记住这些仅仅是建议,由你决定何时、何处以及是否应用它们。

感谢阅读!

[翻译]作为一个软件开发人员,你必须掌握的技能

原文The Technical Skills You Need to Have as a Software Developer

我强烈支持并帮助软件开发者在他们的技术能力之外提升”软技能”.事实上我曾写过一本关于这点的-但毫无疑问:技术能力也相当重要
我的意思是,当你无法写代码并开发软件,你所有掌握的软技能毫无意义.或许你是一个称职的管理者或指导者,但不是一个合格的开发者.
但如果你读了下面这些,我相信你会有兴趣成为一名软件开发者-或是一个大牛-所以我们来聊聊 你需要掌握的技术技能
#技术买单

如今,这个话题淹没了众多开发新人,因为感觉太多东西要掌握,而且不知道从哪里开始.
这里我想打破这个话题.探讨一下最基本和受益的技术技能,让你在成为软件开发者的路上越走越远.
这一章并不是要把你成为开发者需要掌握的技术技能全部列出,我会试着列举几个基本的,给你一个概述.
不用害怕.本书这一章标题”关于软件开发你需要知道的”,我给大家一个章节概述这些技能,深入浅出.
废话少说,简洁明了的看一下我认为最重要的技术技能.

#一门编程语言
我认为这个话题作为开始是明智之选,你觉得呢?
不掌握一门编程语言就不是一个合格的程序员-Vern,你知道我什么意思(详见Ernest P. Worrell不懂这个梗也没关系)
我在”学习哪种编程语言”章节中讨论过如何选择,所以不要太紧张.
我马上就表示编程语言的选择并不是你想象的那样重要.
相反,我们来聊聊为什么我建议学习一门编程语言,而不是广泛涉猎.
很多开发新手在找到一份工作前,花时间同时学习很多编程语言.
然而我认为你最好从一门编程语言开始,不要超前学习,否则你会迷惑,并让你无力学习其他的技术技能.
我建议你深入学习一门编程语言,然后你会对用这门语言写代码非常的自信.
记住一旦你决定成为何种软件开发者,你必须足够专一.

#结构化代码

当学习完一门编程语言-或是正在学习.接下来你需要知道的一件事情就是如何正确的结构化你的代码.
我已经给过你一份很棒的资源来帮助你学习这种技能:Code Complete by Steven McConnell.
什么是结构化代码?
意思是编写 好的,清晰的,易懂的代码不需要大量注释,因为代码本身就传递了信息.
很多软件开发者在整个职业生涯中都没有学习这种技术,不幸的是这种技术也是成为合格开发者的基础.
好的代码结构不仅仅是完成工作,更是艺术的结晶.
结构化你的代码真的是软件开发的艺术但这也饱受批评,因为无论你或同事维护起你的代码都要比写新代码更费时间.
我并不想在这教你如何正确结构化你的代码-因为我在上面已经给过你一个很棒的资源了.但你一定要在开始就学会如何编写干净整洁的代码,而不是在之后.
我确信这一点,即使你是个初学者,只要你能写干净整洁易懂,表达自身意义的结构化代码,任何看到你代码的人都觉得你是个有经验的专家.
至少在一定程度上,你将把职业当做专业,而不是一份工作:匠人的标志

#面向对象设计

这一点仍待讨论,尤其是你正在学习一门非面向对象的编程语言.但大多软件开发的思想建立在面向对象之上,所以你必须了解它.
面向对象设计是在设计复杂的程序时,将它们分割成独立的类和对象(实例化的类),每个都有封装单独的功能,角色和责任.
在软件开发中,我们总是尝试处理复杂度.
基于对象的思想帮助我们处理问题,因为它允许我们把复杂的系统定义并设计成相互联系的组件,而不是设计成一个复杂的整体.
如今有大量的函数语言,但你会发现,受面向对象设计和分析的影响,软件开发中流行的语言和模式仍然很重要.
你必须深刻理解什么是类,继承有哪几种形式-该什么时候使用,也要理解多态和封装.

#算法和数据结构
如果你在大学学习编程并取得计算机科学的学位,算法和数据结构是其中非常重要的一部分.
算法是解决计算机科学/编程问题的一种方式.
举个例子,从编程角度来说,这里有很多算法来实现对一个列表的排序.每一种排序算法的速度,需要的内存和基于的思想都各不相同.
在计算机科学中有很多算法,基于这些算法,面对你自己遇到的问题,理解并实现你自己的算法也相当重要.
通常,算法好的开发者用一个小时解决的问题,其他开发者可能要几天时间才能解决.
除非你对算法非常熟悉,不然你面对问题时可能意识不到解决方案早已存在.所以,我认为这是一种非常重要的技能.

数据结构分为一些相似的类别,并与算法紧密相关.
下面是一些开发者必须熟知的数据结构:
* 数组(Arrays or vectors)
* 链表(Linked lists)
* 堆(Stacks)
* 队列(Queues)
* 树(Trees)
* 哈希(Hashes)
* 集合(Sets)
精通数据结构和算法,你可以轻松解决众多复杂的编程难题.
当我开始编程时,我的数据结构和算法很糟糕,因为我都是自学的.
我从未意识到它的价值,知道我开始参加TopCoder网站的竞赛.从那我懂得了算法和数据结构能把你带到一个更高的境界.
很快,它展示了它的强大,帮助我解决了编程中的众多难题,那些我之前毫无头绪的问题,如今看来简单又有趣.
事实上,我认为这是软件开发中最有趣的一个领域.用算法和数据结构来干净,优雅又高效地解决一个复杂的问题,能给人带来巨大的成就感.
截止写这篇文章时,最好的资源是Gayle Laakmann McDowell写的一本书Cracking the Coding Interview
在这本书中,她描述了所需要知道的一切关于数据结构和算法的知识.
学习数据结构和算法是个很大的挑战,但这是值得的.这也是你能超越其他人的一项技能.多数软件开发者在这方面还是有所欠缺的
如果你想通过Microsoft或是Google的面试,你要把这种技能掌握的炉火纯青.

#开发平台和相关技术

你至少接触过一种开发平台和相关技术,并有一些经验.
我所说的平台是什么意思?
一般来说,它是指操作系统,但也适用于其他和操作系统相似的抽象概念.
举个例子,你可能是个Mac开发者或Windows开发者,取决于你使用Mac或Windows系统,但你也是个web开发者,基于特定的web平台.
我并不想讨论具体的平台,不同的人有不同的意见,讨论的目的是 我所定义的平台是你开发基于的环境,和其生态系统以及特性.
再次重申,你所选择的我认为并不重要,重要的是你有所选择.
公司通常任用不同的开发者在不同的平台和技术上进行开发.
如果你是个IOS开发者,一般容易找到工作.
这意味着你要要熟悉平台,和这个平台上的开发工具,专有的模式,以及通用的框架.
你或许认为编程语言的选择会决定平台,但关系很小.
就拿C#来说.你可以为Windows,Mac,IOS,Android,Linux甚至嵌入式系统编写代码.
所以不仅仅要选择一种语言,也要选择一个平台.

#框架或开发栈
除了学习一门编程语言和平台,我强烈建议学习一种框架,或更深入一点,学习整套工作栈.
什么是框架?
什么是工作栈?
框架就是一个平台或跨平台的函数库集合.它通常使得在平台上的编程任务变简单.
回到C#的例子.大多数C#开发者使用.NET框架或写C#应用,.NET框架包含众多库和类,允许C#开发者做某些事情的时候高度抽象,不需要每次都重复的发明轮子.
举个例子,一些.NET框架包含操作图片的代码.这些代码很难东拼西凑的写出来,所以框架使得C#开发者操作图片时受益匪浅.
开发栈有一点点不同.一个工作栈是一系列技术的集合,通常包含框架,协同开发出整个应用.
举个例子,有个常用的开发栈叫做MEAN,它基于MongoDB,Express.js,AngularJS和Node.js.
MongoDB是数据库技术.
Express.js是Node.js框架,用于创建web应用.
AngularJs是前端JavaScript框架,用于web应用的用户交互界面.
最后,Node.js是用JavaScript开发web应用的运行环境.
你不明白上面这些并不重要-除非你想成为MEAN开发者.重要的是明白这点,如果你掌握了上面这些技术,你可以开发整个web应用.
开发栈使得创建应用变得容易因为众多开发者使用它提供的通用的范例来开发应用,所以知识共享变得容易,你也可以确定这一套技术可以协同工作.
学习一套开发栈很有用,因为这意味着你拥有开发完整应用需要的全部技术.很多公司用一套特别的开发栈来开发应用,并寻找熟悉这套开发栈的开发者,得心应手的开发.

#数据库知识

尽管最近几年数据库略有变化,我并没有看到数据库走远.所以你最好对数据库也略知一二.
我写这本书时,有两种主流数据库技术:关系数据库和文档数据库.
我认为一个开发者如今至少要熟悉关系数据库并理解文档数据库.
在软件开发中,数据库用来存放应用的数据.
当然,一些团队会有专职数据库开发者或数据库管理员(DBA),但这并不妨碍你了解数据库的基本知识.
最少最少,你要了解这些:
* 数据库工作原理
* 基本的数据查询
* 数据的增加,更新和删除
* 如何汇总数据
另外,你很可能想知道如何在你所选的平台或框架上,用代码检索和储存数据.
大多数开发者需要能够写出与数据库交互的代码.

#源代码管理
源代码管理是每个软件开发项目的重点.
回忆过去,在我们使用源代码管理之前,我们需要通过网络共享工程的所有文件,手动的翻阅不同版本的软件源代码.
我很羞愧的承认我不止一次做这种事情.
但是,我还小,还不懂事,你可不要像我一样.
如今,几乎所有的专业开发者需要知道如何通过源代码管理来检查代码,检出代码和合并不同源代码的差异.
源代码管理是基本的技能,让你可以在软件项目中保存不同文件差异的历史记录.
它允许众多开发者在同时,同样的代码上协同工作,最后再合并这些更改.
我们在这并不详细说明了,但你至少要掌握一种源代码控制系统,并熟悉大多基本的源代码控制原则.
如今软件开发世界中,专业的开发团队都会用源代码控制.

#构建和部署
如今,大多数软件开发项目拥有一些自动化构建和部署系统.
有很多不同的软件应用帮助团队将那些手册上指导的步骤任务自动化处理.
你问,什么是构建和部署?
问得好.
你知道如何写代码并提交到源代码管理系统吗?
通过某种方式确保你提交的代码能正常工作,这很重要.
这就需要构建系统了.
最基本的,构建系统编译所有代码,确保没有编译错误.
一个复杂的构建系统当然也会基于代码当前的状态 运行单元测试或用户测试,运行质量检测,并提供报告.
部署系统负责将代码部署到生产环境或其他测试环境.
你并不必深入这些技术,但了解这些系统的工作原理很重要,以及它们如何构建和部署代码.
通常,创建和维护构建和部署平台属于DevOps(开发者操作的简称)的领域.
但这并不妨碍你了解这些基本知识.

#测试

很久之前,开发者并不需要了解测试.
之前我们只需要编写大量代码然后”事不关己”,把代码扔给测试人员,让他们找出代码中的bug,然后我们再去修复.
今非昔比.
伴随着众多软件项目使用敏捷开发(我们了解更多的方法论之后在讨论敏捷开发),开发人员和测试人员需要更紧密的协同工作.
质量需要整个团队负责-我很认同这一点.
这意味着,你需要了解一些测试.
你至少要熟悉下面这些技术术语:
* 白盒测试
* 黑盒测试
* 单元测试
* 边界状况
* 自动化测试
* 验收测试
一个好的开发者-我希望你也成为一个好的开发者-在把代码交给别人前测试自己的代码.
如果你想成为专业的,而不是业余开发者,测试是必须掌握

#调试
很多开发新手梦想碾碎调试路上的巨石.
每个人都想写代码.
但不是每个人都想调试他们的代码.
真相了.
你会花费90%的时间来找出为什么你的代码不能工作.
我知道调试并不迷人.我知道你每天只想写新的代码,但这个世界并不是你想象的那样.
如果你实践某种方法论,比如测试驱动开发,你很可能在调试上花费相对较少的时间,但无论怎样,你必须学会如何调试代码.
所以,既然你知道要做什么,相比无计划的忙碌,不如努力高效地学习.
关于调试的这一章,我本来要说的还有很多,但现在你只需要知道如何调试.

#方法论
是不是已经被我所说的一系列你需要了解的知识吓到了?
如果没有,这有额外一点-我保证是最后一点了.
当一些软件开发团队万事俱备开始写代码时,他们大多数遵循某种方法论.
(顺便说一下,不要期待任何团队会完全遵循他们声称的开发方法论.我在此并不想详细说明这点.我只是一个现实主义者,听闻很多软件开发者声称遵循Scrum开发,仅仅是因为他们每天开站立会.)
正因如此,你很有必要熟悉基本的软件开发方法论.
如今,有 瀑布式开发敏捷开发.
多数团队声称他们是敏捷开发.敏捷本身就是一个宽泛的概念,但有一些最佳实践.如果你想适应敏捷团队,你必须多交流,多沟通.
我们将在软件开发方法论的章节讨论的更深入.

#不知所措?别这样
我知道这里有一堆需要掌握的东西,我只是在每个关注点上轻描细节.
你现在可能有点不知所措了似乎并不理解上面所说的技术技能.
没关系.你也不是一个有经验的开发者,真遗憾(开玩笑的,么么哒,但你必须要努力)
无论如何,我在书中的”关于软件开发你需要知道的事情”标题下详细论述这几点.
所以淡定.
今后,我会继续指导你各种技术,所以准备好学习这些技术.

Redis 实现分布式锁

编程中多线程并发和锁的相关应用,尤其是现在应用都是部署在多台服务器上,分布式锁尤其重要。我们可以用 redis 来非常简单地实现分布式锁。其核心思想就是将对锁的操作转换为 redis 操作时,要保障原子性。

用到的两个关键 redis 操作 SETNXGETSET

基础设计

首先让我们自己来构思如何用 redis 实现一个分布式锁。
很简单,在 redis 中存放一个键值对,key 标识锁,value 做其他用途。
当一个线程需要同步操作时:

  1. 首先从 redis 中获取指定 key 的值,
  2. 如果返回 null,说明空闲,当前线程设置 keyvalue 值;如果获取该 key 时如果有值,说明是加锁状态,某个线程正在执行同步操作,当前线程应该挂起等待一段时间再执行。
  3. 当同步操作完成后再删除此 key

这时就要考虑并发情况下对锁的操作了,比如加锁操作包含两步 redis 操作,获取和设值,如果并发情况下两个线程先后交叉执行这两步操作,就会出问题。

  1. 线程一获取 key 的值为 null
  2. 线程二获取 key 的值为 null
  3. 线程一设置 value 为 value1,并认为自己获取到了锁。
  4. 线程二设置 value 为 value2, 并认为自己获取到了锁。

所以我们需要将这两步合为一步,成为原子操作,便可消除这个问题,这就需要上面提到的 SETNX

SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
设置成功,返回 1 。设置失败,返回 0 。

很简单,返回 1 取到锁;返回 0 没有取到锁。

异常状况

上面其实已经解决了分布式锁的最基本的问题,包括对锁的操作。下面,我们就要考虑一些特殊情况了,比如某个线程挂掉或网络问题或其他原因导致没有释放锁,这样一来所有线程就陷入了死锁,为了解决这个问题,我们可以为锁设置超时时间,value 值设置为 当前时间戳+过期时长,当其他线程无法获取锁时,可以查看 value 值是否已过期,如果过期,则可以删除掉 value ,执行上面的争用锁的操作。

  1. 获取 key 的值 value 不为 null,说明有锁
  2. 查看 value 的值是否过期
  3. 如果过期,删除 key
  4. key 值设置为自己的 value,认为获取到锁。

这也会出现上面两个线程交叉执行出现的问题,所以我们需要保障 redis 操作的原子性。
我们需要 GETSET 操作:

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
当 key 存在但不是字符串类型时,返回一个错误。

当我们判断 value 值过期之后,我们直接用 GETSET 设置新值。如果返回值为原 value ,说明我们设置成功,并获取了锁,如果返回值不为原 value,说明有其他线程抢到了锁。

可重入锁

简单来说,可重入锁就是一个线程获取某个锁之后,可以再次获取该锁。为了实现这一特性,我们可以在 value 值中加入一些标识,比如 UUID,可以让线程确认是不是自己持有的锁。

架构中的数据校验与处理

最近做一些东西,需要前后端协调,遇到一个问题是表单提交中,某个字段我们需要确保它是唯一的,比如注册时的用户名字段username

1.前端
我选择用ajax来向后端校验username的唯一性。绑定了输入框的blur事件,当输入框失去焦点时校验。

$('#username').blur(function () {
    $.getJson('url',{username:$('#username').val()},function(response){
        //如果不唯一,提示,并禁止表单提交
    });
});

2.后端
防御式编程核心思想就是”假设输入都是非法的”。这个思想在前后端编程中尤其重要,你永远不要相信网络那头传过来的数据。所以当用户提交表单时,我们需要在后端同样对数据校验,在校验username的唯一性时,我将所有的username作为List放在redis中。
尤其是在前端ajax校验时,会有大量请求,使用redis可以提高效率,减少数据库负担。

public boolean uniqueUserName(String username) {
    List<String> usernames = Redis.use().lrange("usernames",0,-1);
    return usernames == null || !usernames.contains(username) ;
}

3.数据库
在并发的可能性下,数据库可以作为我们最后一道防线,非常稳固。我在数据库用户表中,为username字段建立的unique索引。保证字段唯一性。

ALTER TABLE `user`
ADD UNIQUE INDEX `username` (`username`) ;

综上所述,即使构建一个小工程,也要缜密思考并详细设计每一个环节和流程,在工程架构的每一层都需要有独立处理数据的能力,又要联系上下游的数据传递和错误处理。

后续:当我测试Mysql的唯一索引这层保障时,发现抛出异常,因为后端配置了500页面,返回消息实体是页面,然而前端用的ajax请求,并不能解析消息,也不能直接跳转。所以错误处理和数据传递出现了问题。后来发现了jquery可以配置全局ajax错误处理函数,十分方便,之后无论前端跳转请求还是ajax请求,后端如果发生异常,都可以处理。

$.ajaxSetup({
    error: function () {
        alert('/(ㄒoㄒ)/~~哎呀,服务器有问题了,请刷新后再试试');
    }
});

简单的依赖注入与Bean工厂

我们在写代码时,经常需要在多个controller中引用多个service,比如下面这样。

public class UserController {
    UserService userService = new UserService();
    UserValidate userValidate = new UserValidate();
}
public class CodeController {
    UserService userService = new UserService();
}

这时候我才意识到依赖注入的好处,每个service只需要生成一个实例,然后注入到各个controller。
所以我想实现下面这样自动注入的效果

public class UserController {
    @DI
    UserService userService;
    @DI
    UserValidate userValidate;
}

我们梳理一下思路,首先我们需要有一个Map来存储已经实例化的Bean,这样在注入到多个地方的时候,不需重复创建新实例。
其次我们需要标注需要注入的位置,可以在需要注入的字段上用一个注解,比如@DI
最后,我们需要获取字段的类型并创建实例,然后注入到这个字段,这并不复杂,用反射可以搞定。
值得注意的是,有可能有多个层次的注入,比如除了service注入到controller外,service也可能互相注入,我们可以用递归解决。

1.首先建立Map

private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();

其中key保存类的名字,value保存类的实例

2.声明注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DI {
}

Target设置的是FIELD,表示只能用在类的变量上。

3.向一个实例中有注解的地方注入

private <T> void inject(T bean) throws IllegalAccessException {
    Class claz = bean.getClass();
    //遍历声明的变量
    for (Field field : claz.getDeclaredFields()) {
        //找到有@DI注解的变量
        if (field.getAnnotation(DI.class) != null) {
            //获取变量的实例(getBean方法后面有提到)
            Object o = getBean(field.getType());
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            //利用反射注入
            field.set(bean,o);
        }
    }
}

4.用Map管理众多实例,以及递归注入

public <T> T getBean(Class<T> claz) {
    //查看map中是否有该bean的实例
    Object bean = map.get(claz.getName());
    try {
        //如果没有,创建实例,调用刚刚写的inject方法其,注入所需字段
        if (bean == null) {
            bean = claz.newInstance();
            map.put(claz.getName(), bean);
            inject(bean);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return (T)bean;
}

上面两个方法inject()getBean()相互调用,最终递归完成多层次的注入工作。
当我们需要一个bean的时候只需要用getBean()方法,上面的代码可以整合成一个简单的Bean工厂

public class BeanFactory {
    private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
    private static BeanFactory instance = new BeanFactory();

    public static BeanFactory getInstance() {
        return instance;
    }

    public <T> T getBean(Class<T> claz) {
        Object bean = map.get(claz.getName());
        try {
            if (bean == null) {
                bean = claz.newInstance();
                map.put(claz.getName(), bean);
                inject(bean);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) bean;
    }

    private <T> void inject(T bean) throws IllegalAccessException {
        Class claz = bean.getClass();
        for (Field field : claz.getDeclaredFields()) {
            if (field.getAnnotation(DI.class) != null) {
                Object o = getBean(field.getType());
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                field.set(bean, o);
            }
        }
    }
}

通过BeanFactory.getInstance().getBean()来获取一个Bean。

最近在用jFinal框架。似乎里面并没有这种依赖注入的功能,所以写这么一个简单的注入。然后我想办法在框架实例化各个controller的时候,使用这个方法。一直没想出来怎么把这段代码嵌入这个框架,最后发现原来不需要这么复杂。

public abstract class BaseController extends Controller {

    protected Log log = Log.getLog(this.getClass());

    public BaseController() {
        for (Field field : this.getClass().getDeclaredFields()) {
            if (field.getAnnotation(DI.class) != null) {
                Object o = BeanFactory.getInstance().getBean(field.getType());
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                try {
                    field.set(this, o);
                } catch (IllegalAccessException e) {
                    log.error(this.getClass().getName() + " inject fail: " + field.getName());
                }
            }
        }
    }
}

定义一个基类集成 jFinal 的 Controller,构造方法中实现注入,然后其他 Controller 继承这个 BaseController 就ok了。

最后说一下,这个依赖注入代码灵活性和可伸缩性并不高,使用条件很单一,只能用于这种无参的构造方法,不过对于我来说足够了。

Keep Coding … Stay Cool …

[翻译]我们不再需要StringBuilder拼接字符串

原文We Don’t Need StringBuilder for Concatenation Anymore
(译文发布在码农网)

在Java开发者中,字符串的拼接占用资源高往往是热议的话题.
让我们深入讨论一下为什么会占用高资源.
在Java中,字符串对象是不可变的,意思是它一旦创建,你就无法再改变它.所以在我们拼接字符串的时候,创建了一个新的字符串,旧的被垃圾回收器所标记.

如果我们处理上百万的字符串.然后,我们就会生成百万的额外字符串被垃圾回收器处理.
虚拟机底层在拼接字符串时执行了众多操作.拼接字符串最直接的点操作(dot operator)就是String#concat(String)操作.

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}
public static char[] copyOf(char[] original, int newLength) {
    char[] copy = new char[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, 0, dst, dstBegin, value.length);
}

你可以看到一个字符数组被创建,长度则是已有字符和拼接的字符长度之和.然后,它们的值复制到新的字符数组中.最后,用这个字符数组创建一个String对象并返回.
所以这些操作繁多,如果你计算一下,会发现是O(n^2)的复杂度.
为了解决这个问题,我们使用StringBuilder类.它就像可变的String类.拼接方法帮助我们避免不必要的复制.它拥有O(n)的复杂度,远远优于O(n^2).

然而Java 8默认使用StringBuilder拼接字符串.
Java 8的文档说明:

为了提高字字符串拼接的性能,Java编译器可以使用StringBuffer类或类似技术,在使用求值表达式时,减少中间String对象的创建.

Java编译器处理这种情况:

public class StringConcatenateDemo {
  public static void main(String[] args) {
     String str = "Hello ";
     str += "world";
   }
}

上面的代码会被编译成如下字节码:

public class StringConcatenateDemo {
  public StringConcatenateDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Hello
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: ldc           #6                  // String world
      16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_1
      23: return
}

你可以在这些字节码中看到,使用了StringBuilder.所以我们在Java 8中不再需要使用StringBuilder类.

[翻译]为什么我们仍在创建Util类

原文Why Do We Still Create Util Classes?

这个问题困扰了我一段时间,甚至几年,当我们有很棒的开源的项目包含所需的类和方法时,为什么我们坚持创建自己的util类,比如 StringUtils,DateUtils,或 CollectionUtils?事实上,最近我又愚蠢的创建了CSVUtil类,而不是使用Apache Commons CSVOpenCSV.

最常见的方法是 StringUtil.isNullOrEmpty . 就像下面这样:

public static boolean isNullOrEmpty(String myString) {
    if (myString == null || "".equals(myString)) {
        return true;    
    } else {
        return false;    
    } 
}

这并不是糟糕的代码,它让你保证DRY(Don’t Repeat Yourself)原则,不需要重复的判断null或空白的字符串.

但这里有一些问题:
* 重复发明轮子,因为已经有大量优秀的库提供众多的util.
* 你需要维护所写的代码,并添加测试用例.
* 影响代码覆盖率 – 如果你不添加单元测试,会降低你的代码覆盖率.
* 你可能会有更多的bug.

我们使用这样的代码原因之一是软件遗留问题,比如,在Java8之前我们判断null是因为我们没有Optional类.

#我们用什么代替?
* Apache Commons:经验之谈,最通用的util组件,而且应该最适合替换你自己写的uitl.
* Google Guava:比Apache Commons更年轻,尽管现在也相当成熟.实现方式略有不同,并允许你使用链式方法.这或许更适合你的代码风格.

#选择
选择库保持一致性很重要 – 所以不要在一部分代码中用Apache Commons,另一部分中用Guava实现.一致性使得未来的维护更加容易.

#警告
正如所有伟大的规则一样,我们有时候要打破规则.我会在下面的情况创建Util类:
* 特殊的实现:包中没有我需要的方法,但我试着封装已有的util作为基础.
* Java8之前:我或许会创建一个util作为facade,比如Date或因为我没有Optional类.
* 拖延症:最后我创建util类的原因就是拖延我的主要任务!编写util类让我至少感到我并不是无所事事.

所以我的2017年决心是停止创建这样的类,改为使用久经考验的util包,并且替换掉每一个我所看到的自己写的util类.

[翻译]万恶的注解

原文Evil Annotations
(译文发布在码农网)

当Java 1.5引入注解,企业开发者对简化EJB和其他企业产品开发抱有很大期望。可以看一看同一时期的一篇文章用EJB 3.0简化企业Java开发

然而从那时起,Java企业使用注解出现一些无法预料的后果和副作用,一些甚至到今天都没有被注意到。幸运的是,并非所有的副作用都没有被注意到,来看一些例子,在StackOverflow标题为“Why Java Annotations?”有很多有价值的评论,“Are Annotations Bad?”这篇文章有很棒的观点,还有“Magics Is Evil”“Annotations…Good, Bad or Worse?”

##并非所有的注解都相同
尽管上面许多讨论都包含有价值的观点,但并不是所有注解都是相同的。

这里有两类注解,区别在于他们是否在运行期影响程序。首先,说一下无害的一类,它们并不会在运行期对代码产生任何影响;另一种是有害的一类,它们会修改运行期行为。无害的注解包括@Deprecated, @Override, @SuppressWarnings, 等等。有害的注解包括@Entity, @Table, @PostConstruct, @ApplicationScoped,等等。

在无害的注解中存在一小部分注解,它们非常实用。有一些提供在编译期间(静态检查)捕获错误或提供安全保障。一些实用的注解包括:@Override, @NonNull/@Nullable 来自(Checker Framework), 等等。

##为什么有害的注解不好?
我们定义了一些有害的注解,为什么要避免使用它们呢?

想象一个标准的Java Data类拥有@PostConstruct方法。这个注解表示所标注的方法应该在对象创建好之后被调用。这个功能并不是由JVM处理,所以Date类隐式获取未知的框架和容器,而自身语义上并没有做任何事情。如果这些代码并不运行在任何容器中,而只是运行在JVM中呢?这个注解大大降低了这个类的重用性。另外对于任何使用Date的地方进行单元测试就变成了噩梦,因为你必须确保每次都正确绑定post-construction,要模拟一个兼容的容器。这就有点可笑了,一个Date类需要一个容器来运行,但这确实是有害的注解对类、方法和参数的影响。

无可否认,业务逻辑往往复杂,需要更多依赖和关系,而不仅仅是一个简单的Date类。然而没有理由在一个类中显式或隐式地添加不必要的依赖或约束,有害的注解就是:依赖和约束。

##企业陷阱
不幸的是有害的声明在Java Enterprise 5大规模合法化。为了更正早期企业API的易用性问题,注解用来隐藏系统中冗余的和难用的部分。新的JEE 5被称赞为”轻量级”和”简单”,表面上看起来是这样。但是一个微小的,同时也是至关重要的误用蔓延开来。

@Stateless
public class DocumentRepository {
   public Document getDocument(String title) {
      ...
   }
   ...
}

如果想要获取一个Stateless EJB,”只需要”在类上声明@Stateless注解。确实,编写这个类只需要只一点动作,但是请注意这个类中有害的注解绑定了几百页的说明文档,而且只能在百万字节的应用服务器(Application Server)上运行。这又怎么能称的上是”轻量级”呢。所以,这个注解仅仅是真正需要编写的Java代码的占位符而已,代码仍需要以某种形式存在。现在只不过是隐藏在注解之下。

不幸的是,这种变通方案称为一种模式,现在有害的注解广泛分布:JPA, CDI, Common Annotations, JAXB 等等。

##有害的注解有时会出现在错误的地点
因为注解通常作为开发环境,有时有害的注解被当做单一职责原则(Single Responsibility Principle)或关注点分离(Separation of Concerns)的最佳实践。

让我们来考虑一下下面这个CDI例子:

@ApplicationScoped
public class DocumentFormatter {
   ...
}

上面的注解描述这个类应该是一个CDI Bean,意味着它应该只能由CDI实例化,并确保每个应用中只有一个实例。

这些信息并不属于这个类。这个服务在功能上(无论什么方式)并不会对它在当前应用中的作用产生影响。这里有两个明显的关注点。

一个JPA的简单例子:

@Entity
@Table("PERSON")
public class Person {
   ...
}

问题在于这种类往往是”领域对象(domain objects)”,它们直接将领域模型持久化。更糟的是,数据传送对象(DTO)用来在对象之间传送数据,使得整个构造变得脆弱,因为对象间耦合过于紧密。不管怎样,这是一种错误的方式。

所有的这些附加的功能和(或)信息应该从这些类中分离出来,但是它们却悄悄混在一起,因为它们”只不过”是注解。

##有害的注解有时蔓延
注解有时会传染其他对象。回顾上面那个CDI Bean。每个使用它的对象,每个依赖它的对象现在都拥有一个CDI注解,否则依赖关系树就不会构建成功。

@Entity注解也一样。因为对象之间的关系,其他对象也通过注解持久化,很快所有的持久化对象都会有这个注解。我们无法使用原生的第三方对象(除非序列化或包装它们),我们无法使用其他持久化机制(比如用NoSQL DB存放对象)。

这些注解使得这些对象无法复用。它们只能在一个严格的、受控制的、不透明的环境中使用,不能和任何东西整合。

##有什么替代品?
是XML吗?当然不是,至少对于上面的例子来说不是。

Spring框架使用配置来管理对象,因此可以用XML当做配置文件。然而,是否某个依赖需要在运行期改变,而不通过重新编译?如果不需要,那么很难说配置应该用另一门语言来表示,尤其重构困难、测试困难、管理需要特殊工具。

真正的替代品当然是好的Java代码,正确封装并解耦的。是的,用代码来管理对象,尽管有时被当做样板(boilerplate),但并不算糟糕。它带来一些好处,比如让代码可读、可调试、可重构。只有那些长片的、复杂的、冗余的样板是糟糕的,比如”关于EJB 2.0“。但是解决方案并不是摆脱所有的样板或用另一种语言隐藏样板,而是简单干净的架构,直接而不多余的信息,简单并合适的方式来面向对象。

这也适用于JPA、Spring和其他东西。误用注解来表示功能会发生Stcakoverflow上这个问题“Arguments Against Annotations”,为什么不用已有的工具呢:比如Java语言本身和编译器,来解决这类问题,面向对象和软件最佳实践。

##总结
如果注解在代码运行期加上了额外功能和约束,那它是有害的。这很糟糕,因为它隐藏了类或方法的切面,使之难懂、难复用、难重构、难测试。

不幸的是Java Enterprise不理睬Java开发者社区中发对注解的声音。所以企业级Java和其他”官方”框架更不可能重视这类问题。

至少我们可以持续关注有害的注解,如果可能尽量避免使用,编写新的框架和软件替换掉注解,不会出现有害注解所带来的问题。

[翻译]天塌了:Oracle或许要在2017年对Java SE收费

原文The Sky Is Falling: Oracle (Might) Want Your Money for Java SE in 2017
(译文发布在码农网)

最近The Register发表文章”Oracle收购Sun6年之后终于把手伸向了免费用户“伴随着副标题”认为Java是’免费’的?再想想(2017年你就欠我们钱了)”,我被震惊到了-因为我有大量基于Java SE的客户端.
等我读完整篇文章,我好奇真相是什么.

#文章摘要
那篇文章主要说了Oracle运作他们的许可证管理服务(LMS)致力于”找出需要付费的人群”.文章引用了一名拥有八万PC终端的Java客户,以及他们如何因为没有为Java许可证付费而欠Oracle十多万美金.
文章声称,即使Java软件的合作伙伴也不能避开LMS小组,原因就是Java是免费的的这一错误观念.
然后,文章提及了许可证的等级,每位用户四十美金到三百美金不等或每个处理器一万五千美金.
最后,文章讨论了检查你使用的Java SE,确保你只下载安装了你需要的东西.当然,也要注意LMS小组在2017年找上你.

#我的调研
我决定做的第一件事就是去Oracle的网站看一看Java SE的页面.我希望我能找到一个”添加至购物车”的按钮,允许我购买许可证,确保许可证管理服务小组(LMS)不会找上我.这是我找到的一个截图:

不幸的是,只有到下载产品的链接.我之后总会回来看看,即使是周末.
我注意到Oracle有一个在线商店,所以我认为我可以搜索Java SE.我确定它会指引我到一个页面,让我可以添加什么东西到购物车.这是我搜索”java se”的结果.

我找到了几个”现在购买”按钮,一个是Oracle Java SE support的,另一个是Oracle Java SE Advanced的.
当我读到有关Java SE Advanced的信息时,我认为我找到了需要购买的东西.然而点击链接之后,我被带到以下页面:

这看起来并不像我需要的许可证.它像是Java SE,附带捆绑包…或许是”Advanced”版本.更复杂的是我要为我的订单提供至少500个终端(或2个处理器).最低都要每年1万美金.
我把我的调研提高一个层次,从Google搜索”购买java se许可证(buy java se license)”,得到了一个链接指向Java SE概览-常见问题页面.问题是”Java仍然免费吗”,得到以下信息:

当前版本的Java-Java SE 8-在一般计算用途的分配使用中是免费可用的.Java SE在Oracle Binary Code License(Oracle二进制代码许可证 BCL)下仍是免费的.在嵌入式设备和其他计算环境中使用JRE可能需要向Oracle支付许可证费用.阅读更多关于嵌入式使用Java SE的信息,或联系你当地的Oracle经销商来获得许可证.

好吧…当我读到嵌入式设备的时心情舒畅,但Oracle在回答中紧接着加了句”…和其他计算环境”.所以跳到这个链接得到下面截图:

这时,我深吸一口气,因为这是Java的分配使用情况,我并不熟悉.

#我的观点
首先,我不是一个软件许可证专家.那篇文章看起来引用的是LMS小组对没有Java SE许可证的嵌入式产品采取的措施.文章提到的其中一名客户在零售业.所以提到的那些PC可能是收银机(或其他东西),使用了嵌入式Java版本用于专有用途?这只是个猜测.
考虑到我以前的客户端运行着Java SE,我读完那篇文章期待着采取一些行动.可能会提高明年预算的行动.至少我可以说(再次声明我不是软件许可证方面的专家),我的客户端并不受那篇文章的影响.
我的团队已经成功转向了OpenJDK,我在DZone.com上提到过.所以,如果你的团队担心Oracle Java SE许可证,害怕LMS小组,你也可以考虑使用OpenJDK.
我很有兴趣听到你关于这个话题的观点.
Have a really great day!