李森的博客 - Java
type: channel
link: https://lisen.cc/tag/Java/
items: Java线程组和线程优先级
items: Java Callable接口、Future接口及FutureTask类用法
items: Java volatile关键字的用法
items: Java Runnable接口的run()方法和start()方法
items: JVisualVM：多合一故障处理工具
items: 借助jstack分析CPU占用高及线程死锁问题
items: 常用的dump文件分析工具
items: java获取JVM信息
items: VSCode配置Java（Spring Boot）开发环境
items: Spring Cloud Alibaba笔记修订版-第二章微服务环境搭建

Java线程组和线程优先级
type: item
date: 2025-04-24T18:57:23+08:00
description: 一、线程组Java中ThreadGroup表示线程组，每个线程比如属于一个线程组，创建线程时，如果没有指定线程组，那么新创建的线程默认属于其父线程所在线程组。public class ThreadDemo { public static void main(String[] args) { Thread testThread = new Thread(() -> { System.out.println("测试线程组：" + Thread.currentThread().getThreadGroup().getName()); System.out.println("测试线程名：" + Thread.currentThread().getName()); }); testThread.start(); System.out.println("main线程名：" + Thread.currentThread().getName()); } }线程组管理其中的线程，线程组是一个标准的树状结构，这样涉及的原因是防止“上级”线程被“下级”线程引用而无法进行有效的GC回收。二、线程优先级Java中线程优先级是支持指定的，范围是1-10，但并不是所有操作系统都支持10级优先级，，Java只给操作系统一个优先级的参考量，线程在操作系统中最终的优先级还是有操作系统决定的。线程默认优先级是5，线程的执行顺序由调度程序来决定，通常情况下，高优先级的线程会比低优先级的线程有更高的机率得到执行。import java.util.stream.IntStream; public class ThreadDemo { public static void main(String[] args) { IntStream.range(1, 10).forEach(i -> { Thread thread = new Thread(() -> { System.out.println(String.format("当前线程名称%s,优先级%s", Thread.currentThread().getName(), Thread.currentThread().getPriority())); }); thread.setPriority(i); thread.start(); }); } }某次的输出结果当前线程名称Thread-5,优先级6 当前线程名称Thread-0,优先级1 当前线程名称Thread-1,优先级2 当前线程名称Thread-4,优先级5 当前线程名称Thread-7,优先级8 当前线程名称Thread-2,优先级3 当前线程名称Thread-8,优先级9 当前线程名称Thread-6,优先级7 当前线程名称Thread-3,优先级4Java提供⼀个线程调度器来监视和控制处于RUNNABLE状态的线程。线程的调度策略采⽤抢占式，优先级⾼的线程⽐优先级低的线程会有更⼤的⼏率优先执⾏。在优先级相同的情况下，按照“先到先得”的原则。每个Java程序都有⼀个默认的主线程，就是通过JVM启动的第⼀个线程main线程。线程组与线程都有优先级，当线程组内的线程优先级高于线程组的优先级时，系统会自动设置线程的优先级为线程组的优先级。public class ThreadDemo { static int i = 0; public static void main(String[] args) throws Exception { ThreadGroup threadGroup = new ThreadGroup("TestThreadGroup"); threadGroup.setMaxPriority(4); Thread thread = new Thread(threadGroup, () -> { System.out.println("测试线程优先级与线程组优先级"); }); thread.setPriority(6); System.out.println("线程实际优先级：" + thread.getPriority()); } }输出结果线程实际优先级：4三、守护线程如果某线程是守护线程，那如果所有的⾮守护线程结束，这个守护线程也会⾃动结束。守护线程优先级较低。⼀个线程默认是⾮守护线程，可以通过setDaemon(boolean on)来设置一个线程是否是守护线程。import java.util.stream.IntStream; public class ThreadDemo { static int i = 0; public static void main(String[] args) throws Exception { …

Java Callable接口、Future接口及FutureTask类用法
type: item
date: 2025-04-24T15:47:40+08:00
description: 通常来讲，我们使用Runnable接口或者Thread类来创建一个新线程，但是他们有一个缺点，就是run方法没有返回值，如果我们需要开启线程后获得一个返回值，就可以使用Callable接口配合Future类来实现。一、Callable接口Callable⼀般是配合线程池⼯具ExecutorService来使⽤。ExecutorService可以使⽤submit⽅法来让⼀个Callable接⼝执⾏，它会返回⼀个Future，我们后续的程序可以通过这个Future的get⽅法得到结果。可以看一下下面的Demoimport java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.submit(futureDemo); try { System.out.println(tFuture.get()); } catch (Exception exception) { } } }运行后，控制台会输出Hello,World二、Future接口看一下Future接口的源代码public interface Future<V> { /** * 试图取消一个线程的执行，但是不一定能成功，因为任务可能已完成或已取消或其他因素不能取消 * * @param 是否允许打断正在执行的任务 * @return 是否取消成功 */ boolean cancel(boolean mayInterruptIfRunning); /** * 获取线程是否已取消 * * @return 线程完成前被取消返回true */ boolean isCancelled(); /** * 获取线程是否已完成 * * 如果线程异常、被终止或者取消，都会返回true * * @return 线程完成返回true */ boolean isDone(); /** * 获取线程返回值 * * @return 线程返回结果 */ V get() throws InterruptedException, ExecutionException; /** * 在给定时间内获取返回值，如果超时未获取到返回超时异常 * * @param 获取结果最大等待时间 * @param 时间计量单位 * @return 结果 */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }下面通过实际代码看一下Future接口的基本用法。修改上面代码如下import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.sub…

