2024年9月


title: Nuxt Kit 中的模板处理
date: 2024/9/20
updated: 2024/9/20
author:
cmdragon

excerpt:
摘要:本文详细介绍了在Nuxt 3框架中,使用Nuxt Kit进行模板处理的方法,包括理解模板基本概念、使用addTemplate动态生成文件、应用addTypeTemplate注册类型模板以增强TypeScript支持,以及利用updateTemplates实现模板的自动更新。

categories:

  • 前端开发

tags:

  • Nuxt 3
  • 模板处理
  • Nuxt Kit
  • 代码生成
  • 开发效率
  • 文件生成
  • 类型模板


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

在 Nuxt 3 中,模板是扩展项目功能的一种非常强大的工具。利用 Nuxt Kit 提供的功能,你可以在开发和构建过程中生成额外的文件,这不仅可以提高开发效率,还能让你的代码更加整洁。

1. 理解模板的基本概念

模板允许在 Nuxt 应用中动态生成文件。这些生成的文件可以是插件、布局、组件等。模板的使用可以降低重复代码,提高灵活性。

模板的主要类型

  • addTemplate
    : 在构建时将模板文件添加到项目的
    buildDir
  • addTypeTemplate
    : 在构建期间将模板注册为类型。
  • updateTemplates
    : 重新生成与特定筛选器匹配的模板。

2. 使用
addTemplate
方法

2.1 准备工作

首先,确保你已经创建了一个 Nuxt 3 项目。如果你还没有项目,可以用以下命令创建一个新的 Nuxt 项目:

npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install

2.2 创建示例模块

在项目的根目录下,创建一个新的模块,例如
meta-module.ts
。在这个模块中,我们将使用
addTemplate
来生成一个 meta 配置文件。

示例代码

// meta-module.ts
import { addTemplate, defineNuxtModule } from '@nuxt/kit';
import { defu } from 'defu';

export default defineNuxtModule({
  setup(options, nuxt) {
    const globalMeta = defu(nuxt.options.app.head, {
      charset: 'utf-8',
      viewport: 'width=device-width, initial-scale=1',
      title: 'My Nuxt App'
    });

    addTemplate({
      filename: 'meta.config.mjs',
      getContents: () => {
        return 'export default ' + JSON.stringify({ globalMeta });
      }
    });
  }
});

2.3 注册模块

你需要在
nuxt.config.ts
中注册你的模块:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    './meta-module.ts'  // 路径根据实际文件位置调整
  ]
});

2.4 运行项目

运行以下命令以启动开发服务器:

npx nuxi dev

当你运行项目时,Nuxt 会将模板生成到
buildDir
中。

3. 使用
addTypeTemplate
方法

3.1 创建类型模板

你可以通过
addTypeTemplate
来注册类型模板,这样可以为 TypeScript 提供支持。

创建另一个模块文件
type-template-module.ts
,示例代码如下:

// type-template-module.ts
import { addTypeTemplate, defineNuxtModule } from '@nuxt/kit';

export default defineNuxtModule({
  setup() {
    addTypeTemplate({
      filename: 'types/markdown.d.ts',
      getContents: () => `
      declare module '*.md' {
        import type { ComponentOptions } from 'vue';
        const Component: ComponentOptions;
        export default Component;
      }
      `
    });
  }
});

3.2 注册类型模板模块


nuxt.config.ts
中将类型模板模块注册:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    './meta-module.ts',
    './type-template-module.ts'  // 路径根据实际文件位置调整
  ]
});

4. 更新模板

4.1 重新生成模板

利用
updateTemplates
方法,可以根据特定条件重新生成模板。在这个示例中,我们将在文件发生变化时更新模板。

在项目的根目录下创建
watch-module.ts
文件:

// watch-module.ts
import { defineNuxtModule, updateTemplates } from '@nuxt/kit';
import { resolve } from 'path';

export default defineNuxtModule({
  setup(options, nuxt) {
    // 当页面之一发生更改时,监视并重新构建模板列表
    nuxt.hook('builder:watch', async (event, relativePath) => {
      if (event === 'change') { return; }

      const path = resolve(nuxt.options.srcDir, relativePath);
      if (path.startsWith(nuxt.options.srcDir)) {
        await updateTemplates({
          filter: template => template.filename === 'meta.config.mjs'
        });
      }
    });
  }
});

4.2 注册监视模块


nuxt.config.ts
中注册监视模块:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    './meta-module.ts',
    './type-template-module.ts',
    './watch-module.ts'  // 路径根据实际文件位置调整
  ]
});

5. 测试并验证

  1. 启动开发服务器,确保一切正常运行。
  2. 修改一个与模板相关联的文件,确认模板在修改后能自动更新。

