数据专栏

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

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
你可以使用下面的 Confluence MBeans 来实时查看你 Confluence 实例运行的实时信息。
CacheStatistics
这个 MBean 显示了 Confluence 有关的缓存信息。有关更多的内容请查看 Viewing System Information 页面中的内容。
IndexingStatistics
这个 MBean 显示有关搜索索引的相关信息。这里是一些有用的配置属性。
Flushing 显示当前缓存有没有刷新。 布尔值(True/False)
LastElapsedMilliseconds 上一次进行索引所消耗的时间。 毫秒(Milliseconds)
TaskQueueLength
ReIndexing
显示任务队列中的数量。
确定 Confluence 当前是否在进行重新索引。
整数(Integer)
布尔值(True/False)
SystemInformation
这个 MBean 显示了 Confluence 的版本和已经运行的时间想信息。你也可以通过 Viewing System Information 页面来查看这方面的信息。

DatabaseExampleLatency
显示进行数据库示例查询的延迟时间。
毫秒(Milliseconds)
RequestMetrics
这个 MBean 显示了系统负载和错误页面的相关信息。
AverageExecutionTimeForLastTenRequests 最后 10 个查询执行的平均时间。 毫秒(Milliseconds)
CurrentNumberOfRequestsBeingServed 这个实例已经执行的查询数量。 整数(Integer)
ErrorCount
NumberOfRequestsInLastTenSeconds
Confluence 错误页面的数量。
最后 10 秒内执行的查询数量
整数(Integer)
整数(Integer)
MailServer-SMTPServer
这个 MBean 显示了电子邮件发送和失败的相关信息。针对你安装的 Confluence 实例中的每一个系统中配置的 SMTP 邮件服务器,都会有一个独立的 MBean 显示。

EmailsAttempted EmailsSent
Confluence 尝试发送的电子邮件数量。 成功发送的电子邮件数量。
Integer Integer
MailTaskQueue
这个 MBean 显示电子邮件有关的工作情况。
ErrorQueueSize 队列中的错误数量。 整数(Integer)
Flushing 显示状态(例如:flushing 或不是)。 布尔值(True/False)
FlushStarted 操作开始的时间。 时间(Time)
RetryCount
TaskSize
整个操作总计处理已处理的总数。
需要等待发送的电子邮件队列中的消息数量。
整数(Integer)
整数(Integer)
SchedulingStatistics
这个 MBean 显示当前任务有关的信息,已经计划的任务和这些任务上次运行所花费的时间。

AllJobNames CurrentlyRunningJobNames
显示当前任务有关的信息,已经计划的任务和这些任务上次运行所花费的时间。 显示当前正在执行的计划任务。
字符串(String) 列表(List)

https://www.cwiki.us/display/CONF6ZH/Live+Monitoring+Using+the+JMX+Interface
项目管理
2018-07-05 22:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
针对生产系统中,我们推荐你使用远程监控,这个将不会消耗你远程 Confluence 服务器的资源。
启动远程监控: 添加下面的属性到 setenv.sh / setenv.bat 文件中,端口你可以定义任何你没有使用的端口。
set CATALINA_OPTS= -Dcom.sun.management.jmxremote %CATALINA_OPTS%
set CATALINA_OPTS= -Dcom.sun.management.jmxremote.port= 8099 %CATALINA_OPTS%
确定你将如何保持你远程连接的安全。请 Remote Monitoring and Management 页面来获得更多的信息。
尽管可以在监控的时候禁用安全授权,我们不推荐你在生产环境下配置使用禁用安全授权。 启动 JConsole (你可以在 JDK 安装目录中找到了启动需要的 bin)。 选择 远程进程(Remote Process) 。 输入你的主机名和端口(这个端口是你在配置文件中配置的,这个端口不是你 Confluence 运行的端口)。 单击 连接( Connect )。
请参考 Using JConsole 来获得有关远程监控的更多有用信息。
https://www.cwiki.us/display/CONF6ZH/Live+Monitoring+Using+the+JMX+Interface
项目管理
2018-07-05 21:58:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你遇到了一些特定的问题,或者你仅仅是希望在一个很短的时间内监控你 Confluence 的运行,你可以使用本地监控。本地监控将会对你的服务器性能产生影响,所以我们并不推荐你使用本地监控来长时间的监控你运行的 Confluence 实例。
启动本地监控: 启动 JConsole (你可以在 JDK 安装目录中找到了启动需要的 bin)。 选择 本地进程(Local Process) 。 选择 Confluence 进程。这个进程被命名为 org.apache.catalina.startup.Bootstrap start
请参考 Using JConsole 来获得有关本地监控的更多有用信息。

https://www.cwiki.us/display/CONF6ZH/Live+Monitoring+Using+the+JMX+Interface
项目管理
2018-07-05 21:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
使用 JMX 界面( Java Management Extensions API ),你可以实时的查看你 Confluence 运行实例的状态。 JMX 使用的对象被称 MBeans (Managed Beans) ,通过这个对象来向外暴露你应用的数据,提供资源的的使用情况,数据库延迟等很多有用的信息和数据,能够帮助你诊断你 Confluence 实例在运行的时候遇到的问题。
这个页面将会指导你如何使用 JConsole 来在你本地监控远程的 Confluence 实例。JConsole 已经包含在 Java Development Kit (JDK) 中了,你也可以使用其他的任何客户端。
这个指南提供了有关 JMX 界面的一些基本的介绍。我们的支持小组能够帮你解决有关 Confluence 的具体问题,但是我们不能帮助你设置 JMX 的远程监控,同时我们也不能帮你解释监控中数据的意义是什么。
https://www.cwiki.us/display/CONF6ZH/Live+Monitoring+Using+the+JMX+Interface
项目管理
2018-07-05 21:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
当前一个正在运行的 Confluence 6 实例的内存使用情况


https://www.cwiki.us/display/CONF6ZH/Viewing+System+Information
项目管理
2018-07-05 21:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
原文转帖于: https://www.cnblogs.com/teach/p/5791029.html
JSON字符串和Java对象互转
在开发过程中,经常需要和别的系统交换数据,数据交换的格式有XML、JSON等,JSON作为一个轻量级的数据格式比xml效率要高,XML需要很多的标签,这无疑占据了网络流量,JSON在这方面则做的很好,下面先看下JSON的格式。
JSON可以有两种格式 一种是对象格式的 另一种是数组对象 JSON的对象格式的字符串: {"stuName":"张三","stuAge":"","age":"广州天河区"} 数据对象格式:[{"name":"李四","stuAge":30,"address":"广州越秀区"}]
从上面的两种格式可以看出对象格式和数组对象格式唯一的不同则是在对象格式的基础上加上了[],再来看具体的结构,可以看出都是以键值对的形式出现的,中间以英文状态下的逗号(,)分隔。
在前端和后端进行数据传输的时候这种格式也是很受欢迎的,后端返回json格式的字符串,前台使用js中的JSON.parse()方法把JSON字符串解析为json对象,然后进行遍历,供前端使用。

