本文将主要对定义在 XML 文件中的 bean 从静态配置到加载成为可使用对象的过程,即 IoC 容器的初始化过程进行一个整体的分析。在讲解上不主张对各个组件进行深究,只求对简单容器的实现有一个整体的认识,具体实现细节留到后面专门用针对性的篇章进行讲解。
首先我们引入一个 Spring 入门示例,假设我们现在定义了一个类 MyBean,我们希望利用 Spring 管理类对象。
本文将主要对定义在 XML 文件中的 bean 从静态配置到加载成为可使用对象的过程,即 IoC 容器的初始化过程进行一个整体的分析。在讲解上不主张对各个组件进行深究,只求对简单容器的实现有一个整体的认识,具体实现细节留到后面专门用针对性的篇章进行讲解。
首先我们引入一个 Spring 入门示例,假设我们现在定义了一个类 MyBean,我们希望利用 Spring 管理类对象。
控制反转(IoC: Inversion of Control)是 Spring Framework 的核心基础特性,也是面向对象程序设计中的重要原则,其目的在于降低程序之间的耦合度。控制反转一般分为 依赖注入(DI: Dependency Injection) 和 依赖查找(DL: Dependency Lookup) 两种类型,不过依赖注入应用更加广泛,所以大部分时候依赖注入等同于控制反转。
在面向对象程序设计中,对象一般用于承载和处理数据,不同对象之间的相互依赖与合作构成了我们的软件系统。设想在大型软件系统设计中,需要大量的对象通过相互依赖和交互进行合作,如果这些依赖关系由对象自己去控制和管理,那么耦合度将会非常高,不易于系统的扩展和维护。这个时候我们可以将对象的依赖关系交由 IoC 容器进行管理,将对象的新建和引用赋值等操作交由 IoC 容器统一完成,而对象只需要专心负责承载和处理数据即可。这样的设计可以降低系统在实现上的复杂性和耦合度,让系统更加灵活,满足“开闭原则”,并易于扩展和维护。
在编写一个系统的时候,我们总是希望我们的系统在设计上具备较好的可维护性和可扩展性,当客户需求有变,或者需要增加新功能时,能够从容应对,而一些前人总结的设计原则可以让我们在遇到这样的情况时候,不至于被动,从而能够以尽可能小的工作量来实现客户的需求。
最近在看之前一个自己写的项目代码的时候,发现之前构造的责任链像个楼梯台阶一样的堆在那里,很是影响代码的美观性,并且一条链上的七、八个对象在每次请求时都需要创建一遍,对于一个高并发的项目来说,是一笔不小的开销,于是想对这一块的代码进行优化,而享元模式刚好满足我的需求。
享元模式(Flyweight)是 以共享的方式有效地支持大量的细粒度对象 。能做到共享的关键是区分 内部状态(Internal State) 和 外部状态(External State) 。
OAuth2.0 协议定义了授权详细流程,并最终以 token 的形式作为用户授权的凭证下发给客户端,客户端后续可以带着 token 去请求资源服务器,获取 token 权限范围内的用户资源。
对于 token 的描述,OAuth 2.0 协议只是一笔带过的说它是一个字符串,用于表示特定的权限、生命周期等,但是却没有明确阐述 token 的生成策略,以及如何去验证一个 token。RFC6749 对于 access token 的描述:
The client obtains an access token – a string denoting a specific scope, lifetime, and other access attributes.
OAuth 2.0 协议是一种三方授权协议,目前大部分的第三方登录与授权都是基于该协议的标准或改进实现。OAuth 1.0 的标准在 2007 年发布,2.0 的标准则在 2011 年发布,其中 2.0 的标准取消所有 token 的加密过程,并简化了授权流程,但因强制使用 HTTPS 协议,被认为安全性高于 1.0 的标准。
JDK 在给提供基础的 java 依赖库的同时,也在 bin 目录下提供了一系列的小工具,除了我们常用的 java 和 javac 以外,还包含许多对 JVM 进行性能监控和故障诊断的工具。这些工具能够为我们日常程序开发和问题排查提供极大的便利,主要包含以下几种:
工具 | 描述 |
---|---|
jps | 显示系统运行中的 JVM 进程列表 |
jstat | 用于收集 JVM 各方面的运行数据(类加载、GC、JIT 编译等) |
jinfo | 查看和编辑 JVM 配置信息(JVM 启动参数、系统环境变量等) |
jmap | 生成 JVM 的堆转储快照(heapdump 文件) |
jhat | 用于分析 jmp 命令生成的 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果 |
jstack | 生成 JVM 的线程转储快照(通常所说的 threaddump 文件或 javacore 文件),用于判断是否存在死锁、死循环,以及阻塞等情况 |
jconsole | 可视化 java 监视与管理控制台,基于 JMX |
jvisualvm | 多合一故障处理工具,jconsole 的增强版,自 JDK 9 开始不再随 JDK 默认提供,需要独立下载,地址:http://visualvm.github.io/ |
以 java 语言为例,JVM 针对 java 程序的优化可以发生在编译期和运行期,相应的优化操作分别发生在 javac 编译器在将 java 源程序编译成字节码期间,以及运行时即时编译器(JIT: Just In Time Compiler)将字节码编译成本地机器码期间。此外,还有一类编译器可以将源程序直接编译成与目标机器指令集相关的二进制代码,此类编译器称为提前编译器。运行期依赖于 JIT 的编译优化的主要目的在于提升程序的执行效率,而编译期优化的主要目的在于支持 java 语法糖,提升语言的易用性和编码效率。如果以字节码所处的位置作为参考线,那么编译期的编译可以称为前端编译,而即时编译和提前编译合起来则可以称为后端编译。
在不同的虚拟机实现中,执行引擎在执行 java 代码时可能会有 解释执行 (通过解释器执行)和 编译执行 (通过 JIT 生成本地代码执行)两种选择,也可能是二者兼备,但不管采用哪种方式执行,当我们调用一个方法的时候,都需要确定目标方法的具体版本,因为在面向对象语言中存在封装、继承和多态的三大特性,一个方法可能因为重载或覆盖而存在多个版本。
方法调用阶段的主要工作是确定被调用方法的版本,而非执行具体的方法。字节码文件中存储的是方法的符号引用,只有将符号引用解析成直接引用(运行时方法的入口内存地址)才能确定具体调用的方法是谁,这个映射的过程有的发生在类加载的解析阶段,有的则发生在运行期间。
JVM 的类加载机制描述了类数据从字节码文件加载到内存,并对其进行校验、解析、初始化,并最终成为能够直接被 JVM 使用的 java 数据类型的过程。
类的整个生命周期可以分为 加载、连接(验证、准备、解析)、初始化、使用、卸载 5 个阶段(加载、连接和初始化构成了类加载全过程),其中连接又可以细分为验证、准备、解析 3 个阶段。如下图所示: