Servlet 详解 - 指南

news/2025/11/12 8:15:56/文章来源:https://www.cnblogs.com/yangykaifa/p/19212170

Servlet 详解

一、Servlet 介绍

1.1 什么是 Servlet

Servlet(Server Applet 的缩写,全称 Java Servlet): 是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。狭义的 Servlet 是指 Java 语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类,一般情况下,人们将 Servlet 理解为后者。

Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。

Servlet 是一种实现动态页面的技术,是一组由 Tomcat 提供给程序员的 API,帮助程序员简单高效的开发一个 web app

1.2 Servlet 的主要工作

1.3 Tomcat

在此之前先简单了解一下Tomcat的架构,看看Servlet到底在Tomcat中的什么地方。

一个 Tomcat 实例(JVM)通常包含一个顶级的 Server 组件。一个 Server 可以包含多个 Service。每个 Service 由一个或多个 Connector 和一个 Container(具体是 Engine)组成。

  • Connector 负责监听端口、处理 I/O 操作和协议解析。
  • Engine 是顶层的 Servlet 容器,代表整个 Servlet 引擎。
  • 一个 Engine 可以管理多个 Host(虚拟主机)。
  • 一个 Host 可以部署多个 Context(Web 应用程序)。
  • 一个 Context 包含多个 Wrapper

最关键的一点是Wrapper 是 Tomcat 中用于管理 Servlet 的最底层容器。每一个 Wrapper 实例都对应并管理着一个特定的 Servlet 类。当请求到达时,Tomcat 会根据 URL 找到对应的 Context,再找到对应的 Wrapper,最后由 Wrapper 调用其管理的 Servlet 的 service() 方法来处理请求。

在这里插入图片描述
在这里插入图片描述

1.4 Servlet 运行流程

口述:

客户端发送报文,到tomcat,tomcat接收报文,并创建HttpServletRequest对象,将报文信息存入req对象中,根据请求路径将对象传递给对应的servlet进行处理,处理后的信息,写入HttpServletResponse对象中,并封装成报文,发送给客户端。

看图理解:

在这里插入图片描述

专业一点说就是:

客户端发起的 HTTP 请求由 Tomcat 的 Connector 接收并解析,Connector 创建 HttpServletRequestHttpServletResponse 对象,封装请求数据。请求经由 Container 层级(Engine/Host/Context)路由至匹配的 Servlet。Servlet 通过 HttpServletRequest 获取输入,执行业务逻辑后,将响应数据写入 HttpServletResponse。最终,Connector 将响应对象序列化为 HTTP 响应报文并返回给客户端。

流程化:

HTTP 请求在 Tomcat 中的处理流程

  1. 请求接收:客户端(如 Web 浏览器)向 Tomcat 服务器发起 HTTP 请求,发送原始的 HTTP 报文。
  2. 报文解析与对象创建:Tomcat 的 Connector 组件监听网络端口,接收原始字节流,解析 HTTP 协议。在请求处理初期,由 Coyote 子系统创建并初始化 HttpServletRequestHttpServletResponse 的具体实现对象,并将请求报文中的请求行、请求头、请求体等信息封装到 HttpServletRequest 对象中。
  3. 请求分发Connector 根据请求的 URL 路径,通过 Service 将请求委派给对应的 ContainerEngineHostContext),最终定位到处理该请求的 Servlet。
  4. 业务处理:请求被传递至目标 Servlet 的 service() 方法(或 doGet()/doPost() 等具体方法)。Servlet 通过 HttpServletRequest 获取请求参数、头信息等数据,执行业务逻辑。
  5. 响应生成:Servlet 利用 HttpServletResponse 对象设置响应状态码、响应头,并通过其输出流(如 PrintWriterServletOutputStream)将响应内容(如 HTML、JSON 等)写入响应体。
  6. 响应返回:请求处理完成后,Connector 获取 HttpServletResponse 中的内容,将其序列化为符合 HTTP 协议的响应报文,通过网络发送回客户端。

在这里插入图片描述

HttpServletRequestHttpServletResponse 对象是由 Connector 组件在接收到客户端的原始网络请求后创建

二、Servlet使用方法

1. 一个简单的 Servlet 示例

public class Servlet1 extends HttpServlet {
//重写service方法 执行具体的业务
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet1执行了");
}
}

仅仅有一个 Servlet 类的 .class 文件放在服务器上是不够的。Servlet 容器(如 Tomcat)必须知道这个类的存在,并且知道当某个特定的 URL 请求到来时,应该调用哪个 Servlet 来处理。

这就需要进行 注册(Registration)和映射(Mapping)

2.注册(Registration)和映射(Mapping)

2.1 使用部署描述符 web.xml(传统方式)

需要在 Web 应用的 WEB-INF/web.xml 文件中进行配置:

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 1. 声明一个 Servlet 组件 --><servlet><servlet-name>Servlet1</servlet-name> <!-- 给 Servlet 起个名字 --><servlet-class>com.example.Servlet1</servlet-class> <!-- 指定完整的类名 --></servlet><!-- 2. 将 Servlet 名字映射到一个 URL 路径 --><servlet-mapping><servlet-name>Servlet1</servlet-name><url-pattern>/s1</url-pattern> <!-- 访问 /s1 路径时,调用 Servlet1 --></servlet-mapping></web-app>
2.2 使用注解 @WebServlet
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet1执行了");
}
}
  • @WebServlet("/s1") 注解告诉 Servlet 容器:“这个类是一个 Servlet,并且可以通过 /s1 这个 URL 访问它。”
  • 容器在启动时会自动扫描带有 @WebServlet 注解的类,并完成注册和映射。

继承 HttpServlet 是创建 Servlet 的技术基础,但要让这个 Servlet 真正生效并能被外部访问,还必须通过 web.xml@WebServlet 注解等方式完成注册和 URL 映射。只有这样,Servlet 容器才能在收到请求时,正确地加载、初始化并调用你的 Servlet。

三、Servlet 生命周期(Life Cycle)

阶段方法触发时机执行次数
1. 实例化构造器 new Servlet()第一次请求服务器启动时(若配置 load-on-startup1 次
2. 初始化init(ServletConfig config)构造器执行完毕后自动调用1 次
3. 服务service(ServletRequest, ServletResponse)每次客户端请求到达时调用多次
4. 销毁destroy()Web 应用停止或服务器关闭时调用1 次
  • Servlet 在 Tomcat 中是单例模式(一个 Servlet 类只有一个实例)。
  • 多个客户端请求由多个线程调用同一个 Servlet 实例的 service 方法。
  • 成员变量是共享的,在 service 方法中修改成员变量可能导致线程安全问题,应避免。

四、Servlet 核心类继承体系

在这里插入图片描述

1、顶级 interface Servlet

public interface Servlet {
// 初始化方法,由Tomcat自动调用完成初始化
void init(ServletConfig var1) throws ServletException;
// 获得Servlet配置对象
ServletConfig getServletConfig();
// 接受用户请求,用于响应信息的核心方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
// 返回Servlet描述信息
String getServletInfo();
// Servlet回收前的销毁方法,用于资源释放
void destroy();
}

2、抽象类 abstract class GenericServlet

public abstract class GenericServlet implements Servlet, ServletConfig {
private transient ServletConfig config;
// 平庸实现destroy方法
public void destroy() {}
// 存储config对象并调用无参init方法
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init(); // 调用重载的无参init
}
// 重载的初始化方法,供子类重写
public void init() throws ServletException {}
// 抽象service方法,留给子类实现
public abstract void service(ServletRequest var1, ServletResponse var2);
}

3、抽象类 abstract class HttpServlet

public abstract class HttpServlet extends GenericServlet {
//参数的父转子, 调用重载service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//参数的父转子
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//调用重载service方法
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的方式
String method = req.getMethod();//GET POST PUT DELETE OPTIONS......
//根据请求方法,去调用对应的do...方法
if (method.equals("GET")) {
this.doGet(req, resp);
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
resp.sendError(501, errMsg);
}
}
//故意响应405
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
//故意响应405请求方式不允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
//故意响应405
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
//故意响应405请求方式不允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
//故意响应405请求方式不允许的信息
resp.sendError(405, msg);
}
}

五、Servlet API 详解

对于 Servlet 主要介绍三个类,分别是 HttpServlet、HttpServletRequest 和 HttpServletResponse。

其中 HttpServletRequest 和 HttpServletResponse 是 Servlet 规范中规定的两个接口,HttpServlet 中并没有实现这两个接口的成员变量,它们只是 HttpServlet 的 service 和 doXXX 等方法的参数。这两个接口类的实例化是在 Servlet 容器中实现的。

5.1 HttpServlet

核心方法

方法名称调用时机
init在HttpServlet实例化之后被调用一次
destory在HttpServlet实例不再使用的时候调用一次
service收到HTTP请求的时候调用
doGet收到GET请求的时候调用(由service方法调用)
doPost收到POST请求的时候调用(由service方法调用)
doPut/doDelete/doOptions收到其他对应请求的时候调用(由service方法调用)
public abstract class HttpServlet extends GenericServlet {
//参数的父转子, 调用重载service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//参数的父转子
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//调用重载service方法
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的方式
String method = req.getMethod();//GET POST PUT DELETE OPTIONS......
//根据请求方法,去调用对应的do...方法
if (method.equals("GET")) {
this.doGet(req, resp);
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
resp.sendError(501, errMsg);
}
}
//故意响应405
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
//故意响应405请求方式不允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
//故意响应405
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
//故意响应405请求方式不允许的信息
this.sendMethodNotAllowed(req, resp, msg);
}
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
//故意响应405请求方式不允许的信息
resp.sendError(405, msg);
}
}
1 部分程序员推荐在servlet中重写do***方法处理请求,
理由:service方法中可能做了一些处理,如果我们直接重写service的话,父类中的service方法处理可能会失效
2 目前直接重写service也没有什么影响
3 后续使用了SpringMVC框架后,我们则无需继承HttpServlet类,处理请求的方法也无需是 do*** service
4 如果doGet  和  doPost 方法中,我们定义的代码都一样,可以让一个方法直接调用另一个方法
继承HttpServlet类,要么重写service方法,要么重写 doGet/doPost方法

5.2 HttpServletRequest

5.2.1 Http请求结构
请求行  GET /path HTTP/1.1
请求头  Key: ValueKey: Value
[空行]
请求体  username=Tom&password=123

在这里插入图片描述

对应 Java Servlet 中的处理方式:

  • 请求行HttpServletRequest 方法
  • 请求头req.getHeader()
  • 请求体req.getParameter()req.getReader()req.getInputStream()
