Just Do Java

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

手把手教你,本地搭建虚拟机部署微服务

发表于 2020-06-11 | 分类于 虚拟机

关于虚拟机这块以前玩的也很多,但很少总结,容易遗忘,今天索性一条龙总结搞定!

阅读全文 »

高效读取大文件,再也不用担心 OOM 了!

发表于 2020-06-10 | 分类于 Java

最近阿粉接到一个需求,需要从文件读取数据,然后经过业务处理之后存储到数据库中。这个需求,说实话不是很难,阿粉很快完成了第一个版本。

阅读全文 »

Read Large File

发表于 2020-06-10
高效读取大文件,再也不用担心 OOM 了!

最近阿粉接到一个需求,需要从文件读取数据,然后经过业务处理之后存储到数据库中。这个需求,说实话不是很难,阿粉很快完成了第一个版本。

内存读取

第一个版本,阿粉采用内存读取的方式,所有的数据首先读读取到内存中,程序代码如下:

Stopwatch stopwatch = Stopwatch.createStarted();
// 将全部行数读取的内存中
List<String> lines = FileUtils.readLines(new File("temp/test.txt"), Charset.defaultCharset());
for (String line : lines) {
    // pass
}
stopwatch.stop();
System.out.println("read all lines spend " + stopwatch.elapsed(TimeUnit.SECONDS) + " s");
// 计算内存占用
logMemory();

logMemory方法如下:

MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
//椎内存使用情况
MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage();
//初始的总内存
long totalMemorySize = memoryUsage.getInit();
//已使用的内存
long usedMemorySize = memoryUsage.getUsed();

System.out.println("Total Memory: " + totalMemorySize / (1024 * 1024) + " Mb");
System.out.println("Free Memory: " + usedMemorySize / (1024 * 1024) + " Mb");

上述程序中,阿粉使用 Apache Common-Io 开源第三方库,FileUtils#readLines将会把文件中所有内容,全部读取到内存中。

这个程序简单测试并没有什么问题,但是等拿到真正的数据文件,运行程序,很快程序发生了 OOM。

之所以会发生 OOM,主要原因是因为这个数据文件太大。假设上面测试文件 test.txt总共有 200W 行数据,文件大小为:740MB。

通过上述程序读取到内存之后,在我的电脑上内存占用情况如下:

可以看到一个实际大小为 700 多 M 的文件,读到内存中占用内存量为 1.5G 之多。而我之前的程序,虚拟机设置内存大小只有 1G,所以程序发生了 OOM。

当然这里最简单的办法就是加内存呗,将虚拟机内存设置到 2G,甚至更多。不过机器内存始终有限,如果文件更大,还是没有办法全部都加载到内存。

不过仔细一想真的需要将全部数据一次性加载到内存中?

很显然,不需要!

在上述的场景中,我们将数据到加载内存中,最后不还是一条条处理数据。

所以下面我们将读取方式修改成逐行读取。

逐行读取

逐行读取的方式比较多,这里阿粉主要介绍两种方式:

  • BufferReader

  • Apache Commons IO

  • Java8 stream

BufferReader

我们可以使用 BufferReader#readLine 逐行读取数据。

try (BufferedReader fileBufferReader = new BufferedReader(new FileReader("temp/test.txt"))) {
    String fileLineContent;
    while ((fileLineContent = fileBufferReader.readLine()) != null) {
        // process the line.
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Apache Commons IO

Common-IO 中有一个方法 FileUtils#lineIterator可以实现逐行读取方式,使用代码如下:

Stopwatch stopwatch = Stopwatch.createStarted();
LineIterator fileContents = FileUtils.lineIterator(new File("temp/test.txt"), StandardCharsets.UTF_8.name());
while (fileContents.hasNext()) {
    fileContents.nextLine();
    //  pass
}
logMemory();
fileContents.close();
stopwatch.stop();
System.out.println("read all lines spend " + stopwatch.elapsed(TimeUnit.SECONDS) + " s");

这个方法返回一个迭代器,每次我们都可以获取的一行数据。

其实我们查看代码,其实可以发现 FileUtils#lineIterator,其实用的就是 BufferReader,感兴趣的同学可以自己查看一下源码。

由于公号内无法插入外链,关注『Java 极客技术』,回复『20200610』 获取源码

Java8 stream

Java8 Files 类新增了一个 lines,可以返回 Stream我们可以逐行处理数据。

Stopwatch stopwatch = Stopwatch.createStarted();
// lines(Path path, Charset cs)
try (Stream<String> inputStream = Files.lines(Paths.get("temp/test.txt"), StandardCharsets.UTF_8)) {
    inputStream
            .filter(str -> str.length() > 5)// 过滤数据
            .forEach(o -> {
                // pass do sample logic
            });
}
logMemory();
stopwatch.stop();
System.out.println("read all lines spend " + stopwatch.elapsed(TimeUnit.SECONDS) + " s");

使用这个方法有个好处在于,我们可以方便使用 Stream 链式操作,做一些过滤操作。

注意:这里我们使用 try-with-resources 方式,可以安全的确保读取结束,流可以被安全的关闭。

并发读取

逐行的读取的方式,解决我们 OOM 的问题。不过如果数据很多,我们这样一行行处理,需要花费很多时间。

上述的方式,只有一个线程在处理数据,那其实我们可以多来几个线程,增加并行度。

下面在上面的基础上,阿粉就抛转引玉,介绍下阿粉自己比较常用两种并行处理方式。

逐行批次打包

第一种方式,先逐行读取数据,加载到内存中,等到积累一定数据之后,然后再交给线程池异步处理。

@SneakyThrows
public static void readInApacheIOWithThreadPool() {
    // 创建一个 最大线程数为 10,队列最大数为 100 的线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 60l, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100));
    // 使用 Apache 的方式逐行读取数据
    LineIterator fileContents = FileUtils.lineIterator(new File("temp/test.txt"), StandardCharsets.UTF_8.name());
    List<String> lines = Lists.newArrayList();
    while (fileContents.hasNext()) {
        String nextLine = fileContents.nextLine();
        lines.add(nextLine);
        // 读取到十万的时候
        if (lines.size() == 100000) {
            // 拆分成两个 50000 ,交给异步线程处理
            List<List<String>> partition = Lists.partition(lines, 50000);
            List<Future> futureList = Lists.newArrayList();
            for (List<String> strings : partition) {
                Future<?> future = threadPoolExecutor.submit(() -> {
                    processTask(strings);
                });
                futureList.add(future);
            }
            // 等待两个线程将任务执行结束之后,再次读取数据。这样的目的防止,任务过多,加载的数据过多,导致 OOM
            for (Future future : futureList) {
                // 等待执行结束
                future.get();
            }
            // 清除内容
            lines.clear();
        }

    }
    // lines 若还有剩余,继续执行结束
    if (!lines.isEmpty()) {
        // 继续执行
        processTask(lines);
    }
  threadPoolExecutor.shutdown();
}
    private static void processTask(List<String> strings) {
        for (String line : strings) {
            // 模拟业务执行
            try {
                TimeUnit.MILLISECONDS.sleep(10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

上述方法,等到内存的数据到达 10000 的时候,拆封两个任务交给异步线程执行,每个任务分别处理 50000 行数据。

后续使用 future#get(),等待异步线程执行完成之后,主线程才能继续读取数据。

之所以这么做,主要原因是因为,线程池的任务过多,再次导致 OOM 的问题。

大文件拆分成小文件

第二种方式,首先我们将一个大文件拆分成几个小文件,然后使用多个异步线程分别逐行处理数据。

public static void splitFileAndRead() throws Exception {
    // 先将大文件拆分成小文件
    List<File> fileList = splitLargeFile("temp/test.txt");
    // 创建一个 最大线程数为 10,队列最大数为 100 的线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 60l, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100));
    List<Future> futureList = Lists.newArrayList();
    for (File file : fileList) {
        Future<?> future = threadPoolExecutor.submit(() -> {
            try (Stream inputStream = Files.lines(file.toPath(), StandardCharsets.UTF_8)) {
                inputStream.forEach(o -> {
                    // 模拟执行业务
                    try {
                        TimeUnit.MILLISECONDS.sleep(10L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        futureList.add(future);
    }
    for (Future future : futureList) {
        // 等待所有任务执行结束
        future.get();
    }
    threadPoolExecutor.shutdown();


}

private static List<File> splitLargeFile(String largeFileName) throws IOException {
    LineIterator fileContents = FileUtils.lineIterator(new File(largeFileName), StandardCharsets.UTF_8.name());
    List<String> lines = Lists.newArrayList();
    // 文件序号
    int num = 1;
    List<File> files = Lists.newArrayList();
    while (fileContents.hasNext()) {
        String nextLine = fileContents.nextLine();
        lines.add(nextLine);
        // 每个文件 10w 行数据
        if (lines.size() == 100000) {
            createSmallFile(lines, num, files);
            num++;
        }
    }
    // lines 若还有剩余,继续执行结束
    if (!lines.isEmpty()) {
        // 继续执行
        createSmallFile(lines, num, files);
    }
    return files;
}

由于公号内无法插入外链,关注『Java 极客技术』,回复『20200610』 获取源码

上述方法,首先将一个大文件拆分成多个保存 10W 行的数据的小文件,然后再将小文件交给线程池异步处理。

由于这里的异步线程每次都是逐行从小文件的读取数据,所以这种方式不用像上面方法一样担心 OOM 的问题。

另外,上述我们使用 Java 代码,将大文件拆分成小文件。这里阿粉还有一个简单的办法,我们可以直接使用下述命令,直接将大文件拆分想小文件:

 # 将大文件拆分成 100000 的小文件
 split -l 100000 test.txt

后续 Java 代码只需要直接读取小文件即可。

总结

当我们从文件读取数据时,如果文件不是很大,我们可以考虑一次性读取到内存中,然后快速处理。

如果文件过大,我们就没办法一次性加载到内存中,所以我们需要考虑逐行读取,然后处理数据。但是单线程处理数据毕竟有限,所以我们考虑使用多线程,加快处理数据。

本篇文章我们只是简单介绍了下,数据从文件读取几种方式。数据读取之后,我们肯定还需要处理,然后最会会存储到数据库中或者输出到另一个文件中。

这个过程,说实话比较麻烦,因为我们的数据源文件,可能是 txt,也可能是 excel,这样我们就需要增加多种读取方法。同样的,当数据处理完成之后,也有同样的问题。

不过好在,上述的问题我们可以使用 Spring Batch 完美解决。

下一篇文章我们就来看看 Spring Batch 如何解决,敬请期待~

阅读全文 »

荐剧:如果你是柯南迷,这部剧场版一定要再看看

发表于 2020-06-10 | 分类于 程序人生

一部阿粉非常喜欢的剧场版

阅读全文 »

学习Redis好一阵了,我对它有了一些新的看法

发表于 2020-06-08

前言

本篇文章不是一篇具体的教程,阿粉打算记录一下自己对Redis的一些思考。说来惭愧,阿粉刚接触Redis的时候只是简单地使用了一下,背了一些面试题,就在简历上写下了Redis这个技能点。

我们能在网络上轻易地找到关于Redis具体知识点的讲解,但很少有文字说明为什么会有这项技术,阿粉希望通过本文总结一下个人目前对Redis的理解。

阅读全文 »

【Spring 二】面试官:兄弟你来阐述一下Spring框架中Bean的生命周期?

发表于 2020-06-07

前几天在阿粉CSDN上看一个文章,因为一个Spring的问题,期望薪资三万却被生生的压榨成了两万五,高于两万五人家都不要,让我感觉到了Spring的强大,不学习Spring是会吃亏的,那么我们就从各种高频面试来一点点深入吧。

阅读全文 »

关于参数合法性验证,阿粉有话要说

发表于 2020-06-04 | 分类于 springboot

如何优雅的进行参数验证,需要了解的都在这里!

阅读全文 »

面试的时候按照这个套路回答 Java GC 的相关问题一定能过!

发表于 2020-06-04 | 分类于 Java

Hello 大家好,我是鸭血粉丝,2020 注定是一个不平凡的一年,很多小伙伴在后台和星球留言都说今年的工作不好找,也有应届生小伙伴给阿粉发消息问阿粉所在的公司今年是否招应届生,阿粉也只能帮小伙伴问问 HR,但是阿粉也做不了主。

刚好前几天一个小伙伴在微信上问阿粉,说是面试一家公司被问到 Java GC 相关的东西,虽然平时也有准备,但是回答起来总是零零散散,感觉没有逻辑。其实阿粉也能理解,现在面试都是一种问答模式,一个随便问问,一个死记硬背,很多时候面试前准备的好好的一到面试的时候可能紧张就给忘了,事后感觉自己表现的不好。

这篇文章给大家举个例子,在遇到一个问题或者知识点的时候要怎么去理解和学习。

Java GC

目标

遇到一个问题或者一个知识点,我们要理解和明白是要解决什么问题的。说到 Java GC 那这个 GC 的目的是什么呢?很显然是回收内存,因为内存是有限的,随着程序中创建的对象越来越多,如果进行回收就会导致内存越来越大,最后程序就会出现异常。既然目的是为了回收内存,那么新的问题来了,哪些对象可以被回收呢?什么时候进行回收呢?怎么回收呢?

哪些对象可以被回收

简单来说就是无用的对象可以被回收,那么换句话说,如果定义一个对象是无用的呢?这里主要有两种方法,一个叫引用计数法,一个叫可达性分析法。

引用计数

引用计数说的是如果一个对象被别的对象进行了一次引用,那么该对象会有一个引用计数器,这个计数器就会加一;如果被释放一下,引用计数器就会减一。当引用计数器的计数为 0 的时候就表示这个对象是无用的,此时就可以对这样对象进行回收了。表面上看好像挺合理的,实现起来也很方便,但是仔细一想就会发现有问题。既循环引用的问题,比如对象 A 引用了对象 B,但是对象 B 当中也引用了对象 A,那么这个时候对象 A 和对象 B 的引用计数器的计数都不会是 0,但是这两个对象都没有被其他对象引用,理论上来说这两个对象都是可以被回收的。

从上面看到,这种方案是有问题的会导致内存泄露。随之而来的就出现了另一种方案,可就是可达性分析。

可达性分析

可达性分析说的是从 GCRoots 的点作为起点,向下搜索,当找不到任何引用链的时候表示该对象为垃圾对象。那么哪些对象可以被认为是 Roots 节点呢?有 Java 栈中的对象,方法区的静态属性和常量以及本地方法栈中的对象。从这几种对象依次向下搜索,如果没有能达到 Roots 节点的对象就是垃圾对象,就说明可以被回收。

如下图所有,对象 A,B,C都能找到与 Roots 节点的联系,但是对象 D,E,F 三个并不能找到与 Roots 节点的联系,也就是不可达,所以 DEF 这三个对象就是垃圾对象。

什么时候回收

上面的两种方案解决了哪些对象能被回收,那么下个问题,就是什么时候进行垃圾回收呢?在排除人为调用的时候,垃圾回收都是发生在为新生对象进行内存分配的时候,这个时候如果内存空间不足就会触发 GC 进行垃圾回收。

怎么回收

上面我们知道了哪些对象可以被回收,也知道我们应该什么时候进行回收,那下面要解决的就是如何进行垃圾回收了。垃圾回收根据实现的方式不同有多种不同的算法实现。比如有标记清除算法,复制算法,标记整理算法,分代回收算法,下面简单介绍一下,想深入了解的可以自行去研究一下。

标记清除算法

标记清除算法很好理解,主要就是执行两个动作,一个是标记,另一个是对进行标记的对象内存进行清除回收。这个算法有个问题就是会出现内存碎片化严重。如下图所示:

从上图中可以看到,在进行内存回收后出现了严重的内存碎片化,这就导致在分配某些大对象的时候仍然会出现内存不够的情况,但是总体内存确是够的。

复制算法

复制算法的实现方式比较简洁明了,就是霸道的把内存分成两部分,在平时使用的时候只用其中的固定一份,在当需要进行 GC 的时候,把存活的对象复制到另一部分中,然后将已经使用的内存全部清理掉。如下图:

从上图可以看到解决了标记清除的内存碎片化问题,但是很明显复制算法有另一个问题,那就是内存的使用率大大下降,能使用的内存只有原来的一半了。

标记整理算法

既然标记清除和复制算法各有优缺点,那自然的我们就想到是否可以把这两种算法结合起来,于是就出现了标记整理算法。标记阶段是标记清除算法一样,先标记出需要回收的部分,不过清除阶段不是直接清除,而是把存活的对象往内存的一端进行移动,然后清除剩下的部分。如下图:

标记整理的算法虽然可以解决上面两个算法的一些问题,但是还是需要先进行标记,然后进行移动,整个效率还是偏低的。

分代回收算法

分代回收算法是目前使用较多的一种算法,这个不是一个新的算法,只是将内存进行的划分,不同区域的内存使用不同的算法。根据对象的存活时间将内存的划分为新生代和老年代,其中新生代包含 Eden 区和 S0,S1。在新生代中使用是复制算法,在进行对象内存分配的时候只会使用 Eden 和 S0 区,当发生 GC 的时候,会将存活的对象复制到 S1 区,然后循环往复进行复制。当某个对象在进行了 15 次GC 后依旧存活,那这个对象就会进入老年代。老年代因为每次回收的对象都会比较少,因此使用的是标记整理算法。

垃圾回收器

讲完了垃圾回收算法,我们再看下垃圾回收器,每一种垃圾回收器都是不同时代的不同产物,都有其独特性。

  • Serial 垃圾收集器(单线程、复制算法)
  • ParNew垃圾收集器(Serial+多线程)
  • Parallel Scavenge 收集器(多线程复制算法、高效)
  • SerialOld收集器(单线程标记整理算法)
  • ParallelOld收集器(多线程标记整理算法)
  • CMS收集器(多线程标记清除算法)
  • G1收集器

各个垃圾收集器的配合使用情况可以参考下图,个人觉得对这么多的收集器没有必要全部精通,可以注重关注一下 CMS 和 G1 就可以了。感兴趣的小伙伴可以自己的研究一下。

关于本文的高清脑图已经放到公众号上面了,欢迎关注公众号回复【gc】获得。

写在最后


最后邀请你加入我们的知识星球,这里有 1700+ 优秀的人与你一起进步,如果你是小白那你是稳赚了,很多业内经验和干活分享给你;如果你是大佬,那可以进来我们一起交流分享你的经验,说不定日后我们还可以有合作,给你的人生多一个可能。

阅读全文 »

阿粉写了八千多字,就是为了把 ReentrantLock 讲透

发表于 2020-05-31 | 分类于 Java并发

关于 ReentrantLock 看这篇文章就够了

阅读全文 »

AQS 是个啥?

发表于 2020-05-30 | 分类于 Java并发

啥?你连 AQS 是啥都不知道?

阅读全文 »

MySQL如何有效的存储IP地址?

发表于 2020-05-27 | 分类于 java

前几天,阿粉的一个朋友去面试,他回来告诉我,面试官问他 IP 地址是怎么存在数据库的?他当时也没多想,直接就回答的存字符串啊(心想:这么简单的问题,怕不是看不起我吧)

阅读全文 »

网传三星手机大半夜黑屏乱码,原因竟然是闰四月?

发表于 2020-05-26 | 分类于 Java

Hello 大家好,我是鸭血粉丝,今年闰四月,所以阿粉非常羡慕那些农历四月过生日的朋友,因为今年可以过两个,想想都很激动,但是激动归激动,最激动应该是那些用着三星手机的用户了,因为一个闰四月让他们的手机差点又“爆炸”了。

最近微博上有网友曝出自己的三星手机在 5 月 23 号凌晨的时候突然出现黑屏乱码,自动循环重启的问题。维修手机都用户都需要排队了,客服表示是农历闰年 4 月计算逻辑错误的原因(这个锅不知道哪个程序员要背了),而且升级后数据可能会丢失。

微博上很多用户都表示自己的手机出现了这个问题,出现如下乱码(感觉回到了交互模式的年代,有没有很亲切啊)。

就连专柜里面的手机也没有幸免,堪称尴尬翻车现场,感觉就像出师未捷身先死。

问题出现不就后三星官方微博就发了通知,表示收到了反馈,积极排查中,有点略显尴尬,就像聊天的过程中等了半天等来了一句“嗯,我知道,你等等”。

有网友提供了一个临时解决方案如下,但是阿粉没有用三星手机,不知道是否有效果,只能说慎用。保证安全还是到官方售后去维修吧。

重启之后双击电源键,来到拍照界面,这时候下拉通知栏,点击设置按钮,搜索时间,把日期往前改一个月,就可以解决了,亲测有效。然后就静候官方修复吧。

写在最后


最后邀请你加入我们的知识星球,这里有 1700+ 优秀的人与你一起进步,如果你是小白那你是稳赚了,很多业内经验和干活分享给你;如果你是大佬,那可以进来我们一起交流分享你的经验,说不定日后我们还可以有合作,给你的人生多一个可能。

阅读全文 »

为什么会有重排序?和 happens-before 有啥关系

发表于 2020-05-26 | 分类于 Java并发

小朋友,你是不是有很多问号?

阅读全文 »

mybatis系列之mapper接口

发表于 2020-05-24 | 分类于 Java

hello~各位读者好,我是鸭血粉丝(大家可以称呼我为「阿粉」)。今天,阿粉带着大家来了解一下 mybatis 接口的创建。

1.上期回顾

首先,我们还是回顾一下上篇文件的类容。先看下这个测试类,大家还有印象吗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MybatisTest {
    @Test
    public void testSelect() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            FruitMapper mapper = session.getMapper(FruitMapper.class);
            Fruit fruit = mapper.findById(1L);
            System.out.println(fruit);
        } finally {
            session.close();
        }
    }
}

上篇源码分析讲了 mybatis 一级缓存的实现原理。这次,我们来了解下 mybatis 接口的创建。

2. mapper接口的创建流程

2.1 SqlSession的getMapper()

首先,我们来看下 FruitMapper mapper = session.getMapper(FruitMapper.class); 这段代码,意思很简单,根据传入的class 获取这个对象的实例。这个流程有点复杂,阿粉带着大家来跟下源码:

首先还是ctrl + 左键点击 getMapper 方法,然后会进入到 SqlSession 的 getMapper() 方法。然后之前阿粉也带着大家了解了, SqlSession 的默认实现类是 DefaultSqlSession ,所以我们直接看下 getMapper() 在 DefaultSqlSession 里面的实现:

1
2
3
4
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

2.2 Configuration 的getMapper()

这里从 configuration 里面去获取, configuration 是全局配置对象,也就是上下文。参数 this 是当前的SqlSession 对象,继续跟进去看下:

1
2
3
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

2.3 MapperRegistry 的getMapper()

mapperRegistry 对象是干什么的呢?继续点进去:

1
2
3
4
5
6
7
8
9
10
11
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

这里就不好看懂了,需要先看下了解下 MapperRegistry 这个类,我们一步一步来,跟着阿粉的思路走:

1
2
3
4
5
6
7
8
9
10
public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
    ...
}

了解一个类,首先看下成员变量和构造方法。这里 config 不用多说了吧,主要的是 knownMappers 这个成员变量。这就是个map 对象,只是这个 map 对象的 value值是个对象,所以又要去看下 MapperProxyFactory 这个对象,点进去:

1
2
3
4
5
6
7
8
9
public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
    ...
}

首先,单独看下这个类名 MapperProxyFactory ,取名是很有学问的,好的名字让你一下就知道是干啥的。所以一看 MapperProxyFactory ,首先就会联想到工厂模式,工厂模式是干啥的?创建对象的,创建什么对象呢?创建 MapperProxy 对象的。 MapperProxy 也是有玄机的,Proxy 的是什么?看到这个一般都是使用代理模式来创建代理对象的。所以就很清楚了, MapperProxyFactory 这个类就是个工厂,创建的是 mapper 的代理对象。

然后这个类里面存的是 mapper 的接口和接口里面的方法。

最后,我们回到 MapperRegistry 类里面的 getMapper() 方法。现在是不是要清楚一些,通过 mapper 接口去 map 里面获取工厂类 MapperProxyFactory ,然后通过工厂类去创建我们的 mapper 代理对象。然后在看下 getMapper() 方法里面的 mapperProxyFactory.newInstance(sqlSession); 这段代码,继续点进去:

1
2
3
4
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

你看,阿粉猜测对不对,MapperProxy 对象是不是出来了。然后看 newInstance() 这个方法:

1
2
3
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

两个 newInstance() 方法都在MapperProxyFactory 这个类里面,这里就很明显嘛。典型的 JDK 代理对象的创建。

好了,到这里我们的 mapper 对象就获取到了。大家可以想一想,为什么获取一个 mapper 对象会那么复杂?或者说 mapper 对象有什么作用?其实就是为了通过 mapper 接口的方法获取到 mapper.xml 里面的 sql,具体怎么获取的,请允许阿粉卖个关子,请听阿粉下回分解。

3.总结

最后,阿粉以一个时序图来结束本篇文章,喜欢的话,记得点个赞哦。么么哒~

阅读全文 »

女生到底要不要做一个程序媛

发表于 2020-05-24 | 分类于 程序人生

一起来探讨下

阅读全文 »

面试官因为线程池,让我出门左拐!

发表于 2020-05-21

前几天阿粉的朋友面试,在面试的时候,面试官问到了框架,项目,JVM还有一些关于线程池的内容,而这个线程池,让阿粉的朋友分分钟被面试官吊打,只能出门左拐,地铁站回家了。为什么呢?因为线程池他是真的没有下功夫去准备,只能凉凉了。

阅读全文 »

程序员的520和521是这个样子的?

发表于 2020-05-20

之前看一个“伪科学”专家的各种推算,说中国的“单身汪”中,程序员在其中占了非常大的占比,在阿粉看来,这明显就是撒谎,毕竟阿粉作为一名程序员,不但生活美满,还猫狗双全。

阅读全文 »

通过几段 Java 代码理解 RPC

发表于 2020-05-13 | 分类于 Java

RPC 远程过程调用可以说是分布式系统的基础,本文将通过 Java 演示一次普通的 rpc 调用到底发生了什么。

阅读全文 »

常用的 JDK 命令,你知道几个?

发表于 2020-05-11 | 分类于 Java

Hello 大家好,我是鸭血粉丝,不知道你有没有过这样的经历,经常在面试的时候被问到 JDK 相关的命令,如何排查线上的问题,线上程序突然崩了要怎么处理,等等类似这种场景。其实并不是每个开发人员都能有这种实战经验,现实工作中往往很多开发人员是接触不到线上环境的。但是作为一个以 Java 谋生的程序员,如果连这些 JDK 自带的一些命令都不知道,那也说不过去。

阿粉以前也是这样,从来没有接触过线上环境,有任何问题都是运维人员去处理,因为相关制度不允许开发人员接触生产服务器。但是作为一个有梦想的开发人员,不能接触不代表就不用学,阿粉还是私下好好学习了一波,万一哪天就用上了呢。

img

阅读全文 »

Java 生态圈中的嵌入式数据库,哪家强?

发表于 2020-05-07 | 分类于 数据库

嵌入式数据库一个很陌生的词汇,以前只是听说,但是没有真正使用过,今天小编和大家一起来揭开它的面纱。

阅读全文 »
1 … 11 12 13 … 31
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

610 日志
116 分类
24 作者
RSS
GitHub 知乎
Links
  • 纯洁的微笑
  • 沉默王二
  • 子悠
  • 江南一点雨
  • 炸鸡可乐
  • 郑璐璐
  • 程序通事
  • 懿
© 2019 - 2022 Java Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist