-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 293 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 293 KB
1
{"meta":{"title":"XiYang's Blog","subtitle":"让一切慢下来,体验周边的美好","description":null,"author":"XiYang","url":"http://yoursite.com"},"pages":[{"title":"分类","date":"2017-09-14T01:37:51.000Z","updated":"2017-09-14T01:40:55.000Z","comments":false,"path":"categories/index.html","permalink":"http://yoursite.com/categories/index.html","excerpt":"","text":""},{"title":"个人项目","date":"2017-09-14T01:30:29.000Z","updated":"2017-12-12T10:01:09.000Z","comments":true,"path":"project/index.html","permalink":"http://yoursite.com/project/index.html","excerpt":"","text":"工作时间久了,对于重复性的工作越来越排斥,本页面记录了一些在工作中创造的一些轮子,等轮子多了,再开发就是一些积木的拼搭了。 代码生成器概述codeg是一个轻量级的代码生成器内核(代码仅300行左右),基于Python和jinja2开发,你可以定制自己的项目模板,快速生成可以直接运行的工程项目。项目主页: github 功能特点 根据数据库连接配置可以支持直接生成java工程 支持生成指定表的工程代码 支持读取数据库表注释,自动完成java类注释 微框架设计,方便自己修改模板完成自己的重复性代码工作 DBMaster介绍dbmaster是一个python编写的在线数据库查询客户端,可以有效隔离线上数据库环境,提供了一系列便于开发者使用的特性。操作体验尽量兼容navcat。项目主页:github 特性 支持SQL语法高亮和自动提示 支持SQL格式化 支持执行选中的SQL片段 支持数据库SCHEMA显示和表结构信息(双击表名显示表结构信息) 支持快捷键执行,Cmd+R/Ctrl+R执行SQL 支持查询执行记录 支持多个语句同时执行,显示多个result结果集 Knight什么是 KnightKnight 是一个 Java 开发的WEB后台开发微框架,基于成熟的 Spring 、 MyBatis 后端框架,集成AngularJS、echart等一些前端框架,可以极大的简化日常的后台开发。 特性 提供文件导出服务,配置式开发 提供简单的页面自动渲染能力,根据配置,自动生成搜索区域和table数据 集成echart,提供报表工具,可以支持简易的报表渲染功能 配置文件提供历史版本记录,可以在线查看diff 提供易用的debug信息,可以支持查看导出的sql和页面渲染使用的sql 支持动态多数据源,可以动态添加、删除和修改数据源配置 数据源类型支持mysql、hive 导出支持本地字典配置,可以省略大量的case when的写法 为什么要用 KnightKnight的设计初衷是为了支持集中式的文件导出服务,用以解决后台文件导出的需求。经过一系列的迭代之后Knight的目标将会是简化前后端的开发。抽象出后台通用功能,封装前后端流程。提供配置式开发方式,提高开发效率,节省开发成本,规范前后端的写法。 针对于导出需求,无需编写后端代码,只需在配置中心进行简单的配置后,在前端接入导出即可完成开发。导出的字段完全自定义,需求变更也无需上线即可完成修改。针对于普通的页面需求,提供了前端指令可以动态生成搜索框和数据表格,无需编写后台代码,只需在配置中心进行配置即可。页面增减字段也无需上线,只需要修改配置即可。针对于报表需求,集成了echart,后端配置后前端即可通过指令渲染。提供易用的debug功能,可以在页面查看后台执行的sql及出错信息,无需频繁登录服务器查看日志。 Wukong简介wukong是一个独立开发的业务监控系统,提供对线上业务进行监控和报警的能力,可以由各业务开发人员根据产品和业务需求制定规则,当系统出现异常数据时及时通知给相应的责任人。 设计目的 线上业务异常反馈不及时,有些甚至几天后才会发现,导致修复特别困难。 整合监控功能,提供业务代码中嵌入异常报警的功能,进一步提高系统稳定性。 提供基础的报警组件,避免各系统重复开发,重复发明轮子。 具体适应的场景业务规则监控 支付系统支付成功了是否订单系统状态也是已支付 订单5分钟之后超时,是否有订单5分钟之后还没有超时 物业费收款成功,通知龙湖没有返回收据号 支付系统支付金额和订单应付金额不一致 订单平台和业态订单状态不一致 支付系统和订单平台支付状态不一致 券分号可用数量低于某个值报警 积分发送异常 开发者程序埋点异常上报开发者在开发过程中,主动埋点,在触发了某种异常之后,主动通知开发者。比如: 当系统无法连接redis,通知开发者 某个逻辑需要某个文件,当文件没有成功生成,触发报警。"},{"title":"tags","date":"2017-09-14T01:41:48.000Z","updated":"2017-09-14T01:42:05.000Z","comments":true,"path":"tags/index.html","permalink":"http://yoursite.com/tags/index.html","excerpt":"","text":""},{"title":"关于我","date":"2017-09-14T01:22:03.000Z","updated":"2017-12-12T10:00:15.000Z","comments":true,"path":"about/index.html","permalink":"http://yoursite.com/about/index.html","excerpt":"","text":"Name & 联系方式 Name:解西扬 TelPhone:13220100526 QQ:80381107 工作经历 2011-2013 北大方正电子科技有限公司 2013-2014 百度糯米 2014至今 北京千丁互联科技有限公司 专注于 java 领域开发,对分布式系统开发和设计有一定的经验,主导设计开发了当前公司的订单、促销、商城、支付、财务结算和风控等平台系统。 擅于总结,创造轮子,提供了一些效率工具,比如代码生成器、集中式导出服务、通用监控报警平台、静态文档生成系统等。"}],"posts":[{"title":"","slug":"分布式系统-缓存系统总结","date":"2017-12-28T08:01:21.000Z","updated":"2017-12-28T08:01:40.000Z","comments":true,"path":"2017/12/28/分布式系统-缓存系统总结/","link":"","permalink":"http://yoursite.com2017/12/28/分布式系统-缓存系统总结/","excerpt":"","text":"","categories":[],"tags":[]},{"title":"七、java内存模型与线程","slug":"java-虚拟机-07-java-内存模型与线程","date":"2017-12-26T16:00:00.000Z","updated":"2017-12-27T06:31:12.000Z","comments":true,"path":"2017/12/27/java-虚拟机-07-java-内存模型与线程/","link":"","permalink":"http://yoursite.com2017/12/27/java-虚拟机-07-java-内存模型与线程/","excerpt":"","text":"java 内存模型java虚拟机规范中试图定义一种 java 内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。 java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。 此处的变量包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。 java内存模型规定了所有的变量都存储在主内存中。每条线程有自己的工作内存。 线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。 不同的线程之前也无法直接访问对方工作内存中的变量 线程建变量值的传递均需要通过主内存来完成。 内存间的交互操作一个变量如何从主内存拷贝到工作内存,如何从工作内存同不会主内存之类的实现细节,java 内存模型中定义了一下8中操作来完成,虚拟机是现时必须保证下面提及的每一种操作都是原子的、不可再分的。 lock(锁定):作用于主内存的变量,它把一个变量标示为一条线程独占的状态。 unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定。 read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。 laod(载入):作用于工作内存变量,它把Read操作从主内存得到的变量值放入工作内存的变量副本中。 use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。 assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,一遍随后的write操作使用。 write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。 如果要把一个变量从主内存复制到工作内存,就要顺序的执行read和load操作。 如果要把变量从工作内存同步会主内存,就要顺序地执行store和write操作。 java内存模型只要求上述的两个操作必须按顺序执行,而没有保证是连续执行。也就是说这之前可以插入其它指令,比如读取a、b两个变量,一种可能出现的顺序是 read a、read b、load b、load a。除此之外,java内存模型还规定了再执行上述8种基本操作时必须满足如下规则: 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了单工作内存不接受,或则从工作内存发起写回了但是主内存不接受的情况出现。 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。 不允许一个线程无原因(没有发生任何assign操作)地把数据从线程的工作内存同步回主内存。 一个新的变量只能在主内存中”诞生“,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。 一个变量在同一时刻只允许一条线程对齐进行lock操作,但lock操作可以被同一条线程重复多次执行,执行几次lock,就需要执行几次unlock才能被解锁。 如果一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。 如果一个变量没有被lock操作,则不允许执行unlock操作,也不允许unlock一个被其它线程锁定的变量 对一个变量执行unlock之前,必须先把此变量同不会主内存中。 对于 volatile 型变量的特殊规则volatile中文可以翻译为”不稳定的、反复无常的“,被此关键字声明的变量在java中有一些特殊的特性。 第一是保证此变量对所有的线程的可见性,当一条线程修改了这个变量的值,新值对于其他线程莱索是可以立即得知的。在各个线程的工作内存中,volatite变量也可以存在不一致的情况,但由于每次使用前都要先刷新,所以执行引擎看不到不一致的情况。 思考:基于 volatile 变量的运算在并发下是安全的吗? 12345678910111213141516171819202122232425262728293031public class VolatileTest { public static volatile int count = 0; public static void increase(){ count++; } private static final int THREAD_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREAD_COUNT]; for(int i=0;i<THREAD_COUNT;i++){ threads[i] = new Thread(new Runnable() { public void run() { for(int i=0;i<10000;i++){ increase(); } } }); threads[i].start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(count); }} 以上的代码每次执行,并不会打印出200000,而是比200000要小的一个数字,为什么?使用javap查看increase的字节码如下: 12345678910public static void increase(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #2 // Field count:I 3: iconst_1 4: iadd 5: putstatic #2 // Field count:I 8: return 使用volatile变量的第二个语义是禁止指令重排序优化。普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。 以下代码在线程A中执行:123456789Map configOptions;char[] configText;volatile boolean initialized = false;configOptions = new HashMap();configText = readConfigFile(fileName);processConfigOptions(configText, configOptions);initialized = true; 以下代码在线程B中执行 1234while(!initialized){ sleep(1);}doSomethingWithConfig(); 假如initialized变量不加volatile修饰,有可能由于指令重排序的优化,导致线程A最后一句initialized = true;被提前执行。线程B在执行的时候以为配置已经被处理完,实际还没有处理完,导致线程B执行异常。 什么是指令重排序?从硬件架构上讲,指令重排序是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。但并不是说指令任意重排,CPU需要能正确处理指令依赖情况以保障程序能得出正确的执行结果。比如,如下的代码:1234int a = 10;a = a + 10;a = a * 10;int b = 100; 第三行代码肯定不能和第二行代码重排,但是第四行代码的顺序可以放到第1、2、3行的任意位置,不会对结果产生影响。只要保证后面依赖到a和b值的操作能获取到最新的A和B的值即可。所以在本CPU中重排序看起来依然是有序的。 美团点评技术团队有一篇文章专门分析这个问题:Java内存访问重排序的研究 指令重排序发生在什么阶段?大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。除了处理器,常见的Java运行时环境的JIT编译器也会做指令重排序操作,即生成的机器指令与字节码指令顺序不一致。 volatile如何防止指令重排序看这个:剖析Disruptor:为什么会这么快?(三)揭秘内存屏障 如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。 这意味着如果你对一个volatile字段进行写操作,你必须知道: 1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。 什么是内存屏障? 它是一个CPU指令。没错,又一次,我们在讨论CPU级别的东西,以便获得我们想要的性能(Martin著名的Mechanical Sympathy理论)。基本上,它是这样一条指令: a)确保一些特定操作执行的顺序; b)影响一些数据的可见性(可能是某些指令执行后的结果)。 编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。正如去拉斯维加斯旅途中各个站点的先后顺序在你心中都一清二楚。 内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"一、计算机系统漫谈","slug":"SCAPP-01-计算机系统漫谈","date":"2017-12-26T16:00:00.000Z","updated":"2017-12-27T08:34:51.000Z","comments":true,"path":"2017/12/27/SCAPP-01-计算机系统漫谈/","link":"","permalink":"http://yoursite.com2017/12/27/SCAPP-01-计算机系统漫谈/","excerpt":"","text":"","categories":[{"name":"CSAPP","slug":"CSAPP","permalink":"http://yoursite.com/categories/CSAPP/"}],"tags":[{"name":"CS","slug":"CS","permalink":"http://yoursite.com/tags/CS/"}]},{"title":"二、信息的表示和处理","slug":"SCAPP-02-信息的表示和处理","date":"2017-12-26T16:00:00.000Z","updated":"2017-12-28T03:52:57.000Z","comments":true,"path":"2017/12/27/SCAPP-02-信息的表示和处理/","link":"","permalink":"http://yoursite.com2017/12/27/SCAPP-02-信息的表示和处理/","excerpt":"","text":"二进制和十六进制之间的转换 习题一 将 0x39a7f8 转换为二进制 123 9 a 7 f 80011 1001 1010 0111 1111 1000 将二进制 1100100101111011转化为十六进制 121100 1001 0111 1011c 9 7 b 9=1+42 0x20019=3+44 0x8000017=1+44 0x2000016=0+44 0x10000 为什么32位机器只能读取最大4G的内存?什么是32位机器,什么是64位机器? 每台计算机都有一个字长(word size),指明指针数据的标称大小。以为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。","categories":[{"name":"CSAPP","slug":"CSAPP","permalink":"http://yoursite.com/categories/CSAPP/"}],"tags":[{"name":"CS","slug":"CS","permalink":"http://yoursite.com/tags/CS/"}]},{"title":"六、从虚拟机看java一些特性解析","slug":"java-虚拟机-06-从虚拟机看java一些特性解析","date":"2017-12-25T16:00:00.000Z","updated":"2017-12-26T04:05:52.000Z","comments":true,"path":"2017/12/26/java-虚拟机-06-从虚拟机看java一些特性解析/","link":"","permalink":"http://yoursite.com2017/12/26/java-虚拟机-06-从虚拟机看java一些特性解析/","excerpt":"","text":"泛型与类型擦除自动装箱、拆箱与遍历循环即时编译","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"五、虚拟机字节码执行引擎","slug":"java-虚拟机-05-虚拟机字节码执行引擎","date":"2017-12-24T09:06:10.000Z","updated":"2017-12-26T03:57:23.000Z","comments":true,"path":"2017/12/24/java-虚拟机-05-虚拟机字节码执行引擎/","link":"","permalink":"http://yoursite.com2017/12/24/java-虚拟机-05-虚拟机字节码执行引擎/","excerpt":"","text":"之前我们知道了虚拟机的内存布局,Class文件结构,Class文件的加载过程,然后我们来了解下虚拟机是如何执行字节码的,也就是虚拟机的执行引擎。 运行时栈帧的结构 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈中的栈元素。 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。 每一个方法从调用开始至执行完成,都对应着一个栈帧在虚拟机栈里面的入栈到出栈的过程。 同一个进程中会有很多线程在执行,一个线程又通常包含一个很长的方法调用链,会有很多方法同时处于执行状态。对于执行引擎来说,在活动的线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。 局部变量表局部变量表存放方法参数和方法内部定义的局部变量。 在java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了局部变量表的最大容量,如下所示:123public void inc(){ num++;} Class文件中关于此方法对应的字节码如下:123456789101112131415public void inc(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field num:I 5: iconst_1 6: iadd 7: putfield #2 // Field num:I 10: return LineNumberTable: line 10: 0 line 11: 10 操作数栈 操作数栈是一个栈的结构,后入先出。 操作数栈的最大深度也是在编译的时候写入到Code属性中的max_stacks数据项中。 操作数栈的每一个元素可以是任意的java数据类型,包括long和double。32位数据类型占用一个栈容量,64位占用两个。 一个方法开始执行的时候,这个方法的操作数栈是空的,在执行的过程中,会有各字节码指令往操作数栈中写入和提取内容,对应出栈和入栈的操作。 动态连接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。 运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。 在一段java代码中,一个方法的调用,最终需要变成一个方法的入口地址,也就是引用。 有些符号引用在类加载阶段或则第一次使用的时候就会转化为直接引用,这种转化成为静态解析。另外一部分将在每一次运行期间转换为直接引用,这部分成为动态连接。 方法返回地址当一个方法开始执行后,只有两种方式可以退出这个方法。 第一种是遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者。这种退出方法的方式成为”正常完成出口“。 另外一种是在执行过程中遇到了异常,并且这个异常没有子啊方法体内得到处理。这种退出方式称为“异常完成出口”。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者任何返回值的。 无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,如果是正常退出,可以将调用者的PC计数器的值作为返回地址,如果是异常退出,需要根据异常处理器表来确定。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有: 恢复上层方法的局部变量表和操作数栈 把返回值(如果有)压人调用者栈帧的操作数栈 调整PC计数器的值以执行方法调用指令后面的一条指令 方法调用 修改bug并不难,难的是找到引起bug的那段代码 方法调用便是寻找bug原因的过程,它需要定位出到底需要执行哪个类的哪个方法。 程序运行过程中,进行方法调用是最普遍、最频繁的操作,但是前面已经讲过,Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这个特性给java带来了强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至直到运行期间才能确定目标方法的直接引用。 1234567891011121314151617181920212223public class HelloWorld implements Cloneable{ public static final String TAG = \"xiexiyang\"; private int num; public void inc(){ num = exception(); num++; System.out.println(num); } public int exception(){ int x; try{ x =1; return x; }catch(Exception e){ x = 2; return x; }finally{ x = 3; } }} inc方法的字节码解释如下:12345678910111213141516171819202122232425public void inc(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: aload_0 2: invokevirtual #2 // Method exception:()I 5: putfield #3 // Field num:I 8: aload_0 9: dup 10: getfield #3 // Field num:I 13: iconst_1 14: iadd 15: putfield #3 // Field num:I 18: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 21: aload_0 22: getfield #3 // Field num:I 25: invokevirtual #5 // Method java/io/PrintStream.println:(I)V 28: return LineNumberTable: line 10: 0 line 11: 8 line 12: 18 line 14: 28 C语言的编译过程如下是从《计算机组成原理》里面的一段摘录,我们可以对比看一下C语言编译的过程: 解析 前面已经讲解过类加载阶段的“解析”过程,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。 我们这里重点关注关于方法的解析。在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,那那一部分符号引用可以在解析阶段做转化呢? 这些方法应该在运行的时候就只有一个可以确定的调用版本,而且在运行期不可改变,符合这种要求的方法主要包括静态方法和私有方法两大类,另外还包括实例构造器和父类方法。 java虚拟机里面提供了5条方法调用字节码指令,分别如下: invokestatic :调用静态方法 invokespecial :调用实例构造器 init 方法、私有方法和父类方法 invokevirtual :调用所有的虚方法 invokeinterface :调用接口方法,会在运行时再确定一个实现此接口的对象 invokedynamic :现在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。 被invokestatic和invokespecial调用的方法,都可以在解析阶段中确定唯一的一个调用版本,符合这个条件的有静态方法、私有方法、实例构造器和父类方法4类。他们再类加载的时候就会把符号引用解析为改方法的直接引用。这些方法称为非虚方法。 如下演示了调用静态方法生成的字节码指令:1234567891011package com.xiyang.study.methodinvoke;public class StaticResolution { public static void sayHello(){ System.out.println(\"hello world\"); } public static void main(String[] args) { StaticResolution.sayHello(); }} 12345678910public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=0, locals=1, args_size=1 0: invokestatic #5 // Method sayHello:()V 3: return LineNumberTable: line 12: 0 line 13: 3 解析调用是一个静态的过程,在类装载的解析阶段就会把涉及到的符号引用全部转变为可确定的直接引用,不会延迟到运行期去完成。 分派除了解析调用,还有一种是分派调用。分派调用可能是静态的也可能是动态的。 java是一门面向对象的语言,因为java具备面向对象的三个特征:“继承”、“封装”和“多态”。理解了分派可以知道虚拟机中关于“重载”和”重写“是如何实现的。 静态分派首先来看一个例子:123456789101112131415161718192021222324252627282930313233public class StaticDispatch { static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human { } public void sayHello(Human guy){ System.out.println(\"hello,guy!\"); } public void sayHello(Man man){ System.out.println(\"hello,man!\"); } public void sayHello(Woman woman){ System.out.println(\"hello,woman!\"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch sd = new StaticDispatch(); sd.sayHello(man); sd.sayHello(woman); }} 执行结果:12hello,guy!hello,guy! StaticDispatch 有三个sayHello方法,参数不一样,我们知道这是重载,在调用 StaticDispatch 的sayHello方法时,虚拟机是如何确定调用哪个方法呢? 我们首先来看如下的代码了解两个重要的概念:1Human man = new Man(); 上面代码中”Human“称为变量的静态类型(Static Type),或则叫做外观类型,后面的”Man“则称为变量的实际类型。 虚拟机在重载时时通过参数的惊天类型而不是实际雷静作为判定依据的,且静态类型在编译阶段是可知的。通过javap查看生成的字节码如下:12345678910111213141516171819202122232425262728293031public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #7 // class com/xiyang/study/methodinvoke/StaticDispatch$Man 3: dup 4: invokespecial #8 // Method com/xiyang/study/methodinvoke/StaticDispatch$Man."<init>":()V 7: astore_1 8: new #9 // class com/xiyang/study/methodinvoke/StaticDispatch$Woman 11: dup 12: invokespecial #10 // Method com/xiyang/study/methodinvoke/StaticDispatch$Woman."<init>":()V 15: astore_2 16: new #11 // class com/xiyang/study/methodinvoke/StaticDispatch 19: dup 20: invokespecial #12 // Method "<init>":()V 23: astore_3 24: aload_3 25: aload_1 26: invokevirtual #13 // Method sayHello:(Lcom/xiyang/study/methodinvoke/StaticDispatch$Human;)V 29: aload_3 30: aload_2 31: invokevirtual #13 // Method sayHello:(Lcom/xiyang/study/methodinvoke/StaticDispatch$Human;)V 34: return LineNumberTable: line 30: 0 line 31: 8 line 33: 16 line 34: 24 line 35: 29 line 36: 34 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。 动态分派动态分派对应java另外一个重要的特性”重写“,如下例子:12345678910111213141516171819202122232425262728public class DynamicDispatch { static abstract class Human{ protected abstract void sayHello(); } static class Man extends Human{ protected void sayHello() { System.out.println(\"man say hello!\"); } } static class Woman extends Human{ protected void sayHello() { System.out.println(\"woman say hello!\"); } } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man=new Woman(); man.sayHello(); }} 执行结果123man say hello!woman say hello!woman say hello! 执行javap查看main函数的字节码如下:1234567891011121314151617181920212223242526272829303132public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: new #2 // class com/xiyang/study/methodinvoke/DynamicDispatch$Man 3: dup 4: invokespecial #3 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Man."<init>":()V 7: astore_1 8: new #4 // class com/xiyang/study/methodinvoke/DynamicDispatch$Woman 11: dup 12: invokespecial #5 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Woman."<init>":()V 15: astore_2 16: aload_1 17: invokevirtual #6 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Human.sayHello:()V 20: aload_2 21: invokevirtual #6 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Human.sayHello:()V 24: new #4 // class com/xiyang/study/methodinvoke/DynamicDispatch$Woman 27: dup 28: invokespecial #5 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Woman."<init>":()V 31: astore_1 32: aload_1 33: invokevirtual #6 // Method com/xiyang/study/methodinvoke/DynamicDispatch$Human.sayHello:()V 36: return LineNumberTable: line 24: 0 line 25: 8 line 27: 16 line 28: 20 line 30: 24 line 31: 32 line 32: 36 16到21行是方法调用的字节码部分,16和20分别把两个对象入栈,17和21是方法调用指令,这两条调用执行但从字节码角度看,无论是指令还是参数都是完全一样的,但是最终执行的目标方法并不同,为什么呢?原因就需要从invokevirtual指令的多态查找过程开始说起,步骤大致如下: 找到操作数栈顶的第一个元素所执行对象的实际类型,记做C 如果在类型C中找到与常量中的描述符合简单名称都符合的方法,则进行访问权限校验,如果通过说明找到了,返回这个方法的直接引用,查找过程结束;如果不通过,返回java.lang.IllegalAccessError异常。 否则,依次查找C的父类,重复第二步的搜索和验证过程。 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。 虚拟机动态分派的实现由于动态分派很频繁,所以虚拟机在实际的实现中基于性能的考虑,大部分实现都不回真正进行如此频繁的搜索和校验。最常见的”稳定优化“手段是为类在方法去中建立一个虚方法表(vtable),如下所示: 虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都执行父类的实现入口。如果子类重写了该方法,这入口地址会替换为子类实现版本的入口地址。 方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化文笔。 方法执行方法最终要执行必须首先转化为对应硬件的指令集,有些语言会直接生成对应的可执行代码在目标机器上执行,比如c c++,还有些语言提供了执行代码的解释器,不需要提前编译,比如python。java语言是一种平台无关的语言,主要依赖于虚拟机的实现,虚拟机可以解析读取直接码,转化为目标机器的指令,所以java语言经常也被人定位为”解释执行“的语言。 基于栈的字节码解释执行引擎许多java虚拟机的执行引擎在执行java代码的时候都有解释执行和编译执行两种选择。 基于栈的指令集与基于寄存器的指令集基于栈的指令集和基于寄存器的指令集是两种执行引擎的不同实现。举个例子,分别使用这两种指令集计算”1+1“。 基于栈的指令集会是:1234iconst_1iconst_1iaddistore_0 基于寄存器的指令集会是:12mov eax, 1add eax, 1 基于栈的指令集主要优点是可以移植而基于寄存器的指令集指令更少,也更快. 执行过程示例比如针对于如下的java程序:123456public int calc(){ int a = 100; int b = 200; int c = 300; return (a+b)*c;} javap查看生成的字节码:12345678910111213141516public int calc(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: bipush 100 2: istore_1 3: sipush 200 6: istore_2 7: sipush 300 10: istore_3 11: iload_1 12: iload_2 13: iadd 14: iload_3 15: imul","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"四、虚拟机类加载机制","slug":"java-虚拟机-04-虚拟机类加载机制","date":"2017-12-13T05:50:02.000Z","updated":"2017-12-26T02:56:19.000Z","comments":true,"path":"2017/12/13/java-虚拟机-04-虚拟机类加载机制/","link":"","permalink":"http://yoursite.com2017/12/13/java-虚拟机-04-虚拟机类加载机制/","excerpt":"","text":"虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。 通过本篇文章应该知道如下概念和事实: 类的生命周期 何时需要加载类 类加载的过程 什么是双亲委派模型 类加载的时机如下如是类的生命周期: 类什么时候会加载呢?什么时候应该加载类虚拟机并没有明确的规范,但是虚拟机严格规定了如下5中情况必须对类进行初始化(而加载、验证、准备自然需在此之前进行): 遇到new、getstatic、putstatic或invokestatic 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。对应的场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段、调用类静态方法的时候。 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。 如果初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 当虚拟机启动时,用户需要执行一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,冰洁这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。 例子类加载的过程加载“加载”是“类加载”的一个阶段,在加载阶段,虚拟机需要完成以下3件事情: 通过一个类的全限定名来获取定义此类的二进制字节流。 将字节流中代表的静态存储结构转化为方法区的运行时数据结构。 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 虚拟机并没有规定二进制字节流必须要从一个Class文件中获取,在java的发展历程中,基于这个特性发展出来了很多举足轻重的特性: 从 zip 包中读取,最终成为日后JAR、EAR、WAR格式的基础 从网络中获取,比如Applet 运行时计算生成,比如动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定的接口生成形式为“*$Proxy”的代理类的二进制字节流 有其它文件生成,比如JSP,编译后会生成对应的Class类 从数据库中读取 验证这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 文件格式验证 主要是验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。 元数据验证 进行语义分析,验证是否符合Java语言规范 字节码验证 通过数据流和控制流分析,确定程序语义是合法的符合逻辑的。 主要对类的方法体进行校验分析,验证操作码 符号引用验证 符号引用可以看做是对垒自身以外(常量池中的各种符合引用)的信息进行匹配性校验 符号引用通过字符串描述的全限定名是否能找到对应的类 在指定的类中是否存在符合方法的描述符或字段 符号引用中的类、字段、方法的访问性是否被当前类访问 符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号禁用验证,那么将会抛出一个java.lang.IncompatibleClassChangeError异常的子类,比如:java.lang.IllegalAccessError,java.lang.NoSuchFieldError,java.lang.NoSuchMethodError 准备准备阶段是正式为变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。 内存分配仅包括类变量(被static修饰的变量),而不包括实例变量。 初始值通常情况下是数据类型的零值。 解析解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 符号引用: 是用一组符号来描述锁引用的目标 直接引用: 可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。 什么时候进行解析虚拟机要求在执行:anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic这16个操作符号引用的字节码指令之前,先对他们所使用的符合引用进行解析。 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,分别对应: CONSTANT_Class_info CONSTANT_Fieldref_info CONSTANT_Methodref_info CONSTANT_InterfaceMethodref_info CONSTANT_MethodType_info CONSTANT_MethodHandle_info CONSTANT_InvokeDynamic_info 对于后面3种,与JDK1.7 新增的动态语言支持有关。 如下是类、方法和字段对应的类文件格式1234#1 = Methodref #8.#29 // java/lang/Object."<init>":()V#2 = Fieldref #5.#30 // org/xiyang/HelloWorld.num:I#3 = Class #31 // java/lang/Exception#7 = Methodref #36.#37 // java/io/PrintStream.println:(Ljava/lang/String;)V 初始化 类初始化是类加载过程的最后一步。 在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主管计划去初始化类变量和其它资源。 初始化阶段是执行类构造器<clinit>()方法的过程。 clinit() <clinit>()是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。 收集的顺序是由语句在源文件中出现的顺序所决定的 <clinit>()不需要显式地调用父类构造器,虚拟机会保证父类的<clinit>()先执行。 <clinit>() 不是不需的,如果一个类或接口没有静态语句块,就可以不生成<clinit>()。 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁同步。 类加载器虚拟机把”通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到了java 虚拟机的外部去实现,实现这个动作的代码模块叫“类加载器”。 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。 类加载器分类从java虚拟机的角度,只存在两种不同的类加载器: 启动类加载器(Bootstrap ClassLoader),C++实现,是虚拟机自身的一部分 其它类加载器,java实现,独立于虚拟机的外部,继承自抽象类 java.lang.ClassLoader 从java开发人员看,可以划分的更细致一些: 启动类加载器:这个类负责将存放在<JAVA_HOME>\\lib目录中的,或则被 -Xbootclasspath参数所指定的路径中的,并且是虚拟机识别(按照文件名识别,比如rt.jar)的类库加载到虚拟机内存中。 扩展类加载器:这个加载器由 sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>\\lib\\ext目录中的,或则被 java.ext.dirs 系统变量所指定的路径中的所有的类库。 应用程序类加载器:这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责加载用户类路径(CLassPath)上指定的类库。 双亲委派模型类加载器通常是有自己的层次结构,如下图所示: 这种类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。 双亲委派模型要求除顶层的启动类加载器外,其余的类加载器都要有自己的父类加载器。 父子关系一般使用组合(Composition)关系来复用父加载器的代码,不使用继承(Inheritance)。 双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是委派给父类加载器去完成 因此所有的请求最终都会传送到顶层的启动类加载器中 如果父类反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载 为什么要这么设计呢?设计双亲委派模型主要是为了保证Java程序的稳定和安全运行。假如不使用双亲委派模型,而是由各个类加载器自行去加载的话,用户编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类。 实现双亲委派的代码都集中在 java.lang.ClassLoader的loadClass中 12345678910111213141516171819202122232425262728293031323334353637protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }} 执行过程如下: 首先检查类是否已经被加载过 如果没有则调用父加载器的loadClass方法 如果父加载器为空则默认使用启动类加载器作为父加载器 如果父加载器加载失败,抛出CLassNotFoundException异常 再调用自己的findClass方法进行加载","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"三、java 类文件结构","slug":"java-虚拟机-03-类文件结构","date":"2017-12-13T00:29:56.000Z","updated":"2017-12-27T06:31:01.000Z","comments":true,"path":"2017/12/13/java-虚拟机-03-类文件结构/","link":"","permalink":"http://yoursite.com2017/12/13/java-虚拟机-03-类文件结构/","excerpt":"","text":"在java的设计之初,设计者就考虑过并实现让其他语言也可以运行在java虚拟机上的可能性,所以他们在发布规范文档时,可以把java规范拆分成了《java 语言规范》和《java虚拟机规范》。 概述虚拟机的语言无关java 虚拟机是语言无关的,也就是说能在java 虚拟机中运行的代码并不一定都是java源文件编译生成的,也可能是JRuby、Groovy等其它语言,他们通过各自的编译器统一变成 class 文件(字节码)运行在java 虚拟机中,如下图所示: java语言的平台无关性我们知道java程序可以“一次编写,到处运行”,也就是java语言是平台无关的,提供了良好的快平台支持,那么 java是怎么做到平台无关的呢? sun公司及其它虚拟机提供商发布了许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现“一次编写,到处运行”。 Class 类文件的结构 我们使用一个例子来具体分析Class类的文件结构对于如下的java文件,我们使用javac编译完查看生成的class文件如下所示:1234567891011121314151617181920212223242526272829package org.xiyang;/** * Created by xiexiyang on 2017/10/25. */public class HelloWorld implements Cloneable{ public static final String TAG = \"xiexiyang\"; private int num; public void inc(){ num++; } public int exception(){ int x; try{ x =1; return x; }catch(Exception e){ x = 2; return x; }finally{ x = 3; } } public static void main(String[] args) { System.out.println(TAG); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152cafe babe 0000 0034 002f 0a00 0800 1d090005 001e 0700 1f09 0020 0021 0700 22080023 0a00 2400 2507 0026 0700 2701 00035441 4701 0012 4c6a 6176 612f 6c61 6e672f53 7472 696e 673b 0100 0d43 6f6e 7374616e 7456 616c 7565 0100 036e 756d 01000149 0100 063c 696e 6974 3e01 0003 28295601 0004 436f 6465 0100 0f4c 696e 654e756d 6265 7254 6162 6c65 0100 0369 6e630100 0965 7863 6570 7469 6f6e 0100 03282949 0100 0d53 7461 636b 4d61 7054 61626c65 0700 1f07 0028 0100 046d 6169 6e010016 285b 4c6a 6176 612f 6c61 6e67 2f537472 696e 673b 2956 0100 0a53 6f75 72636546 696c 6501 000f 4865 6c6c 6f57 6f726c64 2e6a 6176 610c 000f 0010 0c00 0d000e01 0013 6a61 7661 2f6c 616e 672f 45786365 7074 696f 6e07 0029 0c00 2a00 2b010015 6f72 672f 7869 7961 6e67 2f48 656c6c6f 576f 726c 6401 0009 7869 6578 6979616e 6707 002c 0c00 2d00 2e01 0010 6a617661 2f6c 616e 672f 4f62 6a65 6374 0100136a 6176 612f 6c61 6e67 2f43 6c6f 6e656162 6c65 0100 136a 6176 612f 6c61 6e672f54 6872 6f77 6162 6c65 0100 106a 6176612f 6c61 6e67 2f53 7973 7465 6d01 00036f75 7401 0015 4c6a 6176 612f 696f 2f507269 6e74 5374 7265 616d 3b01 0013 6a617661 2f69 6f2f 5072 696e 7453 7472 65616d01 0007 7072 696e 746c 6e01 0015 284c6a61 7661 2f6c 616e 672f 5374 7269 6e673b29 5600 2100 0500 0800 0100 0900 02001900 0a00 0b00 0100 0c00 0000 0200 06000200 0d00 0e00 0000 0400 0100 0f00 10000100 1100 0000 1d00 0100 0100 0000 052ab700 01b1 0000 0001 0012 0000 0006 00010000 0005 0001 0013 0010 0001 0011 00000027 0003 0001 0000 000b 2a59 b400 020460b5 0002 b100 0000 0100 1200 0000 0a000200 0000 0a00 0a00 0b00 0100 1400 15000100 1100 0000 7800 0100 0500 0000 18043c1b 3d06 3c1c ac4d 053c 053e 063c 1dac3a04 063c 1904 bf00 0400 0000 0400 08000300 0000 0400 1100 0000 0800 0d00 11000000 1100 1300 1100 0000 0200 1200 00001e00 0700 0000 1000 0200 1100 0400 16000800 1200 0900 1300 0b00 1400 0d00 16001600 0000 0a00 0248 0700 1748 0700 18000900 1900 1a00 0100 1100 0000 2500 02000100 0000 09b2 0004 1206 b600 07b1 00000001 0012 0000 000a 0002 0000 001b 0008001c 0001 001b 0000 0002 001c mac 下是用hexfiend软件打开class文件: 魔数和版本号这三项的数据结构如下:123public U4 magic; // magicpublic U2 minorVersion; // minor_versionpublic U2 majorVersion; // major_version 比如上面的代码编译后的class解析后如下: CAFEBABE 是魔数 次版本:0x0000 主版本:0x0034 换算成10进制52 代表java主版本号,1.1是45,52是jdk1.8 常量池常量池的数据结构如下:12public U2 constantPoolCount; // constant_pool_countpublic ConstantPoolInfo[] cpInfo; // cp_info 接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中于其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。由于常量池中常量的双不固定,所以在常量池入口需要放置一项u2类型的数据,代表常量池容量大小,0x002F 换算成10进制47 代表常量池大小,由于常量池是索引是从1开始的所以有46个常量。 常量池中主要存放两大类常量:字面量和符号引用。 常量池中每一项都是一个表,JDK1.7之前共有11中结构,1.7之后有14种。 这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型,具体的类型所代表的具体含义如下图: 对应的14种常量项具体的结构定义如下: javap是jdk提供的专门分析字节码的工具,可以使用它来帮我们列出常量池。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150xiexiyang@XieXiyangdeMacBook-Pro ~/Desktop> javap -verbose HelloWorldClassfile /Users/xiexiyang/Desktop/HelloWorld.class Last modified 2017-12-20; size 828 bytes MD5 checksum 32099c71e0f8e2b55608e228aeae5d21 Compiled from "HelloWorld.java"public class org.xiyang.HelloWorld implements java.lang.Cloneable minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #8.#29 // java/lang/Object."<init>":()V #2 = Fieldref #5.#30 // org/xiyang/HelloWorld.num:I #3 = Class #31 // java/lang/Exception #4 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream; #5 = Class #34 // org/xiyang/HelloWorld #6 = String #35 // xiexiyang #7 = Methodref #36.#37 // java/io/PrintStream.println:(Ljava/lang/String;)V #8 = Class #38 // java/lang/Object #9 = Class #39 // java/lang/Cloneable #10 = Utf8 TAG #11 = Utf8 Ljava/lang/String; #12 = Utf8 ConstantValue #13 = Utf8 num #14 = Utf8 I #15 = Utf8 <init> #16 = Utf8 ()V #17 = Utf8 Code #18 = Utf8 LineNumberTable #19 = Utf8 inc #20 = Utf8 exception #21 = Utf8 ()I #22 = Utf8 StackMapTable #23 = Class #31 // java/lang/Exception #24 = Class #40 // java/lang/Throwable #25 = Utf8 main #26 = Utf8 ([Ljava/lang/String;)V #27 = Utf8 SourceFile #28 = Utf8 HelloWorld.java #29 = NameAndType #15:#16 // "<init>":()V #30 = NameAndType #13:#14 // num:I #31 = Utf8 java/lang/Exception #32 = Class #41 // java/lang/System #33 = NameAndType #42:#43 // out:Ljava/io/PrintStream; #34 = Utf8 org/xiyang/HelloWorld #35 = Utf8 xiexiyang #36 = Class #44 // java/io/PrintStream #37 = NameAndType #45:#46 // println:(Ljava/lang/String;)V #38 = Utf8 java/lang/Object #39 = Utf8 java/lang/Cloneable #40 = Utf8 java/lang/Throwable #41 = Utf8 java/lang/System #42 = Utf8 out #43 = Utf8 Ljava/io/PrintStream; #44 = Utf8 java/io/PrintStream #45 = Utf8 println #46 = Utf8 (Ljava/lang/String;)V{ public static final java.lang.String TAG; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: String xiexiyang public org.xiyang.HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 5: 0 public void inc(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field num:I 5: iconst_1 6: iadd 7: putfield #2 // Field num:I 10: return LineNumberTable: line 10: 0 line 11: 10 public int exception(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=5, args_size=1 0: iconst_1 1: istore_1 2: iload_1 3: istore_2 4: iconst_3 5: istore_1 6: iload_2 7: ireturn 8: astore_2 9: iconst_2 10: istore_1 11: iload_1 12: istore_3 13: iconst_3 14: istore_1 15: iload_3 16: ireturn 17: astore 4 19: iconst_3 20: istore_1 21: aload 4 23: athrow Exception table: from to target type 0 4 8 Class java/lang/Exception 0 4 17 any 8 13 17 any 17 19 17 any LineNumberTable: line 16: 0 line 17: 2 line 22: 4 line 18: 8 line 19: 9 line 20: 11 line 22: 13 StackMapTable: number_of_entries = 2 frame_type = 72 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 72 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String xiexiyang 5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 27: 0 line 28: 8}SourceFile: "HelloWorld.java" 访问标志数据结构:1public U2 accessFlags; // access_flags 常量池结束之后,紧接着的两个字节代表访问标志(access_flags) 这个标志可以用于识别一些类或者接口层次的访问信息 具体的标志位以及白哦之的含义如下表: 类索引、父类索引、接口索引这三项的数据结构:1234public U2 thisClass; // this_classpublic U2 superClass; // super_classpublic U2 interfacesCount; // interfaces_countpublic U2[] interfaces; // interfaces 一个示例如下:1234560021 : access flags0005 : this class0008 : super class0001 : interfaces count0009 : interfaces 类索引(this_class)和父类索引(super_class)都是一个u2类型的数据 接口索引集合(interfaces)是一组u2类型的数据集合 Class文件由这三项数据来确定这个类的集成关系 类索引用于确定这个类的全限定名 父类索引确定这个类的父类全限定名 字段表集合 字段表(field_info)用于描述接口或者类中声明的变量。 包括类级变量和实例级变量,不包括方法内部的局部变量。 一个字段可以包括的信息有: 字段的作用域(public、private、protected修饰符) 是实例变量还是类变量(static 修饰符) 可变性(final) 并发可见性(volatile修饰符,是否强制从主内存读写) 可否被序列化(transient 修饰符) 字段数据类型(基本类型、对象、数组) 字段名称 12public U2 fieldsCount; // fields_countpublic FieldInfo[] fields; // fields 比如针对于本文的示例代码,定义了如下两个字段12public static final String TAG = "xiexiyang";private int num; class 文件片段如下1234567891011120002 : fields count //有两个字段0019 : access flags //第一个字段的访问标记,查表可以知道 代表 public static final000a : name index //字段的简单名称在常量池中的索引000b : descriptor index //字段的描述符0001 : attributes count //属性的数量000c : attribute name index //属性的名字索引 对应( #12 = Utf8 ConstantValue)0000 0002 : attribute length //0006 : constant value index // (#6 = String #35 // xiexiyang)0002 : access flags000d : name index000e : descriptor index0000 : attribute count 访问标志定义如下: 字段和方法的描述符 描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。具体的类型定义的描述符如下: 对于数组类型,每一维度将使用一个前置的“[”来描述,比如:12java.lang.String[][] 记录为:[[Ljava/lang/String;int[] 记录为 [I 描述方法的时候,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号之内,例如:123void int() 记录为 ()Vjava.lang.String toString() 记录为 ()Ljava/lang/String;int indexOf(char[]source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex) 记录为 ([CII[CIII)I 方法表集合Class文件对方法的描述与对字段的描述几乎采用了完全一致的方式。 属性表集合属性表在前面的讲解中出现过多次,在CLass文件、字段表、方法表中都可以携带自己的属性表集合,用于描述某些场景转悠的信息。 虚拟机规范预定义的一些属性如下: 对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的接口则是完全自定义的,只需要通过一个u4的长度属性去说明属性值锁占用的位数即可。 Code属性java 程序方法体中的代码经过javac编译器处理后,最终的字节码执行存储在Code属性中。比如: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455560004 : methods count0001 : access flags000f : name index0010 : descriptor index0001 : attributes count0011 : attribute name index0000 002f : attribute length0001 : max stack0001 : max locals0000 0005 : code length2ab7 0001 b1 : code00 00 : exception table length00 02 : attributes count00 12 : attribute name index00 0000 06 : attribute length00 01 : line number table length00 00 : start pc00 06 : line number00 13 : attribute name index00 0000 0c : attribute length00 01 : local variable table length00 00 : start pc00 05 : length00 14 : name index00 15 : descriptor index00 00 : index00 01 : access flags00 16 : name index00 10 : descriptor index00 01 : attributes count00 11 : attribute name index00 0000 39 : attribute length00 03 : max stack00 01 : max locals00 0000 0b : code length2a 59b4 0002 0460 b500 02b1 : code0000 : exception table length0002 : attributes count0012 : attribute name index0000 000a : attribute length0002 : line number table length0000 : start pc000c : line number000a : start pc000d : line number0013 : attribute name index0000 000c : attribute length0001 : local variable table length0000 : start pc000b : length0014 : name index0015 : descriptor index0000 : index 12345678910111213141516171819202122232425public org.xiyang.HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 5: 0public void inc(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field num:I 5: iconst_1 6: iadd 7: putfield #2 // Field num:I 10: return LineNumberTable: line 10: 0 line 11: 10 思考如何实现一个Java Class解析器ClassAnalyzer 字节码指令 java 虚拟机的指令由一个字节长度的 一个指令包含操作码(Opcode)和操作所需的参数(零或多个,操作数,Operands)构成 java虚拟机采用面向操作数栈而不是寄存器的架构,所以大多数指令都不包含操作数,只有一个操作码 最多有256个指令 不考虑异常处理,可以用如下的伪代码当做虚拟机最简单的执行模型:123456while(字节码流长度>0){ 自动结算PC寄存器的值加一; 根据PC寄存器的指示位置,从字节码流中取出操作码; if(字节码存在操作数) 从字节码流中取出操作数; 执行操作码所定义的操作;} 字节码和数据类型在java 虚拟机中,大多数的指令都包含了其操作所对应的数据类型信息。比如 iload 用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据。 i 代表 对int类型的数据操作 l 代表 long s 代表 short b 代表 byte c 代表 char f 代表 float d 代表 double a 代表 reference 加载和存储指令加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。 虚拟机栈描述的是 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 这类指令包括如下内容: 将一个局部变量加载到操作栈: iload、iload_ 、fload 将一个数值从操作数栈存储到局部变量表: istore、istore_ 将一个常量加载到操作数栈: bipush、sipush、ldc、ldc_w、ldc2w、aconst_null、iconstml、iconst 扩充局部变量表的访问索引的指令: wide 运算指令加减乘除、取反、求余、位移、按位或、按位与、按位异或、局部变量自增、比较指令 类型转换指令对象创建与访问指令 创建类实例: new 创建数组: newarray、anewarray、multianewarray 访问类字段(static字段)和实例字段(非static字段):getfield、putfield/getstatic/putstatic 把一个数组元素加载到操作数栈的指令:baload、caload 讲一个操作数栈的值存储到数组元素中的指令:bastore 取数组长度 检查类实例类型的指令 操作数栈管理指令 将操作数栈的张鼎一个或两个元素出栈: pop、pop2 复制栈顶一个或两个数值并将复制值重新压入栈顶: dup、dup2、dup_x1 将栈最顶端的两个数值互换:swap 控制转移指令 条件分支 ifeq、iflt 复合条件分支 tableswitch 无条件分支 goto 方法调用和返回指令 invokevirtual 调用对象的实例方法 invokeinterface 调用接口方法 invokespecial 调用需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法 invokestatic 调用类方法(static) invokedynamic 动态解析出调用点限定符锁引用的方法 异常处理指令同步指令java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用Monitor来支持的。 方法级的同步隐式的,无需通过字节码指令控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步。当方法调用是,如果是同步方法,执行线程就要求先成功持有管程,然后才能执行方法。方法执行完释放管程。 一段代码片段的同步如果是一个方法中的synchronized同步代码块,虚拟机用如下两条指令来支持。 monitorentermonitorexit","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"二、垃圾收集器与内存分配策略","slug":"java-虚拟机-02-垃圾收集器与内存分配策略","date":"2017-12-12T02:09:31.000Z","updated":"2017-12-12T10:24:39.000Z","comments":true,"path":"2017/12/12/java-虚拟机-02-垃圾收集器与内存分配策略/","link":"","permalink":"http://yoursite.com2017/12/12/java-虚拟机-02-垃圾收集器与内存分配策略/","excerpt":"","text":"我们再来回顾一下java运行时的数据区,如下图所示: 其中程序计数器、虚拟机栈和本地方法栈随线程而生,随线程而死。栈中的栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈的操作。而java 堆和方法区则不一样,我们只有在运行期才能知道会创建哪些对象,创建多少对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的就是这部分内存。 如何判断对象已死首先我们需要判断哪些对象是可以被回收的,也就是无用的死的对象。 引用计数法给对象添加一个引用计数器,每当一个地方引用它时,计数器就加1;当引用失效时,计数器就减1;计数器为0,则说明对象不再被使用。 这种方法有效吗? 首先这种方法实现方式比较简单,判定效率也比较高,也有一些比较著名的应用案例,比如微软公司的 COM 技术。但是主流的java虚拟机中没有选用引用计数来管理内存,其中最主要的原因是它很难解决循环引用的问题。假如两个对象A和B互相引用,那他们的计数器永远无法为0。 可达性分析算法这个算法的思路是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链想连接时,就证明这个对象是不可用的。 可作为 GC Roots的对象包括下面几种: 虚拟机栈(栈帧中的本地变量表)中引用的对象 方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中JNI(即一般说的Native方法)引用的对象 关于GC Roots更详细的介绍可以参考 知乎 关于引用在JDK1.2之前,引用的定义是:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 在 JDK1.2 之后,Java对引用的概念进行了扩充,将引用分为:强引用、软引用、弱引用、虚引用4种,这4种引用的强度依次减弱。 强引用比较好理解,只要引用还存在,就不会被垃圾回收。 软引用是在将要内存溢出之前,会把这些对象列入回收范围之内进行第二次回收。 弱引用的对象在只能生存到下一次垃圾回收之前 虚引用是最弱的一个引用,为一个对象设置虚引用的唯一目的是能在这个对象被回收的时候接到一个系统通知。 清理的过程当一个对象被识别为无用对象时,也不是立马进入被清理的过程,真正宣告一个对象死亡,至少要经历两次标记过程。 垃圾收集算法标记-清除算法 复制算法 标记整理算法 分代收集算法 HotSpot 虚拟机的算法实现垃圾回收很重要的两点是判断哪些对象可以被回收以及什么时候可以进行回收。HotSpot 虚拟机通过 GC Roots 枚举判定待回收的对象,通过安全点和安全区域确定 GC 的触发点,最后通过各种不同的回收算法完成垃圾回收。 GC Roots 枚举过程GC Roots 枚举最大的困难点在于:检查范围比较大,并且必须在内存快照中进行,保证一致性,而且时间要求比较敏感。 在生产环境中,即使不考虑其它部分内存,仅仅 Java 堆内存就可达几百兆甚至上G,在此范围内完成 GC Roots 确定是一件很困难的事情;同时,在进行 GC Roots 枚举时,必须保证一致性,即所有正在运行的程序必须停顿,不能出现正在枚举 GC Roots,而程序还在跑的情况,这会导致 GC Roots 不断变化,产生数据不一致导致统计不准确的情况;最后,由于所有工作线程必须停顿以完成 GC 过程,在大并发高访问量情况下,这个时间必须非常短。 准确式 GC为解决上述问题,HotSpot 采用了一种 “准确式 GC” 的技术;该技术主要功能就是让虚拟机可以准确的知道内存中某个位置的数据类型是什么;比如某个内存位置到底是一个整型的变量,还是对某个对象的 reference;这样在进行 GC Roots 枚举时,只需要枚举 reference 类型的即可。 在能够准确地确定 Java 堆和方法区等 reference 准确位置之后,HotSpot 就能极大地缩短 GC Roots 枚举时间。 OopMap当类加载完成后,HotSpot 就将对象内存布局之中什么偏移量上数值是一个什么样的类型的数据这些信息存放到 OopMap 中;在 HotSpot 的 JIT 编译过程中,同样会插入相关指令来标明哪些位置存放的是对象引用等,这样在 GC 发生时,HotSpot 就可以直接扫描 OopMap 来获取这些信息,从而进行 GC Roots 枚举,下图展示了 JTI 编译的 OopMap 指令: 安全点(Safepoint)Safepoint:会导致 OopMap 内容变化的指令非常多,如果为每一条指令都生成对应的 OopMap,那么将需要大量的额外空间,这样对导致 GC 成本很高,所以 HotSpot 只在 “特定位置” 记录这些信息,这些位置被称为 安全点(Safepoint)。 并非程序在任意时刻都可以停顿下来进行 GC,而只有程序到达 安全点(Safepoint) 以后才可以停顿下来进行 GC;所以安全点既不能太少,以至于 GC 过程等待程序到达安全点的时间过长,也不能太多,以至于 GC 过程带来的成本过高。 安全点上停止线程方式由于在 GC 过程中必须保证程序已停止执行,那么也就是说 必须等待所有线程都到达安全点上方可进行 GC。 一般会有两种解决方案: 抢先式中断:不需要线程的执行代码去主动配合,当发生 GC 时,先强制中断所有线程,然后如果发现某些线程未处于安全点,那么将其唤醒,直至其到达安全点再次将其中断;这样一直等待所有线程都在安全点后开始 GC。 主动式中断:不强制中断线程,只是简单地设置一个中断标记,各个线程在执行时轮询这个标记,一旦发现标记被改变(出现中断标记)时,那么将运行到安全点后自己中断挂起;目前所有商用虚拟机全部采用主动式中断。 HotSpot 选定的标记位置与安全点位置是重合的,如下如图所示: 安全区(Saferegion)安全点的机制似乎已经完美的解决了 “什么时候以及何时开始 GC” 的问题,但是实际情况并非如此;安全点机制仅仅是保证了程序执行时不需要太长时间就可以进入一个安全点进行 GC 动作,但是当特殊情况时,比如线程休眠、线程阻塞等状态的情况下,显然 JVM 不可能一直等待被阻塞或休眠的线程正常唤醒执行;此时就引入了安全区的概念。 安全区(Saferegion):安全区域是指在一段区域内,对象引用关系等不会发生变化,在此区域内任意位置开始 GC 都是安全的;线程运行时,首先标记自己进入了安全区,然后在这段区域内,如果线程发生了阻塞、休眠等操作,JVM 发起 GC 时将忽略这些处于安全区的线程。当线程再次被唤醒时,首先他会检查是否完成了 GC Roots枚举(或这个GC过程),然后选择是否继续执行,否则将继续等待 GC 的完成。 垃圾收集器垃圾收集算法是内存回收的方法论,而垃圾收集器则是具体的实现,在HotSpot中提供的垃圾收集器如下: HotSpot 提供了 7 种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。 CMS (Concurrent Mark Sweep)重点看一下CMS垃圾收集器的实现。它的运作更为复杂,整个过程分为4个步骤: 初始标记(CMS initial mark) 并发标记(CMS concurrent mark) 重新标记(CMS remark) 并发清除(CMS concurrent sweep) 初始标记和重新标记这两个步骤仍需要“Stop The World”。 初始标记:仅仅是标记一下GC Roots能直接关联到的对象,速度很快。 并发标记:GC Roots Tracing 重新标记:修正并发标记期间因用程序继续运作而导致标记产生变动的那一部分对象,比初始标记时间长,但是比并发标记端。 整个过程耗时最长的并发标记和并发清除可以和用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是于用户线程一起并发执行的。 CMS是一款优秀的收集器,可以实现并发收集,低停顿,但是CMS依然存在存在如下几个问题: 1. CMS 收集器对CPU资源非常敏感123456CMS默认启动的回收线程数是(CPU数量+3)/4,如下是分别有2、4、5、13个CPU的情况下,回收线程的数量和占比。(2+3)/4 = 1 50%(4+3)/4 = 1 25%(5+3)/4 = 2 40%(13+3)/4 = 4 30% 可以看到,当CPU的数量比较少时,会导致用户程序的执行速度忽然变慢。 2. 无法避免浮动垃圾 由于进行并发清除的时候用户线程还在运行,所以执行完垃圾收集,仍然会有部分垃圾没有被清理,这部分垃圾就称为“浮动垃圾”。 另外由于在CMS收集器执行垃圾回收的时候还有用户线程在执行,所以不能像其他垃圾收集器一样等着老年代机会完全被填满了再进行收集,需要预留一部分空间供用户程序使用。JDK 1.5 默认设置下,当老年代使用了68%的空间后就会被激活。 3. “标记-清除”算法所引起的空间碎片问题使用“标记-清除”的算法会导致内存回收完,有大量的空间碎片产生,当空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到连续的足够大的空间来分配给当前的对象。 为了解决这个问题,CMS收集器提供了一个开关:-XX:+UseCMSCompactAtFullCollection,用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程。 思考什么时候触发CMS GC,为什么? Minor GC、Major GC和Full GC之间的区别?http://www.importnew.com/15820.html 垃圾收集器参数总结 -XX:+UseSerialGC : Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 -XX:+UseParNewGC : 打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收 -XX:+UseConcMarkSweepGC : 使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用 -XX:+UseParallelGC : Jvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收 -XX:+UseParallelOldGC : 使用Parallel Scavenge + Parallel Old的收集器组合进行回收 -XX:SurvivorRatio : 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1 -XX:PretenureSizeThreshold : 直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 -XX:MaxTenuringThreshold : 晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代 -XX:UseAdaptiveSizePolicy : 动态调整java堆中各个区域的大小以及进入老年代的年龄 -XX:+HandlePromotionFailure : 是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留 -XX:ParallelGCThreads : 设置并行GC进行内存回收的线程数 -XX:GCTimeRatio : GC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效 -XX:MaxGCPauseMillis : 设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效 -XX:CMSInitiatingOccupancyFraction : 设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSCompactAtFullCollection : 由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效 -XX:+CMSFullGCBeforeCompaction : 设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用 -XX:+UseFastAccessorMethods : 原始类型优化 -XX:+DisableExplicitGC : 是否关闭手动System.gc() -XX:+CMSParallelRemarkEnabled : 降低标记停顿 -XX:LargePageSizeInBytes : 内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m –XX:+UseG1GC : 打开此开关后,使用G1垃圾收集器 内存分配策略对象优先在eden分配如果没有足够的空间,会执行一次Minor GC 大对象直接进入老年代如果大对象在新生代分配会导致新生代执行频繁的复制 长期存活的对象将进入老年代对象在Survivor区没经过一次 Minor GC,年龄就增加1岁,当他的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。 动态对象年龄判断一般来讲,对象年龄必须达到 MaxTenuringThreshold 才能晋升到老年代,但是如果Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一般,年龄大于等于该年龄的对象就可以直接进入老年代。 空间分配担保发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置的值是否允许担保失败。 如果允许,则继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试一次 Minor GC,尽管这次 Minor GC 有风险。 如果小于,或者 HandlePromotionFailure 设置为不允许冒险,则这时要进行一次Full GC。","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"一、java 内存管理","slug":"java-虚拟机-01-java-内存管理","date":"2017-12-11T02:05:20.000Z","updated":"2017-12-15T08:30:12.000Z","comments":true,"path":"2017/12/11/java-虚拟机-01-java-内存管理/","link":"","permalink":"http://yoursite.com2017/12/11/java-虚拟机-01-java-内存管理/","excerpt":"","text":"思考几个问题 jvm 内存分几个区域,分别是什么 java 对象在内存中的分布 如何定位无用的对象 垃圾收集的算法有哪些 为什么有这么多的算法 什么时候进行收集 什么是 Stop the world java 虚拟机运行时数据区 程序计数器 可以看做线程所执行的字节码行号指示器。 各线程私有,数据隔离。 如果执行的是java方法,记录的是当前正在执行的虚拟机字节码指令,如果执行 Native 方法时,记录为空。 为什么要有程序计数器呢? 多线程的程序执行是轮流进行的,为了能在切换回来是能继续执行,所以每个线程需要一个自己的程序计数器。 java 虚拟机栈 线程私有,生命周期和线程相同 方法执行的同时会创建一个栈帧,用来存储:局部变量表、操作数栈、动态链接、方法出口等信息 局部变量表储存了在编译器已知的各种基本数据类型、对象引用和returnAddress类型 局部变量表所需的空间在编译期间完成分配,方法运行期间不会改变大小 此区域定义了两个异常:StackOverflow 和 OutOfMemoryError 为什么要有虚拟机栈 程序计数器是记录程序执行到那一步了,虚拟机栈则是在方法运行期间保存数据的数据接口,在方法执行之前分配局部变量需要的内存大小,方法执行之后返回方法的返回值。 本地方法栈本地方法栈和虚拟机栈类似,区别是本地方法栈储存的是本地方法运行期的数据。 java 堆 线程共享 一般来说java堆是java 内存管理中最大的一块区域 几乎所有的对象实例和数组都是在堆上分配内存 堆是垃圾收集器管理的主要区域 堆一般分为新生代 老年代,新生代细分为:Edge,From Survivor,To Survivor 堆的作用 堆是用来存储对象的空间,物理上可以不连续,堆分为不同的类型主要是为了更好的内存管理,便于垃圾回收。一般堆的大小是可以扩展的通过-Xmx -Xms这两个参数来控制大小。如果堆上无法分配内存,并且也无法继续扩展时,就会抛出 OutOfMemoryError异常。 方法区 线程共享 用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 使用HotSpot虚拟机,很多人把方法区称为永久代,这样的目的是可以省去单独编写内存管理的代码 运行时常量池 方法区的一部分 存放编译器生成的各种字面量和符号引用 直接内存 直接内存不是java 虚拟机运行时数据的一部分,也不是java虚拟机规范中定义的内存区域。 jdk 1.4 中新加入的NIO,可以使用Native函数库分配堆外内存 直接内存会占用物理内存空间,假如总的内存不够用,会出现OutOfMemoryError异常 虚拟机对象探秘内存分配的两种方式 假如内存是规整的,内存的分配相当于指针移动。 假如内存是不规整的,使用过的和没有使用的内存相互交错,这个时候就需要一个表记录哪些内存是用过的,哪些是没用过的,然后分配的时候,找一个足够大的空间划分给对象实例。 java堆是否规整是由其所使用的垃圾收集器是否带有压缩整理的功能决定的。 线程安全由于分配内存的操作可能是多线程并发的,如何保证线程安全呢? CAS + 失败重试 本地线程分配缓存 TLAB (Thread Local Allocation Buffer) 创建对象的流程 分配内存 将分配的内存空间初始化为零值 对象头设置(对象是哪个类的实例、如何才能找到类的元数据信息、对象的hash码、GC的分代年龄) 到这个时候,从虚拟机来看,一个对象已经创建完了,但是从java程序的角度看,对象创建才刚刚开始。 执行完new指令后,紧接着执行init方法,会按照程序员的意愿进行初始化。 对象的内存布局 对象头包含两个部分 实例数据是对象真正存储的有效信息,程序代码中所定义的各种类型的字段内容 对齐填充并不是必然存在的,JVM 要求内存起始地址必须是8的整数倍 对象的访问定位创建对象的目的是为了使用对象,那如何找到对应的对象呢?由如下两种方式: 这两种对象访问方式各有优势,使用句柄访问好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。 使用直接指针访问方式最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在java中非常频繁,所以这类开销积少成多也很可观。 Sun HotSpot采用的第二种方式进行对象访问。 OutOfMemoryError异常实战在 java 虚拟机规范中,除了程序计数器外,虚拟机内存的其它几个运行时区域都有发生 OOM 的可能。 堆溢出如下的代码清单中所示,代码限制了java 堆的大小为 20M,不可扩展,通过频繁的创建对象添加到List中去,在对象数量达到最大堆的容量限制后就会产生内存溢出异常。1234567891011121314151617/** * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * Created by xiexiyang on 16/3/22. */public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list= new ArrayList<OOMObject>(); while (true){ list.add(new OOMObject()); } }} 运行结果12345678910111213141516[GC 5963K->3513K(20480K), 0.0151440 secs][GC 9076K->8080K(20480K), 0.0149410 secs][Full GC 17389K->13163K(20480K), 0.2516960 secs][Full GC 16255K->16164K(20480K), 0.1447540 secs][Full GC 16164K->16147K(20480K), 0.1832760 secs]java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid18191.hprof ...Heap dump file created [27741631 bytes in 0.488 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2245) at java.util.Arrays.copyOf(Arrays.java:2219) at java.util.ArrayList.grow(ArrayList.java:242) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208) at java.util.ArrayList.add(ArrayList.java:440) at com.xiyang.study.jvm.HeapOOM.main(HeapOOM.java:20) 虚拟机栈和本地方法栈溢出栈容量有-Xss参数设定。 如果线程请求的栈深度大于虚拟机所允许的最大深度,将会抛出 StackOverflowError 异常。 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。 单线程情况下,设置相对小的栈空间,比如-Xss128k,然后使用递归,比较容易模拟出StackOverflowError。 如果是多线程情况下,可以设置较大的栈空间,然后不断的创建线程最后会抛出 OutOfMemoryError,如下:1Exception in thread "main" java.lang.OutOfMemoryError: unable to create new natice thread 出现上面的异常很容易理解,总的内存空间是一定的,虚拟机提供参数来控制java 堆和方法区的最大值,用总的内存减去最大堆容量(Xmx),再减去最大方法区容量(PermSize),程序计数器消耗内存很小可以忽略,剩下的就是虚拟机栈和本地方法栈的可用空间了。这种情况下可以通过减小栈空间大小来达到增大线程数量的目的。 方法区和运行时常量池溢出在一些大型的web项目启动的时候我们经常会遇到如下的异常: 1Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 出现这个异常代表方法区内存溢出了。我们可以通过调整参数的形式来解决这个问题。","categories":[{"name":"java 虚拟机","slug":"java-虚拟机","permalink":"http://yoursite.com/categories/java-虚拟机/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"爷爷奶奶","slug":"life-爷爷奶奶","date":"2017-12-10T16:00:00.000Z","updated":"2017-12-12T10:09:12.000Z","comments":true,"path":"2017/12/11/life-爷爷奶奶/","link":"","permalink":"http://yoursite.com2017/12/11/life-爷爷奶奶/","excerpt":"","text":"三年前大伯父车祸去世,那年我结婚,半年后奶奶也走了,第二年女儿出生,今年清明节爷爷也去世了。今年九月十六是奶奶三周年忌日,从北京匆匆赶回家,又匆匆赶回。前几天去电影院看了迪士尼今年的新片《寻梦环游记》,看到最后留了很多泪,假如真的有灵界,爷爷奶奶和大伯父现在应该在一起了吧。 爷爷奶奶小的时候家里很穷,奶奶的家在我们那边叫河西,爷爷说奶奶是小时候逃荒的时候要饭要到我们这边的。无法想象他们在那个年代经历过多少的磨难。 爷爷奶奶有6个孩子,三男三女,最大的是大姑,然后是大伯父、二伯父、二姑、三姑,我爸爸最小。","categories":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/categories/随笔/"}],"tags":[]},{"title":"spring 事务管理","slug":"spring-6-spring-的事务管理","date":"2017-10-24T16:00:00.000Z","updated":"2017-09-27T02:20:17.000Z","comments":true,"path":"2017/10/25/spring-6-spring-的事务管理/","link":"","permalink":"http://yoursite.com2017/10/25/spring-6-spring-的事务管理/","excerpt":"","text":"","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"spring AOP原理解析","slug":"spring-5-spring-aop的原理解析","date":"2017-10-24T16:00:00.000Z","updated":"2017-09-27T02:20:14.000Z","comments":true,"path":"2017/10/25/spring-5-spring-aop的原理解析/","link":"","permalink":"http://yoursite.com2017/10/25/spring-5-spring-aop的原理解析/","excerpt":"","text":"","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"调度系统总结","slug":"分布式系统-调度系统总结","date":"2017-10-14T16:00:00.000Z","updated":"2017-12-28T08:00:38.000Z","comments":true,"path":"2017/10/15/分布式系统-调度系统总结/","link":"","permalink":"http://yoursite.com2017/10/15/分布式系统-调度系统总结/","excerpt":"","text":"背景千丁在2.5平台系统中实现了一个集中式调度系统用来管理后台系统中的定时任务。","categories":[{"name":"调度系统","slug":"调度系统","permalink":"http://yoursite.com/categories/调度系统/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://yoursite.com/tags/分布式/"}]},{"title":"消息系统总结","slug":"分布式系统-消息系统总结","date":"2017-10-14T16:00:00.000Z","updated":"2017-12-28T08:38:52.000Z","comments":true,"path":"2017/10/15/分布式系统-消息系统总结/","link":"","permalink":"http://yoursite.com2017/10/15/分布式系统-消息系统总结/","excerpt":"","text":"何时需要消息队列我们会经常听到消息队列的话题,消息系统可以解决那些问题呢?一个相对完整的消息队列是如何实现的?我们来探讨下这个主题。通常情况,可以使用mq的场景很多,比如如下几种: 业务系统解耦 最终一致性 广播 异步 有些请求并不需要及时处理,只需要最终处理掉就可以了,可以简化核心业务模型的复杂度,比如一个下单或支付的业务流程。 错峰流控 削峰填谷,避免瞬时的大流量打垮下游的业务系统 可靠通信 如果是用RPC如果失败无法保持状态,以便于补偿发送 一个消息系统最基础的功能是负责接收消息,并转发给目标系统,为了保证可靠消息,需要将消息持久化,并对投递做一些策略。根据消息队列解决的问题,消息队列的本质我们定义为如下几点: 一次RPC变两次RPC 内容传输 选择合适的时机投递 如何设计一个消息队列 队列的基本功能RPC通信协议队列的核心是消息的投递,这就涉及到进程间通讯,刚才也讲到,所谓的消息队列,就是两次RPC加一次转储。所以进程间的通讯选择RPC的实现。一般我们可以利用公司现有的RPC框架,比如hessian、dubbo或thrift。 数据存储broker接收到消息之后一般会选择对消息持久化,理论上,从速度来看,文件系统>分布式KV(持久化)>分布式文件系统>数据库,而可靠性却截然相反。 如果消息队列是用来支持支付、交易等对可靠性要求非常高,但对性能和量的要求没有那么高,而且没有时间精力专门做文件存储系统的研究,可以选择DB作为存储系统。 异步/同步 异步,归根结底你还是需要关心结果的,但可能不是当时的时间点关心,可以用轮询或毁掉方式处理结果。 同步,是需要当时关心结果的。 oneway,是发出去就不管死活的方式。 pull/push慢消费在push模型中,慢消费会导致消息在broker的堆积,如果消费者的速度比发送者慢很多,则会导致大量的消息无法投递。pull模型中,consumer可以按需消费,不用但系自己处理不了的消息来骚扰自己,而broker堆积消息也相对简单,无需记录每一个要发送消息的状态,只需要维护所有消息的队列和偏移量就可以了。 消息延迟与忙等这是pull模式的最大短板,由于是消费者主动去拉取消息,但是消费者无法准确的决定何时去拉取最新的消息,需要间断的去pull消息,可能会导致消息的延迟。 业界较成熟的做法是从短时间开始(不会对broker有太大负担),然后指数级增长等待。比如开始等5ms,然后10ms,然后20ms,然后40ms……直到有消息到来,然后再回到5ms。即使这样,依然存在延迟问题:假设40ms到80ms之间的50ms消息到来,消息就延迟了30ms,而且对于半个小时来一次的消息,这些开销就是白白浪费的。在阿里的RocketMq里,有一种优化的做法-长轮询,来平衡推拉模型各自的缺点。基本思路是:消费者如果尝试拉取失败,不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来,把连接notify起来,这也是不错的思路。但海量的长连接block对系统的开销还是不容小觑的,还是要合理的评估时间间隔,给wait加一个时间上限比较好~ 千丁为何要设计自己的消息系统 背景:千丁在系统创建初期,自己实现了一套内部的消息系统中间件,用于解决系统间可靠通讯的问题,为什么没有采用业内开源产品呢?几乎每一个有好奇心的入职的新人都会问这个问题。 现在来看,市面上可供选择的开源MQ解决方案有很多,比如RocketMQ、RabbitMQ、ZeroMQ、Kafka,为什么我们会选择自研呢? 首先我们在写imessage的时候是在2015年年初,当时千丁的系统正在进行2.5的开发,核心的几个系统有订单、促销、商品、商城、支付、财务、会员、预存款等。系统间的通信采用RPC(hessian协议),有些业务会涉及多个系统之间的调用和通知,需要保证多个系统都能正常处理,也就是分布式事务和最终一致性的问题。 我们的需求是可靠性通信,消息要有序。 快速开发、能够hold住。 整个imessage开发大约用了一个多星期,由于是自己写的,原理都很清楚,出现问题也可以快速修改。 imessage的实现原理队列使用redis的阻塞队列,持久化存储使用redis+mysql的方式。 每一个业务系统引入imessage-client并对外暴露message的RPC服务,imessage-server通过RPC协议调用对应的业务系统,业务系统使用SPI的机制找到对应的处理类,实现业务的调用。 消息保存到哪里?imessage接到一个消息后会首先保存到数据库,如果保存成功,则push到block队列。 如何保证消息的顺序?imessage并没有刻意的实现顺序消息,但是由于最初和之后的顺序topic都采用单线程消费,可以一定程度上保证消息消费的顺序性。另外通过状态机的方式保证业务方不会处理异常流程的消息,从另外一方面保证了业务状态的正确性。 如何保证消息的可靠性投递?内部有一个重试队列支持失败消息重试,达到可靠投递的目的(实现最终一致性)。当消息发送失败的时候,就将消息放入到重试队列,重试队列的消息会维护一个变量记录发送次数,和下次发送次数。重试队列的消费线程每隔一分钟获取重试队列的中消息,检测是否可以立即重试,如果可以将会发起一次投递,并记录发送结果,如果成功则写会数据库,如果还是失败,将重试次数加一,并重新计算下次发送时间。 imessage的进化过程 第一版的imessage支持p2p的点对点消息。 第二版支持了广播的特性,广播的实现原理是一次消息投递,分裂成多个消息发送出去。 而后支持了topic,可以将消息分组,消费线程可以配置,支持无序和有序队列。 再后来imessage集成了RocketMQ,使用RocketMQ来作为broker,负责消息的存储和转发,转发的协议依然采用hessian。 遇到的一些坑 幂等性 由于我们设计的消息系统失败会放入重试队列继续尝试下次通知,所以需要下游消息系统做好幂等性校验,通常需要采取分布式锁的机制,否则很容易造成消息多次投递后被重复消费,造成业务异常。 消息队列阻塞 最初的所有消息都放到一个队列中,假如有些类型的消息,下游系统处理很慢,会导致队列中的消息,大量堆积,引起后面的消息无法及时送达,对于及时性要求比较高的场景不能容忍,比如支付系统的支付消息通知到订单,如果不能及时送达,会导致订单系统状态长时间无法变为已支付,对用户造成困扰。这和本身imessage使用的push模型也有关系。 kafka 实现解析Kafka深度解析本文转发自技术世界,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 kafka 简介Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输 支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输 同时支持离线数据处理和实时数据处理 RocketMQ 实现解析https://rocketmq.apache.org/docs/motivation/","categories":[{"name":"消息系统","slug":"消息系统","permalink":"http://yoursite.com/categories/消息系统/"}],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://yoursite.com/tags/分布式/"}]},{"title":"spring Bean加载详解","slug":"spring-4-spring-bean的加载详解","date":"2017-09-29T16:00:00.000Z","updated":"2017-09-27T02:20:08.000Z","comments":true,"path":"2017/09/30/spring-4-spring-bean的加载详解/","link":"","permalink":"http://yoursite.com2017/09/30/spring-4-spring-bean的加载详解/","excerpt":"","text":"","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"spring PropertyPlaceholderConfigurer 详解","slug":"spring-spring-properties文件配置PropertyPlaceholderConfigurer解析","date":"2017-09-28T16:00:00.000Z","updated":"2017-09-29T08:10:19.000Z","comments":true,"path":"2017/09/29/spring-spring-properties文件配置PropertyPlaceholderConfigurer解析/","link":"","permalink":"http://yoursite.com2017/09/29/spring-spring-properties文件配置PropertyPlaceholderConfigurer解析/","excerpt":"","text":"A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"}]},{"title":"Spring的IoC 容器","slug":"spring-3-spring-IOC容器详解","date":"2017-09-27T16:00:00.000Z","updated":"2017-09-27T08:07:43.000Z","comments":true,"path":"2017/09/28/spring-3-spring-IOC容器详解/","link":"","permalink":"http://yoursite.com2017/09/28/spring-3-spring-IOC容器详解/","excerpt":"","text":"什么是IoCIoC 的全称为:Inversion of Control,中文通常翻译为 ”控制反转“,还有一个别名叫做依赖注入(Dependency Injection)。 我们考虑一个电商流程下单的过程,对于创建订单的服务来讲,下单的过程描述的是这么一件事情: 记录谁在什么时候购买了哪些商品,使用了什么优惠,配送到何处。 前端传递给后台的参数通常只有: 会员ID 商品ID和数量 优惠券 配送地址ID 后端的服务接到请求后需要做如下的几件事情: 根据会员ID获取到会员详细的信息,比如会员姓名,会员手机号等 根据商品ID获取到商品的具体信息,价格、合同、供方 调用计价服务计算价格 记录用户的配送信息 将订单对象保存到数据库 我们可以看到一个下单服务依赖很多相关的服务,假如订单服务我们创建一个类叫 OrderService ,定义一个方法叫 makeOrder()。1234567891011121314151617public class OrderServiceImpl implements IOrderService { private PassportService passportService; private ProductService productService; @Override public void makeOrder() { //1. 获取会员信息 Member member = passportService.getMember(); //2. 获取商品信息 Product product = productService.getProduct(); }} 上述的代码中 OrderService 依赖 passportService 和 productService,如何完成这两个服务的初始化呢? 假如不使用spring,可能我们需要写类似下面的代码1234567891011121314public OrderServiceImpl(PassportService passportService, ProductService productService) { this.passportService = passportService; this.productService = productService;}public static void main(String[] args) { // 创建 PassportService 和 ProductService,并设置到orderService对象 PassportService passportService = new PassportService(); ProductService productService = new ProductService(); OrderServiceImpl orderService = new OrderServiceImpl(passportService,productService); orderService.makeOrder();} 或使用 setter 的方法构建:123456789101112131415161718192021public void setPassportService(PassportService passportService) { this.passportService = passportService;}public void setProductService(ProductService productService) { this.productService = productService;}public static void main(String[] args) { OrderServiceImpl orderService = new OrderServiceImpl(); // 创建 PassportService 和 ProductService,并设置到orderService对象 PassportService passportService = new PassportService(); ProductService productService = new ProductService(); orderService.setPassportService(passportService); orderService.setProductService(productService); orderService.makeOrder();} 无论使用哪种方式,在实际的编码中都会非常的麻烦,一个类有的时候会依赖可能很多个其它类,如果全部是由程序员去控制类的初始化和依赖关系,将会是一件异常痛苦的事情。spring 通过IOC和DI,通过java本身的一些特性,很好的解决了这个问题。程序员无需再关心类的依赖,只需要在用到某个服务的时候声明一下即可,spring框架会帮助你注入所需的依赖。 spring 是如何知道依赖并完成依赖的注入呢,我们结合源码一点一点的看。 IoC 容器之 BeanFactory首先我们要来探究容器的概念,所谓的容器是可以盛放东西的一类事物,在程序的世界中,我们可以看到很多关于容器的存在,比如java容器类,通常也称为集合类。spring 中的容器和其它的容器类有什么不同呢? spring 提供了一个IoC容器,针对于其存储的对象可以管理其依赖关系,负责其所管理的bean的生命周期,从对象的创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件,这些对象被称为 Spring Beans。 bean 是Spring中最核心的东西,因为Spring就像一个大水桶,而bean就像是容器里面的水,水桶脱离了水便也没有什么用处了。 在spring中,BeanFactory便是一个容器,里面的Bean便是其管理的对象。我们通过一个例子看看 spring BeanFactory 容器的用法。 一个例子首先定义一个bean如下:1234567package com.xiyang.study.spring4.bean;public class MyBeanTest { public String sayHello(String name){ return \"hello \"+name+\"!\"; }} 然后在配置文件中配置这个bean:1234567891011<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \" > <bean id=\"myBeanTest\" class=\"com.xiyang.study.spring4.bean.MyBeanTest\"></bean></beans> 使用如下的测试代码测试它:123456789101112131415161718192021package com.xiyang.study.spring4;import com.xiyang.study.spring4.bean.MyBeanTest;import org.junit.Test;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;/** * Created by xiexiyang on 2017/9/25. */public class BeanFactoryTest { @Test public void test(){ BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(\"beanTest.xml\")); MyBeanTest myBeanTest = (MyBeanTest) beanFactory.getBean(\"myBeanTest\"); String retVal = myBeanTest.sayHello(\"xiyang\"); System.out.println(retVal); }} 运行结果如下:可以看到,我们在测试用例中正确的拿到了 MyBeanTest 的实例,并执行了其中的 sayHello 方法。 说明:直接使用 BeanFactory 作为容器在日常的开发中并不多见,这里只是为了演示spring容器的用法,通常我们使用的是 ApplicationContext ,我们之后对他再做更详细的介绍。 XmlBeanFactory首先我们初始化了一个 XmlBeanFactory ,这个对象是什么,能做什么呢?如下是 XmlBeanFactory 的类图: XmlBeanFactory 源码如下:1234567891011121314151617181920212223242526public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }} XmlBeanFactory 自身的源码非常简单,只有两个构造函数和一个 XmlBeanDefinitionReader 的变量,由于XmlBeanFactory 继承自 DefaultListableBeanFactory。所以欲探究其本质,首先需要搞明白 DefaultListableBeanFactory 和 XmlBeanDefinitionReader这两个类。 DefaultListableBeanFactoryDefaultListableBeanFactory 是 BeanFactory的一个具体实现,所以我们先来看 BeanFactory 接口定义的方法有哪些: IoC 容器之ApplicationContextApplicationContext 是 spring提供的较之 BeanFactory更为先进的IOC容器实现。ApplicationContext 除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的其它功能和特性比如: 统一资源加载策略 国际化信息支持 容器内部事件发布 多配置模块加载的简化 Spring提供了ApplicationContext几个常用的实现: org.springframework.context.support.ClassPathXmlApplicationContext。默认情况下,从classpath加载bean定义及相关资源。 org.springframework.context.support.FileSystemXmlApplicationContext。默认情况下,从文件系统加载bean定义及相关资源。 我们先从类图上对ApplicationContext 和 ClassPathXmlApplicationContext 有一个基础的认识: 统一资源加载策略之前说到 ApplicationContext 相比于 BeanFactory 的不同是 ApplicationContext 实现很多 BeanFactory 没有的特性,其中统一资源加载策略便是其一,这个是什么意思呢? 要搞清楚Spring为什么提供这么一个功能,还是从 Java SE提供的标准类 java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有点名不副实。 首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocal包下所支持的协议)的资源定位功能。虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以以任何形式存在,比如以二进制对象形式存在、以字节流形式存在、以文件形式存在等。而且,资源也可以存在于任何场所,如存在于文件系统、存在于java应用的Classpath中,甚至存在于URL可以定位的地方。 其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该有资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。 所以,在这个前提下,Spring提出了一套基于 org.springframework.core.io.Resource 和 org.springframework.core.io.ResourceLoader 接口的资源抽象和加载策略。 和 Resource 相关的类和组件位于 org.springframework.core.io 包下,如下所示:","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"Spring中对XML配置文件的解析详解","slug":"spring-2-spring-xml配置文件解析","date":"2017-09-25T16:00:00.000Z","updated":"2017-09-27T05:55:22.000Z","comments":true,"path":"2017/09/26/spring-2-spring-xml配置文件解析/","link":"","permalink":"http://yoursite.com2017/09/26/spring-2-spring-xml配置文件解析/","excerpt":"","text":"我们在日常的开发中会频繁的使用 spring 的 xml 的配置,spring 是如何解析这些 xml 文件配置的呢,我们通过本文来探究一下解析xml配置文件的详细流程。 一个示例我们先通过一个简单的示例来回顾下spring的用法,进而再分析spring解析配置文件的全过程。 首先定义一个bean如下:1234567package com.xiyang.study.spring4.bean;public class MyBeanTest { public String sayHello(String name){ return \"hello \"+name+\"!\"; }} 然后在配置文件中配置这个bean:1234567891011<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd \" > <bean id=\"myBeanTest\" class=\"com.xiyang.study.spring4.bean.MyBeanTest\"></bean></beans> 使用如下的测试代码测试它:123456789101112131415161718192021package com.xiyang.study.spring4;import com.xiyang.study.spring4.bean.MyBeanTest;import org.junit.Test;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;/** * Created by xiexiyang on 2017/9/25. */public class BeanFactoryTest { @Test public void test(){ BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(\"beanTest.xml\")); MyBeanTest myBeanTest = (MyBeanTest) beanFactory.getBean(\"myBeanTest\"); String retVal = myBeanTest.sayHello(\"xiyang\"); System.out.println(retVal); }} 运行结果如下:可以看到,我们在测试用例中正确的拿到了 MyBeanTest 的实例,并执行了其中的 sayHello 方法。 上面是一个spring简单的使用示例,我们在xml中配置了bean,创建了一个XmlBeanFactory,并从XmlBeanFactory容器中获取到了我们自己定义的bean并执行了其方法。XML 配置文件的解析是 Spring 中重要的功能,因为Spring 的大部分功能都是以配置作为切入点的。我们下面从XmlBeanDefinitionReader开始梳理一下资源文件读取、解析及注册的大致脉络。 源码剖析让我们从源码出发来一步一步的探究其本质,看如下测试用例的第一行代码:1BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(\"beanTest.xml\")); XmlBeanFactory首先我们初始化了一个 XmlBeanFactory ,这个对象是一个容器,它可以管理bean对象。 XmlBeanFactory 源码如下:1234567891011121314151617181920212223242526public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }} XmlBeanFactory 自身的源码非常简单,只有两个构造函数和一个 XmlBeanDefinitionReader 的变量,由于XmlBeanFactory 继承自 DefaultListableBeanFactory。关于其容器的特性和功能主要是由其父类 DefaultListableBeanFactory来实现的,我们本文重点看一下 XmlBeanDefinitionReader这两个类是如何完成xml配置文件的处理的。 XmlBeanDefinitionReader 从名字上就可以看出 XmlBeanDefinitionReader 它是用来读取bean配置文件的一个类。我们从时序图上看一下spring解析配置文件的全过程: 上面的几个步骤如下:1、在XMLBeanFactory的构造函数中调用XmlBeanDefinitionReader的loadBeanDefinitions方法1234public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource);} 2、loadBeanDefinitions 读取文件并调用 doLoadBeanDefinitions12345678910111213public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { .... try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } } ....} 3、在doLoadBeanDefinitions中解析为Document,并调用registerBeanDefinitions方法1234567protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); }} 4、registerBeanDefinitions 负责初始化 BeanDefinitionDocumentReader,调用其registerBeanDefinitions方法完成注册。1234567public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore;} 5、解析出root 节点调用doRegisterBeanDefinitions123456public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug(\"Loading bean definitions\"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root);} 6、解析root节点,调用parseBeanDefinitions,解析bean12345678910protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent;} 7、在parseBeanDefinitions中完成具体bean的解析12345678910111213141516171819202122protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { //解析默认的节点 parseDefaultElement(ele, delegate); } else { //解析自定义的节点 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); }} 上面的代码逻辑还是比较清晰的,spring的xml配置中有两大类Bean的生命,一个是默认的,比如1<bean id=\"myBeanTest\" class=\"com.xiyang.study.spring4.bean.MyBeanTest\"></bean> 另一类是自定义的,比如1<aop:aspectj-autoproxy proxy-target-class=\"true\"/> 判断是否是默认的节点也很简单,就是拿节点的namespaceUri 和 BEANS_NAMESPACE_URI做比较。12345public static final String BEANS_NAMESPACE_URI = \"http://www.springframework.org/schema/beans\";public boolean isDefaultNamespace(String namespaceUri) { return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));} 默认标签的解析1234567891011121314151617181920// 根据不同的节点名称分别完成解析的过程private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //对import标签的处理 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //对alias标签的处理 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //对bean标签的处理 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //对beans标签的处理 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); }} 如上,对默认标签的解析分为四种,其中最重要也是最复杂的便是对bean标签的处理,我们首先看一下这个方法:1234567891011121314151617protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 解析 element BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 注册到容器 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error(\"Failed to register bean definition with name '\" + bdHolder.getBeanName() + \"'\", ele, ex); } // 发送注册消息 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }} 在上面的方法中我们看到spring首先对element进行解析,然后注册到容器中。其中解析的具体过程如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //解析ID属性 String id = ele.getAttribute(ID_ATTRIBUTE); //解析name属性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug(\"No XML 'id' specified - using '\" + beanName + \"' as bean name and \" + aliases + \" as aliases\"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } //解析 BeanDefinition AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug(\"Neither XML 'id' nor 'name' specified - \" + \"using generated bean name [\" + beanName + \"]\"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null;} 上面的方法会返回一个 BeanDefinitionHolder 对象,具体的定义如下:12345678public class BeanDefinitionHolder implements BeanMetadataElement { private final BeanDefinition beanDefinition; private final String beanName; private final String[] aliases; ....} 对于 BeanDefinition 的解析是在 parseBeanDefinitionElement 方法中,如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } //创建 AbstractBeanDefinition 对象 AbstractBeanDefinition bd = createBeanDefinition(className, parent); //解析bean的属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //解析Meta parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); parseConstructorArgElements(ele, bd); parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error(\"Bean class [\" + className + \"] not found\", ele, ex); } catch (NoClassDefFoundError err) { error(\"Class that bean class [\" + className + \"] depends on not found\", ele, err); } catch (Throwable ex) { error(\"Unexpected failure during bean definition parsing\", ele, ex); } finally { this.parseState.pop(); } return null;} 注册的过程完成一个bean的解析之后,需要将其注册到容器,1234567891011121314151617181920/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 注册 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error(\"Failed to register bean definition with name '\" + bdHolder.getBeanName() + \"'\", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }} BeanDefinitionReaderUtils.registerBeanDefinition方法如下:12345678910111213141516public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String aliase : aliases) { registry.registerAlias(beanName, aliase); } }} 下面是 DefaultListableBeanFactory 注册bean的过程1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, \"Bean name must not be empty\"); Assert.notNull(beanDefinition, \"BeanDefinition must not be null\"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, \"Validation of bean definition failed\", ex); } } BeanDefinition oldBeanDefinition; synchronized (this.beanDefinitionMap) { oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, \"Cannot register bean definition [\" + beanDefinition + \"] for bean '\" + beanName + \"': There is already [\" + oldBeanDefinition + \"] bound.\"); } else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (this.logger.isWarnEnabled()) { this.logger.warn(\"Overriding user-defined bean definition for bean '\" + beanName + \" with a framework-generated bean definition ': replacing [\" + oldBeanDefinition + \"] with [\" + beanDefinition + \"]\"); } } else { if (this.logger.isInfoEnabled()) { this.logger.info(\"Overriding bean definition for bean '\" + beanName + \"': replacing [\" + oldBeanDefinition + \"] with [\" + beanDefinition + \"]\"); } } } else { this.beanDefinitionNames.add(beanName); this.frozenBeanDefinitionNames = null; } this.beanDefinitionMap.put(beanName, beanDefinition); } if (oldBeanDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); }} 在spring中bean的容器就是一个HashMap实现的,key是bean name,也就是在xml中配置的bean 的 id属性。12/** Map of bean definition objects, keyed by bean name */private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64); 自定义标签的解析123456789101112131415161718192021<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:context=\"http://www.springframework.org/schema/context\" xmlns:aop=\"http://www.springframework.org/schema/aop\" xsi:schemaLocation=\" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd \" > <!-- 开启组件扫描 --> <!-- 对包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 --> <context:component-scan base-package=\"com.xiyang.study\"/> <aop:aspectj-autoproxy proxy-target-class=\"true\"/> <bean id=\"myBeanTest\" class=\"com.xiyang.study.spring4.bean.MyBeanTest\"></bean></beans> 很多情况下我们使用spring基于标准的bean的配置就可以了,对于其他的组件依然需要一些特殊的配置,这个时候spring提供了一个良好的可供扩展的配置化支持。任何其它第三方都可以自主的扩展完成自定义配置文件的解析,比如dubbo读取配置文件完全参考了spring的设计。扩展spring 自定义标签配置大致需要以下几个步骤: 创建一个需要扩展的组件 定义一个XSD文件描述组件内容 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器 编写spring.handlers 和 spring.schemas文件 我们以spring aop 标签的解析来看一下上面的步骤。如下是spring aop包结构:spring.handlers 文件内容如下1http\\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler spring.schemas 文件内容如下12345678http\\://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-3.1.xsd=org/springframework/aop/config/spring-aop-3.1.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-3.2.xsd=org/springframework/aop/config/spring-aop-3.2.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-4.0.xsd=org/springframework/aop/config/spring-aop-4.0.xsdhttp\\://www.springframework.org/schema/aop/spring-aop-4.1.xsd=org/springframework/aop/config/spring-aop-4.1.xsdhttp\\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-4.1.xsd spring加载自定义标签的大致流程是遇到自定义标签然后就去 spring.handlers 和 spring.schemas文件中去找对应的handler和XSD,默认位置是/META-INF/下,进而又找到对应的Parser,从而使用各自的parser完成整个自定义元素的解析。和默认标签解析方式的不同在于spring将自定义标签的解析工作委托给我用户自己去实现。 AopNamespaceHandler 的作用是将自定义标签的解析器注册到spring,这样在遇到自定义标签之后就可以知道使用哪个Parser来解析。123456789101112131415package org.springframework.aop.config;import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class AopNamespaceHandler extends NamespaceHandlerSupport { public AopNamespaceHandler() { } public void init() { this.registerBeanDefinitionParser(\"config\", new ConfigBeanDefinitionParser()); this.registerBeanDefinitionParser(\"aspectj-autoproxy\", new AspectJAutoProxyBeanDefinitionParser()); this.registerBeanDefinitionDecorator(\"scoped-proxy\", new ScopedProxyBeanDefinitionDecorator()); this.registerBeanDefinitionParser(\"spring-configured\", new SpringConfiguredBeanDefinitionParser()); }} 我们通过源码来进一步验证上面的步骤,首先是解析自定义标签的入口方法如下:123456789101112public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取命名空间 String namespaceUri = getNamespaceURI(ele); //找到对应的handler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error(\"Unable to locate Spring NamespaceHandler for XML schema namespace [\" + namespaceUri + \"]\", ele); return null; } //执行handler的解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));} Spring Bean最终我们解析出来的bean在spring容器中是以一个什么形式存在呢,我们来看 processBeanDefinition 方法的实现:12345678910111213141516protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error(\"Failed to register bean definition with name '\" + bdHolder.getBeanName() + \"'\", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }} 可以看到我们最终拿到的是一个 BeanDefinitionHolder 类12345678910public class BeanDefinitionHolder implements BeanMetadataElement { private final BeanDefinition beanDefinition; private final String beanName; private final String[] aliases; ....} BeanDefinitionHolder 持有一个BeanDefinition 对象,其所拥有的属性和说明如下: class 这个属性是强制性的,并且指定用来创建 bean 的 bean 类。 name 这个属性指定唯一的 bean 标识符。在基于 XML 的配置元数据中,你可以使用 ID 和/或 name 属性来指定 bean 标识符。 scope 这个属性指定由特定的 bean 定义创建的对象的作用域,它将会在 bean 作用域的章节中进行讨论。 constructor-arg 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 properties 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 autowiring mode 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 lazy-initialization mode 延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时,而不是在启动时去创建一个 bean 实例。 initialization 方法 在 bean 的所有必需的属性被容器设置之后,调用回调方法。它将会在 bean 的生命周期章节中进行讨论。 destruction 方法 当包含该 bean 的容器被销毁时,使用回调方法。它将会在 bean 的生命周期章节中进行讨论。 总结通过阅读上面的源码我们知道了以下事情: Spring 读取xml文件解析其中的配置项 一个bean的定义在Spring中以BeanDefinitionHolder表示,BeanDefinitionHolder里面包含着具体的 Spring 用一个Map存储bean对象 beanDefinitionMap = new ConcurrentHashMap(64) spring 提供了标准配置的解析,同时为自定义配置标签提供了扩展支持。","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"spring 框架概述","slug":"spring-1-spring-框架概述","date":"2017-09-24T16:00:00.000Z","updated":"2017-10-20T08:14:51.000Z","comments":true,"path":"2017/09/25/spring-1-spring-框架概述/","link":"","permalink":"http://yoursite.com2017/09/25/spring-1-spring-框架概述/","excerpt":"","text":"spring的 由来 和 使命 spring 是于2003年兴起的一个轻量级Java开发框架 由Rod Johnson在其著作 Expert One-On-One JeEE Development adn Design中阐述的部分理念和原型衍生而来 它最初的目的主要是为了简化Java EE的企业级应用开发 使命: 简化java开发 采取了以下4种关键策略 基于 POJO 的轻量级和最小侵入性编程 通过依赖注入和面向接口实现松耦合 基于切面和惯例进行声明式编程 通过切面和模板减少样板式代码 spring 框架概述Spring 包含了20多个模块,依照其所属功能可以划分为如下几个不同的功能模块。 另外一种展示方式 假如我们把 spring 的框架看成一颗树,最底层的 spring-core 和 容器工具类 构成了整个树的主干,所有上层的功能都依赖于这两个特性。 The Core Container consists of the spring-core, spring-beans, spring-context, spring-context-support, and spring-expression (Spring Expression Language) modules.The spring-core and spring-beans modules provide the fundamental parts of the framework, including the IoC and Dependency Injection features. 容器是Spring框架最核心的部分,它负责Spring应用中的Bean的创建、配置和管理。spring-core 和 spring-beans 模块提供了框架最基础的部分,包括IOC和DI特性支持。 我们将从下面几个方面来学习spring的一些核心特性,首先我们通过源码来探究spring是如何读取并解析配置文件的,然后我们来看看spring提供的IOC容器的真面目以及容器提供的一些特性,然后我们继续研究一个bean在容器中被加载的全过程及其生命周期。再然后我们继续学习spring的AOP特性的原理,最后我们来看看spring所提供的事务管理是如何实现的。 学习一个东西最重要的是要了解其解决的核心问题是什么?spring提供了一个IOC的容器用来解决我们程序中bean的依赖问题。","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"spring 源码","slug":"spring-源码","permalink":"http://yoursite.com/tags/spring-源码/"}]},{"title":"每周札记","slug":"每周札记","date":"2017-09-24T16:00:00.000Z","updated":"2017-12-15T06:26:39.000Z","comments":true,"path":"2017/09/25/每周札记/","link":"","permalink":"http://yoursite.com2017/09/25/每周札记/","excerpt":"","text":"终身学习,保持兴趣。本文是一些日常学习和网上文章的记录。 博客国内技术团队博客 收录了国内各大技术公司官方技术博客 yikun 有一些高质量的博文,关于java 集合类的几篇文章值得一读 NeoRemind 研究生毕业于清华大学,本科毕业于北京邮电大学,目前工作在Hulu,从事Big data领域的研发工作,曾经在百度ECOM和程序化广告混迹6年,从事系统研发和架构工作,关注大数据、Web后端技术、广告系统技术以及致力于编写高质量的代码。曾在百度工作,写过一些很好的java相关的组件。作者发布的一些文章InfoQ – 谈谈后端业务系统的微服务化改造(http://www.infoq.com/cn/articles/the-back-end-business-systems-service-transformation)InfoQ聊聊架构 – 体系化认识RPC(http://mp.weixin.qq.com/s/lkz3zGk-KyMkI1eYeYH4ng)知乎专栏 – 深入解析Spark中的RPC(https://zhuanlan.zhihu.com/p/28893155) 江南白衣 spring side的作者 伍 翀(WuChong) 2008 - 2015 就读于北京理工大学。2015 年进入阿里巴巴中间件实时计算团队,JStorm 研发。2017 年加入计算平台,Blink/Flink 研发。 2017-09-25OmniGraffle 6/7 Axure7 注册码许可证 源码圈 365 胖友的书单整理 一些高质量的书籍推荐 Mybatis3.3.x技术内幕 关于MyBatis的源码深度分析,作者祖大俊,语言诙谐幽默,文章条理清晰。 pinpoint 技术概述 详解了pinpoint的一些设计思路,对于tracer系统的设计和了解有很大裨益 Java后端,应该日常翻看的中文技术网站 java后端博客和技术网站的推荐 2017-12-04现代支付系统设计 高性能队列——Disruptor Java并发思考-导读&总结篇 通过使用Byte Buddy,便捷地创建Java Agent Pinpoint技术概述 2017-12-15如何实现一个分布式RPC框架","categories":[],"tags":[{"name":"札记","slug":"札记","permalink":"http://yoursite.com/tags/札记/"}]},{"title":"深入解析 servlet","slug":"javaweb-编程-深入理解-servlet","date":"2017-09-20T16:00:00.000Z","updated":"2017-09-27T04:00:57.000Z","comments":true,"path":"2017/09/21/javaweb-编程-深入理解-servlet/","link":"","permalink":"http://yoursite.com2017/09/21/javaweb-编程-深入理解-servlet/","excerpt":"","text":"概述1. 什么是servlet Servlet 是基于 Java 技术的 web 组件,容器托管的,用于生成动态内容。可以被基于 Java 技术的 web server 动态加载并运行。客户端 通过 Servlet 容器实现的请求/应答模型与 Servlet 交互。 servlet 发展历史 123456789101112131415161718192021+=============+================+====================+=============================================================================+| VERSION | DATE | JAVA EE / JDK | FEATURES / CHANGES |+=============+================+====================+=============================================================================+| Servlet 3.1 | May 2013 | JavaEE 7 | Non-blocking I/O, HTTP protocol upgrade mechanism |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 3.0 | December 2009 | JavaEE 6, JavaSE 6 | Pluggability, Ease of development, Async Servlet, Security, File Uploading |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.5 | September 2005 | JavaEE 5, JavaSE 5 | Requires JavaSE 5, supports annotation |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.4 | November 2003 | J2EE 1.4, J2SE 1.3 | web.xml uses XML Schema |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.3 | August 2001 | J2EE 1.3, J2SE 1.2 | Addition of Filter |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.2 | August 1999 | J2EE 1.2, J2SE 1.2 | Becomes part of J2EE, introduced independent web applications in .war files |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.1 | November 1998 | Unspecified | First official specification, added RequestDispatcher, ServletContext |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+| Servlet 1.0 | June 1997 | | |+-------------+----------------+--------------------+-----------------------------------------------------------------------------+ 2. 什么是servlet容器 Servlet 容器是 Web 服务器或应用程序服务器的一部分,用来提供发送 请求和响应 的网络服务,解码基于 MIME 的请求,并格式化基于 MIMIE 的响应。它包含和管理 servlets 的生命周期。 servlet 接口Servlet 接口是 Java Servlet API 的核心抽象。所有 Servlet 类必须直接或间接的实现该接口,或者更通常做法 是通过继承一个实现了该接口的类从而复用许多共性功能。目前有 GenericServlet 和 HttpServlet 这两个类实 现了 Servlet 接口。大多数情况下,开发者只需要继承 HttpServlet 去实现自己的 Servlet 即可。 servlet 相关的类图如下: Servlet 接口定义如下:(servlet 版本号 3.1.0) 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647package javax.servlet;import java.io.IOException;/** * Defines methods that all servlets must implement. * * <p>A servlet is a small Java program that runs within a Web server. * Servlets receive and respond to requests from Web clients, * usually across HTTP, the HyperText Transfer Protocol. * * <p>To implement this interface, you can write a generic servlet * that extends * <code>javax.servlet.GenericServlet</code> or an HTTP servlet that * extends <code>javax.servlet.http.HttpServlet</code>. * */public interface Servlet { /** * Called by the servlet container to indicate to a servlet that the * servlet is being placed into service. * * <p>The servlet container calls the <code>init</code> * method exactly once after instantiating the servlet. * The <code>init</code> method must complete successfully * before the servlet can receive any requests. * */ public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); /** * Called by the servlet container to allow the servlet to respond to * a request. * <p>This method is only called after the servlet's <code>init()</code> * method has completed successfully. */ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy();} 2.1 请求处理方法Servlet 基础接口定义了用于客户端请求处理的 service 方法。当有请求到达时,该方法由 servlet 容器路由到一个 servlet 实例。Web 应用程序的并发请求处理通常需要 Web 开发人员去设计适合多线程执行的 Servlet,从而保证 service 方法能在一个特定时间点处理多线程并发执行。(注:即 Servlet 默认是线程不安全的,需要开发人员处理 多线程问题)通常 Web 容器对于并发请求将使用同一个 servlet 处理,并且在不同的线程中并发执行 service 方法。 2.2 Servlet 生命周期Servlet 是按照一个严格定义的生命周期被管理,该生命周期规定了 Servlet 如何被加载、实例化、初始化、 处理客户端请求,以及何时结束服务。该声明周期可以通过 javax.servlet.Servlet 接口中的 init、service 和 destroy 这些 API 来表示,所有 Servlet 必须直接或间接的实现 GenericServlet 或 HttpServlet 抽象类。 servlet的生命周期有四个阶段: 加载并实例化、初始化、请求处理、销毁。涉及到的方法有:init、service、destory等 加载并实例化Servlet容器负责加载和实例化Servelt。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,Servlet通过类加载器来加载Servlet类,加载完成后再new一个Servlet对象来完成实例化。 初始化在Servlet实例化之后,容器将调用init()方法,并传递实现ServletConfig接口的对象。在init()方法中,Servlet可以部署描述符中读取配置参数,或者执行任何其他一次性活动。在Servlet的整个生命周期类,init()方法只被调用一次。 请求处理当Servlet初始化后,容器就可以准备处理客户机请求了。当容器收到对这一Servlet的请求,就调用Servlet的service()方法,并把请求和响应对象作为参数传递。当并行的请求到来时,多个service()方法能够同时运行在独立的线程中。service()方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet()、doPost()、doPut(),doDelete() 等方法。 如下是HttpServlet里面service的实现:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString(\"http.method_not_implemented\"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); }} 销毁一旦Servlet容器检测到一个Servlet要被卸载,这可能是因为要回收资源或者因为它正在被关闭,容器会在所有Servlet的service()线程之后,调用Servlet的destroy()方法。然后,Servlet就可以进行无用存储单元收集清理。这样Servlet对象就被销毁了。这四个阶段共同决定了Servlet的生命周期。 一个典型的处理过程请看如下的时序图: 请求的过程描述如下: Web Client 向Servlet容器(Tomcat)发出Http请求; Servlet容器接收Web Client的请求; Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中; Servlet容器创建一个HttpResponse对象; Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象; HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息; HttpServlet调用HttpResponse对象的有关方法,生成响应数据; Servlet容器把HttpServlet的响应结果传给Web Client; Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。在Servlet接口中定义了5个方法,其中3个方法代表了Servlet的生命周期: init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性;destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源; Servlet ContextServletContext(Servlet 上下文)接口定义了 servlet 运行在的 Web 应用的视图。容器供应商负责提供 Servlet 容器的 ServletContext 接口的实现。Servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源, 存取当前上下文的其他 Servlet 可以访问的属性。ServletContext 是 Web 服务器中已知路径的根。例如,Servlet 上下文可以从 http://www.mycorp.com/catalog 找出,/catalog 请求路径称为上下文路径,所有以它开头的请求都会被路由到与 ServletContext 相关联的 Web 应用。 Defines a set of methods that a servlet uses to communicate with its servlet container, for example, to get the MIME type of a file, dispatch requests, or write to a log file.There is one context per “web application” per Java Virtual Machine. (A “web application” is a collection of servlets and content installed under a specific subset of the server’s URL namespace such as /catalog and possibly installed via a .war file.) Servlet 中的 Listener整个 Tomcat 服务器中 Listener 使用的非常广泛,它是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是: 4 个 EventListeners 类型的 ServletContextAttributeListener ServletRequestAttributeListener ServletRequestListener HttpSessionAttributeListener 2 个 LifecycleListeners 类型的 ServletContextListener HttpSessionListener 如下图所示: 它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件。这些 Listener 的实现类可以配置在 web.xml 中的 标签中。当然也可以在应用程序中动态添加 Listener,需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现。掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活。 参考资料servlet 规范(3.1)可以去网上下载中英文文档 Java Servlet 3.1 规范笔记https://emacsist.github.io/emacsist/servlet/Java%20Servlet%203.1%20%E8%A7%84%E8%8C%83%E7%AC%94%E8%AE%B0.html#org7916fb9 Servlet 工作原理解析https://www.ibm.com/developerworks/cn/java/j-lo-servlet/ servlet的本质是什么,它是如何工作的?https://www.zhihu.com/question/21416727","categories":[{"name":"javaweb","slug":"javaweb","permalink":"http://yoursite.com/categories/javaweb/"}],"tags":[{"name":"servlet","slug":"servlet","permalink":"http://yoursite.com/tags/servlet/"}]},{"title":"深入解析springmvc","slug":"javaweb-编程-深入解析spring-MVC","date":"2017-09-20T16:00:00.000Z","updated":"2017-09-21T08:45:24.000Z","comments":true,"path":"2017/09/21/javaweb-编程-深入解析spring-MVC/","link":"","permalink":"http://yoursite.com2017/09/21/javaweb-编程-深入解析spring-MVC/","excerpt":"","text":"SpringMVC可以说是当前java web开发领域最流行的mvc框架了,了解其工作原理及设计思路对于开发者而言无疑有很大的益处。 众所周知,SpringMVC是建立在Servlet基础之上,一般来说配置所有的请求都由DispatcherServlet来处理,从web.xml的配置中就可以看出来。 12345678910111213141516<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:spring-servlet.xml </param-value> </init-param> <load-on-startup>0</load-on-startup></servlet><servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern></servlet-mapping>","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"springmvc","slug":"springmvc","permalink":"http://yoursite.com/tags/springmvc/"}]},{"title":"关于自建博客的一些东西","slug":"工具-about-blog","date":"2017-09-19T01:37:28.000Z","updated":"2017-09-20T03:48:11.000Z","comments":true,"path":"2017/09/19/工具-about-blog/","link":"","permalink":"http://yoursite.com2017/09/19/工具-about-blog/","excerpt":"","text":"我日常的文档大部分是用markdown来管理的,日积月累也积攒了很多技术和非技术的文章。最常用的工具是sublime + markdown preview,基本上可以满足大部分日常文档的编写需求。但是大部分是一些草稿或记录,一直没有找到很好的博客解决方案,直到我发现了hexo。本文是我在日常编写文档和使用hexo搭建博客中使用到的一些工具。 markdown 简介markdown 现在已经成了我日常工作和学习的一部分了,惊艳于它简单的语法和自由的表达方式,让人沉浸于写作的乐趣,让人更专注与写作和技术本身。 基础语法可以参考 http://wowubuntu.com/markdown/。 hexo 简介 A fast, simple & powerful blog framework Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 部署到哪里hexo 是一个本地的静态博客框架工具,可以将我们平时的文章渲染为最终的 html 代码,但是如何将文章放到公网上,让其它人也可以访问呢?当然可以买一个主机,自己搭建服务器。更为方便的是可以使用github来托管博客。 GitHub Pages 本用于介绍托管在GitHub的项目,不过,由于他的空间免费稳定,用来做搭建一个博客再好不过了。 简单的步骤: 申请 github page 配置hexo deploy 123456# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: git repo: https://github.com/lepfinder/lepfinder.github.io.git branch: master 安装git 在本地blog目录执行发布 其它如何快速的在 markdown 文档中插入图片插图是编写文档必不可少的一个动作,如何快速的在markdown文本中插入图片呢?我本地使用 “Alfred 工作流 + 七牛云” 外链的方式。使用起来非常便捷: command + control + shift + 4 完成截图 command + control + shift + V 完成图片上传到七牛 command + V 粘贴外链地址到指定位置(如果光标此时在编辑器中,这一步其实是自动的) 具体使用方法参考 简化markdown写作中的贴图流程 如何画出好看的图?推荐 OmniGraffle,下面是一个截图: 如何在修改完文章后自动渲染推荐使用livereload,我使用的是lepture写的一个python的库,项目地址。livereload 可以做什么事情呢?比如作为一个前端开发,每次写完html或js代码之后,保存完,需要去浏览器手动刷新页面才能看到修改后的效果,能不能文件发生变化之后浏览器自动刷新呢,这样将会给实际的开发带来很大的便利,于是livereload便出场了。 使用步骤: 下载安装 server 端,即python-livereload 进入需要监控的地址,比如 12cd /Users/xiexiyang/Documents/mybloglivereload 在浏览器中激活chrome插件 效果图: 如何在文中中插入 gif 动图推荐使用 licecap。 我上面的 livereload 动图就是使用的licecap录制的。","categories":[],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://yoursite.com/tags/hexo/"},{"name":"自建博客","slug":"自建博客","permalink":"http://yoursite.com/tags/自建博客/"}]},{"title":"spring 实现动态数据源配置","slug":"spring-spring-实现动态数据源配置","date":"2017-09-14T16:00:00.000Z","updated":"2017-09-21T03:33:16.000Z","comments":true,"path":"2017/09/15/spring-spring-实现动态数据源配置/","link":"","permalink":"http://yoursite.com2017/09/15/spring-spring-实现动态数据源配置/","excerpt":"","text":"背景 1、当项目慢慢变大,访问量也慢慢变大的时候,就难免的要使用多个数据源和设置读写分离了。 2、如果你做的是一个数据报表或文件导出的系统,需要处理的数据可能是来源于多个数据库,如何在应用中动态的支持多个数据源的添加和删除?可以做到在不重启应用服务器的同时动态的添加和删除数据源吗? 我们分为两个步骤来思考如上的两个问题,首先解决读写分离的问题,再来解决动态多数据源的问题。 见过比较多的读写分离处理方式,主要分为两步: 1、对于开发人员,要求serivce类的方法名必须遵守规范,读操作以query、get等开头,写操作以update、delete开头。2、配置一个拦截器,依据方法名判断是读操作还是写操作,设置相应的数据源。 以上做法能实现最简单的读写分离,但相应的也会有很多不方便的地方: 1、数据源的管理不太方便,基本上只有2个数据源了,一个读一个写。这个可以在spring中声明多个bean来解决该问题,但bean的id和数据源的功能也就绑定了。 2、因为读写分离往往是在项目慢慢变大后加入的,不是一开始就有,上面说到的第二点方法名可能会各式各样,find、insert、save、exe等等,这些都要一一修改,且要保证以后读的方法名中不能有写操作。也可以拦截的底层一点如JdbcTemplate,但这样会导致交叉设置数据源。 3、数据源无法动态修改,只能在项目启动时加载。 以上问题我想开发人员多多少少都会遇到,这也是本文要讨论的问题。 本文使用到的技术栈有: spring druid(alibaba 提供的一个数据库连接池) mybatis 单数据源的配置如下是一个项目中常用的单数据源的配置方式: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950<bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\"> <!-- 基本属性 url、user、password --> <property name=\"url\" value=\"${jdbc.url}?useUnicode=true&amp;zeroDateTimeBehavior=convertToNull&amp;allowMultiQueries=true&amp;autoReconnect=true\"/> <property name=\"username\" value=\"${jdbc.username}\" /> <property name=\"password\" value=\"${jdbc.password}\" /> <!-- 配置初始化大小、最小、最大 --> <property name=\"initialSize\" value=\"1\" /> <property name=\"minIdle\" value=\"1\" /> <property name=\"maxActive\" value=\"20\" /> <!-- 配置获取连接等待超时的时间 --> <property name=\"maxWait\" value=\"60000\" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name=\"timeBetweenEvictionRunsMillis\" value=\"60000\" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name=\"minEvictableIdleTimeMillis\" value=\"300000\" /> <property name=\"validationQuery\" value=\"SELECT 'x'\" /> <property name=\"testWhileIdle\" value=\"true\" /> <property name=\"testOnBorrow\" value=\"false\" /> <property name=\"testOnReturn\" value=\"false\" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name=\"poolPreparedStatements\" value=\"true\" /> <property name=\"maxPoolPreparedStatementPerConnectionSize\" value=\"20\" /> <!-- 配置监控统计拦截的filters --> <property name=\"filters\" value=\"stat,log4j\" /> </bean> <bean id=\"log-filter\" class=\"com.alibaba.druid.filter.logging.Log4jFilter\"> <property name=\"statementExecutableSqlLogEnable\" value=\"true\" /></bean><bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!--dataSource属性指定要用到的连接池--> <property name=\"dataSource\" ref=\"dataSource\"/> <!--configLocation属性指定mybatis的核心配置文件--> <property name=\"configLocation\" value=\"classpath:sqlMapConfig.xml\"/></bean><bean id=\"sqlSession\" class=\"org.mybatis.spring.SqlSessionTemplate\"> <constructor-arg index=\"0\" ref=\"sqlSessionFactory\" /></bean><bean id=\"txManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <property name=\"dataSource\" ref=\"dataSource\" /></bean> 多数据源的配置如下是一个多数据源的配置方式,可以支持读写分离,针对于不同的dao,配置不同的sqlSessionFactory即可实现从不同的数据源获取和操作数据。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748<bean id=\"dataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\"> <!-- 基本属性 url、user、password --> <property name=\"url\" value=\"${jdbc.url}?useUnicode=true&amp;zeroDateTimeBehavior=convertToNull&amp;allowMultiQueries=true&amp;autoReconnect=true\"/> <property name=\"username\" value=\"${jdbc.username}\" /> <property name=\"password\" value=\"${jdbc.password}\" /></bean><bean id=\"log-filter\" class=\"com.alibaba.druid.filter.logging.Log4jFilter\"> <property name=\"statementExecutableSqlLogEnable\" value=\"true\" /></bean><bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!--dataSource属性指定要用到的连接池--> <property name=\"dataSource\" ref=\"dataSource\"/> <!--configLocation属性指定mybatis的核心配置文件--> <property name=\"configLocation\" value=\"classpath:sqlMapConfig.xml\"/></bean><bean id=\"sqlSession\" class=\"org.mybatis.spring.SqlSessionTemplate\"> <constructor-arg index=\"0\" ref=\"sqlSessionFactory\" /></bean><bean id=\"readDataSource\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\"> <!-- 基本属性 url、user、password --> <property name=\"url\" value=\"${jdbc.readonly.url}?useUnicode=true&amp;zeroDateTimeBehavior=convertToNull&amp;allowMultiQueries=true&amp;autoReconnect=true\"/> <property name=\"username\" value=\"${jdbc.readonly.username}\" /> <property name=\"password\" value=\"${jdbc.readonly.password}\" /></bean><bean id=\"readSqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!--dataSource属性指定要用到的连接池--> <property name=\"dataSource\" ref=\"readDataSource\"/> <!--configLocation属性指定mybatis的核心配置文件--> <property name=\"configLocation\" value=\"classpath:sqlMapConfig.xml\"/></bean><bean id=\"readSqlSession\" class=\"org.mybatis.spring.SqlSessionTemplate\"> <constructor-arg index=\"0\" ref=\"readSqlSessionFactory\" /></bean><bean id=\"pdfPasswordDao\" class=\"org.mybatis.spring.mapper.MapperFactoryBean\"> <property name=\"mapperInterface\" value=\"com.qding.dcenter.dao.IPdfPasswordDao\"/> <property name=\"sqlSessionFactory\" ref=\"sqlSessionFactory\"/></bean><bean id=\"summaryReportDaoReadOnly\" class=\"org.mybatis.spring.mapper.MapperFactoryBean\"> <property name=\"mapperInterface\" value=\"com.qding.dcenter.dao.ISummaryReportDao\"/> <property name=\"sqlSessionFactory\" ref=\"readSqlSessionFactory\"/></bean> 动态多数据源这里的动态指的是什么呢? 在我看来一个好的动态数据源,应该跟单数据源一样让使用者感觉不到他是动态的,至少dao层的开发者应该感觉不到。先来看张图: 基于spring实现动态数据源其实spring早就想到了这一点,也已经为我们准备好了扩展类AbstractRoutingDataSource,我们只需要一个简单的实现即可。网上关于这个类文章很多,但都比较粗浅没有讲到点子上,只是实现了多个数据源而已。 这里我们同样来实现AbstractRoutingDataSource,它只要求实现一个方法:12345678/** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */protected abstract Object determineCurrentLookupKey(); 你可以简单的理解它:spring把所有的数据源都存放在了一个map中,这个方法返回一个key告诉spring用这个key从map中去取。 AbstractRoutingDataSource 实现了 InitializingBean 那么spring在初始化该bean时,会调用InitializingBean的接口void afterPropertiesSet() throws Exception; 我们看下AbstractRoutingDataSource是如何实现这个接口的:123456789101112131415@Overridepublic void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException(\"Property 'targetDataSources' is required\"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); }} targetDataSources 是我们在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的dataSourceMaster 和 dataSourceSlave来构造一个HashMap——resolvedDataSources。方便后面根据 key 从该map 中取得对应的dataSource。我们在看下 AbstractDataSource 接口中的 Connection getConnection() throws SQLException; 是如何实现的:1234@Overridepublic Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection();} 关键在于 determineTargetDataSource(),根据方法名就可以看出,应该此处就决定了使用哪个 dataSource :123456789101112protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, \"DataSource router not initialized\"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException(\"Cannot determine target DataSource for lookup key [\" + lookupKey + \"]\"); } return dataSource;} Object lookupKey = determineCurrentLookupKey(); 该方法是我们实现的,在其中获取ThreadLocal中保存的 key 值。获得了key之后,在从afterPropertiesSet()中初始化好了的resolvedDataSources这个map中获得key对应的dataSource。而ThreadLocal中保存的 key 值是通过AOP的方式在调用service中相关方法之前设置好的。OK,到此搞定! 它还有个targetDataSources和defaultTargetDataSource属性,网上的一堆做法是继承这个类,然后在声明bean的时候注入dataSource:12345678910<bean id=\"dynamicdatasource\" class=\"......\"> <property name=\"targetDataSources\"> <map> <entry key=\"dataSource1\" value-ref=\"dataSource1\" /> <entry key=\"dataSource2\" value-ref=\"dataSource2\" /> <entry key=\"dataSource3\" value-ref=\"dataSource3\" /> </map> </property> <property name=\"defaultTargetDataSource\" ref=\"dataSource1\" /></bean> 这样虽然简单,但是弊端也是显而易见的,除了使用了多个数据源之外没有我们想要的任何操作。但是如果不配置targetDataSources,spring在启动的时候就会抛出异常而无法运行。 一种动态数据源配置1) 先定义一个enum来表示不同的数据源:12345678package net.aazj.enums;/** * 数据源的类别:master/slave */public enum DataSources { MASTER, SLAVE} 2)通过 TheadLocal 来保存每个线程选择哪个数据源的标志(key): 123456789101112131415161718192021222324package net.aazj.util;import net.aazj.enums.DataSources;public class DataSourceTypeManager { private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>(){ @Override protected DataSources initialValue(){ return DataSources.MASTER; } }; public static DataSources get(){ return dataSourceTypes.get(); } public static void set(DataSources dataSourceType){ dataSourceTypes.set(dataSourceType); } public static void reset(){ dataSourceTypes.set(DataSources.MASTER0); }} 3)定义 ThreadLocalRountingDataSource,继承AbstractRoutingDataSource: 12345678910package net.aazj.util;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceTypeManager.get(); }} 4)在配置文件中向 ThreadLocalRountingDataSource 注入 master 和 slave 的数据源: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<!-- 配置数据源Master --><bean name=\"dataSourceMaster\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\"> <property name=\"url\" value=\"${jdbc_url}\" /> <property name=\"username\" value=\"${jdbc_username}\" /> <property name=\"password\" value=\"${jdbc_password}\" /> <!-- 初始化连接大小 --> <property name=\"initialSize\" value=\"0\" /> <!-- 连接池最大使用连接数量 --> <property name=\"maxActive\" value=\"20\" /> <!-- 连接池最大空闲 --> <property name=\"maxIdle\" value=\"20\" /> <!-- 连接池最小空闲 --> <property name=\"minIdle\" value=\"0\" /> <!-- 获取连接最大等待时间 --> <property name=\"maxWait\" value=\"60000\" /></bean> <!-- 配置数据源Slave --><bean name=\"dataSourceSlave\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\"> <property name=\"url\" value=\"${jdbc_url_slave}\" /> <property name=\"username\" value=\"${jdbc_username_slave}\" /> <property name=\"password\" value=\"${jdbc_password_slave}\" /> <!-- 初始化连接大小 --> <property name=\"initialSize\" value=\"0\" /> <!-- 连接池最大使用连接数量 --> <property name=\"maxActive\" value=\"20\" /> <!-- 连接池最大空闲 --> <property name=\"maxIdle\" value=\"20\" /> <!-- 连接池最小空闲 --> <property name=\"minIdle\" value=\"0\" /> <!-- 获取连接最大等待时间 --> <property name=\"maxWait\" value=\"60000\" /></bean> <bean id=\"dataSource\" class=\"net.aazj.util.ThreadLocalRountingDataSource\"> <property name=\"defaultTargetDataSource\" ref=\"dataSourceMaster\" /> <property name=\"targetDataSources\"> <map key-type=\"net.aazj.enums.DataSources\"> <entry key=\"MASTER\" value-ref=\"dataSourceMaster\"/> <entry key=\"SLAVE\" value-ref=\"dataSourceSlave\"/> <!-- 这里还可以加多个dataSource --> </map> </property></bean> <bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <property name=\"dataSource\" ref=\"dataSource\" /> <property name=\"configLocation\" value=\"classpath:config/mybatis-config.xml\" /> <property name=\"mapperLocations\" value=\"classpath*:config/mappers/**/*.xml\" /></bean> <!-- Transaction manager for a single JDBC DataSource --><bean id=\"transactionManager\" class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\"> <property name=\"dataSource\" ref=\"dataSource\" /></bean> <!-- 使用annotation定义事务 --><tx:annotation-driven transaction-manager=\"transactionManager\" /> <bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\"> <property name=\"basePackage\" value=\"net.aazj.mapper\" /> <!-- <property name=\"sqlSessionFactoryBeanName\" value=\"sqlSessionFactory\"/> --></bean> 上面spring的配置文件中,我们针对master数据库和slave数据库分别定义了dataSourceMaster和dataSourceSlave两个dataSource,然后注入到 中,这样我们的dataSource就可以来根据 key 的不同来选择dataSourceMaster和 dataSourceSlave了。 5)使用Spring AOP 来指定 dataSource 的 key ,从而dataSource会根据key选择 dataSourceMaster 和 dataSourceSlave: 123456789101112131415161718192021222324package net.aazj.aop;import net.aazj.enums.DataSources;import net.aazj.util.DataSourceTypeManager;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect // for aop@Component // for auto scan@Order(0) // execute before @Transactionalpublic class DataSourceInterceptor { @Pointcut(\"execution(public * net.aazj.service..*.getUser(..))\") public void dataSourceSlave(){}; @Before(\"dataSourceSlave()\") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); } // ... ...} 这里我们定义了一个 Aspect 类,我们使用 @Before 来在符合 @Pointcut(“execution(public net.aazj.service...getUser(..))”) 中的方法被调用之前,调用 DataSourceTypeManager.set(DataSources.SLAVE) 设置了 key 的类型为 DataSources.SLAVE,所以 dataSource 会根据key=DataSources.SLAVE 选择 dataSourceSlave 这个dataSource。所以该方法对于的sql语句会在slave数据库上执行(经网友老刘1987提醒,这里存在多个Aspect之间的一个执行顺序的问题,必须保证切换数据源的Aspect必须在@Transactional这个Aspect之前执行,所以这里使用了@Order(0)来保证切换数据源先于@Transactional执行)。 我们可以不断的扩充 DataSourceInterceptor 这个 Aspect,在中进行各种各样的定义,来为某个service的某个方法指定合适的数据源对应的dataSource。 这样我们就可以使用 Spring AOP 的强大功能来,十分灵活进行配置了。 可以动态添加、修改的动态数据源实现上面的依然有如下的问题 数据源的管理不太方便,基本上只有2个数据源了,一个读一个写。这个可以在spring中声明多个bean来解决该问题,但bean的id和数据源的功能也就绑定了。 因为读写分离往往是在项目慢慢变大后加入的,不是一开始就有,上面说到的第二点方法名可能会各式各样,find、insert、save、exe等等,这些都要一一修改,且要保证以后读的方法名中不能有写操作。也可以拦截的底层一点如JdbcTemplate,但这样会导致交叉设置数据源。 数据源无法动态修改,只能在项目启动时加载。 怎么样实现数据源的动态添加和修改呢,即使项目启动了也可以在不重启服务的时候实现修改和变动 写一个DynamicDataSourceManager 实现InitializingBean接口,继承AbstractRoutingDataSource123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899public class DynamicDataSourceManager extends AbstractRoutingDataSource implements InitializingBean { private Map<Object,Object> dsMap = new ConcurrentHashMap<>(); @Resource(name=\"exportDatasourceDao\") private IExportDatasourceDao exportDatasourceDao; /** * 根据配置动态添加或更新一个数据源 * @param connConfig 数据源配置 */ public void addDataSource(ExportDatasource connConfig) { logger.info(\"[addDataSource] config=\"+ JSON.toJSONString(connConfig)); DruidDataSource ds = createDatasource(connConfig); dsMap.put(connConfig.getName(),ds); this.setTargetDataSources(dsMap); super.afterPropertiesSet(); } /** * 初始化所有的数据源 */ private void initDataSources() { logger.info(\"[initDataSources] start.\"); List<ExportDatasource> dsList = exportDatasourceDao.findAll(); for(ExportDatasource connConfig : dsList){ DruidDataSource ds = createDatasource(connConfig); dsMap.put(connConfig.getName(),ds); } this.setTargetDataSources(dsMap); super.afterPropertiesSet(); logger.info(\"[initDataSources] end, datasource size = \"+dsList.size()); } @Override protected Object determineCurrentLookupKey() { logger.info(\"determineCurrentLookupKey key=\"+DynamicDataSourceHolder.getDataSource()); return DynamicDataSourceHolder.getDataSource(); } @Override public void afterPropertiesSet() { this.initDataSources(); } /** * 初始化一个数据源 * @param connConfig */ private DruidDataSource createDatasource(ExportDatasource connConfig){ DruidDataSource ds = new DruidDataSource(); //连接属性 //jdbc:mysql://10.37.253.42:3307/qding_shield //jdbc:hive2://10.37.5.116:21051/qding_api_log/;auth=noSasl String url = \"\"; if(DB_TYPE_MYSQL.equals(connConfig.getType())){ url = String.format(\"jdbc:mysql://%s:%d?useUnicode=true&amp;zeroDateTimeBehavior=convertToNull&amp;allowMultiQueries=true&amp;autoReconnect=true\",connConfig.getIp(),connConfig.getPort()); ds.setDriverClassName(\"com.mysql.jdbc.Driver\"); }else if(DB_TYPE_IMPALA.equals(connConfig.getType())){ url = String.format(\"jdbc:hive2://%s:%d/;auth=noSasl\",connConfig.getIp(),connConfig.getPort()); ds.setDriverClassName(\"org.apache.hive.jdbc.HiveDriver\"); } ds.setUrl(url); ds.setUsername(connConfig.getUserName()); if(connConfig.getPassword()!=null) { ds.setPassword(connConfig.getPassword()); } //其它参数属性 ds.setInitialSize(initialSize); ds.setMinIdle(minIdle); ds.setMaxActive(maxActive); //配置获取连接等待超时的时间 ds.setMaxWait(maxWait); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 ds.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); //配置一个连接在池中最小生存的时间,单位是毫秒 ds.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); //打开PSCache,并且指定每个连接上PSCache的大小 ds.setPoolPreparedStatements(poolPreparedStatements); ds.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); ds.setName(connConfig.getName()); return ds; }} 我们实现一个初始化数据源的方法initDataSources在本方法中完成数据源的初始化,并调用父类的this.setTargetDataSources(dsMap);那么什么时候来运行这个解析的方法呢?有些同学可能一下就想到了spring声明bean时的init-method属性,但是这里不行。因为init-method是在bean初始化完成之后调用的,当spring在初始化DynamicDataSource时发现这两个属性是空的异常就抛出来了,根本就没有机会去运行init-method。 所以我们要在bean的初始化过程中来解析并存入我们的数据源。要实现这个操作,我们可以实现spring的InitializingBean接口。由于AbstractRoutingDataSource已经实现了该接口,我们只需要重写该方法就行。也就是说DynamicDataSource要实现以下两个方法:12345678@Overrideprotected Object determineCurrentLookupKey() { ...}@Overridepublic void afterPropertiesSet() { this.initDataSources();} 在afterPropertiesSet方法中实现我们解析数据源的操作。但是这样还不够,因为spring容器并不知道你做了这些,所以最后的一行super.afterPropertiesSet();千万别忘了,用来通知spring容器。 到这里数据源的解析已经完成了,我们又怎么样来取数据源呢? 这个我们可以利用ThreadLocal来实现。编写DynamicDataSourceHolder类 123456789101112131415161718192021package com.qding.knight.datasource;/** * Created by xiexiyang on 2017/7/12. */public class DynamicDataSourceHolder { //线程本地环境 private static final ThreadLocal<String> dataSources = new ThreadLocal<String>(); //设置数据源 public static void setDataSource(String customerType) { dataSources.set(customerType); } //获取数据源 public static String getDataSource() { return (String) dataSources.get(); } //清除数据源 public static void clearDataSource() { dataSources.remove(); }} 配置如下:1234567891011121314<bean id=\"dynamicDataSource\" class=\"com.qding.knight.datasource.DynamicDataSourceManager\"></bean><bean id=\"exportSqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <!--dataSource属性指定要用到的连接池--> <property name=\"dataSource\" ref=\"dynamicDataSource\"/> <!--configLocation属性指定mybatis的核心配置文件--> <property name=\"configLocation\" value=\"classpath:exportSqlMapConfig.xml\"/></bean><!-- 通过扫描的模式,扫描目录在com/hoo/mapper目录下,所有的mapper都继承SqlMapper接口的接口 --><bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\"> <property name=\"basePackage\" value=\"com.qding.knight.dao2\" /> <property name=\"sqlSessionFactoryBeanName\" value=\"exportSqlSessionFactory\"/></bean> 使用方式如下: 参考资料Spring, MyBatis 多数据源的配置和管理http://www.cnblogs.com/digdeep/p/4512368.html Spring实现动态数据源,支持动态添加、删除和设置权重及读写分离https://www.dexcoder.com/selfly/article/4048","categories":[{"name":"spring","slug":"spring","permalink":"http://yoursite.com/categories/spring/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://yoursite.com/tags/spring/"},{"name":"动态数据源","slug":"动态数据源","permalink":"http://yoursite.com/tags/动态数据源/"}]},{"title":"浅论软件开发的本质","slug":"方法论-浅论软件开发的本质","date":"2017-09-14T16:00:00.000Z","updated":"2017-09-21T02:31:11.000Z","comments":true,"path":"2017/09/15/方法论-浅论软件开发的本质/","link":"","permalink":"http://yoursite.com2017/09/15/方法论-浅论软件开发的本质/","excerpt":"","text":"从毕业到现在6年了,带上大学的4年,加起来进入这个行业也有了10年了,十年就这么不快不慢的过去了。接触的行业有出版社的资源管理系统、在线学习系统、大数据数据挖掘和电商交易系统。挖过一些坑,也踩过很多雷,回头梳理下开发过和接触过的系统,希望从个人的理解角度探究下软件系统的本质是什么? 我个人认为在软件开发领域几个核心的概念是:数据存储、数据获取传输和数据展示。每一个软件都承担了其中的一个或几个角色。我们软件开发人员要做的事情也是把数据获取出来展示给需要的人看。 为何是这样说呢? 看上面的图,可以套用到大多数的互联网或非互联网软件产品中去。我们每个人每天都在生产大量的数据,也会消费大量的数据。比如视频、图片或文字这些都是数据的一种。 作为软件开发者,我们要做的就是把互联网上用户的数据收集并保存下来,把用户需要的数据拿到并展示给用户。 比如我现在在写的这个文件,假如我使用Sublime来编写,Sublime就是一个软件,这个文章的源文件保存到本地磁盘上,本地磁盘就是一个存储介质,Sublime打开文件便是数据的展示。假如我把源文件上传到github,打开浏览器依然可以看到文件的内容,此时数据便存储到了github远程的服务器上,展示的载体便变为了浏览器。 日常的软件开发也是如此,我们接到一个需求,进入开发之前首先要考虑的便是数据模型的抽象和设计,我们需要保存哪些数据,数据会存放到什么地方,应该给用户如何展示,展示的形式是什么样子的。 对应到具体的开发,比如我们要做一个会员系统,提供会员注册、登录和管理等功能,作为一个java web的开发人员,使用的技术栈有: spring MVC/spring 负责web业务处理 mybatis/druid 负责mysql数据的获取 mysql 存储用户基础信息 七牛 存储用户头像 html/js/angularjs 前端展示 对于普通的用户来讲,注册只需要在浏览器填写用户名,密码,点击提交即可,数据会在服务器端做验证,并保存到数据库。用户登录的时候,从数据库获取数据做匹配,完成登录的动作。 每一个业务流程都是类似的框架结构,数据在不同的端做流转,转化。开发人员的核心价值便是管理这些数据,正确的收集,正确的存储,正确的获取以及有效的展示。","categories":[{"name":"方法论","slug":"方法论","permalink":"http://yoursite.com/categories/方法论/"}],"tags":[{"name":"软件开发","slug":"软件开发","permalink":"http://yoursite.com/tags/软件开发/"}]},{"title":"ArrayList 和 Vector 的区别","slug":"java基础和高级特性-05-java集合类之-ArrayList-和-Vector-的区别","date":"2017-09-12T04:08:58.000Z","updated":"2017-09-14T08:31:41.000Z","comments":true,"path":"2017/09/12/java基础和高级特性-05-java集合类之-ArrayList-和-Vector-的区别/","link":"","permalink":"http://yoursite.com2017/09/12/java基础和高级特性-05-java集合类之-ArrayList-和-Vector-的区别/","excerpt":"","text":"1. Synchronization and Thread-SafeVector is synchronized while ArrayList is not synchronized . Synchronization and thread safe means at a time only one thread can access the code .In Vector class all the methods are synchronized .Thats why the Vector object is already synchronized when it is created . 2. PerformanceVector is slow as it is thread safe . In comparison ArrayList is fast as it is non synchronized . Thus in ArrayList two or more threads can access the code at the same time , while Vector is limited to one thread at a time. 3. Automatic Increase in CapacityA Vector defaults to doubling size of its array . While when you insert an element into the ArrayList , it increasesits Array size by 50% . By default ArrayList size is 10 . It checks whether it reaches the last element then it will create the new array ,copy the new data of last array to new array ,then old array is garbage collected by the Java Virtual Machine (JVM) . 4. Set Increment SizeArrayList does not define the increment size . Vector defines the increment size . You can find the following method in Vector Class public synchronized void setSize(int i) { //some code } There is no setSize() method or any other method in ArrayList which can manually set the increment size. 5. EnumeratorOther than Hashtable ,Vector is the only other class which uses both Enumeration and Iterator .While ArrayList can only use Iterator for traversing an ArrayList . 6. Introduction in Javajava.util.Vector class was there in java since the very first version of the java development kit (jdk).java.util.ArrayList was introduced in java version 1.2 , as part of Java Collections framework . In java version 1.2 , Vector class has been refactored to implement the List Inteface .","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"LinkedList 详解和源码解析","slug":"java基础和高级特性-03-java集合类之LinkedList","date":"2017-09-12T02:54:35.000Z","updated":"2017-09-14T08:31:57.000Z","comments":true,"path":"2017/09/12/java基础和高级特性-03-java集合类之LinkedList/","link":"","permalink":"http://yoursite.com2017/09/12/java基础和高级特性-03-java集合类之LinkedList/","excerpt":"","text":"LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。 LinkedList 实现 List 接口,能对它进行队列操作。 LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。 LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。 LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。 LinkedList 是非同步的。 类声明123public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable 类继承关系图 构造函数1234567public LinkedList() {}public LinkedList(Collection<? extends E> c) { this(); addAll(c);} 数据结构123456789101112131415161718192021222324transient int size = 0;/** * Pointer to first node. */transient Node<E> first;/** * Pointer to last node. */transient Node<E> last;private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }} 重要方法实现add12345678910111213141516171819public boolean add(E e) { linkLast(e); return true;}/** * Links e as last element. */void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;} 新加入的元素会变为最后一个元素,如果是第一次添加,也就是last==null,则first==last==newNode get1234567891011121314151617181920public E get(int index) { checkElementIndex(index); return node(index).item;}Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { //检查index是否小于集合大小的半,来决定是从头查找还是从尾部查找 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; }} 在LinkedList中获取指定位置的一个元素需要挨个遍历,jdk做了简单的一个优化,首先判断index是否小于集合大小的半,来决定是从头查找还是从尾部查找,这样可以减少遍历的次数。 队列的操作12345678910//获取一个元素,不删除public E peek() { final Node<E> f = first; return (f == null) ? null : f.item;}//从头获取一个元素,同时删除此元素public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f);}","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"ArrayList 详解和源码解析","slug":"java基础和高级特性-03-java集合类之ArrayList","date":"2017-09-11T09:33:37.000Z","updated":"2017-09-26T01:27:02.000Z","comments":true,"path":"2017/09/11/java基础和高级特性-03-java集合类之ArrayList/","link":"","permalink":"http://yoursite.com2017/09/11/java基础和高级特性-03-java集合类之ArrayList/","excerpt":"","text":"概述ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。 ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。 ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。 ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。 和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。 ArrayList 构造函数12345678// 默认构造函数ArrayList()// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。ArrayList(int capacity)// 创建一个包含collection的ArrayListArrayList(Collection<? extends E> collection) ArrayList 类图结构如下 源码解析ArrayList 中有两个重要的变量 private transient Object[] elementData; elementData 是一个Object的数组,JDK 实现了一个动态数组,可以动态的增加数组的大小,初始的大小是10。 private int size; size 标示了动态数组的实际大小 构造函数:1234567891011121314151617181920212223242526272829/** * 创建一个指定容量大小的空数组 */public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException(\"Illegal Capacity: \"+ initialCapacity); this.elementData = new Object[initialCapacity];}/** * 创建一个空数组 */public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA;}/** * 创建一个包含传递进来的元素的数组 */public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class);} add方法的实现 12345678910111213141516171819202122232425262728293031323334353637383940414243// 添加一个元素public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true;}// 确定容量private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);}private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;} ArrayList 默认的构造函数,创建的是一个空数组,是在第一次添加对象的时候做的初始化。 添加元素的时候如果容量不足,会进行扩容,新的容量会之前的1.5倍 最大的容量为 Integer.MAX_VALUE 遍历的三种方式ArrayList支持3种遍历方式 (01) 第一种,通过迭代器遍历。即通过Iterator去遍历。 12345Integer value = null;Iterator iter = list.iterator();while (iter.hasNext()) { value = (Integer)iter.next();} (02) 第二种,随机访问,通过索引值去遍历。由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。 12345Integer value = null;int size = list.size();for (int i=0; i<size; i++) { value = (Integer)list.get(i); } (03) 第三种,for循环遍历。如下: 1234Integer value = null;for (Integer integ:list) { value = integ;} ConcurrentModificationExceptionAbstractList 中有一个成员变量 modCount, 记录了数据在结构上变化的次数,比如添加或删除一个元素的次数12// The number of times this list has been structurally modifiedprotected transient int modCount = 0; 在Java中的fail-fast机制中有介绍过。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException。","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"Java Collection 架构","slug":"java基础和高级特性-02-java集合类之Collection-架构","date":"2017-09-07T09:52:03.000Z","updated":"2017-09-14T08:57:04.000Z","comments":true,"path":"2017/09/07/java基础和高级特性-02-java集合类之Collection-架构/","link":"","permalink":"http://yoursite.com2017/09/07/java基础和高级特性-02-java集合类之Collection-架构/","excerpt":"","text":"概要首先看一下 Collection 的类图结构如下: Collection 是一个接口,它主要有两个分支 List 和 Set。 List 和 Set 是两个接口,他们继承自 Collection ,List 是有序的队列,元素可以重复, Set 是不重复的的集合。 为了减少重复代码,jdk 抽象出了一个 AbstractCollection 抽象类,这个类实现了 Collection 中绝大部分函数,这样在其它的实现类中就可以省去重复的编码。 AbstractList 和 AbstractSet都继承于AbstractCollection,具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet。 Collection 类详解Collection 的定义如下:1public interface Collection<E> extends Iterable<E> { 包含的方法如下: List 详解接口声明: 1public interface List<E> extends Collection<E> { 包含的方法:  其中带 上箭头 是从 Collection 继承来的,相比于 Collection ,主要增加了一些在指定位置添加、删除、修改、获取对应元素的接口。还有获取List中的子队列。 Set 详解接口声明 1public interface Set<E> extends Collection<E> { 包含的方法: 关于API方面。Set的API和Collection完全一样。","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"一些好玩的前端项目","slug":"前端-一些好玩的前端项目","date":"2017-07-12T16:00:00.000Z","updated":"2017-09-15T02:44:01.000Z","comments":true,"path":"2017/07/13/前端-一些好玩的前端项目/","link":"","permalink":"http://yoursite.com2017/07/13/前端-一些好玩的前端项目/","excerpt":"","text":"web 代码编辑器https://ace.c9.io/#nav=howto&api=editor Features Syntax highlighting for over 110 languages (TextMate/Sublime Text.tmlanguage files can be imported) Over 20 themes (TextMate/Sublime Text .tmtheme files can be imported) Automatic indent and outdent An optional command line Handles huge documents (four million lines seems to be the limit!) Fully customizable key bindings including vim and Emacs modes Search and replace with regular expressions Highlight matching parentheses Toggle between soft tabs and real tabs Displays hidden characters Drag and drop text using the mouse Line wrapping Code folding Multiple cursors and selections Live syntax checker (currently JavaScript/CoffeeScript/CSS/XQuery) Cut, copy, and paste functionality web diff工具http://www.mergely.com/doc Mergely is a powerful online diff and merge editor and javascript library that highlights changes in text. It can be embedded within your own Web application to compare files, text, C, C++, Java, HTML, XML, CSS, and javascript. Download Mergely, and refer to the reference manual. Please refer to the the license agreement. Browser-based differencing tool Diff or Merge changes in your own web applications Always available Free and GPLv3 Commercial license available Share diffs online for demonstration or discussion Simple to use Mergely is written with the aid of Code Mirror and jQuery javascript libraries, and uses HTML5 canvas to markup the differences between documents. It will work in most modern browsers. 代码高亮http://codemirror.net/ CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and addons that implement more advanced editing functionality. Features Support for over 100 languages out of the box A powerful, composable language mode system Autocompletion (XML) Code folding Configurable keybindings Vim, Emacs, and Sublime Text bindings Search and replace interface Bracket and tag matching Support for split views Linter integration Mixing font sizes and styles Various themes Able to resize to fit content Inline and block widgets Programmable gutters Making ranges of text styled, read-only, or atomic Bi-directional text support Many other methods and addons… pdfjshttps://github.com/mozilla/pdf.js/ PDF.js is a Portable Document Format (PDF) viewer that is built with HTML5. PDF.js is community-driven and supported by Mozilla Labs. Our goal is to create a general-purpose, web standards-based platform for parsing and rendering PDFs. JSON Server && faker.jshttps://github.com/typicode/json-server Get a full fake REST API with zero coding in less than 30 seconds (seriously) https://github.com/marak/Faker.js/ generate massive amounts of fake data in Node.js and the browser 介绍两大神器!——使用json-server和faker.js模拟REST APIhttps://segmentfault.com/a/1190000008574028","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/tags/前端/"}]},{"title":"python 学习笔记","slug":"python-2013-09-26-python","date":"2017-07-06T08:02:32.000Z","updated":"2017-09-21T01:55:42.000Z","comments":true,"path":"2017/07/06/python-2013-09-26-python/","link":"","permalink":"http://yoursite.com2017/07/06/python-2013-09-26-python/","excerpt":"","text":"字符串、文件、目录、列表和网络的一些笔记 字符串相关实现字符串对齐123实现字符串对齐:左对齐、右对齐和居中对齐ljust、rjust、center,可以是一个或两个参数,第二个参数指定占位符'aaa'.ljust(20)或'aaa'.ljust(20,'=') 去掉字符串两端的空格1使用lstrip、rstrip、和strip方法 格式字符串12可以使用%s来格式化字符串,把存储在变量中的字符串片段连接起来s = '%s%s something %s yet more' % (a,b,c) 不要使用+或+=来拼接大的字符串,通常会造成性能问题12可以使用一个list作为中间数据结构来容纳他们,使用list的append或extend方法在末尾添加新的数据。在取得所有的数据之后再调用''.join(thelist)就可以得到合并之后的大字符串。 反转字符串 按字符反转 12a = 'hello'revchars = a[::-1] 按单词反转 12345可以先创建一个单词的列表,然后将列表反转,再用join方法将其合并list = str.split()revwords = ' '.join(list.reverse())或revwords = ' '.join(str.split()[::-1]) 分割字符串(split)1234>>> '1+2+3+4+5'.split('+')['1','2','3','4','5']如果不加参数,默认使用空格,包括空格、制表、换行等 列表定义:列表是Python中使用最频繁的数据类型【可以说没有之一】 关键词:有序,可变12345>一组有序项目的集合>可变的数据类型【可进行增删改查】>列表中可以包含任何数据类型,也可包含另一个列表【可任意组合嵌套】>列表是以方括号“ []”包围的数据集合,不同成员以“ ,”分隔>列表可通过序号访问其中成员 常见的列表操作声明&创建123456l = [] #空列表l = [1, 2, 3, 4]l = [1, 'a', [2,3] ]l = list('hello') #得到 ['h', 'e', 'l', 'l', 'o'] l = list(range(4)) #[0, 1, 2, 3]l = '1,2,3,4,5'.split(',') #['1', '2', '3', '4', '5'] 内建函数list(a_sequence) 可以将一个序列转为列表 通过下标访问12>>>l = [1, 2, 3, 4]>>>l[0] #1 增加元素1234567891011A.新加入一个元素appendappend方法添加。它在原列表末尾添加一个 item, item类型可以是任意的l = [1, 2, 3]l.append('hello') #得到 [1, 2, 3, 'hello']l.append(['hello']) #得到 [1, 2, 3, 'hello', ['hello']]B.插入一个元素insertl1 = [1, 2, 3]l1.insert(1,9) #[1, 9, 2, 3] 删除元素 A.按item的索引或切片删除123l1 = [1, 2, 3, 4, 5, 6]del l1[0] #得到[2, 3, 4, 5, 6]del l1[0:2] #得到[4, 5, 6] B.按item的值进行删除123l1 = [1,2,3,1,2]l1.remove(1) #得到[2,3,1,2]若是remove对应值查无,将跑ValueError C.删除某个位置并返回该位置值12345pop若是不传位置参数,默认删除列表最后一个元素l1 = [1, 2, 3, 4, 5]a = l1.pop(1) #a=2b = l1.pop() #a=5 修改元素 A.某个元素12l1 = [1, 2, 3, 4]l1[0] = 0 #[0,2,3,4] B.某一段元素1234l1= [1,2,3,4]l1[0:2] = [7,8,9] #[7,8,9,3,4]l1[:] = [] #清空了 os & shutilos 和 shutil模块常用操作12345678910111213141516171819202122232425262728293031323334353637os.sep 可以取代操作系统特定的路径分隔符。windows下为 '\\\\'os.name 字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是 'posix'os.getcwd() 函数得到当前工作目录,即当前Python脚本工作的目录路径os.getenv() 获取一个环境变量,如果没有返回noneos.putenv(key, value) 设置一个环境变量值os.listdir(path) 返回指定目录下的所有文件和目录名os.remove(path) 函数用来删除一个文件os.system(command) 函数用来运行shell命令os.linesep 字符串给出当前平台使用的行终止符。例如,Windows使用 '\\r\\n',Linux使用 '\\n' 而Mac使用 '\\r'os.path.split(path) 函数返回一个路径的目录名和文件名os.path.isfile() 和os.path.isdir()函数分别检验给出的路径是一个文件还是目录os.path.exists() 函数用来检验给出的路径是否真地存在os.curdir 返回当前目录 ('.')os.mkdir(path) 创建一个目录os.makedirs(path) 递归的创建目录os.chdir(dirname) 改变工作目录到dirname os.path.getsize(name) 获得文件大小,如果name是目录返回0Los.path.abspath(name) 获得绝对路径os.path.normpath(path) 规范path字符串形式os.path.splitext() 分离文件名与扩展名os.path.join(path,name) 连接目录与文件名或目录os.path.basename(path) 返回文件名os.path.dirname(path) 返回文件路径os.walk(top,topdown=True,onerror=None) 遍历迭代目录os.rename(src, dst) 重命名file或者directory src到dst 如果dst是一个存在的directory, 将抛出OSError. 在Unix, 如果dst在存且是一个file, 如果用户有权限的话,它将被安静的替换. 操作将会失败在某些Unix 中如果src和dst在不同的文件系统中. 如果成功, 这命名操作将会是一个原子操作 (这是POSIX 需要). 在 Windows上, 如果dst已经存在, 将抛出OSError,即使它是一个文件. 在unix,Windows中有效。os.renames(old, new) 递归重命名文件夹或者文件。像rename()# shutil 模块shutil.copyfile( src, dst) 从源src复制到dst中去。当然前提是目标地址是具备可写权限。抛出的异常信息为IOException. 如果当前的dst已存在的话就会被覆盖掉shutil.move( src, dst) 移动文件或重命名shutil.copymode( src, dst) 只是会复制其权限其他的东西是不会被复制的shutil.copystat( src, dst) 复制权限、最后访问时间、最后修改时间shutil.copy( src, dst) 复制一个文件到一个文件或一个目录shutil.copy2( src, dst) 在copy上的基础上再复制文件最后访问时间与修改时间也复制过来了,类似于cp –p的东西shutil.copy2( src, dst) 如果两个位置的文件系统是一样的话相当于是rename操作,只是改名;如果是不在相同的文件系统的话就是做move操作shutil.copytree( olddir, newdir, True/Flase)把olddir拷贝一份newdir,如果第3个参数是True,则复制目录时将保持文件夹下的符号连接,如果第3个参数是False,则将在复制的目录下生成物理副本来替代符号连接shutil.rmtree( src ) 递归删除一个目录以及目录内的所有内容 文件操作文件模式打开一个文件,返回一个文件对象。可以用open()或者file(),建议使用前者1234file_object = open(file_name, access_mode = ‘r’, buffering = -1) file_name:打开的文件名,若非当前路径,需指出具体路径mode:可选参数,文件打开模式 bufsize:可选参数,是否使用缓存 mode12345678910模式 描述r 以读方式打开文件,可读取文件信息.文件必须已存在w 以写方式打开文件,可向文件写入信息。存在则清空,不存在创建a 以追加方式打开文件,文件指针自动移到文件尾。追加r+ 以读写方式打开文件,可对文件进行读和写操作。w+ 消除文件内容,然后以读写方式打开文件。a+ 以读写方式打开文件,并把文件指针移到文件尾。b 以二进制模式打开文件,而不是以文本模式。该模式只对Windows或Dos有效,类Unix的文件是用二进制模式进行操作的U 通用换行符支持,任何系统下的文件, 不管换行符是什么, 使用U模式打开时, 换行符都会被替换为NEWLINE(\\n) bufsizebufsize取值 描述12340 禁用缓冲1 行缓冲,只缓冲一行大于1 指定缓冲区的大小,定制小于1 系统默认的缓冲区大小,m默认 文件对象属性123456file.name 文件名file.encoding文件使用编码,None 时使用系统默认编码file.mode Access文件打开时使用的额访问模式file.closed表文件已关闭,否则Falsefile.newlines未读取到分隔符时为None,包含行结束符的列表file.softspace为0表示在输出一数据后,加上一空格,1表示不加,内部使用 操作列表123456789101112131415161718#读file.read(size=-1) 从文件读取size个字节,未给定或为负,读取所有file.readline(size=-1) 读取并返回一行,或返回最大size个字符,包括\\nfile.readlines(sizeint=0) 读取所有行并返回列表,若给定sizeint>0,返回总和大约为sizeint字节的行, 实际读取值可能比sizhint较大, 因为需要填充缓冲区#写file.write(str) 向文件中写入字符串(文本或二进制)file.writelines(seq) 写入多行,向文件中写入一个字符串列表,注意,要自己加入每行的换行符#其他file.seek(off,whence=0) 从文件中给移动指针,从whence(0起始,1当前,2末尾)偏移off个字节,正结束方向移动,负往开始方向移动file.tell() 返回当前文件中的位置。获得文件指针位置file.truncate(size=file.tell()) 截取文件到最大size个字节,默认为当前文件位置file.close() 关闭打开的文件,垃圾回收机制也会在文件对象的引用计数降至0的时候自动关闭文件file.fileno() 返回文件描述符(file descriptor FD 整型)是一个整数, 可以用在如os模块的read方法等一些底层操作上.file.flush() 刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入.file.isatty() 判断file是否是类tty设备file.next() 返回文件下一行 最佳实践1.养成手动close123456789101112f = open('a.py')……f.close()``` python2.读取大文件方法一:一次性读入,去左右空白+换行符,文件太大不建议这么做``` pythonf = open('bigdata')lines = [ line.strip() for line in f.readlines()] …..f.close() 方法二:迭代12345f = open('bigdata')for line in f: line = line.strip()…..f.close() 3.上下文管理器用with,等价与上面方法二,注意不用显式close123>>> with open('a.py') as f:... for line in f:... line = line.strip() picklepickle任意python对象和字符串之间的序列化类似java序列化存储到文件的过程12345678910111213# encoding: utf-8import pickled = {'a':1,'b':2}f = open('datafile.pkl','wb')pickle.dump(d,f)f.close()f=open('datafile.pkl','rb')e=pickle.load(f)print e 其他相关模块123456789101112131415base64 二进制字符串和文本字符串之间的编码/解码操作binascii 二进制和ascii编码的二进制字符串间的编码/解码操作bz2 访问BZ2格式的压缩文件csv 访问csv文件(逗号分割文件)filecmp 用于比较目录和文件fileinput 提供多个文本文件的行迭代器getopt/optparse 提供了命令行参数的解析/处理glob/fnmatch 提供Unix样式的通配符匹配功能gzip/zlib 读写GNU zip(gzip)文件(压缩需要zlib模块)shutil 提供高级文件访问能力c/StringIO 对字符串对象提供类文件接口tarfile 读写TAR归档文件, 支持压缩文件tempfile 创建一个临时文件(名)uu 格式的编码和解码zipfile 用于读取ZIP归档文件的工具 参考:http://blog.csdn.net/wklken/article/details/6315514 Python 处理 ini 格式文件 | ConfigParser的使用ini文件格式概述ini 文件是文本文件,ini文件的数据格式一般为: 12345678[Section1 Name] KeyName1=value1 KeyName2=value2 ...[Section2 Name] KeyName1=value1 KeyName2=value2 ini 文件可以分为几个 Section,每个 Section 的名称用 [] 括起来,在一个 Section 中,可以有很多的 Key,每一个 Key 可以有一个值并占用一行,格式是 Key=value。 ConfigParser 类概述ConfigParser 可以用来读取ini文件的内容,如果要更新的话要使用 SafeConfigParser. ConfigParse 具有下面一些函数: 读取12345read(filename) 直接读取ini文件内容readfp(fp) 可以读取一打开的文件sections() 得到所有的section,并以列表的形式返回options(sections) 得到某一个section的所有optionget(section,option) 得到section中option的值,返回为string类型 写入 写入的话需要使用 SafeConfigParser, 因为这个类继承了ConfigParser的所有函数,而且实现了下面的函数:1set( section, option, value) 对section中的option进行设置 ConfigParser 使用例子1234567891011121314151617181920212223242526from ConfigParser import SafeConfigParserfrom StringIO import StringIO f = StringIO()scp = SafeConfigParser() print '-'*20, ' following is write ini file part ', '-'*20sections = ['s1','s2']for s in sections: scp.add_section(s) scp.set(s,'option1','value1') scp.set(s,'option2','value2') scp.write(f)print f.getvalue() print '-'*20, ' following is read ini file part ', '-'*20f.seek(0)scp2 = SafeConfigParser()scp2.readfp(f)sections = scp2.sections()for s in sections: options = scp2.options(s) for option in options: value = scp2.get(s,option) print \"section: %s, option: %s, value: %s\" % (s,option,value) 输出结果为: 123456789101112131415-------------------- following is write ini file part --------------------[s2]option2 = value2option1 = value1[s1]option2 = value2option1 = value1-------------------- following is read ini file part --------------------section: s2, option: option2, value: value2section: s2, option: option1, value: value1section: s1, option: option2, value: value2section: s1, option: option1, value: value1 os.path包含的函数1234>>> import os>>> import os.path>>> dir(os.path)['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_abspath_split', '_getfullpathname', 'abspath', 'altsep', 'basename', 'commonprefix', 'curdir', 'defpath', 'devnull', 'dirname', 'exists', 'expanduser', 'expandvars', 'extsep', 'genericpath', 'getatime', 'getctime', 'getmtime', 'getsize', 'isabs', 'isdir', 'isfile', 'islink', 'ismount', 'join', 'lexists', 'normcase', 'normpath', 'os', 'pardir', 'pathsep', 'realpath', 'relpath', 'sep', 'split', 'splitdrive', 'splitext', 'splitunc', 'stat', 'supports_unicode_filenames', 'sys', 'walk', 'warnings'] 获取路径名,文件名,扩展名12345678910111213>>> path = \"d:/test/aaa/demo.txt\"获取目录名>>> os.path.dirname(path)'d:/test/aaa'获取文件名带后缀>>> os.path.basename(path)'demo.txt'分割路径名和文件名>>> os.path.split(path)('d:/test/aaa', 'demo.txt')分割文件名和扩展民>>> os.path.splitext(path)('d:/test/aaa/demo', '.txt') random12345678910111213141516171819202122232425262728293031323334353637随机整数:>>> import random>>> random.randint(0,99)# 21随机选取0到100间的偶数:>>> import random>>> random.randrange(0, 101, 2)# 42随机浮点数:>>> import random>>> random.random()0.85415370477785668>>> random.uniform(1, 10)# 5.4221167969800881随机字符:>>> import random>>> random.choice('abcdefg&#%^*f')# 'd'多个字符中选取特定数量的字符:>>> import randomrandom.sample('abcdefghij', 3)# ['a', 'd', 'b']多个字符中选取特定数量的字符组成新字符串:>>> import random>>> import string>>> string.join( random.sample(['a','b','c','d','e','f','g','h','i','j'], 3) ).replace(\" \",\"\")# 'fih'随机选取字符串:>>> import random>>> random.choice ( ['apple', 'pear', 'peach', 'orange', 'lemon'] )# 'lemon'洗牌:>>> import random>>> items = [1, 2, 3, 4, 5, 6]>>> random.shuffle(items)>>> items# [3, 2, 5, 6, 4, 1] xrange 和 range12345678910111213141516171819202122232425262728293031323334353637383940414243444546range 函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。range示例:>>> range(5)[0, 1, 2, 3, 4]>>> range(1,5)[1, 2, 3, 4]>>> range(0,6,2)[0, 2, 4]xrange 函数说明:用法与range完全相同,所不同的是生成的不是一个数组,而是一个生成器。xrange示例:>>> xrange(5)xrange(5)>>> list(xrange(5))[0, 1, 2, 3, 4]>>> xrange(1,5) xrange(1, 5)>>> list(xrange(1,5))[1, 2, 3, 4]>>> xrange(0,6,2)xrange(0, 6, 2)>>> list(xrange(0,6,2))[0, 2, 4]由上面的示例可以知道:要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间,这两个基本上都是在循环的时候用:for i in range(0, 10): print ifor i in xrange(0, 10): print i这两个输出的结果都是一样的,实际上有很多不同,range会直接生成一个list对象:a = range(0,10)print type(a)print aprint a[0], a[1]输出结果:<type 'list'>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]0 1而xrange则不会直接生成一个list,而是每次调用返回其中的一个值:a = xrange(0,100)print type(a)print aprint a[0], a[1]输出结果:<type 'xrange'>xrange(100)0 1 python 实现ftp上传下载http://www.ifunsion.com/archives/2597 urllib & urllib21、Proxy 的设置urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。如果想在程序中明确控制 Proxy,而不受环境变量的影响,可以使用下面的方式123456789import urllib2enable_proxy = Trueproxy_handler = urllib2.ProxyHandler({\"http\" : 'http://some-proxy.com:8080'})null_proxy_handler = urllib2.ProxyHandler({})if enable_proxy: opener = urllib2.build_opener(proxy_handler)else: opener = urllib2.build_opener(null_proxy_handler) urllib2.install_opener(opener) 这里要注意的一个细节,使用 urllib2.install_opener() 会设置 urllib2 的全局 opener。这样后面的使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener 的 open 方法代替全局的 urlopen 方法。 2、Timeout 设置在老版本中,urllib2 的 API 并没有暴露 Timeout 的设置,要设置 Timeout 值,只能更改 Socket 的全局 Timeout 值。1234import urllib2import socketsocket.setdefaulttimeout(10) # 10 秒钟后超时urllib2.socket.setdefaulttimeout(10) # 另一种方式 在新的 Python 2.6 版本中,超时可以通过 urllib2.urlopen() 的 timeout 参数直接设置。12import urllib2response = urllib2.urlopen('http://www.google.com', timeout=10) 3、在 HTTP Request 中加入特定的 Header要加入 Header,需要使用 Request 对象:1234import urllib2request = urllib2.Request(uri)request.add_header('User-Agent', 'fake-client')response = urllib2.urlopen(request) 对有些 header 要特别留意,Server 端会针对这些 header 做检查User-Agent 有些 Server 或 Proxy 会检查该值,用来判断是否是浏览器发起的 RequestContent-Type 在使用 REST 接口时,Server 会检查该值,用来确定 HTTP Body 中的内容该怎样解析。常见的取值有:123application/xml :在 XML RPC,如 RESTful/SOAP 调用时使用application/json :在 JSON RPC 调用时使用application/x-www-form-urlencoded :浏览器提交 Web 表单时使用 在使用 RPC 调用 Server 提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致 Server 拒绝服务。 4、Redirecturllib2 默认情况下会针对 3xx HTTP 返回码自动进行 Redirect 动作,无需人工配置。要检测是否发生了 Redirect 动作,只要检查一下 Response 的 URL 和 Request 的 URL 是否一致就可以了。123import urllib2response = urllib2.urlopen('http://www.google.cn')redirected = response.geturl() == 'http://www.google.cn' 如果不想自动 Redirect,除了使用更低层次的 httplib 库之外,还可以使用自定义的 HTTPRedirectHandler 类。12345678import urllib2class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): passopener = urllib2.build_opener(RedirectHandler)opener.open('http://www.google.cn') 5、Cookieurllib2 对 Cookie 的处理也是自动的。如果需要得到某个 Cookie 项的值,可以这么做:12345678import urllib2import cookielibcookie = cookielib.CookieJar()opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))response = opener.open('http://www.google.com')for item in cookie: if item.name == 'some_cookie_item_name': print item.value 6、使用 HTTP 的 PUT 和 DELETE 方法urllib2 只支持 HTTP 的 GET 和 POST 方法,如果要使用 HTTP PUT 和 DELETE,只能使用比较低层的 httplib 库。虽然如此,我们还是能通过下面的方式,使 urllib2 能够发出 HTTP PUT 或 DELETE 的包:1234import urllib2request = urllib2.Request(uri, data=data)request.get_method = lambda: 'PUT' # or 'DELETE'response = urllib2.urlopen(request) 这种做法虽然属于 Hack 的方式,但实际使用起来也没什么问题。 7、得到 HTTP 的返回码对于 200 OK 来说,只要使用 urlopen 返回的 response 对象的 getcode() 方法就可以得到 HTTP 的返回码。但对其它返回码来说,urlopen 会抛出异常。这时候,就要检查异常对象的 code 属性了:12345import urllib2try: response = urllib2.urlopen('http://restrict.web.com')except urllib2.HTTPError, e: print e.code 8、Debug Log使用 urllib2 时,可以通过下面的方法把 Debug Log 打开,这样收发包的内容就会在屏幕上打印出来,方便我们调试,在一定程度上可以省去抓包的工作。123456import urllib2httpHandler = urllib2.HTTPHandler(debuglevel=1)httpsHandler = urllib2.HTTPSHandler(debuglevel=1)opener = urllib2.build_opener(httpHandler, httpsHandler)urllib2.install_opener(opener)response = urllib2.urlopen('http://www.google.com')","categories":[{"name":"python","slug":"python","permalink":"http://yoursite.com/categories/python/"}],"tags":[{"name":"python","slug":"python","permalink":"http://yoursite.com/tags/python/"}]},{"title":"git 基础用法和命令","slug":"工具-2013-05-11-git","date":"2017-07-06T08:02:32.000Z","updated":"2017-09-20T08:55:18.000Z","comments":true,"path":"2017/07/06/工具-2013-05-11-git/","link":"","permalink":"http://yoursite.com2017/07/06/工具-2013-05-11-git/","excerpt":"","text":"本文是《git权威指南》和《pro git》的读书笔记。主要是一些命令的记录和对一些概念的理解。讲解的非常详细,强烈推荐这两本书。 爱上Git的理由 每日工作备份 异地协同办公 现场版本控制 所谓现场版本控制,就是在客户现场或在产品部署的现场进行源代码的修改,并在修改过程中进行版本控制,以便在完成修改后能够将修改结果甚至修改过程一并带走,并能够将修改结果合并至项目对应的代码库中。 避免引入辅助目录 比如svn在每一个目录下都创建了.svn目录 git 起步三种状态git 管理项目时,文件流转的三个工作区域:git本地数据目录,工作目录以及暂存区域。任何一个文件在Git内部都只有三种状态: committed(已提交) modified(已修改) staged(已暂存) 常用命令git 配置git config可以用来配置和读取相应的工作环境变量。变量存放与三个地方: /etc/gitconfig文件:系统中对所有用户都使用的配置,配置时添加–system选项。 ~/.gitconfig:用户目录下的配置文件适用于该用户。配置时使用–global选项。 .git/config:当前项目的git目录中的配置文件,仅对当前项目有效。 命令示例: git config --global user.name "xiyang" git config --global user.email "sdlgxxy@gmail.com" git config --global color.ui true git config --global alias.co checkout git config --global alias.ci commit git config --global alias.st status git config --global alias.br branch git config --global core.editor "vim" git config --global merge.tool "vimdiff" git config --list 查看已有的配置信息 基础命令查看/添加/提交/删除/找回/重置修改文件 git help <command> # 显示command的help git show # 显示某次提交的内容 git show $id git co -- <file> # 抛弃工作区修改 git co . # 抛弃工作区修改 git add <file> # 将工作文件修改提交到本地暂存区 git add . # 将所有修改过的工作文件提交暂存区 git rm <file> # 从版本库中删除文件 git rm <file> --cached # 从版本库中删除文件,但不删除文件 git reset <file> # 从暂存区恢复到工作文件 git reset -- . # 从暂存区恢复到工作文件 git reset --hard # 恢复最近一次提交过的状态,即放弃上次提交后的所有本次修改 git ci <file> git ci . git ci -a # 将git add, git rm和git ci等操作都合并在一起做 git ci -am "some comments" git ci --amend # 修改最后一次提交记录 git revert <$id> # 恢复某次提交的状态,恢复动作本身也创建了一次提交对象 git revert HEAD # 恢复最后一次提交的状态 git init可以初始化版本库,执行git add 将文件放到暂存区,git commit后提交文件到版本库。 设置忽略某些文件如果某些文件无需纳入 git 的管理,也不希望他们总出现untracked列表中。可以创建一个.gitignore文件。 # 注释行 *.pyc # 忽略所有的pyc文件 !lib.pyc # lib.pyc除外 /TODO # 忽略根目录下的TODO文件 build/ # 忽略build目录下所有的文件 doc/*.txt # doc目录下所有的txt文件 git diffgit diff B A 比较里程碑B和里程碑A git diff A 比较工作区和里程碑A git diff --cached A 比较暂存区和里程碑A git diff 比较工作区和暂存区 git diff --cached 比较暂存区额HEAD git diff HEAD 比较工作区和HEAD 查看提交日志git log git log --pretty=fuller git log <file> # 查看该文件每次提交记录 git log -p <file> # 查看每次详细修改内容的diff git log -p -2 # 查看最近两次详细修改 git log --stat # 查看提交统计信息 暂存区git stash 保存当前的工作进度,会分别对暂存区和工作区的状态进行保存。 git stash list 显示进度列表 git stash pop 恢复最近保存的工作进度 git stash save "message" 保存工作进度的同时,指定说明 git stash drop [<stash>] 删除一个存储进度 git stash clear 删除所有存储的进度 git stash branch <btanchname> <stash> 基于进度创建分支 重置操作git reset / git rest HAED 使用HEAD执行的目录树重置暂存区,工作区不受影响 git reset --soft HEAD^ 工作区和暂存区不改变,但是引用向前回退一次 git reset HEAD^ 工作区不改变,暂存区回退到上一次提交之前 git reset --mixed HEAD^ 同上 git reset --hard HEAD^ 彻底撤销最近提交。引用回退到前一次,工作区和暂存区都改变。 git 本地分支管理查看/切换/创建和删除分支 git br -r # 查看远程分支 git br <new_branch> # 创建新的分支 git br -v # 查看各个分支最后提交信息 git br --merged # 查看已经被合并到当前分支的分支 git br --no-merged # 查看尚未被合并到当前分支的分支 git co <branch> # 切换到某个分支 git co -b <new_branch> # 创建新的分支,并且切换过去 git co -b <new_branch> <branch> # 基于branch创建新的new_branch git co $id # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除 git co $id -b <new_branch> # 把某次历史提交记录checkout出来,创建成一个分支 git br -d <branch> # 删除某个分支 git br -D <branch> # 强制删除某个分支 (未被合并的分支被删除的时候需要强制) 分支合并和rebase git merge <branch> # 将branch分支合并到当前分支 git merge origin/master --no-ff # 不要Fast-Foward合并,这样可以生成merge提交 git rebase master <branch> # 将master rebase到branch,相当于: git co <branch> && git rebase master && git co master && git merge <branch> o git 远程分支管理git pull # 抓取远程仓库所有分支更新并合并到本地 git pull --no-ff # 抓取远程仓库所有分支更新并合并到本地,不要快进合并 git fetch origin # 抓取远程仓库更新 git merge origin/master # 将远程主分支合并到本地当前分支 git co --track origin/branch # 跟踪某个远程分支创建相应的本地分支 git co -b <local_branch> origin/<remote_branch> # 基于远程分支创建本地分支,功能同上 git push # push所有分支 git push origin master # 将本地主分支推到远程主分支 git push -u origin master # 将本地主分支推到远程(如无远程主分支则创建,用于初始化远程仓库) git push origin <local_branch> # 创建远程分支, origin是远程仓库名 git push origin <local_branch>:<remote_branch> # 创建远程分支 git push origin :<remote_branch> #先删除本地分支(git br -d <branch>),然后再push删除远程分支 git 远程仓库管理git remote -v # 查看远程服务器地址和仓库名称 git remote show origin # 查看远程服务器仓库状态 git remote add origin git@github:robbin/robbin_site.git # 添加远程仓库地址 git remote set-url origin git@github.com:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址) git remote rm <repository> # 删除远程仓库 git blame文件追溯 删除版本库中没有引用的对象 如果不小心把一些无用的文件提交到暂存区,撤回后文件并没有从版本库中上删除,可以使用如下命令清理。 git fsck 查看版本库中包含的没有被任何引用关联的松散对象 git prune 清理这些对象 git 里程碑git tag <tagname> [<commit>] git tag -a <tagname> [<commit>] git tag -m <msg> <tagname> [<commit>] git tag -s <tagname> [<commit>] git tag -u <key-id> <tagname> [<commit>]","categories":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/categories/工具/"}],"tags":[{"name":"git","slug":"git","permalink":"http://yoursite.com/tags/git/"}]},{"title":"论工具的重要性","slug":"方法论-论工具的重要性","date":"2017-06-14T01:38:30.000Z","updated":"2017-09-14T14:55:33.000Z","comments":true,"path":"2017/06/14/方法论-论工具的重要性/","link":"","permalink":"http://yoursite.com2017/06/14/方法论-论工具的重要性/","excerpt":"","text":"最近看了一篇文章:《顶级程序员和普通程序员在思维模式上的5个区别!》 《The Effective Engineer》的作者在写书的过程中,为了了解那些顶级程序员和普通程序员的区别,采访了很多硅谷顶级科技公司的顶尖软件工程师。他发现这些给世界带来巨大影响的的工程师们至少有以下5个共同的思维模式: 1.勇于去研究你不懂的代码 2.精通代码调试(debug) 3.重视能够节约时间的工具 4.优化你的迭代速度 5.系统性的思考方式 其中第三条 重视能够节约时间的工具 带给我很多启发 曾经在Facebook担任技术总监的Bobby Johnson描述过,高效率的程序员都把时间花在制作工具上。很多人也认为工具是很重要的,但是他们并没有花时间去制作、整合自己的工具。但是,Jonson团队最出色的员工耗费了他们1/3的时间在工具制作上,这些工具可以用来发布代码,监控系统,以及能让他们花更少的时间去做更多事情。总之,不要花时间去做机器可以代替你去做的事情。 对于大部分初级工程师来说,大部分的工作内容都是一些重复的业务代码,如果只是被动的编写业务,可能很难有长进,很多人会抱怨整天就是写增删改查的代码,没有技术含量,不能学到新东西,殊不知工作中大部分就是这样的工作。 infoQ负责出品的王概凯写的<<聊聊架构>>第一篇关于生命周期最后有一段话: 非核心生命周期拆分出来成为服务后,这个服务不再仅仅给一个人使用,而变成了所有人能能够共享。非核心生命周期一旦拆分出来后,往往就变成了一个通用的服务,反而获得了更大的生命力,不再局限于原有的大生命周期。对非核心生命周期的掌握,慢慢也就成为了一个一个的职业。而原有的大生命周期则变的更加精简,可以更加专注于自己的核心生命周期活动,以节省更多的时间。 在日常的后台开发中,会有很多通用的需求,是每次遇到复制拷贝一份代码快速实现功能呢,还是可以抽取出一个公共服务或通用的组件提供给大家使用呢? 假如仅仅为了实现需求,正好自己以前写过类似或则网上有别人的代码,然后自己复制拷贝一份,长远来看其实是一直在做重复的工作,也很难有成长,假如同样的事情做了5遍,10遍充其量你也只是比别人熟练一下而已。 那工作中有哪些是通用的并且是可以抽取出来的功能呢?或者说是非核心生命周期。考虑如下的几个场景: 后台文件导出文件导出是一个通用的需求且很常见,在日常的开发中此类的需求和开发会非常频繁,常见的做法是引入POI的包,从数据库中获取到数据,然后调用POI的接口生成excel文件,输出给浏览器。 需要手工做一次java对象到excel列的映射,如果需求字段有变更,一般情况下需要修改代码上线。 假如导出的数据量特别大,后台也比较耗时,则同步的方式很容易造成超时,一般会做成异步导出,异步下载,则需要很多额外的开发,假如有多个系统都需要,那就需要在多个系统都实现一遍。 假如导出的服务在线上的应用中,大量频繁的导出会请求到数据库主库上,会给数据库造成很大的压力,这个时候需要做读写分离。假如需要占用很多内存,有可能影响线上业务,则需要对导出做服务拆分。 另外还有跨数据库访问的问题。是不是可以在接到这种需求的时候不需要写代码,基于配置式完成功能呢? 调度系统我们知道java中使用quartz可以很方便的配置定时调度,但是很多定时任务都要求在同一时间只能有一个任务执行。如何保证呢?方法有很多。但是是不是可以提供一个调度系统可以让各业务系统不需要都自己实现一套。 业务监控报警分布式系统中分布式事务问题是一个通用的问题,很难去保证在分布式系统下多个系统的一致性问题,一般业界的通用做法是保证数据的最终一致性。 但是如何发现业务的异常呢?比如订单在支付系统支付成功,但是订单平台支付状态还是未支付。 代码生成在很多的创业公司,或中小型项目开发的公司,会面临大量新系统的开发,每次搭建项目的基础代码都会非常耗费时间,而且里面有大量的配置文件和配置项。如何快速生成项目模板,省去基础的框架配置呢? 以上的四个场景在我们当前的公司都有独立的系统和工具去支撑对应的需求,每一个系统在不断的迭代过程中都焕发了自己更强大的生命力。","categories":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/categories/随笔/"}],"tags":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/tags/工具/"}]},{"title":"jsonp 解决跨域请求","slug":"前端-jsonp解决跨域请求","date":"2017-01-12T01:08:28.000Z","updated":"2017-09-15T03:19:12.000Z","comments":true,"path":"2017/01/12/前端-jsonp解决跨域请求/","link":"","permalink":"http://yoursite.com2017/01/12/前端-jsonp解决跨域请求/","excerpt":"","text":"什么是跨域请求浏览器的同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互。如果协议,端口和主机对于两个页面是相同的,则两个页面具有相同的源,否则就是不同源的。如果要在js里发起跨域请求,则要进行一些特殊处理了。 解决方案最简单的有两种解决方式: 可以把请求发到自己的服务端,再通过后台代码发起请求,再将数据返回前端。 使用jsonp 第一种需要在服务器端做额外开发,而且频繁修改也需要频繁的改服务器端,一般不建议使用。下面看一下使用jsonp解决跨域问题的解决方案: 什么是jsonpJSONP 和 JSON 看着很像,他们分别是什么呢? JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。对于JSON大家应该是很了解了吧,不是很清楚的朋友可以去json.org上了解下,简单易懂。 JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。 实现演示前端代码1234567891011121314$.ajax({ type: \"get\", async: false, url: \"http://wukong.iqdnet.cn/wukongbg/admin/monitorList/test\", dataType: \"jsonp\", jsonp: \"callbackparam\",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback) success: function(json){ console.log(json); alert(json); }, error: function(){ alert('fail'); } }); 服务器端在controller中定义一个方法 test 返回JSON的数据 123456789101112131415161718192021/** * test */@RequestMapping(value = \"test\",method= RequestMethod.GET)@ResponseBodypublic Object test(String callbackparam,HttpServletRequest request,MonitorListParams monitorListParams) { logger.info(\"find monitorList list.\"); System.out.println(System.getProperty(\"env\")); ModelResult modelResult = new ModelResult(ModelResult.CODE_200); ResultPage<MonitorList> resultPage = null; resultPage = monitorListService.getResultPage(monitorListParams); modelResult.setMessage(\"查询成功\"); modelResult.setResultPage(resultPage); String content = JSON.toJSONString(modelResult); content = callbackparam +\"(\"+content+\")\"; return content;} 效果 参考Jquery 文档","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"jsonp 跨域","slug":"jsonp-跨域","permalink":"http://yoursite.com/tags/jsonp-跨域/"}]},{"title":"HashMap 详解和源码解析","slug":"java基础和高级特性-06-深入研究java基础-HashMap","date":"2016-10-21T10:12:53.000Z","updated":"2017-09-14T08:31:51.000Z","comments":true,"path":"2016/10/21/java基础和高级特性-06-深入研究java基础-HashMap/","link":"","permalink":"http://yoursite.com2016/10/21/java基础和高级特性-06-深入研究java基础-HashMap/","excerpt":"","text":"概述123public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable 类图结构 http://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/ http://www.cnblogs.com/skywang12345/p/3323085.html","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"深入研究java.lang.Object类","slug":"java基础和高级特性-2016-10-21-java基础-深入研究java-lang-Object类","date":"2016-10-21T06:52:09.000Z","updated":"2017-09-21T06:08:53.000Z","comments":true,"path":"2016/10/21/java基础和高级特性-2016-10-21-java基础-深入研究java-lang-Object类/","link":"","permalink":"http://yoursite.com2016/10/21/java基础和高级特性-2016-10-21-java基础-深入研究java-lang-Object类/","excerpt":"","text":"Object类是所有类的基类尽管Object 类是一个具体类,但是设计他主要是为了扩展。他所有的非final方法(equals、hashcode、tostring、clone和finalize)都有明确的通用约定,因为他们被设计成是要被覆盖的。任何一个类,在覆盖这些方法的时候,都有责任遵守这些通用约定,如果不能做到这一点,其它依赖月这些约定的类(例如HashMap和HashSet)就无法结合该类一起正常工作。 Object包含如下API Object() 默认构造方法 clone() 创建并返回此对象的一个副本。 equals(Object obj) 指示某个其他对象是否与此对象“相等”。 finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 getClass() 返回一个对象的运行时类。 hashCode() 返回该对象的哈希码值。 notify() 唤醒在此对象监视器上等待的单个线程。 notifyAll() 唤醒在此对象监视器上等待的所有线程。 toString() 返回该对象的字符串表示。 wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。 wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。 wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。 方法详解equals 用来判断两个对象是否相等==与equals在Java中经常被使用,大家也都知道==与equals的区别:==表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址);equals表示的是对象的内容完全相同,此处的内容多指对象的特征/属性。 实际上,上面说法是不严谨的,更多的只是常见于String类中。首先看一下Object类中关于equals()方法的定义:jdk的默认实现是用 == 来实现的,直接比较的内存地址。 123public boolean equals(Object obj) { return (this == obj);} 由此可见,Object原生的equals()方法内部调用的正是==,与==具有相同的含义。既然如此,为什么还要定义此equals()方法? equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么? 如上,在object类中,此标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。 Java语言规范要求equals方法具有下面的特点: jdk 中的注释如下: 翻译过来有下面的几个规则: 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。 对于任何非空引用值 x,x.equals(null) 都应返回 false String 类重写了equals方法,如下:123456789101112131415161718192021222324252627282930313233343536/** * Compares this string to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * String} object that represents the same sequence of characters as this * object. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false;} toString 方法toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体: 123public String toString() { return getClass().getName() + \"@\" + Integer.toHexString(hashCode());} toString()方法相信大家都经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的。 getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。 如上例中的u1的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.objectsummary.User@27e。 因此:toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。 我们做实验打印某个类如下所示1234567891011121314User u1 = new User();u1.setName(\"u1\");User u2 = new User();u2.setName(\"u2\");System.out.println(\"u1--> \" + u1);System.out.println(\"u2--> \" + u2);输出u1--> com.xiyang.study.reflect.User@99fad95cu2--> com.xiyang.study.reflect.User@cee1149du1--> com.xiyang.study.reflect.User@99fad95cu2--> com.xiyang.study.reflect.User@cee1149d hashcode1public native int hashCode(); 以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 hashCode 的常规协定是: 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 以上这段官方文档的定义,我们可以抽取出以下几个关键点:1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;2、如果两个对象相同(x.equals(y)),那么这两个对象的hashCode一定要相同;3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;4、两个对象的hashCode相同,并不一定表示两个对象就相同,只能够说明这两个对象在散列存储结构中,他们“存放在同一个篮子里”。 再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。 结合HashMap的实现可以很好的理解这个概念。如下是HashMap的数据结构: table 数组代表了桶,每一个桶里可能会存放不止一个元素,如果两个对象的hash值相等,就会放到一个桶里,用链表存放。但是这两个对象并不一定相当,所以这也解释了为什么hashcode相等的两个对象并不一定相等。 既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。 以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现) 在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。 为什么重写了equals 还要重写 hashCode方法?在 hashcode 方法中有一个原则:如果两个对象相同,即x.equals(y)为true, 那么这两个对象的hashCode一定要相同。 假如你自己的类不重写hashCode,那就会使用 Object 类的 hashCode 返回这个类的hashcode值,Object默认返回这个对象存储的内存地址的编号,那么有可能会导致两个对象equals返回为true,但是却有不同的hashcode码。 如何重写equals 和 hashCode 方法不建议自己实现,借助IDE可以很容易的实现一个类的equals 和 hashCode方法,比如我们定义一个类如下:12345678910/** * Created by xiexiyang on 16/5/3. */public class Member{ private String name; private Integer age; private int status;} IDE 辅助生成的 equals 和 hashCode 如下所示:1234567891011121314151617181920212223@Overridepublic boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Member member = (Member) o; if (status != member.status) return false; if (name != null ? !name.equals(member.name) : member.name != null) return false; if (age != null ? !age.equals(member.age) : member.age != null) return false; return true;}@Overridepublic int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (age != null ? age.hashCode() : 0); result = 31 * result + status; return result;}注:上述hashCode()的重写中出现了result*31,是因为result*31 = (result<<5) - result。之所以选择31,是因为左移运算和减运算计算效率远大于乘法运算。当然,也可以选择其他数字。 notify notifyAll wait clone 都是native的方法,依赖于底层的实现","categories":[{"name":"java 基础","slug":"java-基础","permalink":"http://yoursite.com/categories/java-基础/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"java 线程","slug":"java基础和高级特性-java-线程","date":"2016-08-18T09:11:19.000Z","updated":"2017-12-27T01:22:37.000Z","comments":true,"path":"2016/08/18/java基础和高级特性-java-线程/","link":"","permalink":"http://yoursite.com2016/08/18/java基础和高级特性-java-线程/","excerpt":"","text":"基本概念进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位) 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。 多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行。 在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个,有兴趣看这里Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService ) Thread和Runnable的区别如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 总结:实现Runnable接口比继承Thread类所具有的优势:1):适合多个相同的程序代码的线程去处理同一个资源2):可以避免java中的单继承的限制3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类 线程状态转换 1、新建状态(New):新创建了一个线程对象。2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 线程调度1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。 Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。static int MIN_PRIORITY 线程可以具有的最低优先级,取值为1。static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。 Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。 2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。 3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。 4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。 5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。 6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。 注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向 常用函数说明①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) ②join():指等待t线程终止。使用方式。join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。Thread t = new AThread(); t.start(); t.join();为什么要用join()方法在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。不加join。③yield():暂停当前正在执行的线程对象,并执行其他线程。 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。 sleep()和yield()的区别 sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程 另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。 扩展阅读http://blog.csdn.net/evankaka/article/details/44153709 http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=416915373&idx=1&sn=f80a13b099237534a3ef777d511d831a&scene=0#wechat_redirect","categories":[{"name":"java 高级","slug":"java-高级","permalink":"http://yoursite.com/categories/java-高级/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"java 集合类概述","slug":"java基础和高级特性-01-java集合类概述","date":"2016-08-05T03:58:13.000Z","updated":"2017-09-14T08:32:09.000Z","comments":true,"path":"2016/08/05/java基础和高级特性-01-java集合类概述/","link":"","permalink":"http://yoursite.com2016/08/05/java基础和高级特性-01-java集合类概述/","excerpt":"","text":"在java中一切皆是对象,集合就是盛放对象的容器,根据不同的数据结构(集合、链表、队列、栈、数组、映射等)对应不同的集合类。java的集合类都在java.util包下。为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。本文暂不包含并发集合包。 java 集合主要可以分为如下4个部分:List列表、Set集合、Map映射和工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)。如下图所示: 整体的框架图如下: 梳理出主干我们常用的集合类如下: 最主要的两个接口 Collection 和 Map 1 、 Collections 包含了 List 和 Set 两种不同的数据集合。 List 是一个有序的队列,包含了常用的 ArrayList,LinkedList、Vector和Stack 类。 Set 是一个不允许重复元素的集合,包含有 HashSet、TreeSet。HashSet依赖于HashMap,TreeSet依赖于TreeMap 2、 Map 是表示键值对映射的结构。 AbstractMap 是一个抽象类,它实现了Map接口中的大部分API,而HashMap、TreeMap、WeakHashMap都是继承于AbstractMap。HashTable继承于Dictionary,但它实现了Map接口。 Arrays 和 Collections 是两个常用的工具类。","categories":[{"name":"java 集合","slug":"java-集合","permalink":"http://yoursite.com/categories/java-集合/"}],"tags":[{"name":"java","slug":"java","permalink":"http://yoursite.com/tags/java/"}]},{"title":"我的读书史","slug":"life-我的读书史","date":"2016-03-14T08:48:02.000Z","updated":"2017-09-14T10:31:19.000Z","comments":true,"path":"2016/03/14/life-我的读书史/","link":"","permalink":"http://yoursite.com2016/03/14/life-我的读书史/","excerpt":"","text":"一本好书,往往一开始会读的特别快,临到最后反而舍不得读完。读罢,故事里面的人和事,萦萦于脑畔,飘着飘着,慢慢的尘封于一处。 启蒙小的时候可供娱乐的东西很少,不像现在的小孩子在2、3岁的时候,父母就会给孩子买一些绘本启蒙,我是到了小学认字之后才开始看一些”课外书“,而且当时可供阅读的书也很少,好的书就更少了。 小学四五年级的时候,同学中开始流传一些课外书,比较正统的《格林童话》《安徒生童话》《一千零一夜》《十万个为什么》《伊索寓言》,大都是需要借同学的看,我看书比较快,这些书很快就看完了,看的多了发现很多神话故事框架大都很相似,那个时候自己无聊也会给妹妹编一些神话故事听。 那个时候学校里还流传着一些《故事会》《鬼故事》《十八层地狱》之类的一些书,书不大也很薄,一本书大约5毛钱的样子,大都是一些员外,书生小姐,妖狐之类的故事。当时也是饥不择食,几乎什么都看,当然这些书看的时候不能被大人看见,所以只能偷偷的看,有时候看的入迷到了晚上就拿手电筒躲在被窝中看书,所以导致现在眼睛近视加散光,离开眼镜整个世界都是模糊的。 即使这样仍然还是会有没书看的时候,记得以前的日历上每页都会有一些小故事,所以也会找些日历来看,报纸的中缝也会有很多小故事,自己也会拿来看,伯父家里有订阅的报纸,有时候去伯父家的时候,也会拿报纸过来找里面的故事看。 后来大伯父发现了我喜欢看书,当时他在我们小学当校长,暑假的时候就帮我从学校图书馆借了一些书回来。然后我就看到了一些传纪类的书《小兵张嘎》《铁道游击队》还有一些国外的故事书,名字不太记得了,但是故事都很好,印象比较深的是有一个是把玻璃的知识写到了一个童话故事中。如今伯父因为一次意外已经不在了,想来不胜伤感。 成长上了初中可以看得书籍开始变多了,语文老师也会鼓励大家看一些优秀的名著。这个阶段陆续看了《朝花夕拾》《骆驼祥子》《鲁滨逊漂流记》《格列佛游记》《家春秋》《四世同堂》《老人与海》等一些书,很多书看得并不太懂,当时看这些书大多只是因为他们是名著吧,不知道现在再读会有什么新的感想。 所幸爸爸妈妈并不怎么限制我读课外书,有时还会主动给我买一些,陆续让爸妈给买了四大名著来读《西游记》《水浒传》《三国演义》《红楼梦》,当时最喜欢的是《三国演义》,读罢写了一篇作文《蜀相》参加作文比赛获得了二等奖。 初中因为离家比较远,开始住校了,也开始有了自己的零花钱,然后每隔一两个星期我就会去学校的书店买课外书看,有时候和同学约着分别买不同的书,换着看。 这个时候开始迷上了金庸古龙,飞雪连天射白鹿,笑书神侠倚碧鸳,整个初高中,把金庸的小说看了不止一遍。古龙的小说风格和金庸完全不一样,然后数量也更多,印象比较深的有《楚留香传奇》《小李飞刀》《大地飞鹰》《流星蝴蝶剑》《七种武器》《陆小凤传奇》等,看的很多,忘得也多。千古书生侠客梦,金庸古龙伴随着他们给我们创造的武侠世界,应该也是70、80年代的一个整体时代记忆吧。 记得班上有一本韩寒的《三重门》,读完之后特别想读韩寒其它的书,由于没有买到,只得作罢。 初中的宿舍一晚上都不熄灯,我正好在上铺,灯下面,很多书都是趴在床上看完的,还得时刻提防查宿的老师。 高中以前的暑假是读书最好的时光,我家自小便没有地,下午的时候,搬一把椅子,坐在阴凉地,一读便是一整天。 这个时候也开始看一些历史传记,从哥哥家找来的《隋唐演义》《杨家将》《齐白石传》《周恩来传》《洪武皇帝朱元璋》等,上下五千年,瀚如烟海。 高中高中到了泰安去上学,自己能掌握的生活费更多了,然后也就有了更多的钱去买书看,从我们学校往西走有一个夜市,有一些卖书的书摊,大部分都是一些盗版的书,合集比较多,一本书很厚,字也特别小,但是对于学生来说价格相对合适,我在那边买过很多,由于大部分是盗版,大部分书到现在也都遗失了。 初中想读而没有读到的韩寒和郭敬明也是在这里买的,读了《三重门》《零下一度》《通稿2003》《梦里花落知多少》《幻城》《夏至未至》,不一样的作者,不一样的文字,不一样的人生,也讲述不一样的故事。而今郭敬明做了导演,吸金无数,韩寒做了赛车手。匆匆十几年,弹指一挥间。 这个时候也开始接触了一些外国文学,《巴黎圣母院》《茶花女》《双城记》《堂吉诃德》,最喜欢的当属《基督山伯爵》,快意恩仇,执剑江湖。 我读书读完一本,如果喜欢就会找这本书作者的其它书来读,一本好书,往往一开始会读的特别快,临到最后反而舍不得读完。读罢,故事里面的人和事,萦萦于脑畔,飘着飘着,慢慢的尘封于一处。 语文老师上课的时候给我们推荐了余华的《活着》,读完思绪良久,又买了一本余华文集,然后读了《许三观卖血记》《在细雨中呼喊》。然后从这开始喜欢看一些近现代文学。 张爱玲的《半生缘》《金锁记》《倾城之恋》,看张爱玲的文字很容易被吸引,穿越时空回到那个时候的旧上海去旁观一个又一个的故事,在书中演绎着一个又一个各自不同的人生。 余秋雨的《千年一叹》,看余秋雨的书感觉就很博学,总是能看到很多自己不知道的东西。 巴金的《家春秋》,这三本书读起来和《红楼梦》是有一点像的,都是大家族的盛衰,看到最后沧海桑田,世事变迁。其实每个人又何尝不是如此,如今每次回老家,路过破败的老屋,看到已经不属于自己的新屋,再也找不回的儿时伙伴,心里也不胜感叹。 《红楼梦》应该是我最喜欢的文学类书籍,最早接触是在初中,当时并没有看完,高中的时候看完了一遍,深陷其中,又读了刘心武的《刘心武揭秘红楼梦》周汝昌关于红学的系列书籍,记得还有一本《红楼真梦》,高中语文课老师让同学上去分享四大名著,我便上去分享了《红楼梦》不同于其它同学,讲了一些红学和曹雪芹特殊的写作手法,备受老师和同学的赞赏。毕业后来了北京,找了个周末去了趟北京的大观园,可惜很难和书中的大观园产生共鸣。直到现在,每隔几年都会重读一次红楼梦,每次又都有新的收获,看到不同的版本,总会有收藏的欲望。最近在看白先勇老师的《细说红楼梦》,收货颇多。 贾平凹的《废都》《高老庄》,贾平凹的文字当时读不太懂,也不是特别吸引,我所以就只看过这两本,硬着头皮看,讲的什么现在也都记不得了。 《穆斯林的葬礼》是我非常喜欢的另外一本书,不同于其他书籍的叙事风格,带你走进一个穆斯林的世界。 三毛的作品也是在这个时候接触,《哭泣的骆驼》《沙哈拉的故事》《梦里花落知多少》,到后来大学毕业买了三毛全集,三毛的文字总能带给我很多感动,娓娓道来,没有太多华丽的语言却都很真实。现在仿佛感觉她还是一个人在流浪着。 高中的作文如果写的不错会被老师印做范文,在级部中讲评,我的几篇作文也被老师选中,可惜原文都没有留下来了。当时的周记也都遗失了。 大学我们大学的图书馆建的非常漂亮,曾获得过鲁班奖,整体圆弧形的设计,从前面看像一本展开的书页,图书馆的前面是人工湖,后面是大片的草坪。我当时很喜欢去图书馆借书看,说实话,文学区好书并不是很多,大部分很旧,而且热门的书籍通常借不到。偶尔遇到一本,我也很少借出去,大都是一读起来便停不下了,从上午看到晚上,一本书也差不多就看完了。 初读《平凡的世界》对我的人生观和价值观都产生了很大的影响,相逢恨晚。少平的经历或多或少给我了我很多勇气,促使我在那些独自学习的夜晚,虽然看不到未来的方向,依然默默的坚持。 莫言的书也是在这个时候看的,当时莫言还没有得诺贝尔奖,记得有次去我们学校开讲座。《红高粱》《檀香刑》因为讲的是山东的故事,所以有些时候也倍感亲切。 高中以前买的书大部分都是盗版的,而且很多都是合集,字特别小,看的时间长很毁眼睛。大学的时候开始用手机看电子书,看的书类型也更广了。 大学看技术书和专业书相对多一点,其它就是为了消磨时间看了一些《鬼吹灯》等热门的网络文学。 毕业之后初读东野圭吾又是另外一片天地,最早读的《嫌疑犯X的献身》而后看了《白夜行》《解忧杂货铺》都能带来不一样的体验。 最早知道刘心武是高中读《红楼梦》的时候,当时买了很多研究红楼梦的书,而后看了《钟鼓楼》,由于是在北京,听着书中娓娓道来的后海,随着时间的流逝,感受北京和历史的厚重。 《读库》最早是在大学实验室接触的,最早的一本读库是刘老师拿到实验室的,而后一发不可收拾,如今大半个书架都摆满了读库出品的各种书。编剧张立宪江湖人称六哥,十年间不忘初心。 沈复的《浮生六记》一本小书,读过之后感触颇深,现存的总共只有四篇,读的文言文原版,最近出了一本白话版的,读起来却很难有古文的感觉,所以还是推荐读原文。 齐邦媛的《巨流河》一部家国苦难史,战争,苦难,时代变迁,沧海桑田,斗转星移。 野夫的书风格又是不一样,有着江湖人的豪迈,还有那个时代无需过多渲染就能打动人的人和事。《乡关何处》《身边的江湖》《1980年代的爱情》每一本书都值得一看。 柴静的《看见》,媒体人的语言,不带个人情感的给我们讲述她所看见的。很喜欢柴静的人和文字,后来发现读库每年的见面会六哥都会请柴静去做主持,一直想去趟现场,结果到现在也没有去成,而今柴姑娘已经不主持了。 最近一两年看了一些最近出的书《人类简史》《未来简史》是少有的值得推荐给其它所有人的书。读过《人类简史》感觉不过瘾又找来了《枪炮、疾病与革命》。 吴晓波的新书《腾讯传》读来会感觉到我们正在经历着最近的历史,或则你也能在创造者历史,十几年的时间,腾讯的发展历程,其中的波折和故事,又有几个人能讲的清全貌。 而后看了吴军老师三册的《文明之光》,感叹人类生命在整个浩瀚宇宙之渺小,整个人类历史又是如此短暂。近现代文明对文艺复兴有了更深刻的认识,也想更多的去了解关于那段历史。 最近买了威尔杜兰特的《世界的文明》,一个人50年,不知道何时自己可以读完。 后记大部分是文学书的一些记忆,很多书在历次搬家中都遗失掉了,有时候记起来读书的过程也是一段美好的记忆。","categories":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/categories/随笔/"}],"tags":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/tags/读书/"}]},{"title":"简化markdown写作中的贴图流程","slug":"工具-简化markdown写作中的贴图流程","date":"2015-11-13T08:02:00.000Z","updated":"2017-12-12T08:58:19.000Z","comments":true,"path":"2015/11/13/工具-简化markdown写作中的贴图流程/","link":"","permalink":"http://yoursite.com2015/11/13/工具-简化markdown写作中的贴图流程/","excerpt":"","text":"一直很喜欢用markdown写文章,但是在markdown中贴图很麻烦,记得最早的时候我会将图片保存到本地,然后在markdown中使用相对路径显示。后来可以将图片放到图床上,然后在文档中使用链接。 最早一直使用ST写markdown的文档然后用了一段时间为知笔记再后来发现了这篇文章 http://tianweishu.com/2015/10/16/simplify-the-img-upload-in-markdown/ 结合自己的实践,记录一下实现方案1、申请七牛的账号,这个就不多说了,这里给七牛做一个广告,标准版10G空间,API也简单好用,传送门2、创建 Alfred 工作流,实现流程的自动化 先放一个完成后的效果图: 简单的步骤就是: 创建一个 trigger,快捷键自己设置 创建一个 run script,选择语言为 python 创建一个 output 可以设置一个系统提醒,在完成之后右上角提示。 run script 的脚本如下 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970query = \"{query}\"# -*- coding: utf-8 -*-import osimport timefrom qiniu import Auth, put_filefrom AppKit import NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeTIFFaccess_key = '你自己的 AK' # AKsecret_key = '你自己的 SK-3' # SKbucket_name = 'lepfinder-wiki' # 七牛空间名q = Auth(access_key, secret_key)def upload_qiniu(path): ''' upload file to qiniu''' dirname, filename = os.path.split(path) key = 'markdown/%s' % filename # upload to qiniu's markdown dir token = q.upload_token(bucket_name, key) ret, info = put_file(token, key, path, check_crc=True) return ret != None and ret['key'] == keydef get_paste_img_file(): pb = NSPasteboard.generalPasteboard() data_type = pb.types() # if img file print data_type now = int(time.time() * 1000) # used for filename if NSPasteboardTypePNG in data_type: # png data = pb.dataForType_(NSPasteboardTypePNG) filename = '%s.png' % now filepath = '/tmp/%s' % filename ret = data.writeToFile_atomically_(filepath, False) if ret: return filepath elif NSPasteboardTypeTIFF in data_type: # tiff data = pb.dataForType_(NSPasteboardTypeTIFF) filename = '%s.tiff' % now filepath = '/tmp/%s' % filename ret = data.writeToFile_atomically_(filepath, False) if ret: return filepath elif NSPasteboardTypeString in data_type: # string todo, recognise url of png & jpg passif __name__ == '__main__': url = \"http://7xo9p3.com1.z0.glb.clouddn.com/markdown\" img_file = get_paste_img_file() if img_file: # has file ret = upload_qiniu(img_file) if ret: # upload success name = os.path.split(img_file)[1] markdown_url = \"\" % (url, name) # make it to clipboard os.system(\"echo '%s' | pbcopy\" % markdown_url) os.system('osascript -e \\'tell application \"System Events\" to keystroke \"v\" using command down\\'') else: print \"upload_failed\" else: print \"get img file failed\" 使用方法 command + control + shift + 4 完成截图 command + control + shift + V 完成图片上传到七牛 command + V 粘贴外链地址到指定位置(如果光标此时在编辑器中,这一步其实是自动的)","categories":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/categories/工具/"}],"tags":[{"name":"工具","slug":"工具","permalink":"http://yoursite.com/tags/工具/"},{"name":"markdown","slug":"markdown","permalink":"http://yoursite.com/tags/markdown/"}]},{"title":"意料之外的大雪","slug":"life-snow","date":"2013-03-19T16:00:00.000Z","updated":"2017-09-14T10:43:48.000Z","comments":true,"path":"2013/03/20/life-snow/","link":"","permalink":"http://yoursite.com2013/03/20/life-snow/","excerpt":"","text":"进入三月,北京的天气本来已经开始转暖,屋里的冷气也在这几天停止了供暖,有几天气温已经接近二十度,没想到从昨天中午开始,天却起了大雪,说是鹅毛大雪倒也不为过,下午下班的时候还在下雨加雪,我因为没有带伞,所以想等着小一点再走,一直到晚上8点反而越下越大。找了把同事的旧伞,凑合还可以用,就出门准备回去了,路上行人仍然不少,都是行色匆匆,估计都是下班往家里赶的人。我平时都是走路回家,大约30多分钟,今天由于天气不怎么好所以就坐公交回去,等车的时候一个女孩也没有带伞,一直在打电话。 今天早上起来推开门,才发现路上竟然积起了20厘米左右的雪,雪很白很软,树上挂满了晶光闪闪的雪花,一片银装素裹的世界。很多人在路边拍照,倒有了一种寒冬初雪的气氛。 已经有好多年不曾看到很厚又很白的雪,特别是最近很多年在外求学,大都是在城市里面,下雪的时候要么雪量很少,要么下了也就化掉了,要么就是根本没有时间和心情去外面赏雪玩雪。 上大一的时候,南方来的同学大都很期待冬天快点到来,因为可以看到雪,有些人可能从小到大都没有见过雪的世界是什么样子。不过那一年虽然雪也下了,但是赶上了多年不遇的大雪灾,南方受灾更为严重,过年回家家里下的雪一点也不比北边要少。记得我坐长途客车回去路上遇上好几个事故。 走在去公司的路上,还不是上班高峰,洁白的雪,踩上去沙沙作响,突然间就希望把自己想象成10几年前小学的时候下雪天去学校的场景,那时候天一下雪学校就会广播,让离家近的同学回家拿扫雪的工具,整个学校也开始集体大扫除,本是少年爱玩的年纪,扫雪的时候大家便免不了打打闹闹,然后校园里就会多上几个大雪堆和奇形怪状的雪人。记忆中那个时候天总是很蓝,雪也是洁白洁白。 不像城市,农村里面大片的农田,冬天下雪后很难化掉,一般会保留很长一段时间,极目望去,远山和一望无际的原野,到处都是洁白的雪,置身与其中,别有一番意境。 最近北京雾霾的天气越来越多,走在街上呼吸的空气都有一种沉重感,每次雨雪或大风过后空气会恢复过来一点,再隔一两日则又会变成昏昏暗暗的天气,真的希望有时候人可以慢一点下来,看看路边的风景,品味下静静的生活。","categories":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/categories/随笔/"}],"tags":[{"name":"大雪","slug":"大雪","permalink":"http://yoursite.com/tags/大雪/"}]},{"title":"追忆似水流年","slug":"life-childhood","date":"2013-03-11T16:00:00.000Z","updated":"2017-09-14T12:38:54.000Z","comments":true,"path":"2013/03/12/life-childhood/","link":"","permalink":"http://yoursite.com2013/03/12/life-childhood/","excerpt":"","text":"现在回想起来,儿时的记忆真的是最值得珍藏的一段时光,很多东西也都已经模糊不全,留下的只有一些片段,几点画面,但即使如此,淡忘的或许永远想不起,而留下的也将一直记着无法抹去。 西池塘小时候居住的村子叫“李所村”,地处丘陵,两面环山,村子的西边有一个很大的池塘,有一口很大的泉眼,据说是从北面山上过来的水源,夏天汛期水量尤足。向南则一条小河首先向东,途径学校,直贯南北,流经村中间的时候又有几眼泉,清而洌,水质自然也是极好,河边一口老井,方一平有余,石砌而成,深不足十米,经年不曾干涸,村中人多半饮此水为生,或挑或担,或用水车,每逢朝晚,络绎不绝。 河边多树,夏日凉风习习,绿影斑驳,多有姑婆河边洗衣,家常里短,喧闹声不绝于耳。几多顽童,嬉与水间,捕鱼戏水,无不尽兴而归,全身尽湿。 村里人管东西朝向的这一段小河叫做“下沟”,沟的北面则称为“沟北沿(崖)”,我的家便在村子的最北边,本是多年老屋,大部分的石头都不是整块的,听奶奶说是当时爷爷一点一点捡碎石垒起来的。后来大约上三年级的时候就在村子的东边盖了后来的新房子,之前的老屋也拆掉了。 苹果园村子的北边是一条水渠,顺着水渠的方向则是大片的苹果园,姑姑家跟我们在一个村里,那个时候姑姑家里承包了其中的一片苹果园,有的时候我就会和儿时伙伴一起去那个地方玩耍,现在想来,倒留下很多记忆。每年的四五月份是苹果开花的季节,很淡雅的白色,微带一些红色,置身与其中,微风阵阵,一股清香,自有一番乐趣。 苹果大约在10月份左右成熟,苹果快成熟的时候就需要有人看着,防止有人偷,在苹果园里有一间屋子,平时的时候表哥会在那边看苹果园,有些时候我也会过去,游荡与满是果子的果园,又红又大的苹果触手可及。很难想象一棵苹果树怎么会结那么多果子,很多枝条被压的弯弯的。快成熟的时候,一个个红红的苹果,甚是惹人喜欢。 收苹果的时候,尤其热闹,很多人会来帮忙。诺大的苹果园仿佛进了王母娘娘的蟠桃园,红红的果子挂满枝头,在太阳下面闪着金灿灿的光。苹果按照个头大小被分别装箱,装车,运向不知道哪里的远方。 清明春游春天,万物复苏,新芽初生。小学的时候,每逢清明节,学校每年都会组织去烈士扫墓,大约提前一个多月就要准备,学校的仪仗队也会开始训练,敲鼓的、打镲的、吹号的还有举旗的,好生威武。如果能被选入仪仗队,都会被同学们羡慕。除了仪仗队的同学,其他人就要忙着训练赞歌。 等到出发的那天,大家排好队,两人一排,便浩浩荡荡的出发了,我们那个时候上学的人多,队伍都能排出两三里路。会时不时的看到有些“传令兵”小跑着传递消息,颇有些战时行军的味道。 因为是春游,午饭是要在外边解决的,吃饭的时候大家拿出各自准备的食品,分而食之,多有一番乐趣在其中。 野味在农村,地里的很多东西都是可以吃的,比如野菜、蚕蛹、蚂蚱。春天,万物复苏,路边的榆树长出榆钱,路边开出野花,长出嫩绿的野菜苗,荠菜、蓬蓬菜采一些,即使用最原始的做法,也相当的可口,最近再会老家问起家里的老人还吃么,都说大部分都有农药不敢吃了,而且村里也少了小时候的那份闲适。父辈们都在外打工,祖辈们大多年龄都大了。 收麦子在我上小学的时候我们还有“麦假”,大约一个多星期左右,因为到了麦子成熟的季节,学校的老师家里也大都有地,需要回家收麦子,然后便会给我们放假。 这个时候山上的桑葚正好成熟,和同学结伴去山上采一些,算是很好的果子了。 夏日乘凉小的时候,村子里的电视还不是特别多,每到夏天傍晚的时候,日头渐落,大家就会走出家门来到外边乘凉。有的搬一个凳子,有的拿一床凉席,手里拎一个大蒲扇,夏天由于是刚收完麦子,村里的场还没有撤掉,所以会有很多开阔的地方,地也很平整,特别适合乘凉。 邻里拉拉家常,儿童追赶嬉闹,天上的星星很多,记忆中的月亮也是又大又圆。 打牌山东人是比较喜欢打牌的,小到五六岁小孩,老到五旬老人都能够在夏日午后围坐一起,打牌聊天,可能农村的娱乐活动本来就少。我说的牌就是指纸牌,玩的花样也很多,比如入门级的“拖拉机”(“也叫排火车”),再有最常玩的”升级“,以及”保皇“”够级“,每一种玩法除了需要的人数不同,规则也是不同,所需要的牌数也不同。 小学的时候一到放寒暑假我三姑家的表哥就会来我家这边住,那时候似乎大家都很有时间,哥哥们也都在家,很容易就凑够五六个人,一起围坐一起打牌也成为那时候的一件乐事。现在除了过年大家还都会回家,再围坐一起的机会越来越少,每个人也都有了自己的家庭,也都有了孩子。 游戏机最早见过的游戏机还是表哥用弹珠“换”来的,只能玩俄罗斯方块,躲障碍之类的游戏,但是当时也玩的不亦乐乎,再后来有一天大伯父家的堂哥拿来了一个能插卡玩游戏的学习机,才第一次玩到了双截龙之类的游戏,之后便久久不能忘,过了段时间便让爸妈给买了一个那时叫“小霸王”的学习机,也算是我们80年代孩子的一个时代记忆吧。那时候在学校除了交流游戏心得,便是打听谁有新的游戏卡然后互相交换玩,“魂斗罗”“超级玛丽”“雪人兄弟”“冒险岛”“影子传说”“坦克大战”,每一个名字现在听起来还是那么有亲切。 那时候玩游戏经常是一玩一天,只能两个人一起玩,有时候人多的时候就会交替着玩,谁输了谁就下。差不多过关的游戏都会想办法通关,还记得和表哥一起把魂斗罗通关,五爷爷喜欢玩坦克,那时候我们爷俩可以玩一整天,不知爷爷是否还记得。 小学在我四年级之前还仍然在村里的小学上学,叫“李所小学”,原本是德国人在1910年左右建的教堂,在村子的最中心的位置,大小有几十间房子,有很大的院子,课件的时候异常热闹。 后来我上四年级的时候再村子的东边建了“东平成才希望小学”,有两座2层和一座3层的教学楼。这个学校是一个华侨赞助创建的,记得刚成立的时候还给我们学校的每个同学发了棉衣,文具。","categories":[{"name":"随笔","slug":"随笔","permalink":"http://yoursite.com/categories/随笔/"}],"tags":[{"name":"小时候","slug":"小时候","permalink":"http://yoursite.com/tags/小时候/"}]}]}