数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
64位py2.7版本 更新 document_GetResources 枚举页面资源 document_GetresourceText 获取指定url的内容 包括页面图片
下载地址 下载地址 密码:upr47x
项目管理
2018-07-22 14:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
64位py2.7版本 更新 document_GetResources 枚举页面资源 document_GetresourceText 获取指定url的内容 包括页面图片
下载地址 下载地址 密码:upr47x
项目管理
2018-07-22 14:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在Java(或者叫做面向对象语言)的世界中,工厂模式被广泛应用于项目中,也许你并没有听说过,不过也许你已经在使用了。 简单来说,工厂模式的出现源于增加程序序的可扩展性,降低耦合度。之所以叫做工厂模式,是用工厂生产产品来形象的比喻代码中生产对象的过程。总体来说,工厂模式分为以下几种: 简单工厂模式(Simple Factory Pattern) 工厂方法模式(Factory Method Pattern) 抽象工厂模式(Abstract Factory Pattern)
简单工厂模式(Simple Factory Pattern)
我们模拟一种场景,有一家汽车厂(AutoFactory)要生产汽车,现在主要生产小轿车(Car)和大巴车(Bus),那用代码模拟如下:
首先“设计”一个汽车原型(定义汽车接口),这个接口体现了所有汽车的共性: public interface Auto { //所有汽车都可以被驾驶 public void drive(); }
接下来我们“设计”两种汽车:小轿车和大巴车: //小轿车 public class Car implements Auto{ @Override public void drive(){ System.out.println(“小轿车启动了”); } } //大巴车 public class Bus implements Auto{ @Override public void drive(){ System.out.println(“大巴车启动了”); } }
开始“建厂”了,我们实现一个简单工厂类: public class AutoFactory{ //生产汽车 public Auto produce(String name){ if("car".equals(name)){ return new Car(); } else if("bus".equals(name)){ return new Bus(); } } }
一切就绪,我们开始生产汽车了,先生产一辆小轿车: AutoFactory factory = new AutoFactory(); Auto car = factory.produce("car"); car.drive();
简单工厂模式实现了生成产品类的代码跟具体的产品实现分离,在工厂类中你可以添加所需的生成产品的逻辑代码,但是问题来了,这不符合“开放-封闭”原则的,也就是说 对扩展开放,对修改关闭 ,如果你要加一个新的汽车类型还需要修改produce方法,为解决这个问题,从而引入了工厂方法模式(Factory Method Pattern)。
工厂方法模式(Factory Method Pattern)
工厂为了扩大市场,现在要开始生产卡车(Truck)了,于是我们设计一辆卡车: //卡车 public class Truck implements Auto{ @Override public void drive(){ System.out.println(“卡车启动了”); } }
如果按照简单工厂的逻辑,需要修改produce方法(也就是我们要改造已有工厂),这样会影响已有生产,怎么办呢?解决办法是再新建新的工厂:
首先我们“设计”一个工厂原型(工厂接口): public interface IAutoFactory{ //生产汽车 public Auto produce(String name); }
然后将原来的工厂简单改造符合设计好的工厂原型(实现接口即可,所有逻辑不变): public class AutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(String name){ if("car".equals(name)){ return new Car(); } else if("bus".equals(name)){ return new Bus(); } } }
好的,接下来为了生产卡车,我们要为卡车单独建厂: public class TruckAutoFactory implements IAutoFactory{ //生产卡车 @Override public Auto produce(String name){ return new Truck(); } }
开始生产卡车: IAutoFactory factory = new TruckAutoFactory(); Auto car = factory.produce(null); car.drive();
这里的抽象工厂中,我们为了减少改造成本,在简单工厂基础上做最小修改,理论上produce参数可以没有,然后为小轿车、大巴车和卡车分别建立工厂,分别生产。这样如果有了新的类型的车,可以不改动之前的代码,新建一个“工厂”即可,做到“开放封闭原则”。
虽然看似类变多了,逻辑复杂了,但是这种改造带来的好处也是显而易见的:不变动老的代码,通过新建工厂类完成新功能的添加,老功能不变,最大限度的避免动了老代码的逻辑导致引入新的bug。
工厂方法的结构图如下:
抽象工厂模式(Abstract Factory Pattern)
我们继续针对汽车工厂说明,由于接下来工厂需要继续扩大规模,开始涉足汽车配件,上层决定涉足汽车大灯业务,针对已有车型生产前大灯。但是如果按照工厂方法模式,需要再继续新建一批工厂,针对每种汽车再建N个工厂,考虑到成本和简单性,针对对已有汽车工厂改造。
首先“设计”大灯原型: //大灯 public interface Light { //开灯 public void turnOn(); }
再“设计”小轿车、大巴车和卡车大灯: //小轿车大灯 public class CarLight implements Light{ @Override public void tunOn(){ System.out.println(“小轿车大灯亮了”); } } //大巴车大灯 public class BusLight implements Light{ @Override public void tunOn(){ System.out.println(“大巴车大灯亮了”); } } //卡车大灯 public class TruckLight implements Light{ @Override public void tunOn(){ System.out.println(“卡车大灯亮了”); } }
接下来我们重新“设计”原有的汽车工厂(修改工厂接口或者抽象工厂类) public interface IAutoFactory{ //生产汽车 public Auto produce(); //生产大灯 public Light produceLight(); }
好的,改造工厂,首先改造小轿车工厂: public class CarAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Car(); } //生产车灯 @Override public Light produceLight(){ return new CarLight(); } }
改造大巴车工厂: public class BusAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Bus(); } //生产车灯 @Override public Light produceLight(){ return new BusLight(); } }
改造卡车工厂: public class TruckAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Truck(); } //生产车灯 @Override public Light produceLight(){ return new TruckLight(); } }
开始生产: //生产小轿车和小轿车大灯 IAutoFactory factory = new CarAutoFactory(); Auto car = factory.produce(); car.drive(); Light light = factory.produceLight(); light.turnOn(); //生产大巴车和小大巴车大灯 IAutoFactory factory = new BusAutoFactory(); Auto bus = factory.produce(); bus.drive(); Light light = factory.produceLight(); light.turnOn(); //生产卡车和卡大灯 IAutoFactory factory = new TruckAutoFactory(); Auto truck = factory.produce(); truck.drive(); Light light = factory.produceLight(); light.turnOn();
抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类,抽象工厂模式较好的实现了“开放-封闭”原则,是三个模式中较为抽象,并具一般性的模式。
抽象工厂模式示意图如下:
参考资料 Java设计模式——工厂模式 结合实例分析简单工厂模式&工厂方法模式&抽象工厂模式的区别
工厂模式在Hutool中的应用
在 Hutool 中,Hutool-db模块为了简化和抽象连接池的创建,使用了 工厂方法模式 ,首先定义了 DSFactory : //数据源工厂 public abstract class DSFactory { //获取数据源 public abstract DataSource getDataSource(String group); }
然后分别创建了: HikariDSFactory 、 DruidDSFactory 、 TomcatDSFactory 、 DbcpDSFactory 、 C3p0DSFactory 几种常见连接池的工厂实现,这样用户可以很容易的使用对应的连接池工厂创建需要的连接池数据源(DataSource)。
同样,用户也可以自己继承 DSFactory 实现抽象方法 getDataSource 来自定义数据源。
在此基础上,对于数据源工厂的创建,又使用了 简单工厂模式 ,代码如下: private static DSFactory doCreate(Setting setting) { try { return new HikariDSFactory(setting); } catch (NoClassDefFoundError e) { //ignore } try { return new DruidDSFactory(setting); } catch (NoClassDefFoundError e) { //ignore } try { return new TomcatDSFactory(setting); } catch (NoClassDefFoundError e) { //ignore } try { return new DbcpDSFactory(setting); } catch (NoClassDefFoundError e) { //ignore } try { return new C3p0DSFactory(setting); } catch (NoClassDefFoundError e) { //ignore } // 默认使用Hutool实现的简易连接池 return new PooledDSFactory(setting); }
通过try的方式,按照优先级尝试创建对应连接池的工厂类,如果用户没有引入对应连接池库,就会报 NoClassDefFoundError 异常,从而尝试创建下一个连接池工厂,依次类推,直到发现用户未引入任何连接池库,则使用Hutool默认的简单连接池 PooledDSFactory 。通过这种方式,简化了用户对连接池的选择配置。
项目管理
2018-07-21 23:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
介绍
TCP和UDP使用同一网络层(IP),但TCP提供了面向连接、可靠的传输层服务
TCP传输给IP层的信息单位称为报文段或段
TCP通过如下方式保证可靠性: 应用数据被分割成TCP认为最合适发送的数据块 TCP发送一个段后,会启动一个定时器等待目的端确认收到该段,否则重发 当TCP接收端收到数据,会发送一个确认 TCP保持首部和数据校验和,接收端发现校验和有差错就丢弃报文段 TCP将对收到的数据重排序,保证顺序交给应用层 TCP接收端必须丢弃重复数据 TCP提供了流量控制,避免“快”主机压垮“慢”主机
TCP提供字节流传输服务,它交换的数据都是由字节(8位)构成
TCP不解释字节流内容,也就说它不知道传输的是二进制还是字符,这个内容由应用层解释
TCP首部

由图可知,TCP首部最少 5*4=20 字节 端口:通过TCP首部的源、目的端口,和IP首部的源、目的IP,可以唯一确定一个连接 序号:下一字节数据的序号。每发送一字节数据,序号+1,到达 2^31-1 后置0 确认序号:确认端期望接收下一字节的序号,值为上次成功接收数据序号+1 首部长度:代表首部占了多少个32位,4位代表最多 2^4=16 个32位word,也就是64字节 标志位:6个标志位 URG:紧急指针有效 ACK:确认序号有效 PSH:接收方应该尽快将这个报文段交给应用层 RST:重建连接 SYN:同步序号用来发起一个连接 FIN:发送端完成发送任务 窗口大小:接收端期望接收的字节数,TCP基于该窗口大小进行流量控制 校验和:覆盖TCP首部和TCP数据,发送端计算和存储,接收端验证 紧急指针:代表一个正偏移,表示紧急数据的序号到哪结束。当URG=1该值才有意义,该偏移加上序号字段的值等于紧急数据最后一字节的序号 选项:最常见选项为MSS,代表最长报文段大小
项目管理
2018-07-21 19:42:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
JAVA 中共有三种WebService 规范,分别是 JAX-WS (JAX-RPC)、JAXM&SAAJ、JAX-RS。
1. Jaxws(掌握)
JAX-WS 的全称为 Java API for XML-Based Webservices ,早期的基于SOAP 的JAVA 的Web 服务规范JAX-RPC(Java API For XML-RemoteProcedure Call)目前已经被JAX-WS 规范取代。从java5开始支持JAX-WS2.0版本,Jdk1.6.0_13以后的版本支持2.1版本,jdk1.7支持2.2版本。
Jaxws 开发的 webservice 传输 soap 协议。
2JAXM&SAAJ(了解)
JAXM(JAVA API For XML Message)主要定义了包含了发送和接收消息所需的API,SAAJ(SOAP With Attachment APIFor Java,JSR 67)是与JAXM 搭配使用的API,为构建SOAP 包和解析SOAP 包提供了重要的支持,支持附件传输等,JAXM&SAAJ 与JAX-WS 都是基于SOAP 的Web 服务,相比之下JAXM&SAAJ 暴漏了SOAP更多的底层细节,编码比较麻烦,而JAX-WS 更加抽象,隐藏了更多的细节,更加面向对象,实现起来你基本上不需要关心SOAP 的任何细节
3. JAX-RS(掌握)
JAX-RS 是JAVA 针对REST(Representation State Transfer)风格制定的一套Web 服务规范,由于推出的较晚,该规范(JSR 311,目前JAX-RS 的版本为1.0)并未随JDK1.6 一起发行。
Rest定义可以自行搜索
jax-RS可以发布 rest风格webservice,因为rest的webservice不采用soap传输,直接采用http传输,可以返回xml或json,比较轻量。
以后可能会流行Rest风格的
项目管理
2018-07-21 19:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
#为什么(要使用)GNU Affero GPL? 作者信息:Copyright © 2010, 2013, 2014, 2015 Free Software Foundation, Inc. This page is licensed under a Creative Commons Attribution-NoDerivs 3.0 United States License. 翻译者:IFRFSX. 1079092922@qq.com ,“(* )”内的文字是添加的补充注释。 翻译文本的许可证:CC BY 4.0
GNU Affero 通用公共许可证是由普通的GNU 公共许可证的修改版。它有一个额外要求:如果您在服务器上运行一个(* 由GNU AGPL许可的)修改版的软件,并且让其他用户与这个软件与它(* 这个程序)通信,那么这个服务器也必须允许他们下载(* 这个)正在运行的修改版本相对应的源代码。 The GNU Affero General Public License is a modified version of the ordinary GNU GPL version 3. It has one added requirement: if you run a modified program on a server and let other users communicate with it there, your server must also allow them to download the source code corresponding to the modified version running there. GNU Affero GPL 的用途是防止影响(* 那些)在服务器上经常使用的自由程序的开发人员的问题。
假设您在普通的GNU GPL下开发和发布程序,如果开发人员 D 修改了程序并且发布了它,GPL要求他在GPL下发布他的版本。因此,如果您获得了他的修改版本,(* 那么)您可以自由地将他的部分或全部更改合并到您自己的版本中。 The purpose of the GNU Affero GPL is to prevent a problem that affects developers of free programs that are often used on servers. Suppose you develop and release a free program under the ordinary GNU GPL. If developer D modifies the program and releases it, the GPL requires him to distribute his version under the GPL too. Thus, if you get a copy of his version, you are free to incorporate some or all of his changes into your own version.
但是假如这个程序在服务器上很有用,当 D 修改程序时,他很有可能会在自己的服务器上运行它,而不会发布副本。然后,您将永远得不到他的版本的源代码副本,您可能不喜欢那样的结果。 使用 GNU Affero 通用公共许可证 就避免了这种结果。如果D在每个人都可以使用的服务器上运行他的版本,那么您也可以也可以使用它。假如他遵守许可证要求,让服务器的用户下载他的版本的源代码,那么您也可以这样做,然后您可以将他的修改合并到您的版本中。(如果他没有遵守,您的律师就会投诉他。) But suppose the program is mainly useful on servers. When D modifies the program, he might very likely run it on his own server and never release copies. Then you would never get a copy of the source code of his version, so you would never have the chance to include his changes in your version. You may not like that outcome. Using the GNU Affero GPL avoids that outcome. If D runs his version on a server that everyone can use, you too can use it. Assuming he has followed the license requirement to let the server's users download the source code of his version, you can do so, and then you can incorporate his changes into your version. (If he hasn't followed it, you have your lawyer complain to him.)
普通的GNU 通用公共许可证 第3版 和GNU Affero GPL(* 第三版)[^1] 都有(* 一段)文本允许您在一个程序中将这两个许可证下的模块链接在一起。
GNU Affero GPL并没有解决“服务作为软件替代品(SaaSS)”的问题。
SaaSS的意思是,用户使用其他人的web服务器来完成他们自己的计算。这要求他们将数据发送到服务器,服务器为他们进行计算,并将结果发送回给他们。SaaSS是一种不公正的现象,因为用户无法控制其计算过程。
如果这个服务器上的某个程序是在 GNU Affero GPL 下发布的,那么服务器就需要为用户提供相应的源代码。这很好,但是拥有这个源代码并不会让用户控制服务器上的计算。它也不会告诉用户,有什么其它以别的方式检查或更改用户数据的软件可能也运行在同一服务器。
我们没有看到任何合理的方法来解决关于特定程序的许可证条件下的SaaSS问题。甚至写一个区分 使用SaaSS 和 不使用SaaSS 的法律条件也是一种挑战,我们也不会看到程序许可证会对能解决 SaaSS 固有问题的 SaaSS 案例作任何要求。因此,我们对SaaSS问题的解决方案很简单:拒绝使用SaaSS服务。
如果一个程序是专门针对SaaSS的,你不应该写它。但是许多程序对于各种各样的服务是有用的,包括 SaaSS 的和非 SaaSS 的。编写和发行这些程序是很有用的,这样人们就可以用它们建立非saass服务,并且可以在AGPL下发行它们。 Both the ordinary GNU GPL, version 3, and the GNU Affero GPL have text allowing you to link together modules under these two licenses in one program. The GNU Affero GPL does not address the problem of Service as a Software Substitute (SaaSS). SaaSS means that users use someone else's web server to do their own computing. This requires them to send their data to the server, which does their computing for them and sends the results back to them. SaaSS is an injustice because the users cannot control their computing when it's done that way. If some program on this server is released under the GNU Affero GPL, the server is required to offer the users the corresponding source of that program. That is good, but having this source code does not give them control over the computing done on that server. It also does not tell them what other software may be running on that server, examining or changing their data in other ways. We don't see any sensible way to address the SaaSS problem with license conditions on particular programs. Even to write a legal condition to distinguish between SaaSS use and non-SaaSS use would be a challenge, and if we had that, we don't see anything that the program's license might require in the SaaSS case that would correct the inherent wrong of SaaSS. Thus, our solution to the problem of SaaSS is simple: refuse to use services that are SaaSS. If a program is meant specifically and only for SaaSS, you shouldn't write it. But many programs are useful for a variety of kinds of services, including some that are SaaSS and some that are not. It's useful to write and release these programs so people can set up non-SaaSS services with them, and good to release them under the AGPL.
####注释
[^1]:根据GNU官方的说明,Affero 通用公共许可证第 1 版不是一个 GNU 许可证,但它设计的目标与 GNU AGPL 类似。 具体请参见: GNU 许可证页面 ;
项目管理
2018-07-20 21:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在这篇博文编写时,博主已经做了3年的小组长。在这之前,很多总结性的文字,仅限于在每月上交的月总结中体现。而月总结这个东西,写过了,就忘记了。也并不会给自己和他人留下多少可借鉴的东西。所以,这次写这篇博文,一是对自己的成长立一个标志,以激励自己变的更优秀;另一方面,也希望能够给看到这篇博文的朋友,带去一些有价值的东西。同时也欢迎各方大佬批评指教(喷子手动屏蔽)。
一、个人能力发展
这些年来,从刚入行到现在,我一直都很重视个人能力的发展。在我看来,在软件这一行业能够走多远,首先一个标准就是自己拥有多少专业技能。自己的价值,就是自己能够干多少别人干不了的事情,别人能干的事情,自己能比别人快多少,质量高多少。否则,自己就随时有被替代的可能。而在成长过程中,有很多因素是很必要的。
1、热爱
我在入行的时候,曾有一位前辈问我,为什么要进这一行。我回答的是,因为喜欢。没错,我确实是因为喜欢,而进了软件这一行。但其中的缘由,为什么喜欢这一行,在这里就不说啦(羞耻.....)!而正因为这份喜欢,从而使我度过了各种各样的困难,而从来没有想过要放弃。所以,以后在我带的每一个人,我都会问他,你为什么要做软件这一行。因为足够喜欢,才会有足够的激情,才会不向困难低头,才会一步步成为自己想要成为的人。而这也是我认为的最重要的因素。那么,你为什么要做这一行?
2、目标
做什么事情,都需要有一个目标。没有目标的努力,就像漫无目的地行走,永远不知道终点在何方。以至于兜兜转转,很可能就又回到了起点,浪费的不止时间,更消费了青春和激情。说起我的目标,在大学时,我的目标是成为一个能够成为一个架构师。但当时对架构师并没有明确的概念。为什么呢?因为在我学习的过程中,发现很多时候,写代码的时候很费力(主要是能力不足),而如果换一种方式,封装一下,写起来就很轻松。那个时候我接触了框架(SpringMVC)。在一定的规则下,代码显得如此优雅,我深深地为之着迷。所以我想要成为这种规则的制定者,也就是我当时理解的架构师。到现在为之,4年之后的我,依然想要成为一名架构师,只是知道了更多成为架构师必须的条件,不仅仅是写代码而已。现如今我正在向着这个目标而努力。那么,你的目标是什么呢?
3、规划
前面说的目标,只是你想要最终达到的一个高度,而在奋斗的过程中,更需要一些规划。规划也分长期规划和短期规划。长期规划,是自己定一个比较长的时间,达到一个目标。在这个过程中,需要做那些事,学哪些技术,拥有哪些思想。而短期规划,就是怎样去实现这些规划。比如,要学习一个新的技术。那么自己应该从哪里找资料,学到什么程度算是完成任务,学完之后要做出什么东西。而将这样一个个小的规划,充实到一个长期规划当中,按照规划一步步去实现,必然会在将来的某一天,达到自己心目中的那个位置。
4、执行
上面说的都是一些主观因素,而一些客观因素,最重要的一点,就是执行。你对软件有足够的热爱,你很清楚自己的目标,并且也有自己的规划。但是,不去执行,还是空。之前在知乎看到一个问题: 如何从一个空有上进心的人,变成行动上的巨人?
想要变成行动上的巨人有那么难吗?想干什么,直接去干不就行了吗?与其在网上搜多少鸡汤灌下去,也不如马上去做来得实惠。我喜欢一句话“累,是因为你在走上坡路!”。想要轻轻松松完成人生目标?拼爹是个好主意。
这些年里,见到了太多因为困难而离开软件这一行的,也见过有人将难做的部分都留给别人,自己干着最轻松的活。而这些人的现状,也都在我眼里。因为困难离开软件这一行的,在另一行业遇到了困难,然后又逃避困难转到了其他行业,始终在做新人。干着最轻松的活的人,到现在还是只能干那些轻松的活。你付出了多少努力,就会有多少收获。这是一个很简单,但不是所有人都懂的道理。
二、带队开发
带队开发,听起来很简单,好像只要往外分配任务就好了。但是,为什么能做领队人的那么少?自然是因为做领队,真的不是那么简单。在这里面你要知道每个人的水平实力,每个人的擅长和缺陷,谁适合做什么,谁不适合做什么。整体任务,怎样设计,怎样模块化,怎样功能化,怎样开发,达到什么效果,简单的功能谁去写,困难的功能谁去写,写困难任务的人手不够怎么办,简单任务量太大怎么办........要考虑到的东西实在太多,就不在这一一列举了。要做好一个领队,也有许多重要的因素。
1、心态
领队,不是超然于整个团队的人员,领队也是团队成员。很多人在成为领队的时候,就会产生一种超然的姿态(本人也出现过)。以至于迷失了自己,在团队里面颐指气使,嚣张跋扈。做一个合格的领队,不是让你去主宰别人的命运,而是带领大家做好事情。一个前辈曾告诉我:领导是要为团队服务的。以前我不理解,以为就像餐厅的服务员那样,笑脸相迎。后来我发现,那样的做法是在做服务生,而不是领队。在我看来,领队是团队的中枢,把人员安排到最合适的地方,做好成员间的沟通、调节,任务整体的划分、分配和衔接,核心、困难问题的解决。而要做到这些,首先必须要摆正自己的心态,不要把自己超然于团队之外,始终记住自己也是团队的一份子,只是和普通成员要负责的工作不同罢了。
2、关心团队成员
关心团队成员,说的不是关心团队成员的个人生活,而是团队成员的能力、成长方向、和诉求。在团队中,一个人强,不是真正的强,每个人都强才是真正的强。作为领队,有义务去提升团队的战斗力。注意,我这里说的是提升团队的战斗力,而不是提升团队成员每个人的战斗力,因为每个人的情况都不同,不可能做到人人都一样。团队中,需要高精尖人才,同样也需要普通人。而需不需要培养人才,这就要看要培养的人的诉求是什么,是按时上班下班的纯上班族,还是有更高追求,想要提升自己能力的人。另外,这里面也有一个非常主观的因素,就是这个人值不值得培养。这个问题就只能靠领队人自己去判断了。像是有些人,你费尽心力培养了他,结果他拍拍屁股走人了,不仅没有为团队带来价值,还浪费了精力和时间。
3、领队必须清楚系统的设计
作为一个合格的领队,自己必须要搞清楚的系统设计。否则,在开发过程中必然会遇到各种各样难以预料的问题。设想,当团队成员在做某个模块的时候,不清楚业务流程是怎样运转的了,这时候,如果领队不能进行讲解,那该是有多么尴尬。甚至严重的,直接影响到整个系统的研发进度。
4、将任务分给合适的人
不是所有人的能力都一样,不是所有的人都能干同样的活。所以,将任务分给合适的人,是作为领队很重要也是很艰巨的一个任务。你不能让能力高的人累成狗,也不能让能力低的人闲成猪。合理的切分任务、分配任务,让每个人都做能力范围内的事情,是作为领队必须要考虑的事情。将没什么难度的任务,分给能力较低的人,将有些难度的任务,分给能力高些的人,将如果分下去肯定会出问题的任务分给自己。这样,才不至于让累的累死,闲的闲死,才能发挥团队的战斗力。
写到这里,其实我已经不知道自己在写什么了,就像是自己必须要说的话,不吐不快。在这篇博文的结尾,希望我写的这些东西,能给你带去一些有用的东西。也希望有大佬能多多指教。转载请注明出处,谢谢。
项目管理
2018-07-20 16:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
每个人都有不同的认知规律和习惯, 有的人喜欢搞一套严密的大理论, 论述起来滔滔不绝, 不管自己懂不懂, 反正读者/听者是没搞懂。 有的人喜欢从实践出发, 没看到代码, 不运行一下, 不看到结果, 就不太舒服。 我感觉, 我偏向后者, 必须有一个结果作用于我, 我才有感触。
比如初学C语言, 我也很不喜欢老师总在讲什么原码, 反码和补码, 讲什么 32767 + 1 为什么等于-32768, 讲什么m.nf%控制输出的具体含义. 我更喜欢搞个阶乘运行一下来玩玩, 搞个简单的界面问答系统感知一下, 搞个排序或者汉诺塔什么的玩玩。 等玩到一定程度, 再回去学那些略显枯燥的无聊知识。
好吧, 不扯了, 毕竟每个人的学习习惯不一样。

在本文中, 我就不大讲特讲组播了, 至于组播概念和原理, 度娘谷哥会告诉你, 请自学去, 其实也很简单 。当然, 如果后续有必要涉及, 我也会介绍一下。 现在仅仅来实战一下组播。

(有必要说明一下, 下面两个程序必须运行在相同的机器上才有效)

发送程序为:
#include #include #include #pragma comment(lib, "ws2_32.lib") int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET sock = socket(AF_INET,SOCK_DGRAM,0); int n = 0; // 注意, 这个n值很重要, 下面我会讲到 setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&n, sizeof(n)); sockaddr_in addr; addr.sin_addr.S_un.S_addr = inet_addr("234.2.2.2"); addr.sin_family = AF_INET; addr.sin_port = htons(8888); static int i = 0; while(1) { char buf[100] = {0}; sprintf(buf, "blablablabla:%d", i++); sendto(sock, buf, strlen(buf) + 1, 0, (sockaddr*)&addr, sizeof(sockaddr)); Sleep(500); } closesocket(sock); WSACleanup(); return 0; }
运行它。


加入组播的接收程序为:
#include #include #include #pragma comment(lib, "ws2_32.lib") int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.S_un.S_addr = INADDR_ANY; addr.sin_port = htons(8888); bind(sock, (sockaddr*)&addr, sizeof(addr)); // 加入组播啦 ip_mreq multiCast; multiCast.imr_interface.S_un.S_addr = INADDR_ANY; multiCast.imr_multiaddr.S_un.S_addr = inet_addr("234.2.2.2"); setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multiCast, sizeof(multiCast)); int len = sizeof(sockaddr); while(true) { char buf[100] = {0}; recvfrom(sock, buf, sizeof(buf) - 1, 0, (sockaddr*)&addr, &len); printf("%s\n",buf); } closesocket(sock); WSACleanup(); return 0; }
运行它。


结果如下(两个进程通信了):

上面我们说过, 如上两个程序必须运行在同一电脑上才有效, 那要运行在不同的电脑上, 该怎么办呢? 我们注意到, 在发送程序中有int n = 0; 这个n为零表示发送端和接收端必须位于同一机器上, 如果n为1, 那么发送端和接收端就可以位于不同机器上了, 只需要他们位于同一个子网内即可。 实战过程如下: 假设pc0, pc1, pc2, pc3...在同一个子网内,将发送端的n改为 1且, 运行在pc0上, 然后在pc1, pc2, pc3...上运行接收端, 可以发现, pc1, pc2, pc3都接收到了组播消息, 与我们预期的结果吻合。



http://blog.csdn.net/victoryknight/article/details/7814243
主题 UDP Qt 路由器
局域网内的两台机器如果隔有路由器,那么这两台机器之间不能进行广播通信,但是我们可以换成组播的通信的方式,达到相互通信的效果。
一 服务器端 QUdpSocket udp_socket; udp_socket.bind(QHostAddress::Any, udp_listen_port, QUdpSocket::ReuseAddressHint); QHostAddress mcast_addr("224.0.0.17"); udp_socket.setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0);//禁止本机接收 udp_socket.joinMulticastGroup(mcast_addr);//这句是关键,加入组播地址
至此,我们已经完成了服务器的绑定并加入了组播组的工作。

二 客户端 QHostAddress mcast_addr("224.0.0.17");//组播地址与服务器相同 QUdpSocket udp_socket; udp_socket.writeDatagram(datagram, mcast_addr, UDP_SEND_PORT);//向服务器发送数据(UDP_SEND_PORT与服务器的监听端口相同)
项目管理
2018-07-20 14:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
https://blog.csdn.net/v_july_v/article/details/6530142
http://www.cnblogs.com/CarpenterLee/p/5503882.html
项目管理
2018-07-20 14:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
使用PD(UML工具——Power Designer)设计数据库正向工程
现在我们来使用Power Designer这个工具设计数据库。首先打开Power Designer这个工具,点击【文件】菜单,在下拉框中选中【建立新模型】,如下:
在弹出的对话框中创建一个Model,可按下图创建
接着来创建一个数据库表t_user,可按下图来创建。
选中【General】选项卡,在Name输入框中输入要新建的表名。
接着再选中【Columns】选项卡,新建表中的列。
最后点击【确定】按钮,则新建的表为
上面新建完t_user表之后,使用Power Designer这个工具还可以将建表语句导出到一个文件中,怎么做呢?可参考下图:

点击【确定】按钮之后,可以在桌面上找到bos_bc.sql这样的一个文件,打开,即可看到t_user表的建表语句:
有了这样一个bos_bc.sql文件之后,怎样将其导入到数据库中并创建相应的表呢?有两种方式:
第一种,使用图形化界面工具来操作。
第二种,使用命令行工具,在mysql中执行sql脚本
接下来,我就来告诉大家如何创建有外键关联关系的两张表,以员工和部门为例来讲解。
上面我们新建了一张员工表——t_user,现在就要新建一张部门表——t_dept了,我们尽量设计得简单点,该表的设计可仿照上面的步骤,在此不过多赘述。新建完的t_dept表为:
现在就要建立t_user表和t_dept表之间的外键关联关系了,具体做法是在t_user表新创建一列——dept_id,该列引用t_dept表的主键id,具体操作如下:
可发现这并不是我们所想要的,因为虽然建立了两表之间的外键关联关系,但是是t_user表的主键(id)引用了t_dept表的主键(id)。为了在两表之间建立正确的外键关联关系,就要双击 连接线 ,在弹出的对话框中选择【Joins】选项卡,如下:
点击【确定】按钮,即可如我们所愿:
而且建表语句为:
反向工程
File —> Reverse Engineer –>Database
修改PDM 模块名称和 DBMS 类型,这里是Oracle 10gR2:
配置数据源:


Connection profile name: 生成连接文件名称
Directory:生成连接文件保存位置,我这里保存到桌面
Connection type:连接类型,可是使用Native(tnsnames.ora)或者JDBC。

确定之后进行reverse。具体需要的时间要根据数据库对象的多少来定
逆向工程之后的PDM 如下图
项目管理
2018-07-14 17:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
这几天面试互联网公司的高级java工程师,多线程问的相对而言比较多。所以,从各种角度来看看java中多线程的实现方式。
一.Java多线程中的内存模型
1.java主内存和工作内存
根据java内存模型,java中所有的变量都存储在主内存中(main memory),每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了主内存的副本拷贝,线程对所有变量的操作都必须在工作内存中进行而不能操作主内存的变量。不同的线程不能访问别的线程工作内存中的变量,线程之间的变量值传递必须要通过主内存来完成,线程、主内存、工作内存三者的交互关系如下图所示:
2.内存之间的交互操作
java内存模型定义了8种不同操作来完成,虚拟机实现时候必须保证这8种操作都是原子的、不可再分的。
lock(锁定):主要作用于主内存变量,它把一个变量标志为一条线程独占的状态。
unlock(解锁):作用于主内存变量,它将处于一个锁定状态的变量释放出来,释放出来的内存变量才能被其他线程锁定。
read(读取):作用于主内存的变量,它把一个变量的值从主内存中运输到线程的工作内存中,以便随后的load指令使用。
laod(载入):作用于工作内存的变量,它把主内存中读到的变量放入工作内存变量的副本中。
use(使用):作用于工作内存中的变量,它把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量值的字节码指令时候回执行这个操作。
assign(赋值):作用于工作内存变量,它把一个从执行引擎接受到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码的时候执行这个操作。
store(存储):作用于工作内存中的变量,它把工作内存中的一个变量送到主内存中,以便以后的write操作使用。
write(写入):作用于主内存中,它把store操作从工作内存中得到的变量的值放入主内存中。

2.多线程中的原子性、可见性、有序性
1.原子性
原子性:由java内存模型来直接保证原子性的操作包括read、load、assign、use、store和write。基本数据类型访问具有原子性(除了long 和double类型特殊的非原子协定)。
如果需要更大范围的原子性操作的时候,java内训模型还提供了lock()和unlock()操作来完成这种需求。在字节码层次上提供了monitorebter和moniterexit来隐式的使用这个操作,这两个字节码反映到java代码中就是同步代码块--sychronized关键字。
2.可见性
可见性:是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
java内存模型是通过变量在修改后的新值同步回主内存中,在变量读取前从主内存刷新变量值住,这种依赖 主内存作为传递媒介的方式来实现可见性的。通过volatile保证了线程之间的可见性。同样还有synchronized 和final 来实现线程可见性。
同步代码块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store 和 write操作)”。被final修饰的普通变量在构造器中一旦初始化完成,并且构造器没有把"this"关键字的引用传递出去,那么在其他线程中就能看见final关键字的值。
3.有序性
有序性:在java语言中提供了volatile和synchronized俩个关键字来保证线程之间操作是有序性,volatile关键字本身包含了禁止指令重排序的语义,而sychronized则是由“一个变量在同一时刻只允许一个线程进行lock操作”,这条规则决定了持有同一个锁的同步代码块只能串行地进入。
4、happen-before原则
项目管理
2018-07-14 16:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
什么是springboot
用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置(properties或yml文件)
创建独立的spring引用程序 main方法运行
嵌入的Tomcat 无需部署war文件
简化maven配置
自动配置spring添加对应功能starter自动化配置