Java volatile关键字的用法
type: item
date: 2025-04-23T21:59:46+08:00
description: volatile是Java中的关键字，用于修饰变量，被volatile修饰的变量可以确保线程对这个变量的读写都是直接从主内存在中进行的。我们先来看下面一段代码public class Volatile { private boolean ready; public void setReady(boolean ready) { this.ready = ready; } public boolean getReady() { return this.ready; } public static void main(String[] args) throws Exception { Volatile volatile1 = new Volatile(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } volatile1.setReady(true); System.out.println("设置ready值为true"); }).start(); new Thread(() -> { while (!volatile1.getReady()) { } System.out.println("检测到ready变成" + volatile1.getReady()); }).start(); } }运行这段代码，我们查看控制台，可以看到虽然我们在第一个线程中将ready值设置成了true，但是第二个线程中，while仍然进入了一个死循环。这是因为在两个线程中，ready的值被隔离了，所以我们在第一个线程中修改的ready值在第二个线程中是获取不到的。这个时候，我们可以给ready变量加上volatile关键字进行修饰，修改后代码如下public class Volatile { private volatile boolean ready; public void setReady(boolean ready) { this.ready = ready; } public boolean getReady() { return this.ready; } public static void main(String[] args) throws Exception { Volatile volatile1 = new Volatile(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } volatile1.setReady(true); System.out.println("设置ready值为true"); }).start(); new Thread(() -> { while (!volatile1.getReady()) { } System.out.println("检测到ready变成" + volatile1.getReady()); }).start(); } }再次运行代码，可以发现第一个线程中的内容正常输出了，这就是volatile关键字的作用，一个线程中修改被volatile修饰的变量，另外一个变量能立即看到修改后的值。volatile关键字有两个作用：可见性及有序性。可见性当一个线程修改了被volatile修饰的变量的值，其他线程能够立刻看到修改后的值。这是因为被volatile修饰的变量不会被缓存到寄存器或其他处理器不可见的地方，因此保证了每次读取volatile变量都会从住内存中读取最新的值。有序性被volatile修饰的变量的读写具有一定的有序性，也就是禁止了指令重排序优化，这就意味着，在一个线程中，对被volatile修饰的变量的写操作一定发生在后续对这个变量的读操作之前。

