Java Web从入门到精通:全面探索与实战(一)
目录
引言:开启 Java Web 之旅
一、Java Web 基础概念大揭秘
1.1 什么是 Java Web
1.2 Java Web 的优势剖析
1.3 Java Web 相关核心概念详解
二、搭建 Java Web 开发环境:步步为营
2.1 所需软件大盘点
2.2 软件安装与配置全流程
三、深入 Java Web 核心技术:Servlet 与 JSP
3.1 Servlet 详解
3.2 JSP 探秘
3.3 Servlet 与 JSP 交互案例实操
引言:开启 Java Web 之旅
在互联网技术飞速发展的当下,Web 应用已成为连接用户与数据的关键桥梁,深入到生活的各个角落。从日常使用的社交平台、购物网站,到企业内部的管理系统,Web 应用无处不在,为人们的生活和工作带来了极大的便利。而 Java Web,凭借其强大的功能、卓越的稳定性和广泛的适用性,在 Web 开发领域占据着举足轻重的地位。
Java,作为一种跨平台的编程语言,拥有丰富的类库和强大的开发工具,为 Web 开发提供了坚实的技术支撑。Java Web 不仅继承了 Java 语言的优点,还融合了一系列专门用于 Web 开发的技术和框架,使得开发者能够高效地构建出功能丰富、性能卓越的 Web 应用程序。
Java Web 技术涵盖了多个层面,从底层的 Servlet 和 JSP,到中层的各种框架,如 Spring、Spring MVC、MyBatis 等,再到上层的前端技术,如 HTML、CSS、JavaScript 等,形成了一个完整的技术体系。这些技术相互协作,共同完成了 Web 应用从请求处理、业务逻辑实现到数据展示的全过程。
Servlet 作为 Java Web 应用的基础组件,运行在服务器端,负责接收客户端的 HTTP 请求,处理业务逻辑,并将处理结果返回给客户端。它的生命周期由 Servlet 容器管理,使得开发者可以专注于业务逻辑的实现,而无需过多关注底层的细节。
JSP 则是一种用于生成动态 Web 内容的技术,它允许将 Java 代码嵌入到 HTML 页面中,使得页面能够根据不同的请求动态生成内容。通过 JSP,开发者可以方便地实现页面的动态化,提高用户体验。
随着 Web 应用规模的不断扩大和业务复杂度的不断增加,各种框架应运而生。Spring 框架以其强大的依赖注入(DI)和面向切面编程(AOP)功能,简化了 Java Web 应用的开发过程,提高了代码的可维护性和可扩展性。Spring MVC 作为 Spring 框架的一个重要模块,实现了 MVC 设计模式,将业务逻辑、数据展示和用户交互分离,使得代码结构更加清晰,易于开发和维护。MyBatis 框架则专注于数据库访问层的开发,通过简单的 XML 配置或注解,实现了 Java 对象与数据库表之间的映射,大大提高了数据库操作的效率和灵活性。
本博客旨在深入剖析 Java Web 开发的核心技术,从基础概念到高级应用,从理论知识到实际案例,全面而系统地介绍 Java Web 开发的各个方面。通过阅读本博客,读者将对 Java Web 开发有一个全面而深入的了解,掌握 Java Web 开发的核心技能,能够独立开发出功能完善、性能卓越的 Web 应用程序。无论是初学者还是有一定经验的开发者,都能从本博客中获得启发和帮助,开启自己的 Java Web 开发之旅。
一、Java Web 基础概念大揭秘
1.1 什么是 Java Web
Java Web,从本质上来说,是运用 Java 技术来解决 Web 领域相关问题的技术集合。它涵盖了服务器端和客户端两部分的技术应用 ,不过当前 Java 在客户端的应用,如 Java Applet,已经较少使用,而在服务器端的应用则极为丰富,像 Servlet、JSP 以及各种第三方框架等都得到了广泛应用。
以常见的电商网站为例,当用户在浏览器中输入网址并访问电商网站时,浏览器作为客户端向服务器发送请求。服务器端的 Java Web 应用程序接收到请求后,通过 Servlet 来处理业务逻辑,比如验证用户身份、查询商品信息等。然后,利用 JSP 生成动态的 HTML 页面,将商品列表、用户购物车等信息展示给用户。在这个过程中,还可能会使用到各种第三方框架,如 Spring 来管理对象的生命周期和依赖关系,MyBatis 来进行数据库操作,从而实现一个完整的、功能丰富的电商购物流程。
1.2 Java Web 的优势剖析
Java 语言自身具备的跨平台特性,使得基于 Java Web 开发的应用程序能够轻松地在不同的操作系统上运行,无需针对每个操作系统进行单独的开发和适配。这大大降低了开发成本和维护难度,提高了应用程序的通用性和可移植性。
在安全性能方面,Java Web 有着严格的安全机制,通过字节码验证和安全管理器等手段,能够有效抵御各种潜在的恶意入侵,保障应用程序和用户数据的安全。以用户登录模块为例,Java Web 可以利用其安全特性,对用户输入的账号和密码进行加密传输和存储,防止被黑客窃取。
Java 的多线程机制允许 Java Web 应用程序同时处理多个用户请求,通过为每个用户创建独立的线程,实现高效的并发处理。这使得应用程序在面对大量用户访问时,依然能够保持良好的性能和响应速度,确保用户能够获得流畅的使用体验。
Java 拥有丰富的类库和各种优秀的开发框架,如前面提到的 Spring、MyBatis 等。这些框架提供了大量的通用功能和工具,开发者可以基于这些框架快速搭建应用程序的基础架构,减少了重复开发的工作量,提高了开发效率,并且使得代码的结构更加清晰,易于维护和扩展。
1.3 Java Web 相关核心概念详解
- B/S 架构:即 Browser/Server(浏览器 / 服务器)架构,是随着 Web 技术兴起而流行的一种网络结构模式。在这种架构下,客户端只需要安装一个浏览器,如常见的 Chrome、Firefox、Edge 等,而系统功能实现的核心部分则集中在服务器端。用户通过浏览器向服务器发送请求,服务器接收请求后进行处理,并将处理结果返回给浏览器进行展示。例如,我们日常使用的各类网页版邮箱、在线办公系统等,都是基于 B/S 架构实现的。用户无需在本地安装复杂的软件,只需通过浏览器即可随时随地访问和使用这些服务。
- 静态资源与动态资源:静态资源指的是那些内容固定不变的 Web 资源,如 HTML 页面、CSS 样式表、JavaScript 脚本文件、图片、音频、视频等。无论何时何地,不同用户访问这些静态资源,看到的内容都是相同的。它们可以直接被浏览器加载和解析,无需经过服务器的动态处理。而动态资源则是指内容会根据不同的请求和条件动态生成的 Web 资源。比如 JSP 页面、Servlet 等,它们会根据用户的请求参数、数据库中的数据等,在服务器端动态生成相应的 HTML 内容返回给浏览器。以新闻网站为例,新闻列表页面可能是一个动态资源,服务器会根据用户的浏览历史、所在地区等因素,动态生成个性化的新闻列表展示给用户。
- 数据库:在 Java Web 应用中,数据库用于存储和管理应用程序所需的数据。常见的关系型数据库有 MySQL、Oracle、SQL Server 等,非关系型数据库有 MongoDB、Redis 等。数据库与 Java Web 应用程序之间通过各种数据库访问技术进行交互,如 JDBC(Java Database Connectivity)。以一个简单的用户注册功能为例,当用户在 Web 页面上填写注册信息并提交后,Java Web 应用程序会通过 JDBC 将用户信息插入到数据库中进行保存,以便后续的登录验证和用户数据管理。
- HTTP 协议:超文本传输协议(Hypertext Transfer Protocol,HTTP)是用于从万维网(WWW)服务器传输超文本到本地浏览器的传送协议,它基于 TCP/IP 通信协议来传递数据,包括 HTML 文件、图片文件、查询结果等。HTTP 是一个简单的请求 - 响应协议,客户端向服务器发送请求报文,服务器接收到请求后返回响应报文。请求报文包括请求行、请求头和请求体,响应报文则包括响应行、响应头和响应体。例如,当我们在浏览器中输入一个网址并按下回车键时,浏览器会根据 HTTP 协议向服务器发送一个 GET 请求,请求行中包含请求方法(GET)、请求资源的 URL 和 HTTP 协议版本;服务器接收到请求后,根据请求的资源返回相应的响应,响应行中包含 HTTP 协议版本、状态码和状态描述,状态码如 200 表示请求成功,404 表示请求的资源未找到等。
- Web 服务器:Web 服务器的作用是接收客户端的请求,对请求进行处理,并返回相应的响应。常见的 Web 服务器有 Tomcat、Jetty、Apache 等。其中,Tomcat 是一个免费的开源 Web 应用服务器,也是 Java Web 开发中常用的服务器之一,它不仅可以处理 HTML 页面的请求,还是一个 Servlet 和 JSP 容器,能够很好地支持 Java Web 应用的运行。当我们开发好一个 Java Web 应用后,需要将其部署到 Web 服务器上,才能对外提供服务。例如,将一个基于 Spring MVC 框架开发的 Web 应用部署到 Tomcat 服务器上,用户就可以通过浏览器访问该应用的 URL 来使用其提供的功能。
二、搭建 Java Web 开发环境:步步为营
2.1 所需软件大盘点
- JDK(Java Development Kit):Java 开发工具包,是 Java 开发的核心,包含了 Java 运行时环境(JRE)、Java 编译器(javac)、Java 解释器(java)等一系列开发工具和 Java 的核心类库。它是开发和运行 Java 程序的基础,无论是简单的 Java 应用程序还是复杂的 Java Web 项目,都离不开 JDK 的支持。
- MyEclipse 或 IntelliJ IDEA:这两者都是强大的 Java 集成开发环境(IDE)。MyEclipse 是在 Eclipse 基础上开发的企业级集成开发环境,对 Java EE 和各种开源技术有很好的支持,提供了丰富的 Web 开发功能,如服务器集成、HTML/CSS/JavaScript 编辑器等,适合企业级应用开发。IntelliJ IDEA 则以其强大的代码编辑、智能代码补全、代码导航和重构等功能著称,拥有丰富的插件生态系统,能够极大地提高开发效率,在企业开发和大型项目中应用广泛,深受开发者喜爱。
- Tomcat:一个开源的轻量级应用服务器,由 Apache 软件基金会开发。它实现了 Java Servlet、JavaServer Pages(JSP)和 Java Expression Language(EL)等 Java 技术,是 Java Web 应用程序开发的重要组成部分。Tomcat 可以作为独立的 Web 服务器运行,处理 HTTP 请求并返回响应,同时也是一个 Servlet 容器,能够运行 Servlet 和 JSP,为 Java Web 应用提供了一个稳定、高效的运行环境,适合中小型系统和并发访问用户不多的场合。
- MySQL:一种流行的开源关系型数据库管理系统,由瑞典 MySQL AB 公司开发,现属于 Oracle 旗下产品。在 Web 应用方面,MySQL 以其体积小、速度快、总体拥有成本低,尤其是开源的特点,成为众多中小型网站开发的首选数据库。它使用 SQL 语言进行数据的存储、查询、更新和管理,能够高效地处理大量数据,为 Java Web 应用提供数据存储和管理的支持。
- Navicat for MySQL:一款强大的 MySQL 数据库管理和开发工具,为数据库管理员和开发人员提供了一套功能齐全的工具集。它基于 Windows 系统,提供了直观的图形用户界面(GUI),可以与任何 3.21 或以上版本的 MySQL 一起工作,并支持大部分的 MySQL 最新功能,包括触发器、存储过程、函数、事件、视图、管理用户等。使用 Navicat for MySQL,用户可以方便地创建、管理和维护 MySQL 数据库,进行数据的导入导出、备份恢复、结构同步等操作,大大提高了数据库管理的效率。
2.2 软件安装与配置全流程
- JDK 的安装与配置:首先,从 Oracle 官网下载适合操作系统的 JDK 安装包,下载完成后,双击安装包进行安装。在安装过程中,可以选择默认的安装路径,也可以根据个人需求自定义安装路径。安装完成后,需要配置环境变量。以 Windows 系统为例,右键点击 “此电脑”,选择 “属性”,在弹出的窗口中点击 “高级系统设置”,然后点击 “环境变量”。在系统变量中,新建一个变量名为 “JAVA_HOME”,变量值为 JDK 的安装路径;接着找到 “Path” 变量,点击 “编辑”,在变量值的开头添加 “% JAVA_HOME%\bin;”;再新建一个变量名为 “CLASSPATH”,变量值为 “.;% JAVA_HOME%\lib”。配置完成后,打开命令提示符,输入 “javac” 和 “java -version”,如果能正确显示相关信息,则说明 JDK 安装和配置成功。
- MyEclipse 的安装与配置:从 MyEclipse 官方网站下载安装包,下载后运行安装程序。安装过程中,按照提示逐步完成安装,包括选择安装路径、接受许可协议等步骤。安装完成后,首次启动 MyEclipse 时,会提示选择工作空间,工作空间用于存放项目文件和相关配置信息,可以根据自己的需求选择或创建一个新的工作空间。MyEclipse 默认已经集成了一些常用的插件和工具,但在开发 Java Web 项目时,可能还需要根据项目需求安装其他插件,如数据库驱动插件、代码生成插件等。可以通过 MyEclipse 的插件管理功能,在线或离线安装所需的插件。
- IntelliJ IDEA 的安装与配置:在 JetBrains 官网下载 IntelliJ IDEA 的安装包,有旗舰版(Ultimate Edition)和社区版(Community Edition)可供选择,旗舰版功能更全面,社区版免费但功能有所缩减,可根据个人需求选择下载。下载完成后,运行安装程序,按照安装向导的提示完成安装,选择安装路径、关联文件类型等。安装完成后启动 IntelliJ IDEA,首次启动时可以选择导入以前的设置,也可以使用默认设置。在创建 Java Web 项目之前,需要配置项目的 SDK(Software Development Kit),即指定项目使用的 JDK 版本。在 IntelliJ IDEA 的设置中,找到 “Project Structure”,在 “Project” 选项卡中选择正确的 JDK 版本。如果没有检测到已安装的 JDK,可以手动添加 JDK 的安装路径。IntelliJ IDEA 也拥有丰富的插件生态系统,可以根据开发需求安装各种插件,如代码检查插件、版本控制插件、数据库管理插件等。在设置中找到 “Plugins”,在插件市场中搜索并安装所需插件。
- Tomcat 的安装与配置:从 Apache Tomcat 官网下载 Tomcat 的压缩包,根据自己的需求选择合适的版本,如 Tomcat 8、Tomcat 9 等。下载完成后,将压缩包解压到指定的目录,解压后的目录即为 Tomcat 的安装目录。Tomcat 默认使用 8080 端口,可以根据实际情况修改端口号。打开 Tomcat 安装目录下的 “conf” 文件夹,找到 “server.xml” 文件,使用文本编辑器打开,在文件中找到类似 “” 的代码段,将 “port” 属性的值修改为需要的端口号,如 “80”(如果 80 端口未被占用,可直接修改为 80,这样访问 Web 应用时就不需要在 URL 中输入端口号)。配置完成后,启动 Tomcat。在 Tomcat 安装目录的 “bin” 文件夹下,找到 “startup.bat”(Windows 系统)或 “startup.sh”(Linux 系统),双击运行(Linux 系统需要赋予执行权限后再运行)。如果启动成功,会在命令行中看到 “Server startup in xxx ms” 的提示信息。此时,打开浏览器,输入 “http://localhost:8080/”(如果修改了端口号,将 8080 替换为修改后的端口号),如果能看到 Tomcat 的欢迎页面,则说明 Tomcat 安装和配置成功。
- MySQL 的安装与配置:从 MySQL 官网下载 MySQL 的安装包,根据操作系统和硬件环境选择合适的版本,如 Windows 64 位版本、Linux 版本等。下载完成后,运行安装程序,按照安装向导的提示进行安装,包括选择安装类型(如典型安装、自定义安装等)、设置安装路径、配置 MySQL 服务等步骤。在安装过程中,需要设置 root 用户的密码,务必牢记该密码,后续登录 MySQL 和管理数据库时会用到。安装完成后,配置 MySQL 的环境变量。在系统变量中,新建一个变量名为 “MYSQL_HOME”,变量值为 MySQL 的安装路径;然后找到 “Path” 变量,点击 “编辑”,在变量值中添加 “% MYSQL_HOME%\bin;”。配置完成后,打开命令提示符,输入 “mysql -u root -p”,然后输入设置的 root 用户密码,如果能成功进入 MySQL 命令行界面,则说明 MySQL 安装和配置成功。
- Navicat for MySQL 的安装与配置:从 Navicat 官网下载 Navicat for MySQL 的安装包,下载完成后,运行安装程序,按照安装向导的提示完成安装,包括选择安装路径、接受许可协议、选择安装组件等步骤。安装完成后,首次启动 Navicat for MySQL,需要进行注册或激活。如果是试用版,可以选择试用一定期限;如果购买了正版授权,可以输入授权信息进行激活。激活成功后,打开 Navicat for MySQL,点击 “连接” 按钮,选择 “MySQL”,在弹出的连接设置窗口中,填写连接名称(可自定义)、主机(通常为 “localhost”,如果 MySQL 安装在远程服务器上,则填写服务器的 IP 地址)、端口(默认 3306,如无特殊情况无需修改)、用户名(如 root)和密码(安装 MySQL 时设置的密码)。填写完成后,点击 “测试连接”,如果提示连接成功,则说明 Navicat for MySQL 与 MySQL 数据库连接配置成功,点击 “确定” 保存连接设置,即可通过 Navicat for MySQL 对 MySQL 数据库进行管理和操作 。
三、深入 Java Web 核心技术:Servlet 与 JSP
3.1 Servlet 详解
Servlet 作为 Java Web 的核心技术之一,是运行在服务器端的 Java 程序,主要用于处理客户端的 HTTP 请求并生成动态的 Web 内容。它实现了 Java EE 中的 Servlet 规范,能够在服务器上扩展应用程序的功能,为 Web 应用提供了强大的后端支持。
Servlet 的主要作用是充当客户端请求与服务器资源之间的桥梁。当客户端向服务器发送 HTTP 请求时,Servlet 容器(如 Tomcat)会接收该请求,并将其分配给相应的 Servlet 进行处理。Servlet 根据请求的内容,执行相应的业务逻辑,如查询数据库、处理表单数据等,然后生成动态的 HTML、XML 或其他格式的响应内容,返回给客户端。
以一个简单的用户注册功能为例,当用户在 Web 页面上填写注册信息并提交表单时,表单数据会以 HTTP 请求的形式发送到服务器。服务器上的 Servlet 接收到该请求后,会从请求中获取用户输入的注册信息,如用户名、密码、邮箱等。然后,Servlet 会对这些信息进行验证和处理,比如检查用户名是否已存在、密码是否符合强度要求等。如果信息验证通过,Servlet 会将用户信息插入到数据库中,并返回注册成功的提示页面给用户;如果验证失败,Servlet 则会返回包含错误信息的页面,提示用户重新填写。
接下来,我们通过一个简单的 Hello World 案例来快速入门 Servlet 开发。首先,创建一个 Java Web 项目,在项目中新建一个 Servlet 类,代码如下:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 设置响应内容类型为HTMLresponse.setContentType("text/html");// 获取输出流对象PrintWriter out = response.getWriter();// 输出HTML内容out.println("<html><body>");out.println("<h1>Hello, World!</h1>");out.println("</body></html>");}
}
在上述代码中,我们创建了一个名为HelloWorldServlet的 Servlet 类,它继承自HttpServlet类。@WebServlet("/hello")注解用于将该 Servlet 映射到/hello路径,即当客户端访问/hello时,会调用这个 Servlet。在doGet方法中,我们设置了响应内容类型为 HTML,并通过PrintWriter对象向客户端输出了一段 HTML 代码,显示 “Hello, World!”。
Servlet 的执行流程如下:
- 客户端向服务器发送 HTTP 请求,请求的 URL 中包含了 Servlet 的映射路径。
- 服务器接收到请求后,根据请求的 URL 找到对应的 Servlet。
- 如果 Servlet 尚未被加载,服务器会加载 Servlet 类,并创建一个 Servlet 实例。
- 服务器调用 Servlet 的init方法,对 Servlet 进行初始化,该方法只会在 Servlet 第一次被加载时执行一次。
- 服务器调用 Servlet 的service方法,根据请求的方法(如 GET、POST 等),调用相应的doGet、doPost等方法来处理请求。在处理请求过程中,Servlet 可以从请求对象中获取参数、请求头信息等,进行业务逻辑处理,并通过响应对象生成响应内容。
- 处理完请求后,service方法返回,Servlet 继续等待下一个请求。
- 当服务器关闭或 Servlet 需要被卸载时,服务器会调用 Servlet 的destroy方法,释放 Servlet 占用的资源。
Servlet 的生命周期包括初始化、服务和销毁三个阶段:
- 初始化阶段:在 Servlet 被加载到服务器内存时,服务器会创建一个 Servlet 实例,并调用其init方法。在init方法中,可以进行一些初始化操作,如读取配置文件、建立数据库连接等。init方法只在 Servlet 的生命周期中执行一次。
- 服务阶段:当有客户端请求到达时,服务器会调用 Servlet 的service方法,根据请求的方法类型,service方法会调用相应的doGet、doPost等方法来处理请求。这个阶段是 Servlet 处理业务逻辑的主要阶段,会被多次调用,处理不同的客户端请求。
- 销毁阶段:当服务器关闭或 Servlet 需要被卸载时,服务器会调用 Servlet 的destroy方法。在destroy方法中,可以进行一些资源释放操作,如关闭数据库连接、释放文件句柄等。destroy方法执行后,Servlet 实例被销毁,其占用的资源被释放。
Servlet 类中常用的方法有:
- init(ServletConfig config):初始化方法,在 Servlet 实例被创建后调用,用于完成 Servlet 的初始化工作,如获取 Servlet 的配置参数等。
- service(ServletRequest request, ServletResponse response):服务方法,用于处理客户端的请求。根据请求的方法类型,service方法会调用相应的doGet、doPost等方法。在service方法中,可以获取请求对象和响应对象,进行业务逻辑处理和响应生成。
- doGet(HttpServletRequest request, HttpServletResponse response):处理 HTTP GET 请求的方法。GET 请求通常用于从服务器获取数据,在doGet方法中,可以从请求对象中获取参数,查询数据库或执行其他业务逻辑,然后将结果返回给客户端。
- doPost(HttpServletRequest request, HttpServletResponse response):处理 HTTP POST 请求的方法。POST 请求通常用于向服务器提交数据,如表单数据等。在doPost方法中,可以从请求对象中获取提交的数据,进行数据验证和处理,然后将处理结果返回给客户端。
- destroy():销毁方法,在 Servlet 实例被销毁前调用,用于释放 Servlet 占用的资源,如关闭数据库连接、释放文件句柄等。
Servlet 的体系结构主要包括以下几个部分:
- Servlet 接口:所有 Servlet 都必须实现的接口,定义了 Servlet 的生命周期方法(init、service、destroy)以及获取 Servlet 配置信息的方法(getServletConfig)和获取 Servlet 信息的方法(getServletInfo)。
- GenericServlet 类:实现了 Servlet 接口的抽象类,提供了与协议无关的 Servlet 实现。它将 Servlet 接口中的方法进行了一些默认实现,使得开发者在创建 Servlet 时可以继承GenericServlet类,只需重写service方法即可,无需实现所有的 Servlet 接口方法。
- HttpServlet 类:继承自GenericServlet类,专门用于处理 HTTP 请求的 Servlet。它提供了doGet、doPost等方法来处理不同类型的 HTTP 请求,开发者在创建 HTTP Servlet 时,通常继承HttpServlet类,并根据需要重写doGet、doPost等方法,而无需直接实现service方法。
在配置 Servlet 的映射路径时,可以使用@WebServlet注解或者在web.xml文件中进行配置。使用@WebServlet注解的方式比较简洁,如上述HelloWorldServlet类中的@WebServlet("/hello"),将 Servlet 映射到/hello路径。在web.xml文件中配置的方式如下:
<web-app><servlet><servlet-name>HelloWorldServlet</servlet-name><servlet-class>com.example.HelloWorldServlet</servlet-class></servlet><servlet-mapping><servlet-name>HelloWorldServlet</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping>
</web-app>
在上述配置中,<servlet>标签用于定义一个 Servlet,<servlet-name>指定 Servlet 的名称,<servlet-class>指定 Servlet 类的全限定名。<servlet-mapping>标签用于将 Servlet 映射到一个 URL 路径,<servlet-name>必须与<servlet>标签中的<servlet-name>一致,<url-pattern>指定映射的 URL 路径。
3.2 JSP 探秘
JSP(JavaServer Pages)是一种基于 Java Servlet 及 Java 平台的动态网页技术,它允许将 Java 代码嵌入到 HTML 页面中,使得页面能够根据不同的请求动态生成内容。JSP 本质上是 Servlet 的一种变体,在运行时会被编译成 Servlet,然后由 Servlet 容器来执行。
JSP 与 Servlet 有着密切的关系,它们都是 Java Web 开发中的重要技术。JSP 可以看作是 Servlet 的一种简化形式,它更侧重于页面的展示,将动态内容的生成与 HTML 页面的编写结合在一起,使得开发者可以更方便地创建动态网页。而 Servlet 则更侧重于业务逻辑的处理,负责接收请求、处理业务逻辑,并将处理结果传递给 JSP 进行页面展示。在实际的 Java Web 应用开发中,通常会将 JSP 和 Servlet 结合使用,利用 Servlet 处理业务逻辑,JSP 负责页面的显示,以实现 MVC(Model - View - Controller)设计模式,提高代码的可维护性和可扩展性。
下面我们来看一个简单的 JSP 页面示例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head><title>JSP Example</title>
</head>
<body><h1>Welcome to JSP!</h1><p>Today is <%= new java.util.Date() %></p>
</body>
</html>
在上述 JSP 页面中,<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>是 JSP 的指令标签,用于定义页面的属性,如使用的语言、内容类型和编码格式等。<h1>Welcome to JSP!</h1>是普通的 HTML 标签,用于在页面上显示标题。<p>Today is <%= new java.util.Date() %></p>中,<%= new java.util.Date() %>是 JSP 的表达式,用于在页面上输出 Java 代码的执行结果,这里输出当前的日期和时间。
JSP 的语法元素主要包括以下几种:
1.指令(Directives):用于定义 JSP 页面的全局属性和行为,如<%@ page %>用于定义页面的基本属性,<%@ include %>用于在 JSP 页面中包含其他文件,<%@ taglib %>用于引入自定义标签库等。
2.脚本元素(Scripting Elements):
- 表达式(Expressions):以<%= %>形式出现,用于在页面上输出 Java 表达式的结果,如上述示例中的<%= new java.util.Date() %>。
- 脚本段(Scriptlets):以<% %>形式出现,用于在 JSP 页面中嵌入 Java 代码块,可以包含多行 Java 代码,进行复杂的业务逻辑处理。例如:
<%int num1 = 5;int num2 = 3;int sum = num1 + num2;
%>
<p>The sum of <%= num1 %> and <%= num2 %> is <%= sum %></p>
声明(Declarations):以<%! %>形式出现,用于在 JSP 页面中声明变量、方法或类,这些声明的内容会被编译成 Servlet 类的成员。例如:
<%!public int add(int a, int b) {return a + b;}
%>
<p>The result of 5 + 3 is <%= add(5, 3) %></p>
3.动作(Actions):以<jsp:xxx>形式出现,用于在 JSP 页面中执行特定的操作,如<jsp:forward>用于将请求转发到另一个资源,<jsp:include>用于动态包含另一个资源,<jsp:useBean>用于创建和使用 JavaBean 对象等。例如,使用<jsp:forward>将请求转发到另一个 JSP 页面:
<jsp:forward page="another.jsp" />
JSP 还提供了一些内置对象,这些对象可以在 JSP 页面中直接使用,无需显式声明:
1.request:类型为HttpServletRequest,代表客户端的请求对象,用于获取客户端发送的请求参数、请求头信息等。例如,获取表单提交的用户名参数:
<%String username = request.getParameter("username");
%>
2.response:类型为HttpServletResponse,代表服务器的响应对象,用于向客户端发送响应内容、设置响应头信息等。例如,设置响应内容类型为 JSON:
<%response.setContentType("application/json");
%>
3.application:类型为ServletContext,代表整个 Web 应用的上下文对象,用于在整个应用中共享数据。例如,获取应用的初始化参数:
<%String initParam = application.getInitParameter("appParam");
%>
4.out:类型为JspWriter,用于向客户端输出内容,相当于PrintWriter的一个子类,但提供了更多的功能,如自动缓冲等。例如,输出一段文本:
<%out.println("This is a message from JSP.");
%>
5.pageContext:类型为PageContext,代表当前 JSP 页面的上下文对象,用于管理 JSP 页面的属性、获取其他内置对象等。例如,获取request对象:
<%HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
%>
6.config:类型为ServletConfig,代表 Servlet 的配置对象,用于获取 Servlet 的初始化参数等。例如,获取 Servlet 的初始化参数:
<%String initParam = config.getInitParameter("servletParam");
%>
7.page:代表当前 JSP 页面本身,相当于 Java 中的this关键字。
8.exception:类型为Throwable,用于处理 JSP 页面中的异常。只有在page指令中设置了isErrorPage="true"时,才可以使用该对象。例如,在错误页面中输出异常信息:
<%@ page isErrorPage="true" %>
<%exception.printStackTrace(out);
%>
3.3 Servlet 与 JSP 交互案例实操
为了更深入地理解 Servlet 与 JSP 的交互过程,我们通过一个用户登录的案例来进行实操。在这个案例中,Servlet 负责处理用户登录的业务逻辑,验证用户输入的用户名和密码是否正确;JSP 则用于展示登录页面和登录结果。
首先,创建一个 Java Web 项目,项目结构如下:
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── LoginServlet.java
│ └── webapp
│ ├── login.jsp
│ ├── success.jsp
│ └── WEB-INF
│ └── web.xml
接下来,我们分别看一下各个文件的代码:
1.login.jsp:登录页面,用于收集用户输入的用户名和密码。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head><title>User Login</title>
</head>
<body><h2>User Login</h2><form action="login" method="post"><label for="username">Username:</label><br><input type="text" id="username" name="username" required><br><label for="password">Password:</label><br><input type="password" id="password" name="password" required><br><br><input type="submit" value="Login"></form>
</body>
</html>
在上述代码中,<form action="login" method="post">表示将表单数据以 POST 方式提交到login路径,该路径会映射到LoginServlet。
2.LoginServlet.java:处理用户登录的 Servlet。
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;@WebServlet("/login")
public class LoginServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 获取用户输入的用户名和密码String username = request.getParameter("username");String password = request.getParameter("password");// 模拟数据库验证,这里假设用户名和密码都为admin时验证通过if ("admin".equals(username) && "admin".equals(password)) {// 验证成功,将用户名存入会话中HttpSession session = request.getSession();session.setAttribute("username", username);// 转发到success.jsp页面request.getRequestDispatcher("success.jsp").forward(request, response);} else {// 验证失败,返回登录页面并显示错误信息request.setAttribute("error", "Invalid username or password");request.getRequestDispatcher("login.jsp").forward(request, response);}}
}
在LoginServlet中,doPost方法接收用户提交的表单数据,验证用户名
相关文章:
Java Web从入门到精通:全面探索与实战(一)
目录 引言:开启 Java Web 之旅 一、Java Web 基础概念大揭秘 1.1 什么是 Java Web 1.2 Java Web 的优势剖析 1.3 Java Web 相关核心概念详解 二、搭建 Java Web 开发环境:步步为营 2.1 所需软件大盘点 2.2 软件安装与配置全流程 三…...
5G从专家到小白
文章目录 第五代移动通信技术(5G)简介应用场景 数据传输率带宽频段频段 VS 带宽中低频(6 GHz以下):覆盖范围广、穿透力强高频(24 GHz以上):满足在热点区域提升容量的需求毫米波热点区…...
leetcode111 二叉树的最小深度
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。 最小深度的定义:从根节点到最近叶子节点的最短路径上的节点数量。 特别注意: 如果一个子树不存在,就不能用它来计算深度&#x…...
算法设计学习10
实验目的及要求: 本查找实验旨在使学生深入了解不同查找算法的原理、性能特征和适用场景,培养其在实际问题中选择和应用查找算法的能力。通过实验,学生将具体实现多种查找算法,并通过性能测试验证其在不同数据集上的表现ÿ…...
数字统计题解
题目理解 题目要求计算所有不大于 N 的非负整数中数字 D 出现的总次数。例如,当 D1 且 N12 时,数字1出现在1、10、11(两次)、12中,共5次。 输入输出分析 输入格式: 两个正整数 D 和 N,其中1≤…...
eclipse导入工程提示Project has no explicit encoding set
eclipse导入工程提示Project has no explicit encoding set-CSDN博客...
【网络安全论文】筑牢局域网安全防线:策略、技术与实战分析
【网络安全论文】筑牢局域网安全防线:策略、技术与实战分析 简述一、引言1.1 研究背景1.2 研究目的与意义1.3 国内外研究现状1.4 研究方法与创新点二、局域网网络安全基础理论2.1 局域网概述2.1.1 局域网的定义与特点2.1.2 局域网的常见拓扑结构2.2 网络安全基本概念2.2.1 网络…...
JVM虚拟机篇(五):深入理解Java类加载器与类加载机制
深入理解Java类加载器与类加载机制 深入理解Java类加载器与类加载机制一、引言二、类加载器2.1 类加载器的定义2.2 类加载器的分类2.2.1 启动类加载器(Bootstrap ClassLoader)2.2.2 扩展类加载器(Extension ClassLoader)2.2.3 应用…...
纯个人整理,蓝桥杯使用的算法模板day4(图论 最小生成树问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个
目录 最小生成树Prim代码模拟流程图 kruskal代码 代码对应实现案例 最小生成树 最小生成树:在无向图中求一棵树(n-1条边,无环,连通所有点),而且这棵树的边权和最小 (ps:可能结果不止…...
学习笔记,DbContext context 对象是保存了所有用户对象吗
DbContext 并不会将所有用户对象保存在内存中: DbContext 是 Entity Framework Core (EF Core) 的数据库上下文,它是一个数据库访问的抽象层它实际上是与数据库的一个连接会话,而不是数据的内存缓存当您通过 _context.Users 查询数据时&…...
Kafka 和 Flink的讲解
一、Kafka:分布式消息队列 1. 核心概念 角色:Kafka 是一个分布式、高吞吐量的消息队列(Pub-Sub 模型),用于实时传输数据流。关键术语: Producer(生产者&…...
Kafka 高吞吐量的原因是什么?
Kafka 的高吞吐量是它成为“数据中枢”的关键特性之一,这背后是多个技术设计的巧妙配合。下面我给你整理一下 Kafka 高吞吐量的主要原因,通俗又系统。 ✅ 1. 顺序写磁盘(磁盘也能飞) Kafka 的消息写入是追加到日志末尾ÿ…...
基于Python+Flask的服装零售商城APP方案,用到了DeepSeek AI、个性化推荐和AR虚拟试衣功能
首先创建项目结构: fashion_store/ ├── backend/ │ ├── app/ │ │ ├── __init__.py │ │ ├── models/ │ │ ├── routes/ │ │ ├── services/ │ │ └── utils/ │ ├── config.py │ ├── requirements.t…...
26.[MRCTF2020]Transform 1
打开文件是可执行程序.exe,打开看一下,顺便拖入ExeinfoPE 查询一下基本信息。如图。 无壳,且是64-bit,打开执行文件也没有什么特别的信息。那就 IDA-x64 分析吧。 🆗,简单的一个加密,我们直接逆…...
LeetCode-98. 验证二叉搜索树
一、题目 给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的…...
【LeetCode Solutions】LeetCode 146 ~ 150 题解
CONTENTS LeetCode 146. LRU 缓存(中等)LeetCode 147. 对链表进行插入排序(中等)LeetCode 148. 排序链表(中等)LeetCode 149. 直线上最多的点数(困难)LeetCode 150. 逆波兰表达式求值…...
leetcode二叉树刷题调试不方便的解决办法
1. 二叉树不易构建 在leetcode中刷题时,如果没有会员就需要将代码拷贝到本地的编译器进行调试。但是leetcode中有一类题可谓是毒瘤,那就是二叉树的题。 要调试二叉树有关的题需要根据测试用例给出的前序遍历,自己构建一个二叉树,…...
【家政平台开发(16)】消息通知系统设计:搭建高效沟通桥梁
本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化…...
AI比人脑更强,因为被植入思维模型【44】成长破圈思维
giszz的理解:芒格说,不懂不投。我们一生都在追求破圈,突破本我,突破舒适圈、恐惧圈、学习圈、成长圈、自在圈,可是我们真正能懂的知识有实际真的太少了。这个思维模型给我的启迪,一是要破圈,二是…...
【JavaWeb-Spring boot】学习笔记
目录 <<回到导览Spring boot1. http协议1.1.请求协议1.2.响应协议 2.Tomcat2.1.请求2.1.1.apifox2.1.2.简单参数2.1.3.实体参数2.1.4.数组集合参数2.1.5.日期参数2.1.6.(重点)JSON参数2.1.7.路径参数 2.2.响应2.3.综合练习 3.三层架构3.1.三层拆分3.2.分层解耦3.3.补充 &…...
基于GitLab+Jenkins的持续集成实践指南
架构设计原则 分层自动化策略 基础层: 代码提交触发自动构建(100%自动化)中间层: 制品生成与验证(自动化+人工审核)发布层: 环境部署(受控手动触发)工具定位矩阵 工具核心职责关键优势GitLab源码管理+MR流程精细的权限控制Jenkins流水线编排+任务调度插件生态丰富Nexus制…...
用HTML.CSS.JavaScript实现一个贪吃蛇小游戏
目录 一、引言二、实现思路1. HTML 结构2. CSS 样式3. JavaScript 逻辑 三、代码实现四、效果展示 一、引言 贪吃蛇是一款经典的小游戏,曾经风靡一时。今天,我们将使用 HTML、CSS 和 JavaScript 来实现一个简单的贪吃蛇小游戏。通过这个项目,…...
医疗思维图与数智云融合:从私有云到思维图的AI架构迭代(代码版)
医疗思维图作为AI架构演进的重要方向,其发展路径从传统云计算向融合时空智能、大模型及生态开放的“思维图”架构迭代,体现了技术与场景深度融合的趋势。 以下是其架构迭代的核心路径与关键特征分析: 一、从“智慧云”到“思维图”的架构演进逻辑 以下是针对医疗信息化领域…...
Kafka 中,为什么同一个分区只能由消费者组中的一个消费者消费?
在 Kafka 中,同一个分区只能由消费者组中的一个消费者消费,这是 Kafka 的设计决策之一,目的是保证消息的顺序性和避免重复消费。这背后有几个关键的原因: 1. 保证消息顺序性 Kafka 中的每个 分区(Partitionÿ…...
Kafka 中的批次
在 Kafka 中,批次(Batch) 是生产者发送消息的一个重要概念。它对 Kafka 的性能、吞吐量、延迟等有很大影响。批量处理可以使消息发送更高效,减少网络往返和磁盘写入的开销。 下面我将详细解释 Kafka 中的批次机制,包括…...
《UNIX网络编程卷1:套接字联网API》第7章:套接字选项深度解析
《UNIX网络编程卷1:套接字联网API》第7章:套接字选项深度解析 一、套接字选项核心原理 1.1 选项层级体系 套接字选项按协议层级划分(图1): SOL_SOCKET:通用套接字层选项IPPROTO_IP:IPv4协议层…...
使用 pytest-xdist 进行高效并行自化测试
pytest-xdist 是 pytest 的一个扩展插件,主要用于实现测试用例的并行执行和分布式测试。通过利用多进程或者多机分布式的方式,pytest-xdist 能够显著缩短测试执行时间,提升持续集成(CI)流程的效率。 在自动化测试中&a…...
谈谈策略模式,策略模式的适用场景是什么?
一、什么是策略模式? 策略模式(Strategy Pattern)属于行为型设计模式。核心思路是将一组可替换的算法封装在独立的类中,使它们可以在运行时动态切换,同时使客户端代码与具体算法解耦。它包含三个…...
网络安全防御核心原则与实践指南
一、四大核心防御原则 A. 纵深防御原则(Defense in Depth) 定义:通过在多个层次(如网络、系统、应用、数据)设置互补的安全措施,形成多层次防护体系。 目的:防止单一漏洞导致整体安全失效&…...
动态规划2——斐波那契数列模型——三步问题
1.题目 三步问题。有个小孩正在上楼梯,楼梯有 n 阶台阶,小孩一次可以上 1 阶、2 阶或 3 阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模 1000000007。 示例 1: 输入:n 3 …...
周末总结(2024/04/05)
工作 人际关系核心实践: 要学会随时回应别人的善意,执行时间控制在5分钟以内 坚持每天早会打招呼 遇到接不住的话题时拉低自己,抬高别人(无阴阳气息) 朋友圈点赞控制在5min以内,职场社交不要放在5min以外 职场的人际关系在面对利…...
常见的图像生成算法
综合技术原理、优化方向和应用场景,结合经典模型与前沿进展进行分述: 一、经典生成模型 1. 生成对抗网络(GAN) 原理:由生成器(Generator)和判别器(Discriminator)通过…...
PE结构(十五)系统调用与函数地址动态寻找
双机调试 当需要分析一个程序时,这个程序一定是可以调试的,操作系统也不例外。在调试过程中下断点是很重要的 当我们对一个应用程序下断点时,应用程序是挂起的。但当我们对操作系统的内核程序下断点时,被挂起的不是内核程序而是…...
verilog状态机思想编程流水灯
目录 一、状态机1. 状态机基本概念2. 状态机类型3. Verilog 状态机设计要点 二、状态机实现一个1s流水灯三、DE2-115实物演示 一、状态机 1. 状态机基本概念 状态机(Finite State Machine, FSM)是数字电路设计中用于描述系统状态转换的核心组件&#x…...
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
N皇后问题要求在NN的棋盘上放置N个皇后,使得她们无法互相攻击。本文提供递归和循环迭代两种解法,并通过图示解释核心逻辑。 一、算法核心思想 使用回溯法逐行放置皇后,通过冲突检测保证每行、每列、对角线上只有一个皇后。发现无效路径时回退…...
#SVA语法滴水穿石# (000)断言基本概念和背景
一、前言 随着数字电路规模越来越大、设计越来越复杂,使得对设计的功能验证越来越重要。首先,我们要明白为什么要对设计进行验证?验证有什么作用?例如,在用FPGA进行设计时,我们并不能确保设计出来的东西没有功能上的漏洞,因此在设计后我们都会对其进行验证仿真。换句话说…...
【Android Studio 下载 Gradle 失败】
路虽远行则将至,事虽难做则必成 一、事故现场 下载Gradle下载不下来,没有Gradle就无法把项目编译为Android应用。 二、问题分析 观察发现下载时长三分钟,进度条半天没动,说明这个是国外的东西,被墙住了,需…...
贪心算法之Huffman编码
1. 算法推理 Huffman 编码的目标是为给定字符构造一种前缀码,使得整体编码长度最短。基本思想是: 贪心选择:每次选择频率最小的两个节点合并。合并后的节点的权值为两个子节点权值之和,代表这部分子树出现的总频率。 局部最优导…...
Flask学习笔记 - 表单
表单处理 基本表单处理:使用 request.form 获取表单数据。使用 Flask-WTF:结合 WTForms 进行表单处理和验证,简化表单操作。表单验证:使用验证器确保表单数据的有效性。文件上传:处理文件上传和保存文件。CSRF 保护&a…...
指针的补充(用于学习笔记的记录)
1.指针基础知识 1.1 指针变量的定义和使用 指针也是一种数据类型,指针变量也是一种变量 指针变量指向谁,就把谁的地址赋值给指针变量 #include<stdio.h>int main() {int a 0;char b 100;printf("%p,%p \n", &a,&b); // …...
【mongodb】mongodb的字段类型
目录 1. 基本数据类型1.1 String1.2 Number1.3 Boolean1.4 Date1.5 Null1.6 ObjectId1.7 Array1.8 Binary Data1.9 Object 2. 特殊数据类型2.1 Regular Expression2.2 JavaScript2.3 Symbol2.4 Decimal1282.5 Timestamp2.6 MinKey/MaxKey2.7 DBPointer 3. 常用字段类型示例4. 注…...
计算机视觉图像处理基础系列:滤波、边缘检测与形态学操作
计算机视觉图像处理基础系列:滤波、边缘检测与形态学操作 一、前言二、滤波:图像的精细化处理2.1 滤波基础概念2.1.1 滤波的本质2.1.2 图像噪声来源与类型 2.2 线性滤波2.2.1 均值滤波2.2.2 高斯滤波 2.3 非线性滤波2.3.1 中值滤波 三…...
实用的alias别名命令——比2=1+1简单的基础命令
目录 alias命令的用处alias命令的写法让alias别名永久存在的办法下篇预告 alias命令的用处 别名,就是linux系统中的命令的别称,而alias命令,可以显示linux系统当前设定的全部别名,当然,也可以自己定义一个别名。 ali…...
JAVA单例模式
目录 一、什么是单例模式: 二、饿汉模式: 代码示例: 饿汉模式的特点: 三、懒汉模式: 正确代码示例(双重检查锁): 一、什么是单例模式: 一个类,在语法角度…...
k8s安装cri驱动创建storageclass动态类
部署nfs服务器 #所有k8s节点安装nfs客户端 yum install -y nfs-utils mkdir -p /nfs/share echo "/nfs/share *(rw,sync,no_root_squash)" >> /etc/exports systemctl enable --now nfs-serverhelm部署nfs的provisioner&sc 所有k8s节点安装客户端 yu…...
嵌入式Linux驱动—— 1 GPIO配置
目录 1.GPIO操作 1.1 IO命名 1.2 GPIO 时钟使能(CCM) 1.3 IO 复用(IOMUXC) 1.4 IO 配置 1.5 GPIO 配置 1.GPIO操作 GPIO操作主要是以下流程: 使能某组GPIO模块(GPIO1、2、...)&#…...
【C++11】lambda
lambda lambda表达式语法 lambda表达式本质是一个匿名函数对象,跟普通函数不同的是它可以定义在函数内部。lambda表达式语法使用层而言没有类型,所以一般是用auto或者模板参数定义的对象去接收lambda对象。 lambda表达式的格式:[capture-l…...
自旋锁(C++实现)
1 简介 自旋锁是一种典型的无锁算法,在任何时刻,它都最多只能有一个保持者。当有一个线程试图获得一个已经被占用的锁时,该线程就会一直进行循环等待,直到该锁被释放。自旋锁的优点是不会使线程状态发生切换,一直处于用…...
python基础-11-调试程序
文章目录 【README】【11】调试【11.1】抛出异常【11.1.1】抛出异常代码块 【11.2】获取回溯字符串【11.2.1】traceback获取异常回溯信息 【11.3】断言【11.3.1】断言代码示例 【11.4】日志(使用logging模块)【11.4.1】使用logging模块操作日志【11.4.3】…...
我的创作历程:从不情愿到主动分享的成长
🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 二、转变:从被动到主动的心路历程 三、挑战:时间的压力与写作的坚持 四、收获:分享与成长 五、展望…...