springboot常用的starter有哪些
spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
spring-boot-starter-data-jpa 数据库支持
spring-boot-starter-data-redis redis数据库支持
spring-boot-starter-data-solr solr支持
mybatis-spring-boot-starter 第三方的mybatis集成starter

springboot自动配置的原理
在spring程序main方法中 添加@SpringBootApplication或者@EnableAutoConfiguration
会自动去maven中读取每个starter中的spring.factories文件 该文件里配置了所有需要被创建spring容器中的bean

springboot读取配置文件的方式
springboot默认读取配置文件为application.properties或者是application.yml

springboot集成mybatis的过程
添加mybatis的starter maven依赖

org.mybatis.spring.boot
mybatis-spring-boot-starter
1.2.0

在mybatis的接口中 添加@Mapper注解
在application.yml配置数据源信息

springboot如何添加【修改代码】自动重启功能
添加开发者工具集=====spring-boot-devtools

什么是微服务
以前的模式是 所有的代码在同一个工程中 部署在同一个服务器中 同一个项目的不同模块不同功能互相抢占资源
微服务 将工程根据不同的业务规则拆分成微服务 微服务部署在不同的机器上 服务之间进行相互调用
Java微服务的框架有 dubbo(只能用来做微服务),spring cloud(提供了服务的发现,断路器等)

springcloud如何实现服务的注册和发现
服务在发布时 指定对应的服务名(服务名包括了IP地址和端口) 将服务注册到注册中心(eureka或者zookeeper)
这一过程是springcloud自动实现 只需要在main方法添加@EnableDisscoveryClient 同一个服务修改端口就可以启动多个实例
调用方法:传递服务名称通过注册中心获取所有的可用实例 通过负载均衡策略调用(ribbon和feign)对应的服务

ribbon和feign区别
Ribbon添加maven依赖 spring-starter-ribbon 使用@RibbonClient(value="服务名称") 使用RestTemplate调用远程服务对应的方法
feign添加maven依赖 spring-starter-feign 服务提供方提供对外接口 调用方使用 在接口上使用@FeignClient("指定服务名")
Ribbon和Feign的区别:
Ribbon和Feign都是用于调用其他服务的,不过方式不同。
1.启动类使用的注解不同,Ribbon用的是@RibbonClient,Feign用的是@EnableFeignClients。
2.服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
3.调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。
Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,
不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。

springcloud断路器的作用
当一个服务调用另一个服务由于网络原因或者自身原因出现问题时 调用者就会等待被调用者的响应 当更多的服务请求到这些资源时
导致更多的请求等待 这样就会发生连锁效应(雪崩效应) 断路器就是解决这一问题
断路器有完全打开状态
一定时间内 达到一定的次数无法调用 并且多次检测没有恢复的迹象 断路器完全打开,那么下次请求就不会请求到该服务
半开
短时间内 有恢复迹象 断路器会将部分请求发给该服务 当能正常调用时 断路器关闭
关闭
当服务一直处于正常状态 能正常调用 断路器关闭
项目管理
2018-07-14 15:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
containsKey方法用来判断Map集合对象中是否包含指定的键名。
语法 boolean containsKey(Object key)
返回值:如果Map集合中包含指定的键名,则返回true;否则返回false。
参数:key是要查询的Map集合的键名对象。
本示例首先使用HashMap类创建Map集合对象,并向集合中添加几个元素,然后调用containsKey方法查询是否包含指定的键名。 public static void main(String[] args) { Map map = new HashMap(); //定义Map对象 map.put("apple", "新鲜的苹果"); //向集合中添加对象 map.put("computer", "配置优良的计算机"); map.put("book", "堆积成山的图书"); map.put("time", new Date()); String key = "book"; boolean contains = map.containsKey(key); //判断是否包含指定的键值 if (contains) { //如果条件为真 System.out.println("在Map集合中包含键名" + key); //输出信息 } else { System.out.println("在Map集合中不包含键名" + key); } }
你们都学会了吗
项目管理
2018-07-14 15:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportOperationException异常 说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。 String[] str = new String[]{"1","2"}; List list = Arrays.asList(str); 第一种情况:list.add("x");//运行时异常 第二种情况:str[0] = "unv";//那么list.get(0)也随着修改。 此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。 除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 NullPointerException。一段有意思的代码如下:
Java代码
public static void main(String[] args) {
int[] data = {1,2,3,4,5};
List list = Arrays.asList(data);
System.out.println("列表中的元素数量是:" + list.size());
} 注意这里输出的数量是1,原因是,在Arrays.asList中,该方法接受一个变长参数,一般可看做数组参数,但是因为int[] 本身就是一个类型,所以data变量作为参数传递时,编译器认为只传了一个变量,这个变量的类型是int数组,所以size为1。基本类型是不能作为泛型的参数,按道理应该使用包装类型,但这里缺没有报错,因为数组是可以泛型化的,所以转换后在list中就有一个类型为int的数组
Java代码 :
int[] data = {1,2,3,4,5};
List list = Arrays.asList(data);
System.out.println("元素类型:" + list.get(0).getClass());
System.out.println("前后是否相等:"+data.equals(list.get(0))); 可以看到, 输出的为元素类型:class [I 前后是否相等:true 因为jvm不可能输出array类型,array类型属于java.lang.reflect包,通过反射访问数组的这个类,编译时候生成的。所以要改为:
Java代码
Integer[] data = {1,2,3,4,5};
List list = Arrays.asList(data);
System.out.println("列表中的元素数量是:" + list.size()); 输出结果: 列表中的元素数量是:5

说明编译器对Integer[] 处理不一样。Integer是可变长参数。传入过程中asList()方法实际是将Integer数组里的元素进行存储。 此外,假如如下代码:
Java代码
//枚举,声明一个星期
enum Week{Sun,Mon, Tue, Wed,Thu,Fri,Sat}
public static void main(String[] args) {
//工作日
Week[] workDays = {Week.Mon, Week.Tue, Week.Wed,Week.Thu,Week.Fri};
//转换为列表
List list = Arrays.asList(workDays);
//增加周六也为工作日
list.add(Week.Sat); 注意这里用add会出错,因为是arrays.aslist中,看代码可以看到这里返回的ArrayList不是原来的传统意义上的java.util.arraylist了,而是自己工具类的一个静态私有内部类,并没有提供add方法,要自己实现,所以这里是出错了,因此,除非确信array.aslist后长度不会增加,否则谨慎使用:List abc=Arrays.asList("a","b","c"),因为这样的长度是无法再add的了.
项目管理
2018-07-14 15:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
集合判断:
  例1: 判断集合是否为空:
  CollectionUtils.isEmpty(null): true
  CollectionUtils.isEmpty(new ArrayList()): true  
  CollectionUtils.isEmpty({a,b}): false
例2: 判断集合是否不为空:
  CollectionUtils.isNotEmpty(null): false
  CollectionUtils.isNotEmpty(new ArrayList()): false
  CollectionUtils.isNotEmpty({a,b}): true
CollectionUtils在真实项目中,是一个非常好用的工具类,使用非常频繁。它可以使代码更加简洁和安全。刚好在工作中利用这个工具类重构代码,顺便总结下分享分享:
并集 @Test public void testUnion(){ String[] arrayA = new String[] { "A", "B", "C", "D", "E", "F" }; String[] arrayB = new String[] { "B", "D", "F", "G", "H", "K" }; List listA = Arrays.asList(arrayA); List listB = Arrays.asList(arrayB); //2个数组取并集 System.out.println(ArrayUtils.toString(CollectionUtils.union(listA, listB))); //[A, B, C, D, E, F, G, H, K] }
交集 @Test public void testIntersection(){ String[] arrayA = new String[] { "A", "B", "C", "D", "E", "F" }; String[] arrayB = new String[] { "B", "D", "F", "G", "H", "K" }; List listA = Arrays.asList(arrayA); List listB = Arrays.asList(arrayB); //2个数组取交集 System.out.println(ArrayUtils.toString(CollectionUtils.intersection(listA, listB))); //[B, D, F] }
交集的补集(析取) @Test public void testDisjunction(){ String[] arrayA = new String[] { "A", "B", "C", "D", "E", "F" }; String[] arrayB = new String[] { "B", "D", "F", "G", "H", "K" }; List listA = Arrays.asList(arrayA); List listB = Arrays.asList(arrayB); //2个数组取交集 的补集 System.out.println(ArrayUtils.toString(CollectionUtils.disjunction(listA, listB))); //[A, C, E, G, H, K] }
差集(扣除) @Test public void testSubtract(){ String[] arrayA = new String[] { "A", "B", "C", "D", "E", "F" }; String[] arrayB = new String[] { "B", "D", "F", "G", "H", "K" }; List listA = Arrays.asList(arrayA); List listB = Arrays.asList(arrayB); //arrayA扣除arrayB System.out.println(ArrayUtils.toString(CollectionUtils.subtract(listA, listB))); //[A, C, E] }
集合是否为空 @Test public void testIsEmpty(){ class Person{} class Girl extends Person{} List first = new ArrayList<>(); List second = null; List boy = new ArrayList<>(); //每个男孩心里都装着一个女孩 boy.add(new Girl()); //判断集合是否为空 System.out.println(CollectionUtils.isEmpty(first)); //true System.out.println(CollectionUtils.isEmpty(second)); //true System.out.println(CollectionUtils.isEmpty(boy)); //false //判断集合是否不为空 System.out.println(CollectionUtils.isNotEmpty(first)); //false System.out.println(CollectionUtils.isNotEmpty(second)); //false System.out.println(CollectionUtils.isNotEmpty(boy)); //true }

集合是否相等 @Test public void testIsEqual(){ class Person{} class Girl extends Person{ } List first = new ArrayList<>(); List second = new ArrayList<>(); first.add(1); first.add(2); second.add(2); second.add(1); Girl goldGirl = new Girl(); List boy1 = new ArrayList<>(); //每个男孩心里都装着一个女孩 boy1.add(new Girl()); List boy2 = new ArrayList<>(); //每个男孩心里都装着一个女孩 boy2.add(new Girl()); //比较两集合值 System.out.println(CollectionUtils.isEqualCollection(first,second)); //true System.out.println(CollectionUtils.isEqualCollection(first,boy1)); //false System.out.println(CollectionUtils.isEqualCollection(boy1,boy2)); //false List boy3 = new ArrayList<>(); //每个男孩心里都装着一个女孩 boy3.add(goldGirl); List boy4 = new ArrayList<>(); boy4.add(goldGirl); System.out.println(CollectionUtils.isEqualCollection(boy3,boy4)); //true }
不可修改的集合
我们对c进行操作,s也同样获得了和c相同的内容,这样就可以避免其他人员修改这个s对象。有时候需要对它进行保护,避免返回结果被人修改。 @Test public void testUnmodifiableCollection(){ Collection c = new ArrayList<>(); Collection s = CollectionUtils.unmodifiableCollection(c); c.add("boy"); c.add("love"); c.add("girl"); //! s.add("have a error"); System.out.println(s); }
Collections.unmodifiableCollection可以得到一个集合的镜像,它的返回结果是不可直接被改变,否则会提示错误 java.lang.UnsupportedOperationException at org.apache.commons.collections.collection.UnmodifiableCollection.add(UnmodifiableCollection.java:75)
项目管理
2018-07-14 15:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
…………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。
HandlerInterceptorAdapter public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,可以进行编码、安全控制等处理;
在postHandle中,有机会修改ModelAndView;
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。 //自定义注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface NeedLogin { String value() default ""; } //拦截器,继承HandlerInterceptorAdapter接口 public class LoginInterceptor extends HandlerInterceptorAdapter{ public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception { if(handler.getClass().isAssignableFrom(HandlerMethod.class)){//如果是spring mvc 方法 NeedLogin needLogin = ((HandlerMethod)handler).getMethodAnnotation(NeedLogin.class); if(needLogin!=null){ NewsUser u = (NewsUser) request.getSession().getAttribute("user"); if(u==null){ String rt = request.getHeader("X-Requested-With"); if(rt!=null && "XMLHttpRequest".equals(rt)){ JSONObject json = new JSONObject(); json.put("isLogin", false); response.getWriter().print(json.toString()); return false; }else{ response.sendRedirect(request.getContextPath()+"/news/index"); return false; } }else{ return true; } } } return true; } } 根据以上写法: 1.如果写了@NeedLogin注解,则调用方法之前会进行是否登录校验 2.未写,则不校验 @NeedLogin @ResponseBody @RequestMapping(value="addOrUpdate") public String addHistory(Long roomId, Long userId) { JSONObject obj = new JSONObject(); boolean flag = false; ZhiboWatchRecord zhiboWatchRecord = zhiboWatchRecordService .addOrUpdateToHistory(roomId, userId); if (zhiboWatchRecord != null) { flag = true; } obj.put("status", flag? 1:0); return obj.toString(); }
项目管理
2018-07-14 01:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
下面的部分告诉你如何在不同的系统中来设置原生的 WebDAV 客户端,这个客户端通常显示在你操作系统的文件浏览器中,例如,Windows 的 Windows Explorer 或者 Linux 的 Konqueror。
在 Mac OSX Finder 中访问 Confluence
你可以成功的连接,但是在使用 HTTPS 的时候你不能看到内容。针对 Confluence Cloud 来说,你不能使用 Mac OSX Finder 来访问 WebDAV,你可以使用第三方的 WebDAV 客户端来访问。
如何使用 Finder 来查看和管理 Confluence 内容: 在 Finder 选择 进入(Go) > 连接到服务器(Connect to Server) 输入下面格式的 URL:
http: ///plugins/servlet/confluence/default
针对上面的示例,你的 Confluence URL 是 http://ourconfluence.atlassian.net/wiki 的话,你需要输入下面的内容:
http: // ourconfluence.atlassian.net/wiki/plugins/servlet/confluence/default
输入你的 Confluence 用户名和密码,然后单击 连接(Connect)
使用的用户名(jsmith),不要使用你的电子邮件地址,除非你是用电子邮件地址作为你的用户名的。
Confluence 将会显示在 Finder 的分享目录中。你可以使用相同的 URL 来用第三方的客户端来连接 Confluence 实例,例如 CyberDuck。
在 Microsoft Windows 中用 Explorer 访问 Confluence
这部分我们有下面 2 个方法来配置你的 Microsoft Windows 访问 WebDAV 客户端: 通过一个网络驱动器 通过一个 Web 文件夹
如果可能的话,使用网络驱动的方法对比使用 Web 文件夹的方法来说能够提供更多的功能,也更加全面。然而,你的 Confluence 安装实例必须满足一些特定的要求才能满足使用网络驱动的方法进行访问。如果你配置的 Confluence 安装实例不能满足使用网络驱动器访问 Confluence 内容的要求的时候,你可以使用 web 文件夹的方法或者第三方的 WebDAV 客户端。
如果你使用了 SSL,你需要将 @SSL 参数配置添加到你服务器的 URL 中,例如:
http: //@SSL/confluence/plugins/servlet/confluence/default
如果你在配置的时候遇到了任何问题,请参考 WebDAV Troubleshooting 页面中的内容。
Windows 网络驱动器
为了将 Confluence WebDAV 客户端映射为网络驱动器,你 Confluence 的安装实例必须满足下所有的配置要求: 没有上下文的根(context root) 如果你在映射为网络驱动的时候,有配置阻止你成功进行配置,你可以配置使用下面的步骤来配置 网络文件夹(Network Folders)。
有这些限制的原因是因为 Microsoft's Mini-Redirector 组件的限制。有关更多的问题,请参考微软的 server discovery issue 。
希望在 Microsoft Window 中映射 WebDAV 客户端网络: 在 Window XP 中,进入 My Computer -> Tools menu -> Map Network Drive
在 Windows Vista 中,进入 Computer -> Map Network Drive 指定下面的 2 个配置参数来映射 WebDAV 客户端: Drive: (例如,z:) Folder: \\\webdav (例如 \\localhost\webdav ) 单击 完成(Finish)
当需要你输入 Confluence 的登录信息的时候,输入你 Confluence 的用户名和密码。
Windows Web 文件夹
希望在 Microsoft Window XP 中映射 WebDAV 客户端为 Web 文件夹: 进入 My Network Places 然后选择 Add a network place 然后单击 Next 请确定 Choose another network location 已被选择了,然后单击 Next 继续 在 'Internet or network address' 字段中,输入 Confluence WebDAV 的 URL 路径(例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 Next 如果你的 Confluence 用户名和密码 为你的 Web 文件夹提供一个有意义的名字 单击 完成(Finish)
屏幕截图:一个在 Windows XP 中的 Confluence WebDAV 客户端 Web 文件夹
希望在 Microsoft Window Vista 中映射 WebDAV 客户端为 Web 文件夹:
在 Vista 映射的过程与 XP 的映射过程非常相似。然后有关的界面与 XP 比起来还是有点不同的: 打开 'Map Network Drive' 对话框(请参考上面的映射网络驱动器的步骤)然后选择 ' Connect to a Web site that you can use to store your documents and pictures ' 单击 ' Next ' 输入选择的 Choose a custom network location ,然后单击 Next 继续 在 'Internet or network address' 字段中,输入 Confluence WebDAV 的 URL 路径(例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 Next 输入你的 Confluence 用户名和密码 为你的 Web 文件夹提供一个有意义的名字 单击 完成(Finish)
在 Linux 或 Solaris 中设置 WebDAV 客户端
在这 2 个操作系统中有很多工具可以被用来配置 WebDAV 客户端。因此,我们使用 Konqueror 来管理,这个是 K Desktop Environment 的一部分。
希望在 Konqueror 中设置 Confluence WebDAV 客户端: 打开 Konqueror 在 'Location' 字段,输入 Confluence WebDAV 的位置,使用 'protocol' 为 webdavs (例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 回车( Enter )。 如果弹出对话框,输入你的 Confluence 用户名和密码
你应该可以单击来载入很多文件,但是并不会载入所有的文件。在通常情况下,你应该保存修改了的文件夹在本地,然后拖到 Konqueror 窗口中上传到 Confluence。

https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-14 00:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MySQL 3.x is 已知在大写和小写转换的时候有些问题(non-ASCII)。
问题诊断 请按照 Troubleshooting Character Encodings 页面中的内容对问题进行诊断。 如果大小写字符串编码诊断显示不同,那么有可能是你数据库导致的。
一个错误的数据库大小写问题诊断输入如下:
屏幕截图:编码测试输出(异常)
解决方案
升级到新版本的 MySQL(MySQL 4.1 已经确定这个问题被解决了。)
https://www.cwiki.us/display/CONF6ZH/MySQL+3.x+Character+Encoding+Problems
项目管理
2018-07-14 00:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence 为订阅者发送常规邮件报告,这个邮件报告中包含有用户具有查看权限的空间的最新的内容。这个被称为 推荐更新(Recommended Updates)通知。
如果你具有 Confluence 管理员或者系统管理员 权限 ,你可以对默认的配置进行修改来确定推荐更新多长时间发送一次。当一个新用户添加到 Confluence 中后,这个默认的设置将会应用到这个新添加用户的用户属性中。
Confluence 的用户也可以对他们的个人设置进行修改,这个修改可以重置系统的默认修改。请参考 Email Notifications 页面中的内容。
默认的初始化设置
当你安装 Confluence 的时候,初始变量的默认值如下: 默认的发送频率为每周。 如果你的 Confluence 站点允许公众自行注册,那么在默认的情况推荐的邮件更新是禁用的。如果你的 Confluence 站点不允许公众注册,那么这个通知是启用的,并且使用的是默认的发送频率配置。
你可以对上面的设置进行修改,为你的站点指定不同的默认值。
备注: 推荐更新只会对在 Confluence 有用户属性的用户进行发送。如果你的 Confluence 站点使用的是外部用户管理,例如 LDAP,那么用户将会受到推荐更新在用户第一次登录使用系统之后(第一次登录将会为用户创建用户属性)。 每天的电子邮件更新将会在用户配置时区的下午 1 点 (1 PM)发送。 每天的电子邮件更新将会在用户配置时区的每周四下午 1 点 (1 PM)发送。
https://www.cwiki.us/display/CONF6ZH/Configuring+the+Recommended+Updates+Email+Notification
项目管理
2018-07-14 00:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在取消点赞事件中,你可能会遇到 WebDAV 客户端的问题或者不稳定的情况,你可以启用访问自动创建(虚拟)文件和文件夹。
备注:
在默认情况下,这个选项隐藏在 'WebDAV Configuration' 页面中。为了让这个可见,添加参数 ?hiddenOptionsEnabled=true 到你的 URL 的结尾,然后重新载入页面。例如:
/admin/plugins/webdav/ config.action?hiddenOptionsEnabled= true
屏幕截图:隐藏虚拟文件和文件夹选项

希望启用或者禁用虚拟文件夹选项: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 在左侧边栏中选择 WebDav 配置(WebDav Configuration') 。 添加你的 URL 按照上面提示的说明,然后重新载入 'WebDav Configuration' 页面。 选择你希望取消的虚拟文件夹选项 'Virtual Files and Folders' ,将选择对话框取消选择就可以了。 单击 保存(Save)。
https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-14 00:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你在你的 WebDAV 客户端发现了一些不正常的现象,例如文件夹在 Confluence 中是存在的,但是在你客户端下载的文件中就不存在了。你可以禁用 WebDAV 插件中的严格路径检查选项,这个选项能够帮你缩小你遇到的问题。
希望禁用 WebDAV 插件中的严格路径检查选项: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 在左侧边栏中选择 WebDav 配置(WebDav Configuration') 。 取消选择 'Disable strict path check' 选择对话框。 单击 保存( Save )。
https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-14 00:02:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在早期的 WebDAV 插件中分离了 WebDAV 客户端的写入权限(不能使用,创建/修改,编辑和删除操作)是分开配置的。但是在新版版本的插件中,我们将这些权限合并到了一起。
WebDAV 客户端现在被显示了写入权限通过设置正则表达式来匹配你 WebDAV 客户的用户的头。通过设置正则表达式,我们将会把一系列的限制列表限制到 WebDAV 的客户端中。任何用户如果满足了匹配的头部,将会被限制写入权限。
示例:一个 PROPFIND 方法的头部,这个是由 Microsoft Web Folder WebDAV 客户端创建,显示在 user agent 头部的字段中:
PROPFIND /plugins/servlet/confluence/ default HTTP/ 1.1
Content-Language: en-us
Accept-Language: en-us
Content-Type: text/xml
Translate: f
Depth: 1
Content-Length: 489
User-Agent: Microsoft Data Access Internet Publishing Provider DAV
Host: 127.0 . 0.1 : 8082
Connection: Keep-Alive
与早期版本的 WebDAV 插件不同,你只能所有 WebDAV 客户端的权限,当前的配置能够允许你针对不同的客户端进行匹配。
限制 WebDAV 客户端向你安装的 Confluence 的写入权限: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 在左侧边栏中选择 WebDav 配置(WebDav Configuration') 。 输入正则表达式来满足你 user agent 的头部,这个是由你期望限制的 WebDAV 客户端发送的。 单击 添加新正则表达式(Add new regex) 按钮。
重复上面的第三和第四步,对你期望进行限制的其他 WebDAV 客户端。 单击 保存(Save) 。
在你 Confluence 的安装实例中,恢复一个或者多个限制的 WebDAV 客户端: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 在左侧边栏中选择 WebDav 配置(WebDav Configuration') 。 在正则表达式列表中,选择你希望恢复的 WebDAV 客户端。 单击 删除选择的正则表达式(Remove selected regexes) 按钮。 单击 保存(Save)。
屏幕截图:WebDAV 配置

https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-13 23:58:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
下面的部分告诉你如何在不同的系统中来设置原生的 WebDAV 客户端,这个客户端通常显示在你操作系统的文件浏览器中,例如,Windows 的 Windows Explorer 或者 Linux 的 Konqueror。
在 Mac OSX Finder 中访问 Confluence
你可以成功的连接,但是在使用 HTTPS 的时候你不能看到内容。针对 Confluence Cloud 来说,你不能使用 Mac OSX Finder 来访问 WebDAV,你可以使用第三方的 WebDAV 客户端来访问。
如何使用 Finder 来查看和管理 Confluence 内容: 在 Finder 选择 进入(Go) > 连接到服务器(Connect to Server) 输入下面格式的 URL:
http: ///plugins/servlet/confluence/default
针对上面的示例,你的 Confluence URL 是 http://ourconfluence.atlassian.net/wiki 的话,你需要输入下面的内容:
http: // ourconfluence.atlassian.net/wiki/plugins/servlet/confluence/default
输入你的 Confluence 用户名和密码,然后单击 连接(Connect)
使用的用户名(jsmith),不要使用你的电子邮件地址,除非你是用电子邮件地址作为你的用户名的。
Confluence 将会显示在 Finder 的分享目录中。你可以使用相同的 URL 来用第三方的客户端来连接 Confluence 实例,例如 CyberDuck。
在 Microsoft Windows 中用 Explorer 访问 Confluence
这部分我们有下面 2 个方法来配置你的 Microsoft Windows 访问 WebDAV 客户端: 通过一个网络驱动器 通过一个 Web 文件夹
如果可能的话,使用网络驱动的方法对比使用 Web 文件夹的方法来说能够提供更多的功能,也更加全面。然而,你的 Confluence 安装实例必须满足一些特定的要求才能满足使用网络驱动的方法进行访问。如果你配置的 Confluence 安装实例不能满足使用网络驱动器访问 Confluence 内容的要求的时候,你可以使用 web 文件夹的方法或者第三方的 WebDAV 客户端。
如果你使用了 SSL,你需要将 @SSL 参数配置添加到你服务器的 URL 中,例如:
http: //@SSL/confluence/plugins/servlet/confluence/default
如果你在配置的时候遇到了任何问题,请参考 WebDAV Troubleshooting 页面中的内容。
Windows 网络驱动器
为了将 Confluence WebDAV 客户端映射为网络驱动器,你 Confluence 的安装实例必须满足下所有的配置要求: 没有上下文的根(context root) 如果你在映射为网络驱动的时候,有配置阻止你成功进行配置,你可以配置使用下面的步骤来配置 网络文件夹(Network Folders)。
有这些限制的原因是因为 Microsoft's Mini-Redirector 组件的限制。有关更多的问题,请参考微软的 server discovery issue 。
希望在 Microsoft Window 中映射 WebDAV 客户端网络: 在 Window XP 中,进入 My Computer -> Tools menu -> Map Network Drive
在 Windows Vista 中,进入 Computer -> Map Network Drive 指定下面的 2 个配置参数来映射 WebDAV 客户端: Drive: (例如,z:) Folder: \\\webdav (例如 \\localhost\webdav ) 单击 完成(Finish)
当需要你输入 Confluence 的登录信息的时候,输入你 Confluence 的用户名和密码。
Windows Web 文件夹
希望在 Microsoft Window XP 中映射 WebDAV 客户端为 Web 文件夹: 进入 My Network Places 然后选择 Add a network place 然后单击 Next 请确定 Choose another network location 已被选择了,然后单击 Next 继续 在 'Internet or network address' 字段中,输入 Confluence WebDAV 的 URL 路径(例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 Next 如果你的 Confluence 用户名和密码 为你的 Web 文件夹提供一个有意义的名字 单击 完成(Finish)
屏幕截图:一个在 Windows XP 中的 Confluence WebDAV 客户端 Web 文件夹
希望在 Microsoft Window Vista 中映射 WebDAV 客户端为 Web 文件夹:
在 Vista 映射的过程与 XP 的映射过程非常相似。然后有关的界面与 XP 比起来还是有点不同的: 打开 'Map Network Drive' 对话框(请参考上面的映射网络驱动器的步骤)然后选择 ' Connect to a Web site that you can use to store your documents and pictures ' 单击 ' Next ' 输入选择的 Choose a custom network location ,然后单击 Next 继续 在 'Internet or network address' 字段中,输入 Confluence WebDAV 的 URL 路径(例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 Next 输入你的 Confluence 用户名和密码 为你的 Web 文件夹提供一个有意义的名字 单击 完成(Finish)
在 Linux 或 Solaris 中设置 WebDAV 客户端
在这 2 个操作系统中有很多工具可以被用来配置 WebDAV 客户端。因此,我们使用 Konqueror 来管理,这个是 K Desktop Environment 的一部分。
希望在 Konqueror 中设置 Confluence WebDAV 客户端: 打开 Konqueror 在 'Location' 字段,输入 Confluence WebDAV 的位置,使用 'protocol' 为 webdavs (例如, http:///confluence/plugins/servlet/confluence/default 或 http:///plugins/servlet/confluence/default )然后单击 回车( Enter )。 如果弹出对话框,输入你的 Confluence 用户名和密码
你应该可以单击来载入很多文件,但是并不会载入所有的文件。在通常情况下,你应该保存修改了的文件夹在本地,然后拖到 Konqueror 窗口中上传到 Confluence。

https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-13 23:54:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
WebDAV 允许用户通过一个 WebDAV 客户端来访问 Confluence。例如,微软 Windows 的 'My Network Places'。通过为访问的用户提供权限,这个用户可以在 Confluence 中阅读和写入空间,页面附件。用户将会被要求在访问之前登录和基本的 Confluence 访问权限将会应用到用户使用的客户端上。
映射 Confluence WebDAV 网络驱动器,要求必须满足一些要求。有关特定的要求,请参考 Windows Network Drive Requirements 中的内容。
Confluence 的 WebDAV 客户端整合介绍
在默认的情况下,所有的 WebDAV 客户端都具有向 Confluence 写入内容的权限。写入的权限包括有通过 WebDAV 客户端来在连接的 Confluence 中创建,编辑或者删除空间,页面和附件。
在 Confluence 管理员控制台的 WebDAV 配置(WebDAV Configuration) 界面,你可以: Deny a WebDAV client write permissions to a Confluence installation 使用正则表达式(regex) Disable or enable strict path checking Enable or disable access to specific virtual files/folders
备注: WebDAV 配置(WebDAV Configuration)页面仅仅在 WebDAV 插件启用后才会看到。这个插件是与 Confluence 安装绑定在一起的。系统管理员可以启用或者禁用这个插件。 WebDAV 配置(WebDAV Configuration)的配置 不能应用 到外部附件存储配置。

https://www.cwiki.us/display/CONF6ZH/Configuring+a+WebDAV+client+for+Confluence
项目管理
2018-07-13 23:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
@Column(name="price", nullable=false, precision = 23, scale = 6)
private BigDecimal price;
@Column(name="num" ,columnDefinition="int(9) default 2 comment '提现金额'")
public int num;
@Column(name="f_colum" ,columnDefinition="float(10,2)")
public float fColum;
项目管理
2018-07-13 17:24:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
萌新今天上手app了.. 有点小激动,但是app大佬们要求返回的所有参数都是String 类型的,就写了一个奇葩的工具类,自测了一下还可以用,一听老大说出了BUG责任自负,果断扔了。。不过正好练练反射,还蛮好的~ /** * 反射处理Bean 获取对象的属性值 * * @author hao.wei * @date 2018/7/12 */ public class ReflexObjectUtil { private static List packagingClass = new ArrayList<>(); static { packagingClass.add(Byte.class); packagingClass.add(Short.class); packagingClass.add(Integer.class); packagingClass.add(Long.class); packagingClass.add(Float.class); packagingClass.add(Double.class); packagingClass.add(Character.class); packagingClass.add(Boolean.class); packagingClass.add(String.class); } /** * 将指定对象的属性 全部转换成 目标对象的String类型 * * @param sourceObj 来源对象 * @param desObj 目标对象 * @return */ public static Object sourceObjFieldToDesObjFieldStr(Object sourceObj, Object desObj) { // 获取来源类对象 Class sourceObjClass = sourceObj.getClass(); /* 来源类中的所有属性集合 */ Field[] sourceObjClassDeclaredFields = sourceObjClass.getDeclaredFields(); // 获取目标类对象 Class desObjClass = desObj.getClass(); /* 目标类中的所有属性集合 */ Field[] desObjClassDeclaredFields = desObjClass.getDeclaredFields(); for (int i = 0; i < sourceObjClassDeclaredFields.length; i++) { Field sourceObjClassDeclaredField = sourceObjClassDeclaredFields[i]; // 设置属性可访问 sourceObjClassDeclaredField.setAccessible(true); // 获取来源类的属性值 Object sourceVal = null; try { sourceVal = sourceObjClassDeclaredField.get(sourceObj); } catch (IllegalAccessException e) { e.printStackTrace(); } if (null !=sourceVal) { for (int j = 0; j < desObjClassDeclaredFields.length; j++) { try { Field desObjClassDeclaredField = desObjClassDeclaredFields[j]; // 设置属性可访问 desObjClassDeclaredField.setAccessible(true); // 属性名相同 if (desObjClassDeclaredField.getName().equals(sourceObjClassDeclaredField.getName())) { // 获取来源类属性的对象类型 Class type = sourceObjClassDeclaredField.getType(); // 如果是基本类型/包装类型/BigDecimal 直接转换为 String if (type.isPrimitive() || packagingClass.contains(type)|| type.equals(BigDecimal.class)) { sourceVal = String.valueOf(sourceVal); } else if (type.equals(Date.class)) { Date date = (Date) sourceVal; sourceVal = ThreadSafeDateUtil.format(date, "yyyy-MM-dd HH:mm:ss"); } // 设置目标类的属性值 desObjClassDeclaredField.set(desObj, sourceVal); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } return desObj; } /** * 单个对象的某个键的值 * * @param obj 对象 * @param key 键 * @return Object 键在对象中所对应得值 没有查到时返回空字符串 */ public static Object getValueByKey(Object obj, String key) { // 得到类对象 Class userCla = (Class) obj.getClass(); /* 得到类中的所有属性集合 */ Field[] fs = userCla.getDeclaredFields(); for (int i = 0; i < fs.length; i++) { Field f = fs[i]; f.setAccessible(true); // 设置些属性是可以访问的 try { if (f.getName().endsWith(key)) { System.out.println("单个对象的某个键的值==反射==" + f.get(obj)); return f.get(obj); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } // 没有查到时返回空字符串 return ""; } /** * 多个(列表)对象的所有键值 * * @param object * @return */ public static List> getKeysAndValues(List object) { List> list = new ArrayList>(); for (Object obj : object) { Class userCla; // 得到类对象 userCla = (Class) obj.getClass(); /* 得到类中的所有属性集合 */ Field[] fs = userCla.getDeclaredFields(); Map listChild = new HashMap(); for (int i = 0; i < fs.length; i++) { Field f = fs[i]; f.setAccessible(true); // 设置些属性是可以访问的 Object val = new Object(); try { val = f.get(obj); // 得到此属性的值 listChild.put(f.getName(), val);// 设置键值 } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } list.add(listChild);// 将map加入到list集合中 } System.out.println("多个(列表)对象的所有键值====" + list.toString()); return list; } /** * 多个(列表)对象的某个键的值 * * @param object * @param key * @return */ public static List getValuesByKey(List object, String key) { List list = new ArrayList(); for (Object obj : object) { // 得到类对象 Class userCla = (Class) obj.getClass(); /* 得到类中的所有属性集合 */ Field[] fs = userCla.getDeclaredFields(); for (int i = 0; i < fs.length; i++) { Field f = fs[i]; f.setAccessible(true); // 设置些属性是可以访问的 try { if (f.getName().endsWith(key)) { list.add(f.get(obj)); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } System.out.println("多个(列表)对象的某个键的值列表====" + list.toString()); return list; } }
项目管理
2018-07-13 15:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
启动顺序:1。licationEnvironmentPreparedEventListener @Slf4j public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { log.info("......ApplicationEnvironmentPreparedEvent......"); } }
2.ApplicationPreparedEventListener @Slf4j public class ApplicationPreparedEventListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationPreparedEvent event) { log.info("......ApplicationPreparedEvent......"); } }
3.ApplicationStartedEventListener @Slf4j public class ApplicationStartedEventListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationStartedEvent event) { log.info("......ApplicationStartedEvent......"); } }
4.Application$DataLoader @Slf4j @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public DataLoader dataLoader() { return new DataLoader(); } @Slf4j static class DataLoader implements CommandLineRunner { @Override public void run(String... strings) throws Exception { log.info("Loading data..."); } } }
5.ApplicationReadyEventListener @Slf4j public class ApplicationReadyEventListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationReadyEvent event) { log.info("......ApplicationReadyEvent......"); } }
项目管理
2018-07-13 14:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1. 概述
本文对Hystrix的配置参数的覆盖优先级,可配置参数的种类、配置项进行详细的介绍。
Hystrix可以配置属性的有以下类型: Execution:控制HystrixCommand.run() 的如何执行 Fallback: 控制HystrixCommand.getFallback() 如何执行 Circuit Breaker: 控制断路器的行为 Metrics: 捕获和HystrixCommand 和 HystrixObservableCommand 执行信息相关的配置属性 Request Context:设置请求上下文的属性 Collapser Properties:设置请求合并的属性 Thread Pool Properties:设置线程池的属性
2. Hystrix参数的覆盖优先级
每个Hystrix参数都有4个地方可以配置,优先级从低到高如下,如果每个地方都配置相同的属性,则优先级高的值会覆盖优先级低的值 1 内置全局默认值:写死在代码里的值 2 动态全局默认属性:通过属性文件配置全局的值 3 内置实例默认值:写死在代码里的实例的值 4 动态配置实例属性:通过属性文件配置特定实例的值
3 Hystrix配置属性详解
Hystrix可以配置属性的有以下类型: Execution:控制HystrixCommand.run() 的如何执行 Fallback: 控制HystrixCommand.getFallback() 如何执行 Circuit Breaker: 控制断路器的行为 Metrics: 捕获和HystrixCommand 和 HystrixObservableCommand 执行信息相关的配置属性 Request Context:设置请求上下文的属性 Collapser Properties:设置请求合并的属性 Thread Pool Properties:设置线程池的属性
3.1. Execution
以下属性控制HystrixCommand.run() 的如何执行
1. execution.isolation.strategy
表示HystrixCommand.run()的执行时的隔离策略,有以下两种策略 1 THREAD: 在单独的线程上执行,并发请求受线程池中的线程数限制 2 SEMAPHORE: 在调用线程上执行,并发请求量受信号量计数限制
在默认情况下,推荐HystrixCommands 使用 thread 隔离策略,HystrixObservableCommand 使用 semaphore 隔离策略。
只有在高并发(单个实例每秒达到几百个调用)的调用时,才需要修改HystrixCommands 的隔离策略为semaphore 。semaphore 隔离策略通常只用于非网络调用
默认值:THREAD, // 设置所有实例的默认值 hystrix.command.default.execution.isolation.strategy=.. // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.isolation.strategy=...
2. execution.isolation.thread.timeoutInMilliseconds
设置调用者执行的超时时间(单位毫秒)
默认值:1000 // 设置所有实例的默认值 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds=...
3. execution.timeout.enabled
表示是否开启超时设置。
默认值:true // 设置所有实例的默认值 hystrix.command.default.execution.timeout.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.timeout.enabled=...
4. execution.isolation.thread.interruptOnTimeout
表示设置是否在执行超时时,中断HystrixCommand.run() 的执行
默认值:true // 设置所有实例的默认值 hystrix.command.default.execution.isolation.thread.interruptOnTimeout=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.isolation.thread.interruptOnTimeout=...
5. execution.isolation.thread.interruptOnCancel
表示设置是否在取消任务执行时,中断HystrixCommand.run() 的执行
默认值:false // 设置所有实例的默认值 hystrix.command.default.execution.isolation.thread.interruptOnCancel=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.isolation.thread.interruptOnCancel
6. execution.isolation.semaphore.maxConcurrentRequests
当HystrixCommand.run()使用SEMAPHORE的隔离策略时,设置最大的并发量
默认值:10 // 设置所有实例的默认值 hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.execution.isolation.semaphore.maxConcurrentRequests=...
3.2. Fallback
以下属性控制HystrixCommand.getFallback() 如何执行。这些属性对隔离策略THREAD 和SEMAPHORE都起作用.
1. fallback.isolation.semaphore.maxConcurrentRequests
此属性设置从调用线程允许HystrixCommand.getFallback()方法允许的最大并发请求数
如果达到最大的并发量,则接下来的请求会被拒绝并且抛出异常.
默认值:10 // 设置所有实例的默认值 hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.fallback.isolation.semaphore.maxConcurrentRequests=...
2. fallback.enabled
是否开启fallback功能
默认值:true // 设置所有实例的默认值 hystrix.command.default.fallback.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.fallback.enabled=...
3.3. Circuit Breaker
控制断路器的行为
1. circuitBreaker.enabled
是否开启断路器功能
默认值:true // 设置所有实例的默认值 hystrix.command.default.circuitBreaker.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.enabled=...
2. circuitBreaker.requestVolumeThreshold
该属性设置滚动窗口中将使断路器跳闸的最小请求数量
如果此属性值为20,则在窗口时间内(如10s内),如果只收到19个请求且都失败了,则断路器也不会开启。
默认值:20 // 设置所有实例的默认值 hystrix.command.default.circuitBreaker.requestVolumeThreshold=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.requestVolumeThreshold=...
3. circuitBreaker.sleepWindowInMilliseconds
断路器跳闸后,在此值的时间的内,hystrix会拒绝新的请求,只有过了这个时间断路器才会打开闸门
默认值:5000 // 设置所有实例的默认值 hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.sleepWindowInMilliseconds=...
4. circuitBreaker.errorThresholdPercentage
设置失败百分比的阈值。如果失败比率超过这个值,则断路器跳闸并且进入fallback逻辑
默认值:50 // 设置所有实例的默认值 hystrix.command.default.circuitBreaker=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.errorThresholdPercentage=...
5. circuitBreaker.forceOpen
如果设置true,则强制使断路器跳闸,则会拒绝所有的请求.此值会覆盖circuitBreaker.forceClosed的值
默认值:false // 设置所有实例的默认值 hystrix.command.default.circuitBreaker.forceOpen=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.forceOpen=...
6. circuitBreaker.forceClosed
如果设置true,则强制使断路器进行关闭状态,此时会允许执行所有请求,无论是否失败的次数达到circuitBreaker.errorThresholdPercentage值
默认值:false // 设置所有实例的默认值 hystrix.command.default.circuitBreaker.forceClosed=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.circuitBreaker.forceClosed=...
3.4. Metrics
捕获和HystrixCommand 和 HystrixObservableCommand 执行信息相关的配置属性
1. metrics.rollingStats.timeInMilliseconds
设置统计滚动窗口的时间长度
如果此值为10s,将窗口分成10个桶,每个桶表示1s时间,则统计信息如下图:
默认值: 10000 // 设置所有实例的默认值 hystrix.command.default.metrics.rollingStats.timeInMilliseconds=.... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingStats.timeInMilliseconds=...
2. metrics.rollingStats.numBuckets
设置统计滚动窗口的桶数量,
注意:以下配置必须成立,否则会抛出异常。 metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0
如:10000/10、10000/20是正确的配置,但是10000/7错误的
在高并发的环境里,每个桶的时间长度建议大于100ms
默认值:10 // 设置所有实例的默认值 hystrix.command.default.metrics.rollingStats.numBuckets=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingStats.numBuckets=...
3. metrics.rollingPercentile.enabled
设置执行延迟是否被跟踪,并且被计算在失败百分比中。如果设置为false,则所有的统计数据返回-1
默认值: true // 设置所有实例的默认值 hystrix.command.default.metrics.rollingPercentile.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingPercentile.enabled=...
4. metrics.rollingPercentile.timeInMilliseconds
此属性设置统计滚动百分比窗口的持续时间
默认值:60000 // 设置所有实例的默认值 hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingPercentile.timeInMilliseconds=...
5. metrics.rollingPercentile.numBuckets
设置统计滚动百分比窗口的桶数量
注意:以下配置必须成立,否则会抛出异常。 metrics.rollingPercentile.timeInMilliseconds % metrics.rollingPercentile.numBuckets == 0
如: 60000/6、60000/60是正确的配置,但是10000/7错误的
在高并发的环境里,每个桶的时间长度建议大于1000ms
默认值:6 // 设置所有实例的默认值 hystrix.command.default.metrics.rollingPercentile.numBuckets=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingPercentile.numBuckets=...
6. metrics.rollingPercentile.bucketSize
此属性设置每个桶保存的执行时间的最大值。如果桶数量是100,统计窗口为10s,如果这10s里有500次执行,只有最后100次执行会被统计到bucket里去
默认值:100 // 设置所有实例的默认值 hystrix.command.default.metrics.rollingPercentile.bucketSize=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.rollingPercentile.bucketSize=...
7. metrics.healthSnapshot.intervalInMilliseconds
采样时间间隔
默认值:500 // 设置所有实例的默认值 hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.metrics.healthSnapshot.intervalInMilliseconds=...
3.5. Request Context
此属性控制HystrixCommand使用到的Hystrix的上下文
1. requestCache.enabled
是否开启请求缓存功能
默认值:true // 设置所有实例的默认值 hystrix.command.default.requestCache.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.requestCache.enabled=...
2. requestLog.enabled
表示是否开启日志,打印执行HystrixCommand的情况和事件
默认值:true // 设置所有实例的默认值 hystrix.command.default.requestLog.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.command.HystrixCommandKey.requestLog.enabled=...
3.6. Collapser Properties
设置请求合并的属性
1. maxRequestsInBatch
设置同时批量执行的请求的最大数量
默认值:Integer.MAX_VALUE // 设置所有实例的默认值 hystrix.collapser.default.maxRequestsInBatch=... // 设置实例HystrixCommandKey的此属性值 hystrix.collapser.HystrixCollapserKey.maxRequestsInBatch=...
2. timerDelayInMilliseconds
批量执行创建多久之后,再触发真正的请求
默认值:10 // 设置所有实例的默认值 hystrix.collapser.default.timerDelayInMilliseconds=... // 设置实例HystrixCommandKey的此属性值 hystrix.collapser.HystrixCollapserKey.timerDelayInMilliseconds=...
3. requestCache.enabled
是否对HystrixCollapser.execute() 和 HystrixCollapser.queue()开启请求缓存
默认值:true // 设置所有实例的默认值 hystrix.collapser.default.requestCache.enabled=... // 设置实例HystrixCommandKey的此属性值 hystrix.collapser.HystrixCollapserKey.requestCache.enabled=...
3.7. Thread Pool Properties
设置Hystrix Commands的线程池行为,大部分情况线程数量是10。
线程池数量的计算公式如下: 最高峰时每秒的请求数量 × 99%命令执行时间 + 喘息空间
设置线程池数量的主要原则是保持线程池越小越好,因为它是减轻负载并防止资源在延迟发生时被阻塞的主要工具
1. coreSize
设置线程池的core的大小
默认值:10 // 设置所有实例的默认值 hystrix.threadpool.default.coreSize=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.coreSize=...
2. maximumSize
设置最大的线程池的大小,只有设置allowMaximumSizeToDivergeFromCoreSize时,此值才起作用
默认值:10 // 设置所有实例的默认值 hystrix.threadpool.default.maximumSize=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.maximumSize=...
3. maxQueueSize
设置最大的BlockingQueue队列的值。如果设置-1,则使用SynchronousQueue队列,如果设置正数,则使用LinkedBlockingQueue队列
默认值:-1 // 设置所有实例的默认值 hystrix.threadpool.default.maxQueueSize=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.maxQueueSize=...
4. queueSizeRejectionThreshold
因为maxQueueSize值不能被动态修改,所有通过设置此值可以实现动态修改等待队列长度。即等待的队列的数量大于queueSizeRejectionThreshold时(但是没有达到maxQueueSize值),则开始拒绝后续的请求进入队列。
如果设置-1,则属性不启作用
默认值:5 // 设置所有实例的默认值 hystrix.threadpool.default.queueSizeRejectionThreshold=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.queueSizeRejectionThreshold=...
5. keepAliveTimeMinutes
设置线程多久没有服务后,需要释放(maximumSize-coreSize )个线程
默认值:1 // 设置所有实例的默认值 hystrix.threadpool.default.keepAliveTimeMinutes=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.keepAliveTimeMinutes=...
6. allowMaximumSizeToDivergeFromCoreSize
设置allowMaximumSizeToDivergeFromCoreSize值为true时,maximumSize才有作用
默认值:false // 设置所有实例的默认值 hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize=.... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.allowMaximumSizeToDivergeFromCoreSize=...
7. metrics.rollingStats.timeInMilliseconds
设置滚动窗口的时间
默认值:10000 // 设置所有实例的默认值 hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds=true // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolKey.metrics.rollingStats.timeInMilliseconds=true
8. metrics.rollingStats.numBuckets
设置滚动静态窗口分成的桶的数量
配置的值必须满足如下条件: metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0
默认值:10
建议每个桶的时间长度大于100ms // 设置所有实例的默认值 hystrix.threadpool.default.metrics.rollingStats.numBuckets=... // 设置实例HystrixCommandKey的此属性值 hystrix.threadpool.HystrixThreadPoolProperties.metrics.rollingStats.numBuckets=...
项目管理
2018-07-13 10:55:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
第一步:源码分析 看看大牛是如何写代码的,提升技术审美,提升核心竞争力。(重点)

第二步:工程化 工欲善其事必先利其器,不管是小白,还是资深开发,都需要先选择好的工具,提升开发效率和团队协作效率,让自己有更多的时间来思考。

第三步:分布式 从深度到广度的完善自己的知识体系,准备迎接高并发大数据的挑战

第四步:微服务 绕微服务的通用模式,学习spring Cloud的常见用法及原理,让微服务的开发更加方便 快捷,让微服务应用更加稳定 可用。

第五步:性能优化 从JVM底层原理到内存优化 再到各个中间件的性能调优,做最科学和最严谨的性能调优

第六步:并发编程 标重点: 深入了解底层原理,不只是做一个开发者。
项目管理
2018-07-13 10:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
修改你 Confluence 的索引语言将有助于你提高搜索的准确性,如果你网站使用的主要语言是除了英语以外的其他语言。
Confluence 可以支持下面语言的的内容索引: Arabic Brazilian Chinese CJK Czech Custom Japanese English (default) French German Greek Persian Russian
希望配置索引的语言: 进入 > 基本配置( General Configuration) 然后选择 编辑(Edit) 。 在 格式和国际化设置(Formatting and International Settings) 部分,选择 索引语言(Indexing Language) 。 选择 保存(Save) 。
https://www.cwiki.us/display/CONF6ZH/Configuring+the+Server+Base+URL
项目管理
2018-07-09 09:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
任何网页的 crawler 工具都可以被用来索引你的 Confluence 站点中的内容。如果你希望注册用户才能够查看的内容也被索引的话,你需要为你的 Confluence 创建一个只被 crawler 使用的用户。将这个用户赋予你需要索引内容的访问权限,但是限制这个用户具有删除和管理员的权限。这个限制能够阻止 crawler 用户不能对你的站点进行修改。
有关外部应用对你的 Confluence 进行索引的功能,请参考 Confluence APIs 中的内容。
https://www.cwiki.us/display/CONF6ZH/Setting+Up+an+External+Search+Tool+to+Index+Confluence
项目管理
2018-07-10 22:25:08
「深度学习福利」大神带你进阶工程师,立即查看>>>
ancestor 表记录了上级和下级(子页面)页面之间的关系。这个表格同时被用来确定子页面是否具有从上级页面继承来的限制(restrictions)权限。
偶尔 ancestor 表格中的数据可能被损坏,这就要求你需要对 ancestor 表进行重构了。
如何对 ancestor 表进行重构: 备份你的数据库。 使用下面的连接访问 页面级别权限(Page Level Permissions) 页面。 /admin/permissions/pagepermsadmin.action 选择 重构 ancestor 表(Rebuild ancestor table) 。 进入 > 基本配置( General Configuration) > 缓存管理(Cache Management) 。 刷新 内容继承权限(Inherited Content Permissions) 缓存。
你同时需要进行下面的操作 Content Index Administration 这样能够保证权限的限制在查找界面中生效。
针对基本数据量的网站,这个操作通常需要大概 10 分钟才能完成,在操作没有完成之前,你可能不能保存新的内容。 我们推荐你对你的数据库进行完整的备份,不要在网站访问高峰期进行重构操作,可以选择在网站维护时间进行重构操作。
https://www.cwiki.us/display/CONFLUENCEWIKI/Rebuilding+the+Ancestor+Table
项目管理
2018-07-10 22:14:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在 OpenSearch autodiscovery 自动发现,你可以添加 Confluence 搜索到你的的 Firefox 或者 IE7 查找对话框中(请参考 Searching Confluence from your Browser's Search Box )。在默认情况下 ,OpenSearch autodiscovery 是启用的。这个功能可以按照下面的方法启用或者禁用。
启用或者禁用 OpenSearch autodiscovery: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链接。 在左侧面板中选择 进一步配置(Further Configuration) 。 选择 编辑(Edit) Choose . 选择 开放搜索(Open Search) 选择对话框来启用这个功能(取消选择就禁用这个功能了)。 选择 保存(Save)。
https://www.cwiki.us/display/CONF6ZH/Setting+Up+an+External+Search+Tool+to+Index+Confluence
项目管理
2018-07-10 22:10:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
(一)
有时,记忆的碎片会从脑神经的连接中苏醒。
(二)
浏览器,客户端browser,client 服务器server
view:jsp(含前端控制器DispatcherServlet) 表现层(controller/action),业务层持久层(model:po,service,dao)
前端 后端

设计模式的优点: 分层设计,实现业务系统各个组件之间的解耦;(单个) 有利于系统的可扩展性、可维护性;(多个) 有利于实现系统的并行开发,提升开发的效率。(多对多)
补充一点,我上面的归纳是根据我的理解编写的,实际情况的逻辑很复杂。毕竟我认为java是关系型分层结构的语言。
(三)
准备工具:eclipse
创建项目:Maven工程
1.properties-java build path
2.project facets-dynamic web module:2.5(servlet规范,3.0不用web.xml文件) 或 Java EE Tools-Generate Deployment Descriptor Stub-->src-main-webapp- WEB-INF -web.xml.
3.deployment assembly:打包的环境
搭建环境(配置文件):
1.pom.xml 4.0.0 com.buff springmvc-work 0.0.1-SNAPSHOT war 4.3.8.RELEASE 1.2 org.springframework spring-core ${spring.version} org.springframework spring-web ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-context-support ${spring.version} jstl jstl ${jstl.version} junit junit 4.12 test org.apache.tomcat.maven tomcat7-maven-plugin 2.1 8080 /springmvc-work UTF-8 tomcat7
2.框架核心配置文件:src/main/resources/anyname.xml(约束) xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
3.src/main/webapp/WEB-INF/web.xml springmvc-work springmvcfancyname-work org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:anyname.xml 1 springmvcfancyname-work *.do index.html index.htm index.jsp default.html default.htm default.jsp
4.src/main/webapp/WEB-INF/jsp/hello.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> Demo hello ${hello}
5.编写controller:相当于struts2中处理请求的action
src/main/java/com/buff/controller/DemoController.java @Controller //对表现层的注解。最后,对应spring管理文件中的包扫描。 public class DemoController { /** * hello test * ModelAndView:模型和视图。 * 1.用于设置响应的模型数据; * 2.用于设置响应的视图 * @RequestMapping:设置请求的url */ @RequestMapping("/hello.do")//响应数据响应页面有了之后,需要接受方法:创建对应关系 public ModelAndView hello(){ // 1.创建ModelAndView ModelAndView mav = new ModelAndView(); // 2.设置响应的模型数据 /** * addObject方法:设置模型数据 * 参数: * attributeName:模型数据名称 * attributeValue:模型数据 */ mav.addObject("hello", "world"); // 3.设置响应的视图 /** * setViewName方法:设置响应的视图 * 参数: * viewName:页面的物理路径 */ mav.setViewName("/WEB-INF/jsp/hello.jsp"); return mav; } }
项目管理
2018-07-10 21:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Sequence data formats
1. Common sequence data formats including GenBank, FASTA, FASTQ formats. GenBank and FASTA format often represent curated sequencing information. FASTQ often represent experimentally obtained data.
(1) GenBank file format
GenBank is part of the International Nucleotide Sequence Database Collaboration , which comprises the DNA DataBank of Japan (DDBJ), the European Nucleotide Archive (ENA), and GenBank at NCBI. These three organizations exchange data on a daily basis.
More information on GenBank format can be found here
When do we use the GenBank format?
GenBank format can represent variety of information while keeping this information human-readable. It is not suitable for data-analysis.
(2) FASTA format
在生物信息学中,FASTA格式是一种用于记录核酸序列或肽序列的文本格式,其中的核酸或氨基酸均以单个字母编码呈现。该格式同时还允许在序列之前定义名称和编写注释。这一格式最初由FASTA软件包定义,但现今已是生物信息学领域的一项标准。
FASTA简明的格式降低了序列操纵和分析的难度,令序列可被文本处理工具和诸如Python、Ruby和Perl等脚本语言处理。
FASTA is a DNA sequence format for specifying or representing DNA sequences. It does not contain sequence quality information.
Reference: Wikipedia FASTA格式
(3) FASTQ file format
FASTQ is extended FASTA file format with sequencing quality score (phred score).
Please refer to the following references: fasta与fastq格式文件解读 Wikipedia FASTQ格式 (Simplified Chinese) or FASTQ format (English)
FASTQ文件中,一个序列通常由四行组成:
第一行以@开头,之后为序列的标识符以及描述信息(与FASTA格式的描述行类似)
第二行为序列信息
第三行以+开头,之后可以再次加上序列的标识及描述信息(可选)
第四行为质量得分信息,与第二行的序列相对应,长度必须与第二行相同
The character '!' represents the lowest quality while '~' is the highest. Here are the quality value characters in left-to-right increasing order of quality (ASCII): !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Further reading:
Differences between FASTA, FASTQ and SAM formats
2. Databases that contain gene sequencing data NCBI GEO : can search datasets (sequencing data from a series of participants) NCBI SRA : can search sequencing data from individual participant ArrayExpress : Experiments are submitted directly to ArrayExpress or are imported from the NCBI Gene Expression Omnibus database. For high-throughput sequencing based experiments the raw data is brokered to the European Nucleotide Archive, while the experiment descriptions and processed data are archived in ArrayExpress. European Nucleotide Archive : Learn more about how to use ENA by reading ENA: Guidelines and Tips .
I prefer NCBI GEO and SRA because I can use Aspera to download SRA files, which is super fast. It's best to keep Aspera connect software up-to-date.
Install Aspera connect on Ubuntu Linux mkdir -p ~/biosoft/ascp && cd ~/biosoft/ascp wget https://download.asperasoft.com/download/sw/connect/3.7.4/aspera-connect-3.7.4.147727-linux-64.tar.gz tar -zxvf aspera-connect-3.7.4.147727-linux-64.tar.gz bash aspera-connect-3.7.4.147727-linux-64.sh # Installing Aspera Connect # Deploying Aspera Connect (/home/jshi/.aspera/connect) for the current user only. # Unable to update desktop database, Aspera Connect may not be able to auto-launch # Restart firefox manually to load the Aspera Connect plug-in # Install complete. # construct soft link sudo ln -s /home/jshi/.aspera/connect/bin/ascp /usr/bin/ascp ascp -h # help ascp -A # version
If you have older version, you need to uninstall before you install newer version of Aspera. Actually, you need to delete related files in the following folder: # ~/.mozilla/plugins/libnpasperaweb.so # ~/.aspera/connect rm ~/.mozilla/plugins/libnpasperaweb_{connect build #}.so yes|rm -rf ~/.aspera/connect
3. How to download SRA files from NCBI SRA database?
According to SRA group, they recommand Prefetch program provided in SRAtoolkit . More detail can be found in Download Guide .
1. Download SRA files by using prefetch
I don't recommand install SRAtoolkit by using sudo apt-get install sratoolkit because the version might be older. I personally prefer to install the latest softwares.
SRA files will be deposited in the default file folder ~/ncbi/public/sra . # Install SRAtoolkit mkdir -p ~/biosoft/sratools && cd ~/biosoft/sratools wget https://ftp-trace.ncbi.nlm.nih.gov/sra/sdk/2.8.2-1/sratoolkit.2.8.2-1-ubuntu64.tar.gz tar -zxvf sratoolkit.2.8.2-1-ubuntu64.tar.gz # You echo 'export PATH=$PATH:/home/jshi/biosoft/sratools/sratoolkit.2.8.2-1-ubuntu64/bin' >> ~/.bashrc source ~/.bashrc
Prefetch can use several different way to download SAR files, the default one is Aspera, if you want prefetch to use only Aspera to download, you can use the following code. mkdir -p ~/data/project/GSE48240 && cd ~/data/project/GSE48240 # manually generate SRA file list touch GSE48240.txt for i in $(seq -w 1 3); do echo "SRR92222""$i" >>GSE48240.txt;done # Using efetch to generate SRA file list esearch -db sra -query PRJNA209632 | efetch -format runinfo | cut -f 1 -d ',' |grep SRR >> GSE48240.txt prefetch -t ascp -a "/usr/bin/ascp|/home/jshi/.aspera/connect/etc/asperaweb_id_dsa.openssh" --option-file GSE48240.txt
Alternatively, you can use curl , wget or ftp to download from generated download links, but will be as slow as snail.
2. Convert SRA files to FASTQ files on the fly
This is a better way if you don't have too much space to save the SRA files. fastq-dump will covert SRA files to fastq files on the fly. cat GSE48240.txt | xargs -n 1 echo fastq-dump --split-files $1
other R中修改个别变量名(reshape包)使用names()函数 names(leadership) names(leadership)[2] <- “testDate” names(leadership)[6:10] <-c(“item1”, “item2”, “item3”, “item4”, “item5”) How do I remove part of a string?
https://stackoverflow.com/questions/9704213/r-remove-part-of-string
gsub
sub_str # install bioawk apt-get install bison cd ~/biosoft git clone https://github.com/lh3/bioawk cd bioawk make sudo cp bioawk /usr/local/bin # Download and unzip the file on the fly. curl http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/chr22.fa.gz | gunzip -c > chr22.fa # Look at the file cat chr22.fa | head -4 # Count how many "N" are in chr22 sequence cat chr22.fa | grep -o N | wc -l # Count how many bases are in Chr22? cat chr22.fa | bioawk -c fastx '{ print length($seq) }'
-------------------------------------------------------------------------------------------------------
各基因组的对应关系
首先是NCBI对应UCSC,对应ENSEMBL数据库: GRCh36 (hg18): ENSEMBL release_52.
GRCh37 (hg19): ENSEMBL release_59/61/64/68/69/75.
GRCh38 (hg38): ENSEMBL release_76/77/78/80/81/82.
可以看到ENSEMBL的版本特别复杂!!!很容易搞混!
但是UCSC的版本就简单了,就hg18,19,38, 常用的是hg19,但是我推荐大家都转为hg38
看起来NCBI也是很简单,就GRCh36,37,38,但是里面水也很深! Feb 13 2014 00:00 Directory April_14_2003 Apr 06 2006 00:00 Directory BUILD.33 Apr 06 2006 00:00 Directory BUILD.34.1 Apr 06 2006 00:00 Directory BUILD.34.2 Apr 06 2006 00:00 Directory BUILD.34.3 Apr 06 2006 00:00 Directory BUILD.35.1 Aug 03 2009 00:00 Directory BUILD.36.1 Aug 03 2009 00:00 Directory BUILD.36.2 Sep 04 2012 00:00 Directory BUILD.36.3 Jun 30 2011 00:00 Directory BUILD.37.1 Sep 07 2011 00:00 Directory BUILD.37.2 Dec 12 2012 00:00 Directory BUILD.37.3
可以看到,有37.1, 37.2, 37.3 等等,不过这种版本一般指的是注释在更新,基因组序列一般不会更新!!!
反正你记住hg19基因组大小是3G,压缩后八九百兆即可!!!
如果要下载GTF注释文件,基因组版本尤为重要!!!
对NCBI: ftp://ftp.ncbi.nih.gov/genomes/H_sapiens/GFF/ ##最新版(hg38)
ftp://ftp.ncbi.nlm.nih.gov/genomes/Homo_sapiens/ARCHIVE/ ## 其它版本
对于ensembl:
ftp://ftp.ensembl.org/pub/release-75/gtf/homo_sapiens/Homo_sapiens.GRCh37.75.gtf.gz
变幻中间的release就可以拿到所有版本信息: ftp://ftp.ensembl.org/pub/
对于UCSC,那就有点麻烦了:
需要选择一系列参数:
http://genome.ucsc.edu/cgi-bin/hgTables 1. Navigate to http://genome.ucsc.edu/cgi-bin/hgTables
2. Select the following options:
clade: Mammal
genome: Human
assembly: Feb. 2009 (GRCh37/hg19)
group: Genes and Gene Predictions
track: UCSC Genes
table: knownGene
region: Select "genome" for the entire genome.
output format: GTF - gene transfer format
output file: enter a file name to save your results to a file, or leave blank to display results in the browser
3. Click 'get output'.
现在重点来了,搞清楚版本关系了,就要下载呀!
UCSC里面下载非常方便,只需要根据基因组简称来拼接url即可: http://hgdownload.cse.ucsc.edu/goldenPath/mm10/bigZips/chromFa.tar.gz
http://hgdownload.cse.ucsc.edu/goldenPath/mm9/bigZips/chromFa.tar.gz
http://hgdownload.cse.ucsc.edu/goldenPath/hg19/bigZips/chromFa.tar.gz
http://hgdownload.cse.ucsc.edu/goldenPath/hg38/bigZips/chromFa.tar.gz
或者用shell脚本指定下载的染色体号: for i in $(seq 1 22) X Y M;
do echo $i;
wget http://hgdownload.cse.ucsc.edu/goldenPath/hg19/chromosomes/chr${i}.fa.gz;
## 这里也可以用NCBI的:ftp://ftp.ncbi.nih.gov/genomes/M_musculus/ARCHIVE/MGSCv3_Release3/Assembled_Chromosomes/chr前缀
done
gunzip *.gz
for i in $(seq 1 22) X Y M;
do cat chr${i}.fa >> hg19.fasta;
done
rm -fr chr*.fasta
---------------------------------------------------------------------------------------------------------
Tool: fastq-dump
Usage:
fastq-dump [options] [ ...]
fastq-dump [options]
Frequently Used Options:
General:
-h | --help Displays ALL options, general usage, and version information.
-V | --version Display the version of the program.
Data formatting:
--split-files Dump each read into separate file. Files will receive suffix corresponding to read number.
--split-spot Split spots into individual reads.
--fasta <[line width]> FASTA only, no qualities. Optional line wrap width (set to zero for no wrapping).
-I | --readids Append read id after spot id as 'accession.spot.readid' on defline.
-F | --origfmt Defline contains only original sequence name.
-C | --dumpcs <[cskey]> Formats sequence using color space (default for SOLiD). "cskey" may be specified for translation.
-B | --dumpbase Formats sequence using base space (default for other than SOLiD).
-Q | --offset Offset to use for ASCII quality scores. Default is 33 ("!").
Filtering:
-N | --minSpotId Minimum spot id to be dumped. Use with "X" to dump a range.
-X | --maxSpotId Maximum spot id to be dumped. Use with "N" to dump a range.
-M | --minReadLen Filter by sequence length >=
--skip-technical Dump only biological reads.
--aligned Dump only aligned sequences. Aligned datasets only; see sra-stat.
--unaligned Dump only unaligned sequences. Will dump all for unaligned datasets.
Workflow and piping:
-O | --outdir Output directory, default is current working directory ('.').
-Z

|

--stdout
--gzip --bzip2
Output to stdout, all split data become joined into single stream.
Compress output using gzip. Compress output using bzip2.
Use examples:
fastq-dump -X 5 -Z SRR390728
Prints the first five spots (-X 5) to standard out (-Z). This is a useful starting point for verifying other formatting options before dumping a whole file.
fastq-dump -I --split-files SRR390728
Produces two fastq files (--split-files) containing ".1" and ".2" read suffices (-I) for paired-end data.
fastq-dump --split-files --fasta 60 SRR390728
Produces two (--split-files) fasta files (--fasta) with 60 bases per line ("60" included after --fasta).
fastq-dump --split-files --aligned -Q 64 SRR390728
Produces two fastq files (--split-files) that contain only aligned reads (--aligned; Note: only for files submitted as aligned data), with a quality offset of 64 (-Q 64) Please see the documentation on vdb-dump if you wish to produce fasta/qual data. Possible errors and their solution:
fastq-dump.2.x err: item not found while constructing within virtual database module - the path '' cannot be opened as database or table
This error indicates that the .sra file cannot be found. Confirm that the path to the file is correct.
fastq-dump.2.x err: name not found while resolving tree within virtual file system module - failed SRR*.sra
The data are likely reference compressed and the toolkit is unable to acquire the reference sequence(s) needed to extract the .sra file. Please confirm that you have tested and validated the configuration of the toolkit. If you have elected to prevent the toolkit from contacting NCBI, you will need to manually acquire the reference(s) here
--------------------------------------------------------------------------------------------------------
下载流程:
1:wget -i ftp://ftp.ncbi.nlm.nih.gov/sra/sra-instant/reads/ByStudy/sra/SRP/SRP000/SRP000001/SRR000001/SRR000001.sra
从NCBI官网下载sra数据文件
2:
使用fastq-dump工具将sra转换成双端fastq
项目管理
2018-07-10 17:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
策略模式:可以定义一系列的算法,把这些算法一个个封装起来。
比如,一个游戏对不同等级的会员有不同的充值汇率,钻石会员1元=15个币,铂金会员1元=13个币,黄金会员1元=10个币。使用策略模式就可以把三种会员的三种算法分别封装到单独的实现类中,然后三个实现类实现了同一个接口。
示例代码:
1、创建共同接口: 会员策略接口和充值方法,传入原有剩余的币的值和充值金额 public interface MemberStrategy { public int recharge(int num, int money); }
2、创建三个会员的实现类,并重写充值方法
钻石会员的充值算法,金额*15 + 剩余的币 = 充值后的币 public class DiamondRecharge implements MemberStrategy{ @Override public int recharge(int num, int money) { return money*15 + num; } }
铂金会员的充值算法,金额*13 + 剩余的币 = 充值后的币 public class PlatinumRecharge implements MemberStrategy{ @Override public int recharge(int num, int money) { return money*13 + num; } }
黄金会员的充值算法,金额*10 + 剩余的币 = 充值后的币 public class GoldRecharge implements MemberStrategy{ @Override public int recharge(int num, int money) { return money*10 + num; } }
3、新建会员充值的类 public class MemberRecharge { private MemberStrategy memberStrategy ; public MemberRecharge(MemberStrategy memberStrategy ){ this.memberStrategy = memberStrategy ; } public int executeRecharge(int num, int money){ return memberStrategy .recharge(num, money); } }
4、新建测试类 public class TestStrategy { public static void main(String[] args) { MemberRecharge memberRecharge = new MemberRecharge(new DiamondRecharge()); System.out.println("钻石会员第一次充值1元后剩余的币数量:"+memberRecharge.recharge(0,1)); MemberRecharge memberRecharge = new MemberRecharge(new PlatinumRecharge()); System.out.println("铂金会员第一次充值1元后剩余的币数量:"+memberRecharge.recharge(0,1)); MemberRecharge memberRecharge = new MemberRecharge(new GoldRecharge()); System.out.println("黄金会员第一次充值1元后剩余的币数量:"+memberRecharge.recharge(0,1)); } }

策略模式有良好的扩展性,属于行为型模式,比如新增个白银会员的话也只需新建一个实现 MemberStrategy接口的类即可。

项目管理
2018-07-10 15:44:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文主要是继续研读了资深架构师王概凯Kevin执笔的《架构漫谈》系列的《架构漫谈(五):什么是软件》的心得感受。王概凯Kevin结合自己多年的架构经验,通过不同的视角,重新审视架构的本质,从而产生一力作《架构漫谈》系列,作者希望能够抛出自己从实践中得出的一些观点,并引发大家的一些思考,欢迎大家沟通讨论。
如需要阅读原文,请关注公众号“聊聊架构”,从历史文章中获取《架构漫谈》系列。
本文内容结构图:

前几篇重点讲述了,架构产生过程中的三个阶段:Why-How-What。简单给大家回溯下这三个阶段,这样接下来再将这3W阶段的思考,用到软件当中,才会有更深刻的体会。
Why:为何会产生架构 架构本质其实就是解决人的问题,从而满足人不断更高的需求,产生架构的必须动机 : 1. 必须由人执行的工作; 2. 每个人的能力有限; 3. 每个人的时间有限; 4. 人对目标系统有更高的要求; 5. 目标系统的复杂性使得个人独自无法完成这个系统; 复制代码
How:架构中关键步骤 概念认知 :概念是认识沟通这个世界的基础,自然对概念的认知是非常重要的。 1. 概念其实代表其背后解决人的问题而来,不同的概念代表不同的功能,不同的功能解决的问题也不同; 2. 同一事物在不同场景下,解决的问题不同,所代表的功能也不同,从而概念也不同; 复制代码
问题识别 :当找到真正问题时,问题就已解决了80%。 1. 确定问题主体,谁的问题; 2. 确定问题边界,真正的问题是什么; 复制代码
架构切分 :其实就是相关人的利益调整,这就是原动力; 1. 背后的动力,就是每个人自己的利益,这是切分过程中必须要考虑到人的利益; 2. 架构切分最终会反应到组织架构上,形成组织树状; 复制代码
What:架构的本质 架构实际上就是指人们根据自己对世界的认识,为解决某个问题,主动地、有目的地去识别问题,总结得出概念,通过概念作为认识沟通的桥梁,对相关事物进行分解、合并,如:人、利益、问题、组织架构等等,解决这个问题的实践活动。
什么是软件
软件的历史,实际上可以说是用机器模拟人的历史 。不管大家(包括在这个历史过程中的参与者)有没有意识到,我们都有意无意的在计算机上模仿人类的行为。从冯诺依曼结构开始,程序逻辑开始脱离硬件,采用二进制编码。加上存储,配合输入输出,一个简化的大脑就出现了。图灵机则是模拟大脑的计算,用数学的方式把计算的过程定义了出来,著名的邱奇-图灵论题:一切直觉上能行可计算的函数都可用图灵机计算,反之亦然。软硬件两者一结合,一个可编程的大脑出现了,这也是现在为什么我们把计算机叫做电脑。在硬件上编写出的程序,就是软件,是用来控制硬件的行为的。
人们越来越愿意把原来只有人才能做的事情,交给计算机来做。结果就导致软件越来越丰富,能够做的事情也越来越多,成本也越来越低。可以这么说, 成本是我们为什么采用软件的主要动力,可以节省大量的人员培训,减少雇员的数目 。随着互联网的发展,人类社会也开始软件化了。原来必须实体店来进行售卖的,搬到互联网上,开店成本更低,并且能够接触到更多的人。想象一下,一个门店每天的人流达到百万级别是很恐怖的,由实体空间大小来决定。但是在互联网上,访问量千万级别都不算什么。最终的结果就变成,每个人能够负担的工作越来越多,成本越来越低。这也是为什么软件这么热的原因。
有了软件之后,实际上,我们是把我们日常生活中所做的事情,包括我们自己本人都一起虚拟化到了计算机中。 而人则演化成了,通过计算机的输入输出设备,控制计算机中的自己,来完成日常的工作,以及与其他人的沟通 。也就是说, 软件一直以来的动力,始终都是来模拟人和这个社会的 。
不管如何发展,模拟人的所有行为都是一个大的趋势。 也就是说,软件的主要目的,还是把人类的生活模拟化,提供更低成本,高效率的新的生活 。从这个角度来看,软件主要依赖的还是人类的生活知识。软件更多的是扮演一个cost center,这也是为什么会出现很多的软件代工。
软件架构出现
软件工程师是实现这个模拟过程的关键人物,他必须先理解人是怎么在日常生活中完成工作的,才能够很好的把这些工作在计算机中模拟出来 。可是软件工程师需要学习大量的计算机语言和计算机知识,还需要学习各行各业的专业知识。软件工程师本身的培养就比较难,同时行业知识也要靠时间的积累,这样就远远超出了软件工程师的能力了。 所以软件开发就开始有分工了,行业知识和业务的识别,会交给BA,系统的设计会交给架构师,设计的实现交给架构师,实现的检验交给测试,还有很多其他角色的配合 。为了组织这些角色的工作,还有项目经理。 这就把原来一个人的连续工作,拆分成了不同角色的人的连续配合,演化成了不同的软件开发的模式 。然后慢慢演变出专门为别人开发软件的软件公司。
一开始是懵懵懂懂的去写软件,后来慢慢的就有意识的去切分,演变成了不同的架构。 这个背后的动力也是一样的,就是提升参与的人的利益,降低成本。导火索也是软件工程师的任务太重,我们需要把很多工作拆分出来。拆分的原则也是一样的,如何让权责一致 。同样,这个拆分也是需要组织架构的调整,来保证架构的落地。
总结一下 以上通过简单的描述计算机和软件的发展历史,阐明软件的本质, 其实就是通过把人类的日常工作生活虚拟化,减少成本,提升单个人员的生产力,提升人类自己的利益。软件工程师的职责在这个浪潮中,不堪重负,自然而然就分拆为不同的角色,形成了一个独特的架构体系。这一切的背后,仍然是为了提升人类自己的利益,解决人类自己的问题。
作者:猿码道
链接:https://juejin.im/post/5b36ed2af265da597f1c8afe
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
项目管理
2018-07-09 18:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文主要是继续研读了资深架构师王概凯Kevin执笔的《架构漫谈》系列的《架构漫谈(四):如何做好架构之架构切分》的心得感受。王概凯Kevin结合自己多年的架构经验,通过不同的视角,重新审视架构的本质,从而产生一力作《架构漫谈》系列,作者希望能够抛出自己从实践中得出的一些观点,并引发大家的一些思考,欢迎大家沟通讨论。
如需要阅读原文,请关注公众号“聊聊架构”,从历史文章中获取《架构漫谈》系列。
本文内容结构图:


上一篇已经讲过了如何识别问题,只有在识别出谁的问题之后,才能发现真正的问题,在大部分情况下,问题都会迎刃而解,不需要做额外的动作。 很多时候问题的产生都是因为沟通的误解,或者主观上有很多不必要的利益诉求导致的。
但是总还有一部分确实是有问题的,需要做调整,那么就必须要有所动作,做相应的调整。 这个调整就是架构的切分 。
切分就是利益的调整
要非常的清楚, 所有的切分调整,都是对相关人的利益的调整 。为什么这么说呢,因为维护自己的利益,是每个人的本性,是在骨子里面的,我们不能逃避这一点。
我们已经知道,随着社会的发展,分工是必然的,为什么呢? 这个背后的动力就是每个人自己的利益 。每个人都希望能够把自己的利益最大化,比如:生活的更舒适,更轻松,更安全,占用并享有更多的东西。但是每个人的能力和时间都非常的有限,不可能什么都懂,所以自然需要舍掉一些自己不擅长的东西,用自己擅长的东西去换取别人擅长的东西。
对比一个人干所有的事情,结果就是大家都能够得到更多,当然也产生了一个互相依赖的社会,互相谁都离不开谁。 这就是自然而然而产生的架构切分,背后的原动力就是人们对自己利益的渴望 。人们对自己利益的渴望也是推动社会物质发展的原动力。
在这个模式下,比较有意思的是,每个人必须要舍掉自己的东西,才能够得到更多的东西。有些人不愿意和别人进行交换,不想去依赖于别人,这些人的生活就很明显的差很多,也辛苦很多,自然而然的就被社会淘汰了。 如果需要在这个社会上立足,判断标准就变成了:如何给这个社会提供更好更有质量的服务 。提供更好的更多的服务,自然就能够换取更多的更好的生活必需品。 实际上这就是我们做人的道理 。
为什么需要切分
当人们认识到要主动的去切分一个系统的时候, 毫无疑问,我们不能忘掉利益这个原动力。所有的切分决策都不能够违背这一点,这是大方向 。结合上一篇“识别问题”,一旦确定了问题的主体,那么系统的利益相关人(用现代管理学语言叫:stakeholder)就确定了下来。所发现的问题,会有几种情况: 某个或者某些利益相关人负载太重。 时间上的负载太重。 空间上的负载太重,本质上还是时间上的负载太重。 某个或者某些利益相关人的权利和义务不对等。
切分的原则
情况1是切分的原因,情况2是切分不合理而导致的新的问题,最终还是要回到情况1。 对于情况1,本质上都是时间上的负载 。因为每个人的时间是有限的,怎么在有限的时间内做出更多的事情? 那么只有把时间上连续的动作,切分成时间上可以并行的动作,在空间上横向扩展 。所以切分就要有几个原则: **必须在连续时间内发生的一个活动,不能切分。**比如孕妇怀孕,必须要10月怀胎,不能够切成10个人一个月完成。 **切分出来的部分的负责人,对这个部分的权利和义务必须是对等的。**比方说妈妈10月怀胎,妈妈有权利处置小孩的出生和抚养,同样也对小孩的出生和抚养负责。为什么必须是这样呢? 因为如果权利和义务是不对等的话,会伤害每个个体的利益,分出来执行的效率会比没有分出来还要低,实际上也损害了整体的利益,这违背了提升整体利益的初衷。 **切分出来的部分,不应该超出一个自然人的负载。**当然对于每个人的能力不同,负载能力也不一样,需要不断的根据实际情况调整,这实际上就是运营。 **切分是内部活动,内部无论怎么切,对整个系统的外部应该是透明的。**如果因为切分导致整个系统解决的问题发生了变化,那么这个变化不属于架构的活动。当然很多时候当我们把问题分析的比较清楚的时候,整个系统的边界会进一步的完善,这就会形成螺旋式的进化。但这不属于架构所应该解决的问题。进化的发生,也会导致新的架构的切分。
原则2是确保我们不能违反人性,因为维护自己的利益,是每个人的本性 。只有权利和义务对等才能做到这一点。 从原则2的也可以推理,所有的架构分拆,都应该是形成树状的结果,不应该变成有向图,更不应该是无向图 。很多人一谈架构,必谈分层,但是基本上都没意识到,是因为把一个整体分拆为了一棵树,因为有了树,才有层。
从某种意义上来说,谈架构就是谈分层,似乎也没有错,但是还是知道为什么比较好。 从根节点下来,深度相同的是同一层 。
同样我们看一个组织架构,也可以做一个粗略的判断,如果一个企业的组织架构出现了“图”,比方说多线汇报,一定是对stakeholder的利益分析出现了问题,这就会导致问题2的发生。问题2一旦出现,我们必须马上要意识到,如果这个问题持续时间长,会极大的降低企业的运作效率,对相关stakeholder的利益都是非常不利的,同样对于企业的利益也是不利的。 必须快速调整相关stakeholder的职责,使得企业的组织架构成为一个完美的树状,并且使得数的层数达到尽可能的低 。只有平衡数可以比较好的达到这个效果。
当然如果某个节点的能力很强,也可以达到减小树的高度的结果。 技术的提升,也是可以提升每个节点的能力,降低树的层数 。
很多管理学都在讨论如何降低组织架构的层数,使得管理能够扁平化,原因就在于此,这里就不展开讨论了。 从这里也基本可以得出一个结论,一个好的组织的领导,一定也是一个很好的架构师 。
切分与建模
实际上切分的过程就是建模的过程,每次对大问题的切分都会生成很多小问题,每个小问题就形成了不同的概念 。这也是系列第二篇文章尝试表达的。这些不同的概念大部分时候人们自发的已经建好了, 架构师更多的是要去理解这些概念,识别概念背后所代表的的人的利益 。
比如:人类社会按照家庭进行延续,形成了家族,由于共享一片土地资源,慢慢形成了村庄,村庄联合体,不同地域结合,形成了国家。由于利益分配的原因,形成了政权。 每次政权的更迭,都是利益重新分配的动力所决定的 。
同样对于一个企业也是一样的,一开始一个人干所有的事情。当业务量逐渐变大,就超过了一个人能够处理容量,这些内容就会被分解出来,开始招聘人进来,把他们组合在一起,帮助处理企业的事务。整个企业的事务,就按照原则2,分出来了很多新的概念:营销,售前,售中,售后,财务,HR等等。 企业的创始人的工作就变成了如何组合这些不同的概念完成企业的工作 。如果业务再继续增大,这些分出来的部分还要继续分拆,仍然要按照原则2才能够让各方达到利益最大化。如果某个技术的提升,提高了某个角色的生产力,使得某个角色可以同时在承担更多的工作,就会导致职责的合并,降低树的层数。
切分的输出和组织架构
架构切分的输出实际上就是一个系统的模型,对于一个整体问题,有多少的相关方,每个相关方需要承担哪些权利和义务,不同的相关方是如何结合起来完成系统的整体任务的 。有的时候是从上往下切(企业),有的时候是从下往上合并,有的时候两者皆有之(人类社会的发展)。 而切分的结果最终都会体现在组织架构上,因为我们切分的实际上就是人的利益 。
从这方面也可以看出,任何架构调整都会涉及到组织架构,千万不可轻视 。同样,如果对于stakeholder的利益分析不够透彻,也会导致架构无法落地,因为没有入愿意去损坏自己的利益。一旦强制去执行,人心就开始溃散。这个也不一定是坏事,只要满足原则2就能够很好的建立一个新的次序和新的利益关系,保持组织的良性发展,长久来看是对所有人的利益都有益的,虽然短期内有对某些既得利益者会有损害。
总结一下: 架构的切分的导火索是人的负载太重。 架构的切分实际就是对stakeholder的利益进行切分或合并,使得每个stakeholder的权责是对等的,每个stakeholder可以为自己的利益负责。 架构切分的最终结果都会体现在组织架构上,只有这样才能够让架构落地并推进。 架构切分的结果一定是一个树状,这也是为什么会产生分层。层数越多沟通越多,效率越低,分层要越少越好。尽可能变成一颗平衡树,才能让整个系统的效率最大化。
架构切分=利益调整,利益调整代表了以人为中心,进行的利益在分配。从时间负载、空间负载触发架构切分,而架构切分的原则就是以人的利益为本,也可以说不违背人的本性,切分的过程其实就是分层建模的过程,分层建模就是概念划分的过程,每个概念背后都代表一个问题,最终会形成树状的组织结构,而这种组织结构的调整会涉及到人的组织架构,从而要切实要考虑到人的利益。
作者:猿码道
链接:https://juejin.im/post/5b36eae46fb9a00e4f74dcd7
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
项目管理
2018-07-09 17:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文主要是继续研读了资深架构师王概凯Kevin执笔的《架构漫谈》系列的《架构漫谈(三):如何做好架构之识别问题》的心得感受。王概凯Kevin结合自己多年的架构经验,通过不同的视角,重新审视架构的本质,从而产生一力作《架构漫谈》系列,作者希望能够抛出自己从实践中得出的一些观点,并引发大家的一些思考,欢迎大家沟通讨论。
如需要阅读原文,请关注公众号“聊聊架构”,从历史文章中获取《架构漫谈》系列。
本文内容结构图:


按照之前的架构定义, 架构就是要不断解决人遇到的问题,然而做好架构首先需要做的就是识别出需要解决的真实问题 。一般来说,如果把真正的问题能够找到,那么问题就已经解决了80%。这个能力基本上就决定了架构师的水平。
一则笑话: 女主人公:老公,把袋子里的土豆切一半下锅。结果老公是把袋子里的每个土豆都削了一半,然后下锅。
当然很多人会说,这个是沟通问题,然后一笑了之。其实,出现这个现象是 由于我们大部分时候过于关注解决问题,急于完成自己的工作,而不关心“真正的问题是什么”而造成的 。当我们去解决一个问题的时候,一定要先把问题搞清楚。
去看看软件开发工作者的时间分配也可以看出,大家大部分时间花在讨论解决方案和实现的细节上, 基本都不会花时间去想“问题是什么” 。或者即使想了一点点,也是一闪而过,凭自己的直觉下判断。 只有真正投入思考问题是什么的工程师,才可能会真正的成长为架构师 。
以这个笑话为例,看看在我们处理问题的时候,都会犯什么样的惯性错误: 被告知要处理一个问题,但是交过来的实际上是一个解决方案,不是问题本身。
被告知要处理一个问题,直接通过直觉就有了一个解决方案,马上考虑解决方案如何落地,或者有几种解决方案,选哪个合适。
如何识别问题主体呢?
所有的概念基本都有一个很大的问题,就是缺乏主语。而我们大家都心照不宣的忽略这个主语,沟通的时候也都以为大家都懂得对方说的主语是谁,结果大家都一起犯错误。 识别问题的一个最大的前提就是要搞清楚:是谁的问题 。这个搞清楚了,问题的边界也就跟着确定了,再去讨论问题才有意义。
以上面切土豆的例子来分析: 女主人提出一个问题,要切土豆下锅煮。 男主人有一个问题,女主人交代了自己必须要完成的一个任务。
每个人都是优先处理自己的问题 ,自然就选择了2,完成了这个任务。这也是大部分软件工程师处理的方式, 以自己认为对的方式完成自己的问题 ,没什么不对啊,也难怪能得到我们的共鸣。这个里面犯的错误是什么呢?
首先,女主人公提出的实际上是解决方案,而不是烧土豆这个问题本身。女主人当时执行这个解决方案可能有困难,就把执行解决方案作为一个任务,委托给了男主人。
其次,男主人得到了一个任务,尽心尽职地把这个任务完成了。
最后的结果是什么呢,每个人都做了很多工作,每个人都认为自己做的是对的,因此没有一个人对结果满意。 因为真正的问题没有被发现,自然也就没有被解决,那么后续还得收拾残局,还要继续解决问题 。事实上自己的工作并没有完成,反而更多了。把原因归结为沟通问题也是可以的,但对于解决问题似乎并没有太多的帮助。因为要改进沟通,这也是一个大问题。 搞明白目标问题“是谁的问题,是什么问题”,当然也是需要沟通的 。为了帮助自己更快的搞明白,首先要做的事是问正确的问题。 架构师应该问的第一个正确的问题就是:目标问题是谁的问题 。
所以得出: 当我们处理问题的时候,如果发现自己正在致力于把自己的工作完成,要马上警惕起来,因为这样下去会演变成没有ownership的工作态度。在面对概念的时候,也会不求甚解,最终会导致没有真正的理解概念。
作为软件工程师或者架构师,我们大部分时候是要去解决别人的问题,“别人”是谁,是值得好好思考的 。在上面故事里面,男主人要解决的,实际上是这个家庭晚餐需要吃土豆的问题, 目标问题的主体实际上是这个家庭的成员 。
如何识别问题边界呢?
明白了问题的主体,这个主体就自然会带来很多边界约束 ,比如土豆是要吃的,要给人吃的,而且还是要给自己的家人吃的。“切土豆下锅”这个问题,因为识别了问题的主体,自然而然的就附带了这么多的信息。
后续如何煮,是否放高压锅煮,放多少水,煮多长时间等等,就自然而然能够问出来其他问题来了,说不定还能够识别出来,女主人给的这个解决方案可能是有问题的 。这个时候才算是真正的明白了问题。可以想象,这样下去最后的结果一定是大家都满意的,因为真正的问题解决了。 只有真正明白了是谁的问题,才能够真正地完成自己的任务,真正地把自己的问题解决掉,而不是反过来 。
由上面的分析可以看出, 找出问题的主体,是做架构的首要问题 。这也是我一再强调的,我们要解决的问题,一定都是人的问题。 进一步,架构师要解决的,基本都是别人的问题,不是自己的问题 。
再进一步,我们一定要明白,任何找上架构师的问题,绝对都不是真正的问题 。为什么呢? 因为如果是真正的问题的话,提问题过来的人肯定都能够自己解决了,不需要找架构师。 架构师都要有这个自觉:发现问题永远都比解决问题来的更加重要 。
当问题的主体离架构师越远,就会让找出问题主体的过程越加困难 ,我们再举一个软件行业比较熟悉的例子:用户给产品经理提出要求,想要一把锤子。这是典型的拿解决方案作为问题的。真正的问题的主体是谁,是用户还是设计师还是施工队? 如果产品经理当成是自己的问题,那么毫无疑问就给了锤子了。
我们需要识别: 用户究竟是二传手,还是问题的真正主体 。如果是设计师,那么问题的边界就变成了设计师的问题,如果是施工队,那么问题就变成了施工队的问题,如果是用户,那么就要看看用户到底有什么困难,绝对不是要一个锤子这么简单。 这也说明了,问题的主体对问题的边界确定有多么的重要 。
当明白了问题的主体,我们才可能真正的认识问题是什么。 因为问题的主体是问题的隐含边界,边界不确定下来,问题就是不确定的 。一旦确定了主体,剩下的就是去搞明白主体有哪些问题。
一般来说, 从问题暴露的点,一点点去溯源查找,一定会找出来谁的问题,以及是什么问题 。最坏情况就是当我们时间或者能力有限,实在是无法定位出是谁的问题的时候,比如系统出故障,也就意味着我们无法根本解决问题。这时最好的办法就是去降低问题发生所带来的成本,尽量去隔离问题影响的范围。给我留出时间和空间去识别真正的问题。
总结一下,要正确的认识问题,需要问两个问题: 这是谁的问题? 有什么问题?
当得到的回答是支支吾吾的时候,我们就知道正确的方向在哪儿,以及需要做哪些事了。 问题1会花比较多的时间,也是支支吾吾最多的地方,因为架构要解决的问题都是人的问题 。但是一旦确定了答案,问题2就会变得非常容易。可以这样说, 架构师的能力大部分会体现在问题1的识别上 。
在实际工作中存在很多的情况,都只是在完成自己的问题或任务,而忽略了问题或任务的根本:谁的问题、什么问题。作为架构师,不仅是架构师,在面对问题时,而 要更多的找出问题是什么,问题的主体是什么,从而只有有了主体,才能够确定问题的边界,最终才会确定出真正的问题所在 。
作者:猿码道
链接:https://juejin.im/post/5b36e55d51882574dc41e948
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
项目管理
2018-07-09 17:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文主要是继续研读了资深架构师王概凯Kevin执笔的《架构漫谈》系列的《架构漫谈(二):认识概念是理解架构的基础》的心得感受。王概凯Kevin结合自己多年的架构经验,通过不同的视角,重新审视架构的本质,从而产生一力作《架构漫谈》系列,作者希望能够抛出自己从实践中得出的一些观点,并引发大家的一些思考,欢迎大家沟通讨论。
如需要阅读原文,请关注公众号“聊聊架构”,从历史文章中获取《架构漫谈》系列。
本文内容结构图:


如前一篇文章 《架构之缘起》 所述, 架构实际上是解决人的问题,而概念是人认识这个世界的基础,自然概念的认识就非常的重要 。然而,大部分人对于每天都习以为常的概念,都自以为明白了, 但实际上都是下意识的,并不是主动的认识 。
举个栗子:
比如说“什么是桌子?”其实每个人对其的理解描述都是千奇百怪的,这实际上就导致了做架构的时候, 不同角色的沟通会出很多问题 ,那么结果也就可想而知了。
所以, 在架构中对概念的认知,其实是很关键的 ,本文也将尝试讨论下如何认识概念的过程。 首先声明一下,本系列的文章,都是以人的认识为主体去讨论的,解决的都是人的问题,任何没有具体申明的部分,都隐含这一背景,以免大家误解。
概念也属于人认识这个世界并用来沟通的手段 ,包括“概念”这个概念,也是一样的。在古代,不叫“概念”,称之为“名相”。
何为相?
一般我们认为:看到一个东西,比方说杯子,“杯子”就是一个名字,指代的看到的东西就是相,就是事物的相状。我们一听到“杯子”这个词,脑海里就会浮现出一个杯子的形象。而“杯子”这个词,是用来指代的是这个相状的,叫做名。合起来就叫做“名相”。
可是当我们把杯子打碎了的时候,我们还会称这个碎了的东西叫杯子吗? 肯定不会,一般会叫“碎瓦片”,如果我们把碎瓦片磨碎了呢,名字又变了,叫做“沙子”。 这就奇怪了,同样一个东西,怎么会变出这么多的名字出来?
那究竟什么才是相?
实际上“相“表达的不是一个具体的东西,如上面所提的一个瓷器杯子,并不是指这个瓷器,而是这个瓷器所起的一个作用:一手可握,敞口(一般不超过底的大小,太大口就叫碗了),并且内部有一个空间可乘东西的这么一个作用。并不是指这个瓷器本身。这也是为什么我们从电视上看到一个人拿杯子的时候,我们知道这个是杯子。但是实际上我们看到的都是光影而已。 所以说相实际上代表的是这个作用,并不是具体的某个东西,而名是用来标识这个作用的,用来交流的。
为何需要这个作用? 这个作用其实是为了解决“人需要一个可单手持握,但是希望避免直接接触所盛物体”这个问题。
所以说,每个概念实际上所解决的,还是人遇到的某个特定的问题,我们把解决问题的解决方案,给定了一个名字,这个名字就是对应的某个特定的概念。对于概念这个词本身,为了统一指代这些名字,我们称起这类作用的名字称为“概念”。
同样,什么是“建筑”?“建筑”实际上解决的就是“人需要独占的空间,并还能够比较流畅的和外部世界沟通”的问题。
再拿前面的“桌子”来举例,什么叫“桌子”? 很多人回答,四条腿,或者说有腿,有一个平面,等等,柜子不也是这样吗?为什么我们看到柜子,不会认为是桌子呢?即使我们放在柜子上吃饭,我们看到仍然会问,为什么在柜子上吃饭? 不会叫桌子。 如果明白了上面的道理,就很简单了,桌子实际上是为了解决人坐在椅子上,手还能够支撑在一个平面上继续开展活动的问题,一般会和椅子配对出现 。
同理,为何我们可以在不同的语言间进行翻译, 是因为虽然语言不同,但是人类所面临的的问题是一样的,所使用的名不同而已 。对于不同的动物之间的翻译也是同理。
在讨论桌子这个概念的过程中,很多人会提出抽象这个概念,认为定义桌子实际上就是抽象的一个过程。这里,有必要要澄清一下抽象这个概念,这个里面有误解。
抽象这个词代表的含义,实际上是把不同的概念的相似的部分合并在一起,形成一个新的概念。
这个里面问题很多:首先“相似的部分”在不同的人看来,并不一定那么相似;其次,抽象之后形成的是一个新的概念,和原来那个概念并不一样,所解决的问题也不一样。 所以我们不能用抽象来定义一个事物,抽象实际上是一个分类的过程,完全是另一码事。
再举一个例子,杯子和容器,很多人认为容器是杯子的抽象,但是实际上杯子是杯子,容器是容器,它们所解决的问题是不一样的。当我们需要解决装东西的问题的时候,会说容器;当我们需要解决单手持握要装东西的时候,会说要一个杯子。
回过头来,根据架构的定义, 要做好架构所首先必须具备的能力,就是能够正确的认识概念,能够发现概念背后所代表的问题,进而才能够认识目标领域所需要解决的问题 ,这样才能够为做好架构打好基础。
事实上,这一能力,在任何一个领域都是适用的,比如我们如果想要学习一项新的技术,如Hibernate、Spring、PhotoShop、WWW、Internet等等, 如果知道这些概念所要解决的问题,学习这些新的技术或者概念就会如虎添翼,快速的入手 ;学习一个新的领域,也会非常的快速有效;使用这些概念来解释问题,甚至发明新的概念都是很容易的事。 为什么强调这个呢,因为做架构的时候,很多时候都是在一个新的领域解决问题,必须要快速进入并掌握这个领域,然后才能够正确的解决问题 。
总结一下,正如上文所述: 相是一个具体物体的状态和作用,是来解决具体问题的 。而为了人之间的交流,便根据相有了名,名其实就是一个标记,或理解为简单的概念。 所以概念的背后其实是代表解决人的问题 。
而抽象则是对概念或名相似之处的归类、分类的过程 ,不同角度来看会得出不同的相似之处,从而会有不同的抽象出来,所以抽象也会产生不同的新的概念。但抽象出来的概念不能够指具体的事物,而只能做到泛指一类具有相似之处的事物。 其实抽象本身也是一个概念,其背后也在解决归类,分类的问题,但抽象并不是架构的必然产物 。
而在解决问题的工程中,会首先有简单名相(意指解决问题的最原始工具或物体),遇到更高级问题,会有更高级的名相或概念,通过抽象,对名相进行归类、分类,抽象出新的概念,或者再有更高级的抽象,产生更高级的概念,最后由这些概念会组成架构。
作者:猿码道
链接:https://juejin.im/post/5b36e19be51d4558b304325e
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
项目管理
2018-07-09 17:03:00