Jersey Filters and Interceptors – 泽西岛过滤器和拦截器

最后修改: 2018年 3月 4日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this article, we’re going to explain how filters and interceptors work in the Jersey framework, as well as the main differences between these.

在这篇文章中,我们将解释过滤器和拦截器在Jersey框架中如何工作,以及它们之间的主要区别。

We’ll use Jersey 2 here, and we’ll test our application using a Tomcat 9 server.

我们在这里使用Jersey 2,我们将使用Tomcat 9服务器来测试我们的应用程序。

2. Application Setup

2.应用程序的设置

Let’s first create a simple resource on our server:

让我们首先在我们的服务器上创建一个简单的资源。

@Path("/greetings")
public class Greetings {

    @GET
    public String getHelloGreeting() {
        return "hello";
    }
}

Also, let’s create the corresponding server configuration for our application:

另外,让我们为我们的应用程序创建相应的服务器配置。

@ApplicationPath("/*")
public class ServerConfig extends ResourceConfig {

    public ServerConfig() {
        packages("com.baeldung.jersey.server");
    }
}

If you want to dig deeper into how to create an API with Jersey, you can check out this article.

如果您想深入了解如何使用Jersey创建API,您可以查看这篇文章

You can also have a look at our client-focused article and learn how to create a Java client with Jersey.

你也可以看看我们以客户为中心的文章,了解如何用Jersey创建一个Java客户端。

3. Filters

3.过滤器

Now, let’s get started with filters.

现在,让我们开始使用过滤器。

Simply put, filters let us modify the properties of requests and responses – for example, HTTP headers. Filters can be applied both in the server and client side.

简单地说,过滤器让我们修改请求和响应的属性 – 例如,HTTP头。过滤器可以在服务器和客户端应用。

Keep in mind that filters are always executed, regardless of whether the resource was found or not.

请记住,过滤器总是被执行的,无论是否找到了资源,

3.1. Implementing a Request Server Filter

3.1.实现一个请求服务器过滤器

Let’s start with the filters on the server side and create a request filter.

让我们从服务器端的过滤器开始,创建一个请求过滤器。

We’ll do that by implementing the ContainerRequestFilter interface and registering it as a Provider in our server:

我们将通过实现ContainerRequestFilter接口并将其注册为服务器中的Provider来实现:

@Provider
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    
    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getLanguage() != null && "EN".equals(ctx.getLanguage()
          .getLanguage())) {
 
            ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
              .entity("Cannot access")
              .build());
        }
    }
}

This simple filter just rejects the requests with the language “EN” in the request by calling the abortWith() method.

这个简单的过滤器只是通过调用abortWith()方法拒绝请求中含有语言“EN”的请求。

As the example shows, we had to implement only one method that receives the context of the request, which we can modify as we need.

如例子所示,我们只需要实现一个接收请求的上下文的方法,我们可以根据需要修改这个方法。

Let’s keep in mind that this filter is executed after the resource was matched.

让我们记住,这个过滤器是在资源被匹配后执行的。

In case we want to execute a filter before the resource matching, we can use a pre-matching filter by annotating our filter with the @PreMatching annotation:

如果我们想在资源匹配之前执行一个过滤器,我们可以通过用@PreMatching注解来使用一个预匹配过滤器。

@Provider
@PreMatching
public class PrematchingRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        if (ctx.getMethod().equals("DELETE")) {
            LOG.info("\"Deleting request");
        }
    }
}

If we try to access our resource now, we can check that our pre-matching filter is executed first:

如果我们现在尝试访问我们的资源,我们可以检查我们的预匹配过滤器是否首先被执行。

2018-02-25 16:07:27,800 [http-nio-8080-exec-3] INFO  c.b.j.s.f.PrematchingRequestFilter - prematching filter
2018-02-25 16:07:27,816 [http-nio-8080-exec-3] INFO  c.b.j.s.f.RestrictedOperationsRequestFilter - Restricted operations filter

3.2. Implementing a Response Server Filter

3.2.实现一个响应服务器过滤器

We’ll now implement a response filter on the server side that will merely add a new header to the response.

我们现在要在服务器端实现一个响应过滤器,它将只是在响应中添加一个新的头。

To do that, our filter has to implement the ContainerResponseFilter interface and implement its only method:

要做到这一点,我们的过滤器必须实现ContainerResponseFilter接口并实现其唯一方法。

@Provider
public class ResponseServerFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, 
      ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("X-Test", "Filter test");
    }
}

Notice that the ContainerRequestContext parameter is just used as read-only – since we’re already processing the response.

注意,ContainerRequestContext参数只是作为只读使用 – 因为我们已经在处理响应。

2.3. Implementing a Client Filter

2.3.实现客户端过滤器

We’ll work now with filters on the client side. These filters work in the same way as server filters, and the interfaces we have to implement are very similar to the ones for the server side.

我们现在要做的是客户端的过滤器。这些过滤器的工作方式与服务器过滤器相同,我们必须实现的接口与服务器端的非常相似。

Let’s see it in action with a filter that adds a property to the request:

让我们来看看它在行动中的表现,一个过滤器将一个属性添加到请求中。

@Provider
public class RequestClientFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.setProperty("test", "test client request filter");
    }
}

Let’s also create a Jersey client to test this filter:

让我们也创建一个Jersey客户端来测试这个过滤器。

public class JerseyClient {

    private static String URI_GREETINGS = "http://localhost:8080/jersey/greetings";

    public static String getHelloGreeting() {
        return createClient().target(URI_GREETINGS)
          .request()
          .get(String.class);
    }

    private static Client createClient() {
        ClientConfig config = new ClientConfig();
        config.register(RequestClientFilter.class);

        return ClientBuilder.newClient(config);
    }
}

Notice that we have to add the filter to the client configuration to register it.

请注意,我们必须把过滤器添加到客户端配置中来注册它。

Finally, we’ll also create a filter for the response in the client.

最后,我们还将在客户端为响应创建一个过滤器。

This works in a very similar way as the one in the server, but implementing the ClientResponseFilter interface:

它的工作方式与服务器中的非常相似,但实现了ClientResponseFilter接口。

@Provider
public class ResponseClientFilter implements ClientResponseFilter {

    @Override
    public void filter(ClientRequestContext requestContext, 
      ClientResponseContext responseContext) throws IOException {
        responseContext.getHeaders()
          .add("X-Test-Client", "Test response client filter");
    }

}

Again, the ClientRequestContext is for read-only purposes.

同样,ClientRequestContext是用于只读目的。

4. Interceptors

4.拦截器

Interceptors are more connected with the marshalling and unmarshalling of the HTTP message bodies that are contained in the requests and the responses. They can be used both in the server and in the client side.

拦截器与请求和响应中包含的HTTP消息体的编组和解编有很大关系。它们既可以在服务器端使用,也可以在客户端使用。

Keep in mind that they’re executed after the filters and only if a message body is present.

请记住,它们是在过滤器之后执行的,并且只在有消息体存在的情况下执行。

There are two types of interceptors: ReaderInterceptor and WriterInterceptor, and they are the same for both the server and the client side.

有两种类型的拦截器。ReaderInterceptorWriterInterceptor,它们在服务器和客户端都是一样的。

Next, we’re going to create another resource on our server – which is accessed via a POST and receives a parameter in the body, so interceptors will be executed when accessing it:

接下来,我们要在服务器上创建另一个资源–它通过POST访问,并在主体中接收一个参数,所以在访问它时,拦截器将被执行。

@POST
@Path("/custom")
public Response getCustomGreeting(String name) {
    return Response.status(Status.OK.getStatusCode())
      .build();
}

We’ll also add a new method to our Jersey client – to test this new resource:

我们还将为我们的Jersey客户端添加一个新方法–以测试这个新资源。

public static Response getCustomGreeting() {
    return createClient().target(URI_GREETINGS + "/custom")
      .request()
      .post(Entity.text("custom"));
}

4.1. Implementing a ReaderInterceptor

