2012-06-07 6 views
20

Beim Schreiben eines RESTful-Webdiensts treten Probleme auf, wenn ich auf meinem Client eine Art Zwischenspeichern (derzeit ein .NET-Thick-Client) aktiviere. Standardmäßig sendet Jersey keine Art von Cache Control Header, so dass der Client die meisten Seiten automatisch zwischenspeichert (was ein gültiges Verhalten zu sein scheint).Jersey: Standard-Cache-Steuerung auf No-Cache

Ich möchte Jersey haben standardmäßig eine Cache-Kontrolle von "No-Cache" senden, und dann insbesondere Antworten überschreiben die Cache-Kontrolle.

Gibt es eine Möglichkeit, dies mit Jersey zu tun?

Ich habe festgestellt, dass RESTEasy die @NoCache Annotation verwenden kann, um die Einstellung für die gesamte Klasse anzugeben, aber ich habe nichts Ähnliches mit Jersey gefunden.

Antwort

23

Dies ist einfach mit Jersey, indem Sie eine ResourceFilterFactory verwenden - Sie können jede benutzerdefinierte Anmerkung erstellen, die Sie an Ihre Methoden anhängen, um Einstellungen für die Cache-Kontrolle festzulegen. ResourceFilterFactories werden für jede erkannte Ressourcenmethode aufgerufen, wenn die Anwendung initialisiert wird - in Ihrer ResourceFilterFactory können Sie überprüfen, ob die Methode Ihre @CacheControlHeader-Annotation hat (oder wie immer Sie sie nennen möchten) - falls nicht, geben Sie einfach den Antwortfilter zurück, der "no-cache" hinzufügt "Direktive zur Antwort, sonst sollte es die Einstellungen aus der Annotation verwenden. Hier ist ein Beispiel dafür, wie man das macht:

public class CacheFilterFactory implements ResourceFilterFactory { 
    private static final List<ResourceFilter> NO_CACHE_FILTER = Collections.<ResourceFilter>singletonList(new CacheResponseFilter("no-cache")); 

    @Override 
    public List<ResourceFilter> create(AbstractMethod am) { 
     CacheControlHeader cch = am.getAnnotation(CacheControlHeader.class); 
     if (cch == null) { 
      return NO_CACHE_FILTER; 
     } else { 
      return Collections.<ResourceFilter>singletonList(new CacheResponseFilter(cch.value())); 
     } 
    } 

    private static class CacheResponseFilter implements ResourceFilter, ContainerResponseFilter { 
     private final String headerValue; 

     CacheResponseFilter(String headerValue) { 
      this.headerValue = headerValue; 
     } 

     @Override 
     public ContainerRequestFilter getRequestFilter() { 
      return null; 
     } 

     @Override 
     public ContainerResponseFilter getResponseFilter() { 
      return this; 
     } 

     @Override 
     public ContainerResponse filter(ContainerRequest request, ContainerResponse response) { 
      // attache Cache Control header to each response based on the annotation value 
      response.getHttpHeaders().putSingle(HttpHeaders.CACHE_CONTROL, headerValue); 
      return response; 
     } 
    } 
} 

Die Anmerkung wie folgt aussehen kann:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface CacheControlHeader { 
    String value(); 
} 

Die ResourceFilterFactory kann durch Hinzufügen der folgenden init param auf die Definition von Jersey in der Anwendung registriert werden Servlet in web.xml:

+0

Danke für die Zusammenfassung so schön. Ich habe das alleine durchforstet, aber das stimmt perfekt den "richtigen Weg" dazu. – Pete

+0

Gibt es eine Möglichkeit, die Cache-Steuerung für alle Methoden auf No-Cache zu setzen, ohne die Annotation für jede Methode hinzufügen zu müssen? –

+0