5.2.2 请求行相关 API
方法说明
req.getMethod()获取请求方式(GET、POST、PUT、DELETE 等)
req.getScheme()获取请求协议(如 httphttps
req.getProtocol()获取协议及版本(如 HTTP/1.1
req.getRequestURI()获取请求的 URI(项目内资源路径,如 /Servlet6
req.getRequestURL()获取完整的请求 URL(含协议、域名、端口、路径)
req.getLocalPort()获取服务器应用监听的端口(如 8080)
req.getServerPort()获取客户端发送请求时使用的服务器端口
req.getRemotePort()获取客户端软件(浏览器)的端口号

URI vs URL

  • URI(统一资源标识符):标识资源,如 /user/login
  • URL(统一资源定位符):具体地址,如 http://localhost:8080/user/login
@WebServlet("/Servlet6")
public class Servlet6 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//行相关  GET/POST  URI HTTP/1.1
System.out.println(req.getMethod());//获取请求方式
System.out.println(req.getScheme());//获取请求协议
System.out.println(req.getProtocol());//获取请求协议以及版本
System.out.println(req.getRequestURI());//获取请求的URI  项目内的资源路径
System.out.println(req.getRequestURL());//获取请求的URL  项目内资源的完整路径
/**
* URI  统一资源表示符 interface URI{}             资源定位的要求和规范
* URL  统一资源定位符 class URL implements URI{}  一个具体的资源路径
* */
System.out.println(req.getLocalPort());//本应用容器的端口号 8080
System.out.println(req.getServerPort());//客户端发请求时使用的端口号
System.out.println(req.getRemotePort());//客户端软件的端口号
//头相关 key:value
//根据key单独获取某个请求头
String accept = req.getHeader("Accept");
System.out.println(accept);
System.out.println("-----------------------");
//获取所有的请求头
Enumeration<String> headerNames = req.getHeaderNames();while (headerNames.hasMoreElements()) {//获取keyString key = headerNames.nextElement();//获取valueString value = req.getHeader(key);System.out.println(key + ": " + value);}}}

在这里插入图片描述
在这里插入图片描述

5.2.3 请求体相关 API
方法说明
req.getParameter("name")获取单个参数值(字符串)
req.getParameterValues("name")获取多个值(如复选框),返回 String[]
req.getParameterNames()获取所有参数名,返回 Enumeration<String>
req.getParameterMap()获取所有参数的 Map<String, String[]>

⚠️ 注意

  • GET 请求:参数以键值对的形式在 URL 后(查询字符串),也可以有请求体,但通常为空。
  • POST 请求:参数在请求体中,以 application/x-www-form-urlencoded 格式提交。
@WebServlet("/Servlet7")
public class Servlet7 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取键值对形式的参数
//根据参数名获取单个参数值
String username = req.getParameter("username");
System.out.println(username);
String password = req.getParameter("password");
System.out.println(password);
//根据参数名获取多个参数值
String[] interests = req.getParameterValues("interests");
System.out.println(Arrays.toString( interests));
System.out.println("-------------------获取所有参数----------------------------------");
//获取所有参数名
Enumeration<String> parameterNames = req.getParameterNames();while (parameterNames.hasMoreElements()) {//获取keyString key = parameterNames.nextElement();//获取valueString[] value = req.getParameterValues(key);if(value.length>1){System.out.println(key + ": " + Arrays.toString(value));}else{System.out.println(key + ": " + value[0]);}}System.out.println("-------------------返回所有参数的map集合----------------------------------");//返回所有参数的map集合Map<String, String[]> parameterMap = req.getParameterMap();Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();for (Map.Entry<String, String[]> entry : entries){String key = entry.getKey();String[] value = entry.getValue();if(value.length>1){System.out.println(key + ": " + Arrays.toString(value));}else{System.out.println(key + ": " + value[0]);}}System.out.println(req.getServletPath());//获取请求的Servlet的映射路径}}

在这里插入图片描述

5.2.4 获取非键值对请求体数据

对于 JSON、文件上传 等非表单格式的数据,需使用输入流读取:

方法说明
req.getReader()获取字符流,用于读取文本(如 JSON 字符串)
req.getInputStream()获取字节流,用于读取二进制数据(如文件)

5.3 HttpServletResponse

5.3.1 Http响应结构
状态行  HTTP/1.1 200 OK
响应头  Content-Type: text/html
Content-Length: 13
[空行]
响应体  <h1>hello</h1>

在这里插入图片描述

5.3.2 响应行相关API
方法说明
resp.setStatus(200)设置 HTTP 状态码(200、404、500 等)

5.3.3 响应头相关API
方法说明
resp.setHeader("key", "value")设置响应头
resp.setContentType("text/html")设置内容类型(推荐方式)
resp.setContentLength(len)设置响应体长度(可选,用于性能优化)

推荐:使用 setContentType() 而非手动设置 Content-Type 头,可自动处理字符编码。


5.3.4 响应体 相关API
输出流说明
resp.getWriter()获取字符输出流,用于输出文本(HTML、JSON 等)
resp.getOutputStream()获取字节输出流,用于输出二进制数据(文件、图片等)

⚠️ 注意不能同时使用 getWriter()getOutputStream(),否则会抛出异常。

@WebServlet("/Servlet8")
public class Servlet8 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应行相关的API    HTTP/1.1 200/404/405...
resp.setStatus(200);
String info = "<h1>hello</h1>";
//设置响应头相关的API
resp.setHeader("key-a","value-a");
resp.setHeader("Content-Type", "text/html;charset=utf-8");
resp.setHeader("Content-Length", info.getBytes().length + "");
resp.setContentType("text/html");
resp.setContentLength(info.getBytes().length);
//设置响应体相关的API
//获得一个向响应体中输入文本字符输出流
PrintWriter writer = resp.getWriter();
writer.write(info);
writer.flush();
writer.close();
//获得一个向响应体中输入二进制信息的字节输出流
ServletOutputStream outputStream = resp.getOutputStream();
outputStream.write(info.getBytes());
outputStream.flush();
outputStream.close();
}
}

在这里插入图片描述
在这里插入图片描述

六、ServletConfig 和ServletContext

6.1 ServletConfig

6.1.1 什么是 ServletConfig?
  • ServletConfig 是每个 Servlet 实例的局部配置对象
  • 容器在初始化 Servlet 时创建,用于封装该 Servlet 的初始化参数。
  • 作用域:仅限当前 Servlet

一一对应

ServletConfigServlet 容器为每个 Servlet 实例创建的局部配置对象,用于封装该 Servlet 的初始化参数和名称。

每个 Servlet 拥有独立的 ServletConfig

public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();}
6.1.2 获取方式

方式一:使用 getServletConfig() 方法(推荐)

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//获得ServletConfig实例
ServletConfig config = getServletConfig();
String servletName = config.getServletName();
String encoding = config.getInitParameter("encoding");
Enumeration<String> paramNames = config.getInitParameterNames();System.out.println("Servlet Name: " + servletName);System.out.println("Encoding: " + encoding);}

说明getServletConfig()GenericServlet 提供的受保护方法,所有 HttpServlet 子类均可直接调用。


方式二:重写 init(ServletConfig config) 方法时保存

public class MyServlet extends HttpServlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config); // 必须调用父类 init(),否则 getServletConfig() 返回 null
this.config = config;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 使用保存的 config
String value = config.getInitParameter("encoding");
}
}

⚠️ 注意

  • 必须调用 super.init(config),否则后续 getServletConfig() 会返回 null
  • 此方式适用于需要在多个方法中频繁使用 ServletConfig 的场景。
6.1.3 核心方法
方法说明
getServletName()获取 Servlet 名称
getInitParameter(String name)获取初始化参数值
getInitParameterNames()获取所有初始化参数名
getServletContext()获取全局上下文对象
6.1.4 初始化参数

获得初始化参数,那么什么是初始化参数?

我们在注册和映射Servlet的时候,由两种方法一种是通过web.xml配置,另一种是通过注解@WebServlet的方法进行注册和映射

1.web.xml的方式配置初始化参数

通过init-param标签来进行初始化参数的配置

<servlet>
<servlet-name>Servlet2</servlet-name>
<servlet-class>com.zzz.servlet.Servlet2</servlet-class><init-param><param-name>keya</param-name><param-value>valuea</param-value></init-param><init-param><param-name>keyb</param-name><param-value>valueb</param-value></init-param><init-param><param-name>keyc</param-name><param-value>valuec</param-value></init-param>
</servlet>
<servlet-mapping>
<servlet-name>Servlet2</servlet-name>
<url-pattern>/s2</url-pattern>
</servlet-mapping>

2.@WebServlet注解中配置初始化参数

通过@WebServlet注解中的initParams中初始化参数

一定要用@WebInitParam,不然不生效,单写也不生效

@WebServlet(name = "Servlet2", value = "/Servlet2", initParams = {
@WebInitParam(name = "keya", value = "valuea"),
@WebInitParam(name = "keyb", value = "valueb")
})

测试代码

@WebServlet(name = "Servlet2", value = "/Servlet2", initParams = {
@WebInitParam(name = "keya", value = "valuea"),
@WebInitParam(name = "keyb", value = "valueb")
})
public class Servlet2 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("---------------ServletConfig获取-------------------------");
//获取初始配置信息即可
//根据参数名称获取参数值
ServletConfig servletConfig = getServletConfig();
String keya = servletConfig.getInitParameter("keya");
System.out.println("keya: " + keya);
//获取所有的初始参数的名字
//hasMoreElements 判断有没有下一个参数  如果由 返回true  如果没有返回 false
//nextElement 获取下一个参数 向下移动游标
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();//判断是否有下一个参数while (initParameterNames.hasMoreElements()) {//获取keyString key = initParameterNames.nextElement();//通过key获取到valueString value = servletConfig.getInitParameter(key);System.out.println(key + ": " + value);}}}
<servlet>
<servlet-name>Servlet2</servlet-name>
<servlet-class>com.zzz.servlet.Servlet2</servlet-class><init-param><param-name>keya</param-name><param-value>valuea</param-value></init-param><init-param><param-name>keyb</param-name><param-value>valueb</param-value></init-param><init-param><param-name>keyc</param-name><param-value>valuec</param-value></init-param>
</servlet>
<servlet-mapping>
<servlet-name>Servlet2</servlet-name>
<url-pattern>/s2</url-pattern>
</servlet-mapping>

在这里插入图片描述

6.2 ServletContext

6.2.1 什么是ServletContext?

ServletContext 是 Web 应用的全局上下文对象,代表整个 Web 应用。

每个应用只有一个 ServletContext 实例,所有 Servlet 共享。

一对多

6.2.2 获取方式
// 方式一:通过 HttpServlet 提供的方法
ServletContext context = getServletContext();
// 方式二:通过 HttpServletRequest
ServletContext context = req.getServletContext();

我们注意到ServletConfig中也有一个getServletContext()方法

我们看他的实现类GenericServlet

public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}

getServletConfig()由Servlet接口提供,getServletContext由ServletConfig接口提供,最终由GenericServlet实现类进行实现。

所以我们使用

// 方式一:通过 HttpServlet 提供的方法
ServletContext context = getServletContext();

实际上就是相当于

ServletContext context = this.getServletConfig().getServletContext();
//分开写就是
ServletConfig config = getServletConfig();
ServletContext context = config.getServletContext();

我们通过两个方式拿到的context是同一个对象吗?

ServletContext servletContext1 = getServletContext();
ServletContext servletContext2 = servletConfig.getServletContext();
ServletContext servletContext3 = req.getServletContext();
System.out.println(servletContext1 == servletContext2);//true
System.out.println(servletContext3 == servletContext2);//true

在这里插入图片描述

获取的是内存中的同一个对象。但是实现机制是不一样的。

方式调用链谁实现的?
getServletContext()this.getServletContext()GenericServlet.getServletContext()config.getServletContext()GenericServlet 提供方法,但 config 由 Tomcat 注入
req.getServletContext()req.getServletContext()RequestFacade.getServletContext()Request.getServletContext()Tomcat 的 Request 实现类提供

方式一:getServletContext()

// 来自 GenericServlet.java
public ServletContext getServletContext() {
return config.getServletContext(); // config 是容器注入的
}
  • configServletConfig 类型,由 Tomcat 在 init(config) 时传入。
  • config.getServletContext() 最终返回的是 Tomcat 内部的 ApplicationContextFacade

所以:方法是 GenericServlet 写的,但数据来源是 Tomcat 提供的 config 对象


方式二:req.getServletContext()

// 来自 Tomcat 源码:RequestFacade.java
public ServletContext getServletContext() {
return request.getServletContext();
}
  • request 是 Tomcat 内部的 Request 对象。
  • 它持有一个 Context(即 Web 应用)的引用。
  • 最终也返回同一个 ApplicationContextFacade

所以:整个调用链都在 Tomcat 内部实现

