Статьи

Упрощение кэширования JAX-RS с помощью CDI

В этом посте объясняется (на простом примере), как вы можете использовать производителей CDI, чтобы немного упростить использование семантики управления кэшем в ваших сервисах RESTful.

Заголовок Cache-Control был добавлен в HTTP 1.1 как очень необходимое улучшение по сравнению с заголовком Expires, доступным в HTTP 1.0. Веб-сервисы RESTful могут использовать этот заголовок для масштабирования своих приложений и повышения их эффективности, например, если вы можете кэшировать ответ на предыдущий запрос, то вам, очевидно, нет необходимости повторять тот же запрос к серверу, если вы уверены в тот факт, что ваши кэшированные данные не устарели!

Как помогает JAX-RS?

JAX-RS поддерживает заголовок Cache-Control начиная с его начальной версии (1.0). Класс CacheControl представляет реальный HTTP-заголовок Cache-Control и предоставляет возможность конфигурировать заголовок с помощью простых методов установки. Подробнее о классе CacheControl в javadocs JAX-RS 2.0

jaxrs-кэш-контроль

Так как же использовать класс CacheControl ?

Просто верните объект Response, вокруг которого вы можете обернуть экземпляр класса CacheControl.

01
02
03
04
05
06
07
08
09
10
@Path("/testcache")
public class RESTfulResource {
    @GET
    @Produces("text/plain")
    public Response find(){
        CacheControl cc = new CacheControl();
        cc.setMaxAge(20);
        return Response.ok(UUID.randomUUID().toString()).cacheControl(cc).build();
    }
}

Хотя это относительно удобно для одного метода, повторное создание и возврат объектов CacheControl может вызывать раздражение для нескольких методов.

Производители CDI на помощь!

Производители CDI могут помочь внедрить экземпляры классов, которые технически не являются bean-компонентами (согласно строгому определению) или для классов, над которыми у вас нет контроля, если их украшать областями видимости и определителями.

Идея состоит в том, чтобы

  • Используйте пользовательскую аннотацию ( @CacheControlConfig ) для определения значений по умолчанию для заголовка Cache-Control и обеспечения гибкости в случае, если вы хотите переопределить его
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    @Retention(RUNTIME)
    @Target({FIELD, PARAMETER})
    public @interface CachControlConfig {
         
        public boolean isPrivate() default true;
        public boolean noCache() default false;
        public boolean noStore() default false;
        public boolean noTransform() default true;
        public boolean mustRevalidate() default true;
        public boolean proxyRevalidate() default false;
        public int maxAge() default 0;
        public int sMaxAge() default 0;
     
    }
  • Просто используйте CDI Producer для создания экземпляра класса CacheControl с помощью объекта InjectionPoint (с удовольствием вводится CDI!) В зависимости от параметров аннотации
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class CacheControlFactory {
     
        @Produces
        public CacheControl get(InjectionPoint ip) {
     
            CachControlConfig ccConfig = ip.getAnnotated().getAnnotation(CachControlConfig.class);
            CacheControl cc = null;
            if (ccConfig != null) {
                cc = new CacheControl();
                cc.setMaxAge(ccConfig.maxAge());
                cc.setMustRevalidate(ccConfig.mustRevalidate());
                cc.setNoCache(ccConfig.noCache());
                cc.setNoStore(ccConfig.noStore());
                cc.setNoTransform(ccConfig.noTransform());
                cc.setPrivate(ccConfig.isPrivate());
                cc.setProxyRevalidate(ccConfig.proxyRevalidate());
                cc.setSMaxAge(ccConfig.sMaxAge());
            }
     
            return cc;
        }
    }
  • Просто внедрите экземпляр CacheControl в свой класс ресурсов REST и используйте его в своих методах
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    @Path("/testcache")
    public class RESTfulResource {
        @Inject
        @CachControlConfig(maxAge = 20)
        CacheControl cc;
     
        @GET
        @Produces("text/plain")
        public Response find() {
            return Response.ok(UUID.randomUUID().toString()).cacheControl(cc).build();
        }
    }

Дополнительные мысли

  • В этом случае область действия созданного экземпляра CacheControl равна @Dependent, т.е. он будет жить и умирать с классом, который его внедрил . В этом случае сам ресурс JAX-RS является RequestScoped (по умолчанию), поскольку контейнер JAX-RS создает новый экземпляр для каждого клиентского запроса, следовательно, новый экземпляр внедренного экземпляра CacheControl будет создаваться вместе с каждым HTTP-запросом.
  • Вы также можете ввести классификаторы CDI для дальнейшего сужения областей и учета угловых случаев.
  • Вы можете подумать, что того же можно достичь с помощью фильтра JAX-RS. Это правильно. Но вам нужно будет установить заголовок Cache-Control вручную (в изменяемой переменной MultivaluedMap), и логика не будет достаточно гибкой, чтобы учитывать различные конфигурации Cache-Control для разных сценариев

Результаты эксперимента

Используйте среду IDE NetBeans, чтобы поиграть с этим примером (рекомендуется)

  • Разверните WAR и перейдите по адресу http: // localhost: 8080 / JAX-RS-Caching-CDI / testcache
  • Случайная строка, которая будет кэшироваться в течение 20 секунд (согласно конфигурации с помощью аннотации @CacheControl)

    начально-запрос

  • Запрос GET на тот же URL не приведет к вызову службы REST на стороне сервера. Браузер вернет кэшированное значение.

    второй запрос

Хотя код прост, но если вам лень, вы можете взять отсюда (maven) проект и поиграть

Повеселись!

Ссылка: Упрощение кэширования JAX-RS с помощью CDI от нашего партнера JCG Абхишека Гупты в блоге Object Oriented ..