[Cache-Control mit Anmerkungen mit Jersey] (http://alex.nederlof.com/blog/2013/07/28/caching-using-annotations-with-jersey/) ist viel sauberer und vollständig –

13

Basierend auf der Lösung von @ martin-matula habe ich zwei Cache-Annotationen erstellt. Eine @NoCache für keine Zwischenspeicherung und eine @CacheMaxAge für spezifische Zwischenspeicherung. Die CacheMaxAge zwei Argumente, so dass Sie die Sekunden nicht selbst berechnen müssen:

@GET 
@CacheMaxAge(time = 10, unit = TimeUnit.MINUTES) 
@Path("/awesome") 
public String returnSomethingAwesome() { 
    ... 
} 

Die ResourceFilter jetzt hat diese Methode erstellen, die standardmäßig nicht stört (so andere Caching-Mechanismen arbeiten weiter):

@Override 
public List<ResourceFilter> create(AbstractMethod am) { 
    if (am.isAnnotationPresent(CacheMaxAge.class)) { 
     CacheMaxAge maxAge = am.getAnnotation(CacheMaxAge.class); 
     return newCacheFilter("max-age: " + maxAge.unit().toSeconds(maxAge.time())); 
    } else if (am.isAnnotationPresent(NoCache.class)) { 
     return newCacheFilter("no-cache"); 
    } else { 
     return Collections.emptyList(); 
    } 
} 

private List<ResourceFilter> newCacheFilter(String content) { 
    return Collections 
      .<ResourceFilter> singletonList(new CacheResponseFilter(content)); 
} 

Sie können die vollständige Lösung in my blogpost sehen.

Danke für die Lösung Martin!

6

@ martin-matulas Lösung funktioniert nicht mit JAX-RS 2.0/Jersey 2.x, da ResourceFilterFactory und ResourceFilter entfernt wurden. Die Lösung kann wie folgt an JAX-RS 2.0 angepasst werden.

Annotation:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface CacheControlHeader { 
    String value(); 
} 

DynamicFeature:

@Provider 
public class CacheFilterFactory implements DynamicFeature { 

    private static final CacheResponseFilter NO_CACHE_FILTER = 
      new CacheResponseFilter("no-cache"); 

    @Override 
    public void configure(ResourceInfo resourceInfo, 
         FeatureContext featureContext) { 

    CacheControlHeader cch = resourceInfo.getResourceMethod() 
      .getAnnotation(CacheControlHeader.class); 
    if (cch == null) { 
     featureContext.register(NO_CACHE_FILTER); 
    } else { 
     featureContext.register(new CacheResponseFilter(cch.value())); 
    } 
    } 

    private static class CacheResponseFilter implements ContainerResponseFilter { 
    private final String headerValue; 

    CacheResponseFilter(String headerValue) { 
     this.headerValue = headerValue; 
    } 

    @Override 
    public void filter(ContainerRequestContext containerRequestContext, 
         ContainerResponseContext containerResponseContext) { 
     // attache Cache Control header to each response 
     // based on the annotation value      
     containerResponseContext 
       .getHeaders() 
       .putSingle(HttpHeaders.CACHE_CONTROL, headerValue); 
    } 

    } 
} 

CacheFilterFactory muss mit Jersey registriert werden. Ich mache es über Dropwizard - mit environment.jersey(). Register() - aber auf eigenständigen Systemen Ich verstehe, dass dies beispielsweise getan werden kann, indem Jersey Ihre Klassen für @Provider Annotationen scannt, indem Sie Folgendes in Ihrer web.xml definieren :

<servlet> 
    <servlet-name>my.package.MyApplication</servlet-name> 
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> 

    <!-- Register resources and providers under my.package. --> 
    <init-param> 
     <param-name>jersey.config.server.provider.packages</param-name> 
     <param-value>my.package</param-value> 
    </init-param> 
</servlet> 

Weitere Informationen zum Registrieren von Komponenten finden Sie unter this post.

0

Ich habe eine Annotation gefunden, die Caching deaktivieren kann. Sie können folgende Anmerkung für Ihre API verwenden:

@CacheControl(noCache = true) 

Ref: Jersey Annotation for cache control

+0

Sie verlinken zu einer Klasse org.springframework.http.CacheControl von Spring, die auch keine Annotation ist. Sie haben wahrscheinlich javax.ws.rs.core.CacheControl gemeint, was auch keine Annotation ist. – FelixJongleur42