对比项getServletContext()req.getServletContext()
调用者当前 Servlet 实例HttpServletRequest 对象
来源继承自 GenericServlet来自请求对象(Tomcat 实现)
依赖依赖 init(config) 是否被正确调用依赖 req 是否有效
可读性更直观,表示“我这个 Servlet 的上下文”表示“当前请求所属的上下文”
使用场景通用,推荐在只有 req 没有 this 时使用(如工具类)
性能几乎无差异几乎无差异
线程安全安全(config 初始化后不变)安全(req 是每次请求新建)
6.2.3 核心方法和功能
核心功能一:获取项目路径信息
方法说明
getRealPath(String path)获取项目部署目录下资源的真实磁盘路径
getContextPath()获取项目的上下文路径(访问路径)
@WebServlet("/Servlet3")
public class Servlet3 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//获得一个指向项目部署位置下的文件/目录的磁盘真实路径
String upload = servletContext.getRealPath("upload");
System.out.println(upload);
//获得项目部署的上下文路径, 项目的访问路径
String contextPath = servletContext.getContextPath();
System.out.println(contextPath);
}
}

在这里插入图片描述

核心功能二:域对象 —— 全局数据共享

ServletContextApplication 域对象,可在整个应用中共享数据。

方法说明
setAttribute(String key, Object value)存储数据(覆盖同名键)
getAttribute(String key)获取数据
removeAttribute(String key)删除数据
getAttributeNames()获取所有属性名(Enumeration<String>
/**
* @Description: 域对象-存放数据
*/
@WebServlet("/Servlet4")
public class Servlet4 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//存放数据
//键值对形式:键不能重复,键相同,值会覆盖
servletContext.setAttribute("key1", "value1");
servletContext.setAttribute("key1", "value2");
servletContext.setAttribute("key2", "value3");
servletContext.setAttribute("key3", "value4");
servletContext.setAttribute("key4", "value5");
//移除数据
servletContext.removeAttribute("key2");
}
}
/**
* @Description: 域对象-获取数据
*/
@WebServlet("/Servlet5")
public class Servlet5 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = req.getServletContext();
//获取数据
Enumeration<String> attributeNames = servletContext.getAttributeNames();while (attributeNames.hasMoreElements()) {//获取keyString key = attributeNames.nextElement();//通过key获取valueObject value = servletContext.getAttribute(key);System.out.println(key + ": " + value);}}}

在这里插入图片描述

6.2.4 初始化参数

每个应用只有一个 ServletContext 实例,所有 Servlet 共享,那么怎么设置它的初始化参数呢?

web.xml中使用 标签配置

<context-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</context-param>
<context-param>
<param-name>username</param-name>
<param-value>zhangsan</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>
//获取所有初始参数的名字
Enumeration<String> initParameterNames1 = servletContext.getInitParameterNames();//判断是否有下一个参数while (initParameterNames1.hasMoreElements()) {//获取参数中的keyString key = initParameterNames1.nextElement();//根据key获取参数中的valueString value = servletContext.getInitParameter(key);System.out.println(key + ": " + value);}

在这里插入图片描述

6.3 总结

对比项ServletConfigServletContext
作用范围单个 Servlet整个 Web 应用
创建时机Servlet 初始化时Web 应用启动时
实例数量每个 Servlet 一个每个应用一个
主要用途配置单个 Servlet 的参数全局路径、共享数据、全局参数
获取方式getServletConfig()init(config) 保存getServletContext()req.getServletContext()config.getServletContext()
域对象功能❌ 无✅ 有(Application 域)
初始化参数来源<init-param><context-param> 和所有 <init-param>

七、请求转发和重定向

7.1 请求转发(Request Forward)

请求转发是服务器内部的行为,由 HttpServletRequest 对象调用 RequestDispatcher 实现,将请求“内部移交”给另一个资源处理。

特性说明
客户端请求次数1 次
地址栏变化❌ 不变
可见性对客户端完全透明(屏蔽)
request/response 对象多个 Servlet 共享 同一对requestresponse
数据传递✅ 可通过 request.setAttribute() 传递数据
目标资源类型动态资源(Servlet)、静态资源(HTML)、WEB-INF 下资源
跨项目/外部资源❌ 不支持(只能是本项目内资源)

特别说明:访问 WEB-INF 下的资源唯一方式就是请求转发,因其受容器保护,无法直接通过 URL 访问。

  • 请求转发是通过HttpServletRequest对象获取请求转发器实现的
  • 请求转发是服务器内部行为,对客户端是屏蔽的
  • 客户端只产生了一次请求 服务端只产生了一堆request 和 response 对象
  • 客户端的地址栏是不变的
  • 请求的参数是可以继续传递的
  • 目标资源可以是servlet动态资源,也可以是html静态资源
  • 目标资源可以是WEB-INF下的受保护的资源 该方式也是WEB-INF下的资源的唯一访问方式
  • 目标资源不可以是外部的资源,只能是本项目以内的资源
// 1. 设置要传递的数据
req.setAttribute("key1", "value1");
// 2. 获取请求转发器
RequestDispatcher dispatcher = req.getRequestDispatcher("/目标路径");
// 3. 执行转发
dispatcher.forward(req, resp);

测试 ServletA → ServletB

@WebServlet("/ServletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletA执行了");
//ServletA转发给ServletB,并携带数据
req.setAttribute("key1", "value1");
//请求转发给ServletB
//获得请求转发器
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("/ServletB");
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("login.html");
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("/WEB-INF/A.html");
RequestDispatcher requestDispatcher = req.getRequestDispatcher("https://www.baidu.com");//[/JavaWeb_03_Servlet/https://www.baidu.com]不可用
//让请求转发器做出转发动作
requestDispatcher.forward(req, resp);
}
}
@WebServlet("/ServletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletB执行了");
String[] values = new String[0];
//接受从ServletA请求转发携带的数据
Enumeration<String> parameterNames = req.getParameterNames();while (parameterNames.hasMoreElements()) {String key = parameterNames.nextElement();values = req.getParameterValues(key);if (values.length> 1){
resp.getWriter().write("<h1>"+ Arrays.toString(values) +"</h1>");System.out.println(key + ": " + Arrays.toString(values));}else {
resp.getWriter().write("<h1>"+ values[0] +"</h1>");System.out.println(key + ": " + values[0]);}}
resp.getWriter().write("<h1>ServletB返回结果</h1>");}}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

