目录

POM

# widndows 系统安装 jar 包,使用双引号包裹 -Dxxx,否则解析失败
mvn install:install-file "-Dfile=D:/lib/demo-1.0.jar" "-DgroupId=com.demo" "-DartifactId=demo-sdk" "-Dversion=1.0" "-Dpackaging=jar"

# linux 系统安装 jar 包
mvn install:install-file \
-Dfile=/opt/lib/demo-1.0.jar \
-DgroupId=com.demo \
-DartifactId=demo-sdk \
-Dversion=1.0 \
-Dpackaging=jar

# 安装源码
mvn install:install-file -Dfile=xxx.jar -Dsources=xxx-sources.jar -DgroupId=xxx -DartifactId=xxx -Dversion=1.0 -Dpackaging=jar
❗️important
  • 基本原则:

    • 显式 import 的类,必须用 compile,默认是 compile
    • 仅运行时通过反射/配置加载的类,用 runtime,像 jdbc 驱动,日志实现依赖
    • 编译期需要但运行时环境提供的,用 provided,像 lombok
  • 特殊情况:

    • 注解处理器、字节码增强工具等,即使没有 import 也可能需要编译期依赖。
  • 验证方法:

    • 结合编译器报错、mvn dependency:analyze 和 IDE 工具综合判断。
  • 如果你的目标是构建一个通用的 Starter 组件,推荐做法是:

    • 将非核心依赖(如 spring-boot-autoconfigure)设为 optional
    • 将运行环境提供的依赖(如数据库驱动、Servlet API)设为 provided
    • 保留核心依赖(如 jsqlparser、mybatis、caffeine)为默认 scope(compile)
    • 这样既能保证你的组件正常运行,也能避免与用户的项目产生冲突。

resources

<profiles>
    <!-- 公共变量 -->
    <profile>
        <id>dev</id>
        <properties>
            <env>dev</env>
            <db.host>localhost</db.host>
            <redis.port>6379</redis.port>
        </properties>
        <activation><activeByDefault>true</activeByDefault></activation>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <env>prod</env>
            <db.host>prod-db.company.com</db.host>
            <redis.port>6380</redis.port>
        </properties>
    </profile>
</profiles>

<build>
    <resources>
        <!-- 优先加载环境专用资源 -->
        <resource>
            <directory>src/main/resources-${env}</directory>
            <!-- 只对需要变量替换的配置进行过滤,其余不要过滤。会导致文件无法打开 -->
            <filtering>true</filtering>
        </resource>
        <!-- 再加载通用资源 -->
        <resource>
            <directory>src/main/resources</directory>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>
src/main/
 ├── resources-dev/
 │   ├── application.properties     # dev 专用配置
 │   └── logback-spring.xml         # dev 日志
 ├── resources-prod/
 │   ├── application.properties     # prod 专用配置(可不提交 Git)
 │   └── logback-spring.xml         # prod 日志
 └── resources/
     ├── jdbc-template.properties   # 使用 ${db.host} 的模板
     └── common-config.xml          # 通用配置
jdbc.url=jdbc:mysql://${db.host}:3306/mydb
jdbc.username=root
jdbc.password=123456

plugins

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.6.1</version>
    <!-- 插件级别 -->
    <configuration>
        <useRepositoryLayout>true</useRepositoryLayout>        <!-- 只有 copy-dependencies 等少数目标支持 -->
        <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 同上 -->
        <outputFile>${project.build.directory}/tree.txt</outputFile>      <!-- tree 目标支持 -->
    </configuration>
    <executions>
        <execution>
            <id>id</id>
            <phase>package</phase>
            <goals>
                <goal>tree</goal>
            </goals>
            <!-- execution 级别: 执行 mvn package 时,会执行 tree,目标,并使用这里配置的参数 -->
            <configuration>
                <outputFile>${project.build.directory}/tree-g.txt</outputFile> <!-- 覆盖全局 -->
            </configuration>
        </execution>
    </executions>
</plugin>
ℹ️note
  • 有些插件没有 <executions>,它是怎么执行的?
  • Maven 有“默认绑定”(Default Binding)机制,即使没有在 pom.xml 中写 <executions>,Maven 也会根据 项目打包类型(packaging) 自动绑定插件目标到生命周期阶段。
  • <configuration> 中配置的就是插件阶段中的参数

回到目录

Maven

生命周期

阶段和插件的关系

@Mojo(name = "run") // 插件目标名
public class AntrunMojo extends AbstractMojo {
    @Parameter
    private Target target; // ← 这个参数名就是 <target>
    
    public void execute() { ... }
}

Maven 插件

<build>  
  <plugins>    
    <!-- 依赖插件 -->    
    <plugin>      
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.2</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>  
  </plugins>
</build>

Mavn 依赖

 <!-- 用来排除不想要的依赖包 -->  
<exclusions>    
  <exclusion>      
    <groupId>commons-logging</groupId>      
    <artifactId>commons-logging</artifactId>    
  </exclusion>  
</exclusions>
<dependencyManagement>    
  <dependencies>        
    <dependency>            
      <groupId>junit</groupId>            
      <artifactId>junit</artifactId>            
      <version>4.9</version>            
      <scope>test</scope>        
    </dependency>    
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
  </dependency>
</dependencies>

mvn命令

mvn compiler:compile → 等价于 mvn compile
mvn surefire:test → 等价于 mvn test
mvn spring-boot:repackage
mvn versions:use-latest-versions
maven-compiler-plugin → compiler
maven-surefire-plugin → surefire
spring-boot-maven-plugin → spring-boot
部分说明示例
mvnMaven 的命令入口
[选项]控制 Maven 运行方式的全局选项-X, -e, -q
[生命周期阶段]Maven 内置的构建阶段(如 clean, compile, package)mvn clean package
[插件目标]指定插件的具体目标(格式:插件前缀:目标 或 groupId:artifactId:version:目标)mvn exec:java
-D参数动态传递系统属性或覆盖 POM 属性(键值对)-DskipTests=true
-P配置文件激活指定的 Profile(逗号分隔)-Pprod,dev
❗️important
  • 帮助命令 mvn help:help
  • 查看插件 compiler 基础信息 mvn compiler:help,查看所有的目标
  • 查看插件 exec 的 java 目标的参数信息:mvn exec:help -Ddetail=true -Dgoal=java
  • 查看插件的详细信息:mvn help:describe "-Dplugin=org.springframework.boot:spring-boot-maven-plugin" -Ddetail
  • 查看插件指定目标的详细信息:mvn help:describe "-Dplugin=org.springframework.boot:spring-boot-maven-plugin" -Dgoal=repackage -Ddetail

内网环境

根据 pom.xml 下载项目的所有依赖到指定目录

# 复制jar和pom
mvn -f "/path/to/pom.xml" "dependency:copy-dependencies" "-DoutputDirectory=/path/to/repo" "-Dmdep.useRepositoryLayout=true" "-Dmdep.copyPom=true"

# 复制源码
mvn -f "/path/to/pom.xml" "dependency:copy-dependencies" "-DoutputDirectory=/path/to/repo" "-Dmdep.useRepositoryLayout=true" "-Dmdep.copyPom=true" "-Dclassifier=sources"

# powershell 删除 _remote.repositories 文件
Get-ChildItem -Path "/path/to/repo" -Filter "_remote.repositories" -Recurse | Remove-Item -Force
# linux
find /path/to/repo -name "_remote.repositories" -delete

# powershell 生成sha1
Get-ChildItem -Path ./repository -Recurse -Include *.jar,*.pom | ForEach-Object { (Get-FileHash -Path $_.FullName -Algorithm SHA1).Hash.ToLower() | Out-File -FilePath "$($_.FullName).sha1" -Encoding ASCII }

# linux
find /path/to/repo -name "*.jar" -o -name "*.pom" | while read -r file; do sha1sum "$file" | cut -d' ' -f1 > "${file}.sha1"; done
ℹ️note

Windows环境使用 -Dxxx 时一定加上双引号,防止错误解析

Maven 变量信息

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <!-- 在 MANIFEST.MF 中输出类路径 -->
            <addClasspath>true</addClasspath>
            <!-- 指明依赖的路径。注意,配置依赖的存放路径时,要以生成的 Jar 文件的最终存放目录(app 目录)为参照点,以相对路径的方式指定依赖的存放目录-->
            <classpathPrefix>lib/</classpathPrefix>
            <!-- 在 MANIFEST.MF 中指定主类,作为系统入口 -->
            <mainClass>com.example.MyApplication</mainClass>
          </manifest>
        </archive>
        <finalName>SpringBootDemo</finalName>
      </configuration>
    </plugin>
    <plugin>
      <!-- 复制依赖的插件,否则要手动将依赖放置合适位置 -->
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <!-- 绑定生命周期 -->
          <phase>package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <!-- 设置依赖的存放路径 -->
          <configuration>
            <outputDirectory>${project.build.directory}/lib</outputDirectory>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>${start.class}</mainClass>
                <layout>ZIP</layout>
                <finalName>SpringBootDemo</finalName>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <environment>dev</environment>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <environment>prod</environment>
        </properties>
    </profile>
</profiles>

回到目录

命令行运行

类的路径

mvn "dependency:build-classpath" "-Dmdep.outputFile=cp.txt"
$cp = Get-Content cp.txt
java -Dfile.encoding=UTF-8 -cp "target\classes;$cp" com.example.MyMain

手动创建项目

ProtoCode/
├── src/
│   └── com/
│       └── zmy/
│           └── App.java
├── bin/ (编译后的class文件存放位置)
└── lib/ (依赖位置)

javac编译

ℹ️note
  • -classpath,设定要搜索类的路径,可以是目录,jar文件,zip文件(里面都是class文件),会覆盖掉所有在CLASSPATH里面的设定。
  • -sourcepath, 设定要搜索编译所需java文件的路径,可以是目录,jar文件,zip文件(里面都是java文件)。

java运行

ℹ️note
  • -classpath,设定要搜索的类的路径,可以是目录,jar文件,zip文件(里面都是class文件),会覆盖掉所有的CLASSPATH的设定。
  • 由于所要执行的类也是要搜索的类的一部分,所以一定要把这个类的路径也放到-classpath的设置里面。
  • 在要执行的类的路径里面执行java时,一定要添加上点号(.)表示本目录也要搜索。
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <version>3.6.0</version>
  <executions>
    <execution>
      <id>copy-dependencies</id>
      <phase>package</phase>
      <goals>
        <goal>copy-dependencies</goal>
      </goals>
      <configuration>
        <outputDirectory>${project.build.directory}/lib</outputDirectory>
        <includeScope>runtime</includeScope>  <!-- 可选:compile|runtime|test -->
        <excludeTransitive>false</excludeTransitive>
        <stripVersion>true</stripVersion>    <!-- 移除版本号 -->
      </configuration>
    </execution>
  </executions>
</plugin>
❗️important
  • java 运行 class 文件是从 classpath 路径去寻找 class 文件,如果 class 文件中有包名,则会按照 package 指定的包路径转化为文件路径去搜索 class 文件,(java规定运行 class 必须指定完整限定名)如果是当前文件则使用 -cp ".;" 指定 classpath
  • 如果在项目根目录下让 java 命令去执行 com.zmy.util.App,其实它会以为类的路径是:E:\Java\Train\Train\com\zmy\util\App,实际路径是在 target\classes下,所以需要在 E:\Java\Train\Train\target\classes 执行 java -cp . com.zmy.util.App

反编译class

# 使用IDEA的反编译工具,通常在IDEA安装目录的plugins/java-decompiler/lib/

# 反编译单个文件
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler input.class output_dir/
# 反编译整个jar包
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler input.jar /path/to/output
# 反编译整个文件夹
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler /path/to/classes\ /path/to/output

# 使用 cfr 反编译
# 反编译整个jar
java -jar cfr_0.152.jar your_jar.jar --hideutf false --outputdir /path/to/output
# 反编译单独class
java -jar cfr-0.152.jar --hideutf false /path/to/Main.class > Main.java
# 查看帮助
java -jar cfr-0.152.jar ruoyi-admin.jar --help
java -jar cfr-0.152.jar ruoyi-admin.jar --help outputdir

使用 maven 构建

<properties>
    <!-- 1. 设置项目源码编码 使用命令行参数就是 -Dproject.build.sourceEncoding=UTF-8 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
                <mainClass>com.zmy.App</mainClass>
                <arguments>
                    <argument>1</argument>
                    <argument>2</argument>
                </arguments>
            </configuration>
        </plugin>
    </plugins>
</build>
插件关键配置项典型值示例
maven-compiler-plugin<source>,<target>,<encoding>1.8,UTF-8
maven-surefire-plugin<includes>,<argLine>**/*Test.java,-Xmx512m
maven-jar-plugin<mainClass>,<addClasspath>com.example.Main,true
maven-assembly-plugin<descriptorRef>,<appendAssemblyId>jar-with-dependencies,false
maven-deploy-plugin<altDeploymentRepository>http://nexus.example.com/repo
exec-maven-plugin<mainClass>,<arguments>com.example.Run,arg1arg2

Maven常用插件速查表

插件名称作用描述常用配置项示例
maven-compiler-pluginJava源码编译<source>1.8</source>
<target>11</target>
<encoding>UTF-8</encoding>
maven-surefire-plugin执行单元测试<skipTests>false</skipTests>
<includes>**/*Test.java</includes>
maven-jar-plugin生成标准JAR包<mainClass>com.Main</mainClass>
<addClasspath>true</addClasspath>
插件名称作用描述常用配置项示例
maven-dependency-plugin依赖管理工具<outputDirectory>target/lib</outputDirectory>
<includeScope>runtime</includeScope>
maven-enforcer-plugin环境约束检查<requireJavaVersion>11</requireJavaVersion>
<banDuplicateClasses>true</banDuplicateClasses>
插件名称作用描述常用配置项示例
maven-assembly-plugin构建FatJAR<descriptorRef>jar-with-dependencies</descriptorRef>
<appendAssemblyId>false</appendAssemblyId>
maven-shade-plugin处理依赖冲突<relocations>
<pattern>com.google</pattern>
<shadedPattern>hidden.com.google</shadedPattern>
插件名称作用描述常用配置项示例
maven-checkstyle-plugin代码规范检查<configLocation>google_checks.xml</configLocation>
<failOnViolation>true</failOnViolation>
spotbugs-maven-plugin静态代码分析<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
插件名称作用描述常用配置项示例
spring-boot-maven-pluginSpringBoot应用打包<mainClass>com.App</mainClass>
<executable>true</executable>
<layers>true</layers>
插件名称作用描述常用配置项示例
exec-maven-plugin直接运行Java类<mainClass>com.Run</mainClass>
<arguments>arg1arg2</arguments>
versions-maven-plugin依赖版本管理<display-dependency-updates>
<display-plugin-updates>

配置示例代码块

<!-- 典型maven-compiler-plugin配置 -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.11.0</version>
  <configuration>
    <source>11</source>
    <target>11</target>
    <encoding>UTF-8</encoding>
    <showWarnings>true</showWarnings>
  </configuration>
</plugin>

<!-- 典型spring-boot-maven-plugin配置 -->
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <mainClass>com.example.Application</mainClass>
    <executable>true</executable>
  </configuration>
</plugin>

<!-- 典型exec-maven-plugin配置 -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
    <configuration>
        <mainClass>com.zmy.App</mainClass>
        <arguments>
            <argument>1</argument>
            <argument>2</argument>
        </arguments>
        <!-- <arguments>zmy flm</arguments> -->
    </configuration>
</plugin>

maven 调试

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.5.0</version> <!-- 推荐最新稳定版 -->
  <configuration>
    <executable>java</executable>
    <arguments>
      <argument>-Xdebug</argument>
      <argument>-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005</argument>
      <argument>-classpath</argument>
      <classpath/> <!-- 自动包含项目依赖 -->
      <argument>com.zmy.App</argument>
      <argument>1</argument> <!-- 程序参数1 -->
      <argument>2</argument> <!-- 程序参数2 -->
    </arguments>
  </configuration>
</plugin>

回到目录

前端乱码问题

❗️important
  • response.setContentType("text/html;charset=UTF-8") 这个方法包含了上面的两个方法的调用,实际使用这个
🔥warning

虽然response对象的getOutSream()和getWriter()方法都可以发送响应消息体,但是他们之间相互排斥,不可以同时使用,否则会发生异常。

回到目录

函数式接口

  1. Runnable:代表一个没有参数和返回值的代码块,通常用于多线程编程。(无输入,无输出)void run();

  2. Supplier<T>:表示一个生产者,不接受参数,返回一个结果。(无输入,有输出)T get();

  3. Consumer<T>:表示一个消费者,接受一个参数,无返回值。(有输入,无输出)

    void accept(T t); 
    
    Consumer<T> andThen(Consumer<? super T> after){
      return (T t) -> { 
        accept(t); 
        after.accept(t); 
      };
    }
    
  4. Function<T, R>:接受一个类型 T 的输入参数,并返回一个类型 R 的结果。(有输入,有输出)

  1. Predicate<T>:接受一个参数,并返回一个布尔值结果,用于判断条件。(断言)bool = predicate.test(str)

  2. UnaryOperator<T> extends Function<T,T>:接受一个参数,并返回与参数类型相同的结果,相当于Function<T,T>

  3. BinaryOperator<T> extends BiFunction<T,T,T>:接受两个相同类型的参数,并返回一个与参数类型相同的结果。

    BinaryOperator<Integer> adder = Integer::sum;
    int result = adder.apply(10, 20);
    
  4. BiFunction<T, U, R>:接收两个参数 T 和 U,返回 R 类型,

    BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;
    Function<Integer, Integer> multiplier = x -> x * 2;
    // 将 adder 的返回结果当作参数传给 multiplier,等价于 (a, b) -> (a + b) * 2
    BiFunction<Integer, Integer, Integer> addAndMultiply = adder.andThen(multiplier);
    int result = addAndMultiply.apply(10, 20);
    System.out.println(result);  // 输出: 60 = (10+20)*2
    

回到目录

方法引用

  1. 引用静态方法:
  1. 引用成员方法
  1. 引用构造方法
  1. 其他调用方法

回到目录

四种引用

csdn链接

  1. 强引用(NormalReference):正常使用的对象,只有有引用指向它,就不会被GC,即使内存不足报OOM,也不GC,包括类、接口、数组类型、枚举类型、泛型类型、注解类型。
  2. 软引用(SoftReference):当一个对象被软引用所指向的时候,只有系统内存不够的时候才会GC。软引用适合用作缓存,内存不足时可以被回收。SoftReference<T> ref = new SoftReference<>(new T())
  3. 弱引用(WeakReference):当被弱引用指向的时候,只要遇到GC,就会被回收。WeakReference<T> ref = new WeakReference<>(new T())
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
tl.remove();
  1. 虚引用(PhantomReference):需引用是管理堆外内存的,不能被GC回收(GC只能回收堆内存)。

ThreadLocal

// 如果不用继承,需要这样:
class Entry2 {
    WeakReference<ThreadLocal> ref;  // 包含一个弱引用
    Object value;  // 包含一个值
    
    Entry2(ThreadLocal k, Object v) {
        this.ref = new WeakReference<>(k);
        this.value = v;
    }
}

// 源码用的是继承,更简洁:
class Entry extends WeakReference<ThreadLocal> {
    Object value;  // 直接扩展功能
    
    Entry(ThreadLocal k, Object v) {
        super(k);  // 调用父类构造,我就是那个ref
        this.value = v;
    }
}

回到目录

Matcher

回到目录

日期和时间

转换关系

回到目录

集合

回到目录

泛型

链接

ℹ️note
  • class B extends A; class A extends T,继承是 is a 的关系,B is a, A is a T
  • 对于 List<? extends T> list 来说,编译器并不确定 list 中具体存放的是什么类型,只知道是 T 的子类,因此不能向 list 添加数据(理论上可以添加 T 的父类,但是编译器做了限制不让添加,会报错),例如,如果 list 中是 B,这时向 list 中添加一个 A 就会失败,因为 A is not a B,但是可以从中取出数据,因为其中的数据上界是T,可以从中读取一个元素,存入一个T或T的父类中 Object obj = list[0]
  • 通配符 <?> 和类型参数 T 的区别就在于,对编译器来说所有的T都代表同一种类型。比如 public <T> List<T> fill(T... t),三个T都指代同一个类型,要么都是String,要么都是Integer。但通配符 <?> 没有这种约束,List<?> 单纯的就表示:集合里放了一个东西,是什么不知道。
  • 对于 <? super T> 来说,下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制
  • ? 是任何类型,extends 和 super 都是对泛型做了一些范围限制

回到目录

Optional

String name = "Alice";
Optional<String> optionalName = Optional.ofNullable(name);  // 如果 name 为 null,则返回空的 Optional 对象
Optional<String> emptyOptional = Optional.empty();  // 创建一个空的 Optional 对象
boolean isPresent = optionalName.isPresent();  // 检查是否包含值
System.out.println(isPresent);  // 输出: true
try {
    String value = optionalName.get();  // 获取值
    System.out.println(value);  // 输出: Alice
} catch (NoSuchElementException e) {
    System.out.println("No value present");
}
optionalName.ifPresent(System.out::println);  // 如果存在,则打印值

String defaultValue = optionalName.orElse("Default Name");  // 如果不存在,则返回默认值
System.out.println(defaultValue);  // 输出: Alice

String computedValue = optionalName.orElseGet(() -> "Computed Value");  // 如果不存在,则计算并返回值
System.out.println(computedValue);  // 输出: Alice

try {
    String value = optionalName.orElseThrow(() -> new RuntimeException("No value present"));  // 如果不存在,则抛出异常
    System.out.println(value);  // 输出: Alice
} catch (RuntimeException e) {
    System.out.println(e.getMessage());
}
Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Alice"));
Optional<String> flatOptional = nestedOptional.flatMap(Optional::ofNullable);  // 扁平化
System.out.println(flatOptional.orElse("Default Name"));  // 输出: Alice
  Optional<String> filteredOptional = optionalName.filter(s -> s.length() > 5);  // 只保留长度大于5的字符串
  System.out.println(filteredOptional.orElse("Filtered Name"));  // 输出: Alice
Optional<Integer> length = optionalName.map(String::length);  // 将字符串转换为其长度
System.out.println(length.orElse(0));  // 输出: 5 (为 null 则输出0)
Optional<String> anotherName = Optional.ofNullable(null);
Optional<String> combinedOptional = optionalName.or(() -> anotherName);
System.out.println(combinedOptional.orElse("Combined Name"));  // 输出: Alice

回到目录

并发编程

线程池

ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   ThreadFactory threadFactory,
                   RejectedExecutionHandler handler)
💡tip

线程数的设置主要取决于业务是IO密集型还是CPU密集型。

CPU密集型指的是任务主要使用来进行大量的计算,没有什么导致线程阻塞。一般这种场景的线程数设置为CPU核心数+1。

IO密集型:当执行任务需要大量的io,比如磁盘io,网络io,可能会存在大量的阻塞,所以在IO密集型任务中使用多线程可以大大地加速任务的处理。一般线程数设置为 2×CPU核心数

java中用来获取CPU核心数的方法是:Runtime.getRuntime().availableProcessors();

ExecutorService executorService = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100),
      r -> {
          Thread thread = new Thread(r);
          thread.setName("addUser-thread" + thread.getId());
          thread.setDaemon(true);
          return thread;
      }, 
      new ThreadPoolExecutor.DiscardPolicy());

// 创建线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("addUser-thread" + thread.getId());
        thread.setDaemon(true);
        return thread;
    }
};

// 创建线程工厂
ThreadFactory factory = new ThreadFactoryBuilder()
        .setNameFormat("my-thread-%d") // 设置线程命名模式
        .setPriority(Thread.NORM_PRIORITY) // 设置线程优先级
        .setDaemon(false) // 设置线程为守护线程
        .build();

// 创建线程工厂
BasicThreadFactory factory = new BasicThreadFactory.Builder()
        .namingPattern("my-thread-%d") // 设置线程命名模式
        .priority(Thread.NORM_PRIORITY) // 设置线程优先级
        .daemon(false) // 设置线程为守护线程
        .build();

回到目录

流式(响应式)数据

// 从集合创建:
Flux<String> fluxFromList = Flux.fromIterable(Arrays.asList("A", "B", "C"));
// 从数组创建:
Flux<String> fluxFromArray = Flux.just("A", "B", "C");
// 从单个值创建:
Flux<String> fluxFromSingleValue = Flux.just("A");
// 从生成器创建:
Flux<Integer> fluxFromGenerator = Flux.generate(sink -> {
    sink.next(1); // 发射一个值
    sink.complete(); // 完成
});
// 从事件创建:
Flux<String> fluxFromEvent = Flux.create(sink -> {
    sink.next("A");
    sink.next("B");
    sink.complete();
});
fluxFromList.subscribe(System.out::println);
Flux<String> flux = Flux.just("apple", "banana", "cherry");
// 订阅并打印结果
flux.subscribe(
    System.out::println, // 消费者,处理每个数据项
    System.err::println, // 错误处理器,处理异常
    () -> System.out.println("Completed"), // 完成处理器,处理完成事件
    sub -> sub.request(1000) // 订阅并请求 1000 个元素
);
// Flux 依次发射 apple,banana,cherry,执行 out,如果期间没有异常,则最后执行 Completed
// 如果出现任何异常,则执行 err,且不再发射数据,也不执行 Completed
// 映射(Map):
Flux<String> mappedFlux = fluxFromList.map(s -> s.toUpperCase());
// 过滤(Filter):
Flux<String> filteredFlux = fluxFromList.filter(s -> s.length() > 1);
// 合并(Merge):
Flux<String> flux1 = Flux.just("A", "B");
Flux<String> flux2 = Flux.just("C", "D");
Flux<String> mergedFlux = Flux.merge(flux1, flux2);
// 错误处理(OnError):
Flux<String> errorFlux = Flux.just("A", "B", "C")
    .map(s -> {
        if (s.equals("B")) {
            throw new RuntimeException("Error occurred");
        }
        return s;
    })
    .onErrorReturn("Error");
// 转换为单个值(Reduce):
Mono<String> reducedFlux = fluxFromList.reduce((acc, s) -> acc + s);
// 从单个值创建:
Mono<String> monoFromValue = Mono.just("Hello");
// 从 Supplier 创建:
Mono<String> monoFromSupplier = Mono.fromSupplier(() -> "Hello");
// 从 Callable 创建:
Mono<String> monoFromCallable = Mono.fromCallable(() -> "Hello");
// 从 Runnable 创建:
Mono<Void> monoFromRunnable = Mono.fromRunnable(() -> System.out.println("Hello"));
// 从 Future 创建:
Mono<String> monoFromFuture = Mono.fromFuture(new CompletableFuture<String>() {{
    complete("Hello");
}});
// 订阅 Mono
monoFromValue.subscribe(System.out::println);
// 映射(Map):
Mono<String> mappedMono = monoFromValue.map(String::toUpperCase);
// 过滤(Filter):
Mono<String> filteredMono = monoFromValue.filter(s -> s.length() > 5);
// 错误处理(OnError):
Mono<String> errorMono = Mono.just("Hello")
    .map(s -> {
        if (s.equals("Hello")) {
            throw new RuntimeException("Error occurred");
        }
        return s;
    })
    .onErrorReturn("Error");
// 转换为单个值(Reduce):
Mono<Integer> sumMono = Mono.just(1)
    .map(i -> i * 2)
    .reduce(0, Integer::sum);
// 转换为 Flux:
Flux<String> fluxFromMono = monoFromValue.flux();
// 1. then 和 thenMany
// then:在当前 Mono 完成后,返回一个空的 Mono。
thenMany:在当前 Mono 完成后,返回一个新的 Flux。
Mono<String> mono1 = Mono.just("Hello");
Mono<String> mono2 = Mono.just("World");
mono1.then(mono2).subscribe(System.out::println); // 输出: World

// 2. zipWith:将两个 Mono 的结果合并为一个 Mono,使用提供的函数将两个结果组合在一起。
Mono<String> mono1 = Mono.just("Hello");
Mono<String> mono2 = Mono.just("World");
mono1.zipWith(mono2, (a, b) -> a + " " + b)
     .subscribe(System.out::println); // 输出: Hello World

// 3. flatMap:将当前 Mono 的结果转换为另一个 Mono,并返回新的 Mono。
Mono<String> mono = Mono.just("Hello");
mono.flatMap(s -> Mono.just(s.toUpperCase()))
    .subscribe(System.out::println); // 输出: HELLO

回到目录

Netty

各种IO模型的区别

对比维度同步 vs. 异步阻塞 vs. 非阻塞
关注点数据就绪后如何通知调用者调用 I/O 函数时是否立即返回
线程行为同步需主动等待,异步由内核回调阻塞会挂起线程,非阻塞立即返回
典型组合同步阻塞、同步非阻塞、异步非阻塞非阻塞通常配合多路复用(如 epoll)
💡tip
  • 把 channel 理解为数据的通道
  • 把 msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf
  • 把 handler 理解为数据的处理工序
    • 工序有多道,合在一起就是 pipeline,pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
    • handler 分 Inbound 和 Outbound 两类
  • 把 eventLoop 理解为处理数据的工人
    • 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
  • 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
    • 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
ChannelPipeline p = ...;
p.addLast("1", new InboundHandlerA());
p.addLast("2", new InboundHandlerB());
p.addLast("3", new OutboundHandlerA());
p.addLast("4", new OutboundHandlerB());
p.addLast("5", new InboundOutboundHandlerX());
// Inbound event propagation methods:
ChannelHandlerContext.fireChannelRegistered();
ChannelHandlerContext.fireChannelActive();
ChannelHandlerContext.fireChannelRead(Object);
ChannelHandlerContext.fireChannelReadComplete();
ChannelHandlerContext.fireExceptionCaught(Throwable);
ChannelHandlerContext.fireUserEventTriggered(Object);
ChannelHandlerContext.fireChannelWritabilityChanged();
ChannelHandlerContext.fireChannelInactive();
ChannelHandlerContext.fireChannelUnregistered();

// Outbound event propagation methods:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise);
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise);
ChannelHandlerContext.write(Object, ChannelPromise);
ChannelHandlerContext.flush();
ChannelHandlerContext.read();
ChannelHandlerContext.disconnect(ChannelPromise);
ChannelHandlerContext.close(ChannelPromise);
ChannelHandlerContext.deregister(ChannelPromise);

网络编程

public class NioSimpleServer {
    public static void main(String[] args) throws IOException {
        // 1.创建服务端通道:ServerSocketChannel
        ServerSocketChannel channel = ServerSocketChannel.open();
        channel.bind(new InetSocketAddress(10000));
        channel.configureBlocking(false);

        // 2.创建Selector监听器
        Selector selector = Selector.open();
        // 将服务端通道注册到监听器上,监听accept事件
        // register 的含义不是把监听器注册到通道,而是将通道订阅到监听器中,本意是交给 Selector 管理。
        channel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("NIO 单线程服务端启动完成...");
        // 3.循环监听
        while (true){
            System.out.println("等待客户端连接...");
            // 阻塞等待事件发生
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                // 用完后移除
                iterator.remove();

                if(key.isAcceptable()){
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    SocketChannel client = serverSocketChannel.accept();
                    client.configureBlocking(false);
                    System.out.println("客户端连接成功:" + client.getRemoteAddress());
                    // 建立通信通道后,将通道注册到监听器上,监听read事件
                    client.register(selector, SelectionKey.OP_READ);
                }

                if(key.isReadable()){
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = client.read(buffer);
                    if (len == -1) {
                        System.out.println("客户端断开连接:" + client.getRemoteAddress());
                        client.close();
                        continue;
                    }
                    buffer.flip();
                    String request = new String(buffer.array(), 0, len, StandardCharsets.UTF_8);
                    System.out.println("服务端收到客户端消息:" + request);

                    String response = "echo: " + request + "\n";
                    ByteBuffer wrap = ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8));
                    client.write(wrap);
                    System.out.println("服务端发送响应:" + response);
                }
            }
        }
    }
}
public class NioSimpleClient {
    public static void main(String[] args) throws IOException {
        // 1.客户端开启连接通道
        SocketChannel client = SocketChannel.open();
        client.configureBlocking(false);
        client.connect(new InetSocketAddress("localhost", 10000));
        // 2.打开监听器,订阅连接通道事件
        Selector selector = Selector.open();
        client.register(selector, SelectionKey.OP_CONNECT);
        System.out.println("客户端启动成功");

        // 3.监听通道事件
        while (true) {
            // 阻塞监听器,监听通道事件
            selector.select();

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    System.out.println("客户端连接成功:" + channel.getRemoteAddress());

                    // 4.连接成功,订阅写事件
                    if(channel.finishConnect()) {
                        channel.register(selector, SelectionKey.OP_WRITE);
                    }
                }
                if (key.isWritable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    String message = "hello server\n";
                    ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));
                    channel.write(buffer);
                    System.out.println("客户端发送数据:" + message);
                    // 5.发送成功,订阅读事件,读取服务端响应数据
                    channel.register(selector, SelectionKey.OP_READ);
                }

                if(key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int length = channel.read(buffer);
                    if(length > 0) {
                        buffer.flip();
                        String message = new String(buffer.array(), 0, length, StandardCharsets.UTF_8);
                        System.out.println("客户端收到服务端响应:" + message);
                        // 6.读取成功,关闭连接通道
                        channel.close();
                        return;
                    }
                }
            }
        }
    }
}

ByteBuffer

回到目录

Http

// restTemplate
String result = restTemplate.getForObject("https://api.example.com", String.class);

// WebClient
WebClient.create()
    .get()
    .uri("https://api.example.com")
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(result -> System.out.println(result));

// RestClient
// 同步调用
String result = RestClient.create()
    .get()
    .uri("https://api.example.com")
    .retrieve()
    .body(String.class);

// 异步调用
RestClient.create()
    .get()
    .uri("https://api.example.com")
    .retrieve()
    .body(String.class)
    .subscribe(System.out::println);

// Http 接口
@HttpExchange(url = "/users", accept = "application/json")
public interface UserClient {
    @GetExchange("/{id}")
    User getUser(@PathVariable Long id);

    @PostExchange
    Mono<User> createUser(@RequestBody User user);
}
// 使用
@Autowired
private UserClient userClient;
User user = userClient.getUser(1L); // 同步调用

回到目录

类加载器

JDBC 详细说明:

ℹ️note
  • 如果要遵循双亲委派机制,只需重写 findClass 方法。
  • 如果要打破双亲委派机制,需重写 findClass 和 loadClass 方法。
方法名作用
findLoadedClass()检查是否已加载该类
loadClass()根据类名加载类
defineClass()将字节数组转换为Class对象(实现安全控制的关键)
findClass()自定义类加载器的扩展点
resolveClass()执行类的连接阶段(验证、准备、解析)
getParent()获取父加载器
/**
 * 遵循双亲委派机制的自定义类加载器,重写findClass
 *
 * @author xuanwu
 */
public class FollowParentalDelegationClassLoader extends ClassLoader {

    // 定义加载类的路径
    private String classPath;

    public FollowParentalDelegationClassLoader(String classPath) {
        // 将系统类加载器设置为父加载器
        super(ClassLoader.getSystemClassLoader());
        this.classPath = classPath;
    }

    /**
     * 重写findClass方法
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 获取类文件的字节数组
        byte[] classData = getClassData(name);
        if (classData == null) {
            thrownew ClassNotFoundException("Class: " + name + " not found");
        }
        // 将字节数组转换为Class对象
        return defineClass(name, classData, 0, classData.length);
    }

    /**
     * 读取类文件的字节数组
     *
     * @param className 类名
     * @return 字节数组
     */
    private byte[] getClassData(String className) {
        String path = classPath + File.separator +
                className.replace('.', File.separatorChar) + ".class";
        try (InputStream ins = new FileInputStream(path);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

            int bufferSize = 4096;
            byte[] buffer = newbyte[bufferSize];
            int bytesNumRead;

            // 读取类文件到字节数组
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
/**
 * 打破双亲委派机制的自定义类加载器,重写findClass和loadClass
 */
public class NonDelegatingClassLoader extends ClassLoader {

    // 定义加载类的路径
    private String classPath;

    // 定义需要由此类加载器直接加载的类的包名前缀
    private Set<String> directLoadPackages;

    public NonDelegatingClassLoader(String classPath, String... directLoadPackages) {
        this.classPath = classPath;
        this.directLoadPackages = new HashSet<>(Arrays.asList(directLoadPackages));
    }

    @Override
    public 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 {
                    boolean shouldLoadDirectly = directLoadPackages.stream().anyMatch(pkg -> name.startsWith(pkg));
                    // 关键的代码在这里,逻辑可以自已修改
                    if (!shouldLoadDirectly) {
                        // 其他类还是走双亲委派机制
                        c = this.getParent().loadClass(name);
                    } else {
                        // 自己写的类,走自己的类加载器。
                        c = findClass(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;
        }


    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 获取类文件的字节数组
        byte[] classData = getClassData(name);
        if (classData == null) {
            thrownew ClassNotFoundException("Class " + name + " not found");
        }
        // 将字节数组转换为Class对象
        return defineClass(name, classData, 0, classData.length);
    }

    /**
     * 读取类文件的字节数组
     *
     * @param className 类名
     * @return 字节数组
     */
    privatebyte[] getClassData(String className) {
        // 将类名转换为文件路径
        String path = classPath + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try (InputStream ins = new FileInputStream(path);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

            int bufferSize = 4096;
            byte[] buffer = newbyte[bufferSize];
            int bytesNumRead;

            // 读取类文件到字节数组
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

回到目录

日志框架

链接

日志级别生效规则

回到目录

Session

CORS中的Cookie

// 1.使用axios
// axios 配置
axios.defaults.withCredentials = true; // 全局开启携带 Cookie

// 或在单个请求中开启
axios.get('http://api.example.com:8080/user/profile', {
  withCredentials: true
})
.then(response => {
  console.log(response.data);
})
.catch(error => {
  console.error('请求失败:', error);
});

// 2.使用fetch
fetch('http://api.example.com:8080/user/profile', {
  method: 'GET',
  credentials: 'include'  // 关键:包含 Cookie
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));

// 3.使用ajax
$.ajax({
  url: 'http://api.example.com:8080/user/profile',
  type: 'GET',
  xhrFields: {
    withCredentials: true  // 关键
  },
  success: function(data) {
    console.log(data);
  },
  error: function() {
    console.error('请求失败');
  }
});
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
// 1.创建一个全局配置类,统一处理所有跨域请求。
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")           // 拦截的路径
                .allowedOriginPatterns("http://localhost:3000")  // 允许的前端域名
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)          // ✅ 允许携带 Cookie
                .maxAge(3600);                   // 预检请求缓存时间
    }
}

// 2.使用 @CrossOrigin 注解(局部配置)适用于单个 Controller 或方法:
@RestController
@CrossOrigin(
    origins = "http://localhost:3000",
    allowCredentials = "true",  // ✅ 允许 Cookie
    allowedHeaders = "*",
    methods = {RequestMethod.GET, RequestMethod.POST}
)
public class UserController {

    @GetMapping("/user/profile")
    public ResponseEntity<User> getProfile(HttpSession session) {
        String username = (String) session.getAttribute("username");
        if (username == null) {
            session.setAttribute("username", "zhangsan");
        }
        return ResponseEntity.ok(new User(username));
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(HttpSession session) {
        session.setAttribute("username", "zhangsan");
        return ResponseEntity.ok("登录成功");
    }
}

// 3.使用 CorsConfigurationSource Bean(高级控制)适用于需要更精细控制的场景(如集成 Spring Security):
@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(Arrays.asList("http://localhost:3000"));
        config.setAllowedMethods(Arrays.asList("*"));
        config.setAllowedHeaders(Arrays.asList("*"));
        config.setAllowCredentials(true);  // ✅ 关键:允许 Cookie
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config); // 拦截路径
        return source;
    }
}

// 如果使用了 Spring Security,还需要确保它不覆盖 CORS 配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()  // 启用 CORS
        .csrf().disable()
        .authorizeRequests()
        .anyRequest().permitAll();
}
<!-- springmvc 全局 CORS 配置 -->
<mvc:cors>
  <!-- 拦截哪些路径 -->
  <mvc:mapping path="/api/**"
               allowed-origins="http://localhost:3000"
               allowed-methods="GET,POST,PUT,DELETE,OPTIONS"
               allowed-headers="*"
               allow-credentials="true"
               max-age="3600" />
</mvc:cors>
🔥warning
  • ⚠️ 注意:Access-Control-Allow-Origin 不能是 *(通配符),必须是具体的域名,否则浏览器会拒绝带凭据的请求
OPTIONS /user/profile HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
// 使用setAllowedOriginPatterns,灵活易扩展,但不能直接设为Arrays.asList("*"),必须是有约束的
// "*"、"http://*"、"https://*" 这类无域名限制的通配,在 allowCredentials=true 时都是不被允许的。
corsConfiguration.setAllowedOriginPatterns(Arrays.asList("https://*.a1.com"));
corsConfiguration.setAllowCredentials(true);

// setAllowedOrigins,也可用不灵活
corsConfiguration.setAllowedOrigins(Arrays.asList(
    "https://shop.a1.com",
    "https://admin.a1.com",
    "https://app.a1.com"
));
corsConfiguration.setAllowCredentials(true);

tomcat 中的 session

回到目录

Zookeeper

dataDir=D:\\zookeeper\\apache-zookeeper-3.8.4-bin\\data
dataLogDir=D:\\zookeeper\\apache-zookeeper-3.8.4-bin\\logs
# 默认是8080,因为8080可能被web应用使用,Springboot项目中tomcat的默认启动端口就是8080
admin.serverPort=9099
# 添加这个配置才能使用create -t ttl /node_name
extendedTypesEnabled=true
bin/zkServer.sh stop # 停止zookeeper 
bin/zkServer.sh start # 启动zookeeper
bin/zkServer.sh status # 查看zookeeper状态 
bin/zkServer.sh restart # 重启zookeeper 
bin/zkServer.sh start-foreground # 在前台启动zookeeper

集群配置

  1. zoo.cfg 配置文件最后添加如下配置:
ticketTime=2000
clientPort=2181
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/logs
initLimit=10
syncLimit=5
server.1=master:2888:3888 #主节点
server.2=node1:2888:3888  #从节点
server.3=node2:2888:3888  #从节点
  1. 创建myid文件:在 dataDir 指定的目录下 (即 /usr/local/zookeeper/data 目录) 创建名为 myid 的文件, 文件内容和 zoo.cfg 中当前机器的 id 一致。例如master配置如下:touch "1" > /usr/local/zookeeper/data/myid
  2. slave配置:echo -e "node1\nnode2" > /usr/local/zookeeper/conf/slave
  3. 将配置好的zookeeper发送到其他从节点。
  4. 在 dataDir 指定的目录下 (即 /usr/local/zookeeper/data 目录) 创建名为 myid 的文件。在 node1 生成 myid 文件 touch "2" >/usr/local/zookeeper/data/myid。在 node2 生成 myid 文件 touch "3" >/usr/local/zookeeper/data/myid

回到目录

IO 对象

两类IO

// 使用示例
Path path1 = Paths.get("/home/user/doc.txt");
Path path2 = Paths.get("C:", "Users", "doc.txt");  // Windows
Path path3 = Paths.get(URI.create("file:///tmp/test.txt"));
// Java 11 可以直接用 Path.of()
Path path1 = Path.of("/tmp/test.txt");
Path path2 = Path.of("C:", "Users", "test.txt");
// 旧方式
File file = new File("test.txt");
if (file.exists()) {
    long size = file.length();
    // 读取需要 FileInputStream + BufferedReader
}

// 新方式
Path path = Paths.get("test.txt");
if (Files.exists(path)) {
    long size = Files.size(path);
    List<String> lines = Files.readAllLines(path);  // 一行搞定
    // 或者流式处理
    Files.lines(path)
         .filter(line -> !line.isEmpty())
         .forEach(System.out::println);
}
// URI - 统一资源标识符(抽象概念)
URI uri = URI.create("https://user:pass@example.com:8080/path/file?query=1#frag");
System.out.println(uri.getScheme());    // https
System.out.println(uri.getHost());      // example.com
System.out.println(uri.getPath());      // /path/file
System.out.println(uri.getQuery());     // query=1
System.out.println(uri.getFragment());  // frag

// URL - 统一资源定位符(具体地址)
URL url = new URL("https://example.com/file.txt");
try (InputStream is = url.openStream()) {  // URL 可以打开连接获取内容
    byte[] data = is.readAllBytes();
}

// 相互转换
URI uri2 = url.toURI();    // URL → URI
URL url2 = uri.toURL();    // URI → URL (可能抛异常,如果scheme不支持)
// 主要实现类:
// 1. ClassPathResource - 类路径资源
Resource res1 = new ClassPathResource("config/application.yml");
// 对应 classpath:config/application.yml

// 2. FileSystemResource - 文件系统资源
Resource res2 = new FileSystemResource("/tmp/config.yml");
// 对应 file:/tmp/config.yml

// 3. UrlResource - URL资源
Resource res3 = new UrlResource("https://example.com/config.yml");
// 对应 https://example.com/config.yml

// 4. ByteArrayResource - 字节数组资源
Resource res4 = new ByteArrayResource("content".getBytes());

// 5. InputStreamResource - 输入流资源

// 使用 ResourceLoader 自动选择
@Autowired
private ResourceLoader resourceLoader;

public void loadResource(String location) throws IOException {
    Resource resource = resourceLoader.getResource(location);
    // location 可以是:
    // "classpath:config.yml"
    // "file:/tmp/config.yml"  
    // "https://example.com/config.yml"
    
    if (resource.exists()) {
        try (InputStream is = resource.getInputStream()) {
            // 处理资源
        }
    }
}

使用场景

// 方式1: 旧API
File configFile = new File("config.properties");
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(configFile)) {
    props.load(fis);
}

// 方式2: 新NIO API
Path configPath = Paths.get("config.properties");
Properties props2 = new Properties();
try (InputStream is = Files.newInputStream(configPath)) {
    props2.load(is);
}

// 方式3: Spring方式
@Value("classpath:config.properties")
private Resource configResource;

public Properties loadWithSpring() throws IOException {
    Properties props = new Properties();
    try (InputStream is = configResource.getInputStream()) {
        props.load(is);
    }
    return props;
}
// 旧方式
File dir = new File("/tmp");
File[] files = dir.listFiles();
if (files != null) {
    for (File f : files) {
        System.out.println(f.getName());
    }
}

// 新方式
Path dirPath = Paths.get("/tmp");
try (Stream<Path> stream = Files.list(dirPath)) {
    stream.filter(Files::isRegularFile)
          .map(Path::getFileName)
          .forEach(System.out::println);
}

// 递归遍历
try (Stream<Path> stream = Files.walk(dirPath, 3)) {  // 深度3层
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}
// 旧方式
try (FileInputStream fis = new FileInputStream("source.txt");
     FileOutputStream fos = new FileOutputStream("target.txt")) {
    byte[] buffer = new byte[8192];
    int length;
    while ((length = fis.read(buffer)) != -1) {
        fos.write(buffer, 0, length);
    }
}

// 新方式(一行搞定!)
Files.copy(Paths.get("source.txt"), Paths.get("target.txt"),
          StandardCopyOption.REPLACE_EXISTING,
          StandardCopyOption.COPY_ATTRIBUTES);

// 还有更多选项
// StandardOpenOption: READ, WRITE, APPEND, CREATE, CREATE_NEW...
// StandardCopyOption: REPLACE_EXISTING, COPY_ATTRIBUTES, ATOMIC_MOVE...
// LinkOption: NOFOLLOW_LINKS...

文件系统抽象

// 1. 默认文件系统
FileSystem defaultFs = FileSystems.getDefault();
Path defaultPath = defaultFs.getPath("/tmp/test.txt");

// 2. 内存文件系统
try (FileSystem memFs = FileSystems.newFileSystem(
        URI.create("memory:///"), 
        Map.of("create", "true"))) {
    
    Path memPath = memFs.getPath("/data.txt");
    Files.writeString(memPath, "Hello Memory FS!");
    
    // 读取
    String content = Files.readString(memPath);
    System.out.println(content);
}

// 3. ZIP/JAR 文件系统
Path zipPath = Paths.get("archive.zip");
try (FileSystem zipFs = FileSystems.newFileSystem(zipPath, (ClassLoader) null)) {
    Path entry = zipFs.getPath("/doc.txt");
    
    if (Files.exists(entry)) {
        List<String> lines = Files.readAllLines(entry);
    }
}

性能对比

// 大文件复制性能对比
public class PerformanceTest {
    
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("debian-13.1.0-amd64-DVD-1.iso");  // 3.7GB文件
        Path target1 = Paths.get("copy1.iso");
        Path target2 = Paths.get("copy2.iso");
        
        // 测试1: 传统IO
        long start1 = System.nanoTime();
        copyTraditional(source, target1);
        long time1 = System.nanoTime() - start1;
        
        // 测试2: NIO.2 Files.copy
        long start2 = System.nanoTime();
        Files.copy(source, target2, StandardCopyOption.REPLACE_EXISTING);
        long time2 = System.nanoTime() - start2;
        
        // 测试3: NIO 通道传输
        long start3 = System.nanoTime();
        copyWithChannels(source, Paths.get("copy3.iso"));
        long time3 = System.nanoTime() - start3;
        
        System.out.printf("传统IO: %.2f 秒%n", time1 / 1_000_000_000.0);        // 传统IO: 8.37 秒
        System.out.printf("Files.copy: %.2f 秒%n", time2 / 1_000_000_000.0);    // Files.copy: 1.60 秒
        System.out.printf("NIO通道: %.2f 秒%n", time3 / 1_000_000_000.0);       // NIO通道: 2.76 秒
    }
    
    static void copyTraditional(Path source, Path target) throws IOException {
        try (InputStream is = Files.newInputStream(source);
             OutputStream os = Files.newOutputStream(target)) {
            byte[] buffer = new byte[8192];
            int length;
            while ((length = is.read(buffer)) != -1) {
                os.write(buffer, 0, length);
            }
        }
    }
    
    static void copyWithChannels(Path source, Path target) throws IOException {
        try (FileChannel inChannel = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(target, 
                 StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            
            long size = inChannel.size();
            long transferred = 0;
            
            while (transferred < size) {
                transferred += inChannel.transferTo(transferred, 
                    size - transferred, outChannel);
            }
        }
    }
}

回到目录

排序

Comparable 接口

// Comparable<T> 接口定义
public interface Comparable<T> {
    int compareTo(T other);  // 比较自己和另一个对象
}

Comparator 接口

// Comparator<T> 接口定义
public interface Comparator<T> {
    int compare(T o1, T o2);  // 比较两个对象
}
// 1. 按分数升序
Comparator<Student> byScore = Comparator.comparingInt(Student::getScore);
students.sort(byScore);

// 2. 按分数降序
Comparator<Student> byScoreDesc = Comparator.comparingInt(Student::getScore).reversed();
// 1. 实现 Comparable
class ComparableStudent extends Student implements Comparable<ComparableStudent> {
    public ComparableStudent(String name, int score) {
        super(name, score);
    }

    @Override
    public int compareTo(ComparableStudent other) {
        return Integer.compare(this.score, other.score);
    }
}
Set<ComparableStudent> set = new TreeSet<>();
set.add(new ComparableStudent("Alice", 85));
set.add(new ComparableStudent("Bob", 92));


// 1. TreeSet 传入 Comparator
Comparator<Student> byScore = Comparator.comparingInt(Student::getScore);
Set<Student> treeSet = new TreeSet<>(byScore);
treeSet.add(new Student("Alice", 85, 20));
treeSet.add(new Student("Bob", 92, 22));
treeSet.add(new Student("Charlie", 85, 21));  // 分数相同,视为相等,不会添加

hashCode 和 equals

// 使用 Java 7+ 的 Objects.equals
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    
    Person person = (Person) obj;
    return Objects.equals(id, person.id) &&
           Objects.equals(name, person.name) &&
           age == person.age;  // 基本类型直接比较
}

// 使用 IDE 自动生成
// 在 IntelliJ 或 Eclipse 中,可以自动生成 equals 和 hashCode
// 使用 Objects.hash (Java 7+)
@Override
public int hashCode() {
    return Objects.hash(id, name, age);
}

必须遵守的原则

❗️equals 和 hashCode 的黄金法则
  • 规则1:必须同时重写 equals 和 hashCode
  • 规则2:equalshashCode 必须基于相同的字段
  • 规则3:用于计算 hashCode 的字段在对象生命周期中不能改变
ℹ️note
  • equals 定义逻辑相等
  • hashCode 提供快速查找
  • compareTo 定义排序顺序。
  • 三者必须协同工作,特别是在作为集合键时,一致性至关重要。

回到目录

JSON

Jackson

Jackson 常用注解

// 情况1:忽略特定字段
@JsonIgnoreProperties({"password", "salt", "secretKey"})  // 类级别,忽略多个字段
public class User {
    private Long id;
    private String username;
    private String password;  // 会被忽略
    private String salt;  // 会被忽略
    private String secretKey;  // 会被忽略
    private String email;
    private String phone;
}

// 情况2:忽略未知字段(防止前端传额外字段报错)
@JsonIgnoreProperties(ignoreUnknown = true)  // 忽略JSON中不存在的字段
public class UserUpdateDTO {
    private String username;
    private String email;
    
    // 如果前端传了 {"username": "zhang", "xxx": "haha"}
    // xxx字段会被忽略,不会报错
}
public class User {
    private Long id;
    
    @JsonProperty("user_name")  // JSON中显示为 user_name
    private String username;
    
    @JsonProperty("email")  // JSON中显示为 email
    private String emailAddress;
    
    // 还可以控制字段的访问权限
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)  // 只能读,不能通过JSON修改
    private Date createdAt;  // 创建时间应该由系统生成
    
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)  // 只能写,不会在JSON响应中显示
    private String password;  // 接收密码但不返回
  
    // 只读
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)

    // 只写(如密码字段)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)

    // 读写(默认行为)
    @JsonProperty(access = JsonProperty.Access.READ_WRITE)

    // 自动(根据getter/setter存在性决定)
    @JsonProperty(access = JsonProperty.Access.AUTO)
}
public class Order {
    private Long id;
    private String orderNo;
    
    @JsonFormat(
        pattern = "yyyy-MM-dd HH:mm:ss",  // 指定格式
        timezone = "GMT+8"  // 指定时区(北京时间)
    )
    private Date createTime;
    
    @JsonFormat(
        pattern = "yyyy-MM-dd",  // 只要年月日
        timezone = "GMT+8"
    )
    private Date userBirthday;
    
    @JsonFormat(
        pattern = "¥#,##0.00",  // 格式化金额
        locale = "zh_CN"  // 中文环境
    )
    private BigDecimal amount;
}
@JsonInclude(JsonInclude.Include.NON_NULL)  // 类级别:忽略所有null值
public class Product {
    private Long id;
    private String name;
    
    @JsonInclude(JsonInclude.Include.NON_EMPTY)  // 字段级别:非空(对字符串是null或"",对集合是null或空)
    private String description;
    
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<String> tags;
    
    @JsonInclude(JsonInclude.Include.NON_DEFAULT)  // 如果是默认值就不显示(int的默认值是0)
    private Integer stock = 0;
    
    private Double price;
}
// 1. 先定义视图(可以理解为"视角")
public class Views {
    public static class Public {}  // 公开视图
    public static class Internal extends Public {}  // 内部视图(继承公开视图)
    public static class Admin extends Internal {}  // 管理员视图
}

// 2. 在实体类上标注
public class User {
    @JsonView(Views.Public.class)  // 所有人都能看到
    private Long id;
    
    @JsonView(Views.Public.class)
    private String username;
    
    @JsonView(Views.Internal.class)  // 只有内部系统能看到
    private String email;
    
    @JsonView(Views.Internal.class)
    private String phone;
    
    @JsonView(Views.Admin.class)  // 只有管理员能看到
    private Double salary;
    
    @JsonView(Views.Admin.class)
    private String idCardNumber;
    
    // getters/setters...
}

// 3. 在Controller中使用
@RestController
@RequestMapping("/users")
public class UserController {
    
    // 公开接口 - 只返回公开信息
    @GetMapping("/public/{id}")
    @JsonView(Views.Public.class)
    public User getPublicUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    // 返回:{"id": 1, "username": "zhangsan"}
    
    // 内部接口 - 返回内部信息
    @GetMapping("/internal/{id}")
    @JsonView(Views.Internal.class)
    public User getInternalUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    // 返回:{"id": 1, "username": "zhangsan", "email": "zhang@qq.com", "phone": "13800138000"}
    // 注意:Internal继承了Public,所以Public的字段也会显示
    
    // 管理员接口 - 返回所有信息
    @GetMapping("/admin/{id}")
    @JsonView(Views.Admin.class)
    public User getAdminUser(@PathVariable Long id) {
        return userService.getUser(id);
    }
    // 返回所有字段
}

创建和操作 JSON 对象

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;

public class JacksonJsonNodeExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        
        // ========== 1. 创建 JSON 对象(类似 Fastjson 的 JSONObject)==========
        System.out.println("=== 1. 创建 JSON 对象 ===");
        
        // 方法1:使用 ObjectNode(推荐)
        ObjectNode user = mapper.createObjectNode();
        user.put("id", 1);
        user.put("name", "张三");
        user.put("age", 25);
        user.put("isStudent", false);
        
        System.out.println("ObjectNode: " + user);
        // 输出: {"id":1,"name":"张三","age":25,"isStudent":false}
        
        // 方法2:使用 JsonNodeFactory
        ObjectNode product = JsonNodeFactory.instance.objectNode();
        product.put("productId", 1001);
        product.put("productName", "iPhone");
        product.put("price", 6999.99);
        
        // 方法3:从字符串解析
        String jsonStr = "{\"username\":\"李四\",\"email\":\"li@example.com\"}";
        JsonNode parsedNode = mapper.readTree(jsonStr);
        System.out.println("从字符串解析: " + parsedNode);
        
        // ========== 2. 创建 JSON 数组(类似 Fastjson 的 JSONArray)==========
        System.out.println("\n=== 2. 创建 JSON 数组 ===");
        
        ArrayNode hobbies = mapper.createArrayNode();
        hobbies.add("篮球");
        hobbies.add("游泳");
        hobbies.add("读书");
        
        System.out.println("ArrayNode: " + hobbies);
        // 输出: ["篮球","游泳","读书"]
        
        // 添加到对象中
        user.set("hobbies", hobbies);
        
        // ========== 3. 嵌套对象 ==========
        System.out.println("\n=== 3. 嵌套对象 ===");
        
        ObjectNode address = mapper.createObjectNode();
        address.put("province", "北京");
        address.put("city", "北京市");
        address.put("district", "海淀区");
        
        user.set("address", address);
        
        // 嵌套数组
        ArrayNode phoneNumbers = mapper.createArrayNode();
        
        ObjectNode phone1 = mapper.createObjectNode();
        phone1.put("type", "home");
        phone1.put("number", "010-12345678");
        
        ObjectNode phone2 = mapper.createObjectNode();
        phone2.put("type", "mobile");
        phone2.put("number", "13800138000");
        
        phoneNumbers.add(phone1);
        phoneNumbers.add(phone2);
        
        user.set("phones", phoneNumbers);
        
        System.out.println("完整对象: " + user.toPrettyString());
        // 输出格式化的 JSON
        
        // ========== 4. 读取值 ==========
        System.out.println("\n=== 4. 读取值 ===");
        
        // 读取基本类型
        String name = user.get("name").asText();  // "张三"
        int id = user.get("id").asInt();  // 1
        double price = product.get("price").asDouble();  // 6999.99
        boolean isStudent = user.get("isStudent").asBoolean();  // false
        
        System.out.println("name: " + name);
        System.out.println("id: " + id);
        
        // 读取嵌套对象
        String city = user.get("address").get("city").asText();
        System.out.println("city: " + city);
        
        // 读取数组
        JsonNode firstHobby = user.get("hobbies").get(0);
        System.out.println("first hobby: " + firstHobby.asText());
        
        // 安全读取(避免 NullPointerException)
        String country = user.path("address")
                            .path("country")
                            .asText("中国");  // 默认值
        System.out.println("country (默认值): " + country);
        
        // ========== 5. 遍历对象 ==========
        System.out.println("\n=== 5. 遍历对象 ===");
        
        // 遍历所有字段
        user.fields().forEachRemaining(entry -> {
            System.out.println("Key: " + entry.getKey() + 
                             ", Value: " + entry.getValue());
        });
        
        // 遍历数组
        System.out.println("\n遍历爱好:");
        user.get("hobbies").forEach(hobby -> {
            System.out.println("hobby: " + hobby.asText());
        });
        
        // ========== 6. 修改和删除 ==========
        System.out.println("\n=== 6. 修改和删除 ===");
        
        // 修改值
        user.put("age", 26);
        user.put("name", "张三丰");
        
        // 添加新字段
        user.put("gender", "男");
        
        // 删除字段
        user.remove("isStudent");
        
        // 修改嵌套对象
        if (user.has("address")) {
            ((ObjectNode) user.get("address")).put("street", "中关村大街");
        }
        
        // 数组操作
        ArrayNode hobbiesNode = (ArrayNode) user.get("hobbies");
        hobbiesNode.add("编程");  // 添加
        hobbiesNode.remove(0);   // 删除第一个
        
        System.out.println("修改后: " + user);
        
        // ========== 7. 类型判断 ==========
        System.out.println("\n=== 7. 类型判断 ===");
        
        JsonNode testNode = user.get("name");
        System.out.println("isTextual: " + testNode.isTextual());  // true
        System.out.println("isNumber: " + testNode.isNumber());    // false
        System.out.println("isObject: " + testNode.isObject());     // false
        System.out.println("isArray: " + testNode.isArray());       // false
        
        JsonNode hobbiesNode2 = user.get("hobbies");
        System.out.println("hobbies isArray: " + hobbiesNode2.isArray());  // true
        
        // ========== 8. 序列化和反序列化 ==========
        System.out.println("\n=== 8. 序列化和反序列化 ===");
        
        // 转为 JSON 字符串
        String jsonString = mapper.writeValueAsString(user);
        System.out.println("JSON 字符串: " + jsonString);
        
        // 格式化输出
        String prettyJson = mapper.writerWithDefaultPrettyPrinter()
                                 .writeValueAsString(user);
        System.out.println("格式化 JSON:\n" + prettyJson);
        
        // 从字符串解析
        JsonNode parsedUser = mapper.readTree(jsonString);
        System.out.println("解析后的 name: " + parsedUser.get("name").asText());
        
        // ========== 9. 转为 Java 对象 ==========
        System.out.println("\n=== 9. 转为 Java 对象 ===");
        
        // 如果已有对应的 Java 类
        UserPojo userPojo = mapper.treeToValue(user, UserPojo.class);
        System.out.println("转为 UserPojo: " + userPojo);
        
        // Java 对象转为 JsonNode
        JsonNode nodeFromPojo = mapper.valueToTree(userPojo);
        System.out.println("从Pojo转为JsonNode: " + nodeFromPojo);
    }
}

// 对应的 POJO 类
class UserPojo {
    private int id;
    private String name;
    private int age;
    private String gender;
    private List<String> hobbies;
    private Address address;
    
    // 嵌套类
    static class Address {
        private String province;
        private String city;
        private String district;
        private String street;
        
        // getters 和 setters...
    }
    
    // getters 和 setters...
}

LocalDateTime 序列化配置

<!-- Maven -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.0</version>
</dependency>

<!-- 或使用 Spring Boot Starter 中的版本 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
public class Order {
    private Long id;
    private String orderNo;
    
    // 方式1:最简单的格式化
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    
    // 方式2:自定义序列化/反序列化器
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    
    // 方式3:全局格式 + 特定格式
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDateTime deliveryDate;  // 只要日期部分
    
    // 方式4:带时区(推荐用于跨时区系统)
    @JsonFormat(
        pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",  // ISO8601格式
        timezone = "UTC"  // 统一使用UTC
    )
    private LocalDateTime paymentTime;
    
    // 方式5:使用预设格式
    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ",  // 带时区
        timezone = "Asia/Shanghai"
    )
    private LocalDateTime cancelTime;
    
    // 方式6:处理可能为null的情况
    @JsonFormat(
        pattern = "yyyy-MM-dd HH:mm:ss",
        timezone = "GMT+8"
    )
    private LocalDateTime expireTime;
    
    // getters 和 setters...
}
@Configuration
public class JacksonConfig {
    
    // 定义全局日期时间格式
    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String TIME_FORMAT = "HH:mm:ss";
    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 1. 注册 JavaTimeModule
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        
        // 2. 配置 LocalDateTime 序列化/反序列化
        // LocalDateTime
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
        javaTimeModule.addSerializer(LocalDateTime.class, 
            new LocalDateTimeSerializer(dateTimeFormatter));
        javaTimeModule.addDeserializer(LocalDateTime.class,
            new LocalDateTimeDeserializer(dateTimeFormatter));
        
        // LocalDate
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        javaTimeModule.addSerializer(LocalDate.class,
            new LocalDateSerializer(dateFormatter));
        javaTimeModule.addDeserializer(LocalDate.class,
            new LocalDateDeserializer(dateFormatter));
        
        // LocalTime
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(TIME_FORMAT);
        javaTimeModule.addSerializer(LocalTime.class,
            new LocalTimeSerializer(timeFormatter));
        javaTimeModule.addDeserializer(LocalTime.class,
            new LocalTimeDeserializer(timeFormatter));
        
        // 3. 配置 Instant(时间戳)
        javaTimeModule.addSerializer(Instant.class, InstantSerializer.INSTANCE);
        javaTimeModule.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
        
        // 4. 注册模块
        objectMapper.registerModule(javaTimeModule);
        
        // 5. 其他配置
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);  // 不使用时间戳
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 设置时区
        
        return objectMapper;
    }
}
# application.yml
spring:
  jackson:
    # 日期格式化
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Shanghai
    
    # LocalDateTime 相关配置
    serialization:
      write-dates-as-timestamps: false  # 不使用时间戳
      write-date-timestamps-as-nanoseconds: false
      
    deserialization:
      adjust-dates-to-context-time-zone: false
      fail-on-unknown-properties: false
      
    # 空值处理
    default-property-inclusion: non_null
    
    # 美化输出
    pretty-print: true

Fastjson

Fastjson 常用注解

public class User {
    // 1. 修改JSON字段名
    @JSONField(name = "user_id")
    private Long id;
    
    @JSONField(name = "user_name")
    private String username;
    
    // 2. 忽略字段(不序列化)
    @JSONField(serialize = false)
    private String password;
    
    // 3. 控制反序列化(接收但不返回)
    @JSONField(deserialize = false)  // 只能反序列化,不序列化
    private String secretKey;  // 接收但不能返回
    
    // 4. 日期格式化
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    
    @JSONField(format = "yyyy-MM-dd")
    private Date birthday;
    
    // 5. 控制字段顺序
    @JSONField(ordinal = 1)  // 数字越小越靠前
    private String name;
    
    @JSONField(ordinal = 2)
    private Integer age;
    
    @JSONField(ordinal = 3)
    private String email;
    
    // 6. 处理空值
    @JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
    private String nickname;  // 即使为null也序列化
    
    // 7. 默认值
    @JSONField(defaultValue = "unknown")
    private String status;  // 如果为null,使用"unknown"
    
    // 8. 别名(接收多种字段名)
    @JSONField(alternateNames = {"mobile", "phone", "phoneNumber"})
    private String mobilePhone;
    
    // 构造方法
    public User() {}
    
    // getters 和 setters...
}
// 控制整个类的序列化行为
@JSONType(
    // 指定字段顺序
    orders = {"id", "name", "age", "email"},
    
    // 忽略某些字段
    ignores = {"password", "salt"},
    
    // 只包含某些字段
    includes = {"id", "name", "age"},
    
    // 序列化特性
    serialzeFeatures = {
        SerializerFeature.WriteMapNullValue,  // 输出空值字段
        SerializerFeature.WriteDateUseDateFormat,  // 使用日期格式
        SerializerFeature.PrettyFormat  // 美化输出
    },
    
    // 反序列化特性
    parseFeatures = {
        Feature.AllowUnQuotedFieldNames,  // 允许不引号的字段名
        Feature.IgnoreNotMatch  // 忽略不匹配的字段
    }
)
public class Person {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String password;  // 会被忽略
    private String salt;  // 会被忽略
    private String address;  // 不在includes中,也会被忽略
    
    // getters 和 setters...
}
public class Product {
    private Long productId;
    private String productName;
    private BigDecimal price;
    
    // 默认构造方法
    public Product() {}
    
    // 自定义构造方法(用于反序列化)
    @JSONCreator
    public Product(
        @JSONField(name = "id") Long productId,
        @JSONField(name = "name") String productName,
        @JSONField(name = "price") BigDecimal price
    ) {
        this.productId = productId;
        this.productName = productName;
        this.price = price;
    }
    
    // getters 和 setters...
}

// 使用
String json = "{\"id\": 1001, \"name\": \"iPhone\", \"price\": 6999}";
Product product = JSON.parseObject(json, Product.class);
// productId = 1001, productName = "iPhone", price = 6999
public class SerializerFeatureExample {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1L);
        user.setName("张三");
        user.setAge(null);  // age为null
        user.setEmail(null);  // email为null
        
        // 1. 基本序列化
        String json1 = JSON.toJSONString(user);
        // {"user_id":1,"user_name":"张三"}
        // age和email为null,默认不序列化
        
        // 2. 输出null值
        String json2 = JSON.toJSONString(user, 
            SerializerFeature.WriteMapNullValue);
        // {"user_id":1,"user_name":"张三","age":null,"email":null}
        
        // 3. 美化格式
        String json3 = JSON.toJSONString(user, 
            SerializerFeature.PrettyFormat,
            SerializerFeature.WriteMapNullValue);
        // {
        //   "user_id": 1,
        //   "user_name": "张三",
        //   "age": null,
        //   "email": null
        // }
        
        // 4. 空值写为空字符串
        String json4 = JSON.toJSONString(user,
            SerializerFeature.WriteNullStringAsEmpty);
        // {"user_id":1,"user_name":"张三","age":null,"email":""}
        
        // 5. 日期使用时间戳
        user.setCreateTime(new Date());
        String json5 = JSON.toJSONString(user,
            SerializerFeature.WriteDateUseDateFormat);
        // 使用日期格式
        
        String json6 = JSON.toJSONString(user,
            SerializerFeature.WriteDateUseTimestamp);
        // {"createTime":1672531199000}  // 时间戳格式
        
        // 6. 组合使用多个特性
        String json7 = JSON.toJSONString(user,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.PrettyFormat,
            SerializerFeature.WriteDateUseDateFormat,
            SerializerFeature.UseISO8601DateFormat);
    }
}
public class FeatureExample {
    public static void main(String[] args) {
        String json = "{id:1, name:'张三', AGE:25}";
        // 注意:字段名没有引号,大小写不匹配
        
        try {
            // 1. 严格模式(默认)
            User user1 = JSON.parseObject(json, User.class);
            // 会报错:字段名没有引号
            
            // 2. 宽松模式
            User user2 = JSON.parseObject(json, User.class,
                Feature.AllowUnQuotedFieldNames,  // 允许字段名不用引号
                Feature.IgnoreNotMatch,  // 忽略不匹配字段
                Feature.SupportArrayToBean,  // 支持数组转对象
                Feature.AllowSingleQuotes,  // 允许单引号
                Feature.AllowISO8601DateFormat  // 允许ISO8601日期格式
            );
            // 成功解析
            
            // 3. 自动转为下划线命名
            String json2 = "{\"user_id\":2,\"user_name\":\"李四\"}";
            User user3 = JSON.parseObject(json2, User.class,
                Feature.SupportAutoType  // 支持自动类型
            );
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
@Configuration
public class FastjsonConfig {
    
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1. 创建Fastjson消息转换器
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        
        // 2. 创建Fastjson配置
        FastJsonConfig config = new FastJsonConfig();
        
        // 3. 配置序列化规则
        config.setSerializerFeatures(
            SerializerFeature.WriteMapNullValue,  // 输出空字段
            SerializerFeature.WriteNullStringAsEmpty,  // 字符串null转""
            SerializerFeature.WriteNullListAsEmpty,  // List null转[]
            SerializerFeature.WriteNullNumberAsZero,  // 数字null转0
            SerializerFeature.WriteNullBooleanAsFalse,  // Boolean null转false
            SerializerFeature.PrettyFormat,  // 美化输出
            SerializerFeature.WriteDateUseDateFormat,  // 日期格式化
            SerializerFeature.DisableCircularReferenceDetect  // 禁用循环引用
        );
        
        // 4. 配置日期格式
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        
        // 5. 配置反序列化规则
        config.setParserFeatures(
            Feature.AllowUnQuotedFieldNames,
            Feature.AllowSingleQuotes,
            Feature.IgnoreNotMatch,
            Feature.AllowISO8601DateFormat
        );
        
        // 6. 配置序列化过滤器
        config.setSerializeFilters(
            new ValueFilter() {
                @Override
                public Object process(Object object, String name, Object value) {
                    // 自定义序列化逻辑
                    if (value == null) {
                        return "";
                    }
                    if (value instanceof Date) {
                        return ((Date) value).getTime();  // 日期转时间戳
                    }
                    return value;
                }
            }
        );
        
        // 7. 应用配置
        converter.setFastJsonConfig(config);
        
        // 8. 支持的MediaType
        converter.setSupportedMediaTypes(Arrays.asList(
            MediaType.APPLICATION_JSON,
            MediaType.APPLICATION_JSON_UTF8
        ));
        
        return new HttpMessageConverters(converter);
    }
}
// 危险!不要这样用!
String json = "{\"@type\":\"com.xxx.HackerClass\", ...}";
Object obj = JSON.parse(json);  // 可能被攻击!

// 安全的用法:
// 1. 指定具体类型
User user = JSON.parseObject(json, User.class);

// 2. 关闭自动类型推导
ParserConfig config = ParserConfig.getGlobalInstance();
config.setAutoTypeSupport(false);  // 关闭AutoType

// 3. 添加白名单
config.addAccept("com.yourcompany.");
config.addAccept("com.safe.");

// 4. 或者使用安全的API
String safeJson = JSON.toJSONString(user);
User safeUser = JSON.parseObject(safeJson, User.class);

HTTP-流式响应

http请求方式

public String httpURLConnectionGet(String url) throws IOException {
    HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
    conn.setRequestMethod("POST");
    // 设置请求头  
    conn.setRequestProperty("Accept", "application/json");
    // 启用输出流,允许发送请求体
    conn.setDoOutput(true);
    // 发送请求体
    try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream(), StandardCharsets.UTF_8)) {
        wr.write(jsonStr.getBytes(StandardCharsets.UTF_8));
        wr.flush();
    }
  
    // 读取响应
    int responseCode = conn.getResponseCode();
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
        StringBuilder response = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            response.append(line);
        }
        return response.toString();
    }
  
  
    // post请求
    String url = "http://localhost:8080/user/updateUser";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON); // 明确设置 Content-Type
    JSONObject paramMap = new JSONObject();
    paramMap.set("id", "1");
    paramMap.set("name", "zmy");
    paramMap.set("age", 18);
    paramMap.set("birthday", "2025-05-21 00:00:00");
    HttpEntity<String> request = new HttpEntity<>(paramMap.toString(), headers);
    String result = restTemplate.postForObject(url, request, String.class);
    JSONObject jsonObject = JSONUtil.parseObj(result);
    System.out.println(jsonObject.toJSONString(2));
}


// 同步请求
public String getWithRestTemplate(String url) {
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    return response.getBody();
}

// 流式读取
public void streamWithRestTemplate(String url) {
    restTemplate.execute(url, HttpMethod.GET, null, clientHttpResponse -> {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(clientHttpResponse.getBody()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 处理每一行数据
                processLine(line);
            }
        }
        return null;
    });
}
// 1. getForObject - 返回对象
public User getUserSimple(Long userId) {
    // 自动将 JSON 转换为 User 对象
    return restTemplate.getForObject(
        "http://api.example.com/users/{id}", 
        User.class,  // 返回类型
        userId       // 路径变量
    );
}

public List<User> getUserList() {
    // 使用 ParameterizedTypeReference 处理泛型
    return restTemplate.getForObject(
        "http://api.example.com/users",
        List.class  // 注意:这里会丢失泛型信息
    );
}
// 更好的方式处理泛型
public List<User> getUsersWithType() {
    // 使用 exchange 或 包装类
    ResponseEntity<List<User>> response = restTemplate.exchange(
        "http://api.example.com/users",
        HttpMethod.GET,
        null,
        new ParameterizedTypeReference<List<User>>() {}
    );
    return response.getBody();
}

// getForEntity获取完整响应体
public ResponseEntity<User> getUserWithDetails(Long userId) {
    // 返回 ResponseEntity,包含状态码、头部、body
    ResponseEntity<User> response = restTemplate.getForEntity(
        "http://api.example.com/users/{id}",
        User.class,
        userId
    );

    // 获取响应信息
    HttpStatus statusCode = response.getStatusCode();  // 状态码
    HttpHeaders headers = response.getHeaders();       // 响应头
    User user = response.getBody();                    // 响应体

    // 检查状态码
    if (statusCode.is2xxSuccessful()) {
        System.out.println("请求成功");
    }

    // 获取特定头部
    String contentType = headers.getContentType().toString();
    String cacheControl = headers.getCacheControl();

    return response;
}

public void getWithQueryParams() {
    // 使用 URI 构建查询参数
    UriComponentsBuilder builder = UriComponentsBuilder
        .fromUriString("http://api.example.com/users")
        .queryParam("page", 1)
        .queryParam("size", 10)
        .queryParam("sort", "name,desc");

    ResponseEntity<List<User>> response = restTemplate.getForEntity(
        builder.build().toUri(),
        new ParameterizedTypeReference<List<User>>() {}
    );
}

// exchange方法
public User getUserWithCustomHeaders(Long userId) {
    // 1. 创建请求头
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + getToken());
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

    // 2. 创建 HttpEntity(包含请求体和头部)
    User user = new User();
    HttpEntity<String> entity = new HttpEntity<>(user, headers);

    // 3. 发送请求
    ResponseEntity<User> response = restTemplate.exchange(
        "http://api.example.com/users/{id}",
        HttpMethod.POST,
        entity,      // 包含头部的请求实体
        User.class,
        userId
    );

    return response.getBody();
}
// 使用ParameterizedTypeReference返回复杂对象
public Map<String, Object> getComplexResponse() {
    ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
        "http://api.example.com/stats",
        HttpMethod.GET,
        null,
        new ParameterizedTypeReference<Map<String, Object>>() {}
    );

    return response.getBody();
}

// execute方法,最低层
public String executeWithCallback(String url) {
    return restTemplate.execute(
        url,
        HttpMethod.GET,
        request -> {
            // 自定义请求回调
            request.getHeaders().set("X-Custom-Header", "value");
            request.getHeaders().setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        },
        response -> {
            // 自定义响应提取
            if (response.getStatusCode().is2xxSuccessful()) {
                return new BufferedReader(new InputStreamReader(response.getBody()))
                    .lines().collect(Collectors.joining("\n"));
            } else {
                throw new RuntimeException("请求失败: " + response.getStatusCode());
            }
        }
    );
}

public void downloadFile(String fileUrl, String savePath) throws IOException {
    restTemplate.execute(
        fileUrl,
        HttpMethod.GET,
        null,
        clientHttpResponse -> {
            // 处理流式响应
            try (InputStream inputStream = clientHttpResponse.getBody();
                 FileOutputStream outputStream = new FileOutputStream(savePath)) {

                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
            return null;
        }
    );
}
// 同步请求
public String apacheHttpClientGet(String url) throws IOException {
    try (CloseableHttpClient client = HttpClients.createDefault()) {
        HttpGet request = new HttpGet(url);
        try (CloseableHttpResponse response = client.execute(request)) {
            return EntityUtils.toString(response.getEntity());
        }
    }
}

// 流式处理
public void apacheHttpClientStream(String url) throws IOException {
    try (CloseableHttpClient client = HttpClients.createDefault()) {
        HttpGet request = new HttpGet(url);
        try (CloseableHttpResponse response = client.execute(request)) {
            HttpEntity entity = response.getEntity();
            try (InputStream inputStream = entity.getContent();
                 BufferedReader reader = new BufferedReader(
                     new InputStreamReader(inputStream))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    processLine(line);
                }
            }
        }
    }
}

http header设置

public HttpHeaders getBasicRequestHeaders() {
    HttpHeaders headers = new HttpHeaders();
    
    // ============ 强烈推荐 ============
    // 1. User-Agent (必需,服务器识别客户端)
    headers.set("User-Agent", 
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
        "AppleWebKit/537.36 (KHTML, like Gecko) " +
        "Chrome/120.0.0.0 Safari/537.36");
    
    // 2. Accept (告诉服务器能接受的数据类型)
    headers.setAccept(Arrays.asList(
        MediaType.APPLICATION_JSON,       // 优先JSON
        MediaType.APPLICATION_XML,        // 其次XML
        MediaType.TEXT_PLAIN              // 最后文本
    ));
    
    // 3. Accept-Encoding (压缩支持,节省带宽)
    headers.set("Accept-Encoding", "gzip, deflate, br");
    
    // 4. Accept-Language (语言偏好)
    headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
    
    // 5. Connection (连接管理)
    headers.set("Connection", "keep-alive");  // 保持连接
    
    // 6. Cache-Control (缓存控制)
    headers.set("Cache-Control", "no-cache");  // 不要缓存
  
    // API特定Headers
    headers.setContentType(MediaType.APPLICATION_JSON);
    
    return headers;
}
public HttpHeaders getUploadHeaders(String fileName, long fileSize) {
    HttpHeaders headers = new HttpHeaders();
    
    // 必须设置Content-Type
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    
    // 边界字符串,用于multipart
    String boundary = "----WebKitFormBoundary" + UUID.randomUUID().toString();
    headers.setContentType(MediaType.parseMediaType(
        "multipart/form-data; boundary=" + boundary));
    
    // 文件信息
    headers.set("Content-Disposition", 
        "form-data; name=\"file\"; filename=\"" + fileName + "\"");
    
    // 文件类型
    String mimeType = Files.probeContentType(Paths.get(fileName));
    if (mimeType != null) {
        headers.set("Content-Type", mimeType);
    }
    
    // 文件大小
    headers.set("Content-Length", String.valueOf(fileSize));
    
    return headers;
}
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(
        @RequestParam String fileName,
        HttpServletRequest request) throws IOException {

    Path filePath = Paths.get("/files", fileName);
    Resource resource = new FileSystemResource(filePath);

    // 1. 基础响应Headers
    HttpHeaders headers = new HttpHeaders();

    // ============ 核心Headers ============
    // Content-Type (必须)
    String contentType = determineContentType(fileName);
    headers.setContentType(MediaType.parseMediaType(contentType));
    // 使用自定义头传递文件名
    headers.add("X-File-Name", fileName);

    // Content-Disposition (控制下载行为)
    String contentDisposition = String.format(
        "attachment; filename=\"%s\"", 
        URLEncoder.encode(fileName, StandardCharsets.UTF_8));
    headers.add("Content-Disposition", contentDisposition);

    // Content-Length (重要,帮助浏览器显示进度)
    headers.setContentLength(Files.size(filePath));

    // ============ 可选但推荐的Headers ============
    // 缓存控制
    headers.setCacheControl(CacheControl.noCache()
        .mustRevalidate()
        .cachePrivate());

    // 最后修改时间
    FileTime lastModified = Files.getLastModifiedTime(filePath);
    headers.setLastModified(lastModified.toMillis());

    // ETag (实体标签,用于缓存验证)
    String etag = "\"" + Files.size(filePath) + "-" + 
                  lastModified.toMillis() + "\"";
    headers.setETag(etag);

    // 范围请求支持 (大文件分片下载)
    headers.set("Accept-Ranges", "bytes");

    // ============ 安全相关Headers ============
    // 防止MIME类型嗅探
    headers.set("X-Content-Type-Options", "nosniff");

    // 防止点击劫持
    headers.set("X-Frame-Options", "DENY");

    // 内容安全策略
    headers.set("Content-Security-Policy", "default-src 'self'");

    return ResponseEntity.ok()
            .headers(headers)
            .body(resource);
}
public HttpHeaders apiRequestTemplate() {
    return HttpHeaders.of(Map.of(
        "Accept", "application/json",
        "Content-Type", "application/json",
        "User-Agent", "MyApp/1.0.0",
        "Authorization", "Bearer {token}",
        "X-Request-ID", UUID.randomUUID().toString(),
        "Accept-Encoding", "gzip, deflate",
        "Cache-Control", "no-cache"
    ));
}
public HttpHeaders fileUploadTemplate(String fileName) {
    String boundary = "----" + UUID.randomUUID();
    return HttpHeaders.of(Map.of(
        "Content-Type", "multipart/form-data; boundary=" + boundary,
        "Content-Disposition", 
            "form-data; name=\"file\"; filename=\"" + fileName + "\"",
        "Cache-Control", "no-cache"
    ));
}
public HttpHeaders fileDownloadTemplate(String fileName, long fileSize) {
    return HttpHeaders.of(Map.of(
        "Content-Type", getMimeType(fileName),
        "Content-Disposition", "attachment; filename=\"" + fileName + "\"",
        "Content-Length", String.valueOf(fileSize),
        "Accept-Ranges", "bytes",
        "Cache-Control", "no-cache, no-store, must-revalidate",
        "Pragma", "no-cache",
        "Expires", "0",
        "X-Content-Type-Options", "nosniff"
    ));
}
HttpHeaders headers = new HttpHeaders();
// 一次性设置内容类型和编码,不要分开设置:response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");
headers.set("Content-Type", "text/event-stream; charset=utf-8"); 
headers.set("Transfer-Encoding", "chunked");
headers.set("Cache-Control", "no-cache");
headers.set("Connection", "keep-alive");
headers.set("X-Accel-Buffering", "no");  // 如果使用Nginx代理

流式响应

// SSE响应头
response.setContentType("text/event-stream; charset=utf-8");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Connection", "keep-alive");
// 分块传输
response.setHeader("Transfer-Encoding", "chunked");
// 流式JSON
response.setContentType("application/x-ndjson"); // Newline Delimited JSON
@CrossOrigin(origins = "*")
@GetMapping("/bodyEmitter")
public ResponseBodyEmitter handle(HttpServletResponse response) {
    response.setContentType("text/event-stream; charset=utf-8");
    ResponseBodyEmitter emitter = new ResponseBodyEmitter(0L);

    CompletableFuture.runAsync(() -> {
        try {
            for (int i = 0; i < 5; i++) {
                Sse sse = new Sse(i, "eventType", "消息:" + i + ",时间:" + new Date());
                String message = sse.toJson();
                System.out.println(message);
                emitter.send(message, MediaType.APPLICATION_JSON);
                Thread.sleep(1000);
            }
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    }, sseExecutor); // 使用线程池
    return emitter;
}

@CrossOrigin(originPatterns = "http://127.0.0.1:[*], http://localhost:[*]")
@GetMapping("/sse")
public SseEmitter sse(HttpServletResponse response){
    // SseEmitter不设置text/event-stream也能流式输出,SseEmitter重载了extendResponse方法,在其中设置了响应头text/event-stream
    SseEmitter emitter = new SseEmitter(0L);
    CompletableFuture.runAsync(()->{
        try {
            for (int i = 0; i < 5; i++){
                System.out.println("发送消息" + i);
                emitter.send(SseEmitter.event()
                        .id(String.valueOf(i))
                        // .name("eventType")
                        .data("消息" + i + new Date(), MediaType.TEXT_PLAIN));
                TimeUnit.SECONDS.sleep(1);
            }
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    }, sseExecutor); // 使用线程池
    return emitter;
}

@CrossOrigin(origins = "*")
@GetMapping("/streaming")
public ResponseEntity<StreamingResponseBody> streamingResponse(HttpServletResponse response) throws IOException {
    StreamingResponseBody streamingResponseBody = out -> {
        Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
        for (int i = 0; i < 5; i++) {
            try {
                String message = "data: 消息:" + i + ",时间:" + new Date() + "\n\n";
                System.out.println(message);
                writer.write(message);
                writer.flush();// 主动刷新
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    return ResponseEntity.ok()
            .contentType(MediaType.TEXT_EVENT_STREAM) // 显示设置流式输出
            .body(streamingResponseBody);
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setTaskExecutor(taskExecutor);
        configurer.setDefaultTimeout(30000);
    }
}
❗️important
  • SseEmitter 和 ResponseBodyEmitter 应该使用线程池运行,用虚拟线程或固定池;注意超时和异常处理
  • StreamingResponseBody 适用大文件下载、简单流输出等,不需要使用线程池,但会占 Tomcat 线程;避免长时间运行
function setupSSEStream(url) {
    const eventSource = new EventSource(url);
    // onmessage只能处理默认的event:事件,
    eventSource.onmessage = (event) => {
        const data = event.data;
        console.log('Received:', data);
        updateUI(data);
    };
    
    eventSource.onopen = () => {
        console.log('Connection opened');
    };
    
    eventSource.onerror = (error) => {
        console.error('Error:', error);
        eventSource.close();
    };
        
    eventSource.onclose = (close) => {
        console.error('Close:', close);
        eventSource.close();
    };
    
    // 处理自定义结束事件
    eventSource.addEventListener('done', (event) => {
        console.log('Custom event:', event.data);
        eventsource.close();
    });
    
    return eventSource;
}

// 使用Fetch API处理流式响应,支持POST请求
// 处理文本流
async function handleTextStream(url) {
    const response = await fetch(url);
    const reader = response.body
        .pipeThrough(new TextDecoderStream())
        .getReader();
    
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        // 这里获取的value是后端的原始内容,需要手动获取各个部分,后端可返回json字符串,前端自己取
        console.log('Chunk:', value);
        processChunk(value);
    }
}

// 处理JSON流(NDJSON)
async function handleJSONStream(url) {
    const response = await fetch(url);
    const reader = response.body
        .pipeThrough(new TextDecoderStream())
        .getReader();
    
    let buffer = '';
    
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        buffer += value;
        const lines = buffer.split('\n');
        buffer = lines.pop() || ''; // 保存不完整的行
        
        for (const line of lines) {
            if (line.trim()) {
                try {
                    const data = JSON.parse(line);
                    processJSON(data);
                } catch (e) {
                    console.error('Parse error:', e);
                }
            }
        }
    }
}

// axios
// Axios处理流式响应
async function axiosStream(url) {
    try {
        const response = await axios({
            method: 'GET',
            url: url,
            responseType: 'stream', // Node.js环境
            onDownloadProgress: (progressEvent) => {
                // 处理进度
                const chunk = progressEvent.currentTarget.response;
                processPartialData(chunk);
            }
        });
    } catch (error) {
        console.error('Error:', error);
    }
}

// 浏览器环境使用ReadableStream
async function axiosBrowserStream(url) {
    const response = await axios.get(url, {
        responseType: 'blob',
        onDownloadProgress: (progressEvent) => {
            const percentCompleted = Math.round(
                (progressEvent.loaded * 100) / progressEvent.total
            );
            updateProgress(percentCompleted);
        }
    });
    
    const reader = response.data.stream().getReader();
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        processChunk(value);
    }
}

// WebSocket处理
function setupWebSocket(url) {
    const ws = new WebSocket(url);
    
    ws.onopen = () => {
        console.log('WebSocket connected');
        ws.send('start_stream');
    };
    
    ws.onmessage = (event) => {
        const data = event.data;
        processStreamData(data);
    };
    
    ws.onerror = (error) => {
        console.error('WebSocket error:', error);
    };
    
    ws.onclose = () => {
        console.log('WebSocket disconnected');
    };
    
    return ws;
}

Resource类

public void createResources() throws IOException {
    // 1. 通过 ResourceLoader(Spring 自动注入)
    @Autowired
    private ResourceLoader resourceLoader;

    Resource fileResource1 = resourceLoader.getResource("file:/path/to/file.txt");
    Resource classpathResource1 = resourceLoader.getResource("classpath:config/app.properties");
    Resource urlResource1 = resourceLoader.getResource("https://example.com/file.txt");

    // 2. 直接创建
    // FileSystemResource
    Resource fileResource2 = new FileSystemResource("/path/to/file.txt");
    Resource fileResource3 = new FileSystemResource(new File("/path/to/file.txt"));
    Resource fileResource4 = new FileSystemResource(Paths.get("/path/to/file.txt"));

    // ClassPathResource
    Resource classpathResource2 = new ClassPathResource("config/app.properties");
    Resource classpathResource3 = new ClassPathResource("config/app.properties", getClass());
    Resource classpathResource4 = new ClassPathResource("config/app.properties", getClass().getClassLoader());

    // UrlResource
    Resource urlResource2 = new UrlResource("https://example.com/file.txt");
    Resource urlResource3 = new UrlResource(new URL("https://example.com/file.txt"));

    // ByteArrayResource
    byte[] data = "Hello World".getBytes();
    Resource byteArrayResource = new ByteArrayResource(data);

    // InputStreamResource
    InputStream inputStream = new FileInputStream("/path/to/file.txt");
    Resource inputStreamResource = new InputStreamResource(inputStream);
}

回到目录

ByteBuddy

DynamicType.Unloaded<?> dynamicType = new ByteBuddy()//ByteBuddy 是不可变的,Unloaded表示未加载的类
  .with(new NamingStrategy.AbstractBase() {
    @Override
    protected String name(TypeDescription superClass) {
        return "i.love.ByteBuddy." + superClass.getSimpleName();//自定义类名
    }
  })
  .subclass(Object.class)//继承Object
  .make();

new ByteBuddy().subclass(Foo.class);//生成子类
new ByteBuddy().redefine(Foo.class);//重定义类,丢失原始信息
new ByteBuddy().rebase(Foo.class);//重基类,保留所有方法,生成增加方法

// 方法选择,类似于选择重载方法的逻辑,后添加的那个方法选择器位于栈顶,最先被应用,更具体的选择器应该放在最后调用
class Foo {
  public String bar() { return null; }
  public String foo() { return null; }
  public String foo(Object o) { return null; }
}
 
Foo dynamicFoo = new ByteBuddy()
  .subclass(Foo.class)
  .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
  .method(named("foo")).intercept(FixedValue.value("Two!"))
  .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance();

// 委托方法调用
public class Bar {
    public List<String> load(String info) {
        return Arrays.asList(info + ": foo", info + ": bar");
    }
}

public static class Interceptor {
    public static List<String> log(
      @SuperCall Callable<List<String>> zuper,// 调用目标方法,必不可少
      @This Object obj, // 动态生成的目标对象generateBar
      @AllArguments Object[] allArguments, // 注入目标方法的全部参数
      @Origin Method method, // 目标方法
      @Super Bar bar // 动态生成的父类实例,可以调用原始的方法,使用不同的参数,其中的字段和generateBar的字段是不同的
      )throws Exception {
        System.out.println("Calling Interceptor");
        try {
            System.out.println(bar.load(allArguments[0] + "123"));
            return zuper.call();
        } finally {
            System.out.println("Returned from database");
        }
    }

}

public void annotateDelegateTest() throws IllegalAccessException, InstantiationException {
    Bar generateBar = new ByteBuddy()
        .subclass(Bar.class)
        .method(named("load")).intercept(MethodDelegation.to(Interceptor.class))
        .make()
        .load(ByteBuddyTest.class.getClassLoader())
        .getLoaded()
        .newInstance();
  System.out.println(generateBar.load("ssss"));
}

// 转发方法调用
public class Bar {
    public List<String> log(String info) {
        return Arrays.asList(info + ": foo", info + ": bar");
    }
}
public class ForwardingLoggerInterceptor {
    private final Bar bar;
    public ForwardingLoggerInterceptor(Bar bar) {
        this.bar = bar;
    }
    public List<String> log(@Pipe Function<Bar, List<String>> pipe) {
        System.out.println("Calling log");
        try {
            return pipe.apply(bar);
        } finally {
            System.out.println("Returned from log");
        }
    }

    public static void main(String[] args) throws ReflectiveOperationException {
        Bar generateBar = new ByteBuddy()
                .subclass(Bar.class)
                .method(named("log"))
                .intercept(MethodDelegation.withDefaultConfiguration()
                        .withBinders(Pipe.Binder.install(Function.class))
                        .to(new ForwardingLoggerInterceptor(new Bar())))//转发到ForwardingLoggerInterceptor对象上的log方法
                .make()
                .load(ForwardingLoggerInterceptor.class.getClassLoader())
                .getLoaded()
                .getDeclaredConstructor().newInstance();

        System.out.println(generateBar.load("Hello"));;
    }
}

// 访问字段
class UserService {
    public String doSomething() { return null; }
}

interface Strategy {
    String doSomethingElse();
}

interface StrategyAccessor {
    Strategy getStrategy();
    void setStrategy(Strategy strategy);
}

interface InstanceCreator {
    Object makeInstance();
}
Class<? extends UserService> dynamicUserServiceClass = new ByteBuddy()
        .subclass(UserService.class)
        .method(not(isDeclaredBy(Object.class)))
        .intercept(MethodDelegation.toField("strategy"))// doSomething方法委托给strategy字段
        .defineField("strategy", Strategy.class, Visibility.PRIVATE)// 为子类定义字段
        .implement(StrategyAccessor.class)
        .intercept(FieldAccessor.ofBeanProperty())// 实现字段访问器,生成getter/setter
        .make()
        .load(getClass().getClassLoader())
        .getLoaded();
InstanceCreator factory = new ByteBuddy()
        .subclass(InstanceCreator.class)
        .method(not(isDeclaredBy(Object.class)))
        .intercept(MethodDelegation.toConstructor(dynamicUserServiceClass))// makeInstance委托给dynamicUserServiceClass的构造函数
        .make()
        .load(dynamicUserServiceClass.getClassLoader())// 必须使用dynamicUserServiceClass的类加载器
        .getLoaded().newInstance();


class HelloWorldStrategy implements Strategy {
    @Override
    public String doSomethingElse() {
        return "Hello World!";
    }
}

UserService userService = (UserService) factory.makeInstance();
((StrategyAccessor) userType).setStrategy(new HelloWorldStrategy());

// 类型注释
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeDefinition {
}

class RuntimeDefinitionImpl implements RuntimeDefinition {
    @Override
    public Class<? extends Annotation> annotationType() {
        return RuntimeDefinition.class;
    }
}
 
new ByteBuddy()
  .subclass(Object.class)
  .annotateType(new RuntimeDefinitionImpl())
  .make();

回到目录

Jackson

class User {

    @JsonProperty("user_name")
    String name;

    @JsonFormat(pattern="yyyy-MM-dd")
    LocalDate birthday;

    @JsonIgnore
    String password;
}

Mapper 做的事情其实很多:反射,发现字段,读取@JsonProperty,读取@JsonFormat,读取@JsonIgnore,找到LocalDateSerializer,调用Serializer,输出JSON,password 根本不会输出。这些都是 Object Mapping。

{
    "user_name":"Tom",
    "birthday":"2025-01-01"
}
{
    "name":"Tom",
    "age":18
}

回到目录

国产Web中间件

宝兰德中间件

命令行工具

中创中间件

命令行部署

function asadmin(){
  if [[ -z /opt/InforSuiteAS/as/bin/asadmin ]]; then
    sh /opt/InforSuiteAS/as/bin/asadmin "$@"
  else
    echo "/opt/InforSuiteAS/as/bin/asadmin not exists!"
    exit 1
  fi
}

相关配置

rm -rf /opt/InforSuiteAS/as/domains/domain1/logs
rm -rf /opt/InforSuiteAS/as/domains/domain1/generated
rm -rf /opt/InforSuiteAS/as/domains/domain1/osgi-cache
sed -i.bak -e '/<applications>/,/<\/applications>/d' -e '/<application-ref ref="[a-zA-Z]/d' /opt/InforSuiteAS/as/domains/domain1/config/domain.xml
sed -i '/<clusters><\/clusters>/a <applications><\/applications>' /opt/InforSuiteAS/as/domains/domain1/config/domain.xml
  1. /opt/InforSuiteAS/as/config/asenv.conf 配置 AS_JAVA="/usr/java/jdk1.8.0_341-amd64"
  2. /opt/InforSuiteAS/as/domain/domain1/config/domain.xml<java-config/> 配置 java-home<java-config classpath-suffix="" debug-options="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9009" java-home="/usr/java/jdk1.8.0_341-amd64" system-classpath="">
  3. domain.xml 优先级高于 AS_JAVA

东方通中间件

cat > ~/.asadminprefs <<EOF
AS_ADMIN_user=cli
AS_ADMIN_password=cli123.com
EOF
# 设置别名,指定环境变量 TONGWEB_HOME,如果单独运行 ./commandstool.sh,必须在 /opt/TongWeb/bin 目录下
echo alias twtool=\'env TONGWEB_HOME=/opt/TongWeb /opt/TongWeb/bin/commandstool.sh'\' >> ~/.bashrc

回到目录