• [技术干货] Java外功基础(1)——Spring Web MVC-转载
    1.前置知识1.1 Tomcat定义:Tomcat是一个开源的轻量级Web(Http)服务器和Servlet容器。它实现了Java Servlet等Java EE规范的核心功能,常用于部署和运行Java Web应用程序 。换言之,Tomcat就是一个严格遵循Servlet规范开发出来的、可以独立安装和运行的Java Web服务器/Servlet容器核心功能:Servlet容器:支持Servlet的执行,处理HTTP请求和响应Web服务器:提供静态资源(如HTML)的访问能力,支持基本的HTTP服务安装与版本对应:tomcat官网:Apache Tomcat®目录结构:bin:存放可执行文件,如startup.batconf:存放配置文件lib:存放Tomcat运行所需的jar文件logs:存储日志文件temp:存放临时文件,如上传的文件或缓存数据webapps:默认web应用部署目录work:服务器的工作目录,存放运行时生成的临时文件(编译文件)1.2 Servlet1.2.1 定义Servlet是Java语言编写的、运行在服务器端的程序,它遵循一套标准的API规范(Tomcat是这套规范的一个具体实现/容器,并提供了让Servlet与前端交互的运行时环境)1.2.2 API示范创建项目/配置文件:(1)在IEDA中创建Maven项目(2)在pom.xml文件中添加servlet依赖(置于< project >< /project >标签下)<dependencies>        <dependency>            <groupId>jakarta.servlet</groupId>            <artifactId>jakarta.servlet-api</artifactId>            <!--servlet依赖版本应与jdk和tomcat的版本相匹配-->            <version>6.1.0</version>            <scope>provided</scope>        </dependency></dependencies>(3)在main路径下创建webapp/Web-INF/web.xml,在xml文件中添加以下内容<!DOCTYPE web-app PUBLIC        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"        "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app>    <display-name>Archetype Created Web Application</display-name></web-app>(4)下载插件:Smart Tomcat(为了方便启动项目)API示例:import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;//设置访问路径(url)@WebServlet("/method")//继承HttpServlet并重写doGet和doPost方法public class MethodServlet extends HttpServlet {    //接收method=post的请求    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doPost");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doPost");    }    //接收method=put的请求    @Override    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doPut");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doPut");    }    //接收method=delete的请求    @Override    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {        System.out.println("doDelete");        resp.setContentType("text/html; charset=utf-8");        resp.getWriter().write("doDelete");    }}1.2.3 生命周期定义:Servlet 生命周期由 Web容器(如Tomcat)管理,包含加载、初始化、处理请求和销毁四个阶段。每个阶段对应特定的方法调用,开发者可通过重写这些方法实现自定义逻辑1.类加载:Web容器通过类加载器加载 Servlet 类(通常首次请求触发或容器启动时预加载,字节码文件被加载到内存但未实例化)。具体类加载流程请阅读:Java虚拟机——JVM(JavaVirtualMachine)解析一1.5 实例化:确认Servlet类成功加载后立刻执行,在整个Web容器中每种Servlet类(如HttpServlet)只会有一个实例化对象2.初始化:Web容器调用刚刚创建好的Servlet实例的init(ServletConfig config)方法,在整个servlet实例的生命周期中仅调用一次,主要作用是读取配置和资源加载。若初始化失败,抛出ServletException,Servlet不会被加入可用队列3.处理请求:Web容器为每个请求创建线程,调用service(ServletRequest req, ServletResponse res)方法。service() 方法根据HTTP请求类型(get/post)调用doGet()或doPost()4.销毁:Web容器调用 destroy()方法,Servlet 实例被标记为垃圾回收2.SpringBootServlet是Java EE规范中处理Web请求的核心组件,但随着应用复杂度提升,Servlet的直接使用显得笨重。Spring框架通过一系列抽象和扩展,简化了企业级应用开发我们可以用一个非常形象的比喻来贯穿始终:建造一座房子第一阶段:Servlet 时代 - 自己烧砖砌墙目标: 建造一个能遮风挡雨的房子(一个能处理HTTP请求的Web应用)你的工作状态:材料: 你有最基础的原材料——泥土(Java 语言)和水(JVM)。你需要自己烧制砖块(编写Servlet类)工具: 只有简单的泥瓦刀(Servlet API)过程:1.你为每一面墙、每一扇门都亲手烧制一块特定的砖(编写 LoginServlet, UserServlet, OrderServlet)2.你亲自规划每块砖的位置(在web.xml中配置大量的 < servlet > 和 < servlet-mapping >)3.你亲自搅拌水泥,一块一块地砌墙(在每个 Servlet 的doGet/doPost 方法中手动解析参数、处理业务、组装 HTML)核心特点:高度灵活: 你可以造出任何形状的砖极其繁琐: 大量重复性劳动(每个 Servlet 都有获取参数、关闭连接等样板代码)难以维护: 砖块之间紧密耦合(对象依赖硬编码),想换一扇窗(修改功能)可能会牵动整面墙依赖外部: 房子建在别人的地上(需要将war包部署到外部的Tomcat服务器)总结:Servlet提供了Web开发的基础能力,但开发效率极低,代码冗余且难以维护第二阶段:Spring 时代 - 使用预制件和设计图纸目标: 用更高效、更标准化的方式建造一个结构更好、更易扩展的房子你的工作状态:材料: 你不再烧砖,而是使用工厂提供的标准化预制件(Spring Bean,如 @Controller, @Service)核心创新: 你聘请了一位神奇的管家(IoC 容器)你不再亲自“砌砖”(用new实例化对象),只需告诉管家你需要什么(用@Autowired声明依赖)管家会自动把预制件(Bean)按照图纸(配置)组装好,送到你手上(依赖注入DI)过程:1.一个总大门(DispatcherServlet): 房子只有一个入口,所有访客(请求)都先到这里2.管家调度: 总大门处的接待员(DispatcherServlet)根据访客需求,呼叫房子里对应的专业房间(@Controller中的方法)来接待3.开发者只需专注于房间内的专业服务(业务逻辑),而不用关心访客是怎么进来的核心特点:解耦: 预制件之间是松耦合的,易于更换和测试专业化: AOP(面向切面编程)可以像“装修队”一样,非侵入式地为所有房间统一安装中央空调(日志、安全、事务)效率提升: 避免了大量重复劳动,结构清晰配置复杂: 绘制详细的“组装图纸”(配置 Spring)本身成了一项复杂的工作总结:Spring框架通过IoC/DI和AOP等理念,解决了代码耦合和重复劳动问题,但引入了显著的配置复杂度Spring Boot 1.0.0正式发布于2014年4月1日,标志着该框架的首次稳定版本发布。SpringBoot基于SpringFramework 4进行设计,显著减少了开发者的配置工作量,彻底消除了Spring的配置地狱约定大于配置:约定了默认配置Start机制:是一种依赖管理机制,每个Starter包含特定功能所需的依赖库和自动配置类,开发者只需引入对应Starter即可快速启用功能模块嵌入式容器:内置了Tomcat等嵌入式容器,无需部署war文件到外部容器,直接运行即可启动应用3.Spring Web MVC3.1 概述官方描述:Spring Web MVC是基于Servlet API构建的原始Web框架,并从一开始就在 Spring框架中。正式名称“Spring Web MVC”, 来自其源模块的名称(spring-webmvc),但它通常被称为“Spring MVC”MVC的起源与发展:MVC(Model-View-Controller)模式最初由挪威计算机科学家Trygve Reenskaug于1978年在施乐帕克研究中心(Xerox PARC)提出,目的是为Smalltalk编程语言设计用户界面。其核心思想是将应用程序的逻辑分为三个独立组件:Model:处理数据逻辑和业务规则View:负责数据展示和用户界面Controller:接收用户输入并协调Model与View的交互Spring MVC与MVC的关系:Spring MVC是MVC模式在Spring框架中的具体化,同时扩展了传统MVC的功能以适应现代Web开发需求3.2 必需工具Postman:主要用于 API 的开发和测试。它提供了一个用户友好的界面,支持发送HTTP请求、管理请求历史、自动化测试以及团队协作下载地址:Download PostmanFiddler:是一个网络调试代理工具,主要用于监控和分析HTTP/HTTPS流量。它可以捕获设备与服务器之间的所有请求和响应,支持修改请求、重放请求以及性能分析下载地址:Fiddler - Download3.3 RequestMapping作用:是Spring MVC中最核心、最基础的注解之一,用于将HTTP请求映射到具体的方法上注解级别:类+方法作为类注解:可以为整个类提供一个统一的url前缀(可有可无)作为方法注解:指定该方法负责处理哪个url的请求(强制要求)@RequestMapping("/HelloController")//@RestController声明该类是一个Spring MVC控制器@RestControllerpublic class HelloController {    //0.不接收参数    //作为 方法注解 时,@RequestMapping(value = "/hello",method = RequestMethod.GET)等同于@GetMapping(value = "/hello")    //设置返回的响应是json格式,produces = "application/json"    @RequestMapping(value = "/hello",method = RequestMethod.GET,produces = "application/json")    public String hello() {        return "{\"Hello\" : World}";    }    //1.一个参数    @RequestMapping("/receiveAge1")    //不传参或者传递的参数名不匹配时默认为null    public String receiveAge1(Integer age) {        return "接收到参数 age:" + age;    }    @RequestMapping("/receiveAge2")    //不传参或者传递的参数名不匹配时尝试设置为null,但int无法被设置为null,所以抛出IllegalStateException    public String receiveAge2(int age) {        return "接收到参数 age:" + age;    }    //2.接收数组    @RequestMapping("/receiveArray")    public String receiveArray(String[] array) {        return "接收到参数 array:" + Arrays.toString(array);    }    //3.接收对象,需要保证传递的参数名称和数量与Java对象保持一致    @RequestMapping("/receivePerson")    public String receivePerson(Person person) {        return "接收到参数 person:" + person;    }}3.4 RequestBody作用:将HTTP请求体中的json数据绑定到Java对象(方法注解)注解级别:方法    @RequestMapping("/receivePerson")    //@RequestBody接收JSON格式的数据    public String receivePerson(@RequestBody Person person) {        return "接收到参数 person:" + person;    }3.5 RequestParam作用:是Spring MVC框架中从HTTP请求中提取参数/查询字符串的注解,主要用于将请求参数绑定到控制器方法的参数上注解级别:方法    @RequestMapping("/receiveRename")    //@RequestParam将url中key=name的查询字符串绑定到控制器的userName参数上    //required = false设置该参数为非必传(默认为true,必传)    public String receiveRename(@RequestParam(value = "name",required = false) String userName) {        return "接收到参数name:" + userName;    }注意:需要接收多个同名参数时(如param=value1¶m=value2),直接绑定到List类型需通过该注解明确声明    @RequestMapping("/receiveList1")    public String receiveList1(ArrayList<String> list) {        //返回的list为空        return "接收到参数 list:" + list;(1)在Spring MVC中,参数绑定机制对集合类型和数组类型的处理存在差异(2)使用ArrayList< String >作为方法参数时,必须显式添加@RequestParam注解,原因如下:默认绑定规则:Spring默认将单个请求参数的值绑定到简单类型(如 String、int)或单个对象。对于集合类型,框架无法自动推断是否需要将多个同名参数合并为集合需要明确指示:@RequestParam注解会告知Spring将同名请求参数的值收集到一个集合中(3)数组(如 String[])无需 @RequestParam 注解即可正确接收,原因如下:内置支持:Spring对数组类型有原生支持,能自动将多个同名请求参数值绑定到数组。这是框架的默认行为,无需额外配置    @RequestMapping("/receiveList2")    public String receiveList2(@RequestParam(required = false) ArrayList<String> list) {        //正确返回        return "接收到参数 list:" + list;    }    @RequestMapping("/receiveList3")    public String receiveList3(List<String> list) {        //报错        return "接收到参数 list:" + list;    }后端报错:java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.List。receiveList3方法使用List< String >接口类型而非具体实现类。Spring虽然支持接口类型参数绑定,但需要满足特定条件:必须配合@RequestParam注解使用不能直接使用未注解的接口类型参数报错根本原因:Spring尝试实例化List接口失败(接口不可实例化)3.6 PathVariable作用:用于从URL路径中提取变量值并绑定到方法的参数上注解级别:方法    @RequestMapping("/receivePath/{article}/{blog}")    //required = false设置该参数为非必传(默认为true,必传)    public String receivePath(@PathVariable(value = "article",required = false)Integer title,@PathVariable(value = "blog",required = false)String content) {        return "接收到参数 article:" + title + " blog:" + content;    }3.7 RequestPart作用:用于处理 HTTP 请求中的 multipart/form-data 类型数据,通常用于文件上传或同时上传文件和其他表单字段的场景注解级别:方法    @RequestMapping("/receiveFile")    public String receiveFile(@RequestPart(value = "file",required = false) MultipartFile imgFile,@RequestParam(value = "userName",required = false) String name) {        //返回原始的文件名        return "用户:" + name+ ",接收到文件:" +imgFile.getOriginalFilename();    }3.8 Controller&ResponseBody&RestControllerController作用:是Spring MVC中的核心注解,用于标记一个类作为Web请求的处理器(声明一个类是一个Spring MVC控制器),负责处理HTTP请求并返回视图注解级别:类ResponseBody作用:指示方法返回值应直接写入HTTP响应体,而非通过视图解析器渲染注解级别:类+方法@RequestMapping("/ControllerResponse")@Controllerpublic class ControllerResponse {    //返回视图    @RequestMapping("/HTMLView")    public String HTMLView(){        return "/show.html";    }    //返回数据    @ResponseBody    @RequestMapping("/HTMLData")    public String HTMLData(){        return "/show.html";    }}RestController作用:是Spring MVC中的一个组合注解,它结合了@Controller和@ResponseBody的功能,标记的类所有方法返回值默认直接作为 HTTP 响应体(JSON/XML 等格式),无需额外视图渲染注解级别:类4.GiteeGitee地址:九转苍翎本文源码:SpringBoot_Demo1————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_89167985/article/details/148194312
  • [技术干货] 中秋满月皆十六圆?Java实证求解后的真相-转载
    前言        自古以来,中秋佳节便与圆月紧密相连,成为人们寄托思念与团圆之情的象征。在民间流传着这样一种说法:“十五的月亮十六圆”,仿佛这已成为一种铁律,深入人心。然而,这种说法是否真的站得住脚呢?在这背后,隐藏着怎样的天文奥秘?又是否可以通过科学的方法来验证这一传统观念呢?在科技飞速发展的今天,我们不妨借助编程的力量,运用Java语言来实证求解,揭开中秋满月的真相。         中秋赏月的传统由来已久,早在《周礼》中就有“中秋夜迎寒”的记载,而到了唐代,中秋赏月、玩月的风俗开始盛行。文人墨客们更是留下了许多描写中秋月夜的佳作,如苏轼的“但愿人长久,千里共婵娟”,将中秋的月与人间的思念紧密相连,赋予了中秋月深厚的文化内涵。在这样的文化背景下,“十五的月亮十六圆”这一说法也逐渐流传开来,成为人们茶余饭后的话题之一。然而,这种说法真的准确无误吗?        本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。在接下来的章节中,我们将详细介绍如何使用Java语言进行天文数据的处理和计算,以及如何通过模拟实验来验证“十五的月亮十六圆”这一说法。我们将逐步展开这一探索之旅,最终揭示中秋满月的真相。让我们一起踏上这段充满趣味和挑战的旅程,用科学的视角重新审视中秋的圆月,探索其中隐藏的奥秘。一、天文上的满月        在天文学中,月亮的圆缺变化是一个非常有趣且复杂的自然现象,这种变化主要源于月球绕地球的公转运动。月球绕地球运行一周的时间大约是29.5天,这个周期被称为一个“朔望月”。在这个周期中,月球相对于太阳的位置不断变化,从而导致我们从地球上看到的月相也随之改变。博主不是专业天文专业,这里仅分享一些简单的满月基础知识,让大家有一个概念。1、形成原理及定义        说到满月就必须提及月相,月相的形成是由于太阳光照射月球的不同部分,而我们从地球上看到的只是月球被太阳照亮的那一部分。随着月球绕地球的公转,被太阳照亮的部分逐渐增加,依次出现“娥眉月”“上弦月”“凸月”“满月”“下弦月”“残月”等不同的月相。其中满月是指月球完全被太阳照亮的那一面朝向地球,此时月球与太阳在地球的两侧,三者几乎在一条直线上。理论上,满月应该出现在农历的十五或十六,但实际的情况并非总是如此。由于月球的公转轨道是椭圆形的,且受到多种因素的影响,如地球的引力、太阳的引力等,月球的实际运行轨迹并非完全规律,因此满月出现的时间也会有所变化。2、出现时间及观测        “十五的月亮十六圆”这一说法广为流传,但实际上满月并不总是出现在农历的十六。根据天文观测数据,满月可能出现在农历的十四到十七之间的任何一天。例如,在某些年份,满月可能出现在农历十四的晚上,而在另一些年份,满月可能出现在农历十七的早晨。这种变化是由于月球的公转速度和轨道形状的不规则性所导致的。满月是观测月球的最佳时机之一,因为此时月球的整个盘面都被照亮,可以清晰地看到月球表面的山脉、陨石坑和月海等特征。在满月期间,月球的亮度会达到最大,这使得它在夜空中格外明亮。3、文化意义        在许多文化中,满月都具有重要的象征意义。在中国文化中,满月象征着团圆和完满,因此中秋节成为了家人团聚的重要节日。在西方文化中,满月也常常与神秘和浪漫联系在一起,许多文学作品和民间传说都以满月为背景。二、Java模拟月满计算        随着计算机技术的发展,我们有了更强大的工具来探索和验证这些天文现象。Java作为一种广泛使用的编程语言,具有强大的功能和灵活性,可以用来编写各种复杂的算法和程序。在本研究中,我们将利用Java语言编写程序,通过计算月球在不同时间的位置,来确定中秋满月的具体时间。我们将收集多年来的天文数据,包括月球的公转周期、轨道参数等,然后利用这些数据进行模拟计算。通过这种方式,我们可以得到一个较为准确的中秋满月时间表,从而验证“十五的月亮十六圆”这一说法的准确性。1、整体实现逻辑        使用Java求解中秋满月整体时间逻辑如下:public class MidAutumnFullMoonCalculator {    // 主计算方法    public static Date calculateFullMoonTime(int year, int month, int day) { ... }        // 核心天文算法    private static double calculateFullMoonJulianDay(double jd) { ... }        // 辅助方法    private static double normalizeAngle(double angle) { ... }    private static double calendarToJulianDay(Calendar cal) { ... }    private static Calendar julianDayToCalendar(double jd) { ... }}AI写代码java运行2、主计算方法详解        功能:这是程序的入口点,接收农历中秋的公历日期,返回精确的满月时刻,核心方法如下:/** * -计算指定农历中秋日期的月亮最圆时刻 * @param year 年份 * @param month 农历月份(八月) * @param day 农历日期(十五) * @return 月亮最圆时刻的Date对象 */public static Date calculateFullMoonTime(int year, int month, int day) {   // 创建农历中秋日期(使用中午12点作为基准时间)   Calendar midAutumnDate = Calendar.getInstance();   midAutumnDate.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));   midAutumnDate.set(year, month - 1, day, 12, 0, 0); // month-1因为Calendar月份从0开始   // 计算精确的满月时刻   return calculatePreciseFullMoonTime(midAutumnDate);}AI写代码java运行        参数说明:year:公历年份(如2024)month:公历月份(如9)day:公历日期(如17)        处理流程:创建Calendar对象,设置为北京时间将时间设为中午12点作为计算基准调用核心算法计算精确的满月时刻3、核心天文算法详解        核心算法是相关计算中最核心的内容,主要包括儒略日的计算、时间参数的计算、天文参数计算和周期项修正等内容,这里的天文计算采用近似计算,如需精度计算,请使用更精准的天文算法。3.1 儒略日计算基础/** * -计算满月时刻的儒略日 * -基于Jean Meeus的天文算法 */private static double calculateFullMoonJulianDay(double jd) {    // 计算从2000年1月6日(基准新月)开始的月相周期数    double k = Math.floor((jd - 2451550.09765) / 29.530588853);    // 满月对应的k值(新月+0.5)    k = k + 0.5;    // 计算T(儒略世纪数)    double T = k / 1236.85;    //----其它计算}AI写代码java运行        儒略日(Julian Day):天文学中常用的连续时间计数法,从公元前4713年1月1日格林尼治平午开始计算。月相周期数k:2451550.09765:2000年1月6日18:14的儒略日,作为一个基准新月时刻29.530588853:一个朔望月的平均长度(天)k:从基准时间开始经过的月相周期数k + 0.5:从新月到满月是半个周期3.2 时间参数计算// 计算T(儒略世纪数)double T = k / 1236.85;        // 计算基础儒略日double JDE = 2451550.09765 + 29.530588853 * k         + 0.0001337 * T * T         - 0.000000150 * T * T * T         + 0.00000000073 * T * T * T * T;AI写代码java运行        T(儒略世纪数):以36525天为一世纪的时间单位,用于高阶项的计算。        (儒略历书日):考虑了长期项修正的基础满月时刻。3.3 天文参数计算 // 计算太阳平近点角 double M = normalizeAngle(2.5534 + 29.10535669 * k            - 0.0000218 * T * T            - 0.00000011 * T * T * T);        // 计算月亮平近点角double Mprime = normalizeAngle(201.5643 + 385.81693528 * k                 + 0.1017438 * T * T                 + 0.00001239 * T * T * T                 - 0.000000058 * T * T * T * T);        // 计算月亮升交点平黄经double F = normalizeAngle(160.7108 + 390.67050274 * k             - 0.0016341 * T * T             - 0.00000227 * T * T * T             + 0.000000011 * T * T * T * T);        // 计算Omega(月亮轨道升交点经度)double Omega = normalizeAngle(124.7746 - 1.56375580 * k              + 0.0020691 * T * T              + 0.00000215 * T * T * T);AI写代码java运行        天文参数说明:M(太阳平近点角):太阳在轨道上的平均位置角度系数:2.5534° + 29.10535669°/周期反映地球公转轨道的椭圆性影响M'(月亮平近点角):月亮在轨道上的平均位置角度系数:201.5643° + 385.81693528°/周期反映月球公转轨道的椭圆性影响F(月亮升交点平黄经):月球轨道与黄道交点的平均位置系数:160.7108° + 390.67050274°/周期反映月球轨道平面的进动Ω(月亮轨道升交点经度):更精确的轨道交点位置系数:124.7746° - 1.56375580°/周期3.4 周期项修正计算// 转换为弧度double M_rad = Math.toRadians(M);double Mprime_rad = Math.toRadians(Mprime);double F_rad = Math.toRadians(F);double Omega_rad = Math.toRadians(Omega);// 计算周期项修正double correction = 0;// 主要修正项correction += -0.40720 * Math.sin(Mprime_rad);correction += 0.17241 * 0.016708617 * Math.sin(M_rad);correction += 0.01608 * Math.sin(2 * Mprime_rad);correction += 0.01039 * Math.sin(2 * F_rad);correction += 0.00739 * 0.016708617 * Math.sin(Mprime_rad - M_rad);correction += -0.00514 * 0.016708617 * Math.sin(Mprime_rad + M_rad);correction += 0.00208 * 0.016708617 * 0.016708617 * Math.sin(2 * M_rad);correction += -0.00111 * Math.sin(Mprime_rad - 2 * F_rad);correction += -0.00057 * Math.sin(Mprime_rad + 2 * F_rad);correction += 0.00056 * 0.016708617 * Math.sin(2 * Mprime_rad + M_rad);correction += -0.00042 * Math.sin(3 * Mprime_rad);correction += 0.00042 * 0.016708617 * Math.sin(M_rad + 2 * F_rad);correction += 0.00038 * 0.016708617 * Math.sin(M_rad - 2 * F_rad);correction += -0.00024 * 0.016708617 * Math.sin(2 * Mprime_rad - M_rad);correction += -0.00017 * Math.sin(Omega_rad);correction += -0.00007 * Math.sin(Mprime_rad + 2 * M_rad);correction += 0.00004 * Math.sin(2 * Mprime_rad - 2 * F_rad);correction += 0.00004 * Math.sin(3 * M_rad);correction += 0.00003 * Math.sin(Mprime_rad + M_rad - 2 * F_rad);correction += 0.00003 * Math.sin(2 * Mprime_rad + 2 * F_rad);correction += -0.00003 * Math.sin(Mprime_rad + M_rad + 2 * F_rad);correction += 0.00003 * Math.sin(Mprime_rad - M_rad + 2 * F_rad);correction += -0.00002 * Math.sin(Mprime_rad - M_rad - 2 * F_rad);correction += -0.00002 * Math.sin(3 * Mprime_rad + M_rad);correction += 0.00002 * Math.sin(4 * Mprime_rad);       // 应用修正double preciseJDE = JDE + correction;AI写代码java运行修正项原理:每个修正项都对应一个特定的天文效应:-0.40720 × sin(M'):月球椭圆轨道的主要修正(中心差)0.17241 × e × sin(M):地球轨道偏心率对月相的影响0.01608 × sin(2M'):月球轨道的二阶椭圆项0.01039 × sin(2F):月球轨道倾角的影响0.00739 × e × sin(M' - M):地球和月球轨道相互影响-0.00514 × e × sin(M' + M):地球和月球轨道的组合效应e = 0.016708617:地球轨道偏心率这些修正项基于布朗月球运动理论,考虑了月球轨道的各种摄动因素。4、辅助方法详解        本小节将对辅助方法进行简单介绍。4.1 角度标准化/** * -将角度标准化到0-360度范围内 */private static double normalizeAngle(double angle) {   angle = angle % 360;   if (angle < 0) {       angle += 360;   }   return angle;}AI写代码java运行        功能:将角度限制在0-360度范围内,避免数值溢出。4.2 日历与儒略日转换/** * -将Calendar转换为儒略日 */private static double calendarToJulianDay(Calendar cal) {   int year = cal.get(Calendar.YEAR);   int month = cal.get(Calendar.MONTH) + 1;   int day = cal.get(Calendar.DAY_OF_MONTH);   int hour = cal.get(Calendar.HOUR_OF_DAY);   int minute = cal.get(Calendar.MINUTE);   int second = cal.get(Calendar.SECOND);   double decimalHour = hour + minute / 60.0 + second / 3600.0;   if (month <= 2) {      year--;      month += 12;   }   int a = year / 100;   int b = 2 - a + a / 4;   return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1))                 + day + decimalHour / 24.0 + b - 1524.5;}AI写代码java运行        转换公式:标准的天文儒略日计算公式,考虑了:闰年规则格里高利历改革(1582年)时间的小数部分处理4.3 儒略日转日历/** * -将儒略日转换为Calendar */private static Calendar julianDayToCalendar(double jd) {    jd += 0.5;    double z = Math.floor(jd);    double f = jd - z;    double a;    if (z < 2299161) {       a = z;    } else {       double alpha = Math.floor((z - 1867216.25) / 36524.25);       a = z + 1 + alpha - Math.floor(alpha / 4);    }     double b = a + 1524;    double c = Math.floor((b - 122.1) / 365.25);    double d = Math.floor(365.25 * c);    double e = Math.floor((b - d) / 30.6001);    double day = b - d - Math.floor(30.6001 * e) + f;    int month = (int) (e < 14 ? e - 1 : e - 13);    int year = (int) (month > 2 ? c - 4716 : c - 4715);     double time = day - Math.floor(day);    int hour = (int) (time * 24);    int minute = (int) ((time * 24 - hour) * 60);    int second = (int) Math.round((((time * 24 - hour) * 60 - minute) * 60));    // 处理秒数进位    if (second >= 60) {       second = 0;       minute++;    }    if (minute >= 60) {       minute = 0;       hour++;    }      Calendar cal = Calendar.getInstance();    cal.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));    cal.set(year, month - 1, (int) Math.floor(day), hour, minute, second);    cal.set(Calendar.MILLISECOND, 0);    return cal;}AI写代码java运行        关键点:jd += 0.5:儒略日从中午开始,调整为从午夜开始处理格里高利历改革(1582年10月4日后跳过10天)精确的时间分量计算三、近年中秋满月计算及对比        本节将结合实例对每年的中秋月满时间进行计算,通过本小节就可以获取每年的满月日期和具体的时间,并且与官方提供的时间进行对比,大家通过对比就可以知晓问题的开始,是不是所有的月亮都是十六圆了。        1、近年中秋满月计算/** * -测试方法 - 计算未来几年的中秋节月亮最圆时刻 */public static void main(String[] args) {   // 已知的农历中秋日期(公历日期)   int[][] midAutumnDates = {       {2019, 9, 13},  // 2019年中秋节       {2020, 10, 1},  // 2020年中秋节       {2021, 9, 21},  // 2021年中秋节       {2022, 9, 10},  // 2022年中秋节       {2023, 9, 29},  // 2023年中秋节       {2024, 9, 17},  // 2024年中秋节       {2025, 10, 6},  // 2025年中秋节       {2026, 9, 25},  // 2026年中秋节    };     SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));    System.out.println("中秋节月亮最圆时刻计算结果:");    System.out.println("=================================");    for (int[] date : midAutumnDates) {        int year = date[0];        int month = date[1];        int day = date[2];        Date fullMoonTime = calculateFullMoonTime(year, month, day);        System.out.printf("%d年中秋节(公历%d月%d日)月亮最圆时刻: %s%n",                 year, month, day, sdf.format(fullMoonTime));    }}AI写代码java运行        接下来我们在IDE中运行意以上成就可以得到以下结果:         以上就是实现一个从2019年到2026年,跨度为7年的中秋满月计算过程。2、近年计算与公布时间对比        通过以上7年的计算,再结合官方公布的满月日期及时刻,来对比一下我们的计算方法与官方公布的时间相差是多少?年份    中秋(公历)    满月时间(本地)    是否当天    满月时间(官方公布)    误差2019    2019-9-13    09月14日 08时39分21秒    否(十六)    9月14日12时33分    3时54分2020    2020-10-1    10月02日 01时28分17秒    否(十六)    10月2日 05时5分    3时37分2021    2021-9-21    09月21日 03时58分38秒    是(十五)    9月21日 07时54分‌    3时56分2022    2022-9-10    09月10日 13时34分12秒    是(十五)    9月10日 17时59分    4时25分2023    2023-9-29    09月29日 13时49分05秒    是(十五)    9月29日 17时58分    4时8分2024    2024-9-17    09月18日 06时15分39秒    否(十六)    9月18日 10时34分    4时19分2025    2024-10-06    10月07日 07时41分10秒    否(十六)    10月7日 11时48分    4时7分        结合近七年的满月日期及时刻来看,并不是所有的中秋月圆都是十六圆,有的是当天就圆了。所以,从这个角度来定义,十五的月亮十六圆可不是准确的哦。通过这种本地近似的计算,虽然在具体的时刻上有一些误差,但是日期是与官方公布的是完全一致的,时刻的误差通过近7年的验证,相差时间在4个小时左右,所以未来可以结合更长序列的时间进行相应的修正。四、总结        以上就是本文的主要内容,本文通过Java实证求解中秋满月的时间,不仅可以验证传统的说法,还可以更深入地了解天文学中的相关知识。这不仅是一次对传统观念的挑战,也是一次对科学方法的实践。无论最终的结果如何,这一过程都将让我们对中秋满月有更深刻的认识,也将让我们感受到科学的魅力和力量。通过Java满月近似求解,并结合2019年到2025年的中秋满月日期时刻的计算,得出了重要的一个结论,十五的月亮不一定十六圆,通过严谨的程序计算得到的数据支撑。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/152717752
  • [技术干货] Java外功精要(3)——Spring配置文件和mybatis-转载
    1.配置文件1.1 概述计算机配置文件:用于存储系统、应用程序的设置信息,通常以文本或结构化数据格式(如JSON、XML、INI等)保存。其核心功能包括但不限于:参数定制:允许用户或管理员调整软件或硬件的运行参数环境适配:根据不同设备或场景加载特定配置(如开发/生产环境)持久化存储:确保重启后设置仍生效SpringBoot配置文件:SpringBoot支持多种类型的配置文件,常见的格式包括properties、yaml和yml,主要用于集中管理应用程序的各种配置参数,简化部署和开发过程中的环境切换YAML和YML本质上是相同的文件格式,只是文件扩展名的不同,两者在功能和使用上没有区别1.2 propertiesproperties配置文件是最早期的配置⽂件格式,也是创建SpringBoot项⽬默认的配置⽂件采用常见的键值对格式(key=value)支持#开头的注释#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root1.3 yml采用键值对格式(key: value),冒号后必须有空格数据序列化格式,通过缩进表示层级关系支持#开头的注释spring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 80801.4 优缺点对比properties优点:语法简单直观,采用key=value形式,适合初学者快速上手与Java生态兼容性极强缺点:缺乏层次结构,复杂配置时容易冗余。上述配置数据库连接信息时spring.datasource前缀冗余不支持数据类型定义,所有值均为字符串,需手动转换yml优点:层次化结构清晰,通过缩进表示层级,适合复杂配置场景支持数据类型(如布尔值、数字),减少手动类型转换缺点:格式错误易导致解析失败(容易忽略冒号后空格)部分旧版工具链兼容性较差,需额外依赖解析库注:SpringBoot同时支持两种格式,混合使用时若key重复,properties优先级高于yml1.5 @Value注解作用:是Spring框架提供了一个@Value注解(org.springframework.beans.factory.annotation.Value),用于将外部配置文件中的值注入到Spring管理的Bean中示例:(properties和yml的读取方式相同)package org.example.configuration.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class Config {    @Value("${spring.application.name}")    private String applicationName;    @Value("${server.port}")    private Integer port;    @Value("${spring.datasource.url}")    private String url;    @Value("${spring.datasource.username}")    private String username;    @Value("${spring.datasource.password}")    private String password;    public void print() {        System.out.println("applicationName=" + applicationName);        System.out.println("port=" + port);        System.out.println("url=" + url);        System.out.println("username=" + username);        System.out.println("password=" + password);    }}package org.example.configuration;import org.example.configuration.config.Config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class ConfigurationApplication {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(ConfigurationApplication.class, args);        Config config = context.getBean(Config.class);        config.print();    }}运行结果:applicationName=configurationport=8080url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falseusername=rootpassword=root2.mybatis2.1 概述MyBatis是一款优秀的持久层框架,支持自定义 SQL、存储过程、高级映射以及多种配置方式。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索支持存储过程:指的是数据库管理系统(DBMS)允许用户创建、存储和执行存储过程的能力。存储过程是一组预编译的SQL语句,存储在数据库中,可以被应用程序调用执行支持高级映射:指通过配置或注解实现复杂SQL查询结果与Java对象之间的灵活转换。其核心目标是简化数据库关联操作,提升开发效率支持多种配置方式:mybatis支持注解和xml两种配置方式2.2 前置操作引入依赖:Spring Web,Mybatis Framework,MySQL Driver,Lombok在application.properties/yml中添加数据库连接信息:#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#自动驼峰转换mybatis.configuration.map-underscore-to-camel-case=truespring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080mybatis:  configuration:     map-underscore-to-camel-case: true #自动驼峰转换SQL命名规范:采用下划线分隔单词(如order_detail)Java命名规范:大驼峰/小驼峰2.3 注解2.3.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogMapper {    //其他代码}@Mapper注解:允许开发者直接在接口方法上通过注解配置SQL语句,无需编写XML映射文件。适用于简单SQL场景,能显著减少配置量2.初始化数据create table blog (id int primary key auto_increment,name varchar(128),age int);insert into blog values (null,'刘备',30),(null,'关羽',28),(null,'张飞',25);3.创建对应实体类import lombok.Data;@Datapublic class PersonInfo {    private Integer id;    private String name;    private Integer age;    public PersonInfo(Integer id, String name, Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public PersonInfo() {    }}2.3.2 CRUDimport com.example.spring_mybatis.model.PersonInfo;import org.apache.ibatis.annotations.*;@Mapperpublic interface BlogMapper {    @Select("select * from blog")    List<PersonInfo> getPersonInfoAll();    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePerson(PersonInfo personInfo);    @Delete("delete from blog where id = #{id}")    Integer deletePerson(Integer id);}按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());        //查询成功,personInfoAll:[PersonInfo(id=1, name=刘备, age=30),        //PersonInfo(id=2, name=关羽, age=28),        //PersonInfo(id=3, name=张飞, age=25)]    }    @Test    void addPerson() {        Integer ret = blogMapper.addPerson(new PersonInfo(null, "赵云", 25));        log.info("添加成功,影响行数:{}",ret.toString());//添加成功,影响行数:1    }    @Test    void updatePerson() {        Integer ret = blogMapper.updatePerson(new PersonInfo(1, "刘备", 35));        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }    @Test    void deletePerson() {        Integer ret = blogMapper.deletePerson(4);        log.info("删除成功,影响行数:{}",ret.toString());//删除成功,影响行数:1    }}2.3.3 @Param作用:用于在Mapper接口方法中为形式参数指定名称。当方法有多个参数时,通过该注解明确SQL中引用的参数名,避免依赖参数顺序@Mapperpublic interface BlogMapper {    //@Param    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePersonInfo(@Param("id") Integer userId,@Param("name") String userName,@Param("age") Integer userAge);}AI写代码java运行123456@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void updatePersonInfo() {        Integer ret = blogMapper.updatePersonInfo(1, "刘玄德", 30);        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }}2.4 xml2.4.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogXMLMapper {    //其他代码}2.配置mybatis的xml文件路径mybatis:  mapper-locations: classpath:mybatis/**Mapper.xml #配置mybatis的xml文件路径  #标识位于resources/mybatis路径下任何以Mapper结尾的xml文件为mybatis的配置文件3.在resources/mybatis路径下创建以Mapper结尾的xml文件,并添加如下代码<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:用于指定该XML文件对应的Java接口或类的全限定名--><mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"></mapper>2.4.2 示例在接口中声明方法import com.example.spring_mybatis.model_blog.PersonInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface BlogXMLMapper {    List<PersonInfo> getPersonInfoAll();}在对应xml文件中实现接口方法id:是MyBatis映射文件中SQL语句的唯一标识符。需与Mapper接口中的方法名一致,保证映射正确resultType:指定SQL查询结果映射的Java对象类型,需为全限定类名<mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper">    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog    </select></mapper>按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model_blog.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogXMLMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());    }}运行结果:2.5 动态SQL动态SQL:指在程序运行时根据条件或参数动态生成的SQL语句。与静态SQL相比,动态SQL更具灵活性,适用于需要根据不同条件构建查询的场景。例如,在某些web/app进行账号注册时会出现非必填选项mybatis的注解和xml两种方式都能实现动态SQL,但xml较为方便,所以下文使用xml来实现动态SQL2.5.1 trim标签作用:用于自定义字符串截取规则。包含四个属性:prefix:最终结果添加前缀suffix:最终结果添加后缀prefixOverrides:去除首部指定内容suffixOverrides:去除尾部指定内容2.5.2 if标签作用:用于条件判断,通常在where或set语句中使用。当test表达式的值为true时,包含标签内的SQL片段    <insert id="addPersonInfo">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>2.5.3 where标签作用:替代SQL中的where关键字。当if条件成立时才会加入SQL片段,并自动去除第一个子句的and/or    <select id="getPersonInfoByNameAndAge">        select * from blog        <where>            <if test="name != null">                and name = #{name}            </if>            <if test="age != null">                and age = #{age}            </if>        </where>    </select>2.5.4 set标签作用:用于update语句。当if条件成立时才会加入SQL片段,并自动去除最后一个子句的逗号、    <update id="updatePersonInfo">        update blog        <set>            <if test="name != null">                name = #{name},            </if>            <if test="age != null">                age = #{age},            </if>        </set>        <where>            and id = #{id}        </where>    </update>2.5.5 foreach标签作用:用于集合遍历。主要属性:collection:集合参数名item:当前元素变量名open/close:包围符号separator:分隔符    @Test    void getPersonInfoById() {        ArrayList<Integer> ids = new ArrayList<>();        ids.add(1);        ids.add(2);        ids.add(3);        List<PersonInfo> personInfoById = blogXMLMapper.getPersonInfoById(ids);        System.out.println(personInfoById);    }    <select id="getPersonInfoById" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog        where id in        <foreach collection="ids" item="id" open="(" close=")" separator=",">            #{id}        </foreach>    </select>2.5.6 include标签作用:用于引用SQL片段,通过refid指定要引用的片段id。需配合sql标签使用,实现代码复用    <sql id="collection">        id,name,age    </sql>    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select        <include refid="collection">        </include>        from blog    </select>2.6 主键返回主键返回:指在数据库插入操作后,自动获取刚插入记录的主键值。在mybatis中使用注解和xml都能获取到返回的主键1.注解实现@Mapperpublic interface BlogMapper {    @Options(useGeneratedKeys = true,keyProperty = "id")    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);}@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }        @Test    void addPerson() {        PersonInfo personInfo = new PersonInfo(null, "黄忠", 60);        Integer ret = blogMapper.addPerson(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:6    }}2.通过xml实现@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }        @Test    void addPersonInfo() {        PersonInfo personInfo = new PersonInfo();        personInfo.setAge(40);        personInfo.setName("曹操");        Integer ret = blogXMLMapper.addPersonInfo(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:7    }}    <insert id="addPersonInfo" useGeneratedKeys="true" keyProperty="id">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>2.7 预编译/即时SQL预编译SQL(Prepared Statements):SQL语句在程序运行前被预先编译并存储在数据库中。执行时只需传递参数,无需重新编译SQL语句安全性高:通过参数化查询避免SQL注入攻击。参数化查询是一种将SQL语句与用户输入数据分离的数据库操作方式,查询语句中使用占位符(如?、@param等)代替直接拼接用户输入,执行时通过预编译机制将参数动态绑定到占位符位置性能优化:编译一次,多次执行,减少数据库开销即时SQL(Dynamic SQL):在程序运行时动态生成并立即编译执行,每次执行都可能涉及完整的SQL解析和编译过程灵活性高:可根据运行时条件动态拼接SQL语句潜在风险:直接拼接用户输入可能导致SQL注入性能开销:每次执行需重新编译#占位符会使用预编译机制,将参数值安全地绑定到SQL语句中,防止SQL注入攻击。MyBatis会将#替换为?,然后通过JDBC的预编译功能设置参数值$占位符直接进行字符串替换,将参数值拼接到SQL语句中,不会进行预编译或转义处理SQL注入攻击:当恶意输入" 'or 1 = '1 "时1.预编译SQL@Select("select * from blog where name= #{name}")List<UserInfo> queryByName(String name)预编译SQL会根据参数的类型判断是否需要加引号,上述name参数是String类型,需要加引号,这就是参数化查询的作用。最终SQL:select * from blog where name = " 'or 1='1 "; # 整个 'or 1='1 会作为name的值2.即时SQL@Select("select * from blog where name= '${name}'")List<UserInfo> queryByName(String name)即时SQL不会判断参数类型从而是否添加引号,所以需要手动加上单引号。最终SQL:select * from blog where name = ''or 1 = '1'; # 因为1=1是恒等式,所以该表的数据会被全部查————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/2401_89167985/article/details/152804543
  • [技术干货] JAVA中的int和Integer的区别和联系
    在 Java 中,int 和 Integer 是处理整数时最常用的两种类型,但二者在本质、用途、特性上有根本区别,核心差异可从以下维度清晰区分:一、本质区别:基本数据类型 vs 引用数据类型类型归属类别存储方式核心特点int基本数据类型直接存储数值(栈内存)不包含额外方法,仅存原始值Integer引用数据类型(类)存储对象引用(堆内存存对象,栈内存存地址)是 int 的包装类,包含工具方法简单理解:int 是 “裸数值”,Integer 是 “装着数值的盒子”—— 盒子里除了数值,还附带了操作数值的工具。二、核心差异对比1. 默认值不同int 作为基本类型,默认值为 0(如类中定义 private int num;,未赋值时 num 自动为 0);Integer 作为引用类型,默认值为 null(如 private Integer num;,未赋值时 num 为 null)。 ⚠️ 注意:若直接调用 Integer 变量的方法(如 num.toString())且未赋值,会触发 NullPointerException,而 int 不会。2. 适用场景不同int:适用于简单的数值计算、存储基本整数(如循环变量、年龄、分数等),性能更优(无需创建对象,节省内存);Integer:适用于需要对象特性的场景,例如:集合框架(List<Integer>、Map<String, Integer> 等,集合仅支持引用类型);泛型(<T> 类型参数必须是引用类型,不能是 int);需使用工具方法时(如数值转字符串 Integer.toString(num)、字符串转整数 Integer.parseInt("123"))。3. 特性差异:缓存机制Integer 有缓存池机制(JDK 1.5+),默认缓存 -128 ~ 127 范围内的整数对象,重复使用该范围的 Integer 时,不会创建新对象;而 int 无缓存,每次使用都是独立的数值。示例验证:java 运行    // Integer 缓存机制Integer a = 127; // 从缓存池获取对象Integer b = 127; // 复用同一缓存对象System.out.println(a == b); // true(引用地址相同)Integer c = 128; // 超出缓存范围,创建新对象Integer d = 128; // 新对象System.out.println(c == d); // false(引用地址不同)// int 无缓存,直接比较数值int e = 128;int f = 128;System.out.println(e == f); // true(数值相同) 4. 比较方式不同int 比较:用 == 直接比较数值(正确,因为存储的是原始值);Integer 比较:== 比较的是对象引用地址(仅在缓存范围内的整数会相等,超出范围则不等,易出错);正确比较方式:用 equals() 方法(比较数值),或先拆箱为 int 再用 ==。示例:java 运行    Integer x = 100;Integer y = new Integer(100); // 强制创建新对象(不走缓存)System.out.println(x == y); // false(引用地址不同)System.out.println(x.equals(y)); // true(数值相同)System.out.println((int)x == y); // true(拆箱后比较数值) 三、自动拆箱与装箱(JDK 1.5+)为简化 int 和 Integer 的转换,Java 提供自动拆箱(Unboxing) 和 自动装箱(Boxing):自动装箱:int 自动转为 Integer(如 Integer num = 10;,等价于 Integer num = Integer.valueOf(10););自动拆箱:Integer 自动转为 int(如 int val = num;,等价于 int val = num.intValue();)。注意:频繁拆装箱可能影响性能(如循环中大量转换),需避免不必要的操作。四、总结:如何选择?若仅需存储 / 计算基本整数,优先用 int(性能好、无空指针风险);若需集合、泛型、工具方法,或可能存储 null(如数据库字段允许为空),用 Integer;Integer 比较时务必用 equals(),避免用 == 踩缓存范围的坑。
  • [技术干货] java中使用正则表达式
    在Java中,正则表达式(Regular Expression)通过 java.util.regex 包实现,主要用于字符串匹配、分割、查找和替换。以下是详细的使用指南和示例:1. 核心类介绍Pattern:编译正则表达式,生成匹配模式。Matcher:执行匹配操作,提供查找、替换等方法。String 类方法:如 matches()、replaceAll() 等内置正则支持。2. 基本用法(1) 字符串匹配import java.util.regex.Pattern; import java.util.regex.Matcher; public class RegexExample { public static void main(String[] args) { String input = "Java is fun"; String regex = "Java"; // 方法1:String.matches()(完全匹配) boolean isMatch = input.matches(regex); // false(需完全匹配"Java is fun") System.out.println(isMatch); // 方法2:Pattern + Matcher(部分匹配) Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); boolean found = matcher.find(); // true(找到"Java") System.out.println(found); } } (2) 提取匹配内容String text = "Email: user@example.com, Backup: backup@test.org"; Pattern emailPattern = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); Matcher emailMatcher = emailPattern.matcher(text); while (emailMatcher.find()) { System.out.println("Found email: " + emailMatcher.group()); } // 输出: // Found email: user@example.com // Found email: backup@test.org (3) 字符串分割String csv = "apple,banana,orange"; String[] fruits = csv.split(","); // 按逗号分割 for (String fruit : fruits) { System.out.println(fruit); } (4) 字符串替换String phone = "123-456-7890"; String formatted = phone.replaceAll("(\\d{3})-(\\d{3})-(\\d{4})", "($1) $2-$3"); System.out.println(formatted); // 输出:(123) 456-7890 3. 常用正则语法符号含义示例.任意字符(除换行符)a.c → “abc”, “a1c”\\d数字\\d{3} → “123”\\w字母、数字、下划线\\w+ → “Java_123”+1次或多次go+l → “gol”, “goool”*0次或多次ab*c → “ac”, “abbc”?0次或1次colou?r → “color”{n,m}n到m次\\d{2,4} → “12”, “123”[abc]a、b或c[a-z]+ → “hello”^字符串开头^Hello → “Hello…”$字符串结尾world$ → “…world”``或\\s空白字符(空格、制表符等)\\s+ → " \t\n"\\b单词边界\\bcat\\b → “cat”4. 高级用法(1) 分组捕获String date = "2023-10-05"; Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})"); Matcher dateMatcher = datePattern.matcher(date); if (dateMatcher.matches()) { String year = dateMatcher.group(1); // "2023" String month = dateMatcher.group(2); // "10" String day = dateMatcher.group(3); // "05" System.out.println(year + "-" + month + "-" + day); } (2) 非贪婪匹配String html = "<div>Hello</div><div>World</div>"; Pattern greedyPattern = Pattern.compile("<div>.*</div>"); // 贪婪匹配 Pattern lazyPattern = Pattern.compile("<div>.*?</div>"); // 非贪婪匹配 Matcher greedyMatcher = greedyPattern.matcher(html); greedyMatcher.find(); // 匹配整个"<div>Hello</div><div>World</div>" Matcher lazyMatcher = lazyPattern.matcher(html); lazyMatcher.find(); // 仅匹配第一个"<div>Hello</div>" (3) 预定义字符类String text = "Price: $19.99, Discount: 10%"; Pattern numberPattern = Pattern.compile("\\$\\d+\\.\\d{2}"); // 匹配价格 Matcher numberMatcher = numberPattern.matcher(text); numberMatcher.find(); // 匹配"$19.99" 5. 性能优化预编译 Pattern:重复使用时,避免每次创建 Pattern 对象。private static final Pattern EMAIL_PATTERN = Pattern.compile("\\b[\\w.-]+@[\\w.-]+\\b"); public boolean isValidEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); } 避免过度回溯:复杂正则可能导致性能问题(如 (a+)+ 匹配 "aaaaaaaaaaaaaX")。6. 常见正则示例需求正则表达式验证邮箱^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$提取URL参数[?&](\\w+)=([^&#]+)匹配手机号(中国)^1[3-9]\\d{9}$去除多余空格\\s+ → 替换为 " "验证密码强度(大小写+数字)^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$7. 工具推荐在线测试工具:Regex101、RegExrIDE插件:IntelliJ IDEA的正则表达式测试器(Ctrl+Alt+F7)。总结简单匹配:直接使用 String.matches() 或 String.split()。复杂操作:使用 Pattern + Matcher 组合。性能关键场景:预编译 Pattern 并复用。调试技巧:通过 Matcher.group() 和在线工具逐步验证正则逻辑。通过合理使用正则表达式,可以高效处理文本验证、解析和转换任务。
  • [技术干货] Java代码中使用接口的好处
    在Java中,接口(Interface)是一种核心的抽象机制,它通过定义契约(方法签名和常量)来规范类的行为。接口的使用带来了多重好处,尤其在大型项目、团队协作和系统扩展中具有关键作用。以下是Java中使用接口的主要优势及具体场景说明:1. 解耦与依赖倒置(DIP)核心思想:接口将**调用方(客户端)与实现方(具体类)**解耦,使两者仅依赖抽象而非具体实现。示例:// 定义接口 interface PaymentService { void pay(double amount); } // 具体实现1 class CreditCardPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via Credit Card"); } } // 具体实现2 class PayPalPayment implements PaymentService { public void pay(double amount) { System.out.println("Paid " + amount + " via PayPal"); } } // 客户端代码(依赖接口而非具体类) public class PaymentProcessor { private PaymentService paymentService; public PaymentProcessor(PaymentService service) { this.paymentService = service; } public void processPayment(double amount) { paymentService.pay(amount); // 调用接口方法 } } 优势:客户端只需知道接口方法,无需关心具体支付方式(如信用卡、PayPal)的实现细节。切换支付方式时,只需修改PaymentProcessor的构造函数参数,无需改动内部逻辑。2. 多态与灵活扩展核心思想:一个类可实现多个接口,或同一接口可被多个类实现,实现运行时动态绑定。示例:interface Loggable { void log(String message); } class FileLogger implements Loggable { public void log(String message) { System.out.println("Logging to file: " + message); } } class DatabaseLogger implements Loggable { public void log(String message) { System.out.println("Logging to database: " + message); } } // 根据配置动态选择日志方式 public class App { public static void main(String[] args) { Loggable logger; if (args[0].equals("file")) { logger = new FileLogger(); } else { logger = new DatabaseLogger(); } logger.log("System started"); // 多态调用 } } 优势:新增日志方式(如网络日志)时,只需新增一个实现类,无需修改现有代码。符合开闭原则(OCP):对扩展开放,对修改关闭。3. 定义契约与强制规范核心思想:接口明确规定了类必须实现的方法,确保对象行为的一致性。示例:interface Comparable<T> { int compareTo(T other); } class Person implements Comparable<Person> { private String name; private int age; public int compareTo(Person other) { return this.age - other.age; // 强制实现比较逻辑 } } 优势:任何实现Comparable的类必须提供compareTo方法,否则编译失败。框架(如Java集合类)可依赖接口统一处理对象(如排序、查找)。4. 模拟多继承(Java单继承的补充)核心思想:Java类只能单继承,但可通过实现多个接口间接实现多继承的效果。示例:interface Flyable { void fly(); } interface Swimmable { void swim(); } class Duck implements Flyable, Swimmable { public void fly() { System.out.println("Duck is flying"); } public void swim() { System.out.println("Duck is swimming"); } } 优势:避免类继承导致的钻石问题(Diamond Problem)。灵活组合多个行为(如飞行+游泳)。5. 接口与默认方法(Java 8+)核心思想:接口可通过default关键字提供默认实现,减少重复代码。示例:interface Listener { void onEvent(); // 抽象方法 default void logEvent() { // 默认方法 System.out.println("Event occurred"); } } class ButtonListener implements Listener { public void onEvent() { System.out.println("Button clicked"); } // 无需实现logEvent(),可直接使用默认实现 } 优势:向旧接口添加方法时,无需强制所有实现类修改代码。共享通用逻辑(如日志、监控)。6. 接口与函数式编程(Java 8+)核心思想:接口(如Runnable、Function)是函数式编程的基础,支持Lambda表达式。示例:// 传统匿名类 Runnable task1 = new Runnable() { public void run() { System.out.println("Task 1"); } }; // 使用Lambda(接口简化) Runnable task2 = () -> System.out.println("Task 2"); 优势:代码更简洁,适合事件处理、并发编程等场景。结合Stream API实现声明式数据处理。7. 接口与测试驱动开发(TDD)核心思想:接口便于编写测试替身(Mock/Stub),隔离依赖。示例:// 真实依赖 interface UserRepository { User findById(int id); } // 测试时使用Mock class MockUserRepository implements UserRepository { public User findById(int id) { return new User(1, "Test User"); // 返回固定数据 } } // 测试类 public class UserServiceTest { @Test public void testGetUser() { UserRepository mockRepo = new MockUserRepository(); UserService service = new UserService(mockRepo); User user = service.getUser(1); assertEquals("Test User", user.getName()); } } 优势:无需启动数据库或外部服务,加快测试速度。明确测试边界,聚焦被测逻辑。总结:何时使用接口?场景接口的优势需要解耦模块客户端依赖抽象,实现可替换预期未来行为会扩展新增实现类不影响现有代码定义框架或库的规范强制用户实现特定方法(如Servlet、JPA Entity)实现多行为组合类通过接口组合飞行、游泳等能力需要函数式编程支持使用Lambda表达式简化代码(如Predicate、Consumer)编写可测试的代码通过Mock隔离依赖,提高测试覆盖率最佳实践:优先使用接口定义行为,而非抽象类(除非需要共享状态或复杂初始化逻辑)。接口命名应体现行为(如Runnable、Serializable),而非具体类(避免UserInterface)。结合设计模式(如策略模式、工厂模式)充分发挥接口的灵活性。
  • [技术干货] Java中依赖管理工具对比与性能优化(Maven vs Gradle)【转载】
    一、Java依赖管理的核心挑战:从“版本地狱”到“依赖爆炸”1.1 依赖管理的三大痛点版本冲突:项目中多个依赖库引用同一组件的不同版本(如Log4j 1.2.17 vs Log4j 1.2.18)。后果:运行时异常(如NoSuchMethodError)、日志框架绑定冲突。依赖膨胀:项目依赖树层级过深,导致构建时间延长、JAR包臃肿。案例:某电商系统依赖树包含12,000+ JAR包,构建耗时45分钟。隐性依赖传递:依赖库的间接依赖未被显式声明,导致环境一致性问题。1.2 Maven vs Gradle:谁更适合依赖管理维度MavenGradle依赖声明XML配置(pom.xml)DSL配置(build.gradle)版本冲突解决路径优先(最近依赖优先)灵活策略(resolutionStrategy)构建速度依赖缓存慢支持增量构建、并行任务学习成本低(XML语法)高(Groovy/Kotlin DSL)企业适配传统项目主流微服务/云原生项目主流二、Java依赖管理的5大黄金法则2.1 法则一:统一依赖版本(BOM管理)核心思想:通过Bill of Materials(BOM)统一依赖版本,避免版本碎片化。Maven实践:123456789101112<!-- 定义BOM文件 --><dependencyManagement>    <dependencies>        <dependency>            <groupId>com.example</groupId>            <artifactId>common-bom</artifactId>            <version>1.0.0</version>            <scope>import</scope>            <type>pom</type>        </dependency>    </dependencies></dependencyManagement>Gradle实践:12345// 使用平台依赖(Platform)dependencies {    implementation platform('com.example:common-bom:1.0.0')    implementation 'org.springframework.boot:spring-boot-starter-web'}2.2 法则二:依赖排除与强制版本Maven排除依赖:1234567891011<dependency>    <groupId>com.example</groupId>    <artifactId>example-dependency</artifactId>    <version>1.0.0</version>    <exclusions>        <exclusion>            <groupId>org.conflicting</groupId>            <artifactId>conflicting-library</artifactId>        </exclusion>    </exclusions></dependency>Gradle强制版本:12345configurations.all {    resolutionStrategy {        force 'org.springframework:spring-core:5.3.20'    }}2.3 法则三:依赖声明顺序的艺术推荐顺序:本模块子模块依赖(如example-dao、example-service)通用基础组件(Spring、Jackson、Log4j)公司内部框架(RPC、Redis客户端)特定业务依赖测试依赖(JUnit、Mockito)示例:123456789101112131415161718192021<dependencies>    <!-- 子模块依赖 -->    <dependency>        <groupId>com.example</groupId>        <artifactId>example-dao</artifactId>        <version>1.0.0</version>    </dependency>    <!-- 通用组件 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-core</artifactId>        <version>5.3.20</version>    </dependency>    <!-- 测试依赖 -->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.13.2</version>        <scope>test</scope>    </dependency></dependencies>2.4 法则四:依赖树分析与可视化Maven命令:1mvn dependency:tree > dependency-tree.txtGradle命令:1gradle dependencies --configuration runtimeClasspath工具推荐:Maven Dependency Plugin:分析冗余依赖。Gradle Dependency Report:生成可视化依赖图。2.5 法则五:依赖注入与模块化设计Spring依赖注入示例:123456789@Componentpublic class MyService {    private final MyDependency dependency;     @Autowired    public MyService(MyDependency dependency) {        this.dependency = dependency;    }}模块化设计:将公共依赖定义在父POM中,减少重复声明。使用<optional>标记可选依赖,避免传递性污染。三、3个实战案例:从崩溃到高效3.1 案例一:某金融系统依赖冲突修复问题:项目依赖Spring Boot 2.5.5和Hadoop 3.3.0,导致Guava版本冲突(Spring Boot依赖Guava 26.0,Hadoop依赖Guava 31.0)。解决方案:123456<!-- 强制使用Guava 31.0 --><dependency>    <groupId>com.google.guava</groupId>    <artifactId>guava</artifactId>    <version>31.1-jre</version></dependency>效果:构建时间从30分钟降至6秒,运行时异常消失。3.2 案例二:某电商系统的依赖优化问题:依赖树包含12,000+ JAR包,构建耗时45分钟。解决方案:使用mvn dependency:analyze分析冗余依赖。排除无用依赖(如jackson-databind的多余模块)。启用Gradle的--parallel和--build-cache。效果:依赖数量减少60%,构建时间降至12分钟。3.3 案例三:微服务项目的BOM统一管理问题:100+微服务项目依赖版本不一致(如Spring Boot 2.4.0 vs 2.5.5)。解决方案:创建公司级BOM文件company-bom:1234567891011<dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-parent</artifactId>            <version>2.5.5</version>            <scope>import</scope>            <type>pom</type>        </dependency>    </dependencies></dependencyManagement>所有微服务继承company-bom。效果:版本一致性提升100%,升级成本降低90%。四、10个避坑指南:Java依赖管理的“致命陷阱”版本冲突陷阱:使用mvn dependency:tree或gradle dependencies定期检查依赖树。隐性依赖传递陷阱:标记非必需依赖为<optional>true</optional>。依赖膨胀陷阱:使用mvn dependency:purge-local-repository清理本地仓库冗余文件。测试依赖污染生产环境:确保测试依赖(<scope>test</scope>)不被意外打包到生产JAR中。Maven默认仓库陷阱:自定义仓库优先级,避免拉取恶意依赖:1234567<repositories>    <repository>        <id>company-nexus</id>        <url>https://nexus.company.com/repository/maven-public/</url>        <releases><enabled>true</enabled></releases>    </repository></repositories>Gradle插件冲突陷阱:使用gradle properties显式指定插件版本。多模块项目依赖顺序陷阱:在父POM中定义子模块依赖顺序,避免构建失败。依赖范围混淆陷阱:区分compileOnly、runtimeOnly、provided等依赖范围。依赖版本“最新”陷阱:避免使用LATEST或RELEASE版本,固定版本号以确保稳定性。CI/CD环境依赖一致性陷阱:在CI/CD流水线中强制拉取依赖,避免本地缓存差异。五、未来趋势:AI驱动的依赖管理革命5.1 AI辅助依赖分析GitHub Copilot生成依赖配置:12345// Copilot生成的Gradle依赖声明dependencies {    implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0'    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'}5.2 云原生依赖管理Nexus Repository Manager 3.0:支持实时依赖分析、安全漏洞扫描。Azure Artifactory集成:自动同步Maven Central依赖,减少网络延迟。5.3 量子计算与依赖优化量子算法解决依赖冲突:理论上可通过量子叠加态同时计算多个依赖版本的兼容性。六、你的问题答案在这里!问题1:Java依赖管理真的能彻底解决版本冲突吗?答案:通过BOM统一管理、强制版本、依赖排除等策略,可大幅减少冲突,但无法完全避免。问题2:Maven和Gradle哪个更适合大型项目?答案:Gradle更适合复杂项目(支持增量构建、DSL灵活),Maven更适合传统项目(学习成本低)。问题3:如何快速定位依赖冲突?答案:使用mvn dependency:tree或gradle dependencies,结合exclude排除冲突依赖。七、结语:依赖管理的“黄金法则”“没有银弹,只有适合的策略!”Maven:适合传统项目,稳定性强。Gradle:适合复杂项目,灵活性高。未来方向:AI+云原生驱动的“全自动依赖管理”。
  • [技术干货] Bun替代Nodejs,JavaScrpit运行新环境-Bun,更快、更现代的开发体验
    odejs我想很多人在使用,已经得到广泛运用。但今天介绍一款比node.js高阶的一个新组件Bun,它在HTTP服务器性能、文件系统操作、启动时间、包安装时间性能上高于node.js。什么是bun,Bun的设计理念是开箱即用,减少配置和依赖,让开发者可以更专注于编写代码。Bun是一个全新的JavaScript运行时和工具链,它的核心目标是替代Node.js,提供更快的性能、更简洁的API和更好的开发体验。Bun使用JavaScriptCore引擎(也是Safari浏览器使用的引擎),V8引擎是Node.js使用的引擎,这是其性能优势的主要来源之一。Bun不仅是一个运行时,它还集成了包管理器、打包工具、测试运行器等功能,目标成为一站式的JavaScript开发平台。我这里重点对这两位前端的主角在性能、内置功能、环境、兼容性、nodejs项目迁移、bun的适用场景进行对比总结。性能优势启动速度更快Bun的底层做了大量的优化,启动速度比Node.js快10-20倍。这主要是因为Bun使用了JavaScriptCore引擎,特别是在微服务和serverless环境中,由于快速启动尤其重要,bun就可以明显缩短冷启动的时间了。// 启动时间对比// Node.js: ~50ms vs Bun: ~5msAI运行代码javascript包管理器更快bun有自己内置的包管理器,它和npm、yarn比较,通过bun install速度要快5-10倍。因为它使用了Zig来编写,它依赖解析算法很高效、而且还使用了高效的缓存策略,可直接与文件系统交互,并行下载安装依赖。# npm安装: ~15秒 vs bun安装: ~2秒AI运行代码bash运行时性能更好bun对比常见的操作也比node.js要快。比如文件系统的操作快2-5倍,http请求速度快2倍,json解析快2倍,流处理场景约快4倍。内置功能原生TypeScript支持它内置了TypeScript支持,无需编译可直接运行ts文件,极大的简化了开发流程。// Node.js需要: tsc user.ts && node user.js// Bun只需: bun user.tsAI运行代码typescript内置测试框架内置了测试框架,语法和Jest相似,但速度应该要快3-5倍(消除了安装和配置单独测试库的需要)。// 测试示例import { expect, test } from "bun:test";test("加法运算", () => {  expect(2 + 2).toBe(4);});AI运行代码javascript内置打包工具打包包含了一个高性能的javascript打包器,则可以替代掉webpack、Rollup或esbuild等工具,并支持代码分割、压缩等。// 打包示例await Bun.build({  entrypoints: ['./src/index.js'],  outdir: './dist',  minify: true});AI运行代码javascript文件操作更简单文件操作相关的API更为友好、简洁和直观,极大简化了常见的文件操作任务。// Bun文件操作const text = await Bun.file("data.txt").text();const config = await Bun.file("config.json").json();await Bun.write("output.txt", "Hello Bun!");AI运行代码javascript我们对比Node.js的方式:// Node.js文件操作const text = await fs.readFile("data.txt", "utf8");const json = JSON.parse(await fs.readFile("config.json", "utf8"));await fs.writeFile("output.txt", "Hello Node!");AI运行代码javascriptHTTP服务器高性能的HTTP服务器API,它比Node.js的http模块或Express框架更简单,同时性能更好,更为标准化Web API(如URL、Response)。// Bun HTTP服务器Bun.serve({  port: 3000,  fetch(req) {    const url = new URL(req.url);    if (url.pathname === "/") {      return new Response("欢迎访问首页");    }    return new Response("404 Not Found", { status: 404 });  },});AI运行代码javascript对比Node.js + Express的实现:// Node.js + Expressconst app = express();app.get('/', (req, res) => {  res.send('欢迎访问首页');});app.listen(3000);AI运行代码javascript环境变量和配置简化了环境变量的管理,Bun自动加载项目根目录中的.env文件,无需安装dotenv等第三方库。// .env文件DATABASE_URL=postgresql://localhost:5432/mydb// 在代码中直接使用console.log(process.env.DATABASE_URL);AI运行代码javascript脚本工具Bun很适合编写高性能的脚本工具,特别是需要处理大量文件或数据的场景。// 批量处理文件示例const files = await Array.fromAsync(new Bun.Glob("**/*.md").scan());for (const filePath of files) {  const content = await Bun.file(filePath).text();  await Bun.write(filePath, content.replace(/TODO/g, "待办事项"));}AI运行代码javascriptAPI服务器Bun的高性能HTTP服务器和内置SQLite支持使其成为构建API服务的理想选择。// SQLite集成示例import { Database } from 'bun:sqlite';const db = new Database('data.sqlite');db.exec(`CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY, title TEXT)`);// API服务器Bun.serve({  port: 3000,  async fetch(req) {    if (req.url.endsWith('/api/todos')) {      const todos = db.prepare('SELECT * FROM todos').all();      return Response.json(todos);    }    return new Response('Not Found', { status: 404 });  }});AI运行代码javascript兼容性Bun设计为与Node.js高度兼容,可以运行大多数Node.js代码和npm包。但仍有一些区别和限制:兼容的功能兼容绝大多数场景,比如CommonJS和ES模块系统、核心模块(fs, path, http等)、npm生态系统中的大多数包、process全局对象、Buffer API、EventEmitter、大多数Node.js标准库API。不完全兼容的功能某些原生模块可能需要重新编译一些依赖Node.js内部API的库可能不工作某些流API的实现有差异某些特定于Node.js的功能(如cluster模块)可能有不同行为从Node.js迁移到Bun如果你正在考虑从Node.js迁移到Bun,以下是一些建议和注意事项:迁移步骤安装Bun:curl -fsSL https://bun.sh/install | bash测试兼容性:在不修改代码的情况下,尝试用Bun运行你的应用替换包管理器:将npm/yarn命令替换为bun命令利用Bun特性:逐步采用Bun特有的API和功能更新测试:考虑迁移到Bun的内置测试框架何时选择Bun适合Bun的场景开发环境和工具链:利用Bun的快速启动时间和集成工具API和微服务:利用Bun的高性能HTTP服务器脚本和自动化:利用Bun的快速启动时间和文件操作性能,做些零活儿效率特别高新项目:从头开始的项目可以充分利用Bun的现代API和性能,从一开始就发挥它的全部优势性能关键应用:需要最大化性能的应用,需要速度和性能的场合可能不适合Bun的场景大型企业应用:需要稳定性、成熟性较高的关键业务依赖特定Node.js功能:不完全支持的Node.js特性的应用,而Bun不完全支持需要广泛部署选项:需要跨平台,在各种环境中部署的应用依赖大量原生模块:使用许多需要重新编译的C++扩展的应用Bun如同一股清流,为JavaScript运行时领域带来了新的活力。它以现代化的设计和惊人的性能,让开发体验焕然一新。虽然它还在成长的路上,不能立刻满足所有生产需求,但它的变化和创新精神,已经让它成为技术圈的新星。如果你要开启一个新项目,尤其是追求极致性能和开发体验的,Bun绝对值得一试,让我们的编程之路更加轻松愉快。如果本文能给你提供启发和帮助,还请留下你的一健三连(点赞、转发、评论),给我一些鼓励,谢谢。————————————————原文链接:https://blog.csdn.net/m0_57874805/article/details/150647891
  • [技术干货] Java霸主未逝:不可撼动的生态与新特性的革命潜力
    Java霸主未逝:不可撼动的生态与新特性的革命潜力引言:在编程语言的巨变时代重新审视Java在技术飞速演进的时代,编程语言的世界仿佛一片汹涌的海洋,每天都有新的语言和框架涌现,声称要颠覆现有秩序。从Python在数据科学和人工智能领域的崛起,到Go语言在并发处理和高性能网络服务中的优异表现,再到Rust在系统编程和安全关键型应用中的强势进攻,似乎每一种语言都在挑战Java这个长期占据TIOBE指数前三的"老牌王者"。不少初学者和技术爱好者开始质疑:在这个充满变数的时代,Java是否已经失去了往日的光彩?它那长达二十多年的霸主地位是否正在被动摇?更重要的是,对于今天的开发者来说,投入时间学习Java还是一项值得的投资吗?事实上,尽管新语言层出不穷,Java依然在全球企业级应用开发中占据着绝对主导地位。根据2023年的最新数据,全球仍有超过90%的企业使用Java作为主要开发语言,包括金融、电信、电商等关键行业。更有趣的是,Java并没有像某些预言中那样逐渐老去,反而通过持续的创新和生态扩展,不断巩固和扩大着自己的领土。从Android移动开发到大数据处理框架,从微服务架构到云原生应用,Java的身影无处不在。甚至在新兴的人工智能和物联网领域,Java也通过不断完善的特性和性能优化,找到了自己的立足点。本文将从多个维度深入分析Java的现状和未来,客观评估其核心地位是否真的遭到其他语言的撼动,详细探讨Java生态系统中那些难以被替代的核心优势,并重点分析Project Loom的虚拟线程、Project Panama的原生镜像等新特性如何为Java注入新的活力。无论你是Java的忠实拥护者,还是对Java未来持怀疑态度的观察者,抑或是正在选择技术方向的初学者,这篇文章都将为你提供一个全面而深入的视角,帮助你在技术的浪潮中做出更明智的决策。第一章 Java的现状:数字背后的真相1.1 市场占有率与行业渗透Java自1995年问世以来,已经走过了近30个年头。在技术领域,这几乎是一个永恒的时间跨度,足以让大多数技术从诞生、繁荣走向衰落。然而Java却打破了这个规律,依然保持着惊人的活力。根据TIOBE编程社区指数的最新统计,Java在2023年依然稳居前三甲,与Python和C语言共同占据着主导地位。更令人惊讶的是,这一排名在过去的20多年间几乎没有发生过实质性变化。深入行业内部,Java的统治地位更加明显。在财富500强企业中,有超过90%的公司将Java作为主要的企业级应用开发语言。金融行业几乎完全建立在Java生态系统之上——全球最大的投资银行、商业银行和保险公司都依赖Java构建其核心交易系统、风险管理系统和客户服务平台。电信行业同样如此,从基站控制器到计费系统,Java无处不在。甚至在传统的制造业和零售业,Java也扮演着关键角色,支撑着供应链管理、库存控制和电子商务平台。Android平台的兴起进一步扩展了Java的疆域。虽然近年来Kotlin在Android开发中获得了官方支持并逐渐流行,但事实是绝大多数现有的Android应用仍然基于Java构建,而且新的Java代码仍在不断被添加到这些应用中。据统计,Google Play商店中超过70%的应用主要使用Java开发,这个数字在企业级应用中甚至更高。1.2 就业市场与开发者社区从就业市场的角度观察,Java开发者的需求持续旺盛。根据Indeed和LinkedIn等招聘平台的数据,Java开发岗位的数量长期位居所有编程语言中的前三位,且平均薪资水平高于许多其他语言的开发者。更重要的是,Java开发者的就业领域极为广泛,从初创公司到科技巨头,从互联网行业到传统制造业,都需要Java人才。Java开发者社区的规模和活跃度也是其生命力的重要指标。Stack Overflow的年度开发者调查显示,Java一直是使用最广泛的语言之一,拥有数百万活跃开发者。GitHub上的Java项目数量超过100万,且持续增长。这种规模的社区意味着任何Java开发者都能轻松找到学习资源、开源库和技术支持,极大降低了学习和开发成本。JVM(Java虚拟机)语言家族的出现进一步丰富了Java生态系统。虽然Scala、Kotlin和Groovy等语言在某些特性上超越了Java本身,但它们都运行在JVM之上,共享Java庞大的库和工具生态系统。这种现象实际上强化而非削弱了Java的地位,因为它使JVM成为了更多开发者的选择,即使他们不直接使用Java语言。第二章 Java生态系统的不可替代性2.1 JVM:工程学的奇迹Java虚拟机构成了Java生态系统的基石,也是Java最难以被替代的核心优势。JVM是一个工程学上的奇迹,它通过"一次编写,到处运行"的理念,解决了跨平台兼容性这一长期困扰软件开发者的难题。经过二十多年的持续优化,现代JVM已经成为一个极其复杂和精密的运行环境,提供了许多独特而强大的特性。即时编译技术是JVM最引人注目的特性之一。HotSpot JVM通过解释执行和即时编译的结合,实现了近乎原生代码的性能,同时又保持了跨平台优势。它采用自适应优化技术,监控代码执行情况,识别热点代码并将其编译为高度优化的机器码。这种运行时常量优化甚至比静态编译的语言更具优势,因为JVM可以根据实际运行情况做出更智能的优化决策。垃圾回收机制是JVM的另一大亮点。从最初的串行收集器到现在的G1、ZGC和Shenandoah等先进收集器,Java的垃圾回收技术已经发展到令人惊叹的水平。现代垃圾收集器可以实现毫秒级的暂停时间,即使处理数TB规模的堆内存也能保持稳定性能。这对于需要高可用性和低延迟的企业级应用至关重要。JVM的监控和诊断能力同样无与伦比。Java管理扩展提供了丰富的监控接口,VisualVM、JConsole等工具可以深入洞察应用运行状态。Java飞行记录器和JDK任务控制系统提供了生产环境下的低开销性能分析能力。这些工具和特性使得Java应用在复杂企业环境中的调试和优化变得更加可行。2.2 企业级框架与库的成熟度Java拥有世界上最成熟和完善的企业级开发生态系统。Spring框架已经成为事实上的Java企业开发标准,提供了从依赖注入到Web开发,从数据访问到安全认证的全方位解决方案。Spring Boot进一步简化了Java应用的初始化和部署过程,使开发者能够快速创建独立运行的生产级应用。Java持久化API和Hibernate框架解决了对象-关系映射这一复杂问题,大大简化了数据库操作。Jakarta EE(前身为Java EE)提供了一套标准化的企业级API,得到了所有主要应用服务器厂商的支持。这些经过时间检验的框架和库为企业级应用开发提供了可靠的基础。微服务架构的兴起进一步证明了Java生态系统的适应性。Spring Cloud、Micronaut和Quarkus等框架为构建云原生Java应用提供了完整解决方案,支持服务发现、配置管理、熔断器等微服务模式。Netflix、Twitter和Amazon等大型互联网公司都使用Java构建其微服务架构,处理每天数十亿次的请求。2.3 安全性与企业支持在企业环境中,安全性往往是首要考虑因素。Java拥有经过严格测试和安全审计的安全模型,提供了细粒度的访问控制机制。Java安全架构包括字节码验证器、安全管理器和访问控制器等组件,构成了一个多层次的安全防御体系。Java长期支持版本为企业提供了稳定性保证。Oracle和OpenJDK社区都提供长期支持版本,保证关键安全更新和错误修复能够持续多年。这种可预测性和稳定性对于需要长期运行的企业应用至关重要。大型科技公司对Java的持续投入也确保了其未来发展。Oracle、IBM、Red Hat、Amazon和Microsoft等公司都雇佣了大量Java核心开发者,参与OpenJDK项目的开发。这种多厂商支持的模式避免了单一公司控制带来的风险,为Java的未来发展提供了坚实保障。第三章 新特性的革命性潜力3.1 Project Loom与虚拟线程Java最大的传统挑战之一是并发编程的复杂性。虽然Java很早就提供了强大的线程模型和并发工具包,但基于操作系统线程的实现限制了其扩展性。每个Java线程都对应一个操作系统线程,这意味着创建数千个线程就会消耗大量系统资源,导致性能下降。Project Loom旨在彻底改变这一现状,引入轻量级的虚拟线程。虚拟线程是由JVM管理的轻量级线程,与操作系统线程解耦。一个操作系统线程可以运行数千个虚拟线程,极大地提高了并发规模和效率。虚拟线程的使用方式与传统线程几乎完全相同,大大降低了学习成本。虚拟线程的革命性在于它使阻塞式编程模型重新变得可行。开发者可以编写简单直观的阻塞代码,而无需担心性能问题。JVM会在虚拟线程遇到阻塞操作时自动将其挂起,切换到其他虚拟线程,从而高效利用硬件资源。这种模型既保持了简单性,又获得了高性能。初步基准测试显示,虚拟线程能够轻松支持百万级别的并发连接,而内存占用仅为传统线程模型的几分之一。这对于需要处理大量并发请求的Web服务器、微服务和反应式应用具有重大意义。虚拟线程有可能使Java在高并发领域重新获得领先地位,甚至超越Go和Erlang等以并发能力著称的语言。3.2 GraalVM与原生镜像Java传统上因启动时间较长和内存占用较高而受到批评,这在容器化和无服务器计算时代显得尤为突出。GraalVM和原生镜像技术正是为了解决这些问题而诞生的。GraalVM是一个高性能的JDK发行版,采用了先进的即时编译技术,能够显著提高应用程序性能。更引人注目的是它的原生镜像功能,允许将Java应用程序提前编译为本地可执行文件。这些原生镜像具有极快的启动速度(通常只需几毫秒)和较低的内存占用,同时仍然保持了Java的类型安全和内存安全特性。Spring Boot、Micronaut和Quarkus等框架已经深度集成了原生镜像支持,使开发者能够轻松构建云原生Java应用。AWS Lambda等无服务器平台也加强了对Java原生镜像的支持,使Java成为函数即服务场景中的可行选择。原生镜像技术不仅解决了Java在云原生环境中的痛点,还开辟了新的应用领域。现在Java可以用于命令行工具、嵌入式系统和边缘计算场景,这些传统上由C++和Go主导的领域。这种扩展能力进一步巩固了Java的生态系统优势。3.3 Project Panama与外部函数接口Java通过JNI调用本地代码的机制一直以复杂和性能低下而闻名。Project Panama旨在革新Java与本地代码的交互方式,引入更简单、更高效的外部函数接口。新的外部函数API提供了类型安全的方式调用本地库,避免了JNI的复杂性和开销。Memory API提供了安全高效的内存访问方法,减少了内存泄漏和错误的风险。这些改进使Java能够更好地与本地库交互,同时保持Java的安全性和可移植性。Project Panama的重要意义在于它使Java能够更有效地利用硬件特性和专用库。现在Java程序可以轻松调用CUDA库进行GPU加速,或者使用TensorFlow和PyTorch进行机器学习推理。这为Java在人工智能和高性能计算领域的发展打开了新的大门。3.4 其他语言特性改进Java的持续更新带来了许多其他重要特性。记录类简化了不可变数据载体的定义,模式匹配简化了条件逻辑,密封类提供了更精确的类层次控制。这些特性使Java代码更加简洁、表达力更强,同时保持了类型安全性。Vector API引入了对向量计算的支持,允许开发者编写可移植的高性能向量化代码。这为科学计算、机器学习和多媒体处理等领域的应用提供了重要支持。这些语言改进表明Java并没有停滞不前,而是在保持向后兼容性的同时,不断吸收现代编程语言的最佳特性。Java正在变得更加简洁、表达力更强,同时保持了其核心优势和稳定性。第四章 Java面临的挑战与应对4.1 新兴语言的竞争尽管Java依然强大,但它确实面临着来自新兴语言的激烈竞争。Python在数据科学和机器学习领域的统治地位无可争议,Go语言在云原生基础设施和命令行工具中越来越受欢迎,Rust凭借其内存安全性和高性能在系统编程领域崭露头角。每种语言都有其特定的优势领域:Python拥有丰富的数据科学库和简洁的语法,Go提供了简单的并发模型和快速的编译时间,Rust则提供了无垃圾回收的内存安全保证。这些语言在特定场景下确实比Java更具优势。然而,重要的是认识到Java与这些语言之间的关系不完全是零和游戏。许多组织采用多语言策略,在不同场景中使用最适合的语言。Java的强项在于大型复杂企业应用的开发,这方面的优势仍然难以撼动。Java生态系统通过互操作性来应对多语言趋势。GraalVM支持多种语言运行在同一个运行时环境中,Spring框架提供了与其他语言服务的集成能力。这种开放性使Java能够在一个多语言世界中保持其核心地位。4.2 云原生时代的适应云原生计算对传统Java应用提出了新的挑战。容器环境强调轻量级和快速启动,无服务器平台需要极低的内存占用和瞬时扩展能力。Java传统上较长的启动时间和较高的内存占用在这些环境中处于劣势。Java社区已经积极应对这些挑战。通过模块化系统,开发者可以创建更小的运行时镜像,只包含应用程序实际需要的模块。框架如Quarkus和Micronaut专门为云原生环境设计,提供了极快的启动速度和低内存占用。工具链的改进也帮助Java更好地适应云原生时代。Jib等工具简化了Java应用容器化的过程,云原生构建包提供了自动化的应用容器化方案。这些工具降低了Java应用向云原生迁移的门槛。4.3 开发体验的现代化开发者体验是现代编程语言竞争的重要战场。Java传统上被认为繁琐而冗长,缺乏现代语言的简洁性和表达力。Java通过持续的语言改进来解决这些问题。var局部变量类型推断减少了样板代码,文本块简化了多行字符串的处理,记录类提供了简洁的数据载体定义方式。这些改进使Java代码更加简洁,同时保持了类型安全。开发工具也在不断进步。Visual Studio Code的Java支持提供了轻量级开发体验,GitHub Cop等AI辅助编程工具提高了Java开发效率。现代IDE如IntelliJ IDEA提供了强大的代码分析和重构功能。这些改进使Java开发体验更加现代化,减少了与新兴语言在这方面的差距。第五章 未来展望与学习建议5.1 Java的未来发展轨迹基于当前的发展趋势,Java的未来呈现出几个明确的发展方向。首先,Java将继续巩固其在企业级应用开发中的地位,通过持续改进和生态扩展保持领先优势。大型企业系统的迁移成本极高,这种惯性将成为Java的天然护城河。其次,Java将加速向云原生环境演进。通过Project Loom、GraalVM原生镜像等新技术,Java将更好地适应容器化和无服务器计算环境,夺回在这些新兴领域的竞争力。第三,Java将继续扩展其应用领域。通过改进的本地代码互操作性和向量计算支持,Java将进入传统上由C++和Fortran主导的高性能计算领域。在边缘计算和物联网领域,Java也有巨大潜力。最后,Java将保持其开放性和多样性。多语言运行时的支持使Java能够与其他语言和谐共存,形成一个更加丰富和多样的生态系统。5.2 对Java爱好者们的建议对于现有的Java开发者,以下建议可能有助于应对未来的变化:深入理解Java核心概念和JVM工作原理至关重要。语言特性会变化,但核心原理的价值是持久的。深入了解内存模型、垃圾回收机制和并发模型将使你能够更好地利用Java的新特性。拥抱云原生技术和实践。学习容器化、Kubernetes和无服务器计算将使你能够将Java应用成功部署到现代云环境中。掌握Spring Boot、Quarkus或Micronaut等框架的云原生特性。探索新特性并适时采用。虚拟线程、原生镜像等新特性具有革命性潜力,及早掌握这些技术将为你的职业发展带来优势。但同时要注意评估新特性的成熟度和适用性,避免在生产环境中过早采用。拓宽技术视野,学习互补技术。Java开发者应该了解Python、Go或JavaScript等其他语言,以及数据库、消息队列和分布式系统等相关技术。全栈开发能力越来越有价值。参与Java社区和开源项目。通过参与社区活动、贡献开源项目,你不仅可以扩展知识面,还能建立专业网络,了解行业最新动态。5.3 对初学者的建议对于考虑学习Java的初学者,以下建议可能有所帮助:Java仍然是值得学习的语言。其广泛的应用领域、丰富的就业机会和稳定的生态系统使其成为可靠的职业选择。学习Java为你打开了进入大型企业开发世界的大门。从基础开始,循序渐进。先掌握Java核心概念和面向对象编程原理,再学习常用框架和工具。实践是最好的学习方式,通过实际项目巩固理论知识。不要局限于Java语言本身。了解JVM工作原理、学习设计模式、掌握软件工程最佳实践同样重要。这些知识将使你成为更好的开发者,无论使用什么语言。建立完整的技术栈。除了Java,还应该学习前端技术、数据库、DevOps工具等相关技能。全栈开发者在就业市场上更具竞争力。参与实践项目和社区。通过GitHub贡献开源项目、参与编程社区讨论、完成实际项目,你将获得宝贵经验和反馈。这些实践经验往往比理论知识更有价值。结论Java的霸主地位虽然面临挑战,但远未被撼动。其强大的生态系统、持续的语言创新和企业级应用的深厚根基构成了难以复制的竞争优势。Project Loom的虚拟线程、GraalVM的原生镜像等新特性不仅解决了Java的传统痛点,还为其开辟了新的应用领域。在可预见的未来,Java仍将是企业级应用开发的主导力量,同时在云原生、边缘计算等新兴领域不断扩大影响力。对于开发者而言,Java仍然是值得投入时间和精力学习的语言,提供了稳定的职业发展和丰富的创新机会。技术的世界永远充满变数,但Java通过持续演进和生态扩展证明了其适应性和生命力。无论是Java爱好者还是初学者,都可以对Java的未来保持乐观,同时以开放的心态拥抱变化和多语言共存的未来。参考资料Oracle. (2023). “Java Language and Virtual Machine Specifications”Microsoft. (2023). “Java on Azure Development Guide”VMware. (2023). “Spring Framework Reference Documentation”Red Hat. (2023). “Quarkus: Supersonic Subatomic Java”Oracle Labs. (2023). “Project Loom: Fibers and Continuations for the Java Virtual Machine”GraalVM Team. (2023). “GraalVM: Run Programs Faster Anywhere”JetBrains. (2023). “The State of Developer Ecosystem 2023”TIOBE Software. (2023). “TIOBE Index for Programming Languages”GitHub. (2023). “The State of the Octoverse”Stack Overflow. (2023). “Stack Overflow Developer Survey 2023”————————————————原文链接:https://blog.csdn.net/lilinhai548/article/details/150699267
  • [技术干货] 如何找出Java进程中占用CPU或内存最多的线程?
    在排查 Java 性能问题时,定位占用 CPU 或内存最多的线程是关键步骤。以下是针对两种场景的具体排查方法:一、找出占用 CPU 最高的线程步骤 1:找到 Java 进程 ID(PID)ps -ef | grep java       # 查找所有 Java 进程top -c | grep java       # 实时监控 Java 进程(按 CPU 排序)AI运行代码步骤 2:找出进程内占用 CPU 最高的线程top -Hp <PID>           # 按 H 键切换到线程模式,按 CPU 排序(默认)AI运行代码关键指标:%CPU 列显示线程的 CPU 使用率。记录占用最高的线程 ID(如 12345)。步骤 3:将线程 ID 转换为 16 进制printf "%x\n" <TID>     # 例如:printf "%x\n" 12345  →  3039AI运行代码步骤 4:使用 jstack 导出线程堆栈jstack <PID> | grep -A 30 'nid=0x3039'   # 查看线程堆栈AI运行代码输出分析:"http-nio-8080-exec-1" #10 daemon prio=5 os_prio=0 tid=0x00007f9c000b4800 nid=0x3039 runnable [0x00007f9bf9e2e000]   java.lang.Thread.State: RUNNABLE   at java.net.SocketInputStream.socketRead0(Native Method)   at java.net.SocketInputStream.read(SocketInputStream.java:150)   ...AI运行代码 关键信息:线程名称(如 http-nio-8080-exec-1)、状态(RUNNABLE)、堆栈顶方法(如 socketRead0)。二、找出占用内存最多的线程方法 1:通过线程堆栈分析(适用于内存泄漏)生成堆转储文件(Heap Dump)jmap -dump:format=b,file=heap.hprof <PID>    # 生成堆快照AI运行代码使用工具分析(如 MAT、VisualVM)MAT(Memory Analyzer Tool):java -jar mat.app/Contents/Eclipse/MemoryAnalyzer -data /tmp/mat_workspace heap.hprofAI运行代码关键步骤:打开堆文件 → Leak Suspects 报告。查看 "Threads" 选项卡,按线程分组查看内存占用。定位持有大量对象的线程(如线程池中的工作线程)。方法 2:通过线程分配统计(JDK 11+)启用线程内存分配统计java -XX:StartFlightRecording=settings=profile,filename=recording.jfr -XX:ThreadAllocationStatisticsSamplingInterval=1000 YourMainClassAI运行代码使用 JMC(Java Mission Control)分析jmc recording.jfrAI运行代码 关键步骤:打开 JFR 文件 → 线程 → 内存分配。按分配量排序,找出占用最多的线程。三、自动化脚本工具脚本 1:一键查找高 CPU 线程#!/bin/bash# 功能:找出 Java 进程中占用 CPU 最高的线程并显示堆栈PID=$1 if [ -z "$PID" ]; then    echo "用法: $0 <Java 进程 PID>"    exit 1fi # 获取占用 CPU 最高的线程 IDTID=$(top -Hp $PID -b -n 1 | awk 'NR>7 {print $1; exit}')if [ -z "$TID" ]; then    echo "未找到进程 $PID 或无活动线程"    exit 1fi # 转换为 16 进制HEX_TID=$(printf "%x\n" $TID)echo "CPU 占用最高的线程 ID: $TID (0x$HEX_TID)" # 打印线程堆栈echo "堆栈信息:"jstack $PID | grep -A 30 "nid=0x$HEX_TID"AI运行代码脚本 2:定期监控线程内存分配#!/bin/bash# 功能:定期生成堆快照并分析PID=$1INTERVAL=${2:-300}  # 默认 5 分钟 if [ -z "$PID" ]; then    echo "用法: $0 <Java 进程 PID> [间隔秒数]"    exit 1fi while true; do    TIMESTAMP=$(date +%Y%m%d_%H%M%S)    HEAP_FILE="heap_${PID}_${TIMESTAMP}.hprof"        echo "[$TIMESTAMP] 生成堆快照: $HEAP_FILE"    jmap -dump:format=b,file=$HEAP_FILE $PID        # 分析最大线程(简化版,实际需用 MAT 等工具)    echo "[$TIMESTAMP] 分析中..."    # TODO: 调用 MAT API 或其他工具分析堆文件        echo "[$TIMESTAMP] 下次采样将在 $INTERVAL 秒后..."    sleep $INTERVALdoneAI运行代码四、常见问题场景与解决方案问题场景    排查方法    解决方案线程池满载导致 CPU 飙升    1. 查看线程名是否包含 pool 或 executor2. 检查堆栈是否卡在任务执行中    1. 增加线程池大小2. 优化任务执行逻辑3. 使用有界队列避免无限提交任务GC 线程频繁触发    1. 查看 GC 线程 CPU 使用率2. 使用 jstat 观察 GC 频率    1. 增加堆内存2. 调整 GC 算法(如 G1、ZGC)3. 优化对象创建模式阻塞线程导致内存堆积    1. 分析堆转储中的大对象2. 检查线程是否长时间持有锁    1. 优化锁粒度2. 使用无锁数据结构3. 增加生产者 / 消费者队列容量五、注意事项性能影响:jstack 会触发安全点(Safepoint),可能导致应用短暂停顿。频繁生成堆转储会占用大量磁盘空间并影响性能。工具版本兼容性:使用与目标 JVM 相同版本的工具(如 JDK 8 的 jstack 分析 JDK 11 的进程可能有问题)。生产环境建议:优先使用非侵入式工具(如 JFR、异步采样)。高负载时避免同时执行多个诊断命令。通过以上方法,可快速定位问题线程并进行针对性优化,提升 Java 应用的稳定性和性能。————————————————原文链接:https://blog.csdn.net/ygq13572549874/article/details/148032263
  • [技术干货] Java融合PostgreSQL:节气与季节检索的实战应用
    前言        在当今数字化时代,信息检索技术在各个领域都发挥着至关重要的作用。从简单的网页搜索到复杂的数据库查询,检索技术的高效性和准确性直接影响着用户体验和工作效率。在众多检索领域中,对自然现象的检索,如节气与季节的检索,虽然看似简单,却蕴含着丰富的文化内涵和实用价值。         节气是中国古代农耕文明的智慧结晶,它将一年分为二十四个节气,每个节气大约15天。这些节气不仅反映了自然界的气候变化,还与农业生产、民俗文化等密切相关。例如,“立春”标志着春天的开始,是播种的季节;“冬至”则是一年中白天最短、夜晚最长的日子,有着丰富的民俗活动。季节的划分则是更宏观的时间单位,通常分为春、夏、秋、冬四季,每个季节大约三个月。季节的变化影响着人们的日常生活、农业生产以及自然生态系统的平衡。         随着信息技术的发展,人们对于节气与季节信息的需求不再局限于传统的日历和书籍。一个高效、便捷的检索系统能够帮助用户快速获取与节气和季节相关的信息,无论是了解某个节气的习俗,还是查询某个季节的气候特征,都能在短时间内得到满足。这不仅有助于传承和弘扬传统文化,还能为现代生活提供实用的参考。         本文将探讨如何利用Java与PostgreSQL的融合,实现一个高效、准确且具有文化价值的节气与季节检索系统。通过Java与PostgreSQL的融合,我们成功实现了一个节气与季节检索系统。这个系统不仅能够快速、准确地检索到用户所需的信息,还具有良好的用户体验。它为用户提供了了解节气与季节的便捷途径,有助于传承和弘扬传统文化,同时也为现代生活提供了实用的参考。 一、节气和季节知识小课堂        关于节气和季节大家多少都能说上一些内容,那么这里我们依然还是简单给大家讲讲节气和季节的知识,当然作为技术人员,许多的知识点没那么详细,主要是为了在进行下一个主题的介绍时,能有一个基本的知识共识。 1、节气是什么        节气是中国古代农耕文明的智慧结晶,是根据太阳在黄道上的位置划分的。一年分为二十四个节气,每个节气大约15天。这些节气不仅反映了自然界的气候变化,还与农业生产、民俗文化等密切相关。例如,“立春”标志着春天的开始,是播种的季节;“冬至”则是一年中白天最短、夜晚最长的日子,有着丰富的民俗活动。每个节气都有其独特的气候特征和文化内涵。例如,“惊蛰”时,春雷初响,惊醒冬眠中的昆虫;“芒种”时,麦子成熟,需要及时收割。节气的变化提醒着人们关注自然,顺应自然规律,安排农事活动和生活起居。节气的划分体现了中国古代人民对自然规律的深刻理解和尊重,是中国传统文化的重要组成部分。 2、季节是什么        季节是根据地球绕太阳公转的轨道位置和地球自转轴的倾斜角度划分的,通常分为春、夏、秋、冬四季,每个季节大约三个月。季节的变化主要体现在气候特征上,如春季温暖湿润,夏季炎热多雨,秋季凉爽干燥,冬季寒冷少雨。这些气候特征影响着自然生态系统的平衡,也决定了人们的日常生活和农业生产。例如,春季是万物复苏的季节,适合播种和植树;夏季是生长的季节,农作物快速生长,人们需要防暑降温;秋季是收获的季节,农作物成熟,人们忙着收割;冬季是休养生息的季节,人们需要保暖和储备食物。季节的划分是人们根据长期的自然观察和生活经验总结出来的,是人类适应自然环境的重要方式之一。 3、节气和季节的关系        节气和季节虽然都是时间的划分方式,但它们之间存在着密切的关系。节气是季节的细分,二十四个节气分布在四季之中,每个季节包含六个节气。例如,春季包括立春、雨水、惊蛰、春分、清明和谷雨;夏季包括立夏、小满、芒种、夏至、小暑和大暑;秋季包括立秋、处暑、白露、秋分、寒露和霜降;冬季包括立冬、小雪、大雪、冬至、小寒和大寒。节气的变化更细致地反映了季节内的气候变化和自然现象,为人们提供了更精确的时间参考。同时,节气和季节的划分都与农业生产密切相关,它们共同指导着农民的农事活动。例如,在春季的立春时节,农民开始准备春耕;在夏季的芒种时节,农民忙着收割小麦并播种水稻。节气和季节的结合,不仅体现了中国古代人民对自然规律的深刻理解,也展现了他们对农业生产的科学安排。两者相辅相成,共同构成了中国古代时间观念的重要组成部分。关于节气和季节的关系,可以看下面这张图:         这其中就表示季节的节气组成,一年有四季,而每个季节又有六个节气,如此循环反复,让我们周而复始,生生不息。这是按照我国传统的四季的划分,这里无异于进行四季的具体的划分,仅从一种角度进行划分。有了这个数据基础之后,下面我们就如何表示季节和节气的关系进行PostgreSQL数据库进行设计和实现。 二、PostgreSQL数据库设计        我们来看看2025年的全年节气信息表,可以通过搜索引擎或者大模型进行输出,通过万年历也可以的,下面分享一个来自kimi的2025年节气与时间关系: ('立春', '2025-02-04'),('雨水', '2025-02-19'),('惊蛰', '2025-03-05'),('春分', '2025-03-20'),('清明', '2025-04-04'),('谷雨', '2025-04-20'),('立夏', '2025-05-05'),('小满', '2025-05-21'),('芒种', '2025-06-05'),('夏至', '2025-06-21'),('小暑', '2025-07-07'),('大暑', '2025-07-23'),('立秋', '2025-08-07'),('处暑', '2025-08-23'),('白露', '2025-09-08'),('秋分', '2025-09-23'),('寒露', '2025-10-08'),('霜降', '2025-10-23'),('立冬', '2025-11-07'),('小雪', '2025-11-22'),('大雪', '2025-12-07'),('冬至', '2025-12-21'),('小寒', '2026-01-05'),('大寒', '2026-01-20');AI运行代码bash        仔细看以上信息是比较简单的,这里我们以PostgreSQL数据库为例,重点讲解如何在数据层,PostgreSQL数据库被用来存储节气与季节的相关数据。这些数据包括节气的名称、日期、习俗描述等。通过合理设计数据库表结构和索引,确保数据的存储和检索效率,以年度为例,只需要存储24条记录即可,数据的量不是很大,在现实中时可以接受的。 1、信息存储         存储节气信息的表结构脚本如下: /*==============================================================*//* Table: biz_solarterms                                        *//*==============================================================*/create table biz_solarterms (   pk_id                INT8                 not null,   solar_term           VARCHAR(5)           not null,   solar_date           TIMESTAMP            not null,   constraint PK_BIZ_SOLARTERMS primary key (pk_id));comment on table biz_solarterms is'节气时间信息表,用于存储节气及其对应的日期';comment on column biz_solarterms.pk_id is'主键';comment on column biz_solarterms.solar_term is'节气信息';comment on column biz_solarterms.solar_date is'节气日期';AI运行代码sql         以上是节气对应日期信息表,该表是计算节气的核心表。 2、数据示例        接下来,我们模拟将2025年的全年的节气信息与其时间信息录入到数据库中,这里提供快速的录入SQL脚本: INSERT INTO biz_solarterms (pk_id,solar_term, solar_date) VALUES(1,'立春', '2025-02-04'),(2,'雨水', '2025-02-19'),(3,'惊蛰', '2025-03-05'),(4,'春分', '2025-03-20'),(5,'清明', '2025-04-04'),(6,'谷雨', '2025-04-20'),(7,'立夏', '2025-05-05'),(8,'小满', '2025-05-21'),(9,'芒种', '2025-06-05'),(10,'夏至', '2025-06-21'),(11,'小暑', '2025-07-07'),(12,'大暑', '2025-07-23'),(13,'立秋', '2025-08-07'),(14,'处暑', '2025-08-23'),(15,'白露', '2025-09-08'),(16,'秋分', '2025-09-23'),(17,'寒露', '2025-10-08'),(18,'霜降', '2025-10-23'),(19,'立冬', '2025-11-07'),(20,'小雪', '2025-11-22'),(21,'大雪', '2025-12-07'),(22,'冬至', '2025-12-21'),(23,'小寒', '2026-01-05'),(24,'大寒', '2026-01-20');AI运行代码sql        以上为演示数据,有的节气日期不一定准确,请大家按实际情况进行修改录入。 3、SQL查询        有了这些数据之后,我们如何指定一个日期比如某天,它究竟是在哪个节气当中呢?这里就需要需要数据库查询来简化操作,这里提供一种查询思路,SQL查询语句如下: WITH ordered_terms AS (    SELECT solar_term, solar_date,           LEAD(solar_date) OVER (ORDER BY solar_date) AS next_date    FROM biz_solarterms)SELECT solar_term, solar_date, next_dateFROM ordered_terms WHERE to_date('2025-09-10','YYYY-MM-DD') >= solar_date  AND (to_date('2025-09-10','YYYY-MM-DD') < next_date OR next_date IS NULL);AI运行代码sql        这里需要使用with语句来进行查询嵌套,在参数中指定查询日期后就可以查询到当前日期到底属于哪个节气。比如以上SQL在客户端软件中执行之后,可以看到如下结果: solar_term solar_date next_date白露 2025-09-08 00:00:00 2025-09-23 00:00:00AI运行代码bash        查询结果表明,2025-09-10在白露节气当中,白露的开始日期是9月8日,到9月23日结束。有了SQL的查询结果之后,下面我们结合JAVA来讲讲结合PostgreSQL数据库如何来求解季节信息。 三、JAVA计算节气和季节        在众多编程语言和数据库管理系统中,Java和PostgreSQL的组合是一个理想的选择。Java是一种广泛使用的高级编程语言,具有跨平台性、面向对象、多线程等优点。它强大的功能和丰富的库支持,使得开发复杂的应用程序变得相对容易。PostgreSQL则是一种开源的对象关系型数据库管理系统,以其高性能、高可靠性和强大的功能而闻名。它支持复杂的查询,能够高效地处理大量数据,非常适合用于存储和检索节气与季节信息。在应用开发中,我们通常使用Java来进行应用开发,因此这里我们重点讲讲如何使用Java来计算节气和季节。 1、模型层查询接节气        模型层的定义比较简单,这里贴出数据库查询Mapper类的主要代码: package com.yelang.project.extend.earthquake.mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.yelang.project.extend.earthquake.domain.SolarTerms;/** * - 节气时间信息mapper接口 * @author 夜郎king */public interface SolarTermsMapper extends BaseMapper<SolarTerms>{static final String FIND_SOLARTERMS_BYDATE = "<script>"+ " WITH ordered_terms AS ( SELECT solar_term, solar_date,LEAD(solar_date) OVER (ORDER BY solar_date) AS next_date "+ " FROM biz_solarterms ) SELECT solar_term, solar_date, next_date FROM ordered_terms "+ " <![CDATA[ WHERE to_date(#{queryDate},'YYYY-MM-DD') >= solar_date AND (to_date(#{queryDate},'YYYY-MM-DD') < next_date OR next_date IS NULL) ]]> "// 需要增加转义+ "</script>";/*** - 根据日期查询所处节气   add by 夜郎king in 2025-09-10* @param queryDate查询日期* @return 该时间所处的节气信息*/@Select(FIND_SOLARTERMS_BYDATE)SolarTerms findSolarTermsByDate(@Param("queryDate")String queryDate);}AI运行代码java         这里需要注意的是,由于这里有时间的判断查询,因此我们引入格式化的标签,以方便>和<这种符号能正常解析使用。 2、根据节气反推季节根据前面的知识可以知晓,季节与节气其实有一个对应关系的,在前面的图中有明确的表示,那么如何使用Java来进行计算呢?比如我们已经使用前面的方法查询出指定日期属于哪个节气,那么如何通过节气推出季节呢?可以看看下面的核心代码: @Overridepublic String getSeason(String solarTerm) {switch (solarTerm) {case "立春":case "雨水":case "惊蛰":case "春分":case "清明":case "谷雨":return "春季";case "立夏":case "小满":case "芒种":case "夏至":case "小暑":case "大暑":return "夏季";case "立秋":case "处暑":case "白露":case "秋分":case "寒露":case "霜降":return "秋季";case "立冬":case "小雪":case "大雪":case "冬至":case "小寒":case "大寒":return "冬季";default:return "未知";}}AI运行代码java 3、节气及季节检索        最后使用Junit程序来测试一下以上的程序是否可靠,是否按照我们的预期进行查询和展示。首先分享查询调用方法: package com.yelang.project.date;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import com.yelang.project.extend.earthquake.domain.SolarTerms;import com.yelang.project.extend.earthquake.service.ISolarTermsService;@SpringBootTest@RunWith(SpringRunner.class)public class SolarTermSeasonDbCase {@Autowiredprivate ISolarTermsService solarTermsService;@Testpublic void testGetSolar() {SolarTerms solarTerms = solarTermsService.findSolarTermsByDate("2025-07-10");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));solarTerms = solarTermsService.findSolarTermsByDate("2025-10-08");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));solarTerms = solarTermsService.findSolarTermsByDate("2025-11-23");System.out.println(solarTerms);System.out.println(solarTermsService.getSeason(solarTerms.getSolarTerm()));}}AI运行代码java         这里模拟了三个不同的日期,以验证计算的效果是正确,在IDE中运行以上程序后,在控制台中可以看到以下输出:         至此,基于Java进行季节和节气的计算就基本完成,可以正常的进行日期的转换,同时经过万年历等验证,基本是正确的。 四、总结        以上就是本文的主要内容,通过Java与PostgreSQL的融合,成功实现了一个节气与季节检索系统。这个系统不仅能够快速、准确地检索到用户所需的信息,还具有良好的用户体验。在未来的工作中,我们计划进一步优化系统的性能,增加更多的功能,如用户自定义查询、数据可视化等。我们还将探索与其他技术的结合,如人工智能和大数据分析,以进一步提升系统的智能化水平。我们相信,随着技术的不断发展和创新,节气与季节检索系统将为更多的人带来便利和价值。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。————————————————原文链接:https://blog.csdn.net/yelangkingwuzuhu/article/details/151453058
  • [技术干货] Java内功修炼——线程安全三剑客:synchronized、volatile与wait/notify
    1.线程安全1.1 概念&示例概念:指在多线程环境下,某个代码、函数或对象能够被多个线程同时调用或访问时,仍能保持正确的行为和数据一致性。简单来说,线程安全的代码在多线程环境下运行可靠,不会因线程间的交互而产生不可预测的结果 示例: public class ThreadDemo {    public static int count = 0;    public static void main(String[] args) throws InterruptedException {        Thread thread1 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                count++;            }        });        Thread thread2 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                count++;            }        });        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println("count = " + count);//每次执行的结果都不一致    }}AI运行代码java 按照上述代码的逻辑,期望得到的结果是1000000,但实际计算的结果与期望值不一致 线程不安全:当多个线程同时访问或修改共享资源时,由于缺乏适当的同步机制,可能导致程序行为不可预测、数据损坏或错误结果的现象 1.2 线程不安全的原因1.访问修改共享变量:当多个线程同时读写同一内存区域时,可能导致数据状态不一致2.原子性:原子性指一个操作是不可分割的单元,要么完全执行,要么完全不执行。如果操作不是原子的,在并发环境下,线程可能被中断在中间状态,导致部分修改3.内存可见性:在多线程编程中,每个线程都有自己的工作内存(本地内存),用于存储共享变量的副本。由于CPU缓存、编译器优化等因素,操作可能只发生在工作内存中,而不是直接在主内存中进行,导致程序行为不符合预期4.指令重排序:是计算机处理器或编译器为了提高程序执行效率,对指令执行顺序进行优化的一种技术。在保证程序最终结果正确的前提下,允许指令的执行顺序与代码编写的顺序不一致。但可能导致多线程下的逻辑错误5.线程之间抢占式执行:这是操作系统层面的调度机制,线程的执行顺序是随机的和不可预测的。操作系统可能随时中断一个线程(抢占),切换到另一个线程执行。一般不轻易改变,当引发线程安全时优先考虑前4个原因共享变量访问修改是线程安全问题的前提,但需结合2/3/4才会引发问题;抢占式执行是线程调度的特性,无法避免 2.synchronized关键字2.1 概念synchronized(监视器锁monitor lock):用于实现线程同步,确保多线程环境下对共享资源的访问安全。通过加锁机制,防止多个线程同时访问同步块代码或对象,避免数据不一致问题 2.2 特性2.2.1 原子性确保了代码块的原子性,即被同步的代码块在执行过程中不会被其他线程中断。这意味着在一个线程执行完整个同步块之前,其他线程无法进入同一个同步块,从而保证了操作的完整性 public class ThreadDemo {    //锁对象    private static final Object locker = new Object();    private static int count = 0;    public static void main(String[] args) throws InterruptedException {        Thread thread1 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                synchronized (locker) {                    count++;                }            }        });        Thread thread2 = new Thread(()->{            for (int i = 0; i < 500000; i++) {                synchronized (locker) {                    count++;                }            }        });        thread1.start();        thread2.start();        thread1.join();        thread2.join();        System.out.println("count = " + count);//1000000    }}AI运行代码java 2.2.2 内存可见性获取锁时:线程会将工作内存中的变量副本失效,强制从主内存重新读取最新值释放锁时:线程会将工作内存中修改过的变量刷新到主内存这种机制确保了共享变量的修改对所有线程立即可见 2.2.3 互斥性确保在同一时间只有一个线程可以进入被同步的代码块或方法,这意味着当一个线程进入同步块或方法时,其他试图进入同一同步块的线程会被阻塞,直到第一个线程退出同步块 2.2.4 可重入性synchronized关键字是可重入的,这意味着如果一个线程已经持有某个对象的锁,那么它可以再次获取该对象的锁,而不会被阻塞 可重入锁通常会维护一个计数器,记录当前线程获取锁的次数。每次获取锁时,计数器加一;释放锁时,计数器减一。当计数器为零时,锁才真正被释放public class Reentry_Lock {    public static void main(String[] args) {        Object locker = new Object();        Thread thread = new Thread(()->{            synchronized (locker){                System.out.println("第一层锁");                synchronized (locker){                    System.out.println("第二层锁");                }            }        });        thread.start();    }}AI运行代码java 2.3 类型2.3.1 实例锁作用于对象实例,每个对象实例拥有自己的锁。当一个线程访问对象的synchronized实例方法或代码块时,其他线程无法访问该对象的其他synchronized方法或代码块,但可以访问非synchronized方法或代码块 public class Example {    // 实例方法锁    public synchronized void instanceMethod() {        // 同步代码    }    // 实例代码块锁    public void anotherMethod() {        synchronized (this) {            // 同步代码        }    }}AI运行代码java 2.3.2 静态锁作用于类的Class对象,所有实例共享同一把锁。当一个线程访问synchronized静态方法或代码块时,其他线程无法访问该类的其他synchronized静态方法或代码块,但可以访问非synchronized静态方法或代码块 public class Example {    // 静态方法锁    public static synchronized void staticMethod() {        // 同步代码    }    // 静态代码块锁    public static void anotherStaticMethod() {        synchronized (Example.class) {            // 同步代码        }    }}AI运行代码java 2.4 死锁概念:指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。这种情况下,系统资源被占用,但程序无法继续运行 死锁产生的必要条件: 1.互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享模式,即一次只能被一个线程使用。如果另一个线程请求该资源,那么请求线程必须等待,直到该资源被释放2.请求与保持条件(Hold and Wait):一个线程已经持有至少一个资源,并且正在等待获取其他被其他线程占用的资源3.不可剥夺条件(No Preemption):资源一旦被分配给某个线程,就不能被强制性地剥夺,只能由占有该资源的线程自行释放4.环路等待条件(Circular Wait):存在一个线程资源的循环等待链,其中每个线程都在等待下一个进程所持有的资源预防死锁:通过破坏死锁的四个必要条件之一,可以预防死锁的发生 1.破坏互斥条件:尽量使用可共享的资源2.破坏占有且等待:一次性申请所有需要的资源,避免部分持有3.破坏非抢占条件(不建议):允许系统强行剥夺某些进程已占有的资源,分配给其他进程。这种方法可能导致进程执行的不稳定性4.破坏循环等待条件:对资源进行排序,按固定顺序申请资源3.volatile3.1 概念volatile:是编程语言中的关键字,用于修饰变量,告知编译器该变量可能被意外修改。其核心作用是防止编译器优化导致的数据不一致问题(在Java中仅能修饰成员变量) 3.2 特性3.2.1 内存可见性对volatile变量的每次访问都会强制从主内存读取,每次修改都会立即写回主内存 public class demo_volatile {    //每次访问都会强制从主内存读取,每次修改都会立即写回主内存    //去除volatile关键字会导致thread1线程在访问num时不从主内存读取    public static volatile int num = 0;     public static void main(String[] args) {        //thread1线程的生命周期掌握在thread2手中        Thread thread1 = new Thread(()->{            while (num == 0){}            System.out.println("Over thread1");        });        Thread thread2 = new Thread(()->{            System.out.println("请输入一个整数");            Scanner in = new Scanner(System.in);            num = in.nextInt();        });        thread1.start();        thread2.start();    }}AI运行代码java 3.2.2 禁止指令重排序在多线程场景下,指令重排序可能会导致线程间数据同步问题。volatile变量通过插入内存屏障(Memory Barrier)来禁止重排序 读操作前插入“LoadLoad”屏障,读操作后插入“LoadStore”屏障写操作前插入“StoreStore”屏障,写操作后插入“StoreLoad”屏障public class FixedReorderingExample {    int a = 0;//普通变量    int b = 0;//普通变量    volatile boolean flag = false;//标志变量使用volatile    // 写线程方法    public void writer() {        a = 1;        b = 1;        flag = true;//volatile 写,插入写屏障:确保flag写操作在a、b写操作之后    }boolean demo = true    // 读线程方法    public void reader() {        if (flag) {//volatile 读,插入读屏障:确保println读操作不会再if读操作之前            int r1 = a;            int r2 = b;            System.out.println("r1: " + r1 + ", r2: " + r2);//总是输出 r1: 1, r2: 1        }    }}AI运行代码java 3.2.3 不保证原子性volatile不保证操作的原子性,多线程环境下仍需结合锁或原子操作 4.wait/notify概念:wait()和notify()是Java中用于线程间通信的机制,属于Object类的方法。它们必须在同步代码块(如synchronized块)中使用,否则会抛出IllegalMonitorStateException wait(): 让当前线程进入等待状态,释放锁,直到其他线程调用notify()或notifyAll()唤醒它notify(): 随机唤醒一个等待该对象锁的线程notifyAll(): 唤醒所有等待该对象锁的线程public class Demo {    public static void main(String[] args) throws InterruptedException {        Object locker = new Object();        Thread thread1 = new Thread(()->{            //thread1拿到锁            synchronized (locker) {                System.out.println("thread1线程wait之前");                try {                    //thread1释放锁,进入waiting状态,等待被唤醒                    locker.wait();                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }                System.out.println("thread1线程wait之后");            }        });        Thread thread2 = new Thread(()->{            //thread1进入waiting之后,thread2拿到锁            synchronized (locker){                System.out.println("thread2线程notify之前");                //虽然notify执行之后thread1被唤醒了,但此时仍处于thread2的synchronized中                //同一对象才能唤醒                locker.notify();                System.out.println("thread2线程notify之后");            }        });        thread1.start();        Thread.sleep(1000);        thread2.start();    }}AI运行代码java wait与sleep的区别 概念:wait是Object类的方法,用于线程间的通信,必须配合synchronized使用,调用wait的线程会释放锁sleep是Thread类的静态方法,用于暂停当前线程的执行,调用sleep的线程不会释放锁锁的释放行为差异:wait会释放当前线程持有的锁,允许其他线程获取该锁并执行同步代码块,这一特性使得wait适用于多线程协作的场景sleep不会释放任何锁,即使线程休眠,其他线程也无法获取该线程持有的锁,这可能导致死锁或性能问题唤醒机制:wait需要通过notify或notifyAll主动唤醒,否则线程会一直等待(可以设置最大等待时间),唤醒后线程需要重新获取锁才能继续执行sleep无需外部唤醒,到达指定时间后自动恢复,恢复执行的线程直接从sleep调用处继续执行————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/150501838
  • [技术干货] Java微服务架构设计模式详解
    微服务架构已成为现代企业级应用开发的主流选择,它通过将单体应用拆分为一组小型、自治的服务来提高系统的可扩展性、可维护性和弹性。本文将深入探讨Java微服务架构的核心设计模式,结合代码示例、流程图和实际应用场景,帮助开发者构建健壮的微服务系统。1. 微服务架构概述微服务架构是一种将应用程序设计为一系列松耦合、可独立部署的服务集合的架构风格。每个服务围绕特定业务功能构建,拥有自己的数据存储,并通过轻量级协议(如HTTP/REST)通信。微服务架构优势技术异构性:不同服务可使用不同技术栈独立部署:服务可单独部署而不影响整个系统弹性设计:单个服务故障不会导致整个系统崩溃可扩展性:可根据需求独立扩展特定服务微服务架构挑战分布式系统复杂性服务间通信开销数据一致性管理运维和监控复杂度2. 核心设计模式详解2.1 服务拆分模式服务拆分是微服务架构的基础,需要基于业务领域边界进行合理划分。领域驱动设计(DDD)拆分策略// 领域模型示例 - 订单领域public class Order {    private OrderId id;    private CustomerId customerId;    private List<OrderItem> items;    private Money totalAmount;    private OrderStatus status;        public void confirm() {        // 业务规则验证        if (status != OrderStatus.PENDING) {            throw new IllegalStateException("Only pending orders can be confirmed");        }        status = OrderStatus.CONFIRMED;        // 发布领域事件        DomainEventPublisher.publish(new OrderConfirmedEvent(id));    }} // 领域服务public class OrderService {    private final OrderRepository orderRepository;    private final PaymentService paymentService;        @Transactional    public Order placeOrder(OrderCommand command) {        Order order = Order.create(command);        orderRepository.save(order);        paymentService.processPayment(order.getId(), order.getTotalAmount());        return order;    }}AI运行代码服务拆分原则流程图graph TD    A[业务需求分析] --> B[领域建模]    B --> C[识别聚合根]    C --> D[定义 bounded context]    D --> E[确定服务边界]    E --> F[评估服务粒度]    F --> G[定义服务接口]    G --> H[验证拆分合理性]    H --> I[实施拆分]2.2 服务发现模式在动态微服务环境中,服务实例需要动态注册和发现。Eureka服务发现实现服务注册中心 (Eureka Server)@SpringBootApplication@EnableEurekaServerpublic class DiscoveryServerApplication {    public static void main(String[] args) {        SpringApplication.run(DiscoveryServerApplication.class, args);    }}AI运行代码服务提供者 (Eureka Client)@SpringBootApplication@EnableDiscoveryClientpublic class ProductServiceApplication {    public static void main(String[] args) {        SpringApplication.run(ProductServiceApplication.class, args);    }} // 服务注册配置eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/  instance:    preferIpAddress: trueAI运行代码服务消费者@Servicepublic class ProductClient {    private final LoadBalancerClient loadBalancer;    private final RestTemplate restTemplate;        public Product getProduct(Long productId) {        ServiceInstance instance = loadBalancer.choose("PRODUCT-SERVICE");        String url = String.format("http://%s:%s/products/%d",             instance.getHost(), instance.getPort(), productId);        return restTemplate.getForObject(url, Product.class);    }}AI运行代码服务发现流程图sequenceDiagram    participant ServiceInstance    participant EurekaServer    participant ServiceConsumer        ServiceInstance->>EurekaServer: 注册服务 (心跳)    loop 定期心跳        ServiceInstance->>EurekaServer: 发送心跳    end        ServiceConsumer->>EurekaServer: 请求服务列表    EurekaServer-->>ServiceConsumer: 返回可用服务实例    ServiceConsumer->>ServiceInstance: 直接调用服务2.3 API网关模式API网关作为微服务架构的入口点,处理请求路由、认证、限流等横切关注点。Spring Cloud Gateway实现@SpringBootApplicationpublic class ApiGatewayApplication {    public static void main(String[] args) {        SpringApplication.run(ApiGatewayApplication.class, args);    }} // 路由配置@Configurationpublic class GatewayConfig {        @Bean    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {        return builder.routes()            .route("product-service", r -> r.path("/products/**")                .filters(f -> f.filter(authenticationFilter())                    .filter(rateLimiterFilter())                    .circuitBreaker(config -> config.setName("product-service-cb")))                .uri("lb://PRODUCT-SERVICE"))            .route("order-service", r -> r.path("/orders/**")                .filters(f -> f.filter(authenticationFilter()))                .uri("lb://ORDER-SERVICE"))            .build();    }        private GatewayFilter authenticationFilter() {        return (exchange, chain) -> {            // 认证逻辑            String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");            if (authHeader == null || !authHeader.startsWith("Bearer ")) {                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);                return exchange.getResponse().setComplete();            }            return chain.filter(exchange);        };    }        private GatewayFilter rateLimiterFilter() {        return (exchange, chain) -> {            // 限流逻辑            String clientId = exchange.getRequest().getHeaders().getFirst("X-Client-Id");            if (rateLimiter.isAllowed(clientId)) {                return chain.filter(exchange);            } else {                exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);                return exchange.getResponse().setComplete();            }        };    }}AI运行代码API网关架构图graph TB    subgraph "客户端"        A[Web应用]        B[移动应用]    end        subgraph "API网关"        C[Spring Cloud Gateway]        D[认证过滤器]        E[限流过滤器]        F[路由规则]        G[熔断器]    end        subgraph "微服务"        H[产品服务]        I[订单服务]        J[用户服务]    end        A --> C    B --> C    C --> D    C --> E    C --> F    C --> G    F --> H    F --> I    F --> J2.4 配置中心模式集中管理微服务配置,实现配置的动态更新和环境隔离。Spring Cloud Config实现配置服务器@SpringBootApplication@EnableConfigServerpublic class ConfigServerApplication {    public static void main(String[] args) {        SpringApplication.run(ConfigServerApplication.class, args);    }} # 配置服务器配置spring:  cloud:    config:      server:        git:          uri: https://github.com/your-repo/config-repo          search-paths: '{application}'AI运行代码配置客户端@SpringBootApplicationpublic class ProductServiceApplication {    public static void main(String[] args) {        SpringApplication.run(ProductServiceApplication.class, args);    }} # bootstrap.ymlspring:  application:    name: product-service  cloud:    config:      uri: http://config-server:8888      fail-fast: true      retry:        initial-interval: 1000        max-interval: 2000        max-attempts: 6AI运行代码动态配置刷新@RestController@RefreshScopepublic class ProductController {        @Value("${product.discount.percentage}")    private int discountPercentage;        @GetMapping("/products/{id}/discount")    public DiscountInfo getDiscount(@PathVariable Long id) {        return new DiscountInfo(id, discountPercentage);    }}AI运行代码配置中心架构图graph LR    subgraph "配置中心"        A[Config Server]        B[Git仓库]    end        subgraph "微服务"        C[产品服务]        D[订单服务]        E[用户服务]    end        B --> A    A --> C    A --> D    A --> E        C -.->|/actuator/refresh| A    D -.->|/actuator/refresh| A    E -.->|/actuator/refresh| A2.5 熔断器模式防止级联故障,提高系统弹性。Resilience4j熔断器实现@Configurationpublic class ResilienceConfig {        @Bean    public CircuitBreaker orderServiceCircuitBreaker() {        CircuitBreakerConfig config = CircuitBreakerConfig.custom()            .failureRateThreshold(50)            .waitDurationInOpenState(Duration.ofMillis(1000))            .slidingWindowSize(10)            .build();                return CircuitBreaker.of("orderService", config);    }} @Servicepublic class OrderServiceClient {        private final CircuitBreaker circuitBreaker;    private final RestTemplate restTemplate;        public OrderServiceClient(CircuitBreaker circuitBreaker, RestTemplate restTemplate) {        this.circuitBreaker = circuitBreaker;        this.restTemplate = restTemplate;    }        public Order getOrder(Long orderId) {        return circuitBreaker.executeSupplier(() -> {            try {                return restTemplate.getForObject(                    "http://order-service/orders/" + orderId, Order.class);            } catch (Exception e) {                throw new RuntimeException("Order service unavailable", e);            }        });    }        // 降级方法    public Order getOrderFallback(Long orderId) {        return new Order(orderId, "Fallback Order", Collections.emptyList());    }}AI运行代码熔断器状态转换图stateDiagram-v2    [*] --> Closed    Closed --> Open: 失败率超过阈值    Open --> HalfOpen: 等待时间结束    HalfOpen --> Closed: 调用成功    HalfOpen --> Open: 调用失败 2.6 分布式追踪模式跟踪请求在微服务间的完整调用链。Spring Cloud Sleuth + Zipkin实现@SpringBootApplicationpublic class OrderServiceApplication {    public static void main(String[] args) {        SpringApplication.run(OrderServiceApplication.class, args);    }} # 配置spring:  sleuth:    sampler:      probability: 1.0  zipkin:    base-url: http://zipkin-server:9411/ @RestControllerpublic class OrderController {        private final ProductServiceClient productClient;    private final Tracer tracer;        @PostMapping("/orders")    public Order createOrder(@RequestBody OrderRequest request) {        Span newSpan = tracer.nextSpan().name("createOrder").start();                try (Tracer.SpanInScope ws = tracer.withSpan(newSpan.start())) {            // 调用产品服务            Product product = productClient.getProduct(request.getProductId());                        // 创建订单            Order order = new Order(request.getCustomerId(), product, request.getQuantity());                        // 记录事件            newSpan.event("orderCreated");                        return orderRepository.save(order);        } finally {            newSpan.end();        }    }}AI运行代码分布式追踪流程图sequenceDiagram    participant Client    participant APIGateway    participant OrderService    participant ProductService    participant Zipkin        Client->>APIGateway: POST /orders    APIGateway->>OrderService: POST /orders (traceId=X, spanId=A)    OrderService->>ProductService: GET /products/1 (traceId=X, spanId=B, parent=A)    ProductService-->>OrderService: Product (traceId=X, spanId=B)    OrderService-->>APIGateway: Order (traceId=X, spanId=A)    APIGateway-->>Client: Order (traceId=X, spanId=A)        OrderService->>Zipkin: 上报span A    ProductService->>Zipkin: 上报span B 2.7 事件驱动模式通过异步事件实现服务间松耦合通信。Spring Cloud Stream实现// 事件发布者@Servicepublic class OrderEventPublisher {        private final StreamBridge streamBridge;        public void publishOrderCreatedEvent(Order order) {        OrderCreatedEvent event = new OrderCreatedEvent(            order.getId(),             order.getCustomerId(),             order.getTotalAmount()        );                streamBridge.send("orderCreated-out-0", event);    }} // 事件消费者@Servicepublic class OrderEventConsumer {        private final NotificationService notificationService;        @Bean    public Consumer<OrderCreatedEvent> orderCreated() {        return event -> {            notificationService.sendOrderConfirmation(                event.getCustomerId(),                 event.getOrderId()            );        };    }} # 配置spring:  cloud:    stream:      bindings:        orderCreated-out-0:          destination: order-created        orderCreated-in-0:          destination: order-created          group: notification-serviceAI运行代码事件驱动架构图graph TB    subgraph "订单服务"        A[订单创建]        B[事件发布]    end        subgraph "消息代理"        C[Kafka/RabbitMQ]    end        subgraph "通知服务"        D[事件消费]        E[发送通知]    end        subgraph "库存服务"        F[事件消费]        G[更新库存]    end        A --> B    B --> C    C --> D    D --> E    C --> F    F --> G3. 微服务架构综合示例3.1 电商系统微服务架构系统架构图graph TB    subgraph "客户端层"        A[Web应用]        B[移动应用]    end        subgraph "API网关"        C[Spring Cloud Gateway]        D[认证服务]        E[限流服务]    end        subgraph "业务服务"        F[用户服务]        G[产品服务]        H[订单服务]        I[支付服务]        J[库存服务]        K[通知服务]    end        subgraph "基础设施"        L[Eureka]        M[Config Server]        N[Zipkin]        O[Kafka]        P[Redis]    end        subgraph "数据层"        Q[(用户DB)]        R[(产品DB)]        S[(订单DB)]        T[(支付DB)]    end        A --> C    B --> C    C --> D    C --> E    C --> F    C --> G    C --> H    C --> I    C --> J        F --> L    G --> L    H --> L    I --> L    J --> L    K --> L        F --> M    G --> M    H --> M    I --> M    J --> M    K --> M        F --> N    G --> N    H --> N    I --> N    J --> N    K --> N        H --> O    I --> O    J --> O    K --> O        F --> P    G --> P    H --> P    I --> P    J --> P        F --> Q    G --> R    H --> S    I --> T3.2 订单处理流程订单创建流程图sequenceDiagram    participant Client    participant APIGateway    participant OrderService    participant ProductService    participant InventoryService    participant PaymentService    participant NotificationService        Client->>APIGateway: POST /orders    APIGateway->>OrderService: POST /orders    OrderService->>ProductService: GET /products/{id}    ProductService-->>OrderService: Product details    OrderService->>InventoryService: POST /inventory/reserve    InventoryService-->>OrderService: Reservation confirmed    OrderService->>PaymentService: POST /payments    PaymentService-->>OrderService: Payment processed    OrderService->>OrderService: Create order    OrderService->>NotificationService: Send order confirmation    OrderService-->>APIGateway: Order created    APIGateway-->>Client: Order response订单服务实现@Service@Transactionalpublic class OrderService {        private final OrderRepository orderRepository;    private final ProductServiceClient productClient;    private final InventoryServiceClient inventoryClient;    private final PaymentServiceClient paymentClient;    private final OrderEventPublisher eventPublisher;    private final Tracer tracer;        public Order createOrder(OrderRequest request) {        Span newSpan = tracer.nextSpan().name("createOrder").start();                try (Tracer.SpanInScope ws = tracer.withSpan(newSpan.start())) {            // 1. 获取产品信息            Product product = productClient.getProduct(request.getProductId());            newSpan.tag("productId", product.getId().toString());                        // 2. 检查库存            InventoryReservation reservation = inventoryClient.reserveInventory(                product.getId(), request.getQuantity());            newSpan.tag("inventoryReserved", "true");                        // 3. 计算总价            Money totalAmount = product.getPrice().multiply(request.getQuantity());                        // 4. 处理支付            Payment payment = paymentClient.processPayment(                request.getCustomerId(), totalAmount);            newSpan.tag("paymentProcessed", "true");                        // 5. 创建订单            Order order = new Order(                request.getCustomerId(),                product,                request.getQuantity(),                totalAmount,                payment.getId()            );                        order = orderRepository.save(order);            newSpan.tag("orderId", order.getId().toString());                        // 6. 发布事件            eventPublisher.publishOrderCreatedEvent(order);                        return order;        } finally {            newSpan.end();        }    }}AI运行代码3.3 服务监控与告警监控架构图graph TB    subgraph "微服务"        A[产品服务]        B[订单服务]        C[支付服务]    end        subgraph "监控工具"        D[Prometheus]        E[Grafana]        F[AlertManager]    end        subgraph "日志系统"        G[ELK Stack]    end        subgraph "追踪系统"        H[Zipkin]    end        A -->|metrics| D    B -->|metrics| D    C -->|metrics| D        A -->|logs| G    B -->|logs| G    C -->|logs| G        A -->|traces| H    B -->|traces| H    C -->|traces| H        D --> E    D --> F    F -->|alerts| E健康检查实现@RestControllerpublic class HealthController {        private final OrderRepository orderRepository;    private final ProductServiceClient productClient;        @GetMapping("/health")    public ResponseEntity<HealthStatus> health() {        HealthStatus status = new HealthStatus();                // 检查数据库连接        try {            orderRepository.count();            status.setDatabase(true);        } catch (Exception e) {            status.setDatabase(false);        }                // 检查外部服务        try {            productClient.health();            status.setProductService(true);        } catch (Exception e) {            status.setProductService(false);        }                // 整体状态        status.setOverall(status.isDatabase() && status.isProductService());                return ResponseEntity.ok(status);    }} public class HealthStatus {    private boolean overall;    private boolean database;    private boolean productService;        // getters and setters}AI运行代码4. 微服务架构最佳实践4.1 设计原则单一职责:每个服务专注于单一业务功能去中心化治理:服务可自主选择技术栈去中心化数据管理:每个服务拥有自己的数据存储自动化部署:建立CI/CD流水线容错设计:实现熔断、重试、超时等机制监控可观测性:全面监控、日志和追踪4.2 部署策略蓝绿部署流程图graph TD    A[当前生产环境] -->|流量| B[蓝环境]    C[新版本部署] --> D[绿环境]    D -->|测试| E[验证]    E -->|通过| F[切换流量]    F --> G[流量指向绿环境]    G --> H[蓝环境下线]金丝雀发布流程图graph TD    A[生产环境] -->|100%流量| B[当前版本]    C[新版本部署] --> D[金丝雀实例]    D -->|10%流量| E[监控指标]    E -->|正常| F[增加流量]    F -->|50%流量| G[继续监控]    G -->|正常| H[100%流量]    H --> I[全面部署]4.3 安全模式OAuth2 + JWT认证流程sequenceDiagram    participant Client    participant AuthServer    participant ResourceServer    participant APIGateway        Client->>AuthServer: 请求令牌 (用户名/密码)    AuthServer-->>Client: JWT访问令牌    Client->>APIGateway: API请求 + JWT    APIGateway->>ResourceServer: 转发请求 + JWT    ResourceServer->>ResourceServer: 验证JWT签名    ResourceServer-->>APIGateway: 响应数据    APIGateway-->>Client: 响应数据API安全实现@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {        @Override    protected void configure(HttpSecurity http) throws Exception {        http            .csrf().disable()            .authorizeRequests()                .antMatchers("/actuator/**").permitAll()                .antMatchers("/public/**").permitAll()                .anyRequest().authenticated()            .and()            .oauth2ResourceServer()                .jwt()                .jwtDecoder(jwtDecoder());    }        @Bean    public JwtDecoder jwtDecoder() {        return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();    }}AI运行代码5. 微服务架构演进路线5.1 从单体到微服务的演进graph LR    A[单体应用] --> B[识别服务边界]    B --> C[拆分核心服务]    C --> D[引入服务发现]    D --> E[添加API网关]    E --> F[实现配置中心]    F --> G[引入熔断器]    G --> H[实现分布式追踪]    H --> I[事件驱动架构]    I --> J[完整微服务架构]5.2 微服务成熟度模型级别    特征    实践初始级    单体应用    基本功能实现可重复级    服务拆分    核心服务独立部署已定义级    服务治理    服务发现、配置中心量化管理级    弹性设计    熔断器、限流、降级优化级    智能运维    自动扩缩容、自愈能力6. 微服务架构挑战与解决方案6.1 分布式事务管理Saga模式实现@Servicepublic class OrderSagaOrchestrator {        private final OrderService orderService;    private final PaymentService paymentService;    private final InventoryService inventoryService;    private final NotificationService notificationService;        @Transactional    public Order createOrder(OrderRequest request) {        // 1. 创建订单        Order order = orderService.createOrder(request);                try {            // 2. 处理支付            Payment payment = paymentService.processPayment(                order.getId(), order.getTotalAmount());                        // 3. 扣减库存            inventoryService.reserveInventory(                order.getProductId(), order.getQuantity());                        // 4. 发送通知            notificationService.sendOrderConfirmation(order);                        return order;        } catch (Exception e) {            // 补偿事务            compensate(order);            throw new RuntimeException("Order creation failed", e);        }    }        private void compensate(Order order) {        try {            paymentService.refundPayment(order.getId());            inventoryService.releaseInventory(                order.getProductId(), order.getQuantity());            orderService.cancelOrder(order.getId());        } catch (Exception e) {            // 记录补偿失败,需要人工干预            log.error("Compensation failed for order {}", order.getId(), e);        }    }}AI运行代码Saga模式流程图graph TD    A[开始] --> B[创建订单]    B --> C[处理支付]    C --> D[扣减库存]    D --> E[发送通知]    E --> F[完成]        C -->|失败| G[补偿: 取消订单]    D -->|失败| H[补偿: 退款]    E -->|失败| I[补偿: 释放库存]        G --> J[结束]    H --> J    I --> J6.2 服务间通信优化gRPC实现高效通信// 定义服务service ProductService {    rpc GetProduct(GetProductRequest) returns (Product);    rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);} // 服务实现@GrpcServicepublic class ProductGrpcService extends ProductServiceGrpc.ProductServiceImplBase {        private final ProductRepository productRepository;        @Override    public void getProduct(GetProductRequest request,                          StreamObserver<Product> responseObserver) {        ProductEntity entity = productRepository.findById(request.getId())            .orElseThrow(() -> new StatusRuntimeException(                Status.NOT_FOUND.withDescription("Product not found")));                Product product = Product.newBuilder()            .setId(entity.getId())            .setName(entity.getName())            .setPrice(entity.getPrice())            .build();                    responseObserver.onNext(product);        responseObserver.onCompleted();    }} // 客户端调用@Servicepublic class OrderService {        private final ProductServiceGrpc.ProductServiceBlockingStub productClient;        public OrderService(ProductServiceGrpc.ProductServiceBlockingStub productClient) {        this.productClient = productClient;    }        public void processOrder(OrderRequest request) {        GetProductRequest productRequest = GetProductRequest.newBuilder()            .setId(request.getProductId())            .build();                    Product product = productClient.getProduct(productRequest);        // 处理订单逻辑    }}AI运行代码通信协议对比协议    优点    缺点    适用场景REST/HTTP    简单、通用    性能较低、开销大    外部API、简单服务gRPC    高性能、强类型    复杂性高、浏览器支持有限    内部服务、高性能需求GraphQL    灵活查询、减少请求    缓存复杂、学习曲线    复杂数据查询场景消息队列    异步、解耦    最终一致性、延迟    事件驱动架构7. 微服务架构未来趋势7.1 服务网格(Service Mesh)graph TB    subgraph "应用层"        A[产品服务]        B[订单服务]        C[支付服务]    end        subgraph "服务网格"        D[Envoy代理]        E[Envoy代理]        F[Envoy代理]        G[Istio控制平面]    end        subgraph "基础设施"        H[Kubernetes]    end        A --> D    B --> E    C --> F        D --> G    E --> G    F --> G        G --> H7.2 无服务器(Serverless)与微服务结合graph LR    subgraph "API网关"        A[Spring Cloud Gateway]    end        subgraph "微服务"        B[核心业务服务]        C[用户服务]    end        subgraph "Serverless"        D[图片处理函数]        E[通知函数]        F[报告生成函数]    end        subgraph "事件总线"        G[Kafka]    end        A --> B    A --> C    B --> G    C --> G    G --> D    G --> E    G --> F8. 结论Java微服务架构通过一系列设计模式和技术组件,为构建现代化、可扩展的企业级应用提供了强大支持。从服务拆分、服务发现到API网关、配置中心,再到熔断器、分布式追踪和事件驱动架构,每个模式都解决了微服务架构中的特定挑战。成功实施微服务架构需要:合理的服务边界划分完善的服务治理体系强大的自动化运维能力全面的监控和可观测性持续的架构演进和优化随着云原生技术的发展,微服务架构将与容器化、服务网格、无服务器计算等趋势深度融合,为企业数字化转型提供更强大的技术支撑。开发者应持续关注这些技术演进,结合业务需求选择合适的架构模式和技术栈,构建具有高弹性、高可用的现代化应用系统。————————————————原文链接:https://blog.csdn.net/zzywxc787/article/details/150636059
  • [技术干货] Java-Spring入门指南(五)Spring自动装配
    前言在上一篇博客中,我们掌握了Spring依赖注入的两种核心手动方式——构造器注入与setter注入,但也留下了一个明显的“痛点”:当Bean的依赖关系复杂(比如一个Service依赖多个Dao)时,我们需要在XML中反复编写<property ref="..."/>,配置繁琐且容易出错。有没有办法让容器“主动找”依赖,而不是我们“手动指”依赖?答案就是这篇要讲的Spring自动装配(Autowire)。它是Spring在手动注入基础上的优化,核心是“容器根据预设规则自动匹配并注入依赖”,能大幅减少XML配置冗余。本文将从“自动装配的本质”切入,手把手实战两种核心自动装配方式(byName、byType),再详解alias(Bean别名)和import(XML整合)这两个辅助配置。我的个人主页,欢迎来阅读我的其他文章https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343我的Java-Spring入门指南知识文章专栏欢迎来阅读指出不足https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482 一、什么是Spring自动装配?在讲实战前,我们先搞懂“自动装配”到底解决了什么问题——它不是替代手动注入,而是对setter注入的“简化”。1.1 手动注入的痛点《上一篇地址https://blog.csdn.net/2402_83322742/article/details/151373078》回顾上一篇的User依赖Address,我们需要在XML中手动配置<property ref="address"/>:<!-- 手动注入:必须明确写ref指向依赖的Bean --><bean id="user" class="org.example.pojo.User">    <property name="address" ref="address"></property> <!-- 手动关联 --></bean><bean id="address" class="org.example.pojo.Address"></bean>AI运行代码xml如果一个OrderService依赖OrderDao、UserDao、LogDao三个Bean,就需要写3个<property>标签——依赖越多,配置越繁琐。1.2 自动装配的定义自动装配(Autowire)的核心是:容器根据预设的“匹配规则”,自动在IoC容器中查找当前Bean的依赖(如Person依赖的Dog、Cat),并完成注入,无需手动编写<property ref="..."/>。1.3 自动装配的本质容器先通过无参构造器创建当前Bean(如Person);按规则(byName/byType)在容器中找依赖的Bean;找到后,自动调用当前Bean的setter方法完成注入。关键前提:自动装配仅支持setter注入,不支持构造器注入!所以Bean必须有依赖属性的setter方法。二、自动装配的核心方式Spring提供多种自动装配方式,最常用、最核心的是byName(按名称匹配) 和byType(按类型匹配)2.1 准备工作首先编写我们的核心Bean类:// Dog类(被依赖方)public class Dog {    public void eat() {        System.out.println("狗喜欢吃骨头");    }}// Cat类(被依赖方)public class Cat {    public void eat() {        System.out.println("猫喜欢吃小鱼干");    }}// Person类(依赖方:依赖Dog和Cat)public class Person {    private Dog dog;    private Cat cat;    public Cat getCat() {        return cat;    }    public void setCat(Cat cat) {        this.cat = cat;    }    public Dog getDog() {        return dog;    }    public void setDog(Dog dog) {        this.dog = dog;    }}AI运行代码java2.2 方式一:byName2.2.1 核心规则容器会根据当前Bean的“属性名”,去匹配IoC容器中其他Bean的“id属性”——属性名与Bean的id完全一致时,自动注入。用我们的Person举例:// Person类(依赖方:依赖Dog和Cat)public class Person {    private Dog dog;    private Cat cat;    public Cat getCat() {        return cat;    }    public void setCat(Cat cat) {        this.cat = cat;    }    public Dog getDog() {        return dog;    }    public void setDog(Dog dog) {        this.dog = dog;    }}AI运行代码javaPerson有两个属性:dog(属性名)、cat(属性名);容器中需有id="dog"的Dog类Bean、id="cat"的Cat类Bean;配置autowire="byName"后,容器自动匹配并注入。2.2.2 配置实战<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <!-- 1. 配置被依赖方Bean:id必须与Person的属性名一致 -->    <bean id="dog" class="org.example.pojo.autowire.Dog"></bean> <!-- id=dog 匹配 Person的dog属性 -->    <bean id="cat" class="org.example.pojo.autowire.Cat"></bean> <!-- id=cat 匹配 Person的cat属性 -->      <!-- 2. 配置依赖方Bean:开启byName自动装配 -->    <bean id="person" class="org.example.pojo.autowire.Person" autowire="byName">        <!-- 无需写<property ref="dog"/>和<property ref="cat"/>,容器自动匹配 -->    </bean></beans>AI运行代码xml2.2.3 测试代码与结果编写测试类,验证自动装配是否成功:     @Test    public void test2() {        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("autowire.xml");        Person person =  applicationContext.getBean("person", Person.class);        person.getCat().eat();        person.getDog().eat();    }AI运行代码java预期结果:2.2.4 关键注意点属性名与Bean的id必须完全一致:若Person的dog属性改名为doggy,而Bean的id还是dog,容器找不到匹配的Bean,会注入null;允许同类型多Bean:如你代码中注释的cat1,即使开启,只要Person没有cat1属性,就不影响cat属性的匹配(byName只看名称,不看类型);必须有setter方法:容器通过setter方法注入,若删除setDog(),会注入null。2.3 方式二:byType2.3.1 核心规则容器会根据当前Bean的“属性类型”,去匹配IoC容器中其他Bean的“class类型”——属性类型与Bean的class完全一致,且容器中该类型Bean唯一时,自动注入。。2.3.2 配置实战只需将person Bean的autowire改为byType,其他不变:<!-- 被依赖方Bean:id可任意(byType不依赖id) --><bean id="dog" class="org.example.pojo.autowire.Dog"></bean> <!-- 类型是Dog --><bean id="cat" class="org.example.pojo.autowire.Cat"></bean> <!-- 类型是Cat --><!-- 依赖方Bean:开启byType自动装配 --><bean id="person" class="org.example.pojo.autowire.Person" autowire="byType">    <!-- 同样无需手动配置property --></bean>AI运行代码xml2.3.3 测试代码与结果复用上面的test方法(只需保证XML配置是byType),预期结果与byName完全一致:关键区别:此时即使把dog Bean的id改为myDog,cat Bean的id改为myCat,依然能注入成功(byType不看id)。2.3.4 关键注意点容器中该类型Bean必须唯一:这是byType的核心限制!若此时容器有两个Cat类型Bean,启动容器会直接报错:NoUniqueBeanDefinitionException: No qualifying bean of type 'org.example.pojo.autowire.Cat' available: expected single matching bean but found 2: cat,cat1AI运行代码1不依赖Bean的id:即使Bean的id与属性名完全不同(如id=myDog,属性名=dog),只要类型匹配且唯一,就能注入;支持父子类/接口:若有Dog的子类BigDog,Person的dog属性是Dog类型,容器中只有BigDog类型Bean,也能匹配(多态支持)。2.4 byName vs byType 对比两种自动装配方式各有适用场景,用表格对比更清晰:对比维度    byName(按名称)    byType(按类型)匹配规则    属性名 ≡ Bean的id    属性类型 ≡ Bean的class(且唯一)依赖条件    必须保证属性名与Bean的id一致    必须保证容器中该类型Bean唯一灵活性    依赖命名规范,改id需同步改属性名    不依赖id,改id不影响注入容错性    同类型多Bean不报错(只看名称)    同类型多Bean直接报错适用场景    命名规范明确(如属性名=Bean id)    类型唯一且稳定(如工具类Bean)三、alias与import3.1 alias:给Bean起别名3.1.1 核心作用给IoC容器中的Bean分配多个“别名”,后续通过“原id”或“任意别名”都能获取该Bean,解耦Bean的引用与id的绑定。比如:<alias name="person" alias="person_name_1"/>AI运行代码xml原id:person;别名:person_name_1;获取Bean时,ac.getBean("person")和ac.getBean("person_name_1")得到的是同一个对象。3.1.2 适用场景多模块引用同一Bean:A模块习惯用personA称呼,B模块习惯用personB,给Bean起两个别名,无需修改原id;避免id冲突:若导入的第三方XML中已有user Bean,你本地的user Bean可起别名myUser,避免冲突。3.2 import:拆分与整合XML配置3.2.1 核心作用当项目中Bean数量庞大时,将所有Bean写在一个XML中会非常臃肿。import可以将多个分散的XML配置文件导入到一个“主配置文件”,只需加载主配置,就能加载所有XML中的Bean。比如:<import resource="beans.xml"/>AI运行代码xml主配置文件:autowire.xml(当前配置文件);导入的子配置:beans.xml(可存放其他Bean,如上一篇的Student、User);加载主配置时,autowire.xml和beans.xml中的Bean都会被加载到容器。3.2.2 实战场景假设项目分3个模块,每个模块的Bean存放在独立XML中:dao.xml:存放Dao层Bean(如UserDao、OrderDao);service.xml:存放Service层Bean(如UserService、OrderService);autowire.xml:存放Person、Dog、Cat等Bean(主配置);通过import整合主配置:<!-- 主配置文件:autowire.xml --><import resource="dao.xml"/><import resource="service.xml"/><!-- 本地Bean配置 --><bean id="dog" class="org.example.pojo.autowire.Dog"></bean><bean id="cat" class="org.example.pojo.autowire.Cat"></bean><bean id="person" class="org.example.pojo.autowire.Person" autowire="byName"></bean>AI运行代码xml加载时只需加载autowire.xml,就能获取所有模块的Bean,大幅提升可维护性。3.2.3 注意点路径问题:resource属性写XML的“相对路径”,若子XML在config目录下,需写resource="config/dao.xml";顺序无关:import的顺序不影响Bean的加载(Spring会处理依赖关系);重复导入不报错:同一XML被多次导入,Spring只会加载一次。四、自动装配的优缺点与使用建议4.1 优点简化配置:无需手动写大量<property ref="..."/>,减少XML冗余;降低维护成本:依赖关系变更时,只需修改Bean的id或类型,无需修改注入配置;支持模块化:结合import和alias,适合大型项目拆分配置。4.2 缺点可读性下降:无法直观看到Bean的依赖来源(手动注入能明确看到ref指向);排错难度高:注入null时,需排查命名、类型、Bean唯一性等多个维度;功能局限:仅支持setter注入,不支持构造器注入;不支持复杂依赖(如集合类型)。4.3 推荐使用原则简单场景用自动装配:如工具类、单一依赖的Bean(如Person依赖Dog/Cat),优先用byName(命名规范明确时)或byType(类型唯一时);复杂场景用手动注入:如多依赖、构造器注入、集合类型注入,手动配置更明确,排错更简单;大型项目结合注解:XML自动装配是基础,后续我们会讲注解驱动的自动装配(@Autowired、@Qualifier),比XML更灵活、更简洁。————————————————原文链接:https://blog.csdn.net/2402_83322742/article/details/151409439
  • [技术干货] 惊了!用 JavaAI 撸电商核心功能,我从 “代码小白“ 变 “项目大神“,3 小时搞定别人 3 天的活
    惊了!用JavaAI撸电商核心功能,我从"代码小白"变"项目大神",3小时搞定别人3天的活(附完整流程+代码)家人们谁懂啊!以前听说要做电商系统,我直接吓得关掉了IDEA——光是"商品管理"“订单流程”“购物车计算"这几个词,就够我啃一周文档。但自从用了JavaAI工具(我用的是飞算JavaAI,亲测好用),我发现开发居然能这么"躺平”:不用死磕SQL,不用纠结逻辑,甚至不用写重复代码,AI直接把"半成品"喂到嘴边,我只需要做"选择题"就行!今天就带大家手把手用JavaAI开发电商3大核心模块:商品管理(上架/搜索/库存)、购物车(加购/改数量/算总价)、订单系统(下单/改状态/查明细),全程幽默不踩坑,代码复制就能跑,哪怕你是刚学Java的"菜鸡",也能轻松拿捏!一、先唠唠JavaAI的"神奇之处"(新手必看)在开始前,先解答大家最关心的问题:JavaAI到底能帮我们做啥?举个例子:以前写商品列表接口,你得先设计表结构、写实体类、写Mapper、写Service、写Controller,一套下来2小时起步;现在用JavaAI,你只需要说一句"我要商品列表接口,支持按名称模糊查、按价格排序",AI直接把全套代码生成好,你改改数据库配置就能用——这效率,比外卖小哥送快餐还快!咱们今天用的飞算JavaAI,优势主要有3个:懂业务:你说"电商购物车要支持跨设备同步",它知道要用Redis存;你说"订单要防止重复提交",它自动加幂等校验。代码规范:生成的代码自带注释、异常处理、日志记录,比我司资深开发写的还规整。可修改:生成的代码不是"死的",你说"我要给订单加个优惠券字段",它立马帮你改实体类、表结构、接口逻辑。二、环境准备:5分钟搭好"作战基地"先把基础环境搞定,就像做饭前先把锅碗瓢盆摆好,简单得很:1. 安装JavaAI工具我用的是飞算JavaAI的IDEA插件,安装步骤跟装QQ一样简单:打开IDEA → 点击"File"→"Settings"→"Plugins"在搜索框搜"飞算JavaAI" → 点击"Install" → 重启IDEA打开插件后用手机号注册登录,选"智能引导模式"(新手首选,跟着走不迷路)2. 明确需求:跟AI"唠嗑"就行不用写复杂的PRD文档,就像跟朋友吐槽需求一样,把你想要的功能说清楚。我当时是这么跟AI说的(直接复制到插件输入框里): 我要开发电商系统的3个核心模块,具体需求如下:1. 商品管理模块:   - 功能:商品新增(名称、价格、库存、分类ID、图片URL)、商品列表查询(支持按名称模糊查、按价格排序、分页)、商品详情查询(按ID查)、商品上下架(改status字段)   - 约束:价格不能为负、库存不能为负、新增商品时status默认1(上架)2. 购物车模块:   - 功能:添加商品到购物车(用户ID、商品ID、数量)、修改购物车商品数量、删除购物车商品、查询用户购物车列表(显示商品名称、价格、数量、小计)、计算购物车总价   - 约束:添加时要校验商品是否存在+是否上架+库存是否足够、修改数量时不能超过商品库存3. 订单模块:   - 功能:创建订单(从购物车选商品生成订单,生成唯一订单编号)、查询订单列表(按用户ID查,显示订单编号、总金额、支付状态)、查询订单明细(显示每个商品的名称、数量、单价)、修改订单支付状态(待付款→已支付/已取消)   - 约束:创建订单时要扣减商品库存、防止重复下单(按订单编号幂等)、未支付订单不能改发货状态技术要求:- 后端:Spring Boot 2.7.x + MyBatis Plus 3.5.x + MySQL 8.0 + Redis 6.x(购物车用Redis存)- 接口:RESTful风格,返回格式统一(code+msg+data),支持分页(pageNum/pageSize)- 其他:异常处理(比如"商品不存在"返回明确提示)、日志记录(用Slf4j)AI运行代码写完直接点"提交需求",AI会秒回"正在分析需求"——这速度,比我领导批报销还快!三、JavaAI实战:3步生成电商核心功能(附完整代码)第一步:需求分析+表结构设计——AI帮你"拆需求+建表"提交需求后,飞算JavaAI会先帮你拆解需求,生成"功能关系图",然后自动设计数据库表结构。我生成的3张核心表SQL如下(直接复制到MySQL执行,无语法错误):-- 1. 商品表(product)CREATE TABLE product (    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',    product_name VARCHAR(255) NOT NULL COMMENT '商品名称(如“iPhone 15 Pro”)',    price DECIMAL(10,2) NOT NULL CHECK (price >= 0) COMMENT '商品价格(单位:元,不能为负)',    stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0) COMMENT '库存数量(不能为负)',    category_id BIGINT NOT NULL COMMENT '商品分类ID(关联分类表,此处简化暂不建)',    product_img VARCHAR(512) COMMENT '商品图片URL',    status TINYINT NOT NULL DEFAULT 1 COMMENT '商品状态(1=上架,0=下架)',    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',    INDEX idx_product_name (product_name), -- 名称模糊查索引    INDEX idx_category_id (category_id)    -- 分类查索引) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';-- 2. 订单表(order_info)CREATE TABLE order_info (    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号(唯一,格式:YYYYMMDDHHMMSS+随机6位)',    user_id BIGINT NOT NULL COMMENT '用户ID',    total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额(单位:元)',    pay_status TINYINT NOT NULL DEFAULT 0 COMMENT '支付状态(0=待付款,1=已支付,2=已取消)',    order_status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态(0=待发货,1=已发货,2=已完成)',    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',    pay_time DATETIME COMMENT '支付时间',    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',    INDEX idx_user_id (user_id), -- 按用户查订单索引    INDEX idx_order_no (order_no) -- 按订单号查索引) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';-- 3. 订单项表(order_item)—— 存订单里的商品明细CREATE TABLE order_item (    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单项ID',    order_id BIGINT NOT NULL COMMENT '关联订单ID(关联order_info.id)',    order_no VARCHAR(64) NOT NULL COMMENT '关联订单编号(冗余字段,方便查询)',    product_id BIGINT NOT NULL COMMENT '商品ID(关联product.id)',    product_name VARCHAR(255) NOT NULL COMMENT '商品名称(冗余,避免查商品表)',    product_price DECIMAL(10,2) NOT NULL COMMENT '商品购买时的价格(冗余,防止价格变动)',    buy_num INT NOT NULL COMMENT '购买数量',    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',    INDEX idx_order_id (order_id), -- 按订单查明细索引    INDEX idx_product_id (product_id) -- 按商品查订单索引) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';AI运行代码sql连"CHECK约束(价格/库存不能为负)"“冗余字段(订单项存商品名称)“这种细节都考虑到了,我当时只是提了句"要校验库存”,AI直接把"防呆逻辑"写到表结构里——这波属实是"贴心到骨子里”!第二步:接口设计+代码生成——AI帮你"写接口+填逻辑"表结构搞定后,AI会自动生成接口方案和全套代码(实体类、Mapper、Service、Controller)。我挑几个核心接口给大家展示,代码都是AI生成的,我只改了点注释:1. 商品管理核心接口(Controller层)package com.example.ecommerce.controller;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.example.ecommerce.common.Result;import com.example.ecommerce.entity.Product;import com.example.ecommerce.service.ProductService;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.List;/** * 商品管理接口 * 由飞算JavaAI自动生成,可根据需求调整 */@RestController@RequestMapping("/api/product")@Slf4jpublic class ProductController {    @Resource    private ProductService productService;    /**     * 新增商品     * @param product 商品信息(含name、price、stock等)     */    @PostMapping("/add")    public Result<?> addProduct(@RequestBody Product product) {        // AI自动生成的参数校验逻辑        if (product.getPrice().compareTo(BigDecimal.ZERO) < 0) {            return Result.fail("商品价格不能为负");        }        if (product.getStock() < 0) {            return Result.fail("商品库存不能为负");        }        // 默认为上架状态(status=1)        product.setStatus(1);        boolean success = productService.save(product);        if (success) {            log.info("新增商品成功,商品ID:{}", product.getId());            return Result.success("新增商品成功", product.getId());        }        return Result.fail("新增商品失败");    }    /**     * 商品列表查询(支持模糊查+排序+分页)     * @param productName 商品名称(模糊匹配,可为空)     * @param pageNum 页码(默认1)     * @param pageSize 每页条数(默认10)     * @param sortField 排序字段(默认id)     * @param sortType 排序类型(asc=升序,desc=降序,默认desc)     */    @GetMapping("/list")    public Result<?> getProductList(            @RequestParam(required = false) String productName,            @RequestParam(defaultValue = "1") Integer pageNum,            @RequestParam(defaultValue = "10") Integer pageSize,            @RequestParam(defaultValue = "id") String sortField,            @RequestParam(defaultValue = "desc") String sortType) {                // AI自动生成的分页查询逻辑        Page<Product> page = new Page<>(pageNum, pageSize);        List<Product> productList = productService.getProductList(page, productName, sortField, sortType);        page.setRecords(productList);                log.info("查询商品列表成功,页码:{},条数:{}", pageNum, productList.size());        return Result.success("查询成功", page);    }    /**     * 商品上下架     * @param productId 商品ID     * @param status 目标状态(1=上架,0=下架)     */    @PutMapping("/status")    public Result<?> updateProductStatus(            @RequestParam Long productId,            @RequestParam Integer status) {                if (!status.equals(0) && !status.equals(1)) {            return Result.fail("状态只能是0(下架)或1(上架)");        }                Product product = new Product();        product.setId(productId);        product.setStatus(status);        boolean success = productService.updateById(product);                if (success) {            String msg = status == 1 ? "商品上架成功" : "商品下架成功";            log.info(msg + ",商品ID:{}", productId);            return Result.success(msg);        }        return Result.fail("修改商品状态失败,商品不存在");    }}AI运行代码java2. 购物车核心接口(用Redis存储,AI自动生成Redis操作逻辑)package com.example.ecommerce.controller;import com.example.ecommerce.common.Result;import com.example.ecommerce.entity.vo.CartVO;import com.example.ecommerce.service.CartService;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.math.BigDecimal;import java.util.List;/** * 购物车接口(Redis存储) * 由飞算JavaAI自动生成,可根据需求调整 */@RestController@RequestMapping("/api/cart")@Slf4jpublic class CartController {    @Resource    private CartService cartService;    /**     * 添加商品到购物车     * @param userId 用户ID     * @param productId 商品ID     * @param buyNum 购买数量     */    @PostMapping("/add")    public Result<?> addToCart(            @RequestParam Long userId,            @RequestParam Long productId,            @RequestParam Integer buyNum) {                // AI自动生成的业务校验:商品是否存在+上架+库存足够        String checkMsg = cartService.checkBeforeAdd(userId, productId, buyNum);        if (checkMsg != null) {            return Result.fail(checkMsg);        }                // 添加到Redis购物车        boolean success = cartService.addCart(userId, productId, buyNum);        if (success) {            log.info("添加商品到购物车成功,用户ID:{},商品ID:{}", userId, productId);            return Result.success("添加购物车成功");        }        return Result.fail("添加购物车失败");    }    /**     * 查询用户购物车列表(含小计+总价)     * @param userId 用户ID     */    @GetMapping("/list")    public Result<?> getCartList(@RequestParam Long userId) {        List<CartVO> cartVOList = cartService.getCartList(userId);        // 计算购物车总价        BigDecimal totalAmount = cartVOList.stream()                .map(CartVO::getSubtotal)                .reduce(BigDecimal.ZERO, BigDecimal::add);                // 封装返回结果(列表+总价)        return Result.success("查询购物车成功", new HashMap<String, Object>() {{            put("cartList", cartVOList);            put("totalAmount", totalAmount);        }});    }    /**     * 修改购物车商品数量     * @param userId 用户ID     * @param productId 商品ID     * @param newNum 新数量     */    @PutMapping("/num")    public Result<?> updateCartNum(            @RequestParam Long userId,            @RequestParam Long productId,            @RequestParam Integer newNum) {                if (newNum <= 0) {            return Result.fail("数量不能小于等于0");        }                // 校验库存是否足够        String checkMsg = cartService.checkStock(productId, newNum);        if (checkMsg != null) {            return Result.fail(checkMsg);        }                boolean success = cartService.updateCartNum(userId, productId, newNum);        if (success) {            log.info("修改购物车数量成功,用户ID:{},商品ID:{},新数量:{}", userId, productId, newNum);            return Result.success("修改数量成功");        }        return Result.fail("修改数量失败,商品不在购物车中");    }}AI运行代码java3. 订单核心接口(含库存扣减 + 幂等校验)package com.example.ecommerce.controller;import com.example.ecommerce.common.Result;import com.example.ecommerce.entity.OrderInfo;import com.example.ecommerce.entity.vo.OrderDetailVO;import com.example.ecommerce.service.OrderService;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.List;/*** 订单接口* 由飞算JavaAI自动生成,可根据需求调整*/@RestController@RequestMapping("/api/order")@Slf4jpublic class OrderController {   @Resource   private OrderService orderService;   /**    * 创建订单(从购物车选商品生成)    * @param userId 用户ID    * @param productIds 选中的商品ID列表(逗号分隔,如"1,2,3")    */   @PostMapping("/create")   public Result<?> createOrder(           @RequestParam Long userId,           @RequestParam String productIds) {             // 防重复提交:生成请求唯一标识(实际项目可存在Redis,设置5分钟过期)       String requestId = userId + "_" + System.currentTimeMillis();       boolean isRepeat = orderService.checkRepeatRequest(requestId);       if (isRepeat) {           return Result.fail("请勿重复提交订单");       }             try {           // AI自动生成的订单创建逻辑:查购物车→算总价→扣库存→生成订单→清购物车           String orderNo = orderService.createOrder(userId, productIds);           log.info("创建订单成功,用户ID:{},订单编号:{}", userId, orderNo);           return Result.success("创建订单成功", orderNo);       } catch (Exception e) {           log.error("创建订单失败,用户ID:{},错误信息:{}", userId, e.getMessage());           return Result.fail("创建订单失败:" + e.getMessage());       }   }   /**    * 查询订单列表    * @param userId 用户ID    * @param pageNum 页码(默认1)    * @param pageSize 每页条数(默认10)    */   @GetMapping("/list")   public Result<?> getOrderList(           @RequestParam Long userId,           @RequestParam(defaultValue = "1") Integer pageNum,           @RequestParam(defaultValue = "10") Integer pageSize) {             List<OrderInfo> orderList = orderService.getOrderList(userId, pageNum, pageSize);       log.info("查询订单列表成功,用户ID:{},订单数量:{}", userId, orderList.size());       return Result.success("查询订单列表成功", orderList);   }   /**    * 查询订单明细    * @param orderNo 订单编号    * @param userId 用户ID(校验订单归属,防止查别人的订单)    */   @GetMapping("/detail")   public Result<?> getOrderDetail(           @RequestParam String orderNo,           @RequestParam Long userId) {             OrderDetailVO orderDetail = orderService.getOrderDetail(orderNo, userId);       if (orderDetail == null) {           return Result.fail("订单不存在或无权限查看");       }       return Result.success("查询订单明细成功", orderDetail);   }   /**    * 修改订单支付状态    * @param orderNo 订单编号    * @param userId 用户ID    * @param payStatus 目标支付状态(1=已支付,2=已取消)    */   @PutMapping("/payStatus")   public Result<?> updatePayStatus(           @RequestParam String orderNo,           @RequestParam Long userId,           @RequestParam Integer payStatus) {             // 校验支付状态合法性       if (!payStatus.equals(1) && !payStatus.equals(2)) {           return Result.fail("支付状态只能是1(已支付)或2(已取消)");       }             // 校验订单归属+当前状态(待付款才能改)       String checkMsg = orderService.checkOrderBeforeUpdate(orderNo, userId);       if (checkMsg != null) {           return Result.fail(checkMsg);       }             boolean success = orderService.updatePayStatus(orderNo, payStatus);       if (success) {           String msg = payStatus == 1 ? "订单支付成功" : "订单取消成功";           log.info("{},订单编号:{},用户ID:{}", msg, orderNo, userId);           return Result.success(msg);       }       return Result.fail("修改支付状态失败");   }}AI运行代码4. 核心 Service 层代码(AI 生成,含业务逻辑)以订单创建为例,AI 自动处理了 “库存扣减”" 事务管理 "“订单编号生成” 等关键逻辑:package com.example.ecommerce.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.example.ecommerce.entity.*;import com.example.ecommerce.entity.vo.CartVO;import com.example.ecommerce.entity.vo.OrderDetailVO;import com.example.ecommerce.mapper.*;import com.example.ecommerce.service.CartService;import com.example.ecommerce.service.OrderService;import com.example.ecommerce.service.ProductService;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.math.BigDecimal;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.List;import java.util.Random;import java.util.concurrent.TimeUnit;/*** 订单服务实现类* 由飞算JavaAI自动生成,可根据需求调整*/@Servicepublic class OrderServiceImpl implements OrderService {   @Resource   private OrderInfoMapper orderInfoMapper;   @Resource   private OrderItemMapper orderItemMapper;   @Resource   private ProductMapper productMapper;   @Resource   private CartService cartService;   @Resource   private RedisTemplate<String, Object> redisTemplate;   /**    * 创建订单(事务保证:扣库存+生成订单+清购物车要么全成,要么全失败)    */   @Override   @Transactional(rollbackFor = Exception.class)   public String createOrder(Long userId, String productIds) {       // 1. 查询用户购物车中选中的商品       List<CartVO> cartVOList = cartService.getCheckedCartList(userId, productIds);       if (cartVOList.isEmpty()) {           throw new RuntimeException("购物车中无选中商品");       }       // 2. 计算订单总金额       BigDecimal totalAmount = cartVOList.stream()               .map(CartVO::getSubtotal)               .reduce(BigDecimal.ZERO, BigDecimal::add);       // 3. 生成唯一订单编号(格式:YYYYMMDDHHMMSS+6位随机数)       String orderNo = generateOrderNo();       // 4. 扣减商品库存(加行锁防止超卖:SELECT ... FOR UPDATE)       for (CartVO cartVO : cartVOList) {           Long productId = cartVO.getProductId();           Integer buyNum = cartVO.getBuyNum();                     // 查询商品并加锁           Product product = productMapper.selectByIdForUpdate(productId);           if (product == null || product.getStatus() == 0) {               throw new RuntimeException("商品" + cartVO.getProductName() + "已下架或不存在");           }           if (product.getStock() < buyNum) {               throw new RuntimeException("商品" + cartVO.getProductName() + "库存不足,当前库存:" + product.getStock());           }                     // 扣减库存           Product updateProduct = new Product();           updateProduct.setId(productId);           updateProduct.setStock(product.getStock() - buyNum);           productMapper.updateById(updateProduct);       }       // 5. 插入订单表       OrderInfo orderInfo = new OrderInfo();       orderInfo.setOrderNo(orderNo);       orderInfo.setUserId(userId);       orderInfo.setTotalAmount(totalAmount);       orderInfo.setPayStatus(0); // 0=待付款       orderInfo.setOrderStatus(0); // 0=待发货       orderInfo.setCreateTime(LocalDateTime.now());       orderInfoMapper.insert(orderInfo);       // 6. 插入订单项表       for (CartVO cartVO : cartVOList) {           OrderItem orderItem = new OrderItem();           orderItem.setOrderId(orderInfo.getId());           orderItem.setOrderNo(orderNo);           orderItem.setProductId(cartVO.getProductId());           orderItem.setProductName(cartVO.getProductName());           orderItem.setProductPrice(cartVO.getProductPrice());           orderItem.setBuyNum(cartVO.getBuyNum());           orderItem.setCreateTime(LocalDateTime.now());           orderItemMapper.insert(orderItem);       }       // 7. 清空购物车中已下单的商品       cartService.clearCheckedCart(userId, productIds);       return orderNo;   }   /**    * 生成唯一订单编号    */   private String generateOrderNo() {       // 时间戳部分:YYYYMMDDHHMMSS       String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));       // 随机数部分:6位随机数       String randomStr = String.format("%06d", new Random().nextInt(1000000));       return timeStr + randomStr;   }   /**    * 防重复提交校验    */   @Override   public boolean checkRepeatRequest(String requestId) {       String redisKey = "order:repeat:" + requestId;       // 如果Redis中已存在,说明是重复请求       if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {           return true;       }       // 不存在则存入Redis,设置5分钟过期       redisTemplate.opsForValue().set(redisKey, "1", 5, TimeUnit.MINUTES);       return false;   }   // 其他方法(getOrderList、getOrderDetail、updatePayStatus等)省略,AI会自动生成完整实现}AI运行代码第三步:系统测试 + 优化 ——AI 帮你 “找 bug + 提建议”1. 接口测试(用 Postman 快速验证)AI 生成代码后,我们只需要启动项目,用 Postman 调用接口测试即可。以 “添加商品到购物车” 为例:请求地址:http://localhost:8080/api/cart/add请求方式:POST请求参数:userId=1&productId=1&buyNum=2成功响应:{ "code": 200, "msg": "添加购物车成功", "data": null}AI运行代码如果参数有误(比如 buyNum=100,而商品库存只有 10),AI 生成的异常处理会返回明确提示:{ "code": 500, "msg": "添加购物车失败:商品iPhone 15 Pro库存不足,当前库存:10", "data": null}AI运行代码2. AI 自动提优化建议测试过程中,我在飞算 JavaAI 里问了句 “这个电商系统还有哪些可以优化的地方?”,AI 立马给出了 3 个实用建议:库存预热:高并发场景下,可把热门商品库存缓存到 Redis,减少数据库访问(附 Redis 缓存库存的代码示例)。订单超时取消:用定时任务(如 Quartz)扫描超过 30 分钟未支付的订单,自动改为 “已取消” 并恢复库存(附定时任务代码)。接口限流:给 “创建订单” 接口加限流(如每个用户每分钟最多创建 5 个订单),防止恶意请求(附 Redis 限流代码)。比如 AI 生成的 “订单超时取消” 定时任务代码:package com.example.ecommerce.task;import com.example.ecommerce.entity.OrderInfo;import com.example.ecommerce.mapper.OrderInfoMapper;import com.example.ecommerce.mapper.ProductMapper;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.time.LocalDateTime;import java.util.List;/*** 订单超时取消定时任务* 由飞算JavaAI自动生成(优化建议配套代码)*/@Componentpublic class OrderTimeoutTask {   @Resource   private OrderInfoMapper orderInfoMapper;   @Resource   private ProductMapper productMapper;   /**    * 每5分钟执行一次:查询超过30分钟未支付的订单,改为已取消并恢复库存    */   @Scheduled(cron = "0 0/5 * * * ?")   @Transactional(rollbackFor = Exception.class)   public void cancelTimeoutOrder() {       // 查询条件:待付款(payStatus=0)+ 创建时间超过30分钟       LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(30);       List<OrderInfo> timeoutOrders = orderInfoMapper.selectTimeoutOrders(0, timeoutTime);             for (OrderInfo order : timeoutOrders) {           // 1. 修改订单状态为已取消           order.setPayStatus(2);           order.setUpdateTime(LocalDateTime.now());           orderInfoMapper.updateById(order);                     // 2. 恢复商品库存(从订单项表查购买数量)           List<OrderItem> orderItems = orderItemMapper.selectByOrderId(order.getId());           for (OrderItem item : orderItems) {               Product product = productMapper.selectById(item.getProductId());               product.setStock(product.getStock() + item.getBuyNum());               productMapper.updateById(product);           }                     System.out.println("取消超时订单,订单编号:" + order.getOrderNo());       }   }}AI运行代码四、总结:JavaAI 到底让开发变简单了多少?回顾整个开发过程,我只做了 3 件事:跟 AI"唠嗑",把需求说清楚(10 分钟);复制 AI 生成的 SQL 建表(5 分钟);用 Postman 测试接口,改了 2 处注释(15 分钟)。剩下的 90% 工作(表结构设计、代码编写、异常处理、优化建议)全是 AI 搞定的,前后加起来不到 3 小时 —— 要是搁以前,我至少得熬 3 个晚上,还得担心库存超卖、重复下单这些 bug。最后给大家一个真诚建议:现在的开发早就不是 “比谁代码写得快”,而是 “比谁能用好工具”。JavaAI 这种工具就像给你配了个 “全能开发助手”,让你不用再死磕重复代码,把精力放在更有价值的业务设计上。下次再有人跟你说 “电商系统很难做”,你就把这篇文章甩给他,告诉他:“用 JavaAI,小白也能 3 小时搞定核心功能!————————————————原文链接:https://blog.csdn.net/m0_70680929/article/details/151122298