JAX RS过滤器和拦截器

x33g5p2x  于2022-10-15 转载在 其他  
字(9.1k)|赞(0)|评价(0)|浏览(810)

1.概述

本文介绍FiltersInterceptors及其配置。FiltersInterceptors可以在客户端和服务器端使用。
Filters可以修改入站和出站请求和响应,包括修改头、实体和其他请求/响应参数。
Interceptors主要用于修改实体输入和输出流。例如,可以使用Interceptors压缩和解压缩输出和输入实体流。

2.过滤器

当您想修改任何请求或响应参数(如标题)时,可以使用过滤器。例如,您希望将响应标题“X-Powered-By”添加到每个生成的响应中。您将使用响应过滤器来添加此头,而不是在每个资源方法中添加此头。

服务器端和客户端都有筛选器。

服务器筛选器:

  • 容器请求筛选器
  • 容器响应筛选器
    客户端筛选器:
  • 客户端请求筛选器
  • 客户端响应筛选器

2.1服务器筛选器

ContainerResponseFilter-以下示例显示了一个简单的容器响应过滤器,它向每个响应添加一个标头。****

**示例:**容器响应筛选器

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
 
public class PoweredByResponseFilter implements ContainerResponseFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
        throws IOException {
 
            responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
    }
}

1.在上面的示例中,PoweredByResponseFilter 总是在响应中添加标题“X-Powered-By”。筛选器必须从ContainerResponseFilter 继承,并且必须注册为提供程序。
1.在大多数情况下,执行资源方法后,将对每个响应执行筛选器。即使未运行资源方法,也会执行响应过滤器,例如,当未找到资源方法且Jersey运行时返回404“未找到”响应代码时。在这种情况下,将执行过滤器并处理404响应。
1.filter()方法有两个参数,容器请求和容器响应。ContainerRequestContext 只能用于只读目的,因为过滤器已经在响应阶段执行。可以在ContainerResponseContext中进行修改。
ContainerRequestFilter-以下示例显示了请求筛选器的用法。

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
 
public class AuthorizationRequestFilter implements ContainerRequestFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext)
                    throws IOException {
 
        final SecurityContext securityContext =
                    requestContext.getSecurityContext();
        if (securityContext == null ||
                    !securityContext.isUserInRole("privileged")) {
 
                requestContext.abortWith(Response
                    .status(Response.Status.UNAUTHORIZED)
                    .entity("User cannot access the resource.")
                    .build());
        }
    }
}

示例中的AuthorizationRequestFilter 检查经过身份验证的用户是否处于特权角色。

预匹配和后匹配过滤器

上面显示的所有请求筛选器都实现为后匹配筛选器。这意味着只有在选择了合适的资源方法来处理实际请求后,即在进行请求匹配后,才会应用过滤器
请求匹配是根据请求路径和其他请求参数查找应执行的资源方法的过程。由于在选择了特定的资源方法时会调用后期匹配请求筛选器,因此此类筛选器不会影响资源方法匹配过程。
为了克服上述限制,可以将服务器请求过滤器标记为预匹配过滤器,即用@PreMatching注解注解过滤器类。预匹配筛选器是在启动请求匹配之前执行的请求筛选器。正因为如此,预匹配请求过滤器有可能影响匹配的方法。这样一个预匹配请求过滤器示例如下所示:

**示例:**预匹配请求筛选器

...
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
...
 
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext)
                        throws IOException {
        // change all PUT methods to POST
        if (requestContext.getMethod().equals("PUT")) {
            requestContext.setMethod("POST");
        }
    }
}

2.2客户端筛选器

客户端筛选器类似于容器筛选器。ClientRequestFilter中的响应也可以中止,这将导致实际上根本不会向服务器发送任何请求。一个新的响应被传递给中止方法。此响应将作为请求调用的结果使用和交付。这样的响应通过客户端响应过滤器。这与服务器端的情况类似
该过程如以下示例所示:

public class CheckRequestFilter implements ClientRequestFilter {
 
    @Override
    public void filter(ClientRequestContext requestContext)
                        throws IOException {
        if (requestContext.getHeaders(
                        ).get("Client-Name") == null) {
            requestContext.abortWith(
                        Response.status(Response.Status.BAD_REQUEST)
                .entity("Client-Name header must be defined.")
                        .build());
         }
    }
}

**CheckRequestFilter **验证传出请求。检查是否存在客户端名称标头。如果标头不存在,则请求将被中止,并在实体正文中添加一个带有适当代码和消息的虚构响应。这将导致原始请求无法有效地发送到服务器,但实际调用仍将以响应结束,就像它将由服务器端生成一样。如果有任何客户端响应过滤器,它将在该响应上执行。

3.拦截器

拦截器为服务器端和客户端共享一个公共API。过滤器主要用于操作请求和响应参数,如HTTP头、URIs和/或HTTP方法,而拦截器则用于通过操作实体输入/输出流来操作实体。例如,如果您需要对客户机请求的实体体进行编码,那么您可以实现一个拦截器来完成这项工作。
拦截器有两种,

  • 读卡器拦截器
  • 写入侦听器

读卡器拦截器用于操作入站实体流。这些是来自“电线”的溪流。因此,使用读取器拦截器,您可以操作服务器端的请求实体流(从客户端请求读取实体)和客户端的响应实体流(在客户端从服务器响应读取实体)。
编写器拦截器用于将实体写入“连线”的情况,在服务器上,这意味着在写出响应实体时,以及在客户端编写要发送到服务器的请求实体时。
编写器和读取器拦截器在执行消息体读取器或编写器之前执行,其主要目的是包装将在消息体读出器和编写器中使用的实体流。
下面的示例显示了一个writer拦截器,它支持整个实体体的GZIP压缩。

示例:GZIP writer interceptor

public class GZIPWriterInterceptor implements WriterInterceptor {
 
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

拦截器从WriterInterceptorContext获取一个输出流,并设置一个新的输出流,它是原始输出流的GZIP包装器。毕竟,拦截器被执行,最后设置为WriterInterceptorContext的输出流将用于实体的序列化。
现在让我们看一个ReaderInterceptor的例子

**示例:**GZIP阅读器拦截器

public class GZIPReaderInterceptor implements ReaderInterceptor {
 
    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context)
                    throws IOException, WebApplicationException {
        final InputStream originalInputStream = context.getInputStream();
        context.setInputStream(new GZIPInputStream(originalInputStream));
        return context.proceed();
    }
}

GZIPReaderInterceptor GZIPInputStream包装原始输入流。对实体流的所有进一步读取都将导致此流解压缩数据。拦截器方法aroundReadFrom()必须返回实体。该实体是从ReaderInterceptorContext的proceed方法返回的
proceed方法在内部调用包装的拦截器,该拦截器还必须返回实体。从链中的最后一个拦截器调用的proceed方法调用消息体读取器,该读取器反序列化实体端返回它。如果有需要,每个拦截器都可以更改此实体,但在大多数情况下,拦截器只会返回从proceed方法返回的实体。

4.过滤器和拦截器执行顺序

让我们仔细看看过滤器和拦截器的执行上下文。以下步骤描述了JAX-RS客户端向服务器发出POST请求的场景。服务器接收到一个实体,并用同一实体发送回响应。GZIP读写器拦截器在客户端和服务器上注册。此外,过滤器在客户端和服务器上注册,更改请求和响应的头。

