Jersey: Default Cache Control to no-cache

前端 未结 5 2142
孤独总比滥情好
孤独总比滥情好 2020-12-25 13:35

While writing a RESTful web service, I am encountering issues if I enable any sort of caching on my client (currently a .NET thick client). By default Jersey is not sending

相关标签:
5条回答
  • 2020-12-25 13:45

    This is easy with Jersey by using a ResourceFilterFactory - you can create any custom annotation you attach to your methods to set cache control settings. ResourceFilterFactories get called for each discovered resource method when the application initializes - in your ResourceFilterFactory you can check if the method has your @CacheControlHeader annotation (or whatever you want to call it) - if not, simply return response filter that adds "no-cache" directive to the response, otherwise it should use the settings from the annotation. Here is an example of how to do that:

    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;
            }
        }
    }
    

    The annotation can look like this:

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

    The ResourceFilterFactory can be registered in your application by adding the following init param to the definition of Jersey servlet in web.xml:

    <init-param>
        <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
        <param-value>package.name.CacheFilterFactory</param-value>
    </init-param>
    
    0 讨论(0)
  • 2020-12-25 13:46

    @martin-matula's solution does not work with JAX-RS 2.0 / Jersey 2.x as ResourceFilterFactory and ResourceFilter have been removed. The solution can be adapted to JAX-RS 2.0 as follows.

    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 needs to be registered with Jersey. I'm doing it via Dropwizard - using environment.jersey().register() - but on standalone systems I understand this can be done for example by letting Jersey scan your classes for @Provider annotations by defining the following in your web.xml:

    <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>
    

    See this post for more information about registering components.

    0 讨论(0)
  • 2020-12-25 14:01

    Based on the solution by @martin-matula I created two Cache annotations. One @NoCache for no caching at all and one @CacheMaxAge for specific caching. The CacheMaxAge takes two arguments so you don't have to calculate the seconds yourself:

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

    The ResourceFilter now has this create method that by default doesn't interfere (so other caching mechanisms keep working):

    @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));
    }
    

    You can see the full solution in my blogpost.

    Thanks for the solution Martin!

    0 讨论(0)
  • 2020-12-25 14:04

    I think you can use the

    isNoCache(true)
    

    which will stop caching in the browser.

    See:

    http://jersey.java.net/nonav/apidocs/1.12/jersey/javax/ws/rs/core/CacheControl.html#isNoCache%28%29

    Hope this helps.

    0 讨论(0)
  • 2020-12-25 14:07

    I found one annotation which can disable caching. You can use following annotation for your API:

    @CacheControl(noCache = true)
    

    Ref: Jersey Annotation for cache control

    0 讨论(0)
提交回复
热议问题