繁体   English   中英

如何在 JavaEE 中挂钩 BeginRequest 和 EndRequest?

[英]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;
}

现在如何在 JavaEE 中做到这一点?

问题是:Java Web 服务器世界中IHttpModule的道德等价物是什么?

有人说它是一个ServletRequestListener

用于接收有关进入和离开 Web 应用程序范围的请求的通知事件的接口。

ServletRequest 被定义为在即将进入 Web 应用程序的第一个 servlet 或过滤器时进入 Web 应用程序的范围,并在退出链中的最后一个 Servlet 或第一个过滤器时超出范围。

其他人坚持你想要一个“过滤器”和一个“过滤器链”

有人说“过滤器”为您提供了ServletRequestListener所做的一切,但过滤器也可以配置为仅针对特定 URL 运行。

docs.oracle.com 上的这个随机页面没有说明“过滤器” ,而是我需要一个ServletContextListener ,因为这是接收每个请求的通知的唯一方法。 但后来他们有一个似乎与此相矛盾的表,并且我想要一个ServletRequestEvent

目的 事件 监听器接口和事件类
网络上下文 初始化和销毁 javax.servlet.ServletContextListenerServletContextEvent
网络上下文 添加、删除或替换的属性 javax.servlet.ServletContextAttributeListenerServletContextAttributeEvent
会议 创建、失效、激活、钝化和超时 javax.servlet.http.HttpSessionListenerjavax.servlet.http.HttpSessionActivationListenerHttpSessionEvent
会议 添加、删除或替换的属性 javax.servlet.http.HttpSessionAttributeListenerHttpSessionBindingEvent
要求 Web 组件已开始处理 servlet 请求 javax.servlet.ServletRequestListenerServletRequestEvent
要求 添加、删除或替换的属性 javax.servlet.ServletRequestAttributeListenerServletRequestAttributeEvent

我不知道这些东西是什么。 我只想在网络请求期间收到通知:

  • 当请求处理开始时
  • 当请求处理结束时

盲写Java代码

我可以尝试将 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 中长期存在的特性。

示例代码

这是我写的一个小演示。

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 示例代码

如果好奇,我为上面的例子编写的 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM