-
java源码包---java 源码 大量 实例
2013-04-18 23:15:26Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM... -
java源码包2
2013-04-20 11:28:17Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行... -
java源码包3
2013-04-20 11:30:13Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行... -
面试官:你背了几道面试题就敢说熟悉Java源码?对不起,我们不招连源码都不会看的人
2020-03-30 18:47:49你看源码么? 你会看源码么? 你从源码中有收获么?如果你不会看源码,请耐心看下去
一、我的真实经历
标题是我2019.6.28在深圳某500强公司面试时候面试官跟我说的话,即使是现在想起来,也是觉得无尽的羞愧,因为自己的愚钝、懒惰和自大,我到深圳的第一场面试便栽了大跟头。
我确信我这一生不会忘记那个燥热的上午,在头一天我收到了K公司的面试通知,这是我来深圳的第一个面试邀约。收到信息后,我激动得好像已经收到了K公司的offer,我上网专门查了下K公司的面经,发现很多人都说他们很注重源码阅读能力,几乎每次都会问到一些关于源码的经典问题,因此我去网上找了几篇关于String、HashMap等的文章,了解到了很多关于Java源码的内容。看完后我非常的自信,心想着明天的所有问题我肯定都可以回答上来,心满意足的睡觉。
面试的那天上午,我9点钟到了K公司楼下,然后就是打电话联系人带我上去,在等待室等待面试,大概9:30的时候,前台小姐姐叫到了我的名字,我跟着她一起进入到了一个小房间,里面做了两个人,看样子都是做技术的(因为都有点秃),一开始都很顺利,然后问道了一个问题“你简历上说你熟悉Java源码,那我问你个问题,String类可以被继承么”,当然是不可以继承的,文章上都写了,String是用final修饰的,是无法被继承的,然后我又说了一些面试题上的内容,面试官接着又问了一个问题
“请你简单说一下substring的实现过程”
是的,我没有看过这一题,平时使用的时候,也不会去看这个方法的源码,我支支吾吾的回答不上来,我能感觉到我的脸红到发烫。他好像看出了我的窘迫,于是接着说“你真的看过源码么?substring是一个很简单的方法,如果你真的看过,不可能不知道”,到这个地步,我也只好坦白,我没有看过源码,是的我其实连简单的substring怎么实现的都不知道,我甚至都找不到String类的源码。
面试官说了标题上的那句话,然后我面试失败了。
我要感谢这次失败的经历,让我打开了新世界,我开始尝试去看源码,从jdk源码到Spring,再到SpringBoot源码,看得越多我越敬佩那些写出这优秀框架的大佬,他们的思路、代码逻辑、设计模式,是那么的优秀与恰当。不仅如此,我也开始逐渐尝试自己去写一些框架,第一个练手框架是“手写简版Spring框架--YzSpring”,花了我一周时间,每天夜里下班之后都要在家敲上一两个小时,写完YzSpring之后,我感觉我才真正了解Spring,之前看网上的资料时总觉得是隔靴搔痒,只有真正去自己手写一遍才能明白Spring的工作原理。
再后来,我手上的“IPayment”项目的合作伙伴一直抱怨我们接口反馈速度慢,我着手优化代码,将一些数据缓存到Redis中,速度果然是快了起来,但是每添加一个缓存数据都要两三行代码来进行配套,缓存数据少倒无所谓,但是随着越来越多的数据需要写入缓存,代码变得无比臃肿。有天我看到@Autowired的注入功能,我忽然想到,为什么我不能自己写一个实用框架来将这些需要缓存的数据用注解标注,然后用框架处理呢?说干就干,连续加班一周,我完成了“基于Redis的快速数据缓存组件”,引入项目之后,需要缓存的数据只需要用@BFastCache修饰即可,可选的操作还有:对数据进行操作、选择数据源、更新数据源、设置/修改Key等,大大提高了工作效率。第一次自写轮子,而且效果这么好,得到了老大哥的肯定,真的很开心。
那么现在我要问你三个问题:
你看源码么?
你会看源码么?
你从源码中有收获么?
二、看源码可以获得什么
1.快速查错、减少出错
在编码时,我们一般都发现不了RuntimeException,就比如String的substring方法,可能有时候我们传入的endIndex大于字符串的长度,这样运行时就会有个错误
String index out of range: 100
有时候稀里糊涂把代码改正确了,但是却不知道为什么发生这个异常,下次编写的时候又发生同样的问题。如果我们看过源码,我们就可以知道这个异常发生的原因
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) {//起始坐标小于0 throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) {//结束坐标大于字符串长度 throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) {//起始坐标大于结束坐标 throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
源码中给出了三个可能抛出上面异常的情景,那我们就可以根据这三种情景去检查我们的代码,也以后在编码的时候注意这些问题。
2.学习编程习惯
还是说上面的substring源码,请注意他的return,如果是你,你会怎么写?如果没有看过源码,我肯定会写成下面
if ((beginIndex == 0) && (endIndex == value.length)) return this; return new String(value, beginIndex, subLen);
虽然功能是一样的,但是运用三元运算可以用一行代码解决问题,而且又不用写if语句,现在我已迷上了三元运算符,真的很好用。
3.学习设计模式(针对新手)
好吧!我摊牌了,作为一个半路出家的程序员,我没有接受过系统化的教学,所有的都是自学,在之前我完全不了解设计模式,只知道有23种设计模式,最多知道单例模式。
不了解设计模式最主要的原因是当时没有实战经验,自己写的项目都是比赛项目,完全不用不上设计模式,基本上是能跑就行。我第一次接触设计模式是在log4j的工厂模式,当时是完全不懂工厂模式该怎么用,就是看着log4j的源码一步步学会了,然后自己做项目的时候就会有意无意的开始运用设计模式,下面是我项目中使用单例模式获取配置类的代码
import java.util.ResourceBundle; public class Configration { private static Object lock = new Object(); private static Configration config = null; private static ResourceBundle rb = null; private Configration(String filename) { rb = ResourceBundle.getBundle(filename); } public static Configration getInstance(String filename) { synchronized(lock) { if(null == config) { config = new Configration(filename); } } return (config); } public String getValue(String key) { String ret = ""; if(rb.containsKey(key)) { ret = rb.getString(key); } return ret; } }
3.小总结
你们可能很多人都会觉得上面的东西很简单,请不要被我误导,因为上面都是最简单的例子,源码中值得学习的地方非常多,只有你自己去看,才能明白。
三、阅读源码的正确姿势
我们这里以一个热度非常高的类HashMap来举例,同时我非常建议你使用IDEA来阅读编码,其自带反编译器,可以让我们快速方便的看到源码,还有众多快捷键操作,让我们的操作爽到飞起。
1.定位源码
其实定位的时候也有多种情况
Ctrl+左键
像这种情况,我们要进入只属于HashMap类的方法,我们可以直接Ctrl+左键就可以定位到源码位置了
Ctrl+Alt+B
HashMap的put方法是重写了Map的方法,如果我们用Ctrl+左键,会直接跳到Map接口的put方法上,这不是我们想要的结果,此时我们应该把鼠标光标放到put上,然后按下Ctrl+Alt+B,然后就出现了很多重写过put方法的类
找到我们需要查看的类,左键点击就可以定位到put方法了
2.查看继承关系
一个类的继承关系很重要,特别是继承的抽象类,因为抽象类中的方法在子类中是可以使用的。
上一步中我们已经定位到了HashMap源码上,现在拉到最上面,我们可以看到类定义的时候是有一下继承关系
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
当然,如果想更直观更详细的话,在IDEA中有个提供展示继承关系的功能,可以把鼠标放在要查看的类上,然后Ctrl+Alt+Shift+U,或者右键=》Diagrams=》Show Diagram,然后我们就可以看到继承关系
然后大致查看下AbstractMap抽象类,因为有可能等下会用到。
3.查看类常量
我们进到HashMap构造函数时,发现了以下代码
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
我们只知道initialCapacity是我们传入的初始容量,但完全不知道这个DEFAULT_LOAD_FACTOR是什么、等于多少,我们可以先大致看一下这个类所拥有的的常量,留个印象就好,有利于等下阅读源码,Ctrl+左键定位到这个量的位置,然后发现还有好几个常量,常量上面有注释,我们看一下,这有助于我们理解这些常量
//序列号 private static final long serialVersionUID = 362498820763181265L; /** * 初始容量,必须是2的幂数 * 1 << 4 = 10000 = 16 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始默认值二进制1左移四位 = 16 /** * 最大容量 * 必须是2的幂数 <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 加载因子,构造函数中没有指定时会被使用 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 从链表转到树的时机 */ static final int TREEIFY_THRESHOLD = 8; /** * 从树转到链表的时机 */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64;
这样,我们就对HashMap中常量的作用和意义有所理解了
4.查看构造函数
我们一般看一个类,首先得看这个类是如何构建的,也就是构造方法的实现
/** * 构造一个空的,带有初始值和初始加载因子的HashMap * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
很明显,上面的构造函数指向了另一个构造函数,那么我们点进去看看
/** * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
这里就是我们构造函数实现的地方了,我们来一行一行的去分析:
1.我们的initialCapacity参数是我们一开始传进来的16,loadFactor是上一步中用的默认参数0.75f
2.判断初始容量是否小于0,小于0就抛出异常,不小于0进行下一步
3.判断初始容量是否大于最大容量(1 << 30),如果大于,就取最大容量
4.判断加载因子是否小于等于0,或者是否为数字,抛出异常或下一步
5.初始化这个HashMap的加载因子
6.最后一行是HashMap的扩容机制,根据我们给的容量大小来确定实际的容量,我们来看一下该方法的源码
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
这一步其实就是为了求大于我们设定的容量的最小2的幂数,以这个值作为真正的初始容量,而不是我们设定的值,这是为了随后的位运算的。现在我们解释一下上面的运算:
以cap=13为例,那么n初始=12,n的二进制数为00001100,随后一次右移一位并进行一次与n的或运算,以第一次为例,首先|=右边运算为无符号右移1位,那么右边的值为00000110,与n进行或运算值为00001110,反复运算到最后一步的时候,n=00001111,然后在return的时候便返回了n+1,也就是16.
至此,我们完成了一个空HashMap的初始化,现在这个HashMap已经可以操作了。
5.查看方法逻辑
我们一般使用HashMap的时候,put方法用的比较多,而且他涉及的内容也比较多,现在来定位到HashMap的put方法
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
put方法又调用了putVal方法,并且将参数分解了,key和value没什么好说的,我们来先看一下hash(key)这个方法干了什么
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
如果当前key是null,那么直接返回哈希值0,如果不是null,那就获取当前key的hash值赋值给h,并且返回一个当前key哈希值的高16位与低16位的按位异或值,这样让高位与低位都参与运算的方法可以大大减少哈希冲突的概率。
OK!多出来的三个参数,其中hash值的内容我们已经知道了,但是三个值都不知道有什么用,不要急,我们进入putVal方法
/** * Implements Map.put and related methods. * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
看这上面一堆代码,是不是又开始头疼了,不要怕他,我们一行一行分解他,就会变得很容易了。
第一步还是要看注释,注释已经翻译好了,请享用
/** * 继承于 Map.put. * * @param hash key的hash值 * @param key key * @param value 要输入的值 * @param onlyIfAbsent 如果是 true, 不改变存在的值 * @param evict if false, the table is in creation mode. * @return 返回当前值, 当前值不存在返回null */
然后来看内容
1.创建了几个变量,其中Node是HashMap的底层数据结构,其大致属性如下
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } }
2.判断当前table是否为空,或者table的长度是否为0,同时给tab和n赋值,如果条件成立(当前的HashMap是空的),那就进行resize,并将resize的值赋予tab,把tab数组的长度赋予n,由于篇幅原因,这里不详细解说resize()方法,这个方法内容比较多,在其他文章中也说了很多,今天的重点是说明如何去读源码,而不是HashMap。
3.判断底层数组中当前key值元素的hash值对应的位置有没有元素,如果没有,直接将当前元素放进去即可
4.接上一步,如果底层数组对应位置中已经有值,那就进行其他的一些列操作把数据写入,并返回oldValue。
我们走完整个流程后,总结几个需要注意的点,比如HashMap.put方法里要注意的就是resize,尾插,树与列表之间的转换。
由于篇幅问题,这个方法里的内容,我只是简略的说一下,具体的查看源码的方式和之前大同小异,一步步分析即可。
6.小总结
查看源码的几个技巧
1.Ctrl+左键或Ctrl+Alt+B定位到正确的源码位置
2.查看类里面一些量,有个大概的认识
3.查看构造函数看实例的初始化状况
4.如果代码比较复杂,分解代码,步步为营
5.其他的源码的阅读都可以按照这个套路来分析
四、总结
作者=萌新,如有错误,欢迎指出
阅读源码绝对是每个程序员都需要的技能,即使刚开始很难读懂,也要慢慢去习惯
如果喜欢,欢迎点赞、评论、收藏、关注
-
查看JAVA API以及JAVA源码的方法
2018-06-05 01:27:07在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码 对于java的api,一般是在下面的网址中进行查看 ...在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码
对于java的api,一般是在下面的网址中进行查看
https://docs.oracle.com/javase/8/docs/api/而对于java的源码,我们现在来演示查看nextLine()的源码:
将鼠标放置在希望转跳到源码的函数上,等待系统浮现这个黄色的框
然后点击下面的Open Declaration,即可进入源码的界面
以下是源码的界面:
这种方法虽然可以查看源码,但速度较慢,有什么方法可以快速的查看源码吗?
接下来展示如何通过添加快捷键,快速查看api的源码:
首先:在Eclipse里面从Window --》Peference --》Java --》Installed JREs
就到了如下图所示的界面:
通过双击jre1.8.0_152打开:
选择其中的rt.jar
选择Source Attachment
并选择OK就可以了,然后选择重启eclipse
重新选择你需要查看源码的api,按下F3,即可转跳到源码的位置 -
深入OpenJDK源码全面理解Java类加载器(下 -- Java源码篇)
2021-03-19 18:56:26目录前言一、双亲委派1.1 类加载器结构1.2 双亲委派二、使用步骤1....这篇文章主要从Java源码层面总结一下双亲委派、TCCL的应用等,然后在聊聊自定义类加载器的注意事项。 一、双亲委派 1.1 类加载器前言
在深入openjdk源码全面理解Java类加载器(上 – JVM源码篇)我们分析了JVM是如何启动,并且初始化BootStrapClassLoader的,也提到了sun.misc.Launcher被加载后会创建ExtClassLoader和AppClassLoader。关于类加载的基础知识请参考虚拟机类加载机制(上)。这篇文章主要从Java源码层面总结一下双亲委派、TCCL的应用等,然后再聊聊自定义类加载器的注意事项。
一、双亲委派
1.1 类加载器结构
直接在idea里看看AppClassLoader的继承关系(ExtClassLoader一样):
AppClassLoader和ExtClassLoader都继承自URLClassLoader,URLClassLoader继承自SecureClassLoader,最终继承自ClassLoader。类加载的核心方法以private native定义在ClasssLoader中,只能由ClassLoader调用,所以所有的自定义类加载器都必须直接或间接继承ClassLoader。1.2 双亲委派
加载类的核心方法是loadClass,默认实现在ClassLoader中:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //同步锁,可能是一个和name对应的Object,也可能是this //取决于类加载器是否具备并行能力 //首先检查类是否被本类加载器加载了 Class<?> c = findLoadedClass(name); if (c == null) { //如果没有找到需要加载的类 long t0 = System.nanoTime(); try { //使用父类加载器加载类 //如果parent不为null,说明设置了父加载器,直接用parent if (parent != null) { c = parent.loadClass(name, false); } else { //如果parent为null,使用BootStrapClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //如果父类加载器没能加载到类,使用本类加载器加载 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; } }
注:关于getClassLoadingLock,可参考:关于类加载的并发控制锁。
从loadClass的逻辑中可以很清晰的看到双亲委派的实现:首先查看类是否已经加载,如果未加载则委派给父类加载,如果父类加载器没能加载成功,那么才由本类加载器加载。
通常情况下,所有Java实现的类加载器都是调用ClassLoader的这个loadClass方法,所以本类加载和父类加载器都是这个逻辑:本类加载器委托父类加载器,父类加载器委托租父类加载器等等。顶层类加载器如果无法加载则依次回溯。二、自定义类加载器
自定义类加载器需要直接或间接继承ClassLoader,最简单的一个自定义类加载器就是继承ClassLoader,重写其findClass方法,通过ClassLoader.defineClass方法创建一个Class类(defineClass最终会调用ClassLoader的native方法):
public class MyClassLoader extends ClassLoader { private URLClassPath ucp; public MyClassLoader(String path, ClassLoader parent) throws Exception { super(parent); this.ucp = new URLClassPath(new URL[]{new File(path).toURI().toURL()}); } static { ClassLoader.registerAsParallelCapable(); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String usePath = name.replace('.', File.separatorChar).concat(".class"); Resource resource = ucp.getResource(usePath, false); if (resource != null) { try { byte[] bytes = resource.getBytes(); return defineClass(name, bytes, 0, bytes.length); } catch (IOException var) { return null; } } else { return null; } } }
MyClassLoader从我们指定的路径搜寻类文件,如果没有找到,那么父类ClassLoader的加载逻辑会遵循双亲委派交给我们指定的父类加载器加载,若未指定,那么寻找BootStrapClassLoader。
正是由于我们只重写了findClass方法,类加载的过程还是双亲委派的逻辑,这也是Java官方建议的自定义类加载的方式。但是如果我们需要打破双亲委派规则,就必须重写loadClass方法,比如:public class MyClassLoader2 extends ClassLoader { private URLClassPath ucp; public MyClassLoader2(String path, ClassLoader parent) throws MalformedURLException { super(parent); this.ucp = new URLClassPath(new URL[]{new File(path).toURI().toURL()}); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("com.demo")) { Resource resource = ucp.getResource(name.replace('.', File.separatorChar).concat(".class"), false); if (resource != null) { try { byte[] bytes = resource.getBytes(); Class clazz = defineClass(name, bytes, 0, bytes.length); if (resolve) { resolveClass(clazz); } return clazz; } catch (IOException e) { throw new ClassNotFoundException(e.getMessage()); } } else { throw new ClassNotFoundException(); } } else { return super.loadClass(name, resolve); } } }
这个类加载器对于com.demo包的类都由自己加载,其余的才委托给父类。测试一下:
public class JavaMain { public static void main(String[] args) throws Exception { String classPath = JavaMain.class.getClassLoader().getResource("").getPath(); MyClassLoader2 myClassLoader = new MyClassLoader2(classPath, JavaMain.class.getClassLoader()); Class clazz = myClassLoader.loadClass("com.demo.classloader.TestClass1", false); System.out.println(clazz.getClassLoader()); } } //输出 com.demo.classloader.MyClassLoader2@5cad8086
2.1 全盘委派
在我们的这个工程中,有一个问题,如果运行以下代码:
public static void main(String[] args) throws Exception { String classPath = JavaMain.class.getClassLoader().getResource("").getPath(); MyClassLoader2 myClassLoader = new MyClassLoader2(classPath, JavaMain.class.getClassLoader()); Class clazz = myClassLoader.loadClass("com.demo.classloader.TestClass1", false); System.out.println(clazz.getClassLoader()); System.out.println(TestClass1.class.getClassLoader()); TestClass1 testClass1 = (TestClass1) clazz.newInstance(); } //输出: com.demo.classloader.MyClassLoader2@5cad8086 sun.misc.Launcher$AppClassLoader@18b4aac2 ClassCastException
类型强转操作会抛出java.lang.ClassCastException异常。造成这个的原因已经在输出结果中体现了,clazz是由自定义类加载加载的,而TestClass1.class是由AppClassLoader加载的。可以打印看看AppClassLoaer的加载目录:
System.out.println(System.getProperty("java.class.path"));
在mac下结果如下:
/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/tools.jar:/Users/loren/work/github/test/out/production/test:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
输出的目录包含了当前项目目录,所以目录中的class可以被AppClassLoader加载。
注:除了当前项目目录,还有很多系统jar包,包括rt.jar、jce.jar等,当然由于AppClassLoader遵循双亲委派,路径包含这些jar包也不会有什么问题。
那么TestClass1是什么时候被AppClassLoader加载的呢?对于上述代码中的:
...... 1.Class clazz = myClassLoader.loadClass("com.demo.classloader.TestClass1", false); 2.System.out.println(clazz.getClassLoader()); 3.System.out.println(TestClass1.class.getClassLoader()); ......
当代码执行到第三行打印TestClass1.class.getClassLoader的时候,会检查TestClass1.class是否已经被加载,如果没有加载则需要触发类加载的逻辑。这里需要注意的是,当前类(JavaMain)是被AppClassLoader加载的,它所依赖的类默认也会使用加载当它的类加载器(也就是AppClassLoader)去检查,这个叫做“全盘委派机制”(我也不知道官方是不是叫这个名字)。
为了验证这一点,我们再新建一个TestClass2.java,在构造方法中打印类加载器:public class TestClass2 { public TestClass2() { System.out.println("testClass2.classLoader:" + this.getClass().getClassLoader()); } }
然后在TestClass1中创建一个方法触发TestClass2的实例化:
public class TestClass1 { public void run() { new TestClass2(); } }
由于main方法中使用TestClass1会被AppClassLoader加载,所以我们不能强转类型,只能通过反射调用该方法:
public class JavaMain { public static void main(String[] args) throws Exception { String classPath = JavaMain.class.getClassLoader().getResource("").getPath(); MyClassLoader2 myClassLoader = new MyClassLoader2(classPath, JavaMain.class.getClassLoader()); Class clazz = myClassLoader.loadClass("com.demo.classloader.TestClass1", false); Object obj = clazz.newInstance(); Method method = obj.getClass().getDeclaredMethod("run", null); method.setAccessible(true); method.invoke(obj, null); } }
输出如下:
testClass2.classLoader:com.demo.classloader.MyClassLoader2@5cad8086
2.2 覆盖核心类?
如果用户自定义一个全路径相同的Java核心类,能否有办法覆盖原版呢?正常情况下,根据双亲委派机制是没办法的:根据委派规则,加载动作会委派到BootStrapClassLoader,而BootStrap能加载这些核心类。既然如此,那么我们打破双亲委派尝试一下。
首先在项目中创建一个java.util.HashMap:package java.util; public class HashMap { }
然后创建一个自定义类加载器,这个和之前类似:
public class MyClassLoader3 extends ClassLoader { private URLClassPath ucp; public MyClassLoader3(String path, ClassLoader parent) throws MalformedURLException { super(parent); this.ucp = new URLClassPath(new URL[]{new File(path).toURI().toURL()}); } static { ClassLoader.registerAsParallelCapable(); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Resource resource = ucp.getResource(name.replace('.', File.separatorChar).concat(".class"), false); if (resource != null) { try { byte[] bytes = resource.getBytes(); Class clazz = defineClass(name, bytes, 0, bytes.length); if (resolve) { resolveClass(clazz); } return clazz; } catch (IOException e) { throw new ClassNotFoundException(e.getMessage()); } } else { throw new ClassNotFoundException(); } } }
在main方法中创建自定义类加载器,加载路径为当前项目路径,然后尝试加载java.util.HashMap:
public static void main(String[] args) throws Exception { String classPath = JavaMain.class.getClassLoader().getResource("").getPath(); MyClassLoader3 myClassLoader = new MyClassLoader3(classPath, JavaMain.class.getClassLoader()); Class clazz = myClassLoader.loadClass("java.util.HashMap", false); }
当然不出意外的是,有异常堆栈抛出:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.util at java.lang.ClassLoader.preDefineClass(ClassLoader.java:655) at java.lang.ClassLoader.defineClass(ClassLoader.java:754) at java.lang.ClassLoader.defineClass(ClassLoader.java:635) at com.demo.classloader.MyClassLoader3.loadClass(MyClassLoader3.java:29) at com.demo.classloader.JavaMain.main(JavaMain.java:13)
提示禁止加载包:java.util,看堆栈信息异常是ClassLoader.preDefineClass抛出来的,看看相应的代码:
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd){ ...... if ((name != null) && name.startsWith("java.")) { throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } ...... }
源码写的很清楚,java.打头的包都不允许加载,所以我们项目中建包还是不要以java打头。
既然检查工作是在preDefineClass中完成的,那么我们能否绕过predefineClass方法呢?
现在回到类加载的流程,我们先通过findClass找到需要加载的字节码文件,这一步没有问题。找到字节码文件之后,需要调用defineClass方法生成Class,defineClass定义在ClassLoader中: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; }
该方法是一个final方法,我们无法重写,那能在自定义类加载器中调用defineClass1方法吗?defineClass1方法定义在ClassLoader中,是一个private native方法:
private native Class<?> defineClass0(String name, byte[] b, int off, int len,ProtectionDomain pd); private native Class<?> defineClass1(String name, byte[] b, int off, int len,ProtectionDomain pd, String source); private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,int off, int len, ProtectionDomain pd,String source);
所以我们只能通过父类的defineClass创建Class,也就没法绕过preDefineClass方法的检查。既然如此,那能不能从本地方法入手呢?理论上是可行的,但是需要修改动态链接文件。但是都能操作dll了,还需要费尽心思去覆盖核心类库吗?
三、TCCL
Thread Context ClassLoader(TCCL),即线程上下文类加载器。对于一些场景,可能会需要父类加载器调用子类加载器的情况,一个典型的例子就是SPI。
对于某些功能,比如日志、JDBC等等,Java本身只提供接口,由用户自己实现或选择第三方提供的实现类,这样遵循了可插拔的特性。为了支持这点,Java提供了一种服务发现机制:为一些接口寻找具体的实现类。当作为服务提供者实现了某个服务接口之后,需要在jar包的META-INF/services/目录下创建一个以服务接口全限定名命名的文件,将接口实现类全限定名配置在该文件中。JDK提供了一个根据此规则寻找服务实现者的工具:ServiceLoader。使用ServiceLoader可以找到指定接口的实现类,进而完成服务实现者的加载。
这其中出现的问题就是ServiceLoader是由启动类加载器加载,而服务实现者并不在其能加载的文件允许范围内,于是便出现了冲突。TCCL便能够解决这个问题,Thread类有一个contextClassLoader成员变量:/* The context ClassLoader for this thread */ private ClassLoader contextClassLoader;
通过相应的set方法:
Thread.currentThread().setContextClassLoader(classloader);
将一个类加载器和线程绑定。这样在一个线程中,需要加载当前类加载器无法加载的类的时候,可以从当前线程中获取TCCL进行加载:
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
TCCL默认为AppClassLoader,初次在sun.misc.Launcher的构造方法中设置:
public Launcher() { ...... try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } //TCCL默认为AppClassLoader Thread.currentThread().setContextClassLoader(this.loader); ...... }
四、spring的类加载
对于一个servlet容器来说,还是以Tomcat为例。一个webapps可以同时部署多个应用,而每个应用可能引用相同的jar包,在没有版本冲突的情况下,可以把这些jar包放到shared目录,由SharedClassLoader加载(不考虑高版本合并到lib目录),以达到让每个WebAppClassLoader共享的目的。
对于每个webapp来说,其字节码文件默认由各自的WebAppClassLoader加载。但是像spring这种bean工厂来说,它要管理bean,就要能加载这些类,但是如果spring的jar包放在上层目录,其类加载器是无法加载webapp下的类的,该如何是好呢?
其实这也是一个父类加载器需要反向调用的例子,使用TCCL就可以解决:spring在加载一个类的时候从当前线程获取TCCL,而servlet容器将TCCL设置为WebAppClassLoader。这样不论哪个webapp使用spring,spring使用的都是各自的WebAppClassLoader。就像这样:ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { //spring在webapp下,类加载器相同 currentContext = this.context; } else if (ccl != null) { //加载spring的类加载器和TCCL不同,将classLoader和WebApplicationContext用map //保存起来,用的时候根据classLoader获取context currentContextPerThread.put(ccl, this.context); }
当然,如果在SpringBoot中使用内嵌servlet容器的时候,就不会出现一个servlet容器包含多个应用的情况了,也就不用再用map维护不同的context了,直接使用TCCL即可:
ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable ex) { // Cannot access thread context ClassLoader - falling back... } if (cl == null) { // No thread context class loader -> use class loader of this class. cl = ClassUtils.class.getClassLoader(); if (cl == null) { // getClassLoader() returning null indicates the bootstrap ClassLoader try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable ex) { // Cannot access system ClassLoader - oh well, maybe the caller can live with null... } } } return cl;
-
如何阅读Java源码?
2018-07-30 19:58:00阅读Java源码的前提条件:1、技术基础在阅读源码之前,我们要有一定程度的技术基础的支持。假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃《Core Ja...阅读Java源码的前提条件:
1、技术基础
在阅读源码之前,我们要有一定程度的技术基础的支持。
假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃《Core Java》,那样是很难有收获的,尤其是《深入Java虚拟机》这类书,或许别人觉得好,但是未必适合现在的你。
比如设计模式,许多Java源码当中都会涉及到。再比如阅读Spring源码的时候,势必要先对IOC,AOP,Java动态代理等知识点有所了解。
2、强烈的求知欲
强烈的求知欲是阅读源码的核心动力!
大多数程序员的学习态度分为如下几个层次:
完成自己的项目就可以了,遇到不懂的地方就百度一下。
不仅做好项目,还会去阅读一些和项目有关的书籍。
除了阅读和项目相关的书籍之外,还会阅读一些IT行业相关的书籍。
平时会经常逛逛GitHub,找一些开源项目看看。
阅读基础框架、J2EE规范、源码。
大多数程序员的层次都是在第一层,到第五层的人就需要有强烈的求知欲了。
3、足够的耐心
通过阅读源码我们可以学习大佬的设计思路,技巧。还可以把我们一些零碎的知识点整合起来,从而融会贯通。总之阅读源码的好处多多,想必大家也清楚。
但是真的把那么庞大复杂的代码放到你的眼前时,肯定会在阅读的过程中卡住,就如同陷入了一个巨大的迷宫,如果想要在这个巨大的迷宫中找到一条出路,那就需要把整个迷宫的整体结构弄清楚,比如:API结构、框架的设计图。而且还有理解它的核心思想,确实很不容易。
刚开始阅读源码的时候肯定会很痛苦,所以,没有足够的耐心是万万不行的。
如何读Java源码:
团长也是经历过阅读源码种种痛苦的人,算是有一些成功的经验吧,今天来给大家分享一下。
如果你已经有了一年左右的Java开发经验的话,那么你就有阅读Java源码的技术基础了。
1、建议从JDK源码开始读起,这个直接和eclipse集成,不需要任何配置。
可以从JDK的工具包开始,也就是我们学的《数据结构和算法》Java版,如List接口和ArrayList、LinkedList实现,HashMap和TreeMap等。这些数据结构里也涉及到排序等算法,一举两得。
面试时,考官总喜欢问ArrayList和Vector的区别,你花10分钟读读源码,估计一辈子都忘不了。
然后是core包,也就是String、StringBuffer等。 如果你有一定的Java IO基础,那么不妨读读FileReader等类。
建议大家看看《Java In A Nutshell》,里面有整个Java IO的架构图。Java IO类库,如果不理解其各接口和继承关系,则阅读始终是一头雾水。
Java IO 包,我认为是对继承和接口运用得最优雅的案例。如果你将来做架构师,你一定会经常和它打交道,如项目中部署和配置相关的核心类开发。
读这些源码时,只需要读懂一些核心类即可,如和ArrayList类似的二三十个类,对于每一个类,也不一定要每个方法都读懂。像String有些方法已经到虚拟机层了(native方法),如hashCode方法。
当然,如果有兴趣,可以对照看看JRockit的源码,同一套API,两种实现,很有意思的。
如果你再想钻的话,不妨看看针对虚拟机的那套代码,如System ClassLoader的原理,它不在JDK包里,JDK是基于它的。JDK的源码Zip包只有10来M,它像是有50来M,Sun公司有下载的,不过很隐秘。我曾经为自己找到、读过它很兴奋了一阵。
2、Java Web项目源码阅读
步骤:表结构 → web.xml → mvc → db → spring ioc → log→ 代码
① 先了解项目数据库的表结构,这个方面是最容易忘记的,有时候我们只顾着看每一个方法是怎么进行的,却没有去了解数据库之间的主外键关联。其实如果先了解数据库表结构,再去看一个方法的实现会更加容易。
② 然后需要过一遍web.xml,知道项目中用到了什么拦截器,监听器,过滤器,拥有哪些配置文件。如果是拦截器,一般负责过滤请求,进行AOP等;如果是监听器,可能是定时任务,初始化任务;配置文件有如 使用了spring后的读取mvc相关,db相关,service相关,aop相关的文件。
③ 查看拦截器,监听器代码,知道拦截了什么请求,这个类完成了怎样的工作。有的人就是因为缺少了这一步,自己写了一个action,配置文件也没有写错,但是却怎么调试也无法进入这个action,直到别人告诉他,请求被拦截了。
④ 接下来,看配置文件,首先一定是mvc相关的,如springmvc中,要请求哪些请求是静态资源,使用了哪些view策略,controller注解放在哪个包下等。然后是db相关配置文件,看使用了什么数据库,使用了什么orm框架,是否开启了二级缓存,使用哪种产品作为二级缓存,事务管理的处理,需要扫描的实体类放在什么位置。最后是spring核心的ioc功能相关的配置文件,知道接口与具体类的注入大致是怎样的。当然还有一些如apectj等的配置文件,也是在这个步骤中完成。
⑤ log相关文件,日志的各个级别是如何处理的,在哪些地方使用了log记录日志。
⑥ 从上面几点后知道了整个开源项目的整体框架,阅读每个方法就不再那么难了。
⑦ 当然如果有项目配套的开发文档也是要阅读的。
3、Java框架源码阅读
当然了,就是Spring、MyBatis这类框架。
在读Spring源码前,一定要先看看《J2EE Design and Development》这本书,它是Spring的设计思路。注意,不是中文版,中文版完全被糟蹋了。
想要阅读MyBatis的源码就要先了解它的一些概念,否则云里来雾里去的什么也不懂。有很多人会选择去买一些书籍来帮助阅读,当然这是可取的。那么如果不想的话,就可以去官网查看它的介绍(MyBatis网站:http://www.mybatis.org/mybatis-3/zh/getting-started.html),团长也是按照官网上面的介绍来进行源码阅读的。团长认为MyBatis的亮点就是管理SQL语句。
总结没有人一开始就可以看得懂那些源码,我们都是从0开始的,而且没有什么捷径可寻,无非就是看我们谁愿意花时间去研究,谁的求知欲更强烈,谁更有耐心。阅读源码的过程中我们的能力肯定会提升,可以从中学到很多东西。在我们做项目的时候就会体现出来了,的确会比以前顺手很多。
PS:如果觉得我的分享不错,欢迎大家随手点赞、转发。
Java团长
专注于Java干货分享
扫描上方二维码获取更多Java干货
-
IDEA导入Java源码
2019-02-10 14:13:25第一步: 从安装的jdk目录中找到src.zip文件, 这里是java源码的压缩包 第二步: 打开IDEA, 随便一个java相关项目,打开右上角的project structure 第三步: 点开源码, 选择想看java类就能看到源码了... -
如何阅读Java源码
2018-08-15 11:44:19阅读Java源码的前提条件: 1、技术基础 在阅读源码之前,我们要有一定程度的技术基础的支持。 假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃《Core Java》,那样是很难有收获的,尤其是《深入... -
Java源码转C 源码的五款最佳工具
2018-11-15 04:14:50Java源码转C 源码的五款最佳工具 -
Java源码下载和阅读(JDK1.8/Java 11)
2019-03-26 16:28:33文章目录1.openjdk的Java源码2. Oracle 的Java源码 1.openjdk的Java源码 JDK10的源码可以直接从openjdk上下载.下载地址:openjdk-10_src.zip openjdk上也有jdk8的源码可供下载:openjdk-8u40.zip 2. Oracle 的Java... -
intellij IDEA导入java源码
2019-09-28 20:09:24第一步: 从安装的jdk目录中找到src.zip文件, 这里是java源码的压缩包 第二步: 打开IDEA, 随便一个java相关项目,打开右上角的project structure 第三步: 点开源码, 选择想看java类就能看到源码了 转载于... -
Java--Eclipse关联Java源码
2013-07-17 12:43:40Java--Eclipse关联Java源码 -
Java源码转C#源码的五款最佳工具
2014-12-13 20:54:01Java源码转C#源码的五款最佳工具作者:chszs,转载需注明。博客主页:http://blog.csdn.net/chszs出于某些需要,你可能会遇到把Java源码转换成C#源码的任务。如果是自己一边理解源码,再一边手工翻译,那效率肯定是... -
读java源码的好处
2018-05-08 23:04:07结合自己读源码的体会,大概...而当你阅读了java源码之后,你就会发现其实还可以有更高效的方法来解决问题。2.深入体会设计模式。当你深入java源码的时候,你会发现里面用到了很多设计模式,从而加深了自己在实际的... -
java:通过javadoc API读取java源码中的注释信息(comment)
2017-10-16 18:11:35如何从java源码中读取注释信息?(注意不是指通过反射读取annotation类,是comment,就是程序员在源码中加的注释) 比如: /** * 使用当前类的class loader加载工具对象 * @param classname * @return * @throws... -
宾馆酒店客房管理系统java源码
2016-12-10 21:55:00宾馆酒店客房管理系统java源码数据库课程设计 开发语言:java+jsp 数据库:Mysql 开发工具:myeclipse 界面预览: 源代码下载 -
【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法
2016-04-04 16:44:26注:关于排序算法,博主写过【数据结构排序算法系列】数据...当时回答错了,面试结束后看了一下java源码,用的是折半插入排序算法,本来早就打算写此博客,但是因为准备鹅厂的在线考试,所以一直没动手写。既然java的 -
国内在线查看Java源码(支持下载)
2018-02-08 11:32:32现在的IDE都很强大都能直接通过按Ctrl+鼠标左键查看Java源码,但是当你没有IDE时,查看源码就会变得很困难。这里提供了网上查看Java源码的方法。 首先给出源码地址:https://gitee.com/huangtianyu/j... -
java源码阅读方法以及经验
2014-11-29 15:43:44如何更好的阅读java源码,更注重阅读哪些包里面的源码,当然连好的阅读源码的工具也说明一下更好了 -
Android JavaPoet 动态生成Java源码(1)
2016-07-09 10:25:30具体详情请查看:点击下方标题或者查看原文Android JavaPoet 动态生成Java源码(1)公众号查看原文已同步到CSDN:查看CSDN原文文章出处: Coolspan CSDN博客:http://blog.csdn.net/qxs965266509 欢迎关注公众号:... -
Eclipse导入SDK源码的方法---包含java源码和android源码
2015-11-21 15:08:49Eclipse导入SDK源码的方法---包含java源码和android源码 -
如何查看java源码中的native方法源码
2017-08-25 09:45:26Java源码中的native方法是不能直接在jdk中看到的,要看的话就去下载完整的OpenJDK源码包,我这里看的是openjdk8的源码举个例子吧 我要看String类的intern方法的实现public native String intern();上个图一目了然,... -
IDEA查看Java源码技巧
2018-11-09 15:41:071 查看接口的实现类:Ctrl+Alt+B 选中按快捷键,然后跳到实现类的地方去 ...3 查看Java方法调用树(被调/主调):Ctrl+Alt+H 分为调用当前方法的树、当前方法调用的下级方法 4 查看表... -
手把手带你用idea搭建Java源码(JDK源码)阅读调试环境
2020-03-26 21:28:46手把手带你用idea搭建Java源码(JDK源码)阅读调试环境 写在前面:操作系统win10,jdk1.8,idea 2019.1 需自行安装jdk 1.找到自己电脑上的jdk安装位置 每个人的jdk安装位置不一样,请自行找到安装jdk的位置。 安装... -
java源码解析JavaParser
2018-08-26 20:21:31package ... import java.io.File; import java.io.FileNotFoundException; import com.github.javaparser.JavaParser; import com.github.javaparser.ast.CompilationUnit; public class Jpars... -
《Java 源码分析》:Java NIO 之 SelectionKey
2016-10-19 21:45:17《Java 源码分析》:Java NIO 之 SelectionKey在ServerSocketChannel源码分析中我们知道当把一个channel注册到指定的Selector上时, 实际上就是将(channel,selector)封装成了一个SelectionKey对象,并将此对象保存... -
修改java源码无需重启tomcat
2015-03-26 22:28:00修改java源码无需重启tomcat -
手机销售系统(Java源码)
2016-10-15 13:37:17手机销售系统(Java源码)