diff --git "a/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.md" "b/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.md" new file mode 100644 index 0000000..624ef13 --- /dev/null +++ "b/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.md" @@ -0,0 +1,2 @@ +# Hexo搭建博客 + diff --git "a/Java8\347\263\273\345\210\227.md" "b/Java8\347\263\273\345\210\227.md" new file mode 100644 index 0000000..e0b069a --- /dev/null +++ "b/Java8\347\263\273\345\210\227.md" @@ -0,0 +1,2 @@ +# Java8系列 + diff --git a/README.md b/README.md index 924b566..5fcf4e2 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,107 @@ # java-bible -这里记录了一些我的技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。 +这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。 如果你喜欢,`star` 便是,持续更新ing,还有Fork项目的同学,貌似没有什么卵用。。。 ## 目录 -* **开源组件实现** - * [MVC框架实现篇](mvc/index.md) - * [IOC容器实现篇](ioc/index.md) - * AOP原理和实现 - * 缓存的理解和实现 - * 使用NIO实现一个HTTP服务器 - * 实现一个数据库连接池 - * ORM框架实现篇 -* **设计模式该怎么用** - * [如何正确地写出单例模式](designpatterns/singleton.md) - * [代理模式剖析](designpatterns/proxy.md) - * [什么是策略模式](designpatterns/stratege.md) - * 装饰者 - * 观察者 - * 工厂 - * 享元 -* **Java8系列** - * [Java8简明教程](java8/java8-guide.md) - * [Java8 Foreach](java8/foreach.md) - * Java8 Lambda - * Java8 Stream -* **Hexo搭建博客** - * [分分钟部署一个Hexo环境](hexo/hello.md) - * [各种配置详解](hexo/config.md) - * [开始写作吧](hexo/writing.md) -* **用户指南** - * [git - 简明指南](git/guide.md) - * [Jersey-2.x用户指南](https://waylau.gitbooks.io/jersey-2-user-guide/content/index.html) - * [REST 实战](https://waylau.gitbooks.io/rest-in-action/content/) - * [Java Servlet 3.1 规范](https://github.com/waylau/servlet-3.1-specification) - * [MyBatis中文指南](http://mybatis.github.io/mybatis-3/zh/index.html) - * [Apache Shiro 用户指南](https://github.com/waylau/apache-shiro-1.2.x-reference) - * [Spring Boot参考指南](https://github.com/qibaoguang/Spring-Boot-Reference-Guide/blob/master/SUMMARY.md) - * [Netty4 用户指南](https://github.com/waylau/netty-4-user-guide/blob/master/SUMMARY.md) - * [Google Java编程风格指南](user_guide/google-java8-guide.md) -* **运维相关** - * [linux安装jdk、tomcat脚本](shell/install_jdk_tomcat.sh) - * [Web性能测试工具](web/test_tool.md) - * [Java 程序员眼中的 Linux](https://github.com/judasn/Linux-Tutorial) - -## 精品文章 +* [开源组件实现](#开源组件实现) + * [MVC框架实现篇](#MVC框架实现篇) + * [IOC容器实现篇](#IOC容器实现篇) +* [设计模式系列](#设计模式系列) +* [Java8系列](#Java8系列) +* [Hexo搭建博客](#Hexo搭建博客) +* [开发者指南](#开发者指南) +* [运维相关](#运维相关) +* [经典文章](#经典文章) + +## 开源组件实现 + +### [MVC框架实现篇](mvc/index.md) + +* [项目规划](mvc/1.plan.md) +* [路由设计](mvc/2.route.md) +* [控制器设计](mvc/3.controller.md) +* [配置设计](mvc/4.config.md) +* [视图设计](mvc/5.view.md) +* [数据库操作](mvc/6.dbutil.md) +* [增删改查](mvc/7.crud.md) + + +### [IOC容器实现篇](ioc/index.md) + +* [IOC的概念](ioc/1.concept.md) +* [Spring中怎么用](ioc/2.spring.md) +* [设计一个IOC](ioc/3.myioc.md) +* [原理分析](ioc/4.principle.md) + +## 设计模式系列 + +* [如何正确地写出单例模式](designpatterns/singleton.md) +* [代理模式剖析](designpatterns/proxy.md) +* [什么是策略模式](designpatterns/strategy.md) + +## Java8系列 + +* [Java8简明教程](java8/java8-guide.md) +* [Java8 Foreach](java8/foreach.md) + +## 架构博文专栏 + +- [朱晔的互联网架构实践心得](https://juejin.im/user/5ba37b9de51d450e7428affe/posts) + +## Hexo搭建博客 + +* [分分钟部署一个Hexo环境](hexo/hello.md) +* [各种配置详解](hexo/config.md) +* [开始写作吧](hexo/writing.md) + +## 开发者指南 + +* [git - 简明指南](git/guide.md) +* [Jersey-2.x用户指南](https://waylau.gitbooks.io/jersey-2-user-guide/content/index.html) +* [REST 实战](https://waylau.gitbooks.io/rest-in-action/content/) +* [Java Servlet 3.1 规范](https://github.com/waylau/servlet-3.1-specification) +* [MyBatis中文指南](http://mybatis.github.io/mybatis-3/zh/index.html) +* [Apache Shiro 用户指南](https://github.com/waylau/apache-shiro-1.2.x-reference) +* [Spring Boot参考指南](https://github.com/qibaoguang/Spring-Boot-Reference-Guide/blob/master/SUMMARY.md) +* [Netty4 用户指南](https://github.com/waylau/netty-4-user-guide/blob/master/SUMMARY.md) +* [Google Java编程风格指南](user_guide/google-java8-guide.md) + +## 运维相关 + +* [linux安装jdk、tomcat脚本](shell/install_jdk_tomcat.sh) +* [Web性能测试工具](web/test_tool.md) +* [Java 程序员眼中的 Linux](https://github.com/judasn/Linux-Tutorial) +* [写给java开发的运维笔记](learn_server/README.md) + +## 经典文章 * [HTTP请求报文解剖](articles/request_message.md) * [软件架构模式](articles/software_architecture_patterns.md) * [电商网站架构案例](articles/electrical-business-architecture.md) +## 服务器/域名/SSL证书 + +* [可用域名](https://domainr.com) | [秋玉米](http://www.qiuyumi.com/) +* [优惠域名](http://hk.hostsir.com/Affiliates/i/d/976) | [非主流域名]() +* [vultr VPS](http://www.vultr.com/?ref=6886447) | [DO VPS](https://m.do.co/c/e2ceeb8b08fe) +* [免费VPN/SSH服务](https://sshdropbear.net/) +* [免费SS](http://get.ishadow.website/) +* [1小时免费VPN]() +* [SSL.DO](https://saki.ssl.do/aff.php?aff=11) | [Let's Encrypt](http://frontenddev.org/article/using-certbot-deployment-let-s-encrypt-free-ssl-certificate-implementation-https.html) +* [免费虚拟主机](http://api.hostinger.com.hk/redir/22018310) + +## 实用工具/API + +* [PNG图片无损压缩](https://tinypng.com/) +* [在线给图片加水印](http://tool.c7sky.com/image-watermark/) +* [随机密码生成](http://tool.c7sky.com/password/) +* [随机头像生成](https://randomuser.me/) +* [微博一键清理工具](http://tool.c7sky.com/tcleaner/) +* [CSS压缩](http://csscompressor.com/) +* [在线工具](http://www.atool.org/) ## 联系 diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..3065aa4 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,65 @@ +# Summary + +* [介绍](README.md) +* [开源组件实现](开源组件实现.md) + * [MVC框架实现篇](mvc/MVC框架实现篇.md) + * [MVC框架实现篇](mvc/index.md) + * [项目规划](mvc/1.plan.md) + * [路由设计](mvc/2.route.md) + * [控制器设计](mvc/3.controller.md) + * [配置设计](mvc/4.config.md) + * [视图设计](mvc/5.view.md) + * [数据库操作](mvc/6.dbutil.md) + * [增删改查](mvc/7.crud.md) + * [IOC容器实现篇](ioc/IOC容器实现篇.md) + * [IOC的概念](ioc/1.concept.md) + * [Spring中怎么用](ioc/2.spring.md) + * [设计一个IOC](ioc/3.myioc.md) + * [原理分析](ioc/4.principle.md) +* [设计模式系列](设计模式系列.md) + * [如何正确地写出单例模式](designpatterns/singleton.md) + * [代理模式剖析](designpatterns/proxy.md) + * [什么是策略模式](designpatterns/strategy.md) +* [Java8系列](Java8系列.md) + * [Java8简明教程](java8/java8-guide.md) + * [Java8 Foreach](java8/foreach.md) +* [Hexo搭建博客](Hexo搭建博客.md) + * [分分钟部署一个Hexo环境](hexo/hello.md) + * [各种配置详解](hexo/config.md) + * [开始写作吧](hexo/writing.md) +* [开发者指南](开发者指南.md) + * [git - 简明指南](git/guide.md) + * [Jersey-2.x用户指南](https://waylau.gitbooks.io/jersey-2-user-guide/content/index.html) + * [REST 实战](https://waylau.gitbooks.io/rest-in-action/content/) + * [Java Servlet 3.1 规范](https://github.com/waylau/servlet-3.1-specification) + * [MyBatis中文指南](http://mybatis.github.io/mybatis-3/zh/index.html) + * [Apache Shiro 用户指南](https://github.com/waylau/apache-shiro-1.2.x-reference) + * [Spring Boot参考指南](https://github.com/qibaoguang/Spring-Boot-Reference-Guide/blob/master/SUMMARY.md) + * [Netty4 用户指南](https://github.com/waylau/netty-4-user-guide/blob/master/SUMMARY.md) + * [Google Java编程风格指南](user_guide/google-java8-guide.md) +* [运维相关](运维相关.md) + * [Web性能测试工具](web/test_tool.md) + * [Java 程序员眼中的 Linux](https://github.com/judasn/Linux-Tutorial) + * [写给java开发的运维笔记](learn_server/README.md) +* [经典文章](经典文章.md) + * [HTTP请求报文解剖](articles/request_message.md) + * [软件架构模式](articles/software_architecture_patterns.md) + * [电商网站架构案例](articles/electrical-business-architecture.md) +* [服务器/域名/SSL证书](服务器相关.md) + * [可用域名](https://domainr.com) + * [优惠域名](http://hk.hostsir.com/Affiliates/i/d/976) + * [vultr VPS](http://www.vultr.com/?ref=6886447) + * [免费VPN/SSH服务](https://sshdropbear.net/) + * [免费SS](http://get.ishadow.website/) + * 1小时免费VPN + * [SSL.DO](https://saki.ssl.do/aff.php?aff=11) + * [免费虚拟主机](http://api.hostinger.com.hk/redir/22018310) +* [实用工具/API](实用工具.md) + * [PNG图片无损压缩](https://tinypng.com/) + * [在线给图片加水印](http://tool.c7sky.com/image-watermark/) + * [随机密码生成](http://tool.c7sky.com/password/) + * [随机头像生成](https://randomuser.me/) + * [微博一键清理工具](http://tool.c7sky.com/tcleaner/) + * [CSS压缩](http://csscompressor.com/) + * [在线工具](http://www.atool.org/) + diff --git "a/_book/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.html" "b/_book/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.html" new file mode 100644 index 0000000..b26ff3a --- /dev/null +++ "b/_book/Hexo\346\220\255\345\273\272\345\215\232\345\256\242.html" @@ -0,0 +1,1093 @@ + + + +
+ + +大型网站架构是一个系列文档,欢迎大家关注。本次分享主题:电商网站架构案例。从电商网站的需求,到单机架构,逐步演变为常用的,可供参考的分布式架构的原型。除具备功能需求外,还具备一定的高性能,高可用,可伸缩,可扩展等非功能质量需求(架构目标)。
+根据实际需要,进行改造,扩展,支持千万PV,是没问题的。
+电商网站案例,一共有三篇本篇主要说明网站的需求,网站初始架构,系统容量估算方法。
+分布式大型网站,目前看主要有几类1.大型门户,比如网易,新浪等;2.SNS网站,比如校内,开心网等;3.电商网站:比如阿里巴巴,京东商城,国美在线,汽车之家等。大型门户一般是新闻类信息,可以使用CDN,静态化等方式优化,开心网等交互性比较多,可能会引入更多的NOSQL,分布式缓存,使用高性能的通信框架等。电商网站具备以上两类的特点,比如产品详情可以采用CDN,静态化,交互性高的需要采用NOSQL等技术。因此,我们采用电商网站作为案例,进行分析。
+客户需求:
+客户就是客户,不会告诉你具体要什么,只会告诉你他想要什么,我们很多时候要引导,挖掘客户的需求。好在提供了明确的参考网站。因此,下一步要进行大量的分析,结合行业,以及参考网站,给客户提供方案。
+其他的略~
需求功能矩阵
+需求管理传统的做法,会使用用例图或模块图(需求列表)进行需求的描述。这样做常常忽视掉一个很重要的需求(非功能需求),因此推荐大家使用需求功能矩阵,进行需求描述。
+本电商网站的需求矩阵如下:
+| 网站需求 | +功能需求 | +非功能需求 | +
|---|---|---|
| 全品类的电子商务网站 | +分类管理,商品管理 | +方便进行多品类管理(灵活性) 网站访问速度要快(高性能) 图片存储的要求(海量小图片) |
+
| 用户可以在线购买商品 | +会员管理,购物车,结算功能 | +良好购物体验(可用性,性能) | +
| 在线支付或货到付款 | +多种在线支付方式 | +支付过程要安全,数据加密(安全性)多种支付接口灵活切换(灵活性,扩展性) | +
| 可以在线与客服沟通 | +在线客服功能 | +可靠性:即时通讯 | +
| 商品打分评价 | +商品评论 | ++ |
| 目前有成熟的进销存系统 | +对接进销存 | +属于约束条件对接时要考虑数据一致性,鲁棒性 | +
| 支持3~5年,业务的发展 | ++ | 属于约束条件伸缩性,可扩展性 | +
| 3~5年用户数达到1000万 | ++ | 约束条件 | +
| 举办双11,双12,三八男人节等活动 | +活动管理,秒杀 | +突增访问流量(可伸缩)实时性要求(高性能) | +
| 参考京东或国美在线 | ++ | 参考条件 | +
以上是对电商网站需求的简单举例,目的是说明(1)需求分析的时候,要全面,大型分布式系统重点考虑非功能需求;(2)描述一个简单的电商需求场景,使大家对下一步的分析设计有个依据。
+一般网站,刚开始的做法,是三台服务器,一台部署应用,一台部署数据库,一台部署NFS文件系统。
+这是前几年比较传统的做法,之前见到一个网站10万多会员,垂直服装设计门户,N多图片。使用了一台服务器部署了应用,数据库以及图片存储。出现了很多性能问题。
+如下图:
+
但是,目前主流的网站架构已经发生了翻天覆地的变化。一般都会采用集群的方式,进行高可用设计。至少是下面这个样子。
+
(1) 使用集群对应用服务器进行冗余,实现高可用;(负载均衡设备可与应用一块部署)
+(2) 使用数据库主备模式,实现数据备份和高可用;
+预估步骤:
+客户需求:3~5年用户数达到1000万注册用户;
+每秒并发数预估:
+没好好学数学后悔了吧?!(不知道以上算是否有错误,呵呵~~)
+服务器预估:(以tomcat服务器举例)
+容量预估:70/90原则
+系统CPU一般维持在70%左右的水平,高峰期达到90%的水平,是不浪费资源,并比较稳定的。内存,IO类似。
+以上预估仅供参考,因为服务器配置,业务逻辑复杂度等都有影响。在此CPU,硬盘,网络等不再进行评估。
+根据以上预估,有几个问题:
+大型网站一般需要做以下架构优化(优化是架构设计时,就要考虑的,一般从架构/代码级别解决,调优主要是简单参数的调整,比如JVM调优;如果调优涉及大量代码改造,就不是调优了,属于重构):
+根据业务属性进行垂直切分,划分为产品子系统,购物子系统,支付子系统,评论子系统,客服子系统,接口子系统(对接如进销存,短信等外部系统)。
+根据业务子系统进行等级定义,可分为核心系统和非核心系统。核心系统:产品子系统,购物子系统,支付子系统;非核心:评论子系统,客服子系统,接口子系统。
+业务拆分作用:提升为子系统可由专门的团队和部门负责,专业的人做专业的事,解决模块之间耦合以及扩展性问题;每个子系统单独部署,避免集中部署导致一个应用挂了,全部应用不可用的问题。
+等级定义作用:用于流量突发时,对关键应用进行保护,实现优雅降级;保护关键应用不受到影响。
+拆分后的架构图:
+
参考部署方案2
+
分布式部署:将业务拆分后的应用单独部署,应用直接通过RPC进行远程通信;
+集群部署:电商网站的高可用要求,每个应用至少部署两台服务器进行集群部署;
+负载均衡:是高可用系统必须的,一般应用通过负载均衡实现高可用,分布式服务通过内置的负载均衡实现高可用,关系型数据库通过主备方式实现高可用。
+集群部署后架构图:
+
缓存按照存放的位置一般可分为两类本地缓存和分布式缓存。本案例采用二级缓存的方式,进行缓存的设计。一级缓存为本地缓存,二级缓存为分布式缓存。(还有页面缓存,片段缓存等,那是更细粒度的划分)
+一级缓存,缓存数据字典,和常用热点数据等基本不可变/有规则变化的信息,二级缓存缓存需要的所有缓存。当一级缓存过期或不可用时,访问二级缓存的数据。如果二级缓存也没有,则访问数据库。
+缓存的比例,一般1:4,即可考虑使用缓存。(理论上是1:2即可)。
+
根据业务特性可使用以下缓存过期策略:
+系统分割为多个子系统,独立部署后,不可避免的会遇到会话管理的问题。一般可采用Session同步,Cookies,分布式Session方式。电商网站一般采用分布式Session实现。
+再进一步可以根据分布式Session,建立完善的单点登录或账户管理系统。
+
流程说明
+结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。
+大型网站需要存储海量的数据,为达到海量数据存储,高可用,高性能一般采用冗余的方式进行系统设计。一般有两种方式读写分离和分库分表。
+读写分离:一般解决读比例远大于写比例的场景,可采用一主一备,一主多备或多主多备方式。
+本案例在业务拆分的基础上,结合分库分表和读写分离。如下图:
+
相关中间件可参考Cobar(阿里,目前已不在维护),TDDL(阿里),Atlas(奇虎360),MyCat(在Cobar基础上,国内很多牛人,号称国内第一开源项目)。
+分库分表后序列的问题,JOIN,事务的问题,会在分库分表主题分享中,介绍。
+将多个子系统公用的功能/模块,进行抽取,作为公用服务使用。比如本案例的会员子系统就可以抽取为公用的服务。
+
消息队列可以解决子系统/模块之间的耦合,实现异步,高可用,高性能的系统。是分布式系统的标准配置。本案例中,消息队列主要应用在购物,配送环节。
+
目前使用较多的MQ有Active MQ,Rabbit MQ,Zero MQ,MS MQ等,需要根据具体的业务场景进行选择。建议可以研究下Rabbit MQ。
+除了以上介绍的业务拆分,应用集群,多级缓存,单点登录,数据库集群,服务化,消息队列外。还有CDN,反向代理,分布式文件系统,大数据处理等系统。
+此处不详细介绍,大家可以问度娘/Google,有机会的话也可以分享给大家。
+
以上是本次分享的架构总结,其中细节可参考前面分享的内容。其中还有很多可以优化和细化的地方,因为是案例分享,主要针对重要部分做了介绍,工作中需要大家根据具体的业务场景进行架构设计。
+以上是电商网站架构案例的分享一共有三篇,从电商网站的需求,到单机架构,逐步演变为常用的,可供参考的分布式架构的原型。除具备功能需求外,还具备一定的高性能,高可用,可伸缩,可扩展等非功能质量需求(架构目标)。
+ + +HTTP请求报文由3部分组成(请求行+请求头+请求体):
+
下面是一个实际的请求报文:
+
① 是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。不过,当前的大多数浏览器只支持GET和POST,Spring 3.0提供了一个HiddenHttpMethodFilter,允许你通过“_method”的表单参数指定这些特殊的HTTP方法(实际上还是通过POST提交表单)。服务端配置了HiddenHttpMethodFilter后,Spring会根据_method参数指定的值模拟出相应的HTTP方法,这样,就可以使用这些HTTP方法对处理方法进行映射了。
+② 为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL,③是协议名称及版本号。
+④ 是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
+⑤ 是报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数。
+对照上面的请求报文,我们把它进一步分解,你可以看到一幅更详细的结构图:
+
++1.IE系 +HttpWatch是强大的网页数据分析工具,安装后将集成到Internet Explorer工具栏中。它不用代理服务器或一些复杂的网络监控工具,就能抓取请求及响应的完整信息,包括Cookies、消息头、查询参数、响应报文等,是Web应用开发人员的必备工具。 + 2.Chrome,firefox + 自身的调试器已经很好用了,按F12就可以看到(IE10以上也直接按F12就可以调试了)
+
报文头属性是什么东西呢?我们不妨以一个小故事来说明吧。
+++快到中午了,张三丰不想去食堂吃饭,于是打电话叫外卖:老板,我要一份[鱼香肉丝],要12:30之前给我送过来哦,我在江湖湖公司研发部,叫张三丰。
+
这里,你要 [鱼香肉丝] 相当于HTTP报文体,而 “12:30之前送过来”,你叫 “张三丰” 等信息就相当于HTTP的报文头。它们是一些附属信息,帮忙你和饭店老板顺利完成这次交易。
请求HTTP报文和响应HTTP报文都拥有若干个报文关属性,它们是为协助客户端及服务端交易的一些附属信息。
+Accept
+请求报文可通过一个 Accept 报文头属性告诉服务端 客户端接受什么类型的响应。
如下报文头相当于告诉服务端,俺客户端能够接受的响应类型仅为纯文本数据啊,你丫别发其它什么图片啊,视频啊过来,那样我会歇菜的~~~:
+Accept:text/plain
+
+Accept属性的值可以为一个或多个MIME类型的值,关于MIME类型,大家请参考:http://en.wikipedia.org/wiki/MIME_type
+Cookie
+这是第一个要说的,客户端的Cookie就是通过这个报文头属性传给服务端的哦!如下所示:
+Cookie:skin=blue; jsessionid=5F4771183629C9834F8382E23BE13C4C
+
+注意到后台的那个 jsessionid=5F4771183629C9834F8382E23BE13C4C 没有,
+服务端是怎么知道客户端的多个请求是属于一个Session的,原来就是通过HTTP请求报文头的Cookie属性的jsessionid的值关联起来的!(当然也可以通过重写URL的方式将会话ID附带在每个URL的后后面哦)。
Referer
+表示这个请求是从哪个URL过来的,假如你通过google搜索出一个商家的广告页面,你对这个广告页面感兴趣,鼠标一点发送一个请求报文到商家的网站,这个请求报文的Referer报文头属性值就是 http://www.google.com。
+很多貌似神奇的网页监控软件(如著名的 我要啦),只要在你的网页上放上一段JavaScript,就可以帮你监控流量,全国访问客户的分布情况等报表和图表,其原理就是通过这个Referer及其它一些HTTP报文头工作的。
+Cache-Control
+对缓存进行控制,如一个请求希望响应返回的内容在客户端要被缓存一年,或不希望被缓存就可以通过这个报文头达到目的。
+如以下设置,相当于让服务端将对应请求返回的响应内容不要在客户端缓存(当然响应报文也是通过响应报文头通知浏览器客户端的,这个下面再说):
+Cache-Control: no-cache
+
+参见:http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+由于请求报文头是客户端发过来的,服务端当然只能读取了,以下是 HttpServletRequest 一些用于读取请求报文头的API:
//获取请求报文中的属性名称
+java.util.Enumeration<java.lang.String> getHeaderNames();
+
+//获取指定名称的报文头属性的值
+java.lang.String getHeader(java.lang.String name)
+
+由于一些请求报文头属性“太著名”了,因此HttpServletRequest为它们提供了VIP的API:
+//获取报文头中的Cookie(读取Cookie的报文头属性)
+ Cookie[] getCookies() ;
+
+//获取客户端本地化信息(读取 Accept-Language 的报文头属性)
+java.util.Locale getLocale()
+
+//获取请求报文体的长度(读取Content-Length的报文头属性)
+int getContentLength();
+
+HttpServletRequest可以通过 HttpSession getSession()
获取请求所关联的HttpSession,其内部的机理是通过读取请求报文头中Cookie属性的JSESSIONID的值,在服务端的一个会话Map中,根据这个JSESSIONID获取对应的HttpSession的对象。 +(这样,你就不会觉得HttpSession很神秘了吧,你自己也可以做一个类似的会话管理)
+HTTP的响应报文也由三部分组成(响应行+响应头+响应体):
+
以下是一个实际的HTTP响应报文:
+
① 报文协议及版本; +② 状态码及状态描述; +③ 响应报文头,也是由多个属性组成; +④ 响应报文体,即我们真正要的“干货”。
+和请求报文相比,响应报文多了一个“响应状态码”,它以“清晰明确”的语言告诉客户端本次请求的处理结果。
+HTTP的响应状态码由5段组成:
+以下是几个常见的状态码:
+200 OK
+你最希望看到的,即处理成功!
+303 See Other
+我把你redirect到其它的页面,目标的URL通过响应报文头的Location告诉你。
+++悟空:师傅给个桃吧,走了一天了 :relieved: +唐僧:我哪有桃啊!去王母娘娘那找吧 :unamused:
+
304 Not Modified
+告诉客户端,你请求的这个资源至你上次取得后,并没有更改,你直接用你本地的缓存吧,我很忙哦,你能不能少来烦我啊!
+404 Not Found
+你最不希望看到的,即找不到页面。如你在google上找到一个页面,点击这个链接返回404,表示这个页面已经被网站删除了,google那边的记录只是美好的回忆。
+500 Internal Server Error
+看到这个错误,你就应该查查服务端的日志了,肯定抛出了一堆异常,别睡了,起来改BUG去吧!
+有些响应码,Web应用服务器会自动给生成。你可以通过HttpServletResponse的API设置状态码:
+
+//设置状态码,状态码在HttpServletResponse中通过一系列的常量预定义了,如SC_ACCEPTED,SC_OK
+void setStatus(int sc)
+
+Cache-Control
+响应输出到客户端后,服务端通过该报文头属告诉客户端如何控制响应内容的缓存。
+下面,的设置让客户端对响应内容缓存3600秒,也即在3600秒内,如果客户再次访问该资源,直接从客户端的缓存中返回内容给客户,不要再从服务端获取(当然,这个功能是靠客户端实现的,服务端只是通过这个属性提示客户端“应该这么做”,做不做,还是决定于客户端,如果是自己宣称支持HTTP的客户端,则就应该这样实现)。
+Cache-Control: max-age=3600
+
+ETag
+一个代表响应服务端资源(如页面)版本的报文头属性,如果某个服务端资源发生变化了,这个ETag就会相应发生变化。它是Cache-Control的有益补充,可以让客户端“更智能”地处理什么时候要从服务端取资源,什么时候可以直接从缓存中返回响应。
+关于ETag的说明,你可以参见:http://en.wikipedia.org/wiki/HTTP_ETag。
+Spring 3.0还专门为此提供了一个 org.springframework.web.filter.ShallowEtagHeaderFilter (实现原理很简单,对JSP输出的内容MD5,这样内容有变化ETag就相应变化了),用于生成响应的ETag,因为这东东确实可以帮助减少请求和响应的交互。
下面是一个ETag:
+ETag: "737060cd8c284d8af7ad3082f209582d"
+
+Location
+我们在JSP中让页面Redirect到一个某个A页面中,其实是让客户端再发一个请求到A页面,这个需要Redirect的A页面的URL,其实就是通过响应报文头的Location属性告知客户端的,如下的报文头属性,将使客户端redirect到iteye的首页中:
+Location: https://github.com/biezhi/jb
+
+Set-Cookie
+服务端可以设置客户端的Cookie,其原理就是通过这个响应报文头属性实现的:
+Set-Cookie: UserID=Jack; Max-Age=3600; Version=1
+
+更多其它的HTTP请求头报文,参见:http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
+在服务端可以通过HttpServletResponse的API写响应报文头的属性:
+//添加一个响应报文头属性
+void setHeader(String name, String value)
+
+像Cookie,Location这些响应头是有福之人,HttpServletResponse为它们都提供了VIP(非API 哈):
+//添加Cookie报文头属性
+void addCookie(Cookie cookie)
+
+//不但会设置Location的响应报文头,还会生成303的状态码呢,两者天仙配呢
+void sendRedirect(String location)
+
+
+
+ 
对程序员来说很常见一种情况是在没有合理的程序架构时就开始编程,没有一个清晰的和定义好的架构的时候,大多数开发者和架构师通常会使用标准式的传统分层架构模式(也被称为多层架构)—通过将源码模块分割为几个不同的层到不同的包中。不幸的是,这种编码方式会导致一系列没有组织性的代码模块,这些模块缺乏明确的规则、职责和同其他模块之间的关联。这通常被称为架构大泥球。
+应用程序缺乏合理的架构一般会导致程序过度耦合、容易被破坏、难以应对变化,同时很难有个清晰的版本或者方向性。这样的结果是,如果你没有充分理解程序系统里每个组件和模块,就很难定义这个程序的结构特征。有关于程序的部署和维护的基本问题都难以回答,比如:程序架构是什么规模?应用程序有什么性能特点?应用程序有多容易应对变化?应用程序的部署特点是什么?架构是如何反应的?
+架构模式帮助你定义应用程序的基本特征和行为。例如,些架构模式会让程序自己自然⽽而然地朝着具有良 好伸缩性的方向发展,⽽而其他架构模式会让程序朝着高度灵活的方向发展。知道了这些特点,了解架构模式 的优点和缺点是非常必要的,它帮助我们选择个适合自己特定的业务需求和目标的的程序。
+作为个架构师,你必须证明你的架构模式的决策是正确的,特别是当需要选择一个特定的体系结构模式或方法 的时候。这本迷你书的目的就是给你⾜足够的信息让你去做出正确的架构决策。
+分层架构是一种很常⻅见的架构模式,它也叫N层架构。这种架构是大多数Jave EE应用的实际标准,因此很多 的架构师,设计师,还有程序员都知道它。许多传统IT公司的组织架构和分层模式十分的相似。所以它很自 然的成为大多数应用的架构模式。
+分层架构模式里的组件被分成几个平行的层次,每一层都代表了应用的一个功能(展示逻辑或者业务逻辑)。 尽管分层架构没有规定自⾝身要分成几层几种,大多数的结构都分成四个层次:展示层,业务层,持久层,和数 据库层。如表1-1,有时候,业务层和持久层会合并成单独的一个业务层,尤其是持久层的逻辑绑定在业务层 的组件当中。因此,有一些小的应用可能只有3层,一些有着更复杂的业务的大应用可能有5层或者更多的分 层。
+分层架构中的每一层都着特定的角色和职能。举个例子,展示层负责处理所有的界面展示以及交互逻辑,业 务层负责处理请求对应的业务。架构里的层次是具体工作的高度抽象,它们都是为了实现某种特定的业务请 求。比如说展示层并不需要关⼼心怎样得到用户数据,它只需在屏幕上以特定的格式展示信息。业务层并不关 +⼼心要展示在屏幕上的用户数据格式,也不关⼼心这些用户数据从哪里来。它只需要从持久层得到数据,执行与 数据有关的相应业务逻辑,然后把这些信息传递给展示层。
+
分层架构的一个突出特性是组件间关注点分离 (separation of concerns)。一个层中的组件只会处理本层的逻 辑。比如说,展示层的组件只会处理展示逻辑,业务层中的组件只会去处理业务逻辑。多亏了组件分离,让 我们更容易构造有效的角色和强⼒力的模型。这样应用变的更好开发,测试,管理和维护。
+注意表1-2中每一层都是封闭的。这是分层架构中非常重要的特点。这意味request必须一层一层的传递。举 个例子,从展示层传递来的请求⾸首先会传递到业务层,然后传递到持久层,最后才传递到数据层。
+
那么为什么不允许展示层直接访问数据层呢。如果只是获得以及读取数据,展示层直接访问数据层,比穿过一层一层来得到数据来的快多了。这涉及到一个概念:层隔离。
+层隔离就是说架构中的某一层的改变不会影响到其他层:这些变化的影响范围限于当前层次。如果展示层能够 直接访问持久层了,假如持久层中的SQL变化了,这对业务层和展示层都有一定的影响。这只会让应用变得 紧耦合,组件之间互相依赖。这种架构会非常的难以维护。
+从另外一个方面来说,分层隔离使得层与层之间都是相互独立的,架构中的每一层的互相了解都很少。为了 说明这个概念的⽜牛逼之处,想象一个超级重构,把展示层从JSP换成JSF。假设展示层和业务层的之间的联系 保持一致,业务层不会受到重构的影响,它和展示层所使用的界面架构完全独立。
+然⽽而封闭的架构层次也有不便之处,有时候也应该开放某一层。如果想往包含了一些由业务层的组件调用的 普通服务组件的架构中添加个分享服务层。在这个例子里,新建一个服务层通常是个好主意,因为从架构上来说,它限制了分享服务访问业务层(也不允许访问展示层)。如果没有隔离层,就没有任何架构来限制展示层访问普通服务,难以进行权限管理。
+在这个例子中,新的服务层是处于业务层之下的,展示层不能直接访问这个服务层中的组件。但是现在业务 层还要通过服务层才能访问到持久层,这一点也不合理。这是分层架构中的老问题了,解决的办法是开放某 些层。如表1-3所示,服务层现在是开放的了。请求可以绕过这一层,直接访问这层下面的层。既然服务层 是开放的,业务层可以绕过服务层,直接访问数据持久层。这样就非常合理。
+
开放和封闭层的概念确定了架构层和请求流之间的关系,并且给设计师和开发人员提供了必要的信息理解架 构里各种层之间的访问限制。如果随意的开放或者封闭架构里的层,整个项目可能都是紧耦合,一团糟的。以后也难以测试,维护和部署。
+为了演示分层架构是如何工作的,想象一个场景,如表1-4,用户发出了个请求要获得客户的信息。黑色的箭头是从数据库中获得用户数据的请求流,红色箭头显示用户数据的返回流的方向。在这个例子中,用户信 息由客户数据和订单数组组成(客户下的订单)。
+用户界面只管接受请求以及显示客户信息。它不管怎么得到数据的,或者说得到这些数据要用到哪些数据 表。如果用户界面接到了一个查询客户信息的请求,它就会转发这个请求给用户委托(Customer Delegate)模 块。这个模块能找到业务层里对应的模块处理对应数据(约束关系)。业务层里的customer object聚合了业务 请求需要的所有信息(在这个例子里获取客户信息)。这个模块调用持久层中的 customer dao 来得到客户信 息,调用order dao来得到订单信息。这些模块会执行SQL语句,然后返回相应的数据给业务层。当 customer object收到数据以后,它就会聚合这些数据然后传递给 customer delegate,然后传递这些数据到 customer screen 展示在用户面前。
+
从技术的角度来说,有很多的方式能够实现这些模块。比如说在Java平台中,customer screen 对应的是 (JSF) Java Server Faces ,用 bean 组件来实现 customer delegate。用本地的Spring bean或者远程的EJB3 bean 来实现业务层中的customer object。上例中的数据访问可以用简单的POJP's(Plain Old Java Objects),或者可以用MyBatis,还可以用JDBC或者Hibernate查询。Microsoft平台上,customer screen能 +用 .NET 库的ASP模块来访问业务层中的C#模块,用ADO来实现用户和订单数据的访问模块。
+我们看一下淘宝前几年的架构的例子。
+
这是一个标准的分层的架构。每一层中又可以详细的分成更细的层,比如服务层。
+
围着着这个主架构还有一些外围的产品。比如监控和审计。
+分层架构是一个很可靠的架构模式。它适合大多数的应用。如果你不确定在项目中使用什么架构,分层架构 是再好不过的了。然后,从架构的角度上来说,选择这个模式还要考虑很多的东⻄西。
+第一个要注意的就是 污水池反模式(architecture sinkhole anti-pattern)。 在这个模式中,请求流只是简单的 穿过层次,不留一点云彩,或者说只留下一阵⻘青烟。比如说界面层响应了一个获得数据的请求。响应层把这 个请求传递给了业务层,业务层也只是传递了这个请求到持久层,持久层对数据库做简单的SQL查询获得用户的数据。这个数据按照原理返回,不会有任何的二次处理,返回到界面上。
+每个分层架构或多或少都可能遇到这种场景。关键在于这样的请求有多少。80-20原则可以帮助你确定架构是 否处于反污水模式。大概有百分之二十的请求仅仅是做简单的穿越,百分之八十的请求会做一些业务逻辑操 作。然而,如果这个比例反过来,大部分的请求都是仅仅穿过层,不做逻辑操作。那么开放一些架构层会比较好。不过由于缺少了层次隔离,项目会变得难以控制。
+下⾯面的的表⾥里分析了分层架构的各个⽅方⾯面。
+评级:低 分析:总体灵活性是响应环境变化的能⼒力。尽管分层模式中的变化可以隔绝起来,想在这种架构中做⼀一些也改 变也是并且费时费⼒力的。分层模式的笨重以及经常出现的组件之间的紧耦合是导致灵活性降低的原因。
+评级:低 分析:这取决于你怎么发布这种模式,发布程序可能⽐比较⿇麻烦,尤其是很⼤大的项目。⼀一个组件的⼩小⼩小改动可能 会影响到整个程序的发布(或者程序的⼤大部分)。发布必须是按照计划,在⾮非⼯工作时间或者周末进⾏行发布。因此,分层模式导致应⽤用发布⼀一点也不流畅,在发布上降低了灵活性。
+评级:高 分析:因为组件都处于各⾃自的层次中,可以模拟其他的层,或者说直接去掉层,所以分层模式很容易测试。开发者可以单独模拟⼀一个展⽰示组件,对业务组件进⾏行隔绝测试。还可以模拟业务层来测试某个展⽰示功能。
+评级:低 分析:尽管某些分层架构的性能表现的确不错,但是这个模式的特点导致它⽆无法带来⾼高性能。因为⼀一次业务请求要穿越所有的架构层,做了很多不必要的⼯工作。
+评级:低 分析:由于这种模式以紧密耦合的趋势在发展,规模也⽐比较⼤大,⽤用分层架构构建的程序都⽐比较难以扩展。你可 以把各个层分成单独的物理模块或者干脆把整个程序分成多个节点来扩展分层架构,但是总体的关系过于紧
+密,这样很难扩展。
+评级:容易 分析:在开发难度上⾯面,分层架构得到了⽐比较⾼高的分数。因为这种架构对⼤大家来说很熟悉,不难实现。⼤大部分 公司在开发项⺫⽬目的都是通过层来区分技术的,这种模式对于⼤大多数的商业项目开发来说都很合适。公司的组织架构和他们软件架构之间的联系被戏称为"Conway's law"。你可以Google⼀一下查查这个有趣的联系。
+++ + +未完待续
+
维基百科上是这样描述代理模式的:所谓代理者是指一个类可以作为其他东西的接口。代理者可以作任何东西的接口, 例如网络连接,存储器中的大对象,文件或者其他无法复制的资源。
+著名的代理模式的例子就是引用计数(reference counting): 当需要一个复杂对象的多份副本时,代理模式可以结合享元模式以减少存储器的用量。典型做法是创建一个复杂对象以及多个代理者,每个代理者会引用到原本的对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。
+所谓静态代理,就是在编译阶段就生成代理类来完成对代理对象的一系列操作。下面是代理模式的结构类图:
+代理模式的角色分四种:
+下面是代理模式的类图结构:
+
下面以一个延迟加载的例子来说明一下静态代理。我们在启动某个服务系统时,加载某一个类时可能会耗费很长时间。为了获取更好的性能,在启动系统的时候,我们往往不去初始化这个复杂的类,取而代之的是去初始化其代理类。这样将耗费资源多的方法使用代理进行分离,可以加快系统的启动速度,减少用户等待的时间。
+public interface Subject {
+ public void sayHello();
+ public void sayGoodBye();
+}
+
+public class RealSubject implements Subject {
+ public void sayHello() {
+ System.out.println("Hello World");
+ }
+
+ public void sayGoodBye() {
+ System.out.println("GoodBye World");
+ }
+}
+
+public class StaticProxy implements Subject {
+
+ Private RealSubject realSubject = null;
+
+ public StaticProxy() {}
+
+ public void sayHello() {
+ //用到时候才加载,懒加载
+ if(realSubject == null) {
+ realSubject = new RealSubject();
+ }
+ realSubject.sayHello();
+ }
+
+ //sayGoodbye方法同理
+ ...
+}
+
+public class Client {
+ public static void main(String [] args) {
+ StaticProxy sp = new StaticProxy();
+ sp.sayHello();
+ sp.sayGoodBye();
+ }
+}
+
+以上就是静态代理的一个简单测试例子。感觉可能没有实际用途。然而并非如此。使用代理我们还可以将目标对象的方法进行改造,比如数据库连接池中创建了一系列连接,为了保证不频繁的打开连接,这些连接是几乎不会关闭的。然而我们编程总有习惯去将打开的 Connection 去 close 。 这样我们就可以利用代理模式来重新代理 Connection 接口中的 close 方法,改变为回收到数据库连接池中而不是真正的执行 Connection.close 方法。其他的例子还有很多,具体需要自己体会。
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。与静态处理类相比,动态类有诸多好处。
生成动态代理的方法有很多: JDK中自带动态代理,CGlib, javassist等。这些方法各有优缺点。本文主要探究JDK中的动态代理的使用和源码分析。
+下面用一个实例讲解一下JDK中动态代理的用法:
+public class dynamicProxy implements InvocationHandler {
+
+ private RealSubject = null;
+
+ public Object invoke(Object proxy, Method method, Object[] args){
+ if(RealSubject == null) {
+ RealSubject = new RealSubject();
+ }
+ method.invoke(RealSubject, args);
+ return RealSubject;
+ }
+
+}
+
+客户端代码实例
+public class Client {
+ public static void main(Strings[] args) {
+ Subject subject = (Subject)Proxy.newInstance(ClassLoader.getSystemLoader(), RealSubject.class.getInterfaces(), new DynamicProxy());
+ Subject.sayHello();
+ Subject.sayGoodBye();
+ }
+}
+
+从上面的代码可以看出,要利用JDK中的动态代理。利用静态方法Proxy.newInstance(ClassLoader, Interfaces[], InvokeHandler)可以创建一个动态代理类。 newInstance方法有三个参数,分别表示类加载器,一个希望该代理类实现的接口列表,以及实现InvokeHandler接口的实例。 动态代理将每个方法的执行过程则交给了Invoke方法处理。
JDK动态代理要求,被代理的必须是个接口,单纯的类则不行。JDK动态代理所生成的代理类都会继承 Proxy 类,同时代理类会实现所有你传入的接口列表。因此可以强制类型转换成接口类型。 下面是 Proxy 的结构图。