6. 最佳实践与总结

  • 模块化
    : 将功能分散到不同的模块中,提高可维护性。
  • 类型支持
    : 使用
    addTypeTemplate
    为你的项目提供更好的类型支持。
  • 自动更新
    : 通过监视文件变化自动更新模板,提升开发效率。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
Nuxt Kit 中的模板处理 | cmdragon's Blog

往期文章归档:

现象

用户反馈 hs2 打开的文件描述符的数量一直在涨,但是当前 hs2 的连接只有个位数。

wecom-temp-86668-466aa580e0ca33aef0dd18c39de36447.png

排查过程

首先找到 hs2 进程持有了哪些文件描述符,通过
lsof 命令
lsof -p $pid
,看到 hs2 进程确实在
/data/emr/hive/tmp/operation_logs/
目录下打开了大量描述符

在 jira 中找到一个类似 的 issue:
[HIVE-10970] Investigate HIVE-10453: HS2 leaking open file descriptors when using UDFs - ASF JIRA (apache.org)

但是这个场景是由于 UDF 导致的 fd 泄漏,并且泄漏路径是在
hive.downloaded.resources.dir
路径下,跟 operation_logs 目录不一样.看上去不是同一个问题

排查源码 , 找到 operation log 有一个清理逻辑
org.apache.hive.service.cli.operation.Operation#cleanupOperationLog

猜测是在客户端 session
异常结束
的时候,这个方法没有
被正常调用到或者清理逻辑有漏洞
导致的

首先过一遍 session 关闭的逻辑,通过分析 beeline 客户端的火焰图,找到 session 关闭起始点
org.apache.hive.jdbc.HiveStatement#closeClientOperation
Pasted image 20230303195911.png

这里 client 发起了一个 thrift rpc 调用,然后在 hs2 thrift 找到 thrift server 对应的方法
org.apache.hive.service.cli.thrift.ThriftCLIService#CloseOperation
跟踪这个方法,最终会走到
org.apache.hive.service.cli.operation.SQLOperation#close
这里会调用 cleanupOperationLog 方法
Pasted image 20230303200607.png

那么确实是有可能由于客户端 session 异常退出,operation logs 没有被清理的可能的

接着查看 cleanupOperationLog 逻辑, 看这里是否有代码 bug ,于是在 idea 中使用 git 分支比较功能,发现 3.1 版本提交了一个修复

Pasted image 20230303193129.png

[HIVE-18820] Operation doesn't always clean up log4j for operation log - ASF JIRA (apache.org)

结论

  • 客户端 session 异常退出,导致 operation logs 没有被清理,跟 scratch dir 没有被清理场景类似
  • HIVE-18820 社区 bug 导致,可以考虑合入这个 patch

一、Java对象结构

实例化一个Java对象之后,该对象在内存中的结构是怎么样的?Java对象(Object实例)结构包括三部分:对象头、对象体和对齐字节,具体下图所示

Java对象结构

1、Java对象的三部分

(1)对象头

对象头包括三个字段,第一个字段叫作Mark Word(标记字),用于存储自身运行时的数据,例如GC标志位、哈希码、锁状态等信息。

第二个字段叫作Class Pointer(类对象指针),用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例。

第三个字段叫作Array Length(数组长度)。如果对象是一个Java数组,那么此字段必须有,用于记录数组长度的数据;如果对象不是一个Java数组,那么此字段不存在,所以这是一个可选字段。

(2)对象体

对象体包含对象的实例变量(成员变量),用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐。

(3)对齐字节

对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数HotSpot VM的内存管理要求对象起始地址必须是8字节的整数倍。对象头本身是8的倍数,当对象的实例变量数据不是8的倍数时,便需要填充数据来保证8字节的对齐。

2、Mark Word的结构信息

Mark Word是对象头中的第一部分,Java内置锁有很多重要的信息都存在这里。Mark Word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark Word为32位,64位JVM为64位。Mark Word的位长度不会受到Oop对象指针压缩选项的影响。

Java内置锁的状态总共有4种,级别由低到高依次为:无锁、偏向锁、轻量级锁和重量级锁。其实在JDK 1.6之前,Java内置锁还是一个重量级锁,是一个效率比较低下的锁,在JDK 1.6之后,JVM为了提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁和轻量级锁,从此以后Java内置锁的状态就有了4种(无锁、偏向锁、轻量级锁和重量级锁),并且4种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别)。以下是64位的Mark Word在不同的锁状态下的结构信息:

64位Mark Word的结构信息

由于目前主流的JVM都是64位,因此我们使用64位的Mark Word。接下来对64位的Mark Word中各部分的内容进行具体介绍。

(1)lock:锁状态标记位,占两个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,因此设置了lock标记。该标记的值不同,整个Mark Word表示的含义就不同。

(2)biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

lock和biased_lock两个标记位组合在一起共同表示Object实例处于什么样的锁状态。二者组合的含义具体如下表所示

image-20240919171225689

(3)age:4位的Java对象分代年龄。在GC中,对象在Survivor区复制一次,年龄就增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,因此最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

(4)identity_hashcode:31位的对象标识HashCode(哈希码)采用延迟加载技术,当调用Object.hashCode()方法或者System.identityHashCode()方法计算对象的HashCode后,其结果将被写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器)中。

(5)thread:54位的线程ID值为持有偏向锁的线程ID。

(6)epoch:偏向时间戳。

(7)ptr_to_lock_record:占62位,在轻量级锁的状态下指向栈帧中锁记录的指针。

二、使用JOL工具查看对象的布局

1、JOL工具的使用

JOL工具是一个jar包,使用它提供的工具类可以轻松解析出运行时java对象在内存中的结构,使用时首先需要引入maven GAV信息

<!--Java Object Layout -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

当前最新版本是0.17版本,据观察,它和0.15之前(不包含0.15)的版本输出信息差异比较大,而普遍现在使用的版本都比较低,但是不妨碍在这里使用该工具做实验。

jol-core 常用的几个方法

  • ClassLayout.parseInstance(object).toPrintable()
    :查看对象内部信息.
  • GraphLayout.parseInstance(object).toPrintable()
    :查看对象外部信息,包括引用的对象.
  • GraphLayout.parseInstance(object).totalSize()
    :查看对象总大小.
  • VM.current().details()
    :输出当前虚拟机信息

首先创建一个简单的类Hello

public class Hello {
    private Integer a = 1;   
}

接下来写一个启动类测试下

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info(VM.current().details());
        Hello hello = new Hello();
        log.info("hello obj status:{}", ClassLayout.parseInstance(hello).toPrintable());
    }
}

输出结果:

image-20240920092758780

2、结果分析

在代码中,首先使用了
VM.current().details()
方法获取到了当前java虚拟机的相关信息:

  • VM mode: 64 bits - 表示当前虚拟机是64位虚拟机

  • Compressed references (oops): 3-bit shift - 开启了对象指针压缩,在64位的Java虚拟机上,对象指针通常需要占用8字节(64位),但通过使用压缩指针技术,可以减少对象指针的占用空间,提高内存利用率。"3-bit shift" 意味着使用3位的位移操作来对对象指针进行压缩。通过将对象指针右移3位,可以消除指针中的一些无用位,从而减少对象指针的实际大小,使其占用更少的内存。

  • Compressed class pointers: 3-bit shift - 开启了类指针压缩,其余同上。

  • Object alignment: 8 bytes - 字节对齐使用8字节

image-20240920101332652

这部分输出表示引用类型、boolean、byte、char、short、int、float、long、double类型的数据所占的字节数大小以及在数组中的大小和偏移量。

需要注意的是数组偏移量的概念,数组偏移量的数值其实就是对象头的大小,在上图中的16字节表示如果当前对象是数组,那对象头就是16字节,不要忘了,对象头中还有数组长度,在未开启对象指针压缩的情况下,它要占据4字节大小。

接下来是对象结构的输出分析。

三、对象结构输出解析

先回顾下对象结构

Java对象结构

再来回顾下对象结构输出结果

image-20240920103046465

  • OFF
    :偏移量,单位字节

  • SZ
    :大小,单位字节

  • TYPE DESCRIPTION
    :类型描述,这里显示的比较直观,甚至可以看到是对象头的哪一部分

  • VALUE
    :值,使用十六进制字符串表示,注意一个字节是8bit,占据两个16进制字符串,JOL0.15版本之前是小端序展示,0.15(包含0.15)版本之后使用大端序展示。


    1、Mark Word解析

image-20240920104856901

因为当前虚拟机是64位的虚拟机,所以Mark Word在对象头中占据8字节,也就是64位。它不受指针压缩的影响,占据内存大小只和当前虚拟机有关系。

当前的值是十六进制数值:
0x0000000000000001
,为了好看点,将它按照字节分割开:
00 00 00 00 00 00 00 01
,然后,来回顾下mark workd的内存结构:

64位Mark Word的结构信息

最后一个字节是十六进制的01,转化为二进制数,就是
00000001
,那倒数三个bit就是
001
,偏向锁标志位biased是0,lock标志位是01,对应的是
无锁状态
下的mark word数据结构。

2、Class Pointer 解析

image-20240920110627257

该字段在64位虚拟机下开启指针压缩占据4字节,未开启指针压缩占据8字节,它指向方法区的内存地址,即Class对象所在的位置。

3、对象体解析

image-20240920111056379

Hello类只有一个Integer类型的变量a,它在64位虚拟机下开启指针压缩占据4字节,未开启指针压缩占据8字节大小。需要注意的是,这里的8字节存储的是Integer对象指针大小,而非int类型的数值所占内存大小。

四、不同条件下的对象结构变化

1、Mark Word中的hashCode

在无锁状态下,对象头中的mark word字段有31bit是用于存放hashCode的值的,但是在之前的打印输出中,hashCode全是0,这是为什么?

64位Mark Word的结构信息

想要hashCode的值能够在mark word中展示,需要满足两个条件:

  1. 目标类不能重写hashCode方法
  2. 目标对象需要调用hashCode方法生成hashCode

上面的实验中,Hello类很简单

public class Hello {
    private Integer a = 1;   
}

没有重写hashCode方法,使用JOL工具分析没有看到hashCode值,是因为没有调用hashCode()方法生成hashCode值

接下来改下启动类,调用下hashCode方法,重新输出解析结果

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info(VM.current().details());
        Hello hello = new Hello();
        hello.hashCode();
        log.info("hello obj status:{}", ClassLayout.parseInstance(hello).toPrintable());
    }
}

输出结果

image-20240920132209032

可以看到,Mark Word中已经有了hashCode的值。

2、字节对齐

从JOL输出上来看,使用的是8字节对齐,而对象正好是16字节,是8的整数倍,所以并没有使用字节对齐,为了能看到字节对齐的效果,再给Hello类新增一个成员变量
Integer b = 2
,已知一个整型变量在这里占用4字节大小空间,对象大小会变成20字节,那就不是8的整数倍,会有4字节的对齐字节填充,改下Hello类

public class Hello {
    private Integer a = 1;
    private Integer b = 2;
}

然后运行启动类

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info(VM.current().details());
        Hello hello = new Hello();
        hello.hashCode();
        log.info("hello obj status:{}", ClassLayout.parseInstance(hello).toPrintable());
    }
}

运行结果:

image-20240920133520395

果然,为了对齐8字节,多了4字节的填充,整个对象实例大小变成了24字节。

3、数组类型的对象结构

数组类型的对象和普通的对象肯定不一样,甚至在对象头中专门有个“数组长度”来记录数组的长度。改变下启动类,看看Integer数组的对象结构

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info(VM.current().details());
        Integer[] a = new Integer[]{1, 2, 3};
        a.hashCode();
        log.info("hello obj status:{}", ClassLayout.parseInstance(a).toPrintable());
    }
}

输出结果

image-20240920134321859

标红部分相对于普通的对象,数组对象多了个数组长度的字段;而且接下来3个整数,共占据了12字节大小的内存空间。

再仔细看看,加上数组长度部分,对象头部分一共占据了16字节大小的空间,这个和上面的Array base offsets的大小一致,这是因为要想访问到真正的对象值,从对象开始要经过16字节的对象头才能读取到对象,这16字节也就是每个元素读取的“偏移量”了。

4、指针压缩

开启指针压缩:
-XX:+UseCompressedOops

关闭指针压缩:
-XX:-UseCompressedOops

在Intelij中,在下图中的VM Options中添加该参数即可

image-20240920140730247

需要注意的是,指针压缩在java8及以后的版本中是默认开启的。

接下来看看指针压缩在开启和没开启的情况下,相同的解析代码打印出来的结果

代码:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info("\n{}",VM.current().details());
        Integer[] a = new Integer[]{1, 2, 3};
        a.hashCode();
        log.info("hello obj status:\n{}", ClassLayout.parseInstance(a).toPrintable());
    }
}

开启指针压缩的解析结果:

image-20240920141222851

未开启指针压缩的结果:

image-20240920141306194

以开启指针压缩后的结果为基础,观察下未开启指针压缩的结果

image-20240920142324117

需要注意的是这里的Integer[]数组里面都是Integer对象,而非int类型的数值,它是Integer基本类型包装类的实例,这里的数组内存地址中存储的是每个Integer对象的指针引用,从输出的VM信息的对照表中,“ref”类型占据8字节,所以才是3*8为24字节大小。

可以看到,开启指针压缩以后,会产生两个影响

  1. 对象引用类型会从8字节变成4字节
  2. 对象头中的Class Pointer类型会从8字节变成4字节

确实能节省空间。

五、扩展阅读

1、大端序和小端序

大端序(Big Endian)和小端序(Little Endian)是两种不同的存储数据的方式,特别是在多字节数据类型(比如整数)在计算机内存中的存储顺序方面有所体现。

  • 大端序(Big Endian)
    :在大端序中,数据的高位字节存储在低地址,而低位字节存储在高地址。类比于数字的书写方式,高位数字在左边,低位数字在右边。因此,数据的最高有效字节(Most Significant Byte,MSB)存储在最低的地址处。
  • 小端序(Little Endian)
    :相反地,在小端序中,数据的低位字节存储在低地址,而高位字节存储在高地址。这种方式与我们阅读数字的顺序一致,即从低位到高位。因此,数据的最低有效字节(Least Significant Byte,LSB)存储在最低的地址处。

这两种存储方式可以用一个简单的例子来说明:

假设要存储一个 4 字节的整数
0x12345678

  • 在大端序中,存储顺序为
    12 34 56 78
  • 在小端序中,存储顺序为
    78 56 34 12

2、老版本的JOL

老版本的JOL(0.15之前)输出的值是小端序的,可以做个实验,将maven坐标改成0.14版本

<!--Java Object Layout -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.14</version>
</dependency>

同时要引入新的工具类

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.32</version>
</dependency>

然后修改Hello类

import cn.hutool.core.util.ByteUtil;
import cn.hutool.core.util.HexUtil;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
public class Hello {

    private int a = 1;
    private int b = 2;

    public String hexHash() {
        //对象的原始 hashCode,Java默认为大端模式
        int hashCode = this.hashCode();
        //转成小端模式的字节数组
        byte[] hashCode_LE = ByteUtil.intToBytes(hashCode, ByteOrder.LITTLE_ENDIAN);
        //转成十六进制形式的字符串
        return HexUtil.encodeHexStr(hashCode_LE);
    }
}

启动类:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

/**
 * @author kdyzm
 * @date 2024/9/19
 */
@Slf4j
public class JalTest {

    public static void main(String[] args) {
        log.info("\n{}", VM.current().details());
        Hello hello = new Hello();
        log.info("算的十六进制hashCode:{}", hello.hexHash());
        log.info("JOL解析工具输出对象结构:{}", ClassLayout.parseInstance(hello).toPrintable());
    }
}

输出结果:

image-20240920151446421

先不管老版本的输出和新版本的差异性,总之可以看到小端序手动算的hashCode和jol解析得到的hashCode是一致的,说明老版本的jol(0.15之前)输出是小端序的,对应我们的Mark Word图例来看

64位Mark Word的结构信息

我们的图例是按照大端序来画的,所以老版本的输出第一个字节01才是Mark Word上述图例的最后一个字节。

代码不变,改变JOL版本号为0.15

<!--Java Object Layout -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.15</version>
</dependency>

运行结果如下

image-20240920151945745

可以看到手动计算的hashCode和jol解析的hashCode字节码颠倒过来了,也就是说,从0.15版本号开始,jol的输出变成了大端序输出了。

3、hutool的bug

上面的代码中有用到hutool工具类计算hashCode值的十六进制字符串,一开始我引入的依赖是这样的

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.3</version>
</dependency>

其中有个很重要的int类型转换字节数组的方法:
cn.hutool.core.util.ByteUtil#intToBytes(int, java.nio.ByteOrder)

源代码这样子:

image-20240920152601176

很明显的bug,它判断了小端序标志,但是却返回了大端序的字节数组,不出意外的,我的代码运行的有矛盾。。所以我专门去gitee上看了下,发现它的master代码已经发生了变化

image-20240920152920125

没错,它master代码已经修复了。。找了找commit,发现了这个

image-20240920153048975

对应的COMMIT记录链接:
https://gitee.com/dromara/hutool/commit/d4a7ddac3b30db516aec752562cae3436a4877c0

image-20240920153256248

还被人吐槽了,哈哈哈,引入5.8.32版本就解决了

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.32</version>
</dependency>



END.



参考文档

《Java高并发核心编程 卷2:多线程、锁、JMM、JUC、高并发设计模式》

Java对象的内存布局

看到最后了,欢迎关注我的个人博客⌯'▾'⌯:
https://blog.kdyzm.cn

1. Java POM模块化是什么

在Java项目中,特别是在使用Maven作为构建工具时,"POM模块化"是一个重要的概念,它指的是将大型项目拆分成多个更小、更易于管理的模块(或称为子项目)。每个模块都有自己的
pom.xml
文件,该文件定义了模块的构建配置,包括依赖关系、插件、目标平台等。

1.1 POM(Project Object Model)

POM是Maven项目管理和构建的核心文件,它通常是一个名为
pom.xml
的XML文件。POM文件包含了项目的所有配置信息,Maven通过这些信息来构建项目、管理依赖以及执行其他构建任务。

1.2 模块化

模块化是一种将软件分解成一组独立但可互操作的模块的技术。在Maven项目中,模块化意味着将大型应用程序或库拆分成更小的组件,每个组件都负责一组特定的功能或业务逻辑。这些组件(即模块)可以通过Maven的依赖管理机制相互依赖,从而形成一个完整的应用程序或库。

1.3 Maven模块化项目的优点

(1)
可重用性
:模块可以被多个项目共享和重用。

(2)
易于管理
:大型项目拆分成多个小模块后,每个模块都可以独立构建和测试,从而简化了整个项目的构建和测试过程。

(3)
清晰的依赖关系
:通过POM文件中的依赖声明,可以清晰地看到模块之间的依赖关系。

(4)
团队协作
:不同的模块可以由不同的团队或开发者并行开发,提高了开发效率。

(5)
灵活性
:模块化使得项目更加灵活,可以更容易地添加、删除或替换模块。

1.4 Maven模块化项目的结构

一个Maven模块化项目通常包含一个父POM文件和多个子模块。父POM文件定义了所有子模块共享的构建配置和依赖管理策略。子模块则继承父POM的配置,并根据需要添加特定的配置或依赖。

1.5 示例

假设有一个名为
MyProject
的Maven模块化项目,它包含三个子模块:
common

module-a

module-b
。项目的目录结构可能如下所示:

MyProject/  
|-- pom.xml (父POM)  
|-- common/  
|   |-- pom.xml  
|   |-- src/  
|       |-- main/  
|           |-- java/  
|               |-- com/example/common/  
|-- module-a/  
|   |-- pom.xml  
|   |-- src/  
|       |-- main/  
|           |-- java/  
|               |-- com/example/modulea/  
|-- module-b/  
    |-- pom.xml  
    |-- src/  
        |-- main/  
            |-- java/  
                |-- com/example/moduleb/

在这个例子中,
MyProject/pom.xml
是父POM文件,它定义了所有子模块共有的配置和依赖。
common

module-a

module-b
是子模块,它们分别包含自己的
pom.xml
文件和源代码。这些子模块可以通过Maven的依赖机制相互依赖,也可以依赖外部库。

通过模块化,
MyProject
项目变得更加清晰、易于管理和维护。开发者可以独立地构建和测试每个模块,而不必担心它们之间的依赖关系。同时,模块化的结构也使得项目更加灵活,可以更容易地根据需求进行扩展或修改。

2. Java Pom两个模块需要互相引用方法示例

在Maven项目中,当两个模块(或称为子项目)需要互相引用时,通常意味着这两个模块之间存在紧密的依赖关系。然而,Maven的常规依赖管理并不直接支持循环依赖(即A依赖B,B又依赖A),因为这会导致构建过程中的死锁。不过,大多数情况下,可以通过重新设计模块结构或利用Maven的特性(如聚合和继承)来避免直接的循环依赖。

但假设我们的场景是合理的,比如两个模块分别负责不同的业务逻辑,但确实需要共享一些公共的类或接口,而这些类或接口又分布在两个模块中。这种情况下,我们可以考虑将共享的部分提取到一个新的模块中,然后让这两个模块都依赖于这个新模块。

我将展示一个简化的例子,其中两个模块
module-a

module-b
通过Maven的聚合(Aggregation)和继承(Inheritance)机制来组织,并假设它们通过共享一个公共的父POM来管理依赖,而不是直接互相引用。

2.1 项目结构

my-project/  
|-- pom.xml (父POM)  
|-- module-a/  
|   |-- pom.xml  
|   |-- src/  
|       |-- main/  
|           |-- java/  
|               |-- com/example/modulea/ModuleA.java  
|-- module-b/  
|   |-- pom.xml  
|   |-- src/  
|       |-- main/  
|           |-- java/  
|               |-- com/example/moduleb/ModuleB.java  
|-- common/  
    |-- pom.xml  
    |-- src/  
        |-- main/  
            |-- java/  
                |-- com/example/common/SharedClass.java

2.2 父POM (
my-project/pom.xml
)

<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
    <groupId>com.example</groupId>  
    <artifactId>my-project</artifactId>  
    <version>1.0-SNAPSHOT</version>  
    <packaging>pom</packaging>  
  
    <modules>  
        <module>module-a</module>  
        <module>module-b</module>  
        <module>common</module>  
    </modules>  
</project>

2.3
common
模块 (
common/pom.xml
)

<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
    <parent>  
        <groupId>com.example</groupId>  
        <artifactId>my-project</artifactId>  
        <version>1.0-SNAPSHOT</version>  
    </parent>  
  
    <artifactId>common</artifactId>  
  
    <dependencies>  
        <!-- 这里可以添加common模块需要的依赖 -->  
    </dependencies>  
</project>

2.4
module-a

module-b
的POM文件

这两个模块的POM文件将非常相似,除了它们的
artifactId
和可能的一些特定依赖外。它们都将依赖于
common
模块。

<!-- 以module-a为例 -->  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
    <parent>  
        <groupId>com.example</groupId>  
        <artifactId>my-project</artifactId>  
        <version>1.0-SNAPSHOT</version>  
    </parent>  
  
    <artifactId>module-a</artifactId>  
  
    <dependencies>  
        <dependency>  
            <groupId>com.example</groupId>  
            <artifactId>common</artifactId>  
            <version>${project.version}</version>  
        </dependency>  
        <!-- 其他依赖 -->  
    </dependencies>  
</project>

2.5 结论

在这个例子中,
module-a

module-b
没有直接互相引用,而是通过共享一个
common
模块来避免循环依赖。这是处理Maven项目中模块间依赖关系的推荐方式。如果确实需要两个模块直接互相引用,那么可能需要重新考虑我们的项目结构或设计模式。

3. 如何使用Maven模块化

使用Maven进行模块化是一种将大型项目分解为更小、更易于管理的部分的方法。每个模块都是一个独立的Maven项目,拥有自己的
pom.xml
文件,但可以通过Maven的继承和聚合特性与其他模块相关联。以下是如何使用Maven进行模块化的基本步骤:

3.1 创建父POM

首先,我们需要创建一个父POM(
pom.xml
),它将作为所有子模块的通用配置模板。父POM通常不包含源代码,而是定义了项目共有的配置,如依赖管理、插件配置、目标平台等。

<!-- 父POM (位于项目根目录) -->  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
    <groupId>com.example</groupId>  
    <artifactId>my-project-parent</artifactId>  
    <version>1.0-SNAPSHOT</version>  
    <packaging>pom</packaging>  
  
    <!-- 依赖管理 -->  
    <dependencyManagement>  
        <dependencies>  
            <!-- 这里定义子模块可能需要的依赖项及其版本 -->  
        </dependencies>  
    </dependencyManagement>  
  
    <!-- 插件管理 -->  
    <build>  
        <pluginManagement>  
            <!-- 这里定义构建过程中可能需要的插件及其配置 -->  
        </pluginManagement>  
    </build>  
  
    <!-- 模块列表 -->  
    <modules>  
        <module>module-a</module>  
        <module>module-b</module>  
        <!-- 其他子模块 -->  
    </modules>  
</project>

注意:
<packaging>pom</packaging>
表明这是一个聚合POM,它不会构建任何实际的产品,而是用来聚合和管理其他模块。

3.2 创建子模块

然后,我们需要在父POM的同级目录下(或指定的任何子目录中)创建子模块。每个子模块都应该有自己的
pom.xml
文件,并且通常会继承自父POM。

<!-- 子模块A的POM (位于module-a目录) -->  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
    <modelVersion>4.0.0</modelVersion>  
    <parent>  
        <groupId>com.example</groupId>  
        <artifactId>my-project-parent</artifactId>  
        <version>1.0-SNAPSHOT</version>  
    </parent>  
  
    <artifactId>module-a</artifactId>  
  
    <!-- 依赖项(如果需要的话) -->  
    <dependencies>  
        <!-- 这里可以声明具体的依赖项,版本号可以从父POM中继承 -->  
    </dependencies>  
  
    <!-- 其他配置 -->  
</project>

3.3 构建项目

在父POM所在的目录下运行Maven命令,Maven会自动找到并构建所有列在
<modules>
标签下的子模块。

bash复制代码

mvn clean install

这个命令会首先清理之前构建生成的文件,然后编译、测试并安装所有子模块到本地Maven仓库中。

3.4 依赖管理

在父POM中定义的
<dependencyManagement>
部分允许我们指定依赖项及其版本号,但不会在父POM中实际引入这些依赖项。子模块可以通过声明相同的依赖项(不包括版本号)来继承这些依赖项及其版本号。

3.5 插件管理

类似地,
<pluginManagement>
部分允许我们在父POM中定义插件及其配置,但不会在父POM中实际执行这些插件。子模块可以通过继承这些插件配置来简化插件配置过程。

3.6 结论

通过Maven模块化,我们可以将大型项目分解为更小、更易于管理的部分,同时利用Maven的继承和聚合特性来共享配置和依赖项。这有助于提高项目的可维护性、可重用性和可扩展性。

背景

集团有N个基地,所有基地的网络使用的是172.x.x.x网段,这本身没有什么问题!但Docker默认的桥接网段也是172.17.x.x的,如果不修改docker的默认配置会导致个别基地无法访问!列举几个基地使用的网段

基地 网段
A基地 172.30.x.x
B基地 172.28.x.x
C基地 172.18.x.x

如果不修改docker默认配置,随着容器创建得越多,最终总会和某个基地冲突。

参考拓展知识:docker默认子网创建规则

一、检查服务器中的Docker网络情况

检查docker的子网配置

使用
ifconfig docker
查看docker默认分配的子网

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:f6ff:fee8:ad97  prefixlen 64  scopeid 0x20<link>
        ether 02:42:f6:e8:ad:97  txqueuelen 0  (Ethernet)
        RX packets 42355  bytes 67869451 (64.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 55344  bytes 9332802 (8.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

说明:如果此处显示的172.x.x.x则代表是隐患网段,需要更改。

检查docker容器已分配的子网

使用
docker network inspect $(docker network ls -q) | grep Subnet
查看Docker是否已经创建网络

[root@yw_did_test ~]# docker network inspect $(docker network ls -q) | grep Subnet
                    "Subnet": "172.17.0.0/16",
                    "Subnet": "172.23.0.0/16",
                    "Subnet": "172.20.0.0/16",

如果命令执行后有数据返回并且是172.x.x.x的网段,则代表已经创建network,并分配了存在隐患的网段,需要更改。

参考拓展知识:docker默认子网创建规则

二、修改默认子网

场景1:新装Docker(无正在运行的容器

  1. 编辑配置文件
    vi /etc/docker/daemon.json
    ,新增一条配置
   "bip":"192.22.0.1/24"
  1. 重启docker和加载文件
sudo systemctl daemon-reload && systemctl restart docker
  1. 然后使用
    ifconfig docker
    命令查看是否生效,效果如下:
[root@op-manager-center-prod ~]# ifconfig docker
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.22.0.1  netmask 255.255.255.0  broadcast 192.22.0.255
        ether 02:42:9d:8d:d8:92  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

场景2:修改原有Docker(有正在运行的容器)

注意:docker run 时如果没有手动指定子网,会自动按照默认规则创建子网,即使按照场景1进行更改也无法生效。 参考拓展知识:docker默认子网创建规则

解决思路和步骤:

  1. 停止容器
  2. 按照场景1修改默认子网
  3. 删除已有容器的network
  4. 手动关联容器的网络
  5. 再启动容器


Docker-Compose 自动创建的网桥与局域网冲突解决方案 - 知乎 (zhihu.com)

三、docker-compose修改方式

注意:docker-compose的运行方式与默认的docker网络模式不同,也就是说就算修改了默认子网,docker-compose up -d也是默认使用172.x.x.x,如果是docker-compose可以不修改上面的网络
解决方案:指定网络

#se版本,向下兼容,我选的最高版本
version: "3.3"
# 定义服务
services:
  #省略
  network:
   - default-network

networks:
  default-network:
    driver: bridge
    ipam:
      config:
        - subnet: 192.22.1.0/24

拓展知识

  1. docker默认子网创建规则
当您使用默认的桥接网络模式(bridge)时,Docker 会为每个容器创建一个独立的子网,并为每个子网分配一个 CIDR 地址范围。在默认情况下,Docker 使用 172.17.0.0/16 CIDR 地址范围来创建这些子网。

每次创建容器时,Docker 会从该 CIDR 地址范围中选择一个未被使用的子网,并将容器分配到该子网中的一个 IP 地址。

假设您已经运行了 10 个容器,并且每个容器都位于独立的子网中。每个子网的 IP 范围都是 /16,这意味着每个子网有约 65534 个可用 IP 地址(除去网络地址和广播地址)。

Docker 在默认情况下会按顺序分配子网的 IP 地址,每个子网的 IP 范围如下所示:

子网1:172.17.0.1 - 172.17.255.254
子网2:172.18.0.1 - 172.18.255.254
子网3:172.19.0.1 - 172.19.255.254
子网4:172.20.0.1 - 172.20.255.254
子网5:172.21.0.1 - 172.21.255.254
子网6:172.22.0.1 - 172.22.255.254
子网7:172.23.0.1 - 172.23.255.254
子网8:172.24.0.1 - 172.24.255.254
子网9:172.25.0.1 - 172.25.255.254
子网10:172.26.0.1 - 172.26.255.254