第一章 深入Web请求过程
B/S网络架构概述
- 浏览器发起一个URL请求的过程
- CDN内容分布网络
如何发起一个请求
- 发起一个Http请求的过程就是建立一个Socket通信的过程,只不过outputStream.write写的二进制字节数据格式要符合Http协议
- 浏览器、HttpClient、Linux curl命令都能发起Http请求
Http解析
- Http请求头:Accept-CharSet, Accept-Encoding, Accept-Language, Host, User-Agent
- Http响应头:Server,Content-Type, Content-Encoding, Content-Language, Content-Length, Keep-Alive
- Http状态码:200,302,400,403,404,500
- 浏览器缓存机制:Cache-Control/Pragma
DNS域名解析
- DNS域名解析过程
1) 浏览器缓存
2) 操作系统缓存
3) LDNS本地域名服务器
4)Root Server根域名服务器 -> gTLD Server 主域名服务器 -> Name Server 域名注册服务器(域名-IP映射表)
5)LDNS缓存结果并返回给用户 - 几种域名解析方式
1)A记录:Address记录,域名解析成IP地址
2)CNAME:Canonical Name,别名解析,域名解析成另外一个域名
3)MX记录:Mail Exchange,邮件服务器地址解析
4)NS记录:为某个域名指定DNS解析服务器
CDN工作机制
- Content Delivery Network,内容分布网络,以缓存网站中的静态数据为主,如CSS/JS/图片/静态页面等数据,目的是加速网页数据内容的下载速度。
- 主要技术手段是镜像服务器(Mirror)+ 高速缓存(Cache)+全局负载均衡DNS解析。
- 实现方式是在用户和源服务器之间加入一层CDN缓存服务器;CDN对域名解析过程进行了调整,通过全局负载均衡DNS解析,能够根据地理位置信息,解析出最近的CDN服务器地址;用户端向CDN缓存服务器发起请求,缓存服务器从源服务器得到内容之后,一方面在本地保存,以备之后使用,另一方面把数据返回客户端,完成数据服务过程。
- 负载均衡
1)链路负载均衡:如上面所提,通过DNS解析成不同的IP地址(就近原则),用户根据这个IP地址访问就近的目标服务器。
2)集群负载均衡:硬件负载均衡(需要一台昂贵的设备)、软件负载均衡(最普遍的方式,使用廉价的PC就可以搭建,缺点是一次访问需要经过多次代理服务器,增加网络延时;代理服务器的负载均衡算法如基于IP的轮询算法)
3)操作系统负载均衡:利用操作系统级别的软中断或硬中断达到负载均衡,如设置多队列网卡。
第二章 Java I/O的工作机制
Java的I/O类库的基本架构
- 基于字节操作的IO操作接口:InputStream和OutputStream
- 基于字符操作的IO操作接口:Reader和Writer
- 基于磁盘操作的IO接口:File
- 基于网络操作的IO接口:Socket
- 字节与字符的转化接口
1)InputStreamReader,从字节到字符的转化桥梁,从InputStream到Reader的过程需要指定解码字符集,否则采用操作系统默认的字符集,可能会出现乱码问题。由StreamDecoder完成解码过程。
功能:从inputStream读取字节,并以字符格式,存储到内存;inputStream可以是文件流,控制台
构造器参数:new InputStreamReader(InputStream in, String charSet)
方法:read(char[] memory)
2)OuputStreamWriter类完成从字符到字节的编码过程,由StreamEncoder完成编码过程。
功能:将字符写入到outputStream中;outputStream可以是文件流,控制台
构造器参数:OutputStreamWriter(OutputStream out, String charset)
方法:write(String s)
磁盘I/O工作机制
- 访问文件的方式
1)标准方式:read()/write() - 用户地址空间(应用缓存)- 内核地址空间(高速页缓存)- 物理磁盘
2)直接I/O方式:应用程序直接访问磁盘数据,如数据库管理系统
3)同步访问文件、异步访问文件
4)内存映射方式:将内核地址空间的某块内存和磁盘中的文件关联起来,当访问内核地址空间的数据时,转换为访问文件的某一段数据 - Java访问磁盘文件:FileInputStream对象作为InputStreamReader构造器的参数
- Java序列化技术:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据达到持久化的目的。对象需要持久化,必须继承java.io.Serializable接口。反序列化时,需要有原始类作为模板,才能将这个对象还原。如果序列化的属性是对象,则这个对象的也必须实现Serializable接口,否则会报错。
网络I/O工作机制
- TCP状态转化
1)Client端的状态:Closed,SYN-SENT,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,TIME-WAIT
2)Server端的状态:Closed,LISTEN,SYN-RECEIVED,ESTABLISHED,CLOSE-WAIT,LAST-ACK
3)三次握手,四次挥手 - Java Socket的工作机制:上层应用程序-Socket实例-TCP/UDP端口-IP
- 数据传输:每个Socket实例都有一个inputStream和outPutStream,操作系统会为之分配一定大小的缓存区,Client和Server都是通过这两个对象来交换数据。
NIO的工作方式
- BIO(阻塞IO):对于每个请求都必须启用一个线程进行处理
- NIO的工作机制(非阻塞IO):一个线程可以处理多个请求
- Buffer的工作方式
- NIO的数据访问方式:FileChannel.transferXXX,FileChannel.map
- NIO实现原理://www.greatytc.com/p/dd05b079e308
通道、缓存、选择器 - Reactor设计模式:https://www.cnblogs.com/crazymakercircle/p/9833847.html
IO调优
- 磁盘IO优化
- TCP网络参数调优
- 网络IO优化
1)同步与异步:同步就是一个任务的完成需要依赖另外一个任务的完成,异步则没有依赖关系
2)阻塞与非阻塞:阻塞就是IO线程遇到慢的IO操作,仍然占用CPU时间片;非阻塞就是遇到慢的IO操作,释放CPU占有权,让CPU执行其它任务。
设计模式:适配器模式
- 结构:Target目标接口,Adaptee适配接口,Adapter适配器类
- 目的:Target接口和Adaptee接口往往有着类似功能的方法,但是方法的参数不一致;现想将Target接口适配到Adaptee接口,即想在实现了Target接口的Adapter对象中调用Adaptee接口的方法。最终的好处是实现了功能的重用。
- 实现:Adapter适配器类,实现了Target接口,同时继承Adaptee源接口。
- Java IO应用举例:InputStreamReader继承了Reader抽象类,创建它们的对象必须在构造器函数中传入一个InputStream实例。这样InputStreamReader就可以调用read()方法将字节读入内存,并以字符的形式存储。InputStreamReader通过StreamDecoder类间接持有InputStream对象,并实现byte到char的编码。
设计模式:装饰器模式
- 结构:Component(组件接口)、ConcreteComponent(组件实现)、Decorator(装饰器,拥有Component实例)、ConcreteDecorator(装饰器实现)
- 举例:Shape接口(draw方法)、Triangle/Circle/Rectangle(实现了draw方法)、ShapeDecorator(接收Component实例作为构造器参数)、RedShapeDecorator(继承ShapeDecorator,重写draw方法,除了调用super.draw方法,再调用red方法)
- 实现:Decorator类通过接收Component实例作为构造器参数,ConcreteDecorator继承此类,重写方法;
- 目的:增强原有对象的功能、或者改变原有对象的处理方法而提升性能
- Java IO应用举例:InputStream抽象类(组件)、FileInputStream类(组件实现)、FilterInputStream类(装饰器)、BufferdInputStream(装饰器实现)。
第三章 Java Web的中文编码问题
几种常见的编码格式
- ASCII码:128个字符,一个字节表示
- ISO-8859-1:扩展的ASCII码,256个字符,一个字节表示
- GB2312:中文编码字符集,682个符号,6763个汉字,英文:单字节,中文:双字节
- GBK:汉字内码扩展规范,扩展的GB2312编码,21003个汉字。
- UTF-16:左右字符都用两个字节表示(16bit),定长表示
- UTF-8:变长技术,以0开头的字节,表示ASCII字符;以11开头的字节,表示某个字符的首字节,连续的1的个数表示这个字符的字节数。如110xxxxxx代表它是双字节字符的首字节;以10开头,表示它不是首字节,需要向前查找才能找到当前字符的首字节。
在Java中需要编码的场景
- IO操作中存在的编码
- 内存操作中存在的编码
在Java Web中涉及的编解码
- URL的编解码
- Http Header的编解码
- POST表单的编解码
- Http Body的编解码
在JS中的编码问题
- 外部引入JS文件
- JS的URL编码:escape(),encodeURI(),encodeURIComponent()
- Java与JS的编解码问题
第四章 Javac编译原理
Javac编译器的基本结构
- 词法分析器
- 语法分析器
- 语义分析器
- 代码生成器
Javac工作原理分析
- 词法分析器:默认实现类是com.sun.tools.javac.parser.Scanner类,逐个读取Java源文件的字符,解析出符合Java语言规范的Token序列:Token.PACKAGE,Token.IDENTIFIER,Token.SEMI,Token.PUBLIC,Token.CLASS,Token.LBRACE,Token.INT。
- 语法分析器:将Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话。
- 语义分析器:在语法树的基础上再做一些处理,添加默认的构造函数,检查变量在使用前是否已经初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有语句是否可达。
- 代码生成器:com.sun.tools.javac.jvm.Gen遍历语法树,生成最终的Java字节码。
设计模式:访问者模式
- 访问者模式最大的优点就是增加访问者非常容易,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。如果不实用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用 if-else 和类型转换,这使得代码难以升级维护。
- 具体事例://www.greatytc.com/p/1f1049d0a0f4
第五章 深入class文件结构
JVM指令集
- 与类相关的指令:instanceof,new
- 与方法相关的指令:invokeinterface,invokespecial(super.init,init,private),invokestatic,invokevirtual
- 与类属性相关的指令:getField,getstatic,putField,putstatic
- 与栈操作相关:dup(复制栈顶并入栈),pop(弹出栈顶元素),swap
- 与本地变量(方法的局部变量)相关:aload_0,dload_0,iload_0
- 与运算相关:dadd,isub
- 与常量操作相关:dconst_0, ldc(x)
- 与Java控制指令相关:dreturn,goto
- 与Java数据类型转换相关:d2f,d2i
- 与Java同步操作相关:monitorenter,monitorexit
- 与Java数组操作相关: aaload,aastore,anewarray
class文件结构
- cafebabe + major version + minor version
- 常量池:常量类型有UTF8,Integer,Class,String,Fieldref,Methodref,Interfaceref,NameAndType,互相嵌套表示
- 类描述信息:类访问控制信息,类名称,父类名称,接口数量,方法数量,成员变量数量
- Method定义:方法访问控制信息,方法名,方法返回类型和方法参数,方法实现,LineNumberTable(字节码指令与源码行号对应),LocalVariableTable(说明局部变量作用域)
- Field定义:类似Method
- 类属性
第六章 深入分析ClassLoader工作机制
ClassLoader类结构分析
- defineClass:将byte字节流解析成JVM能够识别的Class对象
- findClass:找到类的字节码,然后调用defineClass获取类的Class对象;用户可覆盖此方法实现自己的类加载规则
- loadClass:获取类的Class对象,实现了双亲委派机制(findLoadedClass ? -> parent.loadClass ? -> bootstrap.loadClass ? -> findClass)
- resolveClass:链接类;
ClassLoader的等级加载制度(双亲委派机制)
- 启动类加载器(Bootstrap ClassLoader):负责加载 %JAVA_HOME%/jre/lib/rt.jar
- 扩展类加载器(Extension ClassLoader):负责加载 %JAVA_HOME%/jre/lib/ext/*.jar
- 应用程序类加载器(Application ClassLoader):或者称为系统类加载器(System ClassLoader)。它负责加载用户类路径(ClassPath)上所指定的类库。
- App ClassLoader继承Extension ClassLoader,两者均继承URLClassLoader,URLClassLoader实现了ClassLoader接口
- 隐式加载(类的继承和引用)和显示加载(代码调用)
- 双亲委派机制:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
- 双亲委派机制的优点:避免同名类导致的混乱。比如用户自己编写java.lang.Object类,并放在程序的classpath中,那系统将会出现多个不同的Object类,Java类型体系中最基础的行为就无法保证。
- 双亲委派机制的补缺机制:使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。用于解决上层的基础类调用回用户代码的问题,比如JNDI接口调用JNDI服务实现代码(即Service Provider Interface代码)。JNDI服务可以使用线程上下文加载器去加载用户的SPI代码类。
如何加载class文件
- 加载:加载字节码到内存(findClass)
- 验证:字节码验证
- 准备:类数据结构准备和内存分配
- 链接:符号表链接
- 初始化:静态属性,类属性的初始化
常见的加载类错误
- ClassNotFoundException:当JVM要加载指定类文件的字节码到内存时,没有找到这个类文件对应的字节码。检查当前classpath路径下有没有指定的文件存在。当前classpath:this.getClass().getClassLoader().getResource("")
- NoClassDefFoundError:某个使用类继承或引用某个类,这个类不存在,会导致抛出此类错误,同时也会抛出ClassNotFoundException。
Tomcat类加载机制
- Tomcat目录结构
- /common/*:类库可被Tomcat和所有的Web应用程序共同使用
- /server/*:类库可被Tomcat使用,对所有Web应用程序不可见
- /shared/*:类库可被所有Web应用程序使用,对Tomcat自己不可见
- /WEB-INF/*:仅仅可以被此Web应用程序使用,对Tomcat和其它Web应用程序不可见
-
Tomcat服务器的类加载架构
- Common ClassLoader:加载/common/*所有类
- Catalina ClassLoader:加载/server/*所有类
- Shared ClassLoader:加载/shared/*所有类
- WebApp ClassLoader:可有多个WebApp ClassLoaer的实例,每个实例代表一个Web应用程序,加载对应程序的/WEB-INF/*类。
- JsperLoader:可有多个Jsp类加载器,每个JSP文件对应一个Jsp类加载器。这也是为什么jsp文件可实现热加载,不需要重启服务器就可以替换类。通过卸载原先的JSP的类加载器和对应的JSP文件,然后重新构建一个新的JSP类加载器,并加载新的JSP文件。
- Tomcat破坏了双亲委派机制:WebApp ClassLoader直接加载自己应用程序的/WEB-INF/*类。
实现类的热部署
- JVM判断是否是同一个类:全类名+ClassLoader实例
- 热部署:卸载原ClassLoader实例,创建新的ClassLoader实例对象,并加载同名类。
第七章 JVM体系结构与工作方式
JVM体系结构
- 类加载器:在JVM启动时或者类运行时将需要的class(类的元数据)加载到JVM的方法区;每个被JVM装载的类型都有一个对应的java.lang.Class类的实例来表示该类型,该实例可以唯一表示被JVM装载的class类,存放在Java的堆中。
- 执行引擎:基于栈的执行引擎(hotspot),基于寄存器的执行引擎(Dalvik);基于栈或寄存器指的是一个指令中的操作数是存在栈中,还是寄存器中。
- 内存区:方法区(Perm)和堆,Java栈和PC寄存器,本地方法栈(C栈);其中方法区和堆是线程共享的。
- 本地方法调用:调用C或者C++实现的本地方法的代码返回结果
JVM工作机制
- JVM为何选择基于栈的架构
1)JVM要设计成与平台无关的,而平台无关性就是要保证在没有或者很少寄存器的机器上同样能正确的执行;
2)为了指令的紧凑性,因为Java字节码可能在网络上传输,需要字节码指令尽量对齐,提高压缩率。 - 执行引擎的架构设计
1)每当创建一个新的线程,JVM会为这个线程创建一个Java栈,同时会为这个线程分配一个PC寄存器;
2)线程每调用一个新方法,会在栈上创建一个新的栈帧数据结构,每个栈帧会保留对应方法的一些元信息,比如局部变量、常量池的解析、正常方法返回和异常处理解析;其中关于常量池的解析,需要访问方法区。
第八章 JVM内存管理
物理内存与虚拟内存
- 物理内存:通常所说的RAM(随机存储器)
- 虚拟内存:虚拟内存可以被映射到一段物理内存、磁盘文件或者其他可以寻址的存储上。如Linux系统的swap区就是一块磁盘存储,当系统的物理内存不够用时,可以将物理内存中一部分释放出来,供当前应用程序使用。被释放的物理内存来自于长时间没有操作的程序,被释放空间中的数据保存在swap区。虚拟内存的存在使得多个进程同时运行时可以共享物理内存、扩展了内存空间。
内核空间与用户空间
- 内核空间主要指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者连接硬件资源等程序逻辑。用户空间指用户程序可以使用的空间。
- 用户程序不能直接访问硬件资源,如网络连接等,可以调用操作系统提供的接口来实现。每一次系统调用都会存在两个内存空间的切换,通常的网络传输也是一次系统调用,通过网络传输的数据先是从内核空间接收远程主机的数据,然后从内核空间复制到用户空间。
Java中哪些组件需要使用内存
- Java堆:-Xms和-Xmx选项控制初始空间和最大空间。在JVM启动时就一次性向操作系统申请完成。
- 线程:每个线程创建时,JVM都会为它创建一个堆栈,通常在256k~756k之间。
- 类和类加载器:类加载器本身、类加载器加载的类存储在方法区(Perm区,永久代)。如果Perm区不能对已经失效的类做卸载,可能会导致Perm区的内存泄漏。类卸载条件:没有类加载器实例的引用、该类加载器实例加载的所有类的Class实例和所有对象不再存活。这个条件相当苛刻,所以JVM的3个默认类加载器Bootstrap ClassLoader,ExtClassLoader,AppClassLoader都不可能满足这些条件。
- NIO:NIO使用java.io.ByteBuffer.allocateDirect()方法分配内存,即NIO direct memort,使用的是本机内存而不是Java堆上的内存
- JNI:Java Native Interface,Java字节码调用C/C++方法的技术,使用native memory。比如文件操作、网络IO操作或者其他系统调用,会依赖JNI代码。
JVM内存结构
- 堆:每个存储在堆中的Java对象都是这个对象的类的一个副本。
- 方法区(包括运行时常量池):存储类的Class信息(即Class文件中的常量池、类信息、Method定义、Field定义)、类的静态变量
- Java栈:每个线程都有自己的栈,每个栈中含有多个栈帧(一个方法对应一个栈帧)。栈中主要存放一些基本类型的变量数据和对象应用。
- PC寄存器:记录哪个线程执行到哪条指令
- 本地方法栈(C栈):为JVM运行Native方法准备的空间,包括JIT技术将Java方法重新编译得到的Native Code。
JVM内存分配策略
- Java栈的内存分配和线程绑定:一个线程的方法的调用和返回对应于Java栈的栈帧的压栈和出栈。
- 栈中主要存放一些基本类型的变量数据和对象引用。存取速度比堆快,仅次于寄存器。缺点是栈中的数据大小和生存期必须是确定的。
- 栈中的slot可重用,因为局部变量有作用范围,所以slot数可以小于局部变量数;slot在32位系统中为32bit,一个long型或double型占2个slot;
- 堆中存放所有类实例和数组,由所有线程共享。对象的引用在栈中分配。堆的优势是可以动态的分配内存大小,生存期也不必告诉编译器。缺点是运行时动态分配内存导致的存取速度慢。
- 堆中的对象存储信息
1)对象头:运行时数据和类型指针,运行时数据包括Hash码,GC分代年龄,锁状态标志,线程锁等
2)实例数据:代码中定义的各类型字段,包括继承父类的
3)对齐填充:Hotspot内存管理要求对象起始地址、对象大小、对象头大小必须是8字节的整数倍,否则需要填充 - 堆主要用来存放对象,栈主要用来运行程序。
JVM内存回收策略
- 静态内存分配与回收:静态内存指的是被编译时就能够确定需要的内存空间,主要包括类和方法中的原生数据类型(byte, short, int, long, char)和对象引用。静态内存空间在程序被加载时一次性分配,代码运行结束时收回。
- 动态内存分配与回收:动态内存指的是在程序执行时才知道要分配的存储空间大小,主要指实例对象。具体内存大小只有在运行时,确定对象的具体类型,解析对应的类才知道这个类中有哪些字段及其类型,才能分配空间。对象的回收也是不确定的,由垃圾收集器确定。
- 如何检测垃圾:只要某个对象不再被其他活动对象引用,这个对象就可以被回收。具体来说就是是否能够被根对象集合中任意一个对象引用。根对象集合包括:
1)方法局部变量区对象引用;
2)Java栈中的对象引用;
3)方法区常量池中的对象引用;
4)本地方法中的对象引用; - Hotspot基于分代的垃圾回收算法
1)分代:Young区,Old区,Perm区;Young区分为Eden区和Survivor区(From和To);
2)Eden区满:内存中新new的对象会被放入到Eden区,若Eden区满,将触发minor GC,将Eden区和From区中仍活的对象移动至To区,From区和To区角色对调,保证始终有个Survivor区是空的。如果Survivor区(To区)存放不下这些对象,GC收集器会将这些对象直接放到Old区。
3)Old区满:Old区除了储存上面所说的minor GC后survivor区存放不下的对象,还会存放Eden区过老的对象,即经过多次minor GC仍然存活的对象。若old区满,将会触发Full GC,回收整个堆内存(包括Young区,Survivor区,Perm区)。
4)Perm区满:Perm区存放的主要是类的Class对象,如果加载的类对象过多,也可能会导致Perm区满,也会触发Full GC。 - Hotspot提供的几类垃圾收集器
1)Serial GC:单线程串行回收;JVM client模式下的默认选项;核心思想是上面的分代垃圾回收算法;
额外细节有:在触发Minor GC前会检查之前每次Minor GC晋升到Old区的平均占用空间是否大于现在Old区的剩余空间,如果是直接触发Full GC。
2)ParNewGC:新生代GC实现,SerialGC的多线程版本;
3)ParallelGC:JVM server模式下的默认选择,吞吐量优先的GC,算法和Serial GC相似,特点是老年代和新生代GC并行进行,更加高效。通过ParallelOldGC参数可以选择老年代的GC是否并行进行;
4)CMS GC:Concurrent Mark Sweep GC,基于标记-清除算法,尽量减少停顿时间;存在碎片化问题,长时间运行则会导致Full GC。 - GC参数集合
Heap堆配置:-Xms,-Xmx,-Xmn,-XX:PermSize, -XX:MaxPermSize,-XX:SurvivorRatio
GC组合参数:-XX:+UseSerialGC,-XX:+UseParNewGC,-XX:+UseParallelGC,-XX:+UseConcMarkSweepGC等
GC算法参数:-XX:MaxTenuringThreshold,默认为15,表示15次minorGC仍存活晋升到old区
内存问题分析
- GC日志分析,jstat工具
- 堆快照文件分析:jmap -dump
- JVM Crash日志分析
第九章 Servlet工作原理分析
Servlet容器
- Servlet容器种类:Jetty,Tomcat等
- Tomcat容器模型:Tomcat,Engine,Host,Context,Wrapper
- Servlet容器启动过程:https://blog.csdn.net/dcrose/article/details/79729754
以如下Tomcat的嵌入式例子描述Tomcat容器的启动过程:
Tomcat tomcat = new Tomcat();
File appDir = new File(getBuildDirctory(), "webapps/examples");
// Host host, String contextPath, String docBase
tomcat.addWebapp(null, "/example", appDir.getAbsolutePath());
// 启动tomcat
tomcat.start();
// 调用Servlet
ByteChunk res = getUrl("http://localhost:" + getPort() + "/examples/servlets/servlet/HelloWorldExample");
assert(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
1)添加web应用:tomcat.addWebapp
创建一个与此web应用对应的StandardContext容器,并将此容器添加到父容器Host中;创建StanardContext容器之后,会为此容器添加一个LifecycleListener对象:contextConfig,此contextConfig负责整个web应用的配置解析工作。
2)启动tomat容器:tomcat.start()
Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle接口,它管理着容器的整个生命周期,所有容器的修改和状态的改变都会由它通知已经注册的观察者(Listener)。所以Tomcat容器的启动,会一层层激发子容器的LifecycleEvent。Tomcat启动类的时序如下:Tomcat,StandardServer,StandardService,StandardEngine,StandardHost,StandardContext,ContextConfig,Connector,Http11Protocal,MapperListerner;我们需要重点关注StandardContext容器的启动过程。
3)StandardContext容器的启动过程
执行ContextConfig的init方法:解析配置文件
解析默认的context.xml配置文件
解析默认的Host配置文件
解析默认的Context自身的配置文件
设置Context的DocBase
执行StandardContext的startInternal方法:
创建读取资源文件的对象
创建ClassLoader对象
设置应用的工作目录
启动相关的辅助类,如logger,realm,resources等
修改启动状态
子容器初始化
获取ServletContext并设置必要的参数
初始化"load-on-startup > 0" 的Servlet
执行ContextConfig的configureStart方法:完成Web应用的初始化工作
解析各级web.xml文件,包括globalWebXml,hostWebXml,web应用的web.xml。
然后将解析的对象属性设置到Context容器中,包括创建Servlet对象、filter、listerner。
Servlet对象被包装成StandardWrapper并作为子容器添加到Context容器中。
Servlet工作原理
- Servlet对象的创建时间和生命周期:https://zhuanlan.zhihu.com/p/60921461
tomcat启动时DefaultServelt和JspServlet这两个默认的Servlet就会被启动(globalWebXml)。 - Servlet体系结构:https://blog.csdn.net/SYJ_1835_NGE/article/details/83585577
- Servlet容器对url的匹配过程:https://blog.csdn.net/makao45683968/article/details/83402611
- Servlet中的listener
- Servlet中的filter
第10章 深入理解Session与Cookie
- 什么是cookie
1)当用户通过Http访问一个服务器,这个服务器会将一些Key/Value键值对返回给客户端浏览器;当用户下次访问这个服务器时,数据又被完整地带回给服务器。
2)cookie就是存储在客户端的一小段文本,cookie的作用是为了实现客户端与服务器之间状态的保持。 - cookie应用场景
1)网站登录,记录用户的登录信息;
2)网页的个性化展示:当你访问了某些网页,并且对网页的一些设置进行修改,cookies就能跟踪并记录到这些修改,当你下一次访问这个网页的时候,这个网页会分析你电脑上的cookies,进而采取措施像你返回更符合你个性化的网页;
3)cookie能够记录用户的浏览行为,目前大部分广告的定位基础也是基于cookies的,比如你此前访问了大量的健身类网站,cookies记录了你的访问行为,广告主就能够根据你的访问行为,向你推送健身类的广告。 - cookie属性项
1)NAME=VALUE:键值对
2)Expires/Max-Age:过期时间
3)Domain,记录此cookie是用户访问哪个domain时返回的信息
4)Path,该Cookie是用户访问哪个路径下生成的 - cookie如何工作
1)服务器端创建Cookie:Response.addCookie,Response.generateCookieString,Response.addHeader
2)从客户端获取Cookie:当我们请求某个URL路径时,浏览器会根据这个URL路径将符合条件的Cookie放在Request请求头中传回给服务端,服务器端通过request.getCookies来获取所有Cookie。 - 使用Cookie的限制
Cookie是Http头中的一个字段,浏览器对Cookie的大小和数量有限制,一般为每个域名下的数量不超过50个,总大小不超过4kb; - 什么是Session
NAME为JSESSIONID的一个Cookie。解决了Cookie很多增加客户端与服务器端的数据传输量的问题;同一个客户端每次与服务器端交互时,不需要每次都传回所有的Cookie值,只要传回一个ID,这个ID是客户端第一次访问服务器时生成,客户端只要传回这个ID就行了。如果客户端禁用cookie,可以将JSESSIONID作为path的参数传递到服务端。 - Session如何工作
1)request.getSession()方法触发:如果当前的SessionId还没有对应的HttpSession对象,服务器端根据SessionID创建HttpSession对象,并将对象加入到sessions容器中保存。只要这个HttpSession对象存在,用户就可以根据SessionId来获取这个对象,也就做到了对状态的保持。Tomcat中session有效时间是60s,过期对象将被清除。
2)Servlet容器关闭,会持久化保存容器中的Session对象到文件中;重启时再解析; - Cookie安全问题
Cookie在浏览器端可访问和修改,不安全;Session是将数据保存在服务端,较安全,更适合存储用户隐私和重要的数据。 - 分布式Session框架
1)目的
为了解决cookie的数量和大小限制,以及安全问题;
2)功能
session和cookie的统一配置和管理:服务订阅服务器;
容灾机制,服务器共享session和cookie:分布式缓存服务器;
监控和报警支持;
跨域名Session和Cookie的共享:需要一个跳转应用,次应用可被多个域名访问 - 相关介绍
https://blog.csdn.net/hxfghgh/article/details/82840613 - Cookie压缩
如果Cookie量非常大,可以考虑对cookie进行压缩和编码,可节省带宽成本。 - 表单重复提交问题
这里的重复请求指的是一次表单请求,发了两次(网速慢/恶意程序)。
防止表单重复提交,服务端根据请求表单页面,生成唯一token,存储到用户Session中,返回给客户端。用户再次请求时,服务端验证这个token和Session中的token是否一致。如果一致,说明没有重复提交。 - 多终端session统一
1)多终端共享Session:分布式Session框架
2)多终端登录(app扫码登录):服务端设置标识位。
第11章 Tomcat的系统架构与设计模式
tomcat的总体结构
参考:https://www.cnblogs.com/alimayun/p/10604532.html
1)Connector组件:负责接收浏览器发过来的TCP连接请求,创建一个Request和Response对象分别用于和请求端交换数据。然后产生一个线程处理这个请求,处理这个请求的线程就是Container组件要做的事情。
2)Container组件:Servlet容器
Container是容器的父接口,由4个呈父子关系的子容器组件构成,分别是Engine、Host、Context、Wrapper容器。
3)Service服务:Service用于关联Connector和Container。一个Service可以包含多个Connector和一个Container,这样Connector在获取客户端的socket之后,交给对应的Service,由Service来找到对应的Container,进而处理客户端相关的请求。Container容器具体介绍
1)Engine容器:top容器,没有父容器;
2)Host容器:Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的作用就是提供多个域名的服务。
3)Context容器:Context表示应用本身,管理这个应用里面的所有Servlet实例,它具备了Servelt运行的基本环境。Servlet实例在Context中是以Wrapper出现的。
4)Wrapper容器:Wrapper代表一个Servlet,它负责管理一个Servlet,包括Servlet的装载、初始化、执行及资源回收。Tomcat中的设计模式
1)门面设计模式
优点一:这种设计模式主要用在一个大的系统中有多个子系统时,通过向用户提供一个门户,使得用户只需要通过这个门户来获取他们想要的数据或者进行他们想要的操作,而无需考虑这个大系统内部的子系统构成。
https://zhuanlan.zhihu.com/p/98952478
优点二:门面设计模式是数据隔离的好办法。多个子系统之间往往需要进行相互通信,但是每个子系统又不能将自己的内部数据过多地暴露给其他系统,所以为子系统设计一个门面,把别的系统感兴趣的数据和操作封装起来,通过这个门面来进行访问。
//www.greatytc.com/p/e74bbd9de09d
2)观察者设计模式
通常也叫发布-订阅模式,即事件监听机制,通常在某个事件发生的前后会触发一些操作。Tomcat中控制容器组件生命周期的Lifecycle就是这种模式的体现。
//www.greatytc.com/p/dcb676b58b7c
3)命令设计模式
命令模式的主要作用就是封装命令,把发出命令的责任和执行命令的责任分开,也是一种功能的分工。不同的模块可以对同一个命令做出不同的解释。Tomcat中Connector是通过命令模式调用Container的。
https://blog.csdn.net/ZuoAnYinXiang/article/details/50457169
4)责任链设计模式
责任链模式就是很多对象由每个对象对其下家的引用而连接起来形成一条链,请求在这条链上传递,直到链上的某个对象处理此请求,或者每个对象都可以处理请求,并传给下家,直到最终链上每个对象都处理完。在Tomcat中这种设计模式几乎被完全地使用了,Tomcat的容器设置就是责任链模式,从Engine到Host再到Context一直到Wrapper都通过一个链传递请求。
//www.greatytc.com/p/58dc4580aa71
https://blog.csdn.net/ZuoAnYinXiang/article/details/50457233
第12章 Jetty的工作原理解析
Jetty的基本架构
整个Jetty的核心由Server和Connector两个组件构成:
1)Server组件:基于Handler容器工作,类似于Tomcat的Container容器
2)Connector组件:负责接收客户端的连接请求,并将请求分配给一个处理队列去执行
整个Jetty所有组件的生命周期也是基于观察者模式设计的,它和Tomcat的管理类似,基于LifeCycle接口工作。
https://developer.ibm.com/zh/articles/j-lo-jetty/Handler的体系结构
主要有两种Handler类型:
1)HandlerWrapper,它可以将Handler委托给另一个类去执行;配合ScopeHandler,可以拦截Handler的执行,在Handler执行的前后做另外一些事情。
2)HandlerCollection,可将多个Handler组装在一起,构成一个Handler链,方便我们做扩展。Jetty的启动过程
Jetty的入口是Server类;Jetty中所有组件都会继承LifeCycle,所以Server的start方法就会调用所有已经注册到Server的组件,Server启动其他组件的顺序是:首先启动设置到Server的Handler,然后依次启动这个Handler的子Handler链,接着启动注册在Server的JMX的Mbean,最后启动Connector,打开端口,接收客户端请求。接收请求
Jetty可以作为一个独立的Servlet引擎,提供web服务,基于Http协议工作;也可以与其他Web应用服务器集成,如JBoss服务器,基于AJP协议工作。
1)基于HTTP协议工作:创建队列线程池、创建ServerSocket、创建监听线程
2)基于AJP协议工作:Http的解析工作在外层的Apache或Nginx服务器已经完成了,后面都是基于AJP协议工作了。AJP处理请求相比于HTTP唯一的不同就是读取到Socket数据包时如何来转换这个数据包。
3)基于NIO方式工作:Jetty的connector默认是基于NIO方式工作。其实Jetty仍用一个线程来监听客户端的连接请求,把这个请求再注册到Selector上,然后才以非阻塞的方式来执行。处理请求
Jetty收到一个请求时,就把这个请求交给在Server中注册的代理Handler去执行。接下来是Handler链的层层调用,基于责任链的设计模式。与JBoss集成
1)Jetty可以基于AJP协议工作,在正常的企业级应用中,Jetty作为一个Servlet引擎都是基于AJP工作的,所以它前面必然有一个服务器,通常情况下雨JBoss集成的可能性非常大。
2)JBoss是基于JMX架构(//www.greatytc.com/p/8c5133cab858)的,所以只要符合JMX规范的系统都可以集成进来,Jetty当然也支持。与Tomcat的比较
1)架构比较
从架构上来说Jetty比Tomcat简单。Jetty是面向Handler的架构,它的所有组件都是基于Handler来实现的;很容易被扩展和剪裁;Tomcat是以多级容器构建起来的,相比之下,Tomcat臃肿很多,整体设计比较复杂
2)性能比较
在不同的场景下它们表现得各有差异。Tomcat在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短,Tomcat的总体性能更高。Jetty刚好相反,它可以同时处理大量连接而且可以长时间保持这些连接。例如一些Web聊天应用非常适合使用Jetty做服务器,淘宝的Web旺旺就用Jetty作为Servlet引擎。
3)特性比较
Jetty对新的Servlet规范的支持速度更快,因为它修改起来更加简单。
第13章 Spring框架的设计理念与设计模式分析
Spring框架中的核心组件
1)Bean
位于org.springframework.beans包下,此包主要解决Bean的定义、Bean的创建、Bean的解析。
Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory。
Bean的解析主要是对Spring配置文件的解析。
Bean的定义完整地描述了在Spring的配置文件中你定义的<bean/>节点中所有的信息。
当Spring成功解析你定义的一个<bean/>节点后,在Spring的内部它就被转化成BeanDefinition对象,以后所有的操作都是对这个对象进行。
2)Context
位于org.spring.framework.context包下,对Context来说就是要发现每个Bean之间的依赖关系,为它们建立并维护好这种依赖关系,所以Context就是一个Bean关系的集合,即Ioc容器。实际上它就是给Spring提供一个运行时的环境,用以保存各个对象的状态。ApplicationContext是Context的顶级父类。
3)Core
Core就是发现、建立、维护每个Bean之间的关系所需要的一系列工具,其中包含了很多关键类,一个重要的组成部分就是定义了资源的访问方式。Ioc容器如何工作
Ioc容器实际上是Context组件结合其他两个组件共同构建了一个Bean关系网。
1)如何创建BeanFactory工厂
2)如何创建Bean实例并构建Bean的关系网
3)Ioc容器的扩展点
主要有BeanFactoryPostProcessor和BeanPostProcessor,它们分别在构建BeanFactory和构建Bean对象时调用。还有就是InitializingBean和DisposableBean,它们分别在Bean实例创建和销毁时被调用。用户可以在这些实现在这些接口中定义的方法。
4)Ioc容器如何为我所用
ApplicationContext.xml就是Ioc容器默认的配置文件,Spring的所有特性功能都是基于Ioc容器工作的,如AOP。AOP的实现就是Spring本身实现了其扩展点达到了它想要的特性功能。
https://www.cnblogs.com/linjiqin/archive/2013/11/04/3407126.html
https://blog.csdn.net/qq_39530375/article/details/84533693
- Spring中AOP的特性详解
1)动态代理的实现原理
详细介绍://www.greatytc.com/p/9bcac608c714
动态代理demo:
HelloInterface hello = new Hello();
InvocationHandler invocationHandler = new ProxyHandler(hello);
HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), invocationHandler);
proxyHello.sayHello();
被代理对象:hello,实现了HelloInterface的sayHello()方法;
代理对象:proxyHello,由Proxy.newProxyInstance方法构造,接收参数为ClassLoader、被代理的接口、方法调用处理器;ClassLoader用于加载代理类,通常和被代理类是同一个ClassLoader;被代理的接口即HelloInterface;方法调用处理器是一个实现了InvocationHandler接口的类,代理对象调用被代理接口中的方法时,实际上就是由invocationHandler对象调用InvocationHandler接口中唯一的方法invoke()去处理。
方法调用处理器:ProxyHandler类接收被代理对象作为构造器的唯一参数,实现了InvocationHandler接口的invoke()方法。此方法就是所有被代理对象方法的实际执行程序,通过反射调用被代理对象的方法,并可以在方法调用前后加入我们的逻辑,这也是代理的最终目的。
public class ProxyHandler implements InvocationHandler{
private Object object;
public ProxyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
}
2)Spring AOP如何实现
https://www.cnblogs.com/xuyatao/p/8485851.html
https://juejin.im/post/5af3bd6f518825673954bf22
设计模式-代理模式
代理模式就是给某一个对象创建一个代理对象,由这个代理对象控制原对象的引用,而创建这个代理对象后可以在调用原对象时增加一些额外的操作。设计模式-策略模式
通常是指完成某个操作可能有多种方法,这些方法各有千秋或者各有适用的场合。把各个操作方法都当做一个实现策略,使用者可以根据需要选择合适的策略。Spring中的策略模式的应用有AOP代理的实现,有JDK动态代理和CGLIB代理,JDK动态代理适用于目标类实现了接口,而CGLIB代理适用于目标类没有实现任何接口。
第14章 Spring MVC的工作机制与设计模式
Spring MVC的总体设计
- 搭建一个简单的Spring MVC应用
在web.xml中配置一个DispatcherServlet,再定义一个dispacherServlet-servlet.xml配置文件,一个简单的基于Spring MVC的应用就创建成功。 - DispacherServlet类
继承HttpServlet,执行Spring MVC的初始化工作:
1)initMultipartResolver:用于处理文件上传服务
2)initLocaleResolver:处理应用的国际化问题,即字符编码问题;
3)initThemeResolver:定义用户页面样式主题;
4)initHandlerMapppings:定义用户设置的请求映射关系,默认的HandlerMappings有BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。
5)initHandlerAdapters:用于根据Handler的类型定义不同的处理规则,即用户的业务处理逻辑。默认的有HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,ThrowawayControllerHandlerAdapter和AnnotionMethodHandlerAdapter。
6)initHandlerExceptionResolvers:handler处理出错,统一交由这个Handler来处理。默认为SimpleMappingExceptionResolver。
7)initRequestToViewNameTranslator:将指定的ViewName按照定义的RequestToViewNameTranslator替换成想要的格式,比如加入前缀或后缀。
8)initViewResolvers:用于将View解析成页面。默认的解析策略有:根据JSP来解析(InternalResourceViewResolver),或者Velocity模板解析(VelocityViewResolver),或者FreeMarker模板解析(FreeMarkerViewResolver)。 - Spring MVC组件
核心组件为:HandlerMapping,HandlerAdapter,ViewResolver;
其他组件:MultipartResolver,ThemeResolver,HandlerExceptionResolver,LocaleResolver,RequestToViewNameTranslator。 - Spring MVC初始化流程
https://blog.csdn.net/z695284766/article/details/80677434 -
总体架构图
https://www.cnblogs.com/kanglijun/p/10553994.html
Control设计
Spring MVC的Control主要由HandlerMapping和HandlerAdapters两个组件提供。HandlerMapping负责映射用户的URL和对应的处理类,HandlerMapping并没有规定这个URL与应用的处理类如何映射,接口中只定义了根据一个URL必须返回一个有HandlerExecutionChain代表的处理链,我们可以在这个处理链中添加任意的HandlerAdapters实例来处理这个URL对应的请求。
HandlerMapping初始化
https://blog.csdn.net/qq_38410730/article/details/79507465HandlerAdapter初始化
HandlerAdapter可以帮助自定义各种Handler。Spring MVC中提供了三个典型的简单HandlerAdapter实现类:
1)HttpRequestHandlerAdapter:可以继承HttpRequestHandler接口,所有Handler可以实现其void handleRequest(HttpServletRequest request, HttpServletResponse response)方法
2)SimpleControllerHandlerAdapter:可以继承Controller接口,所有Handler可以实现其 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)方法,改方法返回ModelAndView对象,用于后续的模板渲染。
3)SimpleServletHandlerAdapter:可以直接继承Servlet接口,可以将一个Servlet作为一个Handler来处理这个请求。
HandlerAdapter的初始化,只是简单创建一个HandlerAdapter对象,保存在DispatcherServlet的handlerAdapters集合中。当Spring MVC将某个URL对应到某个handler时,在handlerAdapters集合中查询哪个handlerAdapter对象支持这个handler,返回该对象,并根据此对象的类型调用对应的处理方法。Control的调用逻辑
整个Spring MVC的调用是从DispatcherServlet的doService方法开始的,在doService方法中会将ApplicationContext,localeResolver,themeResolver等对象添加到request中供后面使用。接着调用doDispatch方法,这个方法是主要处理用户请求的地方。
Control的处理逻辑关键就是在DispatcherServlet的handlerMappings集合中根据请求的URL匹配HandlerMapping对象中的某个Handler,匹配成功后将返回这个Handler的处理链HandlerExecutionChian对象,而在这个HandlerExecutionChain对象中将会包含用户自定义的多个HandlerInterceptor对象。HandlerInterceptor对象用于在Handler处理逻辑前后增加额外的逻辑。
HandlerExecutionChain类的getHandler方法返回真正处理逻辑对象Handler,DispacherServlet会根据Handler对象在其handlerAdapters集合中匹配哪个HandlerAdapter实例来支持该Handler对象,然后执行Handler对象的相应方法。
https://www.processon.com/special/template/5d8f13f4e4b0b2348043e0f5
Model设计(https://blog.csdn.net/en_joker/article/details/81903403)
ModelAndView对象是连接业务逻辑层与View展现层的桥梁,对于Spring MVC来说它也是连接Handler和View的桥梁。ModelAndView对象持有一个ModelMap对象和一个View对象,其中ModelMap对象就是执行模板渲染时所需要的变量所在实例,如JSP通过request.getAtrribute(String)获取的变量,Velocity中context.get(String)获取的变量。
参考:[https://blog.csdn.net/en_joker/article/details/81903403]
View设计
对Spring MVC的View模块来说,它由两个组件支持,分别是RequestToViewNameTranslator和ViewResolver。
1)RequestToViewNameTranslator:支持用户自定义对ViewName的解析,如将请求的ViewName加上前缀或者后缀等。
2)ViewResolver:根据用户请求的ViewName创建合适的模板引擎来渲染最终的页面,ViewResolver会根据ViewName创建一个View对象,调用View对象的void render(Map model, HttpServletRequest request, HttpServletResponse response)方法渲染页面。
上面提到的模板引擎有FreeMarkerViewResolver,InternalResourceViewResolver,VelocityViewResoler。其中InternalResourceViewResolver就是JSP的ViewResolver。
参考:https://blog.csdn.net/en_joker/article/details/81903797
设计模式-模板模式
模板模式的核心是,大的逻辑已经定义,你要做的就是实现一些具体步骤。
- 模板模式结构
1)Abstract(抽象模板类):定义了完整的框架后,方法的调用顺序通常已经确定,但是会开放一些抽象的方法给子类去实现。
*2)Concrete(具体模板实现类):实现抽象模板中定义的抽象方法,实现具体的功能,组成一个完整逻辑。 - Spring MVC中的模板模式示例
https://cloud.tencent.com/developer/article/1489850
第15章 深入分析iBatis框架之系统架构与映射原理
iBatis框架主要的类层次结构
- iBatis主要完成以下两件事情:
1)根据JDBC规范建立与数据库的连接
2)通过反射打通Java对象与数据库记录之间相互转化的关系。 - iBatis框架主要类层次结构
https://www.cnblogs.com/shaohz2014/archive/2014/06/16/3791131.html
iBatis框架的设计策略
- SqlMap配置文件:配置CRUD等Statement语句。iBatis通过解析SqlMap配置文件得到所有Statement语句同时形成ParameterMap、ResultMap两个对象和解析后SQL。
- ParameterMap:按照Statement中参数的出现顺序保存在Map集合中,并根据配置解析出参数的Java数据类型。
- ResultMap:包含了数据库返回的查询信息,用于填充返回对象;
iBatis框架的运行原理
- iBatis运行过程中的主要执行步骤
- Spring调用iBatis执行一个Statement的时序图
- 示例:https://www.cnblogs.com/cs-forget/p/6483895.html
1)SqlMapConfig.xml:声明所有SqlMapXXX.xml文件
2)SqlMapXXX.xml:声明跟XXX实体类相关的Statement,id属性供DaoImpl中的方法匹配;
3)XXXDto实体类
4)XXXDao接口和XXXDaoImpl实现类,XXXDaoImpl中实现增删改查方法时关联SqlMapXXX.xml定义的statement元素即可(根据id),传入对应的XXXDto实体类参数。
iBatis对SQL语句的解析
- 解析参数:"#id:INTEGER",根据"#"分隔符构建参数对象数组,顺序就是SQL中变量出现的顺序;最终构建的SQL中参数用?代替
- 根据反射机制给参数赋值
数据库字段映射到Java对象
- ResultMap:根据ResultClass构建对象,根据Map中的列名匹配对象中的属性
- 通过反射调用对象的setter()方法
设计模式解析之简单工厂模式
- iBatis中的简单工厂模式:https://blog.csdn.net/LZGS_4/article/details/45309753
- Java工厂模式:https://blog.csdn.net/llussize/article/details/80276627
第16章 Velocity工作原理解析
Velocity总体架构
- 代码结构:Velocity主要分为App、Context、Runtime和辅助Util。
1)App:封装接口,保留给使用者;主要是Velocity和VelocityEngine
2)Context:封装了模板渲染需要的变量;比如MVC框架的ModelDataMap
3)RuntimeInstance:为整个Velocity渲染提供一个单例模式,是Velocity的一个门面,封装了渲染模板需要的所有接口
JJTree渲染过程分析
Velocity作为模板语言,其核心在于模板文件(.vm)的解析,构建AST抽象语法树。Velocity的解析器是通过JavaCC构建的,JavaCC有一个扩充支持分析树或AST抽象语法树的生成,就是jjTree。
Velocity的语法节点
1)块节点类型:表示一个代码块,主要由ASTReference、ASTBlock、ASTExpression等组成。
2)扩展节点类型:这些节点可以自己扩展,比如#foreach节点
3)中间节点类型:位于树的中间,它的渲染依赖子节点才能完成。ASTIfStatement和ASTSetDirective等
4)叶子节点:ASTText和ASTTrue等,这种节点要么直接输出值,要么写到writer中。
Velocity读取vm模板,根据JavaCC语法分析器将不同类型的节点按照上面几个类型解析成一个完整的语法树。每个节点的渲染都是执行一个render方法。Velocity中常用语法节点的渲染
所谓渲染,即执行的另一种行内说法,意思就是执行了一段代码后,页面会变成什么样子。
1)#set语法:创建一个Velocity变量
2)#if、#elseif和#else语法:逻辑判断节点
3)#foreach语法
4)#parse语法:对Velocity模板进行模块化,可以将一些重复的模块抽取出来单独放在一个模板中,然后在其他模板中引入这个重用的模板。入#parse('head.vm')Velocity的方法调用
方法调用是通过"."来区分,name=person.name隐式调用了person.getName()方法。由于Velocity的方法调用是通过反射执行的,要考虑各种灵活的写法,比较耗时。
事件处理机制
- EventHandler是事件处理类的父接口
1)ReferenceInsertionEventHandler:表示针对Velocity中变量的时间处理,变量修改时触发我们定义的事件,可以防止恶意JS代码出现在页面中
2)NullSetEventHandler:对#set语法定义变量为空时的事件处理。
3)MethodExceptionHandler:Velocity反射执行调用方法报错后的处理,可以捕获异常,返回信息等。
4)InvalidReferenceEventHandler:在解析$变量时没有找到对应对象时的事件处理。
5)IncludeEventHandler:在处理#include和#parse引入时提供了额外逻辑的入口。
常用优化技巧
- 减少树的总节点数量
- 减少渲染耗时的节点数量
与JSP比较
- JSP渲染机制
1)JspServlet专门处理渲染JSP页面,这个Servelt会根据请求的JSP文件名将这个JSP包装成JspServletWrapper对象。
2)JSP在执行渲染时会被编译成一个Java类,这个Java类实际上也是一个Servlet。
3)HttpJspBase类是所有Jsp编译成的类的基类,HttpJspBase类的service方法会调用子类的_jspService方法,这个方法就是JSP页面变异成Java类之后的所有页面内容所在的地方。
4)DTCompiler类调用generateJava方法产生JSP对应的Java文件,翻译成Java类后,JDTCompiler再将这个类编译成class文件,然后创建对象并初始化,接着调用这个类的service方法,实际就是执行_jspService方法,完成渲染。 - Velocity与JSP
1)执行方式不一样:JSP是编译执行,Velocity是解释执行,如果JSP文件被修改,那么对象的Java类也会被重新编译,erVelocity却不需要,只是会重新生成一颗语法树
2)执行效率不同:编译执行明显好于解释执行
3)需要的环境支持不一样:JSP的执行必须要有Servlet的运行环境;Velocity不只应用在Servlet环境中。
设计模式之合成模式
合成模式又叫做部分整体模式,它通常把对象的关系映射到一棵树中,利用树的枝干和叶子节点来描述单个对象和组合对象,从而构建统一的操作这些对象的接口,使得访问对象的方式更加简单。
https://www.cnblogs.com/SamFlynn/p/4501227.html
设计模式之解释器模式
就是将带有一定文法的语句解析成特定的数据结构,并提供一种解释功能,使得能够解释这个语句。
https://www.cnblogs.com/adamjwh/p/10938852.html