往期文章回顾整理列表:
首先说明一下,这里面大部分题都不是三言两语或者几篇文章就能理解明白和透彻的,作为面试题讲解,老四着重重点知识的浅析而不是详细的介绍技术知识,当您参考这些文章时,如果内容对你比较有吸引力,请结合老四的内容自己看书或者Google搜索进行更深入的理解,这样才能事半功倍,加油~
1.具体的描述一下ACID是什么?
所谓的ACID指的是数据库事务四个重要属性,四个大写字母分别是Atomicity(原子性)、Consistemcy(一致性)、Isolation(隔离性)、Durability(持久性)。
- 原子性(Atomicity): 事务中的所有元素作为一个整体提交或回滚,事务的个体元素是不可分的,事务是一个完整操作。
- 一致性(Consistemcy): 事务完成时,数据必须是一致的,也就是说,和事务开始之前,数据存储中的数据处于一致状态,保证数据的无损。
- 隔离性(Isolation): 对数据进行修改的多个事务是彼此隔离的。事务必须是独立的,不应该以任何方式依赖于或影响其他事务。
- 持久性(Durability): 事务完成之后,它对于系统的影响是永久的,即使出现系统故障也将一直保留,是真实的修改了数据库了的。
2.Cookie和Session的区别?
烂大街了问题了,现在估计只有大学中依然会考这样的题目吧!为什么要拿出来再说一下呢?主要是表达一个观点,老四个人觉得,就像Java中的重写和重载,完全是两个不同的设计和使用用途,然而却偏偏拿来一起比较,反而让初学者更加糊涂。人家重写是用来子类对父类同名方法进行重新改造,而重载是在当前类中不考虑返回值等因素情况Java允许同名不同参数这种形式的存在叫做重载,他俩有个卡巴斯基关系?同样,Cookie技术针对的是客户端,Session技术是Web的服务器端,要说有点联系就是JSESSIONID存在cookie中,面试中如果遇到这样的面试官也可以侧面怼一下,老四觉得问这种问题没什么水平….但是既然抛出来就有必要再次回顾一下:
- cookie数据存在客户的浏览器(会话cookie)或者硬盘上,session放在服务器上
- cookie不是很安全,重要信息不能放在cookie中,比如说XSS(Cross-site scripting,通常简称为: XSS以免与css混淆)攻击就能轻易获取到cookie信息
- 访问增多,session增多,影响性能
3.get和post请求的区别?
也算是烂大街的网络相关的笔面试题了吧。你可能见过或者背过如下类似的答案:
- GET使用URL或Cookie传参。而POST将数据放在BODY中。
- GET的URL会有长度上的限制,而POST的数据则可以非常大。
- POST比GET安全一些,因为数据在地址栏上不可见。
单以上这几条答案其实只不过是单纯的从HTML标准对HTTP协议的用法的约定方面来说的,可以理解为get/post在形式上的区别和差异。再次基础之上我们需要了解这些网络请求的设计初衷,因此更好分区分并使用get/post请求等。其实get请求仅用来获取信息,不能改变服务器信息,即使你用过get的方式做过删除和更新等操作,但其实是错误的做法。get译为获取,标识多为进行表单查询等query操作,而post译为发送/提交,多半用于提交数据,比如说表单提交,并且向服务其发送文件等就只能使用post表单形式提交。所以get/post更深层次的一点区别: Get用于从服务器上取内容,虽然可以通过QueryString向服务器发信息,但这违背了Get的本意。QueryString中的信息在HTTP看来仅仅是获取所取得内容的一个参数而已。而Post是由客户端向服务器端发送内容的方式。因此具有请求的第三部分-内容。HTTP协议的组成以及HTTP的请求方法复习:
HTTP协议分为三个部分
- 状态行
- 请求头
- 消息主体
HTTP请求方法
- options 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送”*”的请求来测试服务器的功能性
- head 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
- get 向特定的资源发出请求。
- post 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立或已有资源的修改。
- put 向指定资源位置上传其最新内容。
- delete 请求服务器删除Request-URL所标识的资源。
- trace 回显服务器收到的请求,主要用于测试或诊断。
- connect HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
4.Web容器主要用来承担什么样的角色?除了Tomcat你还知道那些容器?
Web容器用我们自己的俗话来讲就是运行我们的项目的,为我们的应用程序组件(JSP、Servlet等)提供一个可执行环境,servlet可以直接在这个环境中将项目稳定运行,实现页面与高级语言交互等。出了Tomcat,还有WebLogic、WebSphere、Apache、IIS、Resin、Nginx等。
5.RPC和RMI分别是什么?都是干嘛的?
RPC全称Remote Procedure Call,中译远程过程调用,是计算机通信协议的一种,允许一台计算机调用另一台计算机的程序。而RMI是特指Java RMI,全称Java Remote Method Invocation,中译为Java远程方法调用,其实就是依赖于接口实现调用远程服务器的对象。
- RPC采用的是http协议,因此不支持对象传输
- RMI采用TCP/IP协议传输,支持Java对象传输
- RPC不限制语言沟通,比如双方均约定为json格式,那么即使一方是php,一方是Java也没关系。而RMI只限使用Java实现。
6.操作系统中heap(堆)和stack(栈)有什么区别?
在说操作系统中的堆和栈之前,我们需要先来复习一下数据结构中关于堆、栈的基础知识,可以移步《浅析数据结构之栈stack,顺便全面复习线性表List》、《浅析数据结构之堆结构的基础知识以及堆排序》这两篇文章中简单的复习一下数据结构中的堆栈知识,然后在浅析操作系统中的堆栈之后我们还要再补充一下在JVM中关于堆和栈的一些知识点。
在操作系统中存在栈区和堆区。栈区是由程序的编译器自动分配与释放的,用来存放程序中函数的参数值,局部变量的值,数据操作的方式也和数据结构的栈结构类似,先进后出FILO(First In Last out)。而操作系统中的堆区是区别于数据结构中的堆结构的,操作系统中的堆区普遍是由开发人员(即程序员)负责分配释放,C/C++就是这样的,后来的Java搞了个JVM,就不让Java开发者自己动手设置堆和栈了。堆区的数据分配方式类似于链表,属于先进先出FIFO(First In First Out),一般负责存储对象或者数组对象等。
栈使用的是操作系统的一级缓存,调用之后会立即释放存储空间,而堆使用二级缓存,如果开发人员没有对堆中的数据进行释放的话,操作系统会根据相应的算法,系统状态而进行回收,如果是Java这样的语言,会有JVM负责进行回收。
作为Java知识分享小站,在这道问题上我们也有必要探讨一下Java中的堆栈知识点,首先我们来复习一下JVM的内存模型。
- Java虚拟机栈: 每个方法执行的同时都会创建一个栈帧,用于存储局部变量表(boolean、byte、char、short、int、float、long、double、对象引用、returnAddress)、操作数栈、动态链接、方法出口等信息.每个方法从调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。
- 本地方法栈: 为虚拟机使用到的Native方法服务。
- Java堆: 存放对象实例和数组等
剩下的是干嘛的可以自行查询,看到上面这张图其实我们已经一目了然了,接下来在总结一下Java中的堆和栈就是:
- 与C++等高级语言不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
- Java中把内存划分为堆内存和栈内存:
— 栈内存主要负责分配基本类型的变量和对象的引用,JVM会自动释放变量分配的内存空间给别人用。
— 堆内存主要存储数组和new出来的对象,但是实际使用的却是占内存中存储的对象引用的值,当数组和对象没有栈内存的引用变量指向堆内存中存储的数组和对象的时候,那么堆中的对象就变成垃圾了,等到被JVM回收。
在实际面试或者笔试过程中,建议这样的题目一定要论述的全面一些,不要问什么答什么,就像老四浅析的顺序一样,先从数据结构说起,然后点出操作系统中堆栈的一些特点,最后着重介绍一下Java高级语言中对堆栈的处理,效果会非常好。
7.Spring中的AOP思想你怎么理解,注解是如何实现AOP面向切面编程的?
至于什么是AOP,其实它的中文含义也就表达了它的概念,面向切面编程,即将代码通过某种方式动态的织入实现约定的流程当中实现某些业务功能等。切面编程是一种约定流程的编程,在一些大小型Web等项目中,日志,数据库事务等等都离不开切面编程,让这些系统级或者说非业务级的代码流程既不影响项目的主业务执行,从而还能对项目业务的其他方面进行补充,这也解释了我们为什么要使用aop(Aspect Oriented Programming,面向切面编程)。当今Java Web中,Spring Boot的使用越来越多,但是Spring Boot是基本强制我们使用注解来编程的,所以借此来浅析一下Spring中基于注解的切面实现,即@AspectJ注解的方式。在Spring中,AOP是基于方法的,无论怎么样,都需要我们先来复习一下AOP比较多的术语。
- 连接点(join point): 对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法。
- 切点(point cut): 有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则表达式和指示器的规则去定义,从而适配连接点。切点就是提供这样个功能的概念。
- 通知(advice): 就是按照约定的流程下的方法,分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)、事后返回通知(afterReturning advice)和异常通知(after Throwing advice),它会根据约定织入流程中,需要弄明白它们在流程中的顺序和运行的条件。
- 目标对象(target): 即被代理对象。
- 引入(introduction): 指引入新的类和其方法,增强现有Bean的功能。
- 织入(weaving): 它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
- 切面(aspect): 是一个可以定义切点、各类通知和引入的内容,,Spring AOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。
一个简单的示例,对一个简单的控制器中的所有方法都进行日志切面处理。老四使用的是Spring Boot 2.1.1 release环境下的版本,首先创建一个切面类,定义切点和各类通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package com.glorze.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * 切面: 实现指定控制器的所有方法都进行日志监控功能 * * @ClassName: MyAspect * @author: glorze.com_高老四 * @since: 2018/12/14 14:29 */ @Aspect @Component public class MyAspect { private final static Logger logger = LoggerFactory.getLogger(MyAspect.class); @Pointcut("execution(public * com.glorze.controller.AopController.*(..))") public void log(){ } @Before("log()") public void doBefore(JoinPoint joinPoint){ logger.info("方法执行前..."); ServletRequestAttributes sra=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request=sra.getRequest(); logger.info("url:"+request.getRequestURI()); logger.info("ip地址:"+request.getRemoteHost()); logger.info("method:"+request.getMethod()); logger.info("class_method:"+joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName()); logger.info("args:"+joinPoint.getArgs()); } @After("log()") public void doAfter(JoinPoint joinPoint){ logger.info("方法执行后..."); } @AfterReturning(returning="result",pointcut="log()") public void doAfterReturning(Object result){ logger.info("执行返回值结果:"+result); } } |
接下来简单的写一个控制器,测试是否织入成功:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.glorze.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * 测试控制器切面 * * @ClassName: AopController * @author: glorze.com_高老四 * @since: 2018/12/14 14:26 */ @RestController @RequestMapping("/aop") public class AopController { @RequestMapping("/test") public Map<String, String> testAop(){ Map<String, String> map = new HashMap<>(16); map.put("code", "200"); map.put("msg", "测试成功"); return map; } } |
如代码中看到,在自定义切面中我们完全通过注解来实现一个简单的日志切面。更多切面的知识还需要多看资料多看书,并没有说的这么简单。但是笔面试中一定要掌握AOP上面哪几个非常的重要的概念和其在代码中的具体体现。
8.简单说一下Ajax的异步原理以及实现步骤
属性
|
描述
|
onreadystatechange
|
存储函数(或函数名),每当readyState属性改变时,就会调用该函数。
|
readyState
|
存有 XMLHttpRequest 的状态。从0到4发生变化。
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
|
status
|
200: “OK”
404: 未找到页面
|
Ajax执行步骤(没必要死记硬背,一定要理解这个过程):
-
Ajax引擎(即XMLHttpRequest对象),首先为该对象注册一个监听器(该监听器是一个事件处理函数),对状态改变事件(readStatechange)进行监听
-
当用户对GUI(Graphical User Interface,图形用户界面)做了某种操作(将产生对应的事件,如焦点失去事件等)
-
一旦产生对应的事件,将触发事件处理代码
-
在执行事件处理代码时,会调用Ajax引擎(XMLHttpRequest对象)
-
发送请求: Ajax引擎被调用后,将独自向服务器发送请求(独立于浏览器之外)
继续其他操作: 在Ajax引擎发送请求的同时,用户在浏览器还可以对GUI继续做其他操作,该请求时异步请求(Ajax引擎发送请求时,没有打断用户的操作) - 服务器的web组件对请求进行处理
-
服务器可能会调用到数据库或者处理业务逻辑的java类
-
服务器处理结果响应给(只返回部分数据,可以是xml或者JSON或者文本)Ajax引擎
-
监听器通过对Ajax引擎获取响应数据(xml或者JSON或者文本)
-
监听器对GUI中的数据进行更新(局部更新,不是整个页面刷新)在整个过程中大部分是通过JS实现的,响应数据可能是xml,JSON或者文本,所以Ajax可以看做是多种技术的融合。
Ajax标准请求代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$.ajax({ type: "post", url: '<%=basePath%>traineeInfo/selectCoach.do', dataType: 'json', success:function(data){ var coachName=""; var data=eval(data); for(var i in data){ /* 教练选择框 */ coachName +="<option value='"+data[i].coachUid+"'>"+data[i].coachName+"</option>"; } $("#cnm").html("<select style='width:100px;' id='selectCoach'><option value='9999'>选择教练</option>"+coachName+"</select>"); $("#selectCoach").selectpicker(); } }) |
1 2 3 4 5 6 7 8 9 10 11 12 |
$.ajax({ url:"http://www.glorze.com/967.html", dataType:'jsonp', data:'', jsonp:'callback', success:function(result) { for(var i in result) { alert(i+":"+result[i]);// 循环输出a:1,b:2,etc. } }, timeout:3000 }); |
9.请简述Servlet 的生命周期及其相关的方法
在以前这个问题会问到很多,但是现在jsp技术使用率逐渐在下降,各种优秀的框架在老祖宗的基础上封装的非常优秀,貌似很少有人问servlet相关的知识点了,但是毕竟是老祖宗,生命周期是一个很重要的概念,必须掌握。
Servlet的生IM那个周期一般可以用三个方法来表示:
- init() 仅执行一次,负责在装载Servlet时初始化Servlet对象
- service() 核心方法,一般HttpServlet中会有get/post两种处理方式。在调用doGet/doPost方法时会构造servletRequest和servletResponse请求和响应对象作为参数
- destroy() 在停止并且卸载Servlet时执行,负责释放资源
10.fail-fast与fail-safe机制有什么区别?
说道fail-fast和fail-safe其实涉及到的Java并发编程中集合的并发修改的相关问题。早在老四浅析的《阿里巴巴Java开发手册》系列中之《阿里巴巴Java开发规约第一章-集合处理篇》第三条也谈到过相关的问题,只不过当时没有细说,这次就当做做一个补充吧。看了老四之前的文章应该不会对ConcurrentModificationException这个异常感到陌生,这个异常抛出的原则就是 无论是否是多线程还是单线程,当遍历集合的时候集合的结构被修改就会抛出该异常,所以ConcurrentModificationException异常通常被用来检测bug,也逼迫着我们尽量正确的并发编程,此外还有ArrayIndexOutOfBoundsxception异常,代表在对同步容器(Vector、HashTable)进行多线程操作时,虽然同步容器能保证自身是线程安全的,但是无法保证客户端多线程的操作影响。所以JVM尽最大的努力抛出ConcurrentModificationException、ArrayIndexOutOfBoundsxception等异常的的方式也就是我们所说的fail-fast机制,也叫作及时失败。
分别举例在单线程和多线程情况下fail-fast的代码示例:
1 2 3 4 5 6 7 8 |
// 单线程下正确的迭代标准是使用Iterator // 老四在《<a href="http://www.glorze.com/665.html" target="_blank" rel="noopener">阿里巴巴Java开发规约第一章-集合处理篇</a>》第七条中有所浅析 String glorze = "高老四Java知识分享技术博客"; for(String Str : strList) { if(glorze.equals(str)) { list.remove(str); } } |
1 2 3 4 5 6 7 8 9 10 |
// 多线程情况下,如果线程A在调用getLast,同时线程B在调用deleteLast // 两个线程的交替执行会导致ArrayIndexOutOfBoundsxception异常抛出 public static Object getLast(Vector list) { int lastIndex = list.size() - 1; return list.get(lastIndex); } public static void deleteLast(Vector list) { int lastIndex = list.size() - 1; list.remove(lastIndex); } |
至于在并发容器中JVM是通过什么样的算法来抛出ConcurrentModificationException异常的,可以再仔细看一遍这篇文章,那里面有一个重要的标识变量modCount。
至于fail-safe其实指的是fail-fast问题的思路,说白了可以理解为在对集合进行迭代遍历的时候采用再复制一份的方法,然后操作副本数据。既然在设计同步容器类的迭代器时没有考虑到并发修改的问题,后期JDK又提供了并发容器供我们使用来避免ConcurrentModificationException异常的发生。典型的就是CopyOnWriteArrayList替代同步List,它提供了更好的并发性能。CopyOnWriteArrayList(写入时复制)底层就是在修改集合之前会复制底层数组,虽然开销比较大,但是不会抛出ConcurrentModificationException异常,也不需要在客户端中加锁,但是进度当迭代操作远远多于修改操作时才适合使用”写入时复制(Copy on Write)”容器。除此之外,ConcurrentHashMap也是一种并发容器,用于解决HashMap线程不安全的问题,通过使用一种粒度更细的加锁机制实现更大程度的共享,也叫作分段锁,也不需要在迭代过程中对容器加锁。具体可以看一下ConcurrentHashMap源码并结合相关资料了解一下,老四慢慢也会写到。
最后总结一下:
java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。fail-fast机制指的是遍历迭代一个集合时,当集合结构被修改,会抛出ConcurrentModificationException或ArrayIndexOutOfBoundsxception异常。而fail-safe的机制指的是在对任何集合结构的修改时,首先都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException异常,从而实现并发修改安全。不过fail-safe机制有两个缺点:
- 底层或者代码层面需要复制、克隆集合,产生大量的无效对象,系统开销大
- 随着多线程的介入操作,无法保证读取的数据是目前原始数据结构中的数据。
11.知道类加载器是什么嘛?自己写过自定义的类加载器嘛?
Java类加机制属于JVM(Java Virtual Machine,Java虚拟机)底层知识,当我们代码写到一定程度的时候是十分必要掌握这些底层知识的。在说类加载器之前我们需要知道类的加载、连接和初始化。当程序主动使用某个类,如果该类还没有被加载到内存中,JVM会通过加载、连接、初始化三个步骤来对该类进行初始化,这三个步骤统称为类加载或类初始化。类的加载由类加载器来完成,类加载器基本都是JVM提供的,这些类通常被称为系统类加载器,除此之外我们也可以通过继承ClassLoader来自定义类加载器,通过形形色色的类加载器我们可以对不同来源的类的二进制数据进行加载初始化。
类的连接:
当类已经被加载之后,系统会为类生成对应的Class对象,然后进入到连接阶段,类的连接负责把二进制数据合并到JRE(Java Runtime Environment,Java运行环境)中:
- 验证: 验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
- 准备: 类准备阶段负责为类的类变量分配内存,并设置默认初始值
- 解析: 将类的二进制数据中的符号引用替换成直接饮用
类的初始化:
类的初始化就是主要对类变量进行初始化了,即static修饰的类变量。JVM初始化一个类的步骤:
- 加入这个类还没有被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类,依次往上进行初始化,所以JVM最先初始化的总是java.lang.Object类。
- 假如类中有初始化语句,则系统一次执行这些初始化语句
类初始化的时机:
当Java程序首次通过以下方式使用某个或者接口的时候,系统就会进行类的初始化操作。
- 创建类的实例 new或者反射
- 调用类方法,即静态方法
- 访问类变量或者为类变量赋值
- 使用反射创建java.lang.Class对象,例如Class.forName()
- 初始化某个类的子类
- 直接使用java.exe命令运行某个主类
注意: 形如 static final String glorze = “高老四Java知识分享技术博客”; 这样的静态常量,系统不会进行类初始化操作,因为在编译的时候就可以确定下来glorze的值就是”高老四Java知识分享技术博客”,程序中所有使用glorze的地方在编译的时候就会被替换成”高老四Java知识分享技术博客”字符串来使用,相当于”宏变量”。
了解了类的加载、连接以及初始化之后,我们再来详细说一下类加载器。通过上述我们知道类加载器负责将.class文件加载到内存当中,并为之生成对应的java.lang.Class对象。当JVM启动时,会形成三个类加载器组成的初始类加载器层次结构:
- Bootstrap ClassLoader: 根类加载器,负责加载Java的核心类
- Extension ClassLoader: 扩展类加载器,加载JRE的扩展目录
- System ClassLoader: 系统类加载器,我们自定义的类加载器都以系统类加载器作为父加载器
除了根类加载器,其余类加载器都是Java语言写的,所以我们也可以自定义类加载器实现一些特定的功能。类加载器加载Class要经过以下6个步骤:
- 检测此Class是否载入过(即类加载的缓存机制,缓存机制会保证所有加载过的Class都会被缓存),如果有则直接返回对应的java.lang.Class对象
- 如果父类加载器(即类加载的父类委托机制,先让父类加载器试图加载该类,不成功才尝试从自己的类路径加载该类,父类加载器不存在,那么该类的父类加载器一定是根类加载器或者本身就是根类加载器)不存在,则请求使用根类加载器载入目标类,成功则返回Class对象,不成功则抛出ClassNotFoundException异常
- 父类加载器载入目标类,成功则返回Class,不成功则抛出ClassNotFoundException异常
- 根类加载器载入目标类,成功则返回Class,不成功则抛出ClassNotFoundException异常
- 类加载器尝试寻找Class文件,找到了就从文件载入Class,找不到则抛出ClassNotFoundException异常
- 从文件中载入Class,成功即返回java.lang.Class对象。
创建并使用自定义类加载器
创建自定义的类加载器之前我们知道需要继承ClassLoader基类,关于ClassLoader基类源码如下,老四对里面的方法进行了一些注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } /** * ClassLoader的入口点,根据指定名称来加载类 * 系统就是调用ClassLoader的该方法来获取指定类对应的Class对象 */ protected 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; } } /** * 根据指定名称来查找类 * 我们一般自定义类加载器就是重写findClass方法 */ protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } /** * 负责将指定类的字节码文件(如: Glorze.class)读入字节数组byte[] b内,并把它转换为Class对象 * 该字节码文件可以来源于文件、网络等 */ protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; } /** * 从本地文件系统装入文件 */ protected final Class<?> findSystemClass(String name) throws ClassNotFoundException { ClassLoader system = getSystemClassLoader(); if (system == null) { if (!checkName(name)) throw new ClassNotFoundException(name); Class<?> cls = findBootstrapClass(name); if (cls == null) { throw new ClassNotFoundException(name); } return cls; } return system.loadClass(name); } /** * 返回系统类加载器 */ @CallerSensitive public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } /** * 获取该类加载器的父类加载器 */ @CallerSensitive public final ClassLoader getParent() { if (parent == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Check access to the parent class loader // If the caller's class loader is same as this class loader, // permission check is performed. checkClassLoaderPermission(parent, Reflection.getCallerClass()); } return parent; } /** * 链接指定的类 */ protected final void resolveClass(Class<?> c) { resolveClass0(c); } private native void resolveClass0(Class<?> c); /** * 类加载缓存机制 */ protected final Class<?> findLoadedClass(String name) { if (!checkName(name)) return null; return findLoadedClass0(name); } private native final Class<?> findLoadedClass0(String name); } |
从源码中我们大致了解到ClassLoader的基本机制,通过继承ClassLoader类,我们重写findClass方法从而来实现一个自定义的类加载器,该类加载器实现了在加载类之前就编译该类的源文件从而直接运行源程序。由于代码篇幅过长,老四这里就不贴源代码了,如果想要看想要参考可以文末自助获取下载自定义类加载器的类文件源代码,没有账号的使用邮箱注册小站即可。
除此之外,我们通常自定义类加载器可以用于以下常见的功能:
- 执行代码前自动验证数字签名
- 根据用户提供的密码解密代码,从而实现代码混淆器来避免反编译*.class文件加载到内存当中
- 根据用户需求来动态地加载类
- 根据应用需求把其他数据以字节码的形式加载到应用中。
在介绍一个比较重要的类加载器: URLClassLoader,该类是系统类加载器和扩展类加载器的父类,用来负责从本地文件系统获取二进制文件或者远程主机来获取二进制文件来加载类,有兴趣的可以查看api文档来了解一下。
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击”给你买杜蕾斯”),也可扫描小站放的支付宝领红包二维码,线下支付享受优惠的同时老四也可以获得对应赏金,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。