使用Json需要的Jar包:
Jar包的版本号可以忽略,目前使用过多种版本均没发现什么问题
commons-beanutils-1.9.1.jar
commons-collections-3.2.2.jar
commons-lang-2.6.jar
commons-logging-1.2.jar
ezmorph-1.0.6.jar
json-lib-2.4-jdk15.jar
编码/测试
Java普通对象和Json字符串互转
Java对象 - > Json字符串
创建一个实体类用于测试 package com.org.demo; public class Students { private String stuName; private String stuAge; private String address; public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public String getStuAge() { return stuAge; } public void setStuAge(String stuAge) { this.stuAge = stuAge; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String toString() { return "Student [stuName=" + stuName + ", stuAge=" + stuAge + ", address=" + address + "]"; } }
编写转换方法 // Java对象转JSON字符串 public static void javaToJsonStr() { Students students = new Students(); students.setStuName("张三"); students.setStuAge("28"); students.setAddress("广州天河区"); // 1、使用JSONObject JSONObject strJson = JSONObject.fromObject(students); // 2、使用JSONArray JSONArray strArray = JSONArray.fromObject(students); System.out.println("Java对象转JSON字符串"); System.out.println("JSON:" + strJson.toString()); System.out.println("Array:" + strArray.toString()); }
运行结果 Java对象转JSON字符串 JSON:{"address":"广州天河区","stuName":"张三","stuAge":"28"} Array:[{"address":"广州天河区","stuName":"张三","stuAge":"28"}]

Json字符串 -> Java对象 public static void jsonStrToJava() { // 定义两种不同格式的字符串 String objectStr = "{\"stuName\":\"张三\",\"stuAge\":\"28\",\"address\":\"广州天河区\"}"; String arrayStr = "[{\"stuName\":\"张三\",\"stuAge\":\"28\",\"address\":\"广州天河区\"}]"; // 1、使用JSONObject JSONObject jsonObject = JSONObject.fromObject(objectStr); Students students = (Students) JSONObject.toBean(jsonObject, Students.class); // 2、使用JSONArray JSONArray jsonArray = JSONArray.fromObject(arrayStr); // 获得jsonArray的第一个元素 Object o = jsonArray.get(0); JSONObject jsonObject2 = JSONObject.fromObject(o); Students students2 = (Students) JSONObject.toBean(jsonObject2, Students.class); System.out.println("JSON字符串转Java对象"); System.out.println("students:" + students); System.out.println("students2:" + students2); }
运行结果 JSON字符串转Java对象 students:Student [stuName=张三, stuAge=28, address=广州天河区] students2:Student [stuName=张三, stuAge=28, address=广州天河区]

List和Json字符串的互转
list -> Json字符串 //List转换为Json字符串 public static void listToJson() { Students stu = new Students(); stu.setStuName("张三"); stu.setStuAge("28"); stu.setAddress("广州天河区"); List lists = new ArrayList(); lists.add(stu); JSONArray listArray = JSONArray.fromObject(lists); System.out.println("listArray:" + listArray.toString()); }
运行结果 listArray:[{"address":"广州天河区","stuName":"张三","stuAge":"28"}]
Json字符串 -> List //Json字符串转换为List对象 public static void jsonToList() { String arrayStr = "[{\"stuName\":\"张三\",\"stuAge\":\"28\",\"address\":\"广州天河区\"}]"; // 转化为list List list = (List) JSONArray.toList(JSONArray.fromObject(arrayStr), Students.class); for (Students svo : list) { System.out.println(svo); } // 转化为数组 Students[] stuArray = (Students[]) JSONArray.toArray(JSONArray.fromObject(arrayStr), Students.class); for (Students student : stuArray) { System.out.println(student); } }
运行结果 Student [stuName=张三, stuAge=28, address=广州天河区] Student [stuName=张三, stuAge=28, address=广州天河区]

Map和Json字符串互转
Map -> Json字符串 //Map转Json字符串 public static void mapToJson() { Students stu = new Students(); stu.setStuName("张三"); stu.setStuAge("28"); stu.setAddress("广州天河区"); Map map = new HashMap(); map.put("first", stu); // 1、JSONObject JSONObject mapObject = JSONObject.fromObject(map); System.out.println("mapObject" + mapObject.toString()); // 2、JSONArray JSONArray mapArray = JSONArray.fromObject(map); System.out.println("mapArray:" + mapArray.toString()); }
运行结果 mapObject{"first":{"address":"广州天河区","stuName":"张三","stuAge":"28"}} mapArray:[{"first":{"address":"广州天河区","stuName":"张三","stuAge":"28"}}]

Json字符串 -> Map
JSON字符串不能直接转化为map对象,要想取得map中的键对应的值需要别的方式 //Json字符串转Map public static void jsonToMap() { String strObject = "{\"first\":{\"address\":\"广州天河区\",\"stuAge\":\"28\",\"stuName\":\"张三\"}}"; // JSONObject JSONObject jsonObject = JSONObject.fromObject(strObject); Map map = new HashMap(); map.put("first", Students.class); // 使用了toBean方法,需要三个参数 MyBean my = (MyBean) JSONObject.toBean(jsonObject, MyBean.class, map); System.out.println(my.getFirst()); }
运行结果 Student [stuName=张三, stuAge=28, address=广州天河区]

MyBean对象代码 package com.org.demo; public class MyBean { private Students first; public Students getFirst() { return first; } public void setFirst(Students first) { this.first = first; } }
使用toBean()方法是传入了三个参数,第一个是JSONObject对象,第二个是MyBean.class,第三个是一个Map对象。通过MyBean可以知道此类中要有一个first的属性,且其类型为Students,要和map中的键和值类型对应,即,first对应键 first类型对应值的类型。
关于复杂的JSON串转化为java对象的内容,在下篇中会继续说明。
本篇,主要说明了java中的bean、list、map和JSON的互转。
项目管理
2018-06-29 10:27:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
当基于文本的文件上传到 Confluence(例如,Word,PowerPoint 等),这些文件中的文本是可以提取并且添加到索引中的,用户可以通过索引来搜索这些文件中的文本内容,不仅仅是搜索文件名。当文件需要被重新索引的时候,我们存储提取后的文本,我们不需要对文本中的内容重新进行索引。
提取后的文本文件,通常是以版本号进行命名的,例如 2.extracted_text , 同时还会存储文件自己的版本(如上面第八级目录中描述的)。我们只保存提取后文件的最新的版本,而不是和文件一样同时还保存了早期的版本。

https://www.cwiki.us/display/CONF6ZH/Hierarchical+File+System+Attachment+Storage
项目管理
2018-06-28 23:04:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
从 Confluence 3.0 开始,附件的存储方式有了重大的改变和升级。如果你是从 Confluence 2.10 及其早期版本升级上来的,请参考 Upgrading Confluence 页面中推荐的升级路径,同时请阅读 Confluence 3.0 文档中 Hierarchical File System Attachment Storage 页面来获得新系统文件存储结构的相关信息。
Confluence 存储附件,例如文件和图片在文件系统中。Confluence 的附件存储布局设计基于以下的考虑: 限制任何单一目录级别结构中的文件数量(在一些文件系统中,可能有限制每一个目录中可以存储的文件数量)。 针对空间对附件进行分区,这样能够让系统管理对空间进行备份的时候能够针对特定的空间备份附件。
Confluence 的附件有一个数字用来定义属性: 文件自己的内容 id 和 文件所在页面中的内容 id 。这个意思是文件在逻辑上是属于内容的,通常内容又是属于空间(不是所有的内容都属于空间)。Confluence 中的空间文件,目录结构通常有 8 个级别,每一个目录级别的名字通常基于下面的算法。
1 (top) 总是为 'ver003' 这个定义为 Confluence 版本 3 的文件存储格式
2 最小的 3 个数字,这个数字为 空间 id 取模 250
3 下一个最小的 3 个数字,这个数字为 空间 id 取模 250
4 完整的 空间 id
5 附件所附加在 页面的 ID 取模 250 后的最小 3 个数字
6 附件所附加在 页面的 ID 取模 250 后的下一个最小 3 个数字
7 附件所在页面的完整的 content id
8
9
附件所在完整的 content id
这个是文件,这个文件是按照版本号进行命名的,例如:1, 2, 6。
modulo 计算被用来计算整除后的余数,例如 800 modulo 250 = 50.
例如:
希望找到一个特定空间中所有附件存储的目录,进入 /admin/findspaceattachments.jsp 然后输入空间的 Key,这个将会返回这个空间所存储文件系统中的目录附件。
上面图中文件 D 存储的的位置与其他的文件结构不同。这个文件没有空间级别( 2 到 4 级别),这个文件目录被称为 'nonspaced'。这个地方通常存储的文件为全局站点的标志(logo)和没有保存的内容。

https://www.cwiki.us/display/CONF6ZH/Hierarchical+File+System+Attachment+Storage
项目管理
2018-06-28 22:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
当一个文件被上传到 Confluence 后,Confluence 将会尝试对文件进行解压,然后对文件中的内容进行索引。这样系统就能够允许用户对文件中的内容进行搜索,而不仅仅是搜索文件名。这个过程对系统的内存要求比较高,如果你上传的附件比较大的时候还会导致内存溢出。Confluence 有下面的一些配置被用来避免出现内存溢出的错误: 如果你上传的文件大小大于 100 MB,Confluence 将不会尝试对文件进行解压和内容进行索引。你只能在 Confluence 中对文件名进行查找。 如果你上传的文件为下面的一些文件类型,Confluence 仅仅对不超过大小的文件类型进行解压: 1 MB Excel 的文本(.xlsx) 8 MB PDF 的文本(.pdf) 10 MB Text 文件的文本(包括 .txt, .xml, .html, .rtf 等) 16 MB Word 的文本(.docx) 当从上面的文件类型中解压出来的文件超过 1MB,那这个内容就是可以在系统中进行查找,但是 Confluence 将不会在快速查找中显示结果。
如果 Confluence 停止解压文本,那么这个文本文件中只有部分内容可以进行查找。
Confluence 只会对文件进行解压和索引一次,如果在这个过程中失败了,Confluence 不会再出尝试进行解压和索引。
一些变量是可以通过 system properties 的参数进行配置的。如果你在附近进行索引的过程中遇到内存错误,你可能希望对系统进行调整,调整可以使用的参数如下: atlassian.indexing.attachment.maxsize officeconnector.excel.extractor.maxlength officeconnector.textextract.word.docxmaxsize atlassian.indexing.contentbody.maxsize

https://www.cwiki.us/display/CONF6ZH/Configuring+Attachment+Size
项目管理
2018-06-28 12:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
你可以限制上传到 Confluence 的附件的大小。
配置可以上传到 Confluence 的附件所允许的大小: 进入 > 基本配置( General Configuration) . 选择 编辑(Edit) 。 在 最大附件允许大小(Attachment Maximum Size) 的边上输入你允许上传的附件最大的大小。
默认的值为 100MB。 选择 保存(Save) 。

https://www.cwiki.us/display/CONF6ZH/Configuring+Attachment+Size
项目管理
2018-06-28 12:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
当前的分布式微服务云架构平台使用Maven构建,所以common-service的通用服务按照maven构建独立的系统服务,结构如下:
particle-commonservice: spring cloud 系统服务根项目,所有服务项目的根依赖。
particle-commonservice-admin: spring cloud/boot的微服务管理、监控平台(里面会集成很多的组件服务项目)
particle-commonservice-apigateway:API网关通用服务项目,所有的请求首先会经过这个网关。有点类似于前端控制器模式,也有点类似于 Facade模式。由于所有的请求会先经过这个 api 网关,所以可以在这里做权限控制,安全,负载均衡,请求分发,监控等等。以下的一张图片是从网上找的,方便大家理解:
particle-commonservice-cache:针对于分布式缓存提供服务化项目,封装redis,ehcache等。
particle-commonservice-config: 提供独立的微服务配置管理项目项目。配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。
particle-commonservice-erueka: 提供独立的微服务服务发现、注册管理平台。云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
particle-commonservice-mq: 提供独立的消息中间件服务平台。包括对流行的阿里rocketmq、rabbit mq、kafka分布式消息中间件的服务管理。
particle-commonservice-sso: 提供统一用户登录、认证单点登录平台。使用第三方OAuth2.0的解决方案,通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机提供统一认证登录服务。
particle-commonservice-turbine:是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况,提供独立的服务项目。
particle-commonservice-zipkin:提供独立的服务项目,为SpringCloud应用实现了一种分布式追踪解决方案。分布式跟踪系统数据流主要分为三个步骤:采集、发送和落盘分析。
项目管理
2018-06-28 09:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
什么是代码Review?
代码review是指在软件开发过程中,通过对源代码进行系统性检查来确认代码实现的质量保证机制
为什么不做代码Review? ​业务需求大,工作时间紧张 项目小,协作的人少,没必要
为什么要做代码Review? 提高代码质量,提升自身水平 及早发现潜在缺陷与BUG,降低事故成本 促进团队内部知识共享,提高团队整体水平 保证项目组人员的良好沟通 避免开发人员犯一些很常见,很普通的错误
总而言之目的是查找系统缺陷,保证软件总体质量和提高开发者自身水平,使项目代码更加容易维护。
代码Review的好处 在代码提交之前如果有很多双眼睛盯着看可以发现bug,这是代码审查最广为人知的好处。(人们的确可以在代码审查中发现bug,但是这些bug大部分都是显而易见的小bug,开发者分分钟可以发现,而那些真正需要花时间发现的bug通常是在代码审查中发现的) 代码审查最大的好处是纯社会性的。(如果你编程的时候知道你的同事将要看你的代码,你的编程方式会不一样,你的代码会写的更整洁,注释更加清楚,组织得更好。因为你知道其他人会看你的代码,他们的意见是你需要关注的。如果没有审查,你虽然知道人们最后会去看你的代码,但是那样不会给你一种紧迫感,也不会给你同样的个人评判的感觉。) 还有一个更大的好处就是代码审查可以传播知识。(在很多开发小组里,每个人都负责某一个核心组件,专注于自己的这一块,只要其他同事的模块不会破坏自己的代码就不会去关注,这种模式导致一个模块只有一个人熟悉对应的代码,如果一个人请教或者离职,其他人对他负责的模块将一无所知。如果采用代码审查,那么至少有两个人熟悉代码-作者和审查者。审查者知道的代码不如作者多,但是他们都熟悉代码的设计和结构,这意义重大)
Code Review的前提 重视代码review
(Code Review人员是否理解了Code Review的概念和Code Review将做什么如果做Code Review的人员不能理解Code Review对项目成败和代码质量的重要程度,他们的做法可能就会是应付了事。) 代码是否已经正确的build,build的目的使得代码已经不存在基本语法错误
(我们总不希望高级开发人员或是主管将时间浪费在检查连编译都通不过的代码上吧。 ) 代码执行时功能是否正确
(Code Review人员也不负责检查代码的功能是否正确,也就是说,需要复查的代码必须由开发人员或质量人员负责该代码的功能的正确性。 ) 开发人员是否对代码做了单元测试
(这一点也是为了保证Code Review前一些语法和功能问题已经得到解决,Code Review人员可以将精力集中在代码的质量上。 )
Code Review需要注意什么? 完整性检查(Completeness) 代码是否完全实现了设计文档中提出的功能需求 代码是否已按照设计文档进行了集成和Debug 代码是否已创建了需要的数据库,包括正确的初始化数据 代码中是否存在任何没有定义或没有引用到的变量、常数或数据类型 一致性检查(Consistency) 代码的逻辑是否符合设计文档 代码中使用的格式、符号、结构等风格是否保持一致 正确性检查(Correctness) 所有的变量都被正确定义和使用 所有的注释都是准确的 所有的程序调用都使用了正确的参数个数 可修改性检查(Modifiability) 代码涉及到的常量是否易于修改(如使用配置、定义为类常量、使用专门的常量类等) 代码是否只有一个出口和一个入口(严重的异常处理除外) 健壮性检查(Robustness) 可理解性检查(Understandability) 注释是否足够清晰的描述每个子程序 是否使用到不明确或不必要的复杂代码,它们是否被清楚的注释 使用一些统一的格式化技巧(如缩进、空白等)用来增强代码的清晰度 是否在定义命名规则时采用了便于记忆,反映类型等方法 每个变量都定义了合法的取值范围 代码中的算法是否符合开发文档中描述的数学模型 可验证性检查(Verifiability) 代码中的实现技术是否便于测试
Code Review经验检查项
1、 编码规范方面检查项
2、面向对象设计方面检查项
- 类设计和抽象是否合适
- 是否符合面向接口编程的思想
- 是否采用合适的设计模式
3、性能方面检查项
- 对hashtable,vector等集合类数据结构的选择和设置是否合适
- 有无滥用String对象的现象
- 是否采用通用的线程池、对象池模块等cache技术以提高性能
- I/O方面是否使用了合适的类或采用良好的方法以提高性能(如减少序列化,使用buffer类封装流等)
- 同步方法的使用是否得当,是否过度使用
4、数据库处理方面
- 数据库资源是否正常关闭和释放
- 数据库访问模块是否正确封装,便于管理和提高性能
- 是否采用合适的事务隔离级别
- 资源泄漏处理方面检查项 cursor
5、通讯方面检查项
- socket通讯是否存在长期阻塞问题
6、重复代码
7、其他
- 日志是否正常输出和控制
- 配置信息如何获得,是否有硬编码
怎么更有效的做Code Review 一次评审量要低于 200–400 行代码缺陷密度 就是每 1000 行代码之中所发现的错误(bug)数
每小时低于 300–500 LOC 检查率的目标
花足够的时间进行适当缓慢的评审,但是不要超过 60-90 分钟
但反过来说,评审代码所花的时间不得低于五分钟,就算代码只有一行也是如此。通常来说,单行的代码也会影响到整个的系统,所以花上五分钟时间去检查更改可能造成的结果是值得的 确定在评审开始之前代码开发者已经注释源代码了
使用检查表,因为它能极大地影响代码开发者和评审者的结果
另外一个有用的概念就是 个人检查表 。每个人一般都会犯 15-20 个错误(bug)。如果您注意到了一些典型的错误(bug),那么您就可以开发自己的个人检查表 确认缺陷得到了修复
最后,让Code Review成为一种习惯 The biggest thing that makes Google’s code so good is simple:code review

亲,如果您感觉本文有用,请点个赞再走吧✌(>‿◠)!!
项目管理
2018-06-28 09:27:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一、oauth中的角色
client:调用资源服务器API的应用
Oauth 2.0 Provider:包括Authorization Server和Resource Server
(1)Authorization Server:认证服务器,进行认证和授权
(2)Resource Server:资源服务器,保护受保护的资源
user:资源的拥有者

二、下面详细介绍一下Oauth 2.0 Provider
Authorization Server:
(1) AuthorizationEndpoint :进行授权的服务,Default URL: /oauth/authorize
(2) TokenEndpoint :获取token的服务,Default URL: /oauth/token
Resource Server:
OAuth2AuthenticationProcessingFilter :给带有访问令牌的请求加载认证

三、下面再来详细介绍一下Authorization Server:
一般情况下,创建两个配置类,一个继承AuthorizationServerConfigurerAdapter,一个继承WebSecurityConfigurerAdapter,再去复写里面的方法。
主要出现的两种注解:
1、@EnableAuthorizationServer:声明一个认证服务器,当用此注解后,应用启动后将自动生成几个 Endpoint :(注:其实实现一个认证服务器就是这么简单,加一个注解就搞定,当然真正用到生产环境还是要进行一些配置和复写工作的。)
/oauth/authorize:验证
/oauth/token:获取token
/oauth/confirm_access:用户授权
/oauth/error:认证失败
/oauth/check_token:资源服务器用来校验token
/oauth/token_key:如果jwt模式则可以用此来从认证服务器获取公钥
以上这些endpoint都在源码里的endpoint包里面。

2、@Beans:需要实现AuthorizationServerConfigurer
AuthorizationServerConfigurer包含三种配置:
ClientDetailsServiceConfigurer:client客户端的信息配置,client信息包括:clientId、secret、scope、authorizedGrantTypes、authorities
(1)scope:表示权限范围,可选项,用户授权页面时进行选择
(2)authorizedGrantTypes:有四种授权方式 Authorization Code:用验证获取code,再用code去获取token(用的最多的方式,也是最安全的方式) Implicit: 隐式授权模式 Client Credentials (用來取得 App Access Token) Resource Owner Password Credentials
(3)authorities:授予client的权限

这里的具体实现有多种,in-memory、JdbcClientDetailsService、jwt等。
AuthorizationServerSecurityConfigurer:声明安全约束,哪些允许访问,哪些不允许访问
AuthorizationServerEndpointsConfigurer:声明授权和token的端点以及token的服务的一些配置信息,比如采用什么存储方式、token的有效期等

client的信息的读取:在ClientDetailsServiceConfigurer类里面进行配置,可以有in-memory、jdbc等多种读取方式。
jdbc需要调用JdbcClientDetailsService类,此类需要传入相应的DataSource.

下面再介绍一下如何管理token:
AuthorizationServerTokenServices 接口:声明必要的关于token的操作
(1)当token创建后,保存起来,以便之后的接受访问令牌的资源可以引用它。
(2)访问令牌用来加载认证
接口的实现也有多种, DefaultTokenServices 是其默认实现,他使用了默认的InMemoryTokenStore,不会持久化token;

token存储方式共有三种分别是:
(1)InMemoryTokenStore:存放内存中,不会持久化
(2)JdbcTokenStore:存放数据库中
(3)Jwt: json web token

授权类型:
可以通过AuthorizationServerEndpointsConfigurer来进行配置,默认情况下,支持除了密码外的所有授权类型。相关授权类型的一些类:
(1)authenticationManager:直接注入一个AuthenticationManager,自动开启密码授权类型
(2)userDetailsService:如果注入UserDetailsService,那么将会启动刷新token授权类型,会判断用户是否还是存活的
(3)authorizationCodeServices:AuthorizationCodeServices的实例,auth code 授权类型的服务
(4)implicitGrantService:imlpicit grant
(5)tokenGranter:

endpoint的URL的配置:
(1)AuthorizationServerEndpointsConfigurer的pathMapping()方法,有两个参数,第一个是默认的URL路径,第二个是自定义的路径
(2)WebSecurityConfigurer的实例,可以配置哪些路径不需要保护,哪些需要保护。默认全都保护。

自定义UI:
(1)有时候,我们可能需要自定义的登录页面和认证页面。登陆页面的话,只需要创建一个login为前缀名的网页即可,在代码里,设置为允许访问,这样,系统会自动执行你的登陆页。此登陆页的action要注意一下,必须是跳转到认证的地址。
(2)另外一个是授权页,让你勾选选项的页面。此页面可以参考源码里的实现,自己生成一个controller的类,再创建一个对应的web页面即可实现自定义的功能。

下面梳理一下授权获取token流程:
(1)端口号换成你自己的认证服务器的端口号,client_id也换成你自己的,response_type类型为code。
localhost:8080/uaa/oauth/authorize?client_id=client&response_type=code&redirect_uri= http://www .baidu.com
(2)这时候你将获得一个code值: http://www .baidu.com/?code=G0C20Z
(3)使用此code值来获取最终的token:
curl -X POST -H "Cant-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=G0C20Z&redirect_uri= http://www.baidu.com ' " http://client:secret @localhost :8080/uaa/oauth/token"
返回值:
{"access_token":"b251b453-cc08-4520-9dd0-9aedf58e6ca3","token_type":"bearer","expires_in":2591324,"scope":"app"}

(4)用此token值来调用资源服务器内容(如果资源服务器和认证服务器在同一个应用中,那么资源服务器会自己解析token值,如果不在,那么你要自己去做处理)
curl -H "Authorization: Bearer b251b453-cc08-4520-9dd0-9aedf58e6ca3" "localhost:8081/service2(此处换上你自己的url)"

四、Resource Server:保护资源,需要令牌才能访问
在配置类上加上注解@EnableResourceServer即启动。使用ResourceServerConfigurer进行配置:
(1)tokenServices:ResourceServerTokenServices的实例,声明了token的服务
(2)resourceId:资源Id,由auth Server验证。
(3)其它一些扩展点,比如可以从请求中提取token的tokenExtractor
(4)一些自定义的资源保护配置,通过HttpSecurity来设置

使用token的方式也有两种:
(1)Bearer Token(https传输方式保证传输过程的安全):主流
(2)Mac(http+sign)

如何访问资源服务器中的API?
如果资源服务器和授权服务器在同一个应用程序中,并且您使用DefaultTokenServices,那么您不必太考虑这一点,因为它实现所有必要的接口,因此它是自动一致的。如果您的资源服务器是一个单独的应用程序,那么您必须确保您匹配授权服务器的功能,并提供知道如何正确解码令牌的ResourceServerTokenServices。与授权服务器一样,您可以经常使用DefaultTokenServices,并且选项大多通过TokenStore(后端存储或本地编码)表示。
(1)在校验request中的token时,使用RemoteTokenServices去调用AuthServer中的/auth/check_token。
(2)共享数据库,使用Jdbc存储和校验token,避免再去访问AuthServer。
(3)使用JWT签名的方式,资源服务器自己直接进行校验,不借助任何中间媒介。

五、oauth client
在客户端获取到token之后,想去调用下游服务API时,为了能将token进行传递,可以使用RestTemplate.然后使用restTemplate进行调用Api。
注:
scopes和authorities的区别:
scopes是client权限,至少授予一个scope的权限,否则报错。
authorities是用户权限。
以上是我从网上找到的一篇写的不错的博客,希望可以帮助大家快速了解OAuth2.0,下一篇文章我们正式介绍OAuth2.0在当前框架中的使用。
项目管理
2018-06-28 09:27:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在早期的 Confluence 版本中,我们允许存储附件到 WebDav 或者 Confluence 数据库中。针对新的 Confluence 安装,我们不再支持这 2 种存储了。
本地文件系统
在默认的情况下,Confluence 存储附件到 Confluence 配置的 home 目录下的 attachments 目录中。
数据库(已弃用)
在 Confluence 5.4 及其早期的版本,我们给了系统管理员存储附件到数据库中的选项,系统管理员可以在这些版本中配置附件的存储。
存储附件到数据库中可以带来一些好处(例如,可以更加容易的进行备份,避免文件系统中出现的字符集不支持的错误),但是请注意这种存储方式将会大大加大数据库空间的使用,随着时间的推移,你的数据库可能需要更多的存储空间。
WebDav(已弃用)
WebDav 在现在的存储中已经不是一个存储选项了,已经完全启用了。
这个对你使用 WebDav 访问 Confluence 的空间,页面或者附件不会产生影响,请参考页面 Configuring a WebDAV client for Confluence 中的具体配置内容。

https://www.cwiki.us/display/CONF6ZH/Attachment+Storage+Configuration
项目管理
2018-06-28 04:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你遇到了下面的错误信息,例如: ERROR [Importing data task] [confluence.importexport.impl.ReverseDatabinder] endElement net.sf.hibernate.PropertyValueException: not-null property references a null or transient value: com.atlassian.user.impl.hibernate.DefaultHibernateUser.name
这个意思是在表中有一个不因为为 null 的地方的值为 null 了。在上面的例子中,这个在 USERS 表中的 name 列有记录为 null。有时候这个问题也会出现在 ATTACHMENTS 表中。
删除上面为 null 的记录,然后重新进行 xml 导出和导入。

https://www.cwiki.us/display/CONFLUENCEWIKI/Troubleshooting+XML+backups+that+fail+on+restore
项目管理
2018-06-28 04:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你遇到了下面的错误信息,例如: could not insert: [bucket.user.propertyset.BucketPropertySetItem#bucket.user.propertyset.BucketPropertySetItem@a70067d3]; SQL []; Violation of PRIMARY KEY constraint 'PK_OS_PROPERTYENTRY314D4EA8'. Cannot insert duplicate key in object 'OS_PROPERTYENTRY'.; nested exception is java.sql.SQLException: Violation of PRIMARY KEY constraint 'PKOS_PROPERTYENTRY_314D4EA8'. Cannot insert duplicate key in object 'OS_PROPERTYENTRY'.
这个错误信息说的是定义为'PK_OS_PROPERTYENTRY_314D4EA8' 的主键在表 'OS_PROPERTYENTRY' 中重复了。
你可以在 'OS_PROPERTYENTRY' 表中找到 'PK_OS_PROPERTYENTRY_314D4EA8' 中定义的主键,然后找到重复的值后删除重复的值。需要确定 "PRIMARY KEY" 必须保持不重复。一个可以找到 'OS_PROPERTYENTRY' 表中是否有重复主键的 SQL 如下: SELECT ENTITY_NAME,ENTITY_ID,ENTITY_KEY,COUNT(*) FROM OS_PROPERTYENTRY GROUP BY ENTITY_NAME,ENTITY_ID,ENTITY_KEY HAVING COUNT(*)>1 https://www.cwiki.us/display/CONFLUENCEWIKI/Troubleshooting+XML+backups+that+fail+on+restore
项目管理
2018-06-28 04:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你现在正在存储附件到 WebDav 或者你的数据库中。你可以整合附件的存储到文件系统中。当你的附件从数据库中被合并到文件系统后,你存储在数据库中的附件数据就可以从数据库中删除了。
当附件合并进行的时候,所有使用 Confluence 的用户将会被禁止访问 Confluence 实例。这样配置的原因是避免在合并的时候出现数据库错误。当合并完成后,用户就可以继续使用 Confluence 了。
希望让日志在数据库合并的时候能够输出更多的内容,添加 com.atlassian.confluence.pages.persistence.dao 为 DEBUG 级别。请参考 Configuring Logging 页面中的内容。
希望对附件进行合并,请按照下面的步骤进行: 进入 > 基本配置( General Configuration) > 附件存储(Attachment storage) 。 单击 编辑( Edit )来修改配置。 选择 Confluence home 目录中的本地存储(Locally in Confluence home directory) 。 单击 保存(Save ) 来保存修改。 下面的界面将会出现,询问你确定你的修改。单击 'Migrate' 将会带你进入到修改合并进程显示的界面中。
屏幕截图:合并警告

下面的外部链接提供了有关从数据库中合并附件到本地文件系统中的一些有用的信息,这些信息可能能帮到你,请参考 - https://www.scandio.de/blog/de/2013/05/confluence-attachment-migration-the-safe-way-2 .

https://www.cwiki.us/display/CONF6ZH/Attachment+Storage+Configuration
项目管理
2018-06-28 04:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
15.1导入其他配置类
不需要将所有的@Configuration放在一个类中。可以使用@Import注释来导入其他配置类。或者,您可以使用@ComponentScan自动获取所有Spring组件,包括@Configuration类。
15.2导入XML配置
如果您一定要使用基于XML的配置,我们建议您还是从一个@Configuration类开始。然后,您可以使用@ImportResource注释来加载XML配置文件。
16. Auto-configuration
Spring Boot自动配置尝试根据添加的jar依赖项自动配置Spring应用程序。例如,如果HSQLDB在您的类路径上,并且您没有手动配置任何数据库连接bean,那么Spring Boot将自动配置内存中的数据库。
您需要通过将@EnableAutoConfiguration或@SpringBootApplication注释添加到您的@Configuration类之一来选择加入到自动配置中。
【提示】
您应该只添加一个@SpringBootApplication或@EnableAutoConfiguration注释。我们通常建议只在主@Configuration类中添加一个或另一个。
项目管理
2018-06-27 20:55:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring Boot参考指南
作者
菲利普· 韦伯,戴夫 Syer,约什 长,斯特凡 尼科尔,罗布 绞车,安迪· 威尔金森,马塞尔 Overdijk,基督教 杜普伊斯,塞巴斯蒂安· 德勒兹,迈克尔· 西蒙斯,韦德兰 Pavić,周杰伦 科比, Madhura 巴维
2.0.3.RELEASE
版权所有©2012-2018
本文件副本可供您自行使用并分发给其他人,前提是您不收取任何此类副本的费用,并进一步规定每份副本均包含此版权声明,无论是以印刷版还是电子版分发.
I. Spring Boot文档
1.关于文档
2.获得帮助
3.第一步
4.使用Spring Boot
5.了解Spring Boot特性
6.转向生产
7.高级主题
II.入门
8.介绍Spring Boot
9.系统要求
9.1.Servlet容器
10.安装Spring Boot
10.1.Java开发人员的安装说明
10.1.1.Maven安装
10.1.2.Gradle安装
10.2.安装Spring Boot CLI
10.2.1.手动安装
10.2.2.使用SDKMAN进行安装!
10.2.3.OSX Homebrew安装
10.2.4.MacPorts安装
10.2.5.命令行完成
10.2.6.Windows Scoop安装
10.2.7.快速启动Spring CLI示例
10.3.从较早版本的Spring Boot升级
11.开发你的第一个Spring Boot应用程序
11.1.创建POM
11.2.添加类路径依赖关系
11.3.编写代码
11.3.1.@RestController和@RequestMapping注解
11.3.2.@EnableAutoConfiguration注释
11.3.3.“主要”方法
11.4.运行示例
11.5.创建一个可执行的Jar
12.下一步阅读什么
III.使用Spring Boot
13.建立系统
13.1. Dependency Management
13.2.Maven的
13.2.1.继承初始父项
13.2.2.使用没有父POM的Spring Boot
13.2.3.使用Spring Boot Maven插件
13.3.摇篮
13.4.蚂蚁
13.5.首发
14.构建你的代码
14.1.使用“默认”包
14.2.查找主要应用程序类
15.配置类
15.1.导入其他配置类
15.2.导入XML配置
16. Auto-configuration
16.1.逐渐替换自动配置
16.2.禁用特定的自动配置类
17.春豆和依赖注入
18.使用@SpringBootApplication注释
19.运行你的应用程序
19.1.从IDE运行
19.2.作为打包应用程序运行
19.3.使用Maven插件
19.4.使用Gradle插件
19.5.热交换
20.开发人员工具
20.1. Property Defaults
20.2.自动重启
20.2.1.记录条件评估中的更改
20.2.2.排除资源
20.2.3.看额外的路径
20.2.4.禁用重新启动
20.2.5.使用触发文件
20.2.6.自定义重启类加载器
20.2.7. Known Limitations
20.3.LiveReload
20.4.全局设置
20.5.远程应用
20.5.1. Running the Remote Client Application
20.5.2.远程更新
21.包装您的生产申请
22.下一步阅读什么
IV.Spring Boot功能
23. SpringApplication
23.1.启动失败
23.2.自定义横幅
23.3.自定义SpringApplication
23.4.Fluent Builder API
23.5.应用程序事件和监听器
23.6.Web环境
23.7.访问应用程序参数
23.8.使用ApplicationRunner或CommandLineRunner
23.9.申请退出
23.10.管理功能
24.外部化配置
24.1.配置随机值
24.2.访问命令行属性
24.3.应用程序属性文件
24.4.配置文件特定的属性
24.5.属性中的占位符
24.6.使用YAML而不是属性
24.6.1.正在加载YAML
24.6.2.在Spring环境中将YAML作为属性公开
24.6.3.多配置文件YAML文件
24.6.4.YAML的缺点
24.7.类型安全的配置属性
24.7.1.第三方配置
24.7.2.轻松的绑定
24.7.3.合并复杂类型
24.7.4.属性转换
转换持续时间
24.7.5.@ConfigurationProperties验证
24.7.6.@ConfigurationProperties与@Value
25.简介
25.1.添加活动配置文件
25.2.编程设置配置文件
25.3.配置文件特定的配置文件
26. Logging
26.1.日志格式
26.2.控制台输出
26.2.1.彩色编码输出
26.3.文件输出
26.4.日志级别
26.5. Custom Log Configuration
26.6.Logback扩展
26.6.1.配置文件特定的配置
26.6.2.环境属性
27.开发Web应用程序
27.1.“Spring Web MVC框架”
27.1.1.Spring MVC自动配置
27.1.2.HttpMessageConverters
27.1.3.自定义JSON序列化器和反序列化器
27.1.4.MessageCodesResolver的信息
27.1.5.静态内容
27.1.6.欢迎页面
27.1.7.自定义Favicon
27.1.8.路径匹配和内容协商
27.1.9.ConfigurableWebBindingInitializer
10年1月27日.模板引擎
11年1月27日.错误处理
自定义错误页面
Mapping Error Pages outside of Spring MVC
12年1月27日.春天的HATEOAS
13年1月27日.CORS支持
27.2.“Spring WebFlux框架”
27.2.1.Spring WebFlux自动配置
27.2.2.使用HttpMessageReaders和HttpMessageWriters的HTTP编解码器
27.2.3.静态内容
27.2.4.模板引擎
27.2.5.错误处理
自定义错误页面
27.2.6.网页过滤器
27.3.JAX-RS和泽西岛
27.4.嵌入式Servlet容器支持
27.4.1.Servlet,过滤器和监听器
将Spring Servlet,过滤器和监听器注册为Spring Bean
27.4.2.Servlet上下文初始化
扫描Servlet,筛选器和侦听器
27.4.3.ServletWebServerApplicationContext
27.4.4.定制嵌入式Servlet容器
程序化定制
直接自定义ConfigurableServletWebServerFactory
27.4.5.JSP限制
28.安全
28.1.MVC安全
28.2.WebFlux安全
28.3.的OAuth2
28.3.1.客户
28.3.2.服务器
28.4.执行器安全
28.4.1.跨站请求伪造保护
29.使用SQL数据库
29.1.配置一个数据源
29.1.1.嵌入数据库支持
29.1.2.连接到生产数据库
29.1.3.连接到JNDI数据源
29.2.使用JdbcTemplate
29.3.JPA和“Spring Data”
29.3.1.实体类
29.3.2.Spring Data JPA存储库
29.3.3.创建和删除JPA数据库
29.3.4.在View中打开EntityManager
29.4. Using H2’s Web Console
29.4.1.更改H2 Console的路径
29.5.使用jOOQ
29.5.1.代码生成
29.5.2.使用DSLContext
29.5.3.jOOQ SQL方言
29.5.4.定制jOOQ
30.与NoSQL Technologies合作
30.1.Redis的
30.1.1.连接到Redis
30.2.MongoDB的
30.2.1.连接到MongoDB数据库
30.2.2.MongoTemplate
30.2.3.Spring Data MongoDB存储库
30.2.4.嵌入式Mongo
30.3.Neo4j的
30.3.1.连接到Neo4j数据库
30.3.2.使用嵌入式模式
30.3.3.Neo4jSession
30.3.4.Spring Data Neo4j存储库
30.3.5.存储库示例
30.4.的GemFire
30.5.Solr的
30.5.1.连接到Solr
30.5.2.Spring Data Solr存储库
30.6.Elasticsearch
30.6.1.使用Jest连接到Elasticsearch
30.6.2.通过使用Spring数据连接到Elasticsearch
30.6.3.Spring Data Elasticsearch存储库
30.7.卡桑德拉
30.7.1.连接到Cassandra
30.7.2.Spring Data Cassandra存储库
30.8.Couchbase
30.8.1.连接到Couchbase
30.8.2.Spring Data Couchbase存储库
30.9.LDAP
30.9.1.连接到LDAP服务器
30.9.2.Spring数据LDAP存储库
30.9.3.嵌入式内存LDAP服务器
30.10.InfluxDB
30.10.1.连接到InfluxDB
31.缓存
31.1.支持的缓存提供程序
31.1.1.通用
31.1.2.JCache(JSR-107)
31.1.3.EhCache 2.x
31.1.4.Hazelcast
31.1.5.Infinispan的
31.1.6.Couchbase
31.1.7.Redis的
31.1.8.咖啡因
31.1.9.简单
10年1月31日.没有
32. Messaging
32.1.JMS
32.1.1.ActiveMQ支持
32.1.2.Artemis支持
32.1.3.使用JNDI ConnectionFactory
32.1.4.发送消息
32.1.5.接收消息
32.2.AMQP
32.2.1.RabbitMQ支持
32.2.2. Sending a Message
32.2.3.接收消息
32.3.Apache Kafka支持
32.3.1.发送消息
32.3.2.接收消息
32.3.3.额外的卡夫卡属性
33.用REST调用REST服务 RestTemplate
33.1.RestTemplate自定义
34.用REST调用REST服务 WebClient
34.1.WebClient自定义
35.验证
36.发送电子邮件
37.与JTA的分布式事务
37.1.使用Atomikos事务管理器
37.2.使用Bitronix事务管理器
37.3.使用Narayana事务管理器
37.4.使用Java EE托管事务管理器
37.5.混合XA和非XA JMS连接
37.6.支持替代嵌入式事务管理器
38. Hazelcast
39.石英调度器
40.春季融合
41. Spring Session
42.通过JMX进行监视和管理
43.测试
43.1.测试范围依赖关系
43.2.测试Spring应用程序
43.3.测试Spring Boot应用程序
43.3.1. Detecting Web Application Type
43.3.2.检测测试配置
43.3.3.不包括测试配置
43.3.4.使用运行的服务器进行测试
43.3.5.使用JMX
43.3.6.嘲笑和侦察豆
43.3.7. Auto-configured Tests
43.3.8.自动配置的JSON测试
43.3.9.自动配置的Spring MVC测试
43.3.10.自动配置的Spring WebFlux测试
43.3.11.自动配置的数据JPA测试
43.3.12.自动配置的JDBC测试
43.3.13.自动配置的jOOQ测试
43.3.14.自动配置的数据MongoDB测试
43.3.15.自动配置的数据Neo4j测试
43.3.16.自动配置的数据Redis测试
43.3.17.自动配置的数据LDAP测试
43.3.18.自动配置的REST客户端
43.3.19.自动配置的Spring REST Docs测试
自动配置的Spring REST Docs使用Mock MVC进行测试
自动配置的Spring REST Docs使用REST Assured进行测试
43.3.20.用户配置和切片
43.3.21.使用Spock测试Spring Boot应用程序
43.4.测试实用程序
43.4.1.ConfigFileApplicationContextInitializer
43.4.2.TestPropertyValues
43.4.3.OutputCapture
43.4.4.TestRestTemplate
44. WebSockets
45.网络服务
46.创建您自己的自动配置
46.1.了解自动配置的Bean
46.2.查找自动配置候选人
46.3.条件注释
46.3.1.班级条件
46.3.2.豆条件
46.3.3.财产状况
46.3.4.资源条件
46.3.5.Web应用程序条件
46.3.6.SpEL表达条件
46.4.测试你的自动配置
46.4.1.模拟Web上下文
46.4.2.覆盖类路径
46.5.创建你自己的启动器
46.5.1.命名
46.5.2.autoconfigure模
46.5.3.入门模块
47. Kotlin的支持
47.1.要求
47.2.空安全
47.3.Kotlin API
47.3.1.runApplication
47.3.2.扩展
47.4.依赖管理
47.5. @ConfigurationProperties
47.6.测试
47.7.资源
47.7.1.进一步阅读
47.7.2.例子
48.下一步阅读什么
V. Spring Boot执行器:生产就绪功能
49.启用生产就绪功能
50.终点
50.1.启用端点
50.2. Exposing Endpoints
50.3.保护HTTP端点
50.4.配置端点
50.5.执行器Web终端的超媒体
50.6.执行器Web端点路径
50.7.CORS支持
50.8.实现自定义端点
50.8.1.接收输入
输入类型转换
50.8.2.自定义Web端点
Web端点请求谓词
路径
HTTP方法
消费
产生
Web端点响应状态
Web端点范围请求
Web端点安全
50.8.3.Servlet端点
50.8.4.控制器端点
50.9.健康信息
50.9.1.自动配置的HealthIndicators
50.9.2.编写自定义HealthIndicators
50.9.3.反应性健康指标
50.9.4.自动配置的ReactiveHealthIndicators
50.10.应用信息
50.10.1.自动配置InfoContributors
50.10.2.自定义应用信息
50.10.3.Git提交信息
50.10.4.构建信息
50.10.5.编写自定义InfoContributors
51.通过HTTP进行监控和管理
51.1.自定义管理端点路径
51.2.自定义管理服务器端口
51.3.配置管理特定的SSL
51.4.自定义管理服务器地址
51.5.禁用HTTP端点
52.通过JMX进行监控和管理
52.1.定制MBean名称
52.2.禁用JMX终结点
52.3.通过HTTP使用Jolokia进行JMX
52.3.1.定制Jolokia
52.3.2.禁用Jolokia
伐木者
53.1.配置记录器
54.度量
54.1.入门
54.2.支持的监测系统
54.2.1.舆图
54.2.2.Datadog
54.2.3.神经节
54.2.4.石墨
54.2.5.辐辏
54.2.6.JMX
54.2.7.新的遗物
54.2.8.普罗米修斯
54.2.9.SignalFx
54.2.10.简单
54.2.11.StatsD
54.2.12.波前
54.3.支持的度量标准
54.3.1.Spring MVC度量标准
54.3.2.Spring WebFlux指标
54.3.3.RestTemplate指标
54.3.4.高速缓存指标
54.3.5.数据源指标
54.3.6.RabbitMQ指标
54.4.注册自定义指标
54.5.自定义各个指标
54.5.1.每米性能
54.6.指标终点
55.审计
56. HTTP跟踪
56.1.自定义HTTP跟踪
57.过程监测
57.1.扩展配置
57.2.编程
58. Cloud Foundry支持
58.1. Disabling Extended Cloud Foundry Actuator Support
58.2.Cloud Foundry自签名证书
58.3.自定义上下文路径
59.接下来要读什么
VI.部署Spring Boot应用程序
60.部署到云
60.1.Cloud Foundry
60.1.1.绑定到服务
60.2.Heroku的
60.3.OpenShift
60.4.亚马逊网络服务(AWS)
60.4.1.AWS Elastic Beanstalk
使用Tomcat平台
使用Java SE平台
60.4.2.概要
60.5.Boxfuse和亚马逊网络服务
60.6.Google Cloud
61.安装Spring Boot应用程序
61.1.支持的操作系统
61.2.Unix / Linux服务
61.2.1.安装即init.d服务(System V)
确保init.d服务
61.2.2.安装即systemd服务
61.2.3.自定义启动脚本
在写入时自定义启动脚本
在运行时自定义脚本
61.3.Microsoft Windows服务
62.接下来要读什么
七.Spring Boot CLI
63.安装CLI
64.使用CLI
64.1.使用CLI运行应用程序
64.1.1.推导出“抢”依赖
64.1.2.推导出“抢”坐标
64.1.3.默认导入语句
64.1.4.自动主要方法
64.1.5.定制依赖管理
64.2.有多个源文件的应用程序
64.3.打包你的应用程序
64.4.初始化新项目
64.5.使用嵌入式外壳
64.6.向CLI添加扩展
65.使用Groovy Beans DSL开发应用程序
66.使用CLI配置CLI settings.xml
67.接下来要读什么
八.构建工具插件
68. Spring Boot Maven插件
68.1.包括插件
68.2.打包可执行的jar和war文件
69. Spring Boot Gradle插件
70. Spring Boot AntLib模块
70.1.Spring Boot Ant任务
70.1.1. spring-boot:exejar
70.1.2.例子
70.2. spring-boot:findmainclass
70.2.1.例子
71.支持其他构建系统
71.1.重新包装档案
71.2.嵌套库
71.3.找到一个主要类
71.4.示例重新打包实施
72.接下来要读什么
IX.'指导'指南
73. Spring Boot Application
73.1.创建你自己的FailureAnalyzer
73.2.解决自动配置问题
73.3.在开始之前自定义环境或ApplicationContext
73.4.构建ApplicationContext层次结构(添加父级或根级上下文)
73.5.创建一个非Web应用程序
74.属性和配置
74.1.在构建时自动扩展属性
74.1.1.使用Maven自动扩展属性
74.1.2.使用Gradle的自动属性扩展
74.2.外部化配置SpringApplication
74.3.更改应用程序的外部属性的位置
74.4.使用'短'命令行参数
74.5.使用YAML作为外部属性
74.6.设置活动的弹簧配置文件
74.7.根据环境更改配置
74.8.发现外部属性的内置选项
75.嵌入式Web服务器
75.1.使用另一个Web服务器
75.2.禁用Web服务器
75.3.更改HTTP端口
75.4.使用随机未分配的HTTP端口
75.5.在运行时发现HTTP端口
75.6.启用HTTP响应压缩
75.7.配置SSL
75.8.配置HTTP / 2
75.8.1.HTTP / 2与Undertow
75.8.2.HTTP / 2与Jetty
75.8.3.HTTP / 2与Tomcat
75.9.配置Web服务器
75.10.将Servlet,Filter或Listener添加到应用程序中
75.10.1.使用Spring Bean添加Servlet,Filter或Listener
禁用Servlet或Filter的注册
75.10.2.通过使用类路径扫描添加Servlet,筛选器和监听器
75.11.配置访问日志记录
75.12.运行在前端代理服务器后面
75.12.1.自定义Tomcat的代理配置
75.13.使用Tomcat启用多个连接器
75.14.使用Tomcat的LegacyCookieProcessor
75.15.使用Undertow启用多个监听器
75.16.使用@ServerEndpoint创建WebSocket端点
76. Spring MVC
76.1.编写一个JSON REST服务
76.2.编写一个XML REST服务
76.3.自定义Jackson ObjectMapper
76.4.自定义@ResponseBody呈现
76.5.处理多部分文件上传
76.6.关闭Spring MVC DispatcherServlet
76.7.关闭默认的MVC配置
76.8.自定义ViewResolvers
77.泽西岛
77.1.使用Spring Security来保护Jersey端点
HTTP客户端
78.1.配置RestTemplate以使用代理
记录
79.1.配置Logback进行日志记录
79.1.1.为纯文件输出配置Logback
79.2.配置Log4j进行日志记录
79.2.1.使用YAML或JSON配置Log4j 2
80.数据访问
80.1.配置一个自定义数据源
80.2.配置两个数据源
80.3.使用Spring数据存储库
80.4.Spring配置分离@实体定义
80.5.配置JPA属性
80.6.配置Hibernate命名策略
80.7.使用自定义EntityManagerFactory
80.8.使用两个EntityManagers
80.9.使用传统persistence.xml文件
80.10.使用Spring Data JPA和Mongo仓库
80.11.将Spring数据存储库公开为REST端点
80.12.配置由JPA使用的组件
80.13.用两个数据源配置jOOQ
81.数据库初始化
81.1.使用JPA初始化数据库
81.2.使用Hibernate初始化数据库
81.3.初始化数据库
81.4.初始化一个Spring批处理数据库
81.5.使用更高级别的数据库迁移工具
81.5.1.启动时执行Flyway数据库迁移
81.5.2.在启动时执行Liquibase数据库迁移
82.消息
82.1.禁用事务处理JMS会话
83.批量应用程序
83.1.在启动时执行Spring批处理作业
84.执行器
84.1.更改执行器端点的HTTP端口或地址
84.2.自定义'whitelabel'错误页面
84.3. Sanitize sensible values
85.安全
85.1.关闭Spring Boot安全配置
85.2.更改UserDetailsS​​ervice和添加用户帐户
85.3.在代理服务器后运行时启用HTTPS
86.热插拔
86.1.重新加载静态内容
86.2.重新加载模板而不重新启动容器
86.2.1.Thymeleaf模板
86.2.2.FreeMarker模板
86.2.3.Groovy模板
86.3.快速应用程序重启
86.4.重新加载Java类而不重新启动容器
87.建设
87.1.生成构建信息
87.2.生成Git信息
87.3.自定义依赖版本
87.4.用Maven创建一个可执行的JAR
87.5.使用Spring Boot应用程序作为依赖项
87.6.当可执行jar运行时提取特定的库
87.7.用排除项创建一个不可执行的JAR
87.8.远程调试Maven启动的Spring Boot应用程序
87.9.在不使用的情况下从Ant构建可执行文件spring-boot-antlib
88.传统部署
88.1.创建一个可部署的战争文件
88.2.将现有的应用程序转换为Spring Boot
88.3.将WAR部署到WebLogic
88.4.使用Jedis代替生菜
十,附录
A.通用应用程序属性
B. Configuration Metadata
B.1.元数据格式
B.1.1.组属性
B.1.2.属性属性
B.1.3.提示属性
B.1.4.重复的元数据项目
B.2. Providing Manual Hints
B.2.1.价值提示
B.2.2.价值提供者
任何
类参考
处理为
记录器名称
Spring Bean参考
Spring配置文件名称
B.3.使用注释处理器生成您自己的元数据
B.3.1.嵌套属性
B.3.2.添加额外的元数据
C.自动配置类
C.1.从“spring-boot-autoconfigure”模块
C.2.从“spring-boot-actuator-autoconfigure”模块
D.测试自动配置注释
E.可执行的Jar格式
E.1.嵌套JAR
E.1.1. The Executable Jar File Structure
E.1.2.可执行的战争文件结构
E.2.Spring Boot的“JarFile”类
E.2.1.与标准Java“JarFile”兼容
E.3.启动可执行的罐子
E.3.1.启动器清单
E.3.2.爆炸档案
E.4. PropertiesLauncher Features
E.5.可执行的瓶子限制
E.6.替代性单罐解决方案
F.依赖版本
项目管理
2018-06-27 17:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文档提供了 Confluence 的数据结构视图(schema )和数据模型概念上的的概述。
备注: Hibernate 的映射文件是针对 Confluence 数据模型的直接描述。在系统中的 Confluence 主 JRA 中你可以找到 *.hbm.xml 文件,JRA 位于() \confluence\WEB-INF\lib\confluence-5.1.1.jar )。 数据库的表,列和其他的属性可能随着 Confluence 的主要发行版本的变化而有所变化。希望找到你 Confluence 站点的数据库定义语言(DDL),请在 Confluence 安装后运行查询。
https://www.cwiki.us/display/CONFLUENCEWIKI/Confluence+Data+Model
项目管理
2018-06-29 22:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在java中final、finally、finalize三者在写法上很相近,但是实际含义和作用却是相差甚远。本文主要是来深层的研究下这三者的用法以及之间的区别
1.final
属性:java中的关键字,修饰符
作用:用于修饰类、成员方法、变量(成员变量、局部变量)
用法:如果 类 被声明为final,那么该 类 就不能再派生出新的 子类 ,也不能当作父类被子类继承。一个类不能同时被声明为抽象类(absrtact修饰)和final的类;如果 成员方法 被声明final,那么该方法只能使用,不能重载;如果 变量(成员变量、局部变量) 被声明为final,那么必须在声明时给定初始化的值,在后面的引用中只能读取,不可修改值。
注意:1. 类 被声明为final,类中的所有成员方法都会被隐式地指定为final方法; 2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则编译时会报错; 3.在匿名类(内部类)中所有变量都必须是final变量; 4.在接口中声明的所有变量本身是final的; 5.如果基本数据类型的变量被声明为final,则其数值一旦在初始化之后便不能更改
如果引用类型的变量被声明为final,则在对其初始化之后便不能再让其指向另一个对象,但该对象的内容是可以改变的
6.当final变量是基本数据类型以及String类型时,在编译期间是知道它的确切值,那么编译器会把它当做编译期常量,在用到该final变量的地方,相当于直接访问的这个常量

只有在编译期间能确定final变量值的时候才会被当编译常量,以下是编译时不知道确定值的
2.finally
属性:异常处理时的finally块
作用: try { 正常逻辑 } catch(Exception e) { 异常逻辑 } finally{ 一定会被执行的逻辑 }
用法:异常处理(try catch)时finally块无论有没有异常发生,finally块的代码一定会被执行,所以在程序中有需要无论发生什么都必须执行的代码,就可以放在finally块中,最常见流关闭等释放资源的操作
注意:1.finally代码块无论有没有异常发生,finally块的代码一定会被执行; 2.即使try里包含continue、break、return语句,try块结束后,finally块也会执行; 3.finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值
3.finalize
属性:方法名,Object中的方法
作用:finalize()方法是在垃圾收集器删除对象之前对这个对象调用
用法:Java中使用finalize()方法在垃圾收集器将对象从内存中清除出去之前(GC之前)做必要的清理内存的工作。这个方法是在垃圾收集器确认一个对象没有被引用时对这个对象调用的。它在Object类中定义的,所有的类都继承了它。子类覆盖finalize()方法已整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的
注意:1.垃圾回收器要回收对象的时候,首先要调用这个类的finalize方法
2.程序退出时为每个对象调用一次finalize方法
3.当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,但是jvm不保证finalize()一定被调用
4.finalize()方法被关键字protected修饰是防止在该类之外定义的代码访问finalize()标识符
5.finalize()方法的主要用途是释放一些其他做法开辟的内存空间,以及做一些清理工作
6. 一旦垃圾回收器GC准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工 作。只有到下一次再进行垃 圾 回收动作的时候,才会真正释放这个对象所占用的内存空间
7.finalize()现在已经不推荐使用了,java9已经设置为 deprecated了
项目管理
2018-06-29 16:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
第一种写法 : then when select CASE '需要判断的字段' when '条件' then '成功时的返回值' when '条件' then '成功时的返回值' else '前面的when全部不符合才执行的结果' end form a
第二种方法 : if function SELECT if(`需要判断的字段`="条件", '成功时返回','失败时返回') from a
项目管理
2018-06-29 15:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
通过Spring的@Scheduled可以创建定时任务。 引入 xmlns:task 命名空间; 在task任务执行类引入注解:@Component@EnableScheduling; 配置定时执行任务:@Scheduled(cron = "0/10 * * * * *");
任务执行task: @Component @EnableScheduling public class TestDaemon { @Scheduled(cron = "0/10 * * * * *") public void testRunTask(){ try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } DateFormat df = new SimpleDateFormat("HH:mm:ss"); System.out.println(df.format(new Date()) + "********A任务每10秒执行一次进入测试"); } } @Scheduled(cron = "0/5 * * * * *") private void fullQueueTask() { System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date()) + "********B任务每5秒执行一次进入测试"); }
跑起来: 13:21:25********B任务每5秒执行一次进入测试 13:21:30********B任务每5秒执行一次进入测试 13:21:50********A任务每10秒执行一次进入测试 13:21:50********B任务每5秒执行一次进入测试 13:21:55********B任务每5秒执行一次进入测试 13:22:00********B任务每5秒执行一次进入测试 13:22:20********A任务每10秒执行一次进入测试 13:22:20********B任务每5秒执行一次进入测试 13:22:25********B任务每5秒执行一次进入测试
我们发现B并没有每隔5秒钟执行,而是当A任务启动后,B任务需要等待A任务执行完成之后继续执行。 这是因为默认的@Scheduled是单线程执行的,所有任务需要互相排队。
我们在业务上肯定需要不同调度任务有自己的节奏,单线程是满足不了了,Spring为我们提供了多线程的调度方式。
XML:
引入xml:
再次执行: 13:26:00********B任务每5秒执行一次进入测试 13:26:05********B任务每5秒执行一次进入测试 13:26:10********B任务每5秒执行一次进入测试 13:26:15********B任务每5秒执行一次进入测试 13:26:20********B任务每5秒执行一次进入测试 13:26:20********A任务每10秒执行一次进入测试 13:26:25********B任务每5秒执行一次进入测试 13:26:30********B任务每5秒执行一次进入测试 13:26:35********B任务每5秒执行一次进入测试 13:26:40********B任务每5秒执行一次进入测试 13:26:45********B任务每5秒执行一次进入测试 13:26:50********A任务每10秒执行一次进入测试 13:26:50********B任务每5秒执行一次进入测试 13:26:55********B任务每5秒执行一次进入测试
我们发现B任务是按照自己的节奏进行,每隔5秒执行一次。
关于task:executor和task:scheduler的区别
默认任务调度是单线程的,通过task:scheduler我们可以创建一个对应的任务调度线程池执行任务,这样多任务调度就不需要串行执行了。
当我们采用@Async异步执行逻辑时,使用的线程是task:executor中的线程,如果用@Async修饰一个调度任务,则调度线程池就会不用当前调度线程来执行,而是交给 task:executor 这个执行线程池来执行。
所以配置 task:scheduler 参数的线程池,只和调度任务有关,是为了根据任务总数来分配调度线程池的大小; 而配置 task:executor ,只和@Async异步任务有关,是为了某个任务如果要异步的执行时,实现当前任务内的多线程并发。
项目管理
2018-06-29 13:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 循环list中的所有元素然后删除重复 public static List removeDuplicate(List list) { for (int i = 0; i < list.size() - 1; i++) { for (int j = list.size() - 1; j > i; j--) { if (list.get(j).equals(list.get(i))) { list.remove(j); } } } return list; }
通过HashSet踢除重复元素 public static List removeDuplicate(List list) { HashSet h = new HashSet(list); list.clear(); list.addAll(h); return list; }
删除ArrayList中重复元素,保持顺序 // 删除ArrayList中重复元素,保持顺序 public static void removeDuplicateWithOrder(List list) { Set set = new HashSet(); List newList = new ArrayList(); for (Iterator iter = list.iterator(); iter.hasNext();) { Object element = iter.next(); if (set.add(element)) newList.add(element); } list.clear(); list.addAll(newList); System.out.println(" remove duplicate " + list); }
把list里的对象遍历一遍,用list.contain(),如果不存在就放入到另外一个list集合中 public static List removeDuplicate(List list) { List listTemp = new ArrayList(); for (int i = 0; i < list.size(); i++) { if (!listTemp.contains(list.get(i))) { listTemp.add(list.get(i)); } } return listTemp; }
项目管理
2018-06-29 11:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
方法一:简单实用
1、需要在hitbucket上创建一个空仓库。
2、在本地创建一个保存svn工程的文件夹。
3、进入到该文件夹内,右键打开git bash.
4、在该文件夹下创建一个user.txt文件,然后里面按照 SVN用户名=Git用户名 的形式保存,如下所示:(注意,该user文件中的用户信息包含迁移项目中所有的提交用户,并且将自己的用户信息放在首位的话,从svn上down代码的话可能会快点)
SVN用户名=Git用户名
wx_shikq=wx_shikq
wx_wenwj=wx_wenwj
wx_zhaoyz=wx_zhaoyz
5、在打开的git bash上,通过git命令,将要迁移的svn代码从svn库上down到刚建的文件夹下,并转化为本地git库,具体代码为:
git svn clone -T trunk --no-metadata -A user.txt http://172.16.125.37/svn/FARE_Pricing/coding/01GUI/JCF/AFDC
其中,-T后面的文件夹是想要down的svn的该文件夹,后面的svn路径是该文件夹trunk的上一层。上面命令意思是将svn路径为:http://172.16.125.37/svn/FARE_Pricing/coding/01GUI/JCF/AFDC/trunk路径下的项目文件down到本地,并转化为git文件
6、执行完后会在所建的文件夹下有个新建的trunk目录,名称为AFDC,cd到AFDC目录下,会有down的所有内容,并且有个.git文件夹,此时,AFDC文件夹为一个本地git仓库了(此时,可以对本次仓库代码进行增删改等修改,然后通过add / commit 等命令在本地仓库上进行操作)
7、然后按照首次将本地库上传到远程hitbucket仓库上进行操作:
git remote add origin ssh://git@rdgit.travelsky.com:7999/fgui/afdc.git
(ssh://git@rdgit.travelsky.com:7999/fgui/afdc.git为远程hitbucket库地址)
8、然后进行git命令操作:git push -u origin master
9、执行完8后,hitbucket库上就有上传的代码了,通知,之前在svn上的提交记录也都有
项目管理
2018-06-29 11:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
这一篇写有向无环图及其它的应用:

清楚概念:
有向无环图(DAG):一个无环的有向图。通俗的讲就是从一个点沿着有向边出发,无论怎么遍历都不会回到出发点上。
有向无环图是描述一项工程或者系统的进行过程的有效工具,比如办公室,到工商局里面注册的时候,他会提示你一个流程,这个流程就是一个有向无环图。
第一步不做,第二步就做不了。
在其期间关心两个问题:
1.工程是否顺利?(拓扑排序)
2.估算整个工程所必须的最短时间。(关键路径)

拓扑排序:
数学语言:某个集合上的一个偏序得到该集合上的一个全序的操作过程。
百度百科: 拓扑序列
通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。离散数学中关于偏序和全序的定义:
若集合X上的关系是R,且R是自反的、反对称的和传递的,则称R是集合X上的偏序关系。
设R是集合X上的偏序(Partial Order),如果对每个x,y属于X必有xRy 或 yRx,则称R是集合X上的全序关系。
比较简单的理解:偏序是指集合中只有部分成员可以比较,全序是指集合中所有的成员之间均可以比较。
注意:
①若将图中顶点按拓扑次序排成一行,则图中所有的有向边均是从左指向右的。
②若图中存在有向环,则不可能使顶点满足拓扑次序。
③一个DAG的拓扑序列通常表示某种方案切实可行。
看下面一幅图:
这是一个偏序,1到5,可以 1-3-5 也可以是 1-2-5
2和3没有先后,我们认为的加上 2先于3 或者 3先于2,这样的过程就将该偏序图(1,2,3,5)变成了全序图。
我们定义1-2-3-5这个序列称为拓扑有序。而这个输出的过程就是拓扑排序。拓扑排序结果不止一种。这个图可以看到
1-2-3-5 和1-3-2-5都是排序的结果。
拓扑排序:就是对一个有向图构造拓扑有序的过程。

我们把每个顶点看作是一个子过程,边表示子过程的优先顺序,这样的图我们可以定义为AOV。AOV(Activity On Vertex Network)

如果拓扑排序:
(1)在有向图中选一个没有前驱的顶点且输出之
(2)从图中删除该顶点和所有以它为尾的弧
重复上述步骤,直至全部点输出,或者当图中的顶点不存在前驱为止(有环)。

涉及到了有向图,有前驱和后驱,这里的数据结构也要跟着改变。
之前使用过邻接表,这里需要在邻接表的顶点信息里面添加一个入度的信息即可。 #define MAXVEX 100 #define IFY 65535 typedef char VertexType; typedef int EdgeType; typedef int IdxType; typedef int QueueType; typedef int StackType; ///--------------------------------------- //边节点 typedef struct EdgeNode{ IdxType idx; struct EdgeNode* next; }eNode; //顶点节点 typedef struct VexNode{ int numIn; //入度数量 IdxType idx; eNode *fitstedge; }vNode; //图的集合:包含了一个顶点数组 typedef struct { vNode adjList[MAXVEX]; int numVextexs,numEdges; }GraphAdjList;
拓扑排序的代码: int TopplogicalSort(GraphAdjList *g) { int count=0; eNode *e=NULL; StackType *stack=NULL; StackType top=0; stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType)); int i; for (i=0;i<(*g).numVextexs;i++) { if (!(*g).adjList[i].numIn) { stack[++top] = i; // printf("init no In is %c\n",g_init_vexs[i]); } } while(top) { int geter = stack[top]; top--; printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]); count++; //获取当前点出度的点,对出度的点的入度减一(当前点要出图)。 //获取当前顶点的出度点表 e = (*g).adjList[geter].fitstedge; while(e) { //选取的出度点的入度减一 int crntIN = --(*g).adjList[e->idx].numIn; if (crntIN == 0) { //如果为0,则说明该顶点没有入度了,是下一轮的输出点。 stack[++top] = e->idx; // printf("running the vex is %c\n",g_init_vexs[e->idx]); } e = e->next; } } if (count < (*g).numVextexs)//如果图本身就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。 { return false; } else { printf("finish\n"); return true; } }
以下图为例做测试:
找出入度为0的顶点:A、G







源代码: // grp-top-sort.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #define MAXVEX 100 #define IFY 65535 typedef char VertexType; typedef int EdgeType; typedef int IdxType; typedef int QueueType; typedef int StackType; ///--------------------------------------- //边节点 typedef struct EdgeNode{ IdxType idx; struct EdgeNode* next; }eNode; //顶点节点 typedef struct VexNode{ int numIn; //入度数量 IdxType idx; eNode *fitstedge; }vNode; //图的集合:包含了一个顶点数组 typedef struct { vNode adjList[MAXVEX]; int numVextexs,numEdges; }GraphAdjList; ///----------------------------------- VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J','K','L'}; char *g_input[] = { "A->B->C->D", "B->E", "C->F->I->J", "D->E->I->J", "E", "F->K", "G->F->H->K", "H->I", "I->J->L", "J->E->K", "K->L", "L" }; //=============================================================== //队列 //队列节点 typedef struct Node { QueueType data; struct Node *next; }QNode,*qQNode; //队列指示 typedef struct { int length; qQNode frnt,rear; }spQueue; void init_Queue(spQueue *Q) { (*Q).frnt = NULL; (*Q).rear = NULL; (*Q).length = 0; } bool isEmptyQueue(spQueue Q) { if (Q.length == 0) { return true; } return false; } //进队 void unshiftQueue(spQueue *Q,QueueType elem) { //队列空 if (isEmptyQueue(*Q)) { qQNode n = (qQNode)malloc(sizeof(QNode)); n->data = elem; n->next = NULL; (*Q).frnt = n; (*Q).rear = n; (*Q).length = 1; } else { qQNode n = (qQNode)malloc(sizeof(QNode)); n->data = elem; n->next = NULL; (*Q).rear->next = n; (*Q).rear = n; (*Q).length++; } } //出队 QueueType shiftQueue(spQueue *Q) { if (isEmptyQueue(*Q)) { printf("Warning:Queue is empty!!!\n"); return NULL; } if ((*Q).length == 1) { QueueType sh = (*Q).frnt->data; (*Q).frnt = NULL; (*Q).rear = NULL; (*Q).length = 0; return sh; } QueueType sh = (*Q).frnt->data; (*Q).frnt = (*Q).frnt->next; (*Q).length--; return sh; } //打印队列 void prt_que(spQueue que) { if (isEmptyQueue(que)) { return ; } qQNode pos = que.frnt; while(que.rear->next != pos && pos != NULL) { printf(" %d ",pos->data); pos = pos->next; } printf("\n"); } //=============================================================== ///------- //由节点找节点的序号 IdxType strFindIdx(char ch) { int i=0; VertexType *p = g_init_vexs; while(p != NULL) { if(*p == ch) { return i; } p++; i++; } return i; } //由序号找节点 VertexType idxFindStr(IdxType i) { return g_init_vexs[i]; } void prt_strings(char *p) { char *pos = p; while (NULL != *pos) { printf("%c",*pos); pos++; } printf("\n"); } void prt_strArrays(char *p[]) { char **pos = p; while( *pos != NULL) { prt_strings(*pos); pos++; } } //我规定:顶点只能是大写。 bool isVexter(char p) { if (p>='A' && p<='Z') { return true; } return false; } void init_GrapAdjList(GraphAdjList *g,char **str) { char **pos = str; int cnt=0; //入度清零 int i; for (i=0;iidx = strFindIdx(**pos); n->next = NULL; (*g).adjList[cnt].fitstedge = n; i=2; //添加入度 int iidx = strFindIdx(**pos); (*g).adjList[iidx].numIn++; } else //边节点连接到前一个边节点上 { eNode* n = (eNode*)malloc(sizeof(eNode)); n->idx = strFindIdx(**pos); n->next = NULL; //first splist eNode *r = (*g).adjList[cnt].fitstedge; while (r->next != NULL) { r = r->next; } r->next = n; //添加入度 int iidx = strFindIdx(**pos); (*g).adjList[iidx].numIn++; } } (*pos)++; } pos++; cnt++; } (*g).numVextexs = cnt; } int TopplogicalSort(GraphAdjList *g) { int count=0; eNode *e=NULL; StackType *stack=NULL; StackType top=0; stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType)); int i; for (i=0;i<(*g).numVextexs;i++) { if (!(*g).adjList[i].numIn) { stack[++top] = i; // printf("init no In is %c\n",g_init_vexs[i]); } } while(top) { int geter = stack[top]; top--; printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]); count++; //获取当前点出度的点,对出度的点的入度减一(当前点要出图)。 //获取当前顶点的出度点表 e = (*g).adjList[geter].fitstedge; while(e) { //选取的出度点的入度减一 int crntIN = --(*g).adjList[e->idx].numIn; if (crntIN == 0) { //如果为0,则说明该顶点没有入度了,是下一轮的输出点。 stack[++top] = e->idx; // printf("running the vex is %c\n",g_init_vexs[e->idx]); } e = e->next; } } if (count < (*g).numVextexs)//如果图本身就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。 { return false; } else { printf("finish\n"); return true; } } int _tmain(int argc, _TCHAR* argv[]) { GraphAdjList grp; prt_strArrays(g_input); init_GrapAdjList(&grp,g_input); if (!TopplogicalSort(&grp)) { printf("grp wrong!\n"); } getchar(); return 0; }
运行结果:

项目管理
2018-06-20 16:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.x86处理器系统地址空间简介
1.1 CPU地址空间
CPU地址空间是指CPU所能寻址的空间大小,比如对于32位CPU来说,其所能寻址的空间大小为0~4G。这是由CPU自身的地址总线数目决定的。这段空间也被称作CPU物理地址空间。
1.2 内存地址空间
内存地址空间就是指内存控制器所能寻址的空间大小。在x86处理器系统中,内存地址空间是CPU地址空间的一部分。在32位x86系统中,并不是所有的内存地址空间都能被系统使用,如下图所示:
如上图所示,左侧0~4G为CPU所能寻址的地址空间,红框内的空间是能被CPU识别的内存地址空间。右侧为内存控制器所能寻址的空间,大小为4GB,但是只有一部分空间能被CPU所识别。这就是为什么4GB内存在32位x86系统只能被识别为3G多的原因,因为剩下的CPU地址空间被其他设备占用了,比如PCI设备、Local APIC或者IO APIC等,这些CPU地址空间对应的是这些设备的寄存器区域。
1.3 设备的内存映射空间
这里的设备内存映射空间是指硬件机制上实现的设备寄存器访问方式映射,而不是指MMU中虚拟地址到物理地址的映射。CPU通过CPU地址空间中的内存地址空间能访问内存设备,大概的访问流程如下图所示:
CPU发送相关信号给北桥,告知其要访问内存,然后北桥通过内存控制器来访问内存设备。在汇编指令级别,这是通过MOV指令实现的,比如MOV eax,[mem addr],就能触发北桥通过内存控制器访问内存。
在CPU地址空间中有一段空间叫做PCI Memory Range,这段空间也叫做PCI设备寄存器映射区。CPU通过访问这段空间,从而达到访问PCI设备的目的。由于这种方式同样是通过MOV指令实现的,故这种方式被称为内存映射访问,意思就是CPU能通过像访问内存那样的方式来访问这些设备。注意,CPU访问CPU地址空间上的设备都是通过MOV指令来实现的。CPU访问PCI设备的大概流程如下图所示:
CPU发送相关信号给北桥,告知其要访问PCI设备,然后北桥通过PCI控制器来访问PCI设备中的寄存器。
1.4 端口地址空间
端口地址访问是x86处理器系统中另外一种访问设备的方式,不同于CPU地址空间,端口地址空间只有64KB大小,为0~65535,这是因为访问端口时必须将端口地址放入dx寄存器,dx寄存器是16位的,故端口地址空间只有64KB大小。如下图所示:
端口其实就是设备中的寄存器,只不过访问这些寄存器并不是通过MOV指令来访问,而是通过IN/OUT指令来访问。比如在北桥中有一个端口,其地址为0xCF8,访问这个端口流程如下图所示:
CPU发送相关信号给北桥,告知其要访问0xCF8端口,北桥发现这个端口属于自己,直接访问。
项目管理
2018-06-20 10:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence 将会创建备份,同时压缩 XML 文件后存储熬你的 /backups> 目录中。你需要自己访问你安装的 Confluence 服务器,并且从服务器上获得这个文件。
运行从 Confluence 将导出文件下载
在默认情况下,你不能从你的 Confluence 中下载这个备份文件。这个功能被禁用的主要考虑是基于安全额考虑,但是你还是可以选择启用这个功能。一旦你启用了这个功能,Confluence 将会提示你下载备份文件,在你备份完成的时候。我们建议你在生产环境中 不开放 这个功能。
希望在 Confluence 中启用备份下载功能: 停止 Confluence. 编辑 \confluence.cfg.xml 文件。 修改 admin.ui.allow.manual.backup.download 为 true . 重启 Confluence。
如果上面的变量被设置为 'true' 的话,那么你就可以通过 Confluence管理员控制台直接下载已经备份成功的备份文件。
如果上面的变量被设置为 'false' 的话,你需要访问你 Confluence 的服务器,从服务器上直接下载备份的 ZIP 文件,在默认的情况下,这个变量是被设置为 'false' 的。

https://www.cwiki.us/display/CONFLUENCEWIKI/Manually+Backing+Up+the+Site
项目管理
2018-06-18 22:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
希望为你的站点创建一个 XML 导出文件: 进入 > 基本配置( General Configuration) > 备份和恢复(Backup & Restore) 。 选择 归档到备份目录(Archive to backups folder) 来存储备份文件的拷贝,这个文件夹与 Confluence 的 backups 目录是相同的。
如果你不归档备份,那么这个备份将会提供下载链接让你下载,然后将会在 24 小时候删除。 选择 备份附件( Backup attachments )包括到你的备份中。 选择 备份( Backup )。
整个备份进程将会耗费一些时间。

如果你遇到了超时的错误,尝试从 Tomcat 中创建一个导出目录,这个将会加快导出速度和避免超时错误。
例如,如果你的 URL 通常看起来是下面的格式 http://.com 。希望越过这个设置然后直接访问 Tomcat,你可以使用 URL http://localhost:8090/confluence/admin/backup.action . 直接访问你的服务器。
导出文件中包含了什么?
站点的导出文件中包括了空间(包括页面,博客,评论,附件和未发布的修改),用户和用户组,你站点实际使用的插件。

https://www.cwiki.us/display/CONFLUENCEWIKI/Manually+Backing+Up+the+Site
项目管理
2018-06-18 22:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Confluence 被配置自动备份数据,使用压缩的 XML 格式。同时你也可以通过 Confluence 的 管理员控制台(Administration Console) 手动进行备份。
你需要具有 System Administrator 权限才能进行这个操作。
需要了解: 我们推荐你使用 Production backup strategy ,尤其是你的站点具有大量的数据或者是针对任务的站点,我们不推荐你使用 XML 备份的方式备份你的站点。 插件是不会备份到 XML 的导出文件中的。在你使用 XML 备份重新导入新的站点后,你需要为你导入的 Confluence 新站点重新安装没有绑定到 plugindata 表中的所有的插件,这是因为这些插件不会手动备份的。 你不能导出你备份的内容到 Confluence 的早期站点中。换句话说,XML 备份是向下不兼容的。
https://www.cwiki.us/display/CONFLUENCEWIKI/Manually+Backing+Up+the+Site
项目管理
2018-06-18 22:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
下面的代码是用户提交的,在使用的时候需要小心,因为 Atlassian 不提供这些代码的技术支持。如果你在使用或者修改这些代码的时候有任何问题,请粘贴到 post them to Atlassian Answers 。
删除老的备份 —— Windows 的 Wscript 脚本
这个脚本将会检查备份文件然后删除他们(必要的话),下面的代码可能需要一些编辑。
'If you want 3 day old files to be deleted then insert 3 next to Date - "your number here"
'This script will search out and delete files with this string in them ".2005-12-04-" This of course depends on the number you enter.
'You can always do a wscript.echo strYesterday or strFileName to see what the script thinks you are searching for .

dtmYesterday = Date - 3

strYear = Year(dtmYesterday)

strMonth = Month(dtmYesterday)
If Len(strMonth) = 1 Then
strMonth = "0" & strMonth
End If

strDay = Day(dtmYesterday)
If Len(strDay) = 1 Then
strDay = "0" & strDay
End If

strYesterday = strYear & "-" & strMonth & "-" & strDay

strFileName = "C:\test*." & strYesterday & "-*"

Set objFSO = CreateObject( "Scripting.FileSystemObject" )
objFSO.DeleteFile(strFileName)
删除老的备份 —— Linux Basic Bash Script
Old 的 XML 备份可以通过每天晚上或者每周的自动运行脚本进行删除。你也可以在 cron 中设置相似的脚本:
ls -t /* | tail -n + 6 | xargs -i rm {}
或者,使用 tail 命令,如果你的系统不支持标准格式的话:
ls -t /* | tail + 6 | xargs -i rm {}
Del 删除老的备份 —— 高级 Linux Bash Script
Old 的 XML 备份可以通过每天晚上或者每周的自动运行脚本进行删除,针对你的站点设置 BACKUP_DIR 和 DAYS_TO_RETAIN 变量。在运行之间,相对 DAYS_TO_RETAIN 更多文件将会构建。
#!/bin/sh

# Script to remove the older Confluence backup files.
# Currently we retain at least the last two weeks worth
# of backup files in order to restore if needed.

BACKUP_DIR= "/data/web/confluence/backups"
DAYS_TO_RETAIN= 14

find $BACKUP_DIR -maxdepth 1 -type f -ctime +$DAYS_TO_RETAIN -delete
手动数据库和 Home 目录备份 —— Linux Basic Bash Script
这个将会备份 MySQL 数据库和 Confluence 的 Home 目录。
#!/bin/bash
CNFL=/var/confluence
CNFL_BACKUP=/backup/cnflBackup/`date +%Y%m%d-%H%M%S`

rm -rf $CNFL/temp/*
mkdir $CNFL_BACKUP
mysqldump -uroot -p confluence|gzip > $CNFL_BACKUP/confluence.mysql.data.gz
tar -cjvf $CNFL_BACKUP/data.bzip $CNFL > $CNFL_BACKUP/homedir.status
按照日期备份 —— Postgres
export d=`date +%u`
mkdir -p /home/backup/postgres/$d

sudo -u postgres pg_dumpall | bzip2 > /home/backup/postgres/$d/sql.bz2

https://www.cwiki.us/pages/viewpage.action?pageId=33004943
项目管理
2018-06-18 21:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文介绍RabbitMQ与Spring的简单集成以及消息的发送和接收。
在RabbitMQ的Spring配置文件中,首先需要增加命名空间。 xmlns:rabbit="http://www.springframework.org/schema/rabbit"
其次是模式文档,这里按1.0的来。 xsi:schemaLocation=" http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd"
配置connection-factory元素。
配置connection-factory元素实际是注册一个org.springframework.amqp.rabbit.connection.CachingConnectionFactory实例。
参数介绍:
id :bean的id值。
host :RabbitMQ服务器地址。默认值"localhost"。
port :RabbitMQ服务端口,默认值"5672"。
virtual-host :虚拟主机,默认是"/"。
username 和 password 就是访问RabbitMQ服务的账户和密码了。
channel-cache-size :channel的缓存数量。新版本默认是25。
消息队列queue的配置。
参数介绍:
name :queue的名字。
durable :是否为持久的。默认是true,RabbitMQ重启后queue依然存在。
auto-delete :表示消息队列没有在使用时将被自动删除。默认是false。
exclusive :表示该消息队列是否只在当前connection生效。默认false。
交换器exchange的配置。
参数介绍:
name :exchange的名字。
durable :是否为持久的,默认为true,RabbitMQ重启后exhange依然存在。
auto-delete :表示exchange在未被使用时是否自动删除,默认是false。
key :queue在该direct-exchange中的key值。当消息发送给该direct-exchange中指定key为设置值时,消息将会转发给queue参数指定的消息队列。
Spring为方便使用RabbitMQ服务,提供一个操作模板类:org.springframework.amqp.rabbit.core.RabbitTemplate。
配置也很简单。template还有其他的配置项,可以自己查看xsd文件中的说明。
最后一个配置项是消息consumer。其实也可以叫做listener。简单的配置如下。
messageConsumer是一个简单bean类,可以用注解标识。 @Component("messageConsumer") public class MessageConsumer implements MessageListener { public void onMessage(Message message) {} }
类作为消息监听器,必须实现接口MessageListener或者是接口ChannelAwareMessageListener。
另一种配置方式是使用method参数,指定消息处理的方法,以org.springframework.amqp.core.Message类作为方法参数。
配置完成并写好消息监听处理类后就可以尝试发送消息了。
public class MessageProducer{ @Resource private AmqpTemplate amqpTemplate; public void sendMessage(){ Message message = MessageBuilder.withBody("hello rabbit".getBytes("utf-8")) .setMessageId(System.currentTimeMillis()+"") .build(); this.amqpTemplate.send("queueTestKey", message); } }
消息监听方法。 public void onMessage(Message message){ String content = new String(message.getBody(),"utf-8"); system.out.println(content); }
消息内容发送时会被转换为字节数组,默认以UTF-8进行编码。如果想要发送对象信息,按照类实例的序列化和反序列化进行操作即可。
项目管理
2018-06-18 21:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
转自与 https://www.cnblogs.com/RunForLove/p/4641672.html
目前很多公司的架构,从Struts2迁移到了SpringMVC。你有想过为什么不使用Servlet+JSP来构建Java web项目,而是采用SpringMVC呢?
既然这样,我们从源头说起。Struts2的源头其实也是Servlet。Servlet的作用是接收浏览器传给服务端的请求(request),并将服务端处理完的响应(response)返回给用户的浏览器,浏览器和服务端之间通过http协议进行沟通,其过程是浏览器根据用户的选择将相关信息按http协议报文的规范组装请求的http报文,报文通过网络传输到指定的服务器,服务器通过特定的web容器接收这个报文信息,例如:tomcat,jetty,jboss这样的web容器,web容器会将http报文解析出来,如果是用户请求,最终解析出来的报文信息会用一个request对象存储起来,服务端使用这个request做完相应的处理后,服务端程序将结果信息封装到response对象里,然后将response对象交给web容器,web容器则把这个response对象转变为http协议的报文,并将报文回传给浏览器,浏览器最后解析这个响应报文,将最终结果展示给用户。
  Web容器创造了servlet接口,servlet接口就是开发人员自己实现业务逻辑的地方,程序员开发servlet就好比做填空题,而填空题的语境或者说上下文提示就是由request和response对象,但是javaEE规范里的servlet接口很简单,就三个方法init,service和destory,但是这个接口太笼统,所以规范里还提供了一个HttpServlet类,这个类根据http请求类型提供了doGet,doPost等方法,servlet接口最大的特点就是根据http协议的特点进行定义,因此做servlet开发时候如果使用者对http协议特点不是特别熟悉,都会碰到或多或少令人迷惑的问题,特别是碰到一些复杂特殊的请求时候:例如文件上传,返回特殊的文件格式到浏览器,这时候使用servlet开发就不是很方便了,servlet开发还有个问题可能大家常常被忽视,就是请求的数据的类型转化,http协议传输都是文本形式,到了web容器解析后也是文本类型,如果碰到货币,数字,日期这样的类型需要我们根据实际情况进行转化,如果页面传送的信息非常多,我们就不得不做大量类型转化,这种工作没有什么技术含量,是个体力活而且很容易导致程序错误。同时java的企业开发都是围绕javabean进行,类型转化好的数据还要封装到对应的javabean里,这种转来转去的事情对于项目开发绝对不是什么好事情,所以古老的struts1为这种问题找到了一种解决方案,就是定义了一个DTO对象(数据传输对象),专门负责做这样的事情,不过到了struts2,整个替代servlet的action本身就是一个javabean。
  Java的企业开发一个技术特点就是使用javabean进行的,struts2的特点之一就是它替代servlet的操作类就是一个典型的javabean,首先struts2框架将页面传输的数据进行类型转化和封装后将请求信息封装到了这个javabean的属性里,这样我们开发web程序时候就省去了烦心的类型转化和封装的问题,前面我讲到传统的servlet是根据http协议进行定义的,它会按你请求方式(post还是get方式)来处理用户的请求,但是对于一名程序开发人员而言,一个请求,具体到一个url,其实对于服务端而言就是服务端对外提供的一个功能,或者说是服务端对外的一个动作,如果我们使用servlet开发程序我们就得把http的动作转化为具体的业务动作,这就让程序开发变得繁琐,增强了开发的难度,所以struts2替代servlet的javabean就屏蔽了servlet里http的请求方式和具体业务动作转化的问题,javabean里的每一个方法都可以和每一个url请求一一对应,这必然减轻了开发的难度问题。
  Servlet另一个作用就是构造response对象,让页面获得正确的响应,其实现代的浏览器是一个多媒体工具,文字,图片,视屏等等东西都可以在浏览器里显示,资源的不同就会导致http响应报文的差别,如果我们使用servlet开发就要根据资源的不同在java程序里用硬编码的形式处理,这样的程序很难复用,而且如果程序员对某种资源的处理理解不到位,就会导致问题的出现,struts2通过配置文件的形式将这样的逻辑从java程序里剥离出来,使用配置的方式进行统一管理,这个做法和spring的AOP方式类似,这样就让结果处理方式更加统一,更加利于管理,同时也提升了程序的健壮性以及降低了开发的难度。
  Servlet在MVC开发模式里就是其中C层即控制层,控制层就像俄罗斯的双头鹰(一个头向东看一个头向西看)一样,一个头向M层模型层看,一个头向V层视图层看,模型层也是用java编写的,控制层也属于服务端语言开发,所以M层和C层的沟通没有天然的障碍,但是和V层视图层就不一样了,这是一个跨语言的沟通,对于浏览器,它只懂得html,javascript和css,浏览器是理解不了java这种语言的东西,但是要让服务端的东西能被浏览器理解接受,我们就必须得把服务端的响应信息放到页面里,因此就需要一个技术把java的信息转化到html页面里,这就是javaEE规范里提供了jsp技术,jsp其实是一种服务端技术而非客户端技术,不过它看起来似乎更像html技术,最早的jsp开发里都是直接将java代码写到页面里,这种坏处谁都知道,之后javaEE规范提供了自定义标签技术,使用一种类似html标签的方式来解析java代码,struts2框架提供了一整套完整的自定义标签技术,这似乎听起来不算啥,但是它的作用非凡,因为自定义标签之所以叫自定义就是每个人都可以自己来定义,如果没有一个规范必然产生混乱,而且一套完善的自定义标签是个系统工程,一套完整的自定义标签相当于我们在自己定义一套新的开发语言,做程序的人听到这个一定就会明白开发一套完整的自定义标签的工作量和开发难度都是难以想象的,而且自定义标签都是和控制层紧密相连,其难度又会增加一个维度,所以struts2提供的自定义标签对于业务开发带来的将是质的飞越。
   Servlet里还有两个重要的技术:监听器和过滤器,对于监听器在web开发里使用的场景比较少,都是一些十分特别的情况才会使用,大部分web开发里可以忽略它的使用,我们用的最多的监听器可能就是对ServletContext创建和销毁的监听器,ServletContext是整个web应用的全局对象,它和Web应用的生命周期绑定在一起,因此使用这个监听器对Web应用的全局信息进行初始化和销毁操作,例如spring容器的初始化操作。比较有意思的是过滤器,在struts2里有个拦截器,它们的作用相同都是用来拦截请求的,因为拦截器是struts2的特有功能,在struts2里使用拦截器自然比使用过滤器更顺手,其实拦截器所用的技术比过滤器更加先进,因为拦截器使用了反射技术,因此拦截器拦截的面更大,控制请求的能力更强,它能完成的任务也会更加的丰富多彩。
  在我第一次接触struts2时候,有人告诉我struts设计的一个目的就是想屏蔽在控制层里操作request和response对象,因为这两个http协议的儿子会造成web开发里思路的混乱,但是我在实际开发里却经常不自觉的使用这两个对象。而且本人做前端开发非常喜欢使用ajax,使用ajax技术时候我就很讨厌struts2的自定义标签,我更加喜欢在页面里用javascript技术处理各种信息,最终struts2在我眼里就是一个servlet的变体,因此曾经有段时间我常常在想是不是可以抛弃struts2,直接用servlet,因为struts2里用到了太多反射机制,特别是使用注解做配置(注解是用反射实现的),在java里反射的执行效率是非常低的,直接使用servlet一定能提升web应用的执行效率。其实这个倒很难做到,因为当时我没法在servlet里灵活的运用spring技术。 ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^
说完Servlet+jsp技术到Struts2技术的过渡。接下来谈谈Spring。
  spring技术可以说是java企业开发里最重要的技术,不过真的理解spring的作用和意义还真是一件麻烦的事情,很多人对spring理解其实都是停留在使用阶段(例如:声明式事务很好用等等),当今的spring技术生态环境里可谓是蔚为壮观,spring已经包罗万象,它的内容之多完全不亚于它的本源java语言了,而spring这么大的框都是建立在ioc和aop技术之上,只有深入理解了这两个技术我们才能明白为什么spring这个框能装的下那么多东西了。
  首先是ioc,ioc技术第一个解释叫做控制反转,它还有个解释就是依赖注入,这两个名字很难从字面理解,但是当你理解它的原理后就会发现它们的描述是何等准确。Ioc技术的本质就是构建对象的技术换句话说就是将一个类实例化成对象的技术,在java里实例化类通过new关键字进行的,每次new一个类都会产生一个新的实例对象,这么做视乎很浪费,有时这种浪费还挺危险,因为在程序开发时候我们常常只需要某个类永远只能产生一个的实例对象这个时候就得使用单例模式,此外在设计模式里还可以通过工厂方式产生对象,使用过spring的人看到上面的文字就知道了,spring里bean的定义就和上面的内容一一对应,scope属性single产生单例对象,prototype产生新对象,bean还可以通过工厂方式产生对象,可以说spring的bean就是制造对象的工具。面向对象编程里对象相当于显示生活中的一个实体,例如我们有个对象作用是完成打猎的操作,那么打猎这个对象内部包含两个辅助对象:人和枪,只有人和枪赋予了打猎这个对象,那么打猎对象才能完成打猎的操作,但是构建一个人和枪的对象并不是看起来那么简单,这里以枪为例,要创造一把枪我们需要金属,需要机床,需要子弹,而机床和子弹又是两个新对象,这些对象一个个相互嵌套相互关联,大伙试想下如果我们在java代码里构建一个枪的对象那是何其的复杂,假如我们要构造的不是简单的枪对象而是更加复杂的航空母舰,那么构造这个对象的成本之高是让人难以想象的,怎么来消除这种对象相互嵌套相互依赖的关系了?spring提供了一种方式,这种方式就是spring提供一个容器,我们在xml文件里定义各个对象的依赖关系,由容器完成对象的构建,当我们java代码里需要使用某个实例的时候就可以从容器里获取,那么对象的构建操作就被spring容器接管,所以它被称为控制反转,控制反转的意思就是本来属于java程序里构建对象的功能交由容器接管,依赖注入就是当程序要使用某个对象时候,容器会把它注入到程序里,这就叫做依赖注入。在java开发里我们想使用某个类提供的功能,有两种方式,一种就是构造一个新的类,新的类继承该类,另一种方式则是将某个类定义在新类里,那么两个类之间就建立一种关联关系,spring的ioc容器就是实现了这种关联关系(记住不是继承关系哦),那么某个类要被赋予到新类有哪些办法了?一般只有两种:一种就是通过构造函数,一种就是通过setXXX方式,这也是spring容器使用到了两种标准的注入方式。
  不管是上面说的继承方式,还是关联方式其实都是增强目标对象能力的开发手段,在设计模式里有一种代理模式,代理模式将继承模式和关联模式结合在一起使用,代理模式就是继承模式和关联模式的综合体,不过这个综合体的作用倒不是解决对象注入的问题,而是为具体操作对象找到一个保姆或者是秘书,这就和小说里的二号首长一样,这个二号首长对外代表了具体的实例对象,实例对象的入口和出口都是通过这个二号首长,因为具体的实例对象是一号首长,一号首长是要干大事的,所以一些事务性,重复性的工作例如泡茶,安排车子,这样的工作是不用劳烦一号首长的大驾,而是二号首长帮忙解决的,这就是aop的思想,aop解决程序开发里事务性,和核心业务无关的问题,但这些问题对于业务场景的实现是很有必要的,在实际开发里aop也是节省代码的一种方式。
  Spring的核心技术的作用本质就是一个沟通机制,spring总是尽全力的让沟通的双方信息畅通,同时降低双方的沟通成本,在现实机构里一个善于沟通的人肯定是该公司的领导,很会沟通的领导能调动起各种资源的积极性,善于沟通的领导就会做到海纳百川,让各种不同人追随他,所以当今的spring就是一个大框,什么都可以往里装。 Spring很像银行,它不能直接创造物质财富,但是一切资源都要通过它进行流通, 它能控制经济发展的走向,回到程序的世界,spring的作用是被标榜为程序之间的解耦,spring能降低不同模块之间的耦合度,原因就是在程序开发里不同模块之间信息的沟通是通过对象传递完成的,而对象能否顺利传递就是要合理的构建好对象,而管理好对象的构建方式就能管理好对象传递,这就是spring给系统架构设计带来的好处。 ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^
说到Spring, Spring的事务你懂吗?
   什么是事务?为什么事务要管理?什么是Spring事务? 事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。开发中为了避免这种情况一般都会进行事务管理。在JDBC中,是通过Connection对象进行事务管理的,默认是自动提交事务,可以手工将自动提交关闭,通过commit方法进行提交,rollback方法进行回滚,如果不提交,则数据不会真正的插入到数据库中。Hibernate中则是通过Transaction进行事务管理,处理方法与JDBC中类似。Spring中也有自己的事务管理机制,一般是使用TransactionMananger进行管理,可以通过Spring的注入完成此功能。   我通俗的理解如下:spring只是控制数据库的事务提交和回滚,借助于java的反射机制,在事务控制的方法(通常是service层的方法)前后获取事务开启session,然后执行你的数据操作,如果你的方法内有异常被抛出,spring会捕获异常并回滚你在这个方法内所有的数据操作,如果成功则提交所有的数据,最后spring会帮你关闭需要关闭的东西。所以spring想要做的是,要程序员专注于写逻辑,不需要关心数据库何时开启和关闭连接。
  再说的通俗点儿:事务,对于一件事,对了就提交,错了就回滚,什么时候回滚,都是事务要做的事情。具体的操作由spring 配置来管理(同时你也可以脱离框架,自己写事务管理方法)。
  使用Spring事务的优点?

  在SSH框假中Spring充当了管理容器的角色。我们都知道Hibernate用来做持久层,因为它将JDBC做了一个良好的封装,程序员在与数据库进行交互时可以不用书写大量的SQL语句。Struts是用来做应用层的,他它负责调用业务逻辑serivce层。所以SSH框架的流程大致是:Jsp页面----Struts------Service(业务逻辑处理类)---Hibernate(左到右)。struts负责控制Service(业务逻辑处理类),从而控制了Service的生命周期,这样层与层之间的依赖很强,属于耦合。这时,使用spring框架就起到了控制Action对象(Strus中的)和Service类的作用,两者之间的关系就松散了,Spring的Ioc机制(控制反转和依赖注入)正是用在此处。   Spring的Ioc(控制反转和依赖注入) 控制反转:就是由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控 依赖注入:组件之间的依赖关系由容器在运行期决定 ,由容器动态的将某种依赖关系注入到组件之中。 从上面我们不难看出:从头到尾Action仅仅是充当了Service的控制工具,这些具体的业务方法是怎样实现的,他根本就不会管,也不会问,他只要知道这些业务实现类所提供的方法接口就可以了。而在以往单独使用Struts框架的时候,所有的业务方法类的生命周期,甚至是一些业务流程都是由Action来控制的。层与层之间耦合性太紧密了,既降低了数据访问的效率又使业务逻辑看起来很复杂,代码量也很多。Spring容器控制所有Action对象和业务逻辑类的生命周期,由于上层不再控制下层的生命周期,层与层之间实现了完全脱耦,使程序运行起来效率更高,维护起来也方便。   使用Spring的第二个好处(AOP应用): 事务的处理: 在以往的JDBCTemplate中事务提交成功,异常处理都是通过Try/Catch 来完成,而在Spring中。Spring容器集成了TransactionTemplate,她封装了所有对事务处理的功能,包括异常时事务回滚,操作成功时数据提交等复杂业务功能。这都是由Spring容器来管理,大大减少了程序员的代码量,也对事务有了很好的管理控制。Hibernate中也有对事务的管理,hibernate中事务管理是通过SessionFactory创建和维护Session来完成。而Spring对SessionFactory配置也进行了整合,不需要在通过hibernate.cfg.xml来对SessionaFactory进行设定。这样的话就可以很好的利用Sping对事务管理强大功能。避免了每次对数据操作都要现获得Session实例来启动事务/提交/回滚事务还有繁琐的Try/Catch操作。这些也就是Spring中的AOP(面向切面编程)机制很好的应用。一方面使开发业务逻辑更清晰、专业分工更加容易进行。另一方面就是应用Spirng AOP隔离降低了程序的耦合性使我们可以在不同的应用中将各个切面结合起来使用大大提高了代码重用度。有利于代码重用,特别是Dao代码的重用。事务往往和业务规则紧密关联。当业务逻辑发生改变,意味着dao的大幅度改动。系统规模达到一定程度,修改风险相当大。Spring的好处是不更改现有的dao,仅需对现有的service bean进行配置就达到事务效果了。同时,把事务统一在service层,系统结构更清晰。 为什么说风险风大? Spring对于事务的配置有两种方式:第一种,使用xml形式,第二种,使用注解的形式。 基于XMl方式: 优点:可以在后期维护的时候适当的调整事务管理模式,并且只要遵循一定的命名规范,可以让程序员不必关心事务。        缺点:系统越庞大,xml配置就越大。 基于注解方式:优点:配置比较方便,程序员只要在service层代码设置即可以实现。不需要知道系统需要多少个bean,交给容器来注入就好了。        缺点:当你要修改或删除一个bean的时候,你无法确定到底有多少个其他的bean依赖于这个bean。(解决方法:需要有严格的开发文档,在修改实现时尽可能继续遵守相应的接口避免使其他依赖于此的bean不可用)
  在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的 Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如何保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作是由Spring来帮助我们完成的。 以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进 spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由 spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下。如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承,这样事务才会回滚!
  补充文字来说一下上面的关于Spring的一个问题。基于xml和基于注解,当然了它们都有优缺点。我们通俗的说,是这样的。先来回顾一下传统上是如何配置 Bean 并完成 Bean 之间依赖关系的建立。下面是 3 个类,它们分别是 Office、Car 和 Boss,这 3 个类需要在 Spring 容器中配置为 Bean。
// Office.java public class Office { private String officeNo =”001”; //省略 get/setter @Override public String toString() { return "officeNo:" + officeNo; } }

// Car.java public class Car { private String brand; private double price; // 省略 get/setter @Override public String toString() { return "brand:" + brand + "," + "price:" + price; } }

// Boss.java public class Boss { private Car car; private Office office; // 省略 get/setter @Override public String toString() { return "car:" + car + "\n" + "office:" + office; } }

我们在 Spring 容器中将 Office 和 Car 声明为 Bean,并注入到 Boss Bean 中:下面是使用传统 XML 完成这个工作的配置文件 beans.xml:
// bean.xml将以上三个类配置成bean。

// 当我们运行这段代码时,控制台将正确打印出boss的信息。这说明 Spring 容器已经正确完成了 Bean 创建和装配的工作。 import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); } }
我们知道 Spring 2.5 中引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。来看一下使用 @Autowired 进行成员变量自动注入的代码:
// Autowired是自动装配的意思 import org.springframework.beans.factory.annotation.Autowired; public class Boss { @Autowired private Car car; @Autowired private Office office; } // Spring 通过一个 BeanPostProcessor 对 @Autowired 进行解析,所以要让 @Autowired 起作用必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。 // 让系统认识@Autowired,让@Autowired注释工作起来。
// 这里面没配置哦

这样,当 Spring 容器启动时, AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有 @Autowired 注释时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。按照上面的配置,Spring 将直接采用 Java 反射机制对 Boss 中的
car 和 office 这两个私有成员变量进行自动注入。所以对成员变量使用 @Autowired 后,您大可将它们的 setter 方法( setCar() 和 setOffice() )从 Boss 中删除。当然,您也可以通过 @Autowired 对方法或构造函数进行标注,来看下面的代码:
public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired public void setOffice(Office office) { this.office = office; } }
这时, @Autowired 将查找被标注的方法的入参类型的 Bean,并调用方法自动注入这些 Bean。而下面的使用方法则对构造函数进行标注:
 Spring IOC三种注入方式(接口注入、setter注入、构造器注入) public class Boss { private Car car; private Office office; @Autowired public Boss(Car car ,Office office){ this.car = car; this.office = office ; } } // 由于 Boss() 构造函数有两个入参,分别是 car 和 office , @Autowired 将分别寻找和它们类型匹配的 Bean,将它们作为 Boss(Car car ,Office office) 的入参来创建 Boss Bean。 在默认情况下使用 @Autowired 注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。我们可以来做一个实验:

由于 office Bean 被注释掉了,所以 Spring 容器中将没有类型为 Office 的 Bean 了,而 Boss 的 office 属性标注了 @Autowired ,当启动 Spring 容器时,异常就产生了。当不能确定 Spring 容器中一定拥有某个类的 Bean 时,可以在需要自动注入该类 Bean 的地方可以使用 @Autowired(required = false) ,这等于告诉 Spring:在找不到匹配 Bean 时也不报错。来看一下具体的例子:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired(required = false) public void setOffice(Office office) { this.office = office; } }
当然,一般情况下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自动注入而又允许不注入的情况一般仅会在开发期或测试期碰到(如为了快速启动 Spring 容器,仅引入一些模块的 Spring 配置文件),所以 @Autowired(required = false) 会很少用到。和找不到一个类型匹配 Bean 相反的一个错误是:如果 Spring 容器中拥有多个候选 Bean,Spring 容器在启动时也会抛出 BeanCreationException 异常。来看下面的例子:
// 在 beans.xml 中配置两个 Office 类型的 Bean
我们在 Spring 容器中配置了两个类型为 Office 类型的 Bean,当对 Boss 的 office 成员变量进行自动注入时,Spring 容器将无法确定到底要用哪一个 Bean,因此异常发生了。Spring 允许我们通过 @Qualifier 注释指定注入 Bean 的名称,这样歧义就消除了,可以通过下面的方法解决异常: @Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; }
@Qualifier("office") 中的 office 是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。 @Autowired 可以对成员变量、方法以及构造函数进行注释,而 @Qualifier 的标注对象是成员变量、方法入参、构造函数入参。正是由于注释对象的不同,所以 Spring 不将 @Autowired 和 @Qualifier 统一成一个注释类。下面是对成员变量和构造函数入参进行注释的代码:对成员变量进行注释:
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; }

我们面试中提到Spring框架经常被问到的一块儿是Spring的事务,它优于struts2,优于hibernate。那么我们现在就详细的说说这个东西。 // 事务的特性:原子性、一致性、隔离性、持久性。 常常问的:1. Spring事务的传播特性; 2. Spring事务的隔离机制。
我们先来探讨一下数据库事务的隔离级别:
事务的(ACID)特性是由关系数据库管理系统(RDBMS,数据库系统)来实现的。数据库管理系统采用 日志 来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。数据库管理系统采用锁机制来实现事务的隔离性。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。 幻读 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。 脏读 就是指当一个 事务 正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是 脏数据 ,依据脏数据所做的操作可能是不正确的。
 数据库系统有四个 隔离级别 。对数据库使用何种隔离级别要审慎分析,因为维护一个最高的隔离级别虽然会防止数据的出错,但是却导致了并行度的损失,以及导致死锁出现的可能性增加。然而,降低隔离级别,却会引起一些难以发现的bug。
  1. 序列化 Serializable, 添加范围锁(比如表锁,页锁等,关于range lock,我也没有很深入的研究),直到transaction A结束。以此阻止其它trasaction B对此范围内的insert,update等操作。幻读,脏读,不可重复读等问题都不会发生。

  2. 可重复读 repeatable read, 对于读出的记录,添加共享锁直到transaction A结束。其它transaction B对这个记录的试图修改会一直等待直到trasaction A结束。 InnoDB 默认级别 。 可能发生的问题:当执行一个范围查询时,可能会发生幻读。

  3. 提交读 read commited, 在trasaction A中读取数据时对记录添加共享锁,但读取结束立即释放。其它transaction B对这个记录的试图修改会一直等待直到A中的读取过程结束,而不需要整个trasaction A的结束。所以,在trasaction A的不同阶段对同一记录的读取结果可能是不同的。
可能发生的问题:不可重复读。

  4. 未提交读 read uncommited, 不添加共享锁。所以其它trasaction B可以在trasaction A对记录的读取过程中修改同一记录,可能会导致A读取的数据是一个被破坏的或者说不完整不正确的数据。另外,在trasaction A中可以读取到trasaction B(未提交)中修改的数据。比如trasaction     B对R记录修改了,但未提交。此时,在Trasaction A中读取R记录,读出的是被B修改过的数据。可能发生的问题:脏读。
那么Spring的事务隔离机制和Spring事务的传播属性呢,Spring的隔离级别我们知道目的是为了防止幻读和脏读。Spring的事务传播属性似乎好难理解。。接下来,先解释下什么叫做事务的传播属性:
我们都知道事务的概念,那么事务的传播特性是什么呢?(先着重介绍传播特性的概念,关于传播特性的相关配置稍后再介绍) 背景 :当我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 场景 :如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如何保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的1种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。 以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承这样事务才会回滚!
好了,知道了事务的传播特性之后,我们看看Spring提供的六种事务传播Propagation特性: 仔细感觉一下,六中Spring事务传播机制 对称美 1. Propagation required, 如果当前没有事务,就新建一个事务。 2. Propagation supports, 如果当前没有事务,就以非事务方式执行。 3. Propagation mandatory, 如果当前没有事务,就抛出异常。 4. Propagation requires new, 如果当前存在事务,挂起当前事务,新建事务。 5. Propagation not supported, 如果当前存在事务,把当前事务挂起,以非事务方式执行。 6. Propagation never, 如果当前存在事务,抛出异常。以非事务方式执行。
关于六种事务传播机制的具体应用场景,参考: Spring事务传播机制博客
了解了Spring事务的传播特性,再来看看Spring事务的五种隔离级别isolation level。
// 先再来回顾前面说到的三个概念 脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。 不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

// 再来看Spring事务的隔离级别isolation level: 1. isolation default: 这是一个默认的隔离级别,使用数据库默认的事务隔离级别.(一般情况下,使用这种)。另外四个与JDBC的隔离级别相对应. 2. isolation read uncommited: 这个是事务最低的隔离级别,它允许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。(trasaction B可以在trasaction A对记录的读取过程中修改同一记录)    产生脏读、产生不重复读、产生幻读。 3. isolation read commited: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据.(trasaction B提交后trasaction A才能读取)    避免脏读、会产生不重复读、会产生幻读。 4. isolation repeatable read: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。    避免脏读、避免不重复读、会产生幻读。 5. isolation serializable.这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻读。    避免脏读、避免不重复读、避免幻读。

  换种口吻说,声明式事务。声明式事务是Spring提供的对程序事务管理的方式之一。Sping的声明式事务,就是在配置文件中采用配置的方式对事务进行管理。Spring中的AOP即,是完成事务管理工作的。
总结一下我们出几道面试题整合一下Spring3的知识。
1. Spring的工作原理: 1.客户端请求提交到DispatcherServlet 2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller 3. DispatcherServlet将请求提交到Controller 4. Controller调用业务逻辑处理后,返回ModelAndView 5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图 6. 视图负责将结果显示到客户端
2. 为什么要用Spring?
1、Spring能很好的与各大框架进行集成 2、创建对象时,如果我们不用spring。需要用工厂模式来创建,这个spring相当于工厂模式已经帮我们做了创建对象的功能(IOC、依赖注入)。 3、在用Hibernate的时候,如果不用spring每次都要写事务的提交代码,有了spring可以通过AOP帮助我们管理事务。 4、面向切面编程(AOP)在要记录日志的时候添加一条记录后需要在数据里同时添加一条添加成功了或失败的记录,那么就可以用Spring的Aop来处理,虽然不用Aop也能做但是不用Spring的Aop就会写很多重复的代码。 AOP 让开发人员可以创建非行为性的关注点,称为横切关注点,并将它们插入到应用程序代码中。使用 AOP 后,公共服务 (比 如日志、持久性、事务等)就可以分解成方面并应用到域对象上,同时不会增加域对象的对象模型的复杂性。IOC 允许创建一个可以构造对象的应用环境,然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的,IOC 就像反 过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素(singleton)和直接构造(straight construction),每一个对象都是用 其协作对象构造的。因此是由容器管理协作对象(collaborator)。Spring即使一个AOP框架,也是一IOC容器。 Spring 最好的地方是它有助于您替换对象。有了 Spring,只要用 JavaBean 属性和配置文件加入依赖性(协作对象)。然后可以很容易地在需要时替换具有类似接口的协作对象。
3. 请你谈谈SSH的整合?
请你谈谈SSH整合 SSH:Struts(表示层)+Hibernate(持久层)+Spring(业务层) a、Struts Struts是一个表示层框架,主要作用是界面展示,接收请求,分发请求。 b、Hibernate Hibernate是一个持久层框架,它只负责与关系数据库的操作。 c、Spring Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层。

4. 介绍一下Spring的事务管理? 介绍一下Spring的事务管理 事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。开发中为了避免这种情况一般都会进行事务管理。Spring中也有自己的事务管理机制,一般是使用TransactionMananger进行管理,可以通过Spring的注入来完成此功能。

5. 什么是依赖注入,依赖注入的作用是什么? 什么是依赖注入,依赖注入的作用是什么? IOC是一种思想,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试. 有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。 依赖注入的作用:减少类间耦合度,避免用new来创建对象。
6.什么是aop,aop的作用是什么? 什么是AOP,AOP的作用是什么? AOP,面向切面编程,就是把可重用的功能提取出来,然后将这些通用功能在合适的时候织入到应用程序中,比如事务管理、权限控制、日志记录、性能统计等。 AOP的作用 AOP并没有帮助我们解决任何新的问题,它只是提供了一种更好的办法,能够用更少的工作量来解决现有的一些问题,使得系统更加健壮,可维护性更好。
7. Spring中的BeanFactory与ApplicationContext的作用有哪些? 1、BeanFactory负责读取bean的配置文件,管理bean的加载、实例化,维护bean之间的依赖关系,负责bean的生命周期。 2、ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能: a. 国际化支持 b. 资源访问 c. 事件传递

8. Hibernate的工作原理?
Hibernate工作原理及为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory

9. 为什么要用hibernate? // 为什么要用: 1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。 2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作 3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。 4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
10. Hibernate如何延迟加载? Hibernate是如何延迟加载? 1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection) 2. Hibernate3 提供了属性的延迟加载功能 当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。
11. Hibernate怎么样实现类之间的关系? Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 类与类之间的关系主要体现在表与表之间的关系进行操作,它们都是对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、
12. 说下hibernate的缓存机制。
详细说下hibernate的缓存机制: // 为什么要用hibernate缓存:
Hibernate是一个持久层框架,经常访问物理数据库。为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
// Hibernate的缓存原理是怎么样的?
Hibernate一级缓存又称为“Session的缓存”。Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。
Hibernate二级缓存又称为“SessionFactory的缓存”。由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。

// 既然二级缓存是进程级别的缓存,那么它适合缓存什么类型的数据呢?
      什么样的数据适合存放到第二级缓存中?   
          1) 很少被修改的数据   
          2) 不是很重要的数据,允许出现偶尔并发的数据   
          3) 不会被并发访问的数据   
          4) 常量数据   
  不适合存放到第二级缓存的数据?   
          1) 经常被修改的数据   
          2) 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发   
          3) 与其他应用共享的数据。

  // Session的延迟加载
  Session的延迟加载实现要解决两个问题:正常关闭连接和确保请求中访问的是同一个session。Hibernate session就是java.sql.Connection的一层高级封装,一个session对应了一个Connection。http请求结束后正确的关闭session(过滤器实现了session的正常关闭);延迟加载必须保证是同一个session(session 绑定 在ThreadLocal)。

  // Hibernate查找对象, 如何应用缓存?
当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增加数据的时候,同时更新缓存。

接下来说说Spring事务的配置:
  Spring 如果没有特殊说明,一般指是跟数据存储有关的数据操作事务操作;对于数据持久操作的事务配置,一般有三个对象, 数据源(dataSouce),事务管理器(transactionManager),以及事务代理机制 ;Spring 提供了多种的底层数据源实现,以及多种类型的事务管理器;所有的管理器都基于 Platform Transaction Manager 接口实现各自的事务策略;Spring 事务管理采用 AOP 切面代理技术实现,AOP 用于分隔关注点,保证事务的原子性,采用一定的技术 把该关注点 (weaving) 织入到 待完善的关注点上,实现单独组件无法实现的功能,以解决面向对象编程在某些方式下难于实现的操作,更好的支持面向对象的开关原则(扩展开放,修改关闭)。
对于三部分:dataSource、transactionManager、事务代理机制。无论哪种配置方式,一般变化的都是代理机制部分。DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化。比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager 。
  那么我们知道了事务有五种配置方式和三个对象,接下来说说它的层面:Spring的事务到底该给Dao配置还是给Service配置呢?Spring的事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性。事务根据项目性质来细分:事务可以设置到三个层面(dao层、service层和web层),第一:web层事务,这一般是针对那些安全性要求较高的系统来说的。例如电子商务网站。粒度小,一般系统用不着这么细。第二:service层事务,这是一常见的事务划分, 将事务设置在业务逻辑上,只要业务逻辑出错或异常就事务回滚。粒度较小,一般推荐这种方式。第三:数据持久层数据务,也就是常说的数据库事务。这种事务在安全性方面要求低。就是给一个简单的增删改之类的操作增加事务操作,粒度大。    
  Spring声明式事务让我们从复杂的事务处理中得到解脱。使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。
我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性。事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成。我们在进行事务划分时,需要进行事务定义,也就是配置事务的属性。给Service层配置事务,因为一个Service层方法操作可以关联到多个DAO的操作。在Service层执行这些Dao操作,多DAO操作有失败全部回滚,成功则全部提交。事务分为业务事务和系统事务,业务事务也就是业务逻辑上操作的一致性,系统事务自然就是指真正的数据库事务,Spring配置事务的是为了什么进行管理,当然是为业务逻辑进行事务管理,保证业务逻辑上数据的原子性;Dao层是什么,数据访问层,是不应该包含业务逻辑的,这就是和Service层的不同;Service层就是业务逻辑层,事务的管理就是为Service层上的保证。
package com.bluesky.spring.dao; import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; import com.bluesky.spring.domain.User; @Transactional // 看代码(关于事务的配置) @Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List listUsers() { return this.getSession().createQuery("from User").list(); } } // 在DAO层上加了注解 @Transactional ,这种申明式事务方式,让我们从复杂的事务处理中得到解脱,使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。

接下来,我们看看事务的定义
// TransactionDefinition public interface TransactionDefinition { int getPropagationBehavior(); //方法 返回事务的传播行为,是否由一个活动事务来决定一个事务的调用。 int getTimeout(); //方法 返回事务必须在多少秒内执行 boolean isReadOnly(); //方法返回事务是否是只读的      int getIsolationLevel(); // 返回事务的隔离级别 } 说说事务的隔离级别:在 TransactionDefinition 接口中定义了五个不同的事务隔离级别, ISOLATION_DEFAULT 这是一个默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应。 ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
例如: Mary的原工资为1000,财务人员将Mary的工资改为了8000,但未提交事务 Java代码 1. Connection con1 = getConnection(); 2. con.setAutoCommit(false); 3. update employee set salary = 8000 where empId ="Mary"; 与此同时,Mary正在读取自己的工资 Java代码 1. Connection con2 = getConnection(); 2. select salary from employee where empId ="Mary"; 3. con2.commit(); Mary发现自己的工资变为了8000,欢天喜地! 而财务发现操作有误,而回滚了事务,Mary的工资又变为了1000 Java代码 1. //con1 2. con1.rollback(); 像这样,Mary记取的工资数8000是一个脏数据。

ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
在事务1中,Mary 读取了自己的工资为1000,操作并没有完成 Java代码 1. con1 = getConnection(); 2. select salary from employee empId ="Mary"; 在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务. Java代码 1. con2 = getConnection(); 2. update employee set salary = 2000; 3. con2.commit(); 在事务1中,Mary 再次读取自己的工资时,工资变为了2000 Java代码 1. //con1 2. select salary from employee empId ="Mary"; 在一个事务中前后两次读取的结果并不致,导致了不可重复读。 使用ISOLATION_REPEATABLE_READ可以避免这种情况发生。

ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
@Transactional只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能。Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。这种默认的行为是可以改变的。 事务详细讲解的文章 第一种方式,每个bean都有一个代理类。
classpath:/com/nms/entity/**/*.hbm.xml org.hibernate.dialect.MySQL5Dialect true true PROPAGATION_REQUIRED
第二种方式,所有bean共享一个代理类。
PROPAGATION_REQUIRED
第三种方式,使用拦截器。
项目管理
2018-06-18 13:21:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
最近在做TP5的集成微信支付功能,顺便把自己学到的一些东西记录一下
集成微信支付的话,需要在微信商户平台有账号,并且开通了微信支付功能,这里就不细说了
接入微信功能,可以参考一下官方文档: 微信支付接入文档
他提供了很多接入方式的文档,这里我主要使用的是公众号支付

他文档里有官方的SDK和demo,可以下载下来参考

我们主要还是用到的是lib目录,其包括的微信支付的SDK,当然,实例代码也写了怎么调用微信功能

除了SDK,我们还需要在公众号平台和商户平台获取一些信息,才能够使用微信支付
获取到以上信息,去配置文件里加上这些信息,开发的时候就可以直接使用了

项目管理
2018-06-15 22:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
之前讲了微信公众号内网页应用登录的一些配置,不过配置过程中,可能会遇到一些问题,这里简单说一下
首先,在微信公众平台上,我们获取到了APPID和APPSECRET,但是光是在代码里添加这两个,还是不能使用微信登录的,因为还没设置域名授权,所以,这里我们应该到微信公众平台去设置域名授权
找到“公众号设置”=>“功能设置”


点击设置,然后将你需要授权的域名填写,这里记得,下载证书,并拷贝到你的域名指向的程序文件,我的网站用的是TP5开发的,所以我把证书放到了public目录下
证书 拷贝过去后,点击确定,即可验证并设置成功

这样,这个域名对应的程序就可以使用微信登录了

项目管理
2018-06-15 22:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring Cloud Config
配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。
Spring Cloud Bus
​事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。
Eureka
云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
Hystrix
熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
Zuul
Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
Archaius
配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。
Consul
封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成。
Spring Cloud for Cloud Foundry
通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。
Spring Cloud Zookeeper
操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。
Spring Cloud Stream
数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。
Spring Cloud CLI
基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。
Ribbon
提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。
Feign
Feign是一种声明式、模板化的HTTP客户端。
Spring Cloud Task
提供云端计划任务管理、任务调度。
Spring Cloud Connectors
便于云端应用程序在各种PaaS平台连接到后端,如:数据库和消息代理服务。
Spring Cloud Cluster
提供Leadership选举,如:Zookeeper, Redis, Hazelcast, Consul等常见状态模式的抽象和实现。
......等,从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目
项目管理
2018-06-15 16:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring Cloud解决的第一个问题就是:服务与服务之间的解耦。很多公司在业务高速发展的时候,服务组件也会相应的不断增加。服务和服务之间有着复杂的相互调用关系,经常有服务A调用服务B,服务B调用服务C和服务D ...,随着服务化组件的不断增多,服务之间的调用关系成指数级别的增长,这样最容易导致的情况就是牵一发而动全身。经常出现由于某个服务更新而没有通知到其它服务,导致上线后惨案频发。这时候就应该进行服务治理,将服务之间的直接依赖转化为服务对服务中心的依赖。Spring Cloud 核心组件Eureka就是解决这类问题。
Eureka
Eureka是Netflix开源的一款提供服务注册和发现的产品,它提供了完整的Service Registry和Service Discovery实现。也是Spring Cloud体系中最重要最核心的组件之一。
用大白话讲,Eureka就是一个服务中心,将所有的可以提供的服务都注册到它这里来管理,其它各调用者需要的时候去注册中心获取,然后再进行调用,避免了服务之间的直接调用,方便后续的水平扩展、故障转移等。如下图:


当然服务中心这么重要的组件一但挂掉将会影响全部服务,因此需要搭建Eureka集群来保持高可用性,生产中建议最少两台。随着系统的流量不断增加,需要根据情况来扩展某个服务,Eureka内部已经提供均衡负载的功能,只需要增加相应的服务端实例既可。那么在系统的运行期间某个实例挂了怎么办?Eureka内容有一个心跳检测机制,如果某个实例在规定的时间内没有进行通讯则会自动被剔除掉,避免了某个实例挂掉而影响服务。
因此使用了Eureka就自动具有了注册中心、负载均衡、故障转移的功能。
Hystrix
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。


在这种情况下就需要整个服务机构具有故障隔离的功能,避免某一个服务挂掉影响全局。在Spring Cloud 中Hystrix组件就扮演这个角色。
Hystrix会在某个服务连续调用N次不响应的情况下,立即通知调用端调用失败,避免调用端持续等待而影响了整体服务。Hystrix间隔时间会再次检查此服务,如果服务恢复将继续提供服务。
Hystrix Dashboard和Turbine
当熔断发生的时候需要迅速的响应来解决问题,避免故障进一步扩散,那么对熔断的监控就变得非常重要。熔断的监控现在有两款工具:Hystrix-dashboard和Turbine
Hystrix-dashboard是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以直观地看到各Hystrix Command的请求响应时间, 请求成功率等数据。但是只使用Hystrix Dashboard的话, 你只能看到单个应用内的服务信息, 这明显不够. 我们需要一个工具能让我们汇总系统内多个服务的数据并显示到Hystrix Dashboard上, 这个工具就是Turbine. 监控的效果图如下:


配置中心
随着微服务不断的增多,每个微服务都有自己对应的配置文件。在研发过程中有测试环境、UAT环境、生产环境,因此每个微服务又对应至少三个不同环境的配置文件。这么多的配置文件,如果需要修改某个公共服务的配置信息,如:缓存、数据库等,难免会产生混乱,这个时候就需要引入Spring Cloud另外一个组件:Spring Cloud Config。
Spring Cloud Config
Spring Cloud Config是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,Server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,Client通过接口获取数据、并依据此数据初始化自己的应用。
其实就是Server端将所有的配置文件服务化,需要配置文件的服务实例去Config Server获取对应的数据。将所有的配置文件统一整理,避免了配置文件碎片化。
如果服务运行期间改变配置文件,服务是不会得到最新的配置信息,需要解决这个问题就需要引入Refresh。可以在服务的运行期间重新加载配置文件。
当所有的配置文件都存储在配置中心的时候,配置中心就成为了一个非常重要的组件。如果配置中心出现问题将会导致灾难性的后果,因此在生产中建议对配置中心做集群,来支持配置中心高可用性。
Spring Cloud Bus
上面的Refresh方案虽然可以解决单个微服务运行期间重载配置信息的问题,但是在真正的实践生产中,可能会有N多的服务需要更新配置,如果每次依靠手动Refresh将是一个巨大的工作量,这时候Spring Cloud提出了另外一个解决方案:Spring Cloud Bus
Spring Cloud Bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其它的消息指令中。Spring Cloud Bus的一个核心思想是通过分布式的启动器对Spring Boot应用进行扩展,也可以用来建立一个或多个应用之间的通信频道。目前唯一实现的方式是用AMQP消息代理作为通道。
Spring Cloud Bus是轻量级的通讯组件,也可以用在其它类似的场景中。有了Spring Cloud Bus之后,当我们改变配置文件提交到版本库中时,会自动的触发对应实例的Refresh,具体的工作流程如下:


服务网关
在微服务架构模式下,后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。


Spring Cloud体系中支持API Gateway落地的技术就是Zuul。Spring Cloud Zuul路由是微服务架构中不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
它的具体作用就是服务转发,接收并转发所有内外部的客户端调用。使用Zuul可以作为资源的统一访问入口,同时也可以在网关做一些权限校验等类似的功能。
链路跟踪
随着服务的越来越多,对调用链的分析会越来越复杂,如服务之间的调用关系、某个请求对应的调用链、调用之间消费的时间等,对这些信息进行监控就成为一个问题。在实际的使用中我们需要监控服务和服务之间通讯的各项指标,这些数据将是我们改进系统架构的主要依据。因此分布式的链路跟踪就变的非常重要,Spring Cloud也给出了具体的解决方案:Spring Cloud Sleuth和Zipkin


Spring Cloud Sleuth为服务之间调用提供链路追踪。通过Sleuth可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长时间。从而让我们可以很方便的理清各微服务间的调用关系。
Zipkin是Twitter的一个开源项目,允许开发者收集 Twitter 各个服务上的监控数据,并提供查询接口。
项目管理
2018-06-15 16:43:05
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring Cloud Consul项目是针对Consul的服务治理实现。Consul是一个分布式高可用的系统,它包含多个组件,但是作为一个整体,在微服务架构中为我们的基础设施提供服务发现和服务配置的工具。它包含了下面几个特性: 服务发现 健康检查 Key/Value存储 多数据中心
由于Spring Cloud Consul项目的实现,我们可以轻松的将基于Spring Boot的微服务应用注册到Consul上,并通过此实现微服务架构中的服务治理。
以之前实现的基于Eureka的示例(eureka-client)为基础,我们如何将之前实现的服务提供者注册到Consul上呢?方法非常简单,我们只需要在 pom.xml 中将eureka的依赖修改为如下依赖: org.springframework.cloud spring-cloud-starter-consul-discovery
接下来再修改一下 application.properites ,将consul需要的配置信息加入即可,比如:(下面配置是默认值) spring.cloud.consul.host=localhost spring.cloud.consul.port=8500
到此为止,我们将eureka-client转换为基于consul服务治理的服务提供者就完成了。前文我们已经有提到过服务发现的接口 DiscoveryClient 是Spring Cloud对服务治理做的一层抽象,所以可以屏蔽Eureka和Consul服务治理的实现细节,我们的程序不需要做任何改变,只需要引入不同的服务治理依赖,并配置相关的配置属性就能轻松的将微服务纳入Spring Cloud的各个服务治理框架中。
下面可以尝试让consul的服务提供者运行起来。这里可能读者会问,不需要创建类似eureka-server的服务端吗?由于Consul自身提供了服务端,所以我们不需要像之前实现Eureka的时候创建服务注册中心,直接通过下载consul的服务端程序就可以使用。
我们可以用下面的命令启动consul的开发模式:

$consul agent -dev ==> Starting Consul agent... ==> Starting Consul agent RPC... ==> Consul agent running! Version: 'v0.7.2' Node name: 'Lenovo-zhaiyc' Datacenter: 'dc1' Server: true (bootstrap: false) Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400) Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302) Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false Atlas: ==> Log data will now stream in as it occurs: 2017/06/22 07:50:54 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:127.0.0.1:8300 Address:127.0.0.1:8300}] 2017/06/22 07:50:54 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "") 2017/06/22 07:50:54 [INFO] serf: EventMemberJoin: Lenovo-zhaiyc 127.0.0.1 2017/06/22 07:50:54 [INFO] consul: Adding LAN server Lenovo-zhaiyc (Addr: tcp/127.0.0.1:8300) (DC: dc1) 2017/06/22 07:50:54 [INFO] serf: EventMemberJoin: Lenovo-zhaiyc.dc1 127.0.0.1 2017/06/22 07:50:54 [INFO] consul: Adding WAN server Lenovo-zhaiyc.dc1 (Addr: tcp/127.0.0.1:8300) (DC: dc1) 2017/06/22 07:51:01 [ERR] agent: failed to sync remote state: No cluster leader 2017/06/22 07:51:02 [WARN] raft: Heartbeat timeout from "" reached, starting election 2017/06/22 07:51:02 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2 2017/06/22 07:51:02 [DEBUG] raft: Votes needed: 1 2017/06/22 07:51:02 [DEBUG] raft: Vote granted from 127.0.0.1:8300 in term 2. Tally: 1 2017/06/22 07:51:02 [INFO] raft: Election won. Tally: 1 2017/06/22 07:51:02 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state 2017/06/22 07:51:02 [INFO] consul: cluster leadership acquired 2017/06/22 07:51:02 [INFO] consul: New leader elected: Lenovo-zhaiyc 2017/06/22 07:51:02 [DEBUG] consul: reset tombstone GC to index 3 2017/06/22 07:51:02 [INFO] consul: member 'Lenovo-zhaiyc' joined, marking health alive 2017/06/22 07:51:02 [INFO] agent: Synced service 'consul' 2017/06/22 07:51:02 [DEBUG] agent: Node info in sync
consul服务端启动完成之后,我们再将之前改造后的consul服务提供者启动起来。consul与eureka一样,都提供了简单的ui界面来查看服务的注册情况:

从现在开始,我这边会将近期研发的springcloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,希望可以帮助更多的好学者。大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。
项目管理
2018-06-15 16:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spring Cloud简介
Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中涉及的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud0 CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。
微服务架构
“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的开源产品被反复的提及(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。
那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。
对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》(中文版翻译 点击查看 ),本文不做更多的介绍和描述。
服务治理
在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud来实现服务治理。
由于Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、Zookeeper。在Spring Cloud服务治理抽象层的作用下,我们可以无缝地切换服务治理实现,并且不影响任何其他的服务注册、服务发现、服务调用等逻辑。
所以,下面我们通过介绍两种服务治理的实现来体会Spring Cloud这一层抽象所带来的好处。
Spring Cloud Eureka
首先,我们来尝试使用Spring Cloud Eureka来实现服务治理。
Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
项目管理
2018-06-15 16:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在Spring Cloud Commons中提供了大量的与服务治理相关的抽象接口,包括 DiscoveryClient 、这里我们即将介绍的 LoadBalancerClient 等。对于这些接口的定义我们在上一篇介绍服务注册与发现时已经说过,Spring Cloud做这一层抽象,很好的解耦了服务治理体系,使得我们可以轻易的替换不同的服务治理设施。
从 LoadBalancerClient 接口的命名中,我们就知道这是一个负载均衡客户端的抽象定义,下面我们就看看如何使用Spring Cloud提供的负载均衡器客户端接口来实现服务的消费。
下面的例子,我们将利用上一篇中构建的eureka-server作为服务注册中心、eureka-client作为服务提供者作为基础。 我们先来创建一个服务消费者工程,命名为: eureka-consumer 。并在 pom.xml 中引入依赖(这里省略了parent和dependencyManagement的配置): org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator
配置 application.properties ,指定eureka注册中心的地址: spring.application.name=eureka-consumer server.port=2101 eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
创建应用主类。初始化 RestTemplate ,用来真正发起REST请求。 @EnableDiscoveryClient 注解用来将当前应用加入到服务治理体系中。 @EnableDiscoveryClient @SpringBootApplication public class Application { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }
创建一个接口用来消费eureka-client提供的接口: @RestController public class DcController { @Autowired LoadBalancerClient loadBalancerClient; @Autowired RestTemplate restTemplate; @GetMapping("/consumer") public String dc() { ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc"; System.out.println(url); return restTemplate.getForObject(url, String.class); } }
可以看到这里,我们注入了 LoadBalancerClient 和 RestTemplate ,并在 /consumer 接口的实现中,先通过 loadBalancerClient 的 choose 函数来负载均衡的选出一个 eureka-client 的服务实例,这个服务实例的基本信息存储在 ServiceInstance 中,然后通过这些对象中的信息拼接出访问 /dc 接口的详细地址,最后再利用 RestTemplate 对象实现对服务提供者接口的调用。 在完成了上面你的代码编写之后,读者可以将eureka-server、eureka-client、eureka-consumer都启动起来,来跟踪观察eureka-consumer服务是如何消费eureka-client服务的 /dc 接口的。
项目管理
2018-06-15 16:37:00