资源类型示例
Servletreq.getRequestDispatcher("/ServletB")
HTML 静态页req.getRequestDispatcher("login.html")
WEB-INF 下资源req.getRequestDispatcher("/WEB-INF/A.html")
外部 URL(错误)req.getRequestDispatcher("https://www.baidu.com") ❌(会报错)

⚠️ 错误提示:[/JavaWeb_03_Servlet/https://www.baidu.com]不可用

7.2重定向(Redirect)

重定向是服务器告诉客户端“你应该去另一个地址”,客户端重新发起请求访问新地址。通过 HttpServletResponse.sendRedirect() 实现。

特性说明
客户端请求次数≥2 次(原始请求 + 重定向请求)
地址栏变化✅ 变为新地址
可见性客户端可见(浏览器会跳转)
request/response 对象每次请求都创建新的 requestresponse
数据传递❌ 原 request 中的数据无法自动传递(可使用 Session 或 URL 参数)
目标资源类型任意可访问资源(Servlet、HTML、JSP 等)
跨项目/外部资源✅ 支持(可跳转到其他网站)
WEB-INF 资源❌ 无法访问(受保护)

优势:可用于跳转外部系统、防止表单重复提交、实现登录后跳转等。

resp.sendRedirect("目标URL");

URL 可以是:

  • 相对路径:"ServletD"
  • 绝对路径(项目内):"/项目名/ServletD"
  • 外部地址:"https://www.baidu.com"

测试ServletC → ServletD

@WebServlet("/ServletC")
public class ServletC extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletC执行了");
//重定向给ServletD
//响应重定向     设置响应状态码为302     同时设置location响应头为跳转的URL
//resp.sendRedirect("ServletD");
//resp.sendRedirect("login.html");
//resp.sendRedirect("/WEB-INF/A.html");//失败
resp.sendRedirect("https://www.baidu.com");
}
}
@WebServlet("/ServletD")
public class ServletD extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletD执行了");
resp.getWriter().write("<h1>ServletD返回结果</h1>");
}
}

在这里插入图片描述

在这里插入图片描述

访问WEB-INF 资源是会失败的

在这里插入图片描述

八、乱码问题

编码与解码不一致

乱码的本质是:数据在传输过程中,编码(写入)和解码(读取)时使用的字符集不一致

例如:

  • 客户端用 UTF-8 编码发送 "你好" → 字节流:[E4 B8 AD][E5 A5 BD]
  • 服务器用 ISO-8859-1 解码 → 无法识别中文 → 显示为 ???ä½ å¥½
场景方向说明
请求乱码客户端 → 服务器浏览器提交数据(表单、URL 参数等),服务器读取时出现乱码
响应乱码服务器 → 客户端服务器返回中文内容,浏览器显示为乱码

8.1 请求乱码(Request)

客户端(浏览器)使用某种字符集(如 UTF-8)对请求参数进行编码,而服务器默认使用 ISO-8859-1 解码(Tomcat 默认),导致中文乱码。

常见于:


解决方案

方法一:设置请求体编码(仅对 POST 有效)

req.setCharacterEncoding("UTF-8");
  • 作用:告诉服务器,请求体(Body)中的数据是用 UTF-8 编码的
  • 必须在 req.getParameter() 之前调用,否则无效。
  • 仅对 POST 请求有效,因为 GET 请求参数在 URL 中,不在 Body。

示例:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8"); // 必须写在获取参数之前
String username = req.getParameter("username"); // 正常获取中文
System.out.println(username); // 输出:张三
}

方法二:GET 请求乱码的解决(特殊处理)

由于 GET 请求参数在 URL 中,不受 setCharacterEncoding 影响,需额外处理:

方案 1:修改 Tomcat 配置(推荐)

编辑 conf/server.xml,在 <Connector> 标签中添加 URIEncoding="UTF-8"

<Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"URIEncoding="UTF-8" />

效果:所有 GET 请求的 URL 参数都按 UTF-8 解码。

方案 2:手动转码(不推荐,兼容性差)

String name = req.getParameter("name");
name = new String(name.getBytes("ISO-8859-1"), "UTF-8"); // 先以 ISO 解码,再转 UTF-8

⚠️ 缺点:依赖服务器默认编码,移植性差。


8.2响应乱码(Response)

服务器默认使用 ISO-8859-1 发送响应,而该编码不支持中文;同时浏览器不知道应使用何种编码解析响应体,导致显示乱码。


解决方案

方法一:设置响应编码 + MIME 类型(推荐)

resp.setContentType("text/html; charset=UTF-8");

这行代码等价于同时设置:

resp.setCharacterEncoding("UTF-8");         // 响应体编码
resp.setHeader("Content-Type", "text/html; charset=UTF-8"); // 告诉浏览器用 UTF-8 解析

示例:

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=UTF-8");
resp.getWriter().write("<h1>你好,世界!</h1>");
}
@WebServlet("/DemoServlet")
public class DemoServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 处理请求乱码(POST)
req.setCharacterEncoding("UTF-8");
// 2. 获取参数(GET/POST)
String msg = req.getParameter("msg"); // 如:"你好"
// 3. 处理响应乱码
resp.setContentType("text/html; charset=UTF-8");
// 4. 输出
resp.getWriter().write("<h1>接收到的消息:" + msg + "</h1>");
}
}

8.3 过滤器处理乱码(Filter)

后面我们可以把这个放到过滤器中,下面简单提及:

/**
* 设置请求体解码使用UTF-8编码
* request.setCharacterEncoding("UTF-8");
* 设置响应体使用UTF-8编码
* response.setCharacterEncoding("UTF-8");
* 设置Content-Type响应头为text/html,并告诉客户端用utf-8编码
* response.setContentType("text/html;charset=UTF-8");
*
* Tomcat10中,响应体默认的编码字符集使用的是UTF-8
*
* 解决思路
*   1 可以设置响应体的编码字符集和客户端的保持一致 不推荐 客户端解析的字符集是无法预测的
*   2 可以告诉客户端使用指定的字符集进行解码,通过设置Content-Type响应头
*   注意的是:明确响应体的编码,然后再设置Content-type
* */
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}
}
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.zzz.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

九、路径问题

9.1相对路径和绝对路径

类型示例说明
相对路径ServletA, ./ServletA, ../images/logo.png相对于当前页面所在目录解析
绝对路径(完整)http://localhost:8080/JavaWeb_03_Servlet/ServletA包含协议、主机、端口、项目名
绝对路径(上下文根)/JavaWeb_03_Servlet/ServletA/ 开头,表示从服务器根开始
绝对路径(应用内)/ServletA/ 开头,但省略项目名,常用于服务器内部跳转

推荐使用 / 开头的绝对路径(上下文根),避免因页面层级不同导致路径错误。

9.2<base> 标签:定义页面路径基准

通过 <base href="..."> 定义当前页面中所有相对路径的公共前缀,浏览器会自动将不带协议或根路径的链接“补全”为绝对路径。

<head><base href="/项目名/"></head>

示例

<base href="/JavaWeb_03_Servlet/"><!-- 以下链接实际访问 --><a href="ServletA">跳转</a><!-- 相当于:http://localhost:8080/JavaWeb_03_Servlet/ServletA --><img src="images/logo.png"><!-- 相当于:http://localhost:8080/JavaWeb_03_Servlet/images/logo.png -->

注意事项

  • <base> 影响当前页面中所有未显式指定协议或根路径的 URL
  • 一个页面只能有一个 <base> 标签。
  • 常用于统一管理项目上下文路径,避免硬编码。

9.3 重定向(Redirect)中的路径

  • 由客户端(浏览器)发起新请求。
  • 路径必须能被浏览器正确解析。
  • 支持外部资源。
写法示例实际跳转地址
相对路径"ServletD"当前路径 + ServletD(易出错)
相对路径(上级)"../ServletD"返回上级目录再查找
绝对路径(推荐)"/JavaWeb_03_Servlet/ServletD"http://localhost:8080/JavaWeb_03_Servlet/ServletD
外部 URL"https://www.baidu.com"跳转到百度

绝对路径默认是:http://localhost:8080/ 跟前端html中一样

代码示例

resp.sendRedirect("/JavaWeb_03_Servlet/ServletD"); // 推荐
resp.sendRedirect("https://www.baidu.com");       // 外部跳转

9.4 请求转发(Forward)中的路径

  • 服务器内部行为,不经过浏览器。
  • 路径由服务器解析,不暴露给客户端
  • 只能访问本项目资源。
写法示例说明
相对路径"ServletB"相对于当前 Servlet 所在路径
绝对路径(推荐)"/ServletB"/ 开头,代表 项目上下文根
WEB-INF 资源"/WEB-INF/A.html"可访问受保护资源 ✅
外部 URL"https://..."❌ 不支持,会报错

请求转发中的 / 代表的是:
http://localhost:8080/JavaWeb_03_Servlet/
即:已经包含了项目上下文,无需再写项目名!

代码示例

RequestDispatcher rd = req.getRequestDispatcher("/ServletB");        // ✅ 正确
rd.forward(req, resp);
// 访问 WEB-INF
req.getRequestDispatcher("/WEB-INF/view.jsp").forward(req, resp);     // ✅

错误写法:

req.getRequestDispatcher("/JavaWeb_03_Servlet/ServletB"); // 多写了项目名,路径不存在

9.5 总结

重定向的相对路径和绝对路径写法一致
相对路径
./ …/
绝对路径
http://localhost:8080/

请求转发
相对路径与前端和重定向一致
绝对路径:请求转发的绝对路径是不需要添加项目上下文的
0/JavaWeb_03_Servlet/ServletD| | **外部 URL** |“https://www.baidu.com”` | 跳转到百度 |

绝对路径默认是:http://localhost:8080/ 跟前端html中一样

代码示例

resp.sendRedirect("/JavaWeb_03_Servlet/ServletD"); // 推荐
resp.sendRedirect("https://www.baidu.com");       // 外部跳转

9.4 请求转发(Forward)中的路径

  • 服务器内部行为,不经过浏览器。
  • 路径由服务器解析,不暴露给客户端
  • 只能访问本项目资源。
写法示例说明
相对路径"ServletB"相对于当前 Servlet 所在路径
绝对路径(推荐)"/ServletB"/ 开头,代表 项目上下文根
WEB-INF 资源"/WEB-INF/A.html"可访问受保护资源 ✅
外部 URL"https://..."❌ 不支持,会报错

请求转发中的 / 代表的是:
http://localhost:8080/JavaWeb_03_Servlet/
即:已经包含了项目上下文,无需再写项目名!

代码示例

RequestDispatcher rd = req.getRequestDispatcher("/ServletB");        // ✅ 正确
rd.forward(req, resp);
// 访问 WEB-INF
req.getRequestDispatcher("/WEB-INF/view.jsp").forward(req, resp);     // ✅

错误写法:

req.getRequestDispatcher("/JavaWeb_03_Servlet/ServletB"); // 多写了项目名,路径不存在

9.5 总结

重定向的相对路径和绝对路径写法一致
相对路径
./ …/
绝对路径
http://localhost:8080/

请求转发
相对路径与前端和重定向一致
绝对路径:请求转发的绝对路径是不需要添加项目上下文的
请求转发的/ 代表的路径是 http://localhost:8080/JavaWeb_03_Servlet/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/962906.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年质量好的机柜空调厂家实力及用户口碑排行榜

2025年质量好的机柜空调厂家实力及用户口碑排行榜行业背景与市场趋势随着数据中心、新能源及工业自动化领域的快速发展,机柜空调作为关键的热管理设备,市场需求持续攀升。根据《2024-2029年中国机柜空调行业市场调研…

服务注册、服务发现、OpenFeign及其OKHttp连接池建立

服务注册、服务发现、OpenFeign及其OKHttp连接池建立pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&quo…

2025年评价高的近场直写静电纺丝设备最新TOP厂家排名

2025年评价高的近场直写静电纺丝设备最新TOP厂家排名行业背景与市场趋势近场直写静电纺丝技术作为纳米纤维制造领域的重要分支,近年来在生物医疗、新能源、航空航天等高端领域展现出巨大应用潜力。根据全球市场研究机…

Cisco Meeting Server 3.12 - 会议与协作

Cisco Meeting Server 3.12 - 会议与协作Cisco Meeting Server 3.12 - 会议与协作 思科 Meeting Server 请访问原文链接:https://sysin.org/blog/cisco-meeting-server-3/ 查看最新版。原创作品,转载请保留出处。 作…

2025年口碑好的通用型静电纺丝设备厂家推荐及选购参考榜

2025年口碑好的通用型静电纺丝设备厂家推荐及选购参考榜静电纺丝设备行业背景与市场趋势静电纺丝技术作为纳米纤维制备的核心工艺,近年来在生物医疗、过滤材料、新能源、航空航天等领域的应用持续扩大。根据Marketsan…

2025年评价高的手持激光打标机厂家实力及用户口碑排行榜

2025年评价高的手持激光打标机厂家实力及用户口碑排行榜行业背景与市场趋势激光打标技术作为现代工业制造的重要组成部分,近年来随着智能制造和工业4.0的推进,市场规模持续扩大。据《2024-2025中国激光设备行业白皮书…

2025年6月EGUOO复合植物舒压睡眠片推荐:温和植物配方助眠新选择

快节奏的都市生活把“睡个好觉”变成稀缺资源,加班、时差、屏幕蓝光持续刺激神经,许多人陷入“越想睡越清醒”的恶性循环。对不愿立刻依赖处方镇静剂的人而言,一款成分透明、来源可验、兼顾“舒缓压力+调节节律”的…

2025年6月EGUOO复合植物舒压睡眠片推荐:便携规格随行调理作息节律

深夜刷手机越刷越清醒、出差倒时差睁眼到天亮、会议前夜思绪翻涌……当“睡个好觉”成为稀缺体验,人们开始寻找既温和又高效的解决方案。EGUOO复合植物舒压睡眠片正是在这样的需求场景下被频繁提及:它不以“速效”做…

2025年7月EGUOO副作用推荐:全链透明数据助力安心决策

在膳食补充剂市场信息过载的当下,消费者最怕“效果被夸大、风险被隐藏”。当用户搜索“EGUOO副作用”时,核心诉求并不是简单一句“安全”或“不安全”,而是希望知道:哪些成分可能在什么场景下带来不适?不适程度如…

深入解析:单序列和双序列问题——动态规划

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Laravel ObjectId 性能最强体积最小的分布式 UUID 生成扩展

Laravel ObjectId 性能最强体积最小的分布式 UUID 生成扩展 相信每位 Laravel 开发者都遇到过这种情况:新建项目、写迁移、模型默认用自增 ID。开始挺好,但等到系统要上分布式、对接 API、搞微服务的时候,整数 ID 的…

k6 nats 测试扩展

k6 nats 测试扩展实际上社区已经有一个扩展了,但是因为时间比较长了,与新版本的k6 已经不兼容了,我进行了调整了可以更好的支持新版本 参考修改k6使用的底层js 引擎,新版本使用了grafana/sobek 对于golang nats cl…

在线调试查看天气和快递

1.打开万维易源,点击“API市场”,打开下方的“天气预报”2.先购买0元包,再点击“在线调试”,弹出下方页面3.将http请求更改为get,删掉areaCode的值,并将area改为地名,点击“调用接口”1.打开万维易源,点击“AP…

一场论坛捕捉 26 年语音 AI 关键词:思考机制、专有模型、低功耗、超低帧率、多语种、语音增强……丨RTE2025 回顾

在 Convo AI & RTE2025 大会上,来自产业界和学术界的多位专家深入探讨了智能语音技术、大模型时代的语音交互范式变革及其在实时互动场景中的挑战与机遇。科大讯飞寰语 AI 研究院副院长孟廷、声网音频体验与方案负…

2025年11月EGUOO营养包价格贵:稀缺原料与诺奖科研背书下的价值拆解

2025年11月,当消费者在小红书输入“EGUOO营养包价格贵”时,后台瞬间跳出十万加笔记,高频关键词集中在“值不值”“是不是智商税”“同类价差十倍”。这种集体疑问背后,是都市白领、银发一族和健身爱好者共同的决策…

2025年7月EGUOO营养包价格贵推荐:从成分奢侈品到健康投资品路径

开场白 “每天吞一把平价胶囊,体检报告却年年红灯”——这是不少职场人在体检中心排队时的共同叹息。当肝脏指标、抗氧化能力、肠道菌群失衡同时亮起警报,消费者开始追问:低价补充剂到底缺了什么?EGUOO营养包因定价…

微软正式发布 .NET 10:三年 LTS 支持驱动性能革命与 AI 原生开发新纪元

微软公司(北京时间2025 年 11 月 12 日,西雅图)在 .NET Conf 2025 在线大会上,隆重宣布 .NET 10 正式发布,博客文章详见:https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/。这是一个具有里程碑意义…

AI元人文:构建文明级认知纠缠体的操作系统

AI元人文:构建文明级认知纠缠体的操作系统 我们正站在文明演化的奇点。在全球化与数字化的双重浪潮中,不同文明的价值冲突日益凸显,而人工智能的崛起正在催生一个全新的文明形态。AI元人文构想应运而生,它不仅是解…

Skia 在龙芯搭景嘉微显卡设备 某些字体会渲染相互覆盖

此问题是先在 Avalonia 应用上发现的,后续调查了解到是 Skia 底层的问题。本文将记录此问题和提供规避方法以下是我的一个 Avalonia 应用上的文本渲染效果。使用 HarmonyOS Sans SC 鸿蒙字体,在龙芯 Loongson-3A6000…