如何使用CXF、JAX-RS和HTTP缓存

17

CXF文档将缓存作为高级HTTP功能进行了说明:

CXF JAXRS通过处理If-Match、If-Modified-Since和ETags头提供了对许多高级HTTP特性的支持。可以使用JAXRS请求上下文对象来检查先决条件。还支持Vary、CacheControl、Cookies和Set-Cookies。

我非常想使用(或至少探索)这些功能。然而,“提供支持”听起来很有趣,但并没有在实现此类功能方面提供太多帮助。请问如何使用If-Modified-Since、CacheControl或ETags?

3个回答

27

实际上,答案并不特定于CXF - 它是纯JAX-RS:

// IPersonService.java
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;

@GET
@Path("/person/{id}")
Response getPerson(@PathParam("id") String id, @Context Request request);


// PersonServiceImpl.java
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

public Response getPerson(String name, Request request) {
  Person person = _dao.getPerson(name);

  if (person == null) {
    return Response.noContent().build();
  }

  EntityTag eTag = new EntityTag(person.getUUID() + "-" + person.getVersion());

  CacheControl cc = new CacheControl();
  cc.setMaxAge(600);

  ResponseBuilder builder = request.evaluatePreconditions(person.getUpdated(), eTag);

  if (builder == null) {
    builder = Response.ok(person);
  }

  return builder.cacheControl(cc).lastModified(person.getUpdated()).build();
}

4
好的回答。我的唯一评论是,你生成的EntityTag可能不需要包含人的UUID。重要的是,在同一资源的修订之间eTag发生变化。假设ID是不可变的,UUID与资源路径是冗余的(尽管你的Impl将该参数称为“名称”,因此可能不是不可变的)。另外,你应该确保这个值是表示特定的。例如,如果一个资源的两个表述因媒体类型而异,使用媒体类型值以及版本标识符来制作表示特定的ETag值。 - benvolioT
我从不使用Response对象 - 只是让CXF处理那部分。如果没有它,你会怎么做? - oligofren
@oligofren 我之前从未使用过它们,但那是我找到的唯一解决方案。 - sfussenegger
好的。我创建了一个新问题来询问这个。也许有其他人知道 :) https://dev59.com/WGgu5IYBdhLWcg3wdGsb - oligofren

5

更像是声明性地应用过滤器,可以做一些缓存之类的事情,总之是一个很大的改进。感谢您让我(们)知道。 - sfussenegger

0

CXF没有像这里所解释的那样实现动态过滤:http://www.jalg.net/2012/09/declarative-cache-control-with-jax-rs-2-0

如果您使用自己的对象直接返回而不是CXF响应,那么很难添加缓存控制头。

我通过使用自定义注释并创建一个CXF拦截器来读取此注释并添加标头,找到了一种优雅的方法。

因此,首先创建一个CacheControl注释。

@Target(ElementType.METHOD )
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheControl {
    String value() default "no-cache";
}

然后,将此注释添加到您的CXF操作方法中(如果使用接口,则适用于接口或实现)

@CacheControl("max-age=600")
public Person getPerson(String name) {
    return personService.getPerson(name);
}

然后创建一个CacheControl拦截器来处理注释并将标头添加到响应中。

public class CacheInterceptor extends AbstractOutDatabindingInterceptor{
    public CacheInterceptor() {
        super(Phase.MARSHAL);
    }

    @Override
    public void handleMessage(Message outMessage) throws Fault {
        //search for a CacheControl annotation on the operation
        OperationResourceInfo resourceInfo = outMessage.getExchange().get(OperationResourceInfo.class);
        CacheControl cacheControl = null;
        for (Annotation annot : resourceInfo.getOutAnnotations()) {
            if(annot instanceof CacheControl) {
                cacheControl = (CacheControl) annot;
                break;
            }
        }

        //fast path for no cache control
        if(cacheControl == null) {
            return;
        }

        //search for existing headers or create new ones
        Map<String, List<String>> headers = (Map<String, List<String>>) outMessage.get(Message.PROTOCOL_HEADERS);
        if (headers == null) {
            headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            outMessage.put(Message.PROTOCOL_HEADERS, headers);
        }

        //add Cache-Control header
        headers.put("Cache-Control", Collections.singletonList(cacheControl.value()));
    }
}

最后配置 CXF 使用您的拦截器,您可以在此处找到所有所需信息:http://cxf.apache.org/docs/interceptors.html

希望能够帮助到您。

Loïc


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接