[英]How to hook BeginRequest and EndRequest in JavaEE?
在 JavaEE 中,我想知道什么时候:
并能够检查请求和响应对象。
在 ASP.net 世界中,如果您想知道请求何时开始和结束,您可以编写一个IHttpModule
:
public class ExampleModuleForThisQuestion : IHttpModule
{
}
然后在 web xml 配置文件中注册你的“模块” :
网络配置:
<system.webServer>
<modules>
<add name="DoesntMatter" type="ExampleModuleForThisQuestion "/>
</modules>
</system.webServer>
在您的模块中,您可以为以下内容注册回调处理程序:
然后,Web 服务器基础架构会调用您的Init
方法。 这是您注册希望在请求开始和请求结束时接收通知的机会:
public class ExampleModuleForThisQuestion : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(beginRequest); //register the "BeginRequet" event
application.EndRequest += new EventHandler(endRequest); //register the "EndRequest" event
}
}
现在我们有了请求开始时的回调:
private void beginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
//Record the time the request started
application.Context.Items["RequestStartTime"] = DateTime.Now;
//We can even access the Request and Response objects
application.ContenxtLog(application.Context.Request.Headers["User-Agent"]);
}
当请求结束时我们有回调:
private void endRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
//We can even access the Request and Response objects
//Get the response status code (e.g. 418 I'm a teapot)
int statusCode = application.Context.Response.StatusCode;
//Get the request method (e.g. GET, POST, BREW)
String method = application.context.Request.RequestType;
//Get the path from the request (e.g. /ViewCustomer)
String path = application.context.Request.AppRelativeCurrentExecutionFilePath'
//Get when the request started - that we recorded during "Begin Request"
DateTime requestStartTime = (DateTime)application.Context.Items["RequestStartTime"];
//And we can modify the response
if ((DateTime.Now - requestStartTime).TotalSeconds = 17)
application.Context.Response.StatusCode = 451;
}
问题是:Java Web 服务器世界中IHttpModule
的道德等价物是什么?
有人说它是一个ServletRequestListener
:
用于接收有关进入和离开 Web 应用程序范围的请求的通知事件的接口。
ServletRequest 被定义为在即将进入 Web 应用程序的第一个 servlet 或过滤器时进入 Web 应用程序的范围,并在退出链中的最后一个 Servlet 或第一个过滤器时超出范围。
其他人坚持你想要一个“过滤器”和一个“过滤器链” 。
有人说“过滤器”为您提供了ServletRequestListener
所做的一切,但过滤器也可以配置为仅针对特定 URL 运行。
docs.oracle.com 上的这个随机页面没有说明“过滤器” ,而是我需要一个ServletContextListener
,因为这是接收每个请求的通知的唯一方法。 但后来他们有一个似乎与此相矛盾的表,并且我想要一个ServletRequestEvent
:
目的 | 事件 | 监听器接口和事件类 |
---|---|---|
网络上下文 | 初始化和销毁 | javax.servlet.ServletContextListener 和ServletContextEvent |
网络上下文 | 添加、删除或替换的属性 | javax.servlet.ServletContextAttributeListener 和ServletContextAttributeEvent |
会议 | 创建、失效、激活、钝化和超时 | javax.servlet.http.HttpSessionListener 、 javax.servlet.http.HttpSessionActivationListener 和HttpSessionEvent |
会议 | 添加、删除或替换的属性 | javax.servlet.http.HttpSessionAttributeListener 和HttpSessionBindingEvent |
要求 | Web 组件已开始处理 servlet 请求 | javax.servlet.ServletRequestListener 和ServletRequestEvent |
要求 | 添加、删除或替换的属性 | javax.servlet.ServletRequestAttributeListener 和ServletRequestAttributeEvent |
我不知道这些东西是什么。 我只想在网络请求期间收到通知:
我可以尝试将 ASP.net 代码即时转录成 Java 风格的代码,希望有人可以修复它。
首先,我们将创建一个实现... ServletRequestListener
的类?:
public class ExampleListenerForThisQuestion
implements javax.servlet.ServletRequestListener {
}
然后我们在 web xml 配置文件中注册我们的“模块” :
web.xml
<listener>
<listener-class>ExampleListenerForThisQuestion</listener-class>
</listener>
现在我们将实现requestInitialized方法来创建我们的事件监听器:
public class ExampleListenerForThisQuestion
implements javax.servlet.ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
//...now i'm stuck
}
}
这并没有变得非常可变。
关于stackoverflow的最接近我想要的问题是:
java/tomcat/JSP 的 .net 的“Application_Start”和“Begin_Request”之类的事件?
唯一的问题是接受的答案没有回答如何去做。 这是有道理的:问题不是问如何去做。 问题只是询问Java是否有等价物。 显然它确实如此 -但我们不会告诉你如何。
我知道试图弄清楚如何在 JavaEE 中做到这一点至少需要4 天时间。 我希望通过询问 Stackoverflow 将其缩短到 6 或 7 小时。
这个问题花了我一个半小时才写出来。 我在 25 秒内从内存中编写了 ASP.net 代码。 我花了更长的时间来问如何在 Java 中做这件事,而不是我在 ASP.net 中做这件事——这表明我现在是多么的崩溃和失败。
编辑:我碰巧使用的是 Java 8。我假设它对应于 JavaEE 8。
ServletRequestListener
我想知道何时:请求何时开始以及请求何时结束
按照您的问题中的建议使用ServletRequestListener
。 自 Servlet 规范 2.4 版以来,这是 Servlet API 中长期存在的特性。
requestInitialized
方法是您知道何时开始处理请求的钩子。requestDestroyed
方法是处理请求结束时的钩子。这是我写的一个小演示。
package work.basil.example.demo;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpServletRequest;
import java.time.Instant;
@WebListener
public class RequestAnnouncer implements ServletRequestListener
{
@Override
public void requestInitialized ( ServletRequestEvent sre )
{
ServletRequestListener.super.requestInitialized( sre );
ServletRequest sr = sre.getServletRequest();
HttpServletRequest request = ( HttpServletRequest ) sr;
String url = request.getRequestURL() + ( request.getQueryString() == null ? "" : "?" + request.getQueryString() );
System.out.println( "INFO - Request initialized in thread " + Thread.currentThread().getId() + " at " + Instant.now() + " | " + url );
}
@Override
public void requestDestroyed ( ServletRequestEvent sre )
{
ServletRequestListener.super.requestDestroyed( sre );
ServletRequest sr = sre.getServletRequest();
HttpServletRequest request = ( HttpServletRequest ) sr;
String url = request.getRequestURL() + ( request.getQueryString() == null ? "" : "?" + request.getQueryString() );
System.out.println( "INFO - Request destroyed in thread " + Thread.currentThread().getId() + " at " + Instant.now() + " | " + url );
}
}
@WebListener
注意注释@WebListener
。 使用此注释,您可以跳过编辑 Web XML 配置文件。 @WebListener
在运行时充当标志,因此 Servlet 容器将在 Web 应用程序生命周期的适当时间点自动检测、实例化和执行此类。 Servlet 规范 3.0 中添加了这种现代便利。
jakarta.*
包命名还要注意import
语句中的jakarta.*
包命名。 在甲骨文公司几年前将责任移交给 Eclipse 基金会之后,Java EE 现在被称为 Jakarta EE。 Jakarta Servlet 规范版本 4 和 5(此处使用)是等效的,只是包名称从 javax.* 更改为 jakarta.*。
顺便说一句, Jakarta Servlet 规范版本 6作为 Jakarta EE 10 的一部分于今年发布,这是多年来的第一次重大变化。 请参阅文章, 关于 Jakarta Servlet 6.0 API 版本的 5 大须知。
在 Microsoft Edge 浏览器版本 103.0.1264.62(官方构建)(arm64)中运行时,在 MacBook Pro(16 英寸,2021 年)Apple M1 上运行 Apache Tomcat 2022.2 Beta 上的 macOS Monterey 12.2 Beta 和 Adoptium Temurin for Java 18.0.1+10亲,对于 URL http://localhost:8080/demo_war_exploded/hello-servlet
会产生以下输出。
INFO - Request initialized in thread 23 at 2022-07-15T20:55:21.060303Z | http://localhost:8080/demo_war_exploded/hello-servlet
INFO - Request destroyed in thread 23 at 2022-07-15T20:55:21.066361Z | http://localhost:8080/demo_war_exploded/hello-servlet
INFO - Request initialized in thread 26 at 2022-07-15T20:55:21.151810Z | http://localhost:8080/demo_war_exploded/hello-servlet
INFO - Request destroyed in thread 26 at 2022-07-15T20:55:21.152086Z | http://localhost:8080/demo_war_exploded/hello-servlet
为什么单个请求会得到四行而不是两行? 好吧,查看访问日志显示该浏览器在 IPv4 和 IPv6 上都发送了请求。 浏览器只向/
发送了一个请求,但我不知道为什么。
127.0.0.1 - - [15/Jul/2022:13:55:21 -0700] "GET /demo_war_exploded/hello-servlet HTTP/1.1" 200 306
127.0.0.1 - - [15/Jul/2022:13:55:21 -0700] "GET / HTTP/1.1" 404 683
0:0:0:0:0:0:0:1 - - [15/Jul/2022:13:55:21 -0700] "GET /demo_war_exploded/hello-servlet HTTP/1.1" 200 306
何时使用ServletRequestListener
以及何时使用过滤器? 过滤器用于链式处理请求和响应。 侦听器在每个请求的开始和结束时触发一次。 相反,多个过滤器可能会针对单个请求触发。 引用 Javadoc:
用于接收有关进入和离开 Web 应用程序范围的请求的通知事件的接口。
ServletRequest 被定义为在即将进入 Web 应用程序的第一个 servlet 或过滤器时进入 Web 应用程序的范围,并在退出链中的最后一个 Servlet 或第一个过滤器时超出范围。
因此,我们有一个开始、中间和结束的进程,其中侦听器为开始和结束触发,而零个、一个或多个过滤器可能在中间触发。
ServletRequestListener#requestInitialized
> [ … 过滤器 … ] >ServletRequestListener#requestDestroyed
有趣的是,我注意到过滤器API 是在 Servlet 规范版本 2.3 中添加的,而ServletRequestListener API 是在 2.4 中添加的。 所以我猜想,在推出过滤器功能后不久,开发人员发现需要充当临时书挡的钩子,将一系列过滤器括起来。 由于任何数量的过滤器都可能触发,而其中一个过滤器不知道其他过滤器,因此您可能需要在开始和结束时使用挂钩。
如果好奇,我为上面的例子编写的 Servlet:
package work.basil.example.demo;
import java.io.*;
import java.time.Instant;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
@WebServlet ( name = "helloServlet", value = "/hello-servlet" )
public class HelloServlet extends HttpServlet
{
private String message;
public void init ( )
{
message = "What time is it?";
}
public void doGet ( HttpServletRequest request , HttpServletResponse response ) throws IOException
{
response.setContentType( "text/html" );
// Hello
PrintWriter out = response.getWriter();
out.println( "<!DOCTYPE html>" );
out.println(
"""
<head>
<meta charset="utf-8">
<title>Servlet Request Listener Example</title>
<!-- <link rel="stylesheet" href="style.css"> -->
<!-- <script src="script.js"></script> -->
</head>
"""
);
out.println( "<html lang='en'>" );
out.println( "<body>" );
out.println( "<h1>" + message + "</h1>" );
out.println( "<p>" + Instant.now() + "</p>" );
out.println( "</body>" );
out.println( "</html>" );
}
public void destroy ( )
{
}
}
你说:
我碰巧使用的是 Java 8。我假设它对应于 JavaEE 8。
如果您的意思是版本号 8 和 8 之间的对应关系,则不。
Java EE(现在的 Jakarta EE)这个名字,意思是“企业版”,有点用词不当。 Jakarta EE 只是一堆软件的规范,这些软件可以从运行在 Java 虚拟机中的 Java 代码调用。 所以“Java EE”从来就不是真正的“Java”,它是一个规范和库的框架,用于帮助开发人员构建面向企业的应用程序。
Java EE 8 重新发布为Jakarta EE 8 , 需要 Java 8 。 (Java 9 到 11 的重大变化可能会在迁移时引起问题。)
Jakarta EE 9本质上与 Jakarta EE 8 相同,但包命名从javax.*
更改为jakarta.*
。 所以它也需要 Java 8 。
Jakarta EE 9.1是对 Jakarta EE 8 和 9 的改进和清理,删除了一些旧的弃用,添加了更多的弃用,并进行了其他工作,为未来的创新奠定了基础。 除了 Java 8 (两个LTS版本)之外,这些更改还要求实现支持 Java 11 。 虽然不是一个明确的要求,但所有实现都应该能够在 Java 17(当前的LTS版本)中运行。
Jakarta EE 10带来了期待已久的创新的第一波浪潮。
雅加达EE | 需要 Java |
---|---|
8 | 8 |
9 | 8 |
9.1 | 8 和 11(可能是 17) |
10 | ?? |
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.