可以看出Proxy全是静态方法,因此如果代理类没有实现任何接口,那么他就是Proxy类型,没有实例方法。
当然加入你要是非要代理一个没有实现某个接口的类,同时该类的方法与其他接口定义的方法相同,利用反射也是可以轻松实现的。
+public class DynamicProxy implements InvokeHandler {
+
+ //你想代理的类
+ private TargetClass targetClass = null;
+
+ //初始化该类
+ public DynamicProxy(TargetClass targetClass) {
+ this.targetClass = targetClass;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) {
+ //利用反射获取你想代理的类的方法
+ Method myMethod = targetClass.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
+ myMethod.setAccessible(true);
+ return myMethod.invoke(targetClass, args);
+ }
+}
+
+看了上面的例子,我们只是简单会用动态代理。但是对于代理类是如何创建出来的,是谁调用Invoke方法等还云里雾里。下面通过分析
+首先看 Proxy.newInstance 方法的源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
+
+ //获取接口信息
+ final Class<?>[] intfs = interfaces.clone();
+ final SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
+ }
+
+ //生成代理类
+ Class<?> cl = getProxyClass0(loader, intfs);
+ // ...OK我们先看前半截
+}
+
+从源码看出代理类的生成是依靠getProxyClass0这个方法,接下来看getProxyClass0源码:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
+
+ //接口列表数目不能超过0xFFFF
+ if (interfaces.length > 65535) {
+ throw new IllegalArgumentException("interface limit exceeded");
+ }
+
+ //注意这里, 下面详细解释
+ return proxyClassCache.get(loader, interfaces);
+}
+
+对 proxyClassCache.get 的解释是: 如果实现接口列表的代理类已经存在,那么直接从cache中拿。如果不存在,则通过ProxyClassFactory生成一个。
在看 proxyClassCache.get 源码之前,先简单了解一下 proxyClassCache:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
+ proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
+
+proxyClassCache是一个WeakCache类型的缓存,它的构造函数有两个参数,其中一个就是用于生成代理类的ProxyClassFactory,下面是proxyClassCache.get的源码:
final class WeakCache<K, P, V> {
+ ...
+ public V get(K key, P parameter) {}
+}
+
+这里K表示key,P表示parameters, V表示value
+public V get(K key, P parameter) {
+
+ // java7 NullObject判断方法,如果parameter为空则抛出带有指定消息的异常。 如果不为空则返回。
+ Objects.requireNonNull(parameter);
+
+ // 清理持有弱引用的WeakHashMap这种数据结构,一般用于缓存
+ expungeStaleEntries();
+
+ // 从队列中获取cacheKey
+ Object cacheKey = CacheKey.valueOf(key, refQueue);
+
+ //利用懒加载的方式填充Supplier,Concurrent是一种线程安全的map
+ ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
+ if (valuesMap == null) {
+ ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
+ if (oldValuesMap != null) {
+ valuesMap = oldValuesMap;
+ }
+ }
+
+ // create subKey and retrieve the possible Supplier<V> stored by that
+ // subKey from valuesMap
+ Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
+ Supplier<V> supplier = valuesMap.get(subKey);
+ Factory factory = null;
+ while (true) {
+ if (supplier != null) {
+ // 从supplier中获取Value,这个Value可能是一个工厂或者Cache的实
+ //下面这三句代码是核心代码,返回实现InvokeHandler的类并包含了所需要的信息。
+ V value = supplier.get();
+ if (value != null) {
+ return value;
+ }
+ }
+
+ // else no supplier in cache
+ // or a supplier that returned null (could be a cleared CacheValue
+ // or a Factory that wasn't successful in installing the CacheValue)
+ //下面这个过程就是填充supplier的过程
+ if(factory == null) {
+ //创建一个factory
+ }
+ if(supplier == null) {
+ //填充supplier
+ }else {
+ //填充supplier
+ }
+ }
+}
+
+while循环的作用就是不停的获取实现InvokeHandler的类,这个类可以是从缓存中拿到,也可是是从proxyFactoryClass生成的。
+Factory是一个实现了Supplier<V>接口的内部类。这个类覆盖了get方法,在get方法中调用了类型为proxyFactoryClass的实例方法apply。这个方法才是真正创建代理类的方法。下面看ProxyFactoryClass.apply方法的源码:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
+ Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
+ for (Class<?> intf : interfaces) {
+ /* Verify that the class loader resolves the name of this interface to the same Class object.*/
+ Class<?> interfaceClass = null;
+ try {
+ //加载每一个接口运行时的信息
+ interfaceClass = Class.forName(intf.getName(), false, loader);
+ } catch (ClassNotFoundException e) {
+ }
+
+ //如果使用你自己的classload加载的class与你传入的class不相等,抛出异常
+ if (interfaceClass != intf) {
+ throw new IllegalArgumentException(
+ intf + " is not visible from class loader");
+ }
+
+ //如果传入不是一个接口类型
+ if (!interfaceClass.isInterface()) {
+ throw new IllegalArgumentException(
+ interfaceClass.getName() + " is not an interface");
+ }
+
+ //验证接口是否重复
+ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
+ throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
+ }
+ }
+
+ String proxyPkg = null; // package to define proxy class in
+ /* Record the package of a non-public proxy interface so that the proxy class will be defined in the same package.
+ * Verify that all non-public proxy interfaces are in the same package.
+ */
+ //这一段是看你传入的接口中有没有不是public的接口,如果有,这些接口必须全部在一个包里定义的,否则抛异常
+ for (Class<?> intf : interfaces) {
+ int flags = intf.getModifiers();
+ if (!Modifier.isPublic(flags)) {
+ String name = intf.getName();
+ int n = name.lastIndexOf('.');
+ String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
+ if (proxyPkg == null) {
+ proxyPkg = pkg;
+ } else if (!pkg.equals(proxyPkg)) {
+ throw new IllegalArgumentException(
+ "non-public interfaces from different packages");
+ }
+ }
+ }
+ if (proxyPkg == null) {
+ // if no non-public proxy interfaces, use com.sun.proxy package
+ proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
+ }
+ /*
+ * Choose a name for the proxy class to generate.
+ */
+ long num = nextUniqueNumber.getAndIncrement();
+ //生成随机代理类的类名,$Proxy + num
+ String proxyName = proxyPkg + proxyClassNamePrefix + num;
+ /*
+ * 生成代理类的class文件,返回字节流
+ */
+ byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
+ try {
+ return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
+ } catch (ClassFormatError e) {
+ //结束
+ throw new IllegalArgumentException(e.toString());
+ }
+ }
+ }
+}
+
+前文提到ProxyFactoryClass.apply是真正生成代理类的方法,这其实是不准确的。源代码读到这里,我们会发现ProxyGenerator.generateProxyClass才是真正生成代理类的方法。
+根据Java class字节码组成来生成相应的Clss文件。具体ProxyGenerator.generateProxyClass源码如下:
private byte[] generateClassFile() {
+ /*
+ * Step 1: Assemble ProxyMethod objects for all methods to
+ * generate proxy dispatching code for.
+ */
+ //addProxyMethod方法,就是将方法都加入到一个列表中,并与对应的class对应起来
+ //这里给Object对应了三个方法hashCode,toString和equals
+ addProxyMethod(hashCodeMethod, Object.class);
+ addProxyMethod(equalsMethod, Object.class);
+ addProxyMethod(toStringMethod, Object.class);
+ //将接口列表中的接口与接口下的方法对应起来
+ for (int i = 0; i < interfaces.length; i++) {
+ Method[] methods = interfaces[i].getMethods();
+ for (int j = 0; j < methods.length; j++) {
+ addProxyMethod(methods[j], interfaces[i]);
+ }
+ }
+ /*
+ * For each set of proxy methods with the same signature,
+ * verify that the methods' return types are compatible.
+ */
+ for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
+ checkReturnTypes(sigmethods);
+ }
+ /*
+ * Step 2: Assemble FieldInfo and MethodInfo structs for all of
+ * fields and methods in the class we are generating.
+ */
+ //方法中加入构造方法,这个构造方法只有一个,就是一个带有InvocationHandler接口的构造方法
+ //这个才是真正给class文件,也就是代理类加入方法了,不过还没真正处理,只是先加进来等待循环,构造方法在class文件中的名称描述是<init>
+ try {
+ methods.add(generateConstructor());
+ for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
+ for (ProxyMethod pm : sigmethods) {
+ //给每一个代理方法加一个Method类型的属性,数字10是class文件的标识符,代表这些属性都是private static的
+ fields.add(new FieldInfo(pm.methodFieldName,
+ "Ljava/lang/reflect/Method;",
+ ACC_PRIVATE | ACC_STATIC));
+ //将每一个代理方法都加到代理类的方法中
+ methods.add(pm.generateMethod());
+ }
+ }
+ //加入一个静态初始化块,将每一个属性都初始化,这里静态代码块也叫类构造方法,其实就是名称为<clinit>的方法,所以加到方法列表
+ methods.add(generateStaticInitializer());
+ } catch (IOException e) {
+ throw new InternalError("unexpected I/O Exception");
+ }
+ //方法和属性个数都不能超过65535,包括之前的接口个数也是这样,
+ //这是因为在class文件中,这些个数都是用4位16进制表示的,所以最大值是2的16次方-1
+ if (methods.size() > 65535) {
+ throw new IllegalArgumentException("method limit exceeded");
+ }
+ if (fields.size() > 65535) {
+ throw new IllegalArgumentException("field limit exceeded");
+ }
+ //接下来就是写class文件的过程,包括魔数,类名,常量池等一系列字节码的组成,就不一一细说了。需要的可以参考JVM虚拟机字节码的相关知识。
+ cp.getClass(dotToSlash(className));
+ cp.getClass(superclassName);
+ for (int i = 0; i < interfaces.length; i++) {
+ cp.getClass(dotToSlash(interfaces[i].getName()));
+ }
+ cp.setReadOnly();
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ DataOutputStream dout = new DataOutputStream(bout);
+ try {
+ // u4 magic;
+ dout.writeInt(0xCAFEBABE);
+ // u2 minor_version;
+ dout.writeShort(CLASSFILE_MINOR_VERSION);
+ // u2 major_version;
+ dout.writeShort(CLASSFILE_MAJOR_VERSION);
+ cp.write(dout); // (write constant pool)
+ // u2 access_flags;
+ dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
+ // u2 this_class;
+ dout.writeShort(cp.getClass(dotToSlash(className)));
+ // u2 super_class;
+ dout.writeShort(cp.getClass(superclassName));
+ // u2 interfaces_count;
+ dout.writeShort(interfaces.length);
+
+ // u2 interfaces[interfaces_count];
+ for (int i = 0; i < interfaces.length; i++) {
+ dout.writeShort(cp.getClass(
+ dotToSlash(interfaces[i].getName())));
+ }
+
+ // u2 fields_count;
+ dout.writeShort(fields.size());
+ // field_info fields[fields_count];
+ for (FieldInfo f : fields) {
+ f.write(dout);
+ }
+
+ // u2 methods_count;
+ dout.writeShort(methods.size());
+ // method_info methods[methods_count];
+ for (MethodInfo m : methods) {
+ m.write(dout);
+ }
+ // u2 attributes_count;
+ dout.writeShort(0); // (no ClassFile attributes for proxy classes)
+ } catch (IOException e) {
+ throw new InternalError("unexpected I/O Exception");
+ }
+ return bout.toByteArray();
+}
+
+经过层层调用,一个代理类终于生成了。
+我们模拟JDK自己生成一个代理类, 类名为TestProxyGen:
+public class TestGeneratorProxy {
+ public static void main(String[] args) throws IOException {
+ byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen",
+ Subject.class.getInterfaces());
+ File file = new File("/Users/yadoao/Desktop/TestProxyGen.class");
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(classFile);
+ fos.flush();
+ fos.close();
+ }
+}
+
+用JD-GUI反编译该class文件,结果如下:
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.UndeclaredThrowableException;
+
+public final class TestProxyGen extends Proxy implements ISubject {
+
+ private static Method m3;
+ private static Method m1;
+ private static Method m0;
+ private static Method m4;
+ private static Method m2;
+
+ public TestProxyGen(InvocationHandler paramInvocationHandler) throws {
+ super(paramInvocationHandler);
+ }
+
+ public final void sayHello() throws {
+ try {
+ this.h.invoke(this, m3, null);
+ return;
+ } catch (Error|RuntimeException localError) {
+ throw localError;
+ } catch (Throwable localThrowable) {
+ throw new UndeclaredThrowableException(localThrowable);
+ }
+ }
+
+ public final boolean equals(Object paramObject) throws {
+ try {
+ return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
+ } catch (Error|RuntimeException localError) {
+ throw localError;
+ } catch (Throwable localThrowable) {
+ throw new UndeclaredThrowableException(localThrowable);
+ }
+ }
+
+ public final int hashCode() throws {
+ try {
+ return ((Integer)this.h.invoke(this, m0, null)).intValue();
+ } catch (Error|RuntimeException localError) {
+ throw localError;
+ } catch (Throwable localThrowable) {
+ throw new UndeclaredThrowableException(localThrowable);
+ }
+ }
+
+ public final void sayGoodBye() throws {
+ try {
+ this.h.invoke(this, m4, null);
+ return;
+ } catch (Error|RuntimeException localError) {
+ throw localError;
+ } catch (Throwable localThrowable) {
+ throw new UndeclaredThrowableException(localThrowable);
+ }
+ }
+
+ public final String toString() throws {
+ try {
+ return (String)this.h.invoke(this, m2, null);
+ } catch (Error|RuntimeException localError) {
+ throw localError;
+ } catch (Throwable localThrowable) {
+ throw new UndeclaredThrowableException(localThrowable);
+ }
+ }
+
+ static {
+ try {
+ m3 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayHello", new Class[0]);
+ m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
+ m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
+ m4 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayGoodBye", new Class[0]);
+ m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
+ return;
+ } catch (NoSuchMethodException localNoSuchMethodException) {
+ throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
+ } catch (ClassNotFoundException localClassNotFoundException) {
+ throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
+ }
+ }
+}
+
+就此代理模式分析到此结束。
+单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问题,但如果你不知道如何创建一个线程安全的单例,不知道什么是双检锁,那这篇文章可能会帮助到你。
+当被问到要实现一个单例模式时,很多人的第一反应是写出如下的代码,包括教科书上也是这样教我们的。
+public class Singleton {
+ private static Singleton instance;
+ private Singleton (){}
+
+ public static Singleton getInstance() {
+ if (instance == null) {
+ instance = new Singleton();
+ }
+ return instance;
+ }
+}
+
+这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
+为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。
+public static synchronized Singleton getInstance() {
+ if (instance == null) {
+ instance = new Singleton();
+ }
+ return instance;
+}
+
+虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
+双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public static Singleton getSingleton() {
+ if (instance == null) { //Single Checked
+ synchronized (Singleton.class) {
+ if (instance == null) { //Double Checked
+ instance = new Singleton();
+ }
+ }
+ }
+ return instance ;
+}
+
+这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
+但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
+我们只需要将 instance 变量声明成 volatile 就可以了。
+public class Singleton {
+ private volatile static Singleton instance; //声明成 volatile
+ private Singleton (){}
+
+ public static Singleton getSingleton() {
+ if (instance == null) {
+ synchronized (Singleton.class) {
+ if (instance == null) {
+ instance = new Singleton();
+ }
+ }
+ }
+ return instance;
+ }
+
+}
+
+有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
+但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。
+相信你不会喜欢这种复杂又隐含问题的方式,当然我们有更好的实现线程安全的单例模式的办法。
+这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
+public class Singleton{
+ //类加载时就初始化
+ private static final Singleton instance = new Singleton();
+
+ private Singleton(){}
+
+ public static Singleton getInstance(){
+ return instance;
+ }
+}
+
+这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
+我比较倾向于使用静态内部类的方法,这种方法也是《Effective Java》上所推荐的。
+public class Singleton {
+ private static class SingletonHolder {
+ private static final Singleton INSTANCE = new Singleton();
+ }
+ private Singleton (){}
+ public static final Singleton getInstance() {
+ return SingletonHolder.INSTANCE;
+ }
+}
+
+这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
+用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。
+public enum EasySingleton{
+ INSTANCE;
+}
+
+我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。
+一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。上述所说都是线程安全的实现,文章开头给出的第一种方法不算正确的写法。
+就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
+由于作者这篇文章写的非常好,我也就没有重写 原文出处
+ + +策略模式,顾名思义就是指对象具有某个行为,但是在不同的业务场景下,这个行为应该有不同的表现形式,也就是有了不同的策略。让对象能再不同的场景下对同一行为有不同的实现,这就是策略模式。
+下面是策略模式的类图:
+
public interface Strategy {
+ public void algorithmStartegy() ;
+}
+
+public class ConcentrateStrategy_1 implements Strategy{
+ @Override
+ public void algorithmStartegy() {
+ System.out.println("I am algorithm strategy 1");
+ }
+}
+public class ConcentrateStrategy_2 implements Strategy{
+ @Override
+ public void algorithmStartegy() {
+ System.out.println("I am algorithm strategy 2");
+ }
+}
+
+public class Situation {
+ Strategy strategy;
+ public Situation(Strategy strategy) {
+ this.strategy = strategy;
+ }
+ public void handleAlgorithm() {
+ strategy.algorithmStartegy();
+ }
+}
+
+Situation situation = new Situation(new ConcentrateStrategy_1());
+situation.handleAlgorithm();
+ ...
+
+从策略模式的描述以及类图来看真的是非常简单,总结起来就是策略模式定义了一组算法,它们有一个共同的策略行为接口,并且这些算法之间可以互相替换,使算法可以根据场景的不同而改变。
+策略模式的应用有很多, 比如说JDK中FilenameFilter的使用过程, 比如场景java.util.Collections.sort(List<T>list, Comparator<? super T>c)与策略java.util.Comparator的使用等等。
+下面我以一个实际的业务场景来具体实现以下策略模式:
实现
+public interface IProduct {
+public String generateXML() ;
+}
+
+@XMLType("Default")
+class DefaultHead implements IProduct {
+public String generateXML() {
+ return "Defalut XML";
+}
+}
+@XMLType("ChinaMobile")
+class ChinaMobile implements IProduct{
+public String generateXML() {
+ return "China Mobile XML";
+}
+}
+@XMLType("ChinaUnicom")
+class ChinaUnicom implements IProduct {
+public String generateXML() {
+ return "Chinal Unicom XML";
+}
+}
+
+public class XMLGenerator {
+private IProduct product = new DefaultHead();
+//根据用户缴费类型,生成不同的通信报文
+public String generate(String type) {
+ //注意此处
+ product = ProductFactory.getInstance().createProduct(type);
+return product.generateXML();
+}
+}
+
+创建具体的策略类, 本来是可以用一系列的if else判断, 然后new相应的策略类。这里为了避免if else我们定义一个策略工厂,来生产具体的策略类。
+策略类工厂:这个工厂创建策略类的思路就是,载入一些列策略类,根据不同策略类的自定义注解和用户的传入参数来生成具体的策略类。
+```java
+public class ProductFactory {
+// singleton
+private ProductFactory() {}
public static ProductFactory getInstance() { + return ProductFactoryInstance.instance; +}
+private static class ProductFactoryInstance { + static final ProductFactory instance = new ProductFactory(); +}
+//创建一个具体的策略类
+public IProduct createProduct(String productType) throws URISyntaxException {
+ // load all startegys
+ List
//解析策略类的注解
+ XMLType xmlType = praseAnnotation(clazz);
+ if(xmlType.value().equals(productType))
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+} + //create IProduct Object failed + return null; +}
+//载入策略类方法
+private List
@Override
+ public boolean accept(File dir, String name) {
+ if (name.endsWith(".class"))
+ return true;
+ return false;
+ }
+}); + // load class + for (File file : files) {
+ try {
+ Class clazz = getClass().getClassLoader().loadClass(packageName + "." + file.getName().replace(".class", ""));
+ if (clazz != IProduct.class && IProduct.class.isAssignableFrom(clazz)) {
+ strategyList.add(clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+} + return strategyList; +}
+//解析注解方法 +private XMLType praseAnnotation(Class<? extends IProduct> clazz) { + XMLType xmlType = clazz.getAnnotation(XMLType.class); + if (xmlType == null) {
+ return null;
+} + return xmlType; +}
+}
+
+此处我使用**注解**的原因是为了简单, 跟代码的耦合紧一点。
+
+**自定义的注解类: **
+
+```java
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface XMLType {
+ public String value() default "defalut";
+}
+以上就是此处的策略模式的设计过程,如果需要多种策略的叠加, 也就要相应的使用注解的嵌套了, 这里就不在发挥了。
+学习策略模式, 不要记住代码是怎么实现的,更重要的是记住其设计原则。根据原则写代码, 而不是生搬硬套。其实设计模式都是设计原则的体现, 如果理解了设计原则, 那么你写的代码也可以变成一种模式。
+开闭原则(Open-Closed Principle,缩写为OCP) +一个软件实体应当对扩展(例如对抽象层的扩展)开放,对修改(例如对抽象层的修改)关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。 +开闭原则的关键,在于抽象。策略模式,是开闭原则的一个极好的应用范例。
+里氏替换原则(Liskov Substitution Principle,缩写为LSP) +一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉到基类对象和子类对象的区别。比如,假设有两个类,一个是Base类,一个是Derived类,并且Derived类是Base类的子类。那么一个方法如果可以接受一个基类对象b的话:method1(Base b),那么它必然可以接受一个子类对象d,也即可以有method1(d)。反之,则不一定成立 +里氏替换原则讲的是基类与子类的关系。只有当这种关系存在时,里氏替换关系才存在,反之则不存在。
+策略模式之所以可行的基础便是里氏替换原则:策略模式要求所有的策略对象都是可以互换的,因此它们都必须是一个抽象策略角色的子类。在客户端则仅知道抽象策略角色类型,虽然变量的真实类型可以是任何一个具体策略角色的实例
+++助你入门 git 的简明指南,木有高深内容 ;)
+
创建新文件夹,打开,然后执行
+git init
+
+以创建新的 git 仓库。
+执行如下命令以创建一个本地仓库的克隆版本:
+git clone /path/to/repository
+
+如果是远端服务器上的仓库,你的命令会是这个样子:
+git clone username@host:/path/to/repository
+
+你的本地仓库由 git 维护的三棵“树”组成。第一个是你的 工作目录,它持有实际文件;第二个是 暂存区(Index),它像个缓存区域,临时保存你的改动;最后是 HEAD,它指向你最后一次提交的结果。

你可以提出更改(把它们添加到暂存区),使用如下命令:
+git add <filename>
+git add *
+
+这是 git 基本工作流程的第一步;使用如下命令以实际提交改动:
+git commit -m "代码提交信息"
+
+现在,你的改动已经提交到了 HEAD,但是还没到你的远端仓库。
+你的改动现在已经在本地仓库的 HEAD 中了。执行如下命令以将这些改动提交到远端仓库:
+git push origin master
+
+可以把 master 换成你想要推送的任何分支。
+如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:
+git remote add origin <server>
+
+如此你就能够将你的改动推送到所添加的服务器上去了。
+分支是用来将特性开发绝缘开来的。在你创建仓库的时候,master 是“默认的”分支。在其他分支上进行开发,完成后再将它们合并到主分支上。
+
创建一个叫做“feature_x”的分支,并切换过去:
+git checkout -b feature_x
+
+切换回主分支:
+git checkout master
+
+再把新建的分支删掉:
+git branch -d feature_x
+
+除非你将分支推送到远端仓库,不然该分支就是 不为他人所见的:
+git push origin <branch>
+
+要更新你的本地仓库至最新改动,执行:
+git pull
+
+以在你的工作目录中 获取(fetch) 并 合并(merge) 远端的改动。 +要合并其他分支到你的当前分支(例如 master),执行:
+git merge <branch>
+
+在这两种情况下,git 都会尝试去自动合并改动。遗憾的是,这可能并非每次都成功,并可能出现冲突(conflicts)。 这时候就需要你修改这些文件来手动合并这些冲突(conflicts)。改完之后,你需要执行如下命令以将它们标记为合并成功:
+git add <filename>
+
+在合并改动之前,你可以使用如下命令预览差异:
+git diff <source_branch> <target_branch>
+
+为软件发布创建标签是推荐的。这个概念早已存在,在 SVN 中也有。你可以执行如下命令创建一个叫做 1.0.0 的标签:
+git tag 1.0.0 1b2e1d63ff
+
+1b2e1d63ff 是你想要标记的提交 ID 的前 10 位字符。可以使用下列命令获取提交 ID:
+git log
+
+你也可以使用少一点的提交 ID 前几位,只要它的指向具有唯一性。
+假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动:
+git checkout -- <filename>
+
+此命令会使用 HEAD 中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。
+假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:
+git fetch origin
+git reset --hard origin/master
+
+内建的图形化 git:
+gitk
+
+彩色的 git 输出:
+git config color.ui true
+
+显示历史记录时,每个提交的信息只显示一行:
+git config format.pretty oneline
+
+交互式添加文件到暂存区:
+git add -i
+
+').html(content);
+
+ $link.appendTo($title);
+ $title.appendTo($li);
+ $content.appendTo($li);
+ $li.appendTo($searchList);
+ });
+ }
+
+ function launchSearch(q) {
+ // Add class for loading
+ $body.addClass('with-search');
+ $body.addClass('search-loading');
+
+ // Launch search query
+ throttle(gitbook.search.query(q, 0, MAX_RESULTS)
+ .then(function(results) {
+ displayResults(results);
+ })
+ .always(function() {
+ $body.removeClass('search-loading');
+ }), 1000);
+ }
+
+ function closeSearch() {
+ $body.removeClass('with-search');
+ $bookSearchResults.removeClass('open');
+ }
+
+ function launchSearchFromQueryString() {
+ var q = getParameterByName('q');
+ if (q && q.length > 0) {
+ // Update search input
+ $searchInput.val(q);
+
+ // Launch search
+ launchSearch(q);
+ }
+ }
+
+ function bindSearch() {
+ // Bind DOM
+ $searchInput = $('#book-search-input input');
+ $bookSearchResults = $('#book-search-results');
+ $searchList = $bookSearchResults.find('.search-results-list');
+ $searchTitle = $bookSearchResults.find('.search-results-title');
+ $searchResultsCount = $searchTitle.find('.search-results-count');
+ $searchQuery = $searchTitle.find('.search-query');
+
+ // Launch query based on input content
+ function handleUpdate() {
+ var q = $searchInput.val();
+
+ if (q.length == 0) {
+ closeSearch();
+ }
+ else {
+ launchSearch(q);
+ }
+ }
+
+ // Detect true content change in search input
+ // Workaround for IE < 9
+ var propertyChangeUnbound = false;
+ $searchInput.on('propertychange', function(e) {
+ if (e.originalEvent.propertyName == 'value') {
+ handleUpdate();
+ }
+ });
+
+ // HTML5 (IE9 & others)
+ $searchInput.on('input', function(e) {
+ // Unbind propertychange event for IE9+
+ if (!propertyChangeUnbound) {
+ $(this).unbind('propertychange');
+ propertyChangeUnbound = true;
+ }
+
+ handleUpdate();
+ });
+
+ // Push to history on blur
+ $searchInput.on('blur', function(e) {
+ // Update history state
+ if (usePushState) {
+ var uri = updateQueryString('q', $(this).val());
+ history.pushState({ path: uri }, null, uri);
+ }
+ });
+ }
+
+ gitbook.events.on('page.change', function() {
+ bindSearch();
+ closeSearch();
+
+ // Launch search based on query parameter
+ if (gitbook.search.isInitialized()) {
+ launchSearchFromQueryString();
+ }
+ });
+
+ gitbook.events.on('search.ready', function() {
+ bindSearch();
+
+ // Launch search from query param at start
+ launchSearchFromQueryString();
+ });
+
+ function getParameterByName(name) {
+ var url = window.location.href;
+ name = name.replace(/[\[\]]/g, '\\$&');
+ var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'),
+ results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+ }
+
+ function updateQueryString(key, value) {
+ value = encodeURIComponent(value);
+
+ var url = window.location.href;
+ var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'),
+ hash;
+
+ if (re.test(url)) {
+ if (typeof value !== 'undefined' && value !== null)
+ return url.replace(re, '$1' + key + '=' + value + '$2$3');
+ else {
+ hash = url.split('#');
+ url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
+ if (typeof hash[1] !== 'undefined' && hash[1] !== null)
+ url += '#' + hash[1];
+ return url;
+ }
+ }
+ else {
+ if (typeof value !== 'undefined' && value !== null) {
+ var separator = url.indexOf('?') !== -1 ? '&' : '?';
+ hash = url.split('#');
+ url = hash[0] + separator + key + '=' + value;
+ if (typeof hash[1] !== 'undefined' && hash[1] !== null)
+ url += '#' + hash[1];
+ return url;
+ }
+ else
+ return url;
+ }
+ }
+});
diff --git a/_book/gitbook/gitbook-plugin-sharing/buttons.js b/_book/gitbook/gitbook-plugin-sharing/buttons.js
new file mode 100644
index 0000000..709a4e4
--- /dev/null
+++ b/_book/gitbook/gitbook-plugin-sharing/buttons.js
@@ -0,0 +1,90 @@
+require(['gitbook', 'jquery'], function(gitbook, $) {
+ var SITES = {
+ 'facebook': {
+ 'label': 'Facebook',
+ 'icon': 'fa fa-facebook',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href));
+ }
+ },
+ 'twitter': {
+ 'label': 'Twitter',
+ 'icon': 'fa fa-twitter',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href));
+ }
+ },
+ 'google': {
+ 'label': 'Google+',
+ 'icon': 'fa fa-google-plus',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href));
+ }
+ },
+ 'weibo': {
+ 'label': 'Weibo',
+ 'icon': 'fa fa-weibo',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title));
+ }
+ },
+ 'instapaper': {
+ 'label': 'Instapaper',
+ 'icon': 'fa fa-instapaper',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href));
+ }
+ },
+ 'vk': {
+ 'label': 'VK',
+ 'icon': 'fa fa-vk',
+ 'onClick': function(e) {
+ e.preventDefault();
+ window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href));
+ }
+ }
+ };
+
+
+
+ gitbook.events.bind('start', function(e, config) {
+ var opts = config.sharing;
+
+ // Create dropdown menu
+ var menu = $.map(opts.all, function(id) {
+ var site = SITES[id];
+
+ return {
+ text: site.label,
+ onClick: site.onClick
+ };
+ });
+
+ // Create main button with dropdown
+ if (menu.length > 0) {
+ gitbook.toolbar.createButton({
+ icon: 'fa fa-share-alt',
+ label: 'Share',
+ position: 'right',
+ dropdown: [menu]
+ });
+ }
+
+ // Direct actions to share
+ $.each(SITES, function(sideId, site) {
+ if (!opts[sideId]) return;
+
+ gitbook.toolbar.createButton({
+ icon: site.icon,
+ label: site.text,
+ position: 'right',
+ onClick: site.onClick
+ });
+ });
+ });
+});
diff --git a/_book/gitbook/gitbook.js b/_book/gitbook/gitbook.js
new file mode 100644
index 0000000..13077b4
--- /dev/null
+++ b/_book/gitbook/gitbook.js
@@ -0,0 +1,4 @@
+!function e(t,n,r){function o(s,a){if(!n[s]){if(!t[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(i)return i(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[s]={exports:{}};t[s][0].call(l.exports,function(e){var n=t[s][1][e];return o(n?n:e)},l,l.exports,e,t,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s =0&&n-1)o&&o.push(i);else if(c=de.contains(i.ownerDocument,i),s=v(f.appendChild(i),"script"),c&&y(s),n)for(l=0;i=s[l++];)Ve.test(i.type||"")&&n.push(i);return f}function b(){return!0}function w(){return!1}function T(){try{return te.activeElement}catch(e){}}function C(e,t,n,r,o,i){var s,a;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(a in t)C(e,a,n,r,t[a],i);return e}if(null==r&&null==o?(o=n,r=n=void 0):null==o&&("string"==typeof n?(o=r,r=void 0):(o=r,r=n,n=void 0)),o===!1)o=w;else if(!o)return e;return 1===i&&(s=o,o=function(e){return de().off(e),s.apply(this,arguments)},o.guid=s.guid||(s.guid=de.guid++)),e.each(function(){de.event.add(this,t,o,r,n)})}function j(e,t){return de.nodeName(e,"table")&&de.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e:e}function k(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function E(e){var t=rt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function S(e,t){var n,r,o,i,s,a,u,c;if(1===t.nodeType){if(Fe.hasData(e)&&(i=Fe.access(e),s=Fe.set(t,i),c=i.events)){delete s.handle,s.events={};for(o in c)for(n=0,r=c[o].length;n=s&&(r!==u&&(c=void 0,l=[e]),n.rejectWith(c,l))}};t?p():(de.Deferred.getStackHook&&(p.stackTrace=de.Deferred.getStackHook()),e.setTimeout(p))}}var s=0;return de.Deferred(function(e){n[0][3].add(i(0,e,de.isFunction(o)?o:a,e.notifyWith)),n[1][3].add(i(0,e,de.isFunction(t)?t:a)),n[2][3].add(i(0,e,de.isFunction(r)?r:u))}).promise()},promise:function(e){return null!=e?de.extend(e,o):o}},i={};return de.each(n,function(e,t){var s=t[2],a=t[5];o[t[1]]=s.add,a&&s.add(function(){r=a},n[3-e][2].disable,n[0][2].lock),s.add(t[3].fire),i[t[0]]=function(){return i[t[0]+"With"](this===i?void 0:this,arguments),this},i[t[0]+"With"]=s.fireWith}),o.promise(i),t&&t.call(i,i),i},when:function(e){var t=arguments.length,n=t,r=Array(n),o=re.call(arguments),i=de.Deferred(),s=function(e){return function(n){r[e]=this,o[e]=arguments.length>1?re.call(arguments):n,--t||i.resolveWith(r,o)}};if(t<=1&&(c(e,i.done(s(n)).resolve,i.reject),"pending"===i.state()||de.isFunction(o[n]&&o[n].then)))return i.then();for(;n--;)c(o[n],s(n),i.reject);return i.promise()}});var De=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;de.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&De.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},de.readyException=function(t){e.setTimeout(function(){throw t})};var Oe=de.Deferred();de.fn.ready=function(e){return Oe.then(e).catch(function(e){de.readyException(e)}),this},de.extend({isReady:!1,readyWait:1,holdReady:function(e){e?de.readyWait++:de.ready(!0)},ready:function(e){(e===!0?--de.readyWait:de.isReady)||(de.isReady=!0,e!==!0&&--de.readyWait>0||Oe.resolveWith(te,[de]))}}),de.ready.then=Oe.then,"complete"===te.readyState||"loading"!==te.readyState&&!te.documentElement.doScroll?e.setTimeout(de.ready):(te.addEventListener("DOMContentLoaded",l),e.addEventListener("load",l));var Le=function(e,t,n,r,o,i,s){var a=0,u=e.length,c=null==n;if("object"===de.type(n)){o=!0;for(a in n)Le(e,t,a,n[a],!0,i,s)}else if(void 0!==r&&(o=!0,de.isFunction(r)||(s=!0),c&&(s?(t.call(e,r),t=null):(c=t,t=function(e,t,n){return c.call(de(e),n)})),t))for(;a1,null,!0)},removeData:function(e){return this.each(function(){Re.remove(this,e)})}}),de.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Fe.get(e,t),n&&(!r||de.isArray(n)?r=Fe.access(e,t,de.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=de.queue(e,t),r=n.length,o=n.shift(),i=de._queueHooks(e,t),s=function(){de.dequeue(e,t)};"inprogress"===o&&(o=n.shift(),r--),o&&("fx"===t&&n.unshift("inprogress"),delete i.stop,o.call(e,s,i)),!r&&i&&i.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Fe.get(e,n)||Fe.access(e,n,{empty:de.Callbacks("once memory").add(function(){Fe.remove(e,[t+"queue",n])})})}}),de.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length","
"],col:[2,"
"],tr:[2,"","
"],td:[3,"
"],_default:[0,"",""]};Ge.optgroup=Ge.option,Ge.tbody=Ge.tfoot=Ge.colgroup=Ge.caption=Ge.thead,Ge.th=Ge.td;var Ye=/<|?\w+;/;!function(){var e=te.createDocumentFragment(),t=e.appendChild(te.createElement("div")),n=te.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),t.appendChild(n),pe.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,t.innerHTML="",pe.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue}();var Qe=te.documentElement,Je=/^key/,Ke=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ze=/^([^.]*)(?:\.(.+)|)/;de.event={global:{},add:function(e,t,n,r,o){var i,s,a,u,c,l,f,p,h,d,g,m=Fe.get(e);if(m)for(n.handler&&(i=n,n=i.handler,o=i.selector),o&&de.find.matchesSelector(Qe,o),n.guid||(n.guid=de.guid++),(u=m.events)||(u=m.events={}),(s=m.handle)||(s=m.handle=function(t){return"undefined"!=typeof de&&de.event.triggered!==t.type?de.event.dispatch.apply(e,arguments):void 0}),t=(t||"").match(qe)||[""],c=t.length;c--;)a=Ze.exec(t[c])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h&&(f=de.event.special[h]||{},h=(o?f.delegateType:f.bindType)||h,f=de.event.special[h]||{},l=de.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&de.expr.match.needsContext.test(o),namespace:d.join(".")},i),(p=u[h])||(p=u[h]=[],p.delegateCount=0,f.setup&&f.setup.call(e,r,d,s)!==!1||e.addEventListener&&e.addEventListener(h,s)),f.add&&(f.add.call(e,l),l.handler.guid||(l.handler.guid=n.guid)),o?p.splice(p.delegateCount++,0,l):p.push(l),de.event.global[h]=!0)},remove:function(e,t,n,r,o){var i,s,a,u,c,l,f,p,h,d,g,m=Fe.hasData(e)&&Fe.get(e);if(m&&(u=m.events)){for(t=(t||"").match(qe)||[""],c=t.length;c--;)if(a=Ze.exec(t[c])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){for(f=de.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=i=p.length;i--;)l=p[i],!o&&g!==l.origType||n&&n.guid!==l.guid||a&&!a.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(p.splice(i,1),l.selector&&p.delegateCount--,f.remove&&f.remove.call(e,l));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||de.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)de.event.remove(e,h+t[c],n,r,!0);de.isEmptyObject(u)&&Fe.remove(e,"handle events")}},dispatch:function(e){var t,n,r,o,i,s,a=de.event.fix(e),u=new Array(arguments.length),c=(Fe.get(this,"events")||{})[a.type]||[],l=de.event.special[a.type]||{};for(u[0]=a,t=1;t","