4.1.实现一个ReaderInterceptor

Reader interceptors allow us to manipulate inbound streams, so we can use them to modify the request on the server side or the response on the client side.

读者拦截器允许我们操作入站流,所以我们可以用它们来修改服务器端的请求或客户端的响应。

Let’s create an interceptor on the server side to write a custom message in the body of the request intercepted:

让我们在服务器端创建一个拦截器,在被拦截的请求的正文中写一个自定义的消息。

@Provider
public class RequestServerReaderInterceptor implements ReaderInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) 
      throws IOException, WebApplicationException {
        InputStream is = context.getInputStream();
        String body = new BufferedReader(new InputStreamReader(is)).lines()
          .collect(Collectors.joining("\n"));

        context.setInputStream(new ByteArrayInputStream(
          (body + " message added in server reader interceptor").getBytes()));

        return context.proceed();
    }
}

Notice that we have to call the proceed() method to call the next interceptor in the chain. Once all the interceptors are executed, the appropriate message body reader will be called.

注意我们必须调用proceed() method来调用链中的下一个拦截器。一旦所有的拦截器被执行,相应的消息体阅读器就会被调用。

3.2. Implementing a WriterInterceptor

3.2.实现一个WriterInterceptor

Writer interceptors work in a very similar way to reader interceptors, but they manipulate the outbound streams – so that we can use them with the request in the client side or with the response in the server side.

写入者拦截器的工作方式与读取者拦截器非常相似,但它们会对出站流进行操作–这样我们就可以在客户端的请求或服务器端的响应中使用它们。

Let’s create a writer interceptor to add a message to the request, on the client side:

让我们在客户端创建一个写手拦截器,给请求添加一个消息。

@Provider
public class RequestClientWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) 
      throws IOException, WebApplicationException {
        context.getOutputStream()
          .write(("Message added in the writer interceptor in the client side").getBytes());

        context.proceed();
    }
}

Again, we have to call the method proceed() to call the next interceptor.

同样,我们必须调用方法proceed() 来调用下一个拦截器。

When all the interceptors are executed, the appropriate message body writer will be called.

当所有的拦截器被执行后,相应的消息体编写器将被调用。

Don’t forget that you have to register this interceptor in the client configuration, as we did before with the client filter:

别忘了,你必须在客户端配置中注册这个拦截器,就像我们之前对客户端过滤器所做的那样。

private static Client createClient() {
    ClientConfig config = new ClientConfig();
    config.register(RequestClientFilter.class);
    config.register(RequestWriterInterceptor.class);

    return ClientBuilder.newClient(config);
}

5. Execution Order

5.执行命令

Let’s summarize all that we’ve seen so far in a diagram that shows when the filters and interceptors are executed during a request from a client to a server:

让我们用一张图来总结一下到目前为止我们所看到的一切,该图显示了在客户端向服务器发出请求时过滤器和拦截器的执行情况。

Jersey2

As we can see, the filters are always executed first, and the interceptors are executed right before calling the appropriate message body reader or writer.

我们可以看到,过滤器总是先被执行,而拦截器则在调用相应的消息体阅读器或写入器之前被执行

If we take a look at the filters and interceptors that we’ve created, they will be executed in the following order:

如果我们看一下我们创建的过滤器和拦截器,它们将按以下顺序执行。

  1. RequestClientFilter
  2. RequestClientWriterInterceptor
  3. PrematchingRequestFilter
  4. RestrictedOperationsRequestFilter
  5. RequestServerReaderInterceptor
  6. ResponseServerFilter
  7. ResponseClientFilter

Furthermore, when we have several filters or interceptors, we can specify the exact executing order by annotating them with the @Priority annotation.

此外,当我们有几个过滤器或拦截器时,我们可以通过用@Priority注解来指定准确的执行顺序。

The priority is specified with an Integer and sorts the filters and interceptors in ascending order for the requests and in descending order for the responses.

优先级是用Integer指定的,对请求的过滤器和拦截器按升序排列,对响应的过滤器和拦截器按降序排列。