Java Runnable接口的run()方法和start()方法
type: item
date: 2025-04-21T21:57:22+08:00
description: 在Java 1.8及后续的版本中，我们有三种（也可以说是两种）方法开启线程。继承Thread类实现Runnable接口使用Java 8的函数式编程其中1和3的方式没有什么歧义，我们重点说一下Runnable接口。其实标题说Runnable接口的run()方法和start()方法是不准确的，严格来讲，应该是Runnable接口的run()方法和实现Runnable接口的实例的start()。先说一下结论，start()方法来启动线程，真正实现了多线程运行。这时无需等待run方法体代码执行完毕，可以直接继续执行下面的代码；而run()方法当作普通方法的方式调用。程序还是要顺序执行，要等待run方法体执行完毕后，才可继续执行下面的代码，程序中只有主线程——这一个线程，其程序执行路径还是只有一条，这样就没有达到写线程的目的。我们可以通过下面代码进行验证。public class Demo { static class Runner1 implements Runnable { // 实现了Runnable接口，jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner1运行状态——————————" + i); } } } static class Runner2 implements Runnable { // 实现了Runnable接口，jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner2运行状态==========" + i); } } } public static void main(String[] args) { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); runner1.run(); runner2.run(); // new Thread(runner1).start(); // new Thread(runner2).start(); } }看一下控制台输出内容，程序是先执行完runner1之后才执行的runner2方法，系统并没有给线程排队，相当于runner1和runner2是顺序执行的。我们修改上面的代码，改成start()方法执行public class Demo { static class Runner1 implements Runnable { // 实现了Runnable接口，jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner1运行状态——————————" + i); } } } static class Runner2 implements Runnable { // 实现了Runnable接口，jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner2运行状态==========" + i); } } } public static void main(String[] args) { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); // runner1.run(); // runner2.run(); new Thread(runner1).start(); new Thread(runner2).start(); } }再次查看控制台可以看到runner1和runner2是交替执行的，也就证明了，此时正确的开启了线程。

JVisualVM：多合一故障处理工具
type: item
date: 2025-04-20T21:22:11+08:00
description: VisualVM是功能最强大的虚拟机运行监视和故障处理程序之一，曾经在很长一段时间内是Oracle官方助理发展的虚拟机故障处理工具。VisualVM的性能分析功能，比起JProfiler等专业收费工具也不遑多让，相比于第三方的功能，VisualVM还有一个很大的优点：不需要被监视的程序基于特殊的Agent去运行，因此它的通用性很强，对应用程序实际性能影响也比较小，使得它可以直接应用于生产环境。在这个注重颜值的时代，相较于JProfiler，可能界面比较丑陋是最大的缺点了吧。在强大的插件加持下，VisualVM可以实现包括但不限于以下的功能：显示虚拟机进程以及进程的配置、环境信息显示应用程序的处理器、垃圾收集、堆、方法区及线程信息dump以及分析堆转储快照方法级的应用程序性能分析，找出被调用最多、运行时间最长的方法壹、安装插件VisualVM插件可以直接在线安装，依次点击【tools】- 【plugins】勾选需要安装的插件，点击【install】即可进行安装。贰、生成、浏览堆转储快照生成和浏览堆转储快照可能是日常开发过程中分析性能最常见的操作，下面介绍下在visualVM中如何生成或者分析堆转储快照。一、生成堆转储快照我们运行一个程序后，在visualVM左侧Local下，会自动加载对应的进程信息。比如我这里的JHSDB_TestCae，我们下面都以这个为例进行说明。可以通过以下两种方式之一生成堆转储快照选择应用程序，点击右键，然后选择【Heap Dump】双击应用程序，在打开的页面中，选择【Monitor】，然后选择【Heap Dump】快照存储后会自动打开。二、打开堆转储快照如果是别人发过来的快照文件，可以点击左上角的文件图标打开。Overview页签显示的是一些基本信息，包括pid、Java版本、虚拟机参数、环境参数等信息。Monitor页签是图标信息显示的CPU、堆、元空间、类及线程等信息。Threads页签显示的是线程信息Sampler页签以一定的时间间隔对CPU、内存进行采样，可检查出占用CPU时间较多或占用内存空间较大的线程，有助于性能调优。对CPU采样时，该页提供CPU样例(CPU samples)和线程CPU时间(Thread CPU time)两个子页签，前者可用于分析调用链上的方法耗时，后者可用于比较线程CPU耗时。profiler是性能分析页签，因为性能分析对程序运行性能影响较大，生产环境不建议使用。叁、常见插件介绍上面所过，VisualVM是支持插件的，下面我们介绍几款常用的插件。一、BTrace动态日志跟踪BTrace允许在不中断目标程序的前提下，通过HotSpot虚拟机的Instrument功能动态加入原本并不存在的调试代码。在插件页面，选择BTrace Workbench，然后安装我们创建一段输出固定内容的死循环并运行package cc.lisen.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * description: * * @author: lisen * @Blog：lisen.cc * @DateTime: 2025-04-20 20:34 */ public class BTraceTest { public String sayHello(String name) { return "Hello " + name; } public static void main(String[] args) throws IOException { while (true) { BTraceTest bTraceTest = new BTraceTest(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.println(bTraceTest.sayHello(reader.readLine())); } } }打开VisualVM，找到我们的程序，点击右键选择【Trace Application】，然后输入以下脚本/* BTrace Script Template */ import org.openjdk.btrace.core.annotations.*; import static org.openjdk.btrace.core.BTraceUtils.*; @BTrace(unsafe=true) public class TracingScript { @OnMethod( clazz="c…

借助jstack分析CPU占用高及线程死锁问题
type: item
date: 2025-04-18T15:31:00+08:00
description: jstack是用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合，生成线程快照的主要目的是定位线程出现长时间停顿的原因。一、分析CPU占用高的问题在实际项目开发过程中，CPU占用异常是经常遇到的问题，当遇到CPU异常时，我们可以借助jstack进行问题排查。下面我们通过模拟，刨析一下整个问题分析过程。模拟代码在下面的代码中，我们写了一个死循环，模拟CPU占用过高问题。public class ThreadMain { private static ExecutorService executorService = Executors.newFixedThreadPool(5); public static void main(String[] args) { Task task1 = new Task(); Task task2 = new Task(); executorService.execute(task1); executorService.execute(task2); } public static Object lock = new Object(); static class Task implements Runnable { public void run() { synchronized (lock) { long sum = 0L; while (true) { sum += 1; } } } } }查询CPU占用高的进程在Linux服务器，我们通过top命令，可以查看CPU信息，默认按照由高到低显示。输入top命令，查询进程信息。top可以看到，目前占用最高的进程ID是9420查询对应线程在上面，我们已经查询到9420进程，我们可以进一步分析，9420进程内到底是哪个线程CPU占用最高。输入一下命令，可以查看线程信息top Hp 9420可以看到目前占用CPU最高的线程是9445线程转换我们通过top命令查询到的9445线程ID是10进制的，但是jstack展示的线程是16进制的，因此我们需要将10进制的9445转成16进制，也就是0x24e5通过jstack查询线程快照输入一下命令jstack 9420{alert type="info"}jstack后的vmid就是进程ID{/alert}jstack输出信息的nid就是16进制的线程id，我们找到nid是0x24e5线程，可以定位到具体代码位置 at ThreadMain$Task.run(ThreadMain.java:30)，也就是ThreadMain.java的第30行。二、分析线程死锁问题模拟代码我们通过下面的代码，模拟线程死锁问题public class ThreadMain { private final static Object lockA = new Object(); private final static Object lockB = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "获得了锁LockA"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); } } }).start(); new Thread(() -> { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (…

常用的dump文件分析工具
type: item
date: 2025-04-16T23:35:00+08:00
description: 当系统发生内存溢出时，我们一般的处理方式都是通过抓取内存堆转储快照的形式进行分析，本文我们介绍几种日常常用的dump文件分析工具。准备工作我们通过-XX:+HeapDumpOnOutOfMemoryError参数让虚拟机在出现内存溢出时Dump出当前的内存堆转储快照，然后对Dump出的文件进行分析。示例代码如下import java.util.ArrayList; import java.util.List; public class HeapDump { static class HeapDumpObject { } public static void main(String[] args) { List<HeapDumpObject> heapDumpObjectList = new ArrayList<>(); while (true) { heapDumpObjectList.add(new HeapDumpObject()); } } }我们通过命令运行程序javac HeapDump.java java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError HeapDump此时程序会在程序运行目录下生成.hprof格式的dump文件，我们可以针对此文件进行分析，查找内存泄漏对象的引用路径，并定位到代码具体位置。工具一：JVisualVMJVisualVM是JDK自带的dump，目前在JDK中已经没有预置此工具了，我们可以通过 官网进行下载。点击左上角，可以加载我们的dump文件可以切换摘要、线程等页签，查看不同的跟踪信息。工具二：IntelliJ Idea自带的分析器依次打开【视图】-【工具窗口】- 【分析器】点击【打开快照】，选择我们上方生成的.hprof文件可以分析最大对象以及定位我们异常发生的位置。工具三：MATMAT全称Memory Analyzer，是eclipse出品的一款独立的内存分析工具，可以在 官网下载。{message type="success" content="下载时，注意选择镜像位置，默认日本节点较慢"/}打开软件后，选择[Open a Heap Dump]可以查看对象占用空间等信息，也可以定位具体代码位置工具三：JProfilerJProfiler 是一款强大的性能分析工具，能够深入分析 Java 应用程序的 CPU 使用情况、内存消耗、线程状态和数据库查询，从而帮助快速定位性能瓶颈。打开JProfiler后，点击左上角【会话】-【打开快照】，可以选择我们本地的dump文件。双击占用最大的类，然后引用选择【传入引用】在【引用】页签，选择点击【显示更多】，可以查看异常代码的位置。

java获取JVM信息
type: item
date: 2025-04-16T11:38:00+08:00
description: 在Java或者Spring Boot日常开发过程中，因为Java的自动垃圾回收机制，我们可能很少关注JVM的信息，面临性能问题时，我们可能也会更多的借助JProfiler等第三方工具进行问题分析。比如我们需要做一个程序运行健康监控功能，我们可能就需要获取Jvm的信息，下面我们就看一下如何在Java中获取Jvm信息。首先我们定义两个公共方法，分别是用来将毫秒转换成时分秒，另外一个方法是将时间戳转换成时分秒毫秒。/** * 毫秒转换成时分秒 * * @param uptime 毫秒 * @return 时分秒 */ protected static String toDuration(double uptime) { NumberFormat fmtI = new DecimalFormat("###,###", new DecimalFormatSymbols(Locale.ENGLISH)); NumberFormat fmtD = new DecimalFormat("###,##0.000", new DecimalFormatSymbols(Locale.ENGLISH)); uptime /= 1000; if (uptime < 60) { return fmtD.format(uptime) + " 秒"; } uptime /= 60; if (uptime < 60) { long minutes = (long) uptime; return fmtI.format(minutes) + "分"; } uptime /= 60; if (uptime < 24) { long hours = (long) uptime; long minutes = (long)((uptime - hours) * 60); String s = fmtI.format(hours) + "时"; if (minutes != 0) { s += " " + fmtI.format(minutes) + "分"; } return s; } uptime /= 24; long days = (long) uptime; long hours = (long)((uptime - days) * 24); String s = fmtI.format(days) + "天"; if (hours != 0) { s += " " + fmtI.format(hours) + "时"; } return s; } /** * 时间戳钻日期 * * @param timeStamp 时间戳 * @return 日期 */ protected static String timeStamp2Date(long timeStamp) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH); return simpleDateFormat.format(timeStamp); }{mtitle title="获取Jvm基本信息"/}可以获取jvm的名称、jvm版本、jvm制造商、java版本、jvm启动时间、jvm运行时间等信息RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); System.out.printf("jvm名称：%s %n", runtimeMXBean.getVmName()); System.out.printf("jvm版本：%s %n", runtimeMXBean.getVmVersion()); System.out.printf("jvm制造商：%s %n", runtimeMXBean.getVmVendor()); System.out.printf("java版本：%s %n", System.getProperty("java.version")); System.out.printf("jvm启动时间：%s %n", timeStamp2Date(runtimeMXBean.getStartTime())); System.out.printf("jvm运行时间：%s %n", toDuration(runtimeMXBean.getUptime()));{mtitle title="获取编译信息"/}可以获取编译器名称、编译耗时及是否支持及时编译器编译监控。CompilationMXBean compilationMXBean = ManagementFactor…

VSCode配置Java（Spring Boot）开发环境
type: item
date: 2025-03-30T20:52:05+08:00
description: {message type="info" content="VSCode简介"/} 大名鼎鼎的VS Code不用过多介绍吧。VS Code全称Visual Studio Code，是由微软开发的一款免费、开源、跨平台的代码编辑器，支持Windows、macOS和Linux系统。它以其轻量级、高性能和丰富的扩展生态广受开发者欢迎，适用于多种编程语言和开发场景。 如果你也厌倦了一成不变的IntelliJ IDEA开发环境，想换换开发风格，不妨体验一下在VSCode中开发Spring Boot程序。{message type="success" content="前置条件"/} 本文基于Mac OS进行介绍，在Windows及Linux下配置也是类似的，为了顺利完成环境搭建，你需要提前准备好以前软件及配置。JDK并完成环境变量配置。Maven并完成环境变量配置。VSCode软件{message type="warn" content="安装插件"/} 其实所谓的配置环境，不过就是安装一堆Java插件。 打开VS Code，找到【插件】页签，我们需要安装以下插件。Debugger for JavaMaven for JavaLanguage Support for Java(TM) by Red HatExtension Pack for Java{message type="error" content="使用"/} 你可以找一个现有的Spring Boot环境，或者使用脚手架大家一个简单的Spring Boot程序。{alert type="info"} 官方脚手架目前已经不支持JDK1.8，如果使用JDK1.8的话，可以使用阿里云提供的脚手架，地址为： start.aliyun.com {/alert} 使用VS Code打开代码，找到启用程序，如下图 可以点击Run运行程序或者点击Debug调试程序。 也可以打短点调试程序，这里我在启动方法中打一个短点，然后点击Debug调试程序，之后程序就会进入断点。 我们也可以安装依赖，比如下面安装lombok依赖 如果需要打包，可以在控制台输入mvn package命令通过原生Maven的方式进行打包，因为我们上面安装了Maven插件，在VS Code左下角（默认）有个Maven选项，也可以打包，与Idea基本一致。 好了，以上就是在VS Code下搭建Java（Spring Boot）开发环境的完整步骤，感兴趣的童鞋可以去尝试一下。

Spring Cloud Alibaba笔记修订版-第二章微服务环境搭建
type: item
date: 2022-11-23T22:43:00+08:00
description: 因为第一章都是一些概念性的东西，包括系统架构的演变、微服务架构的介绍（服务调度、服务治理、服务容错、链路追踪等等），大家感兴趣的可以阅读原文，我们这里直接从第二章微服务环境搭建开始。本次使用的电商项目中的商品、订单、用户为案例进行讲解。一、技术选型JDK ：1.8maven ：3.8.6数据库 ：MySQL 8.0.31持久层 ：SpringData Jpa其他 ：Spring Cloud Alibaba 2021.0.4.0，截止到目前最新版本开发工具 ：IntelliJ idea 2022.2## 二、模块设计springcloud-alibaba：父工程shop-common：公共模块【实体类】shop-user：用户微服务【端口：807x】shop-product：商品微服务【端口：808x】`shop-order：订单微服务【端口：809x】2.1、创建父工程打开idea，创建maven工程，选择【New Project】，输入Name、GroupId、ArtifactId，选择存储目录，JDK选择本机安装的1.8版本。点击CREATE完成项目创建。springcloud-alibaba只是作为父工程，我们不会写任何代码，所以直接把src文件夹整体删掉。然后在pom.xml中添加相关依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.11</version> <relativePath/> </parent> <groupId>cc.lisen</groupId> <artifactId>springcloud-alibaba</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version> <spring-cloud.version>2021.0.4</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud.version}</version> </dependency> </dependencies> </dependencyManagement> </project>注意一下是Spring Cloud Alibaba与Spring Cloud及Spring Boot的版本对应关系，一定要选择对应…