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

Java 如何使用CXF、JAX-RS和HTTP缓存,java,rest,cxf,jax-rs,http-caching,Java,Rest,Cxf,Jax Rs,Http Caching,文档中提到了缓存: CXF-JAXRS通过处理If-Match、If-Modified-Since和ETags头来支持许多高级HTTP特性。JAXRS请求上下文对象可用于检查前提条件。还支持Vary、CacheControl、Cookies和Set Cookies 我对使用(或至少探索)这些特性非常感兴趣。然而,虽然“提供支持”听起来很有趣,但它对实现这些特性并没有特别的帮助。关于如何使用修改后的CacheControl或ETags的任何帮助或提示?实际上,答案并不特定于CXF,而是纯JAX-R

文档中提到了缓存:

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


我对使用(或至少探索)这些特性非常感兴趣。然而,虽然“提供支持”听起来很有趣,但它对实现这些特性并没有特别的帮助。关于如何使用修改后的CacheControl或ETags的任何帮助或提示?

实际上,答案并不特定于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();
}

在即将发布的JAX-RS 2.0中,可以声明性地应用缓存控制,如中所述


你至少可以用Jersey测试一下。但不确定CXF和RESTEasy。

CXF没有实现此处所述的动态筛选:

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

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

首先,创建一个CacheControl注释

@Target(ElementType.METHOD )
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheControl {
    String value() default "no-cache";
}
然后,将此注释添加到您的CXF操作方法中(如果您使用接口,则此注释可用于接口或实现)

然后创建一个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()));
    }
}
公共类CacheInterceptor扩展了AbstractOutDatabindingInterceptor{
公共缓存拦截器(){
超级(阶段元帅);
}
@凌驾
public void handleMessage(Message outMessage)引发错误{
//搜索操作上的CacheControl批注
OperationResourceInfo resourceInfo=outMessage.getExchange().get(OperationResourceInfo.class);
CacheControl CacheControl=null;
对于(注释注释注释:resourceInfo.getOutAnnotations()){
如果(不指定CacheControl的实例){
cacheControl=(cacheControl)annot;
打破
}
}
//无缓存控制的快速路径
if(cacheControl==null){
返回;
}
//搜索现有标题或创建新标题
映射头=(Map)outMessage.get(Message.PROTOCOL\u头);
if(headers==null){
headers=newtreemap(String.CASE不区分大小写顺序);
outMessage.put(Message.PROTOCOL_头,头);
}
//添加缓存控制头
headers.put(“缓存控制”,Collections.singletonList(cacheControl.value());
}
}
最后,将CXF配置为使用拦截器,您可以在此处找到所有需要的信息:

希望这会有帮助


Loïc

回答得很好。我唯一的意见是,您生成的EntityTag可能不需要此人的UUID。只有在同一资源的修订之间更改eTag才是重要的。假设ID是不可变的,UUID与资源路径是冗余的(尽管您的Impl调用该参数“name”,所以它可能不是不可变的。此外,您应该确保此值是特定于表示的。例如,如果一个资源的两个表示形式因媒体类型而异,请将媒体类型值与版本标识符一起使用,以生成特定于表示形式的ETag值。我从不使用响应对象-只需让CXF处理该部分即可。如何处理你可以不用它吗?@oligofren我以前从未使用过它们,但这是我找到的唯一解决方案。好吧。我提出了一个关于这个问题的新问题。也许其他人知道:)这更像是声明式应用过滤器,可以做缓存之类的事情,但无论如何,这是一个很大的改进。谢谢你让我(我们)知道。
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()));
    }
}