Let’s add a priority to our RestrictedOperationsRequestFilter:

让我们为我们的RestrictedOperationsRequestFilter添加一个优先级。

@Provider
@Priority(Priorities.AUTHORIZATION)
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

Notice that we’ve used a predefined priority for authorization purposes.

注意,我们为授权目的使用了预定义的优先级。

6. Name Binding

6.名称绑定

The filters and interceptors that we’ve seen so far are called global because they’re executed for every request and response.

到目前为止,我们所看到的过滤器和拦截器被称为全局性的,因为它们对每个请求和响应都要执行。

However, they can also be defined to be executed only for specific resource methods, which is called name binding.

然而,它们也可以被定义为只对特定的资源方法执行,这被称为名称绑定。

6.1. Static Binding

6.1.静态绑定

One way to do the name binding is statically by creating a particular annotation that will be used in the desired resource. This annotation has to include the @NameBinding meta-annotation.

做名称绑定的一种方法是静态地创建一个特定的注解,该注解将被用于所需的资源中。这个注解必须包括@NameBinding元注解。

Let’s create one in our application:

让我们在我们的应用程序中创建一个。

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloBinding {
}

After that, we can annotate some resources with this @HelloBinding annotation:

之后,我们可以用这个@HelloBinding注解来注解一些资源。

@GET
@HelloBinding
public String getHelloGreeting() {
    return "hello";
}

Finally, we’re going to annotate one of our filters with this annotation too, so this filter will be executed only for requests and responses that are accessing the getHelloGreeting() method:

最后,我们也要用这个注解来注释我们的一个过滤器,所以这个过滤器将只对访问getHelloGreeting()方法的请求和响应执行。

@Provider
@Priority(Priorities.AUTHORIZATION)
@HelloBinding
public class RestrictedOperationsRequestFilter implements ContainerRequestFilter {
    // ...
}

Keep in mind that our RestrictedOperationsRequestFilter won’t be triggered for the rest of the resources anymore.

请记住,我们的RestrictedOperationsRequestFilter将不会再为其余的资源触发。

6.2. Dynamic Binding

6.2.动态绑定

Another way to do this is by using a dynamic binding, which is loaded in the configuration during startup.

另一种方法是使用动态绑定,它在启动时被加载到配置中。

Let’s first add another resource to our server for this section:

让我们首先为本节添加另一个资源到我们的服务器。

@GET
@Path("/hi")
public String getHiGreeting() {
    return "hi";
}

Now, let’s create a binding for this resource by implementing the DynamicFeature interface:

现在,让我们通过实现DynamicFeature接口为该资源创建一个绑定。

@Provider
public class HelloDynamicBinding implements DynamicFeature {

    @Override
    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
        if (Greetings.class.equals(resourceInfo.getResourceClass()) 
          && resourceInfo.getResourceMethod().getName().contains("HiGreeting")) {
            context.register(ResponseServerFilter.class);
        }
    }
}

In this case, we’re associating the getHiGreeting() method to the ResponseServerFilter that we had created before.

在这种情况下,我们将getHiGreeting()方法与我们之前创建的ResponseServerFilter相关联。

It’s important to remember that we had to delete the @Provider annotation from this filter since we’re now configuring it via DynamicFeature.

重要的是要记住,我们必须从这个过滤器中删除@Provider注解,因为我们现在是通过DynamicFeature来配置它。

If we don’t do this, the filter will be executed twice: one time as a global filter and another time as a filter bound to the getHiGreeting() method.

如果我们不这样做,过滤器将被执行两次:一次是作为全局过滤器,另一次是作为绑定到getHiGreeting()方法的过滤器。

7. Conclusion

7.结论

In this tutorial, we focused on understanding how filters and interceptors work in Jersey 2 and how we can use them in a web application.

在本教程中,我们重点了解过滤器和拦截器在Jersey 2中的工作原理,以及如何在Web应用中使用它们。

As always, the full source code for the examples is available over on GitHub.

一如既往,这些示例的完整源代码可在GitHub上获取