***调用的客户端请求:**带有附加实体的POST请求构建在客户端上并被调用。
***ClientRequestFilters:**客户端请求过滤器在客户端上执行,它们操作请求头。
***客户端WriterInterceptor:**由于请求包含实体,因此在客户端注册的writer interceptor将在执行MessageBodyWriter之前执行。它用GZipOutputStream包装实体输出流。
***客户端MessageBodyWriter:**消息体编写器在客户端上执行,该客户端将实体序列化到新的GZipOutput流中。此流压缩数据并将其发送到“连线”。
***服务器:**服务器接收请求。实体的数据被压缩,这意味着从实体输入流中纯读取将返回压缩数据。
***服务器预匹配ContainerRequestFilters:**执行ContainerQuestFilters可以操作资源方法匹配过程。
***服务器:**匹配:资源方法匹配完成。
***服务器:**后匹配ContainerRequestFilters:Container请求过滤器后匹配过滤器被执行。这包括执行所有全局筛选器(没有名称绑定)和筛选器绑定到匹配方法的名称。
***Server ReaderInterceptor:**读卡器拦截器在服务器上执行。GZIPReaderInterceptor将输入流(来自“wire”的流)包装到GZipInputStream中,并将其设置为context。
***服务器MessageBodyReader:**执行服务器消息体读取器,并从新的GZipInputStream反序列化实体(从上下文获取)。这意味着阅读器将从“连线”读取解压缩的数据,而不是压缩的数据。
***执行服务器资源方法:**反序列化的实体对象作为参数传递给匹配的资源方法。该方法将此实体作为响应实体返回。
***执行服务器ContainerResponseFilters:**在服务器上执行响应筛选器,并操作响应标头。这包括所有全局绑定筛选器(没有名称绑定)和所有绑定到资源方法的筛选器名称。
***服务器WriterInterceptor:**在服务器上执行。它用一个新的GZIPOutStream包装原始输出流。原始流是“连接”的流(底层服务器容器的响应输出流)。

***服务器MessageBodyWriter:**消息体编写器在服务器上执行,该服务器将实体序列化为GZIPOutputStream。此流压缩数据并将其写入原始流,原始流将此压缩数据发送回客户端。
***客户端收到响应:**响应包含压缩的实体数据。
***客户端响应筛选器:**执行客户端响应筛选器并操作响应标头。
***返回客户端响应:javax.ws.rs.core。请求调用返回响应
***客户端代码调用响应。readEntity():**读取实体在客户端上执行,以从响应中提取实体。
***客户端ReaderInterceptor:**客户端读取器拦截器在调用readEntity(Class)时执行。拦截器使用GZIPInputStream包装实体输入流。这将从原始输入流中解压缩数据。
***客户端MessageBodyReaders:**调用客户端消息正文阅读器,从GZIPInputStream读取解压缩数据并反序列化实体。
***客户端:**实体从readEntity()返回。

可以使用@NameBinding注解将筛选器或拦截器分配给资源方法。

示例:

...
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.zip.GZIPInputStream;
 
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
...
 
 
// @Compress annotation is the name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
 
 
@Path("helloworld")
public class HelloWorldResource {
 
    @GET
    @Produces("text/plain")
    public String getHello() {
        return "Hello World!";
    }
 
    @GET
    @Path("too-much-data")
    @Compress
    public String getVeryLongString() {
        String str = ... // very long string
        return str;
    }
}
 
// interceptor will be executed only when resource methods
// annotated with @Compress annotation will be executed
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

上例定义了一个新的@Compress注解,它是一个名称绑定注解,因为它是用@NameBinding进行注解的。上例中的注解是自我描述的。

6.动态绑定

动态绑定是一种以动态方式为资源方法分配过滤器和拦截器的方法。

...
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.container.DynamicFeature;
...
 
@Path("helloworld")
public class HelloWorldResource {
 
    @GET
    @Produces("text/plain")
    public String getHello() {
        return "Hello World!";
    }
 
    @GET
    @Path("too-much-data")
    public String getVeryLongString() {
        String str = ... // very long string
        return str;
    }
}
 
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
public class CompressionDynamicBinding implements DynamicFeature {
 
    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
                && resourceInfo.getResourceMethod()
                    .getName().contains("VeryLongString")) {
            context.register(GZIPWriterInterceptor.class);
        }
    }
}

绑定是使用实现DynamicFeature接口的提供程序完成的。该接口定义了一个带有两个参数(ResourceInfoFeatureContext)的configure方法。ResourceInfo包含有关可以进行绑定的资源和方法的信息。

7.优先事项

如果您注册了更多过滤器和拦截器,您可能需要定义调用它们的确切顺序。顺序可以由javax.annotation.Priority类定义的@Priority注解控制。为过滤器和拦截器分配优先级是一个好做法。

...
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
...
 
@Priority(Priorities.HEADER_DECORATOR)
public class ResponseFilter implements ContainerResponseFilter {
 
    @Override
    public void filter(ContainerRequestContext requestContext,
                    ContainerResponseContext responseContext)
                    throws IOException {
 
        responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
    }
}

参考:https://jersey.github.io/documentation/latest/filters-and-interceptors.html

相关文章