Using JCache Annotations with CDI

JCache (JSR 107 – Java Temporary Caching API) is a specification that will be available under Java EE 8 and will present a standard API for caching solutions (Hazelcast, EhCache, Coherence eg.). The survey has shown that 9.5% (orange) of the community is waiting for JCache.

 

Actually, JCache was going to be present in Java EE 7 but it wasn’t completed by the deadline, it has been deferred to Java EE 8.( Look at JCache to Miss Java EE 7)

JCache Annotations

When using JCache annotations you can meet your caching needs just by declaring JCache annotations on your classes or methods.

Here are JCache’s standard annotations;

JCache Annotations
  1. javax.cache.annotation.CacheDefaults
  2. javax.cache.annotation.CacheKey
  3. javax.cache.annotation.CacheResult
  4. javax.cache.annotation.CacheValue
  5. javax.cache.annotation.CachePut
  6. javax.cache.annotation.CacheRemove
  7. javax.cache.annotation.CacheRemoveAll

CacheDefaults

JCache @CacheDefaults annotation is applied above a class declaration. All the methods inside the applied class gets @CacheDefaults annotation options by default.

For example
@CacheDefaults(cacheName = "personCacher") (1)
public class CacheBean {

///

}
1 Declares default cache name as personCacher. All methods under this class inherit this option if not provided itself.

CachePut

Puts a cache value into the cache object addressed by a key. You can think all the cache object as a Map instance but actually it is a type of javax.cache.Cache.

We can declare which method parameter will be used as the key with @CacheKey annotion, also a value with @CacheValue annotation.

For example
@CacheDefaults(cacheName = "personCacher")
public class CacheBean {

@CachePut
//@CachePut(cacheName = "<cache_name>")
public void put(@CacheKey Long id, @CacheValue Person person) {
   //
}

}

As above a Person object will be put into a javax.cache.Cache object named personCacher with an id. You can keep the method body empty or persist the parameters anywhere (FS, NoSQL, DB eg.) in the method body. It just depends on your needs.

CacheResult

CacheResult is used to get a cached value by its key. If the related value is already in the cache, the cached value is returned from the proxy method without entering method body. if not, execution flow enters into the method body and also puts returned object to the cache.

For example
@CacheDefaults(cacheName = "personCacher")
public class CacheBean {

@CacheResult
public Person get(@CacheKey Long id) { (1)
    Person person = //
    return person;
}

@CachePut
//@CachePut(cacheName = "<cache_name>")
public void put(@CacheKey Long id, @CacheValue Person person) {
   //
}

}
1 Returns the cached value by its key if exist.

CacheRemove

Removes a cached value from cahce by its key.

For example
@CacheDefaults(cacheName = "personCacher")
public class CacheBean {

@CacheRemove
public void invalidate(@CacheKey Long id) {
    //
}

}

We can also use @CacheRemoveAll annotation to remove all cached values.

For example
@CacheDefaults(cacheName = "personCacher")
public class CacheBean {

@CacheRemoveAll
public void invalidateAll() {
    //
}

}

Let’s try

All annotations under javax.cache.annotation.* are only designed so we need to use a DI (Dependency Injection) container technology, to process these annotations in runtime a
nd AOP manner. Yes, we can use any DI container (CDI, Spring and Guice eg.) to use JCache annotations in our applications. It doesn’t matter which one you pick, JCache is a standard.

I want to use CDI container to test JCache annotations. If we have a CDI enabled Java EE application, registering JCache CDI interceptors in beans.xml is enough to start coding JCache. However, I want to use CDI in Java SE environment.

Note

As of  version 3.1 Spring Framework supports JCache annotations.

Here is my project structure

project tree.png
Figure 2. Project structure

The project prepared in maven project format.

  1. pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <packaging>jar</packaging>
    
        <groupId>CDI-JCache</groupId>
        <artifactId>CDI-JCache</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency> (1)
                <groupId>org.jboss.weld.se</groupId>
                <artifactId>weld-se-core</artifactId>
                <version>3.0.0.Alpha13</version>
            </dependency>
            <dependency> (2)
                <groupId>org.jsr107.ri</groupId>
                <artifactId>cache-annotations-ri-cdi</artifactId>
                <version>1.0.0</version>
            </dependency>
            <dependency> (3)
                <groupId>org.jsr107.ri</groupId>
                <artifactId>cache-ri-impl</artifactId>
                <version>1.0.0</version>
            </dependency>
        </dependencies>
    
    </project>
    1 Weld Java SE dependency (CDI RI (Reference Implementation))
    2 Dependency have JCache CDI extensions. (Look at maven.search.org)
    3 JCache’s RI dependency (You can use any other JCache implementation by just replacing this dependency)
  2. beans.xml

    Registers CDI container to use it. We need to declare JCache’s CDI interceptors here.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
    
    <interceptors>
        <class>org.jsr107.ri.annotations.cdi.CacheResultInterceptor</class> (1)
        <class>org.jsr107.ri.annotations.cdi.CacheRemoveEntryInterceptor</class> (2)
        <class>org.jsr107.ri.annotations.cdi.CacheRemoveAllInterceptor</class> (3)
        <class>org.jsr107.ri.annotations.cdi.CachePutInterceptor</class> (4)
    </interceptors>
    
    </beans>
    1 CDI interceptor declaration for @CacheResult annotation.
    2 CDI interceptor declaration for @CacheRemove annotation.
    3 CDI interceptor declaration for @CacheRemoveAll annotation.
    4 CDI interceptor declaration for @CachePut annotation.
  3. com.kodcu.*
    1. com.kodcu.Person

      It will be used as the cached value and required to be Serializable

      public class Person implements Serializable {
      
          private Long id;
          private String name;
          private String status;
      
          public Person() {}
      
          public Person(Long id, String name) {
              this.id = id;
              this.name = name;
          }
      
          public Person(long id) {
              this(id, "Person-" + id);
          }
      
          // getter, setter methods
      
          public void updateStatus() {
              setStatus("Cached " + ThreadLocalRandom.current().nextLong(1, 10000));
          }
      }
    2. com.kodcu.CacheBean

      It is a CDI bean and will be used to test JCache annotations.

      @CacheDefaults(cacheName = "personCacher")
      public class CacheBean {
      
      
          @CachePut
          public void put(@CacheKey Long id, @CacheValue Person person) {
              person.updateStatus();
          }
      
          @CacheResult
          public Person get(@CacheKey Long id) {
              final Person person = new Person(id);
              person.updateStatus();
              return person;
          }
      
          @CacheRemove
          public void invalidate(@CacheKey Long id) {}
      
          @CacheRemoveAll
          public void invalidateAll() {}
      
      }
    3. com.kodcu.App
      public class App {
      
          @Inject
          private CacheBean cacheBean; (3)
      
          public static void main(String[] args) throws Exception {
      
              CDI<Object> cdi = CDI.getCDIProvider().initialize(); (1)
              final App app = cdi.select(App.class).get(); (2)
      
              final CacheBean cacheBean = app.cacheBean;
      
              print("Put first object in cache");
      
              // Put cahce id:1,name:Rahman Usta
              cacheBean.put(1L, new Person(1L));
      
              // Get id:1
              final Person p1 = cacheBean.get(1L);
              print(p1);
              // Get id:1
              final Person p2 = cacheBean.get(1L);
              print(p2);
              // Get id:1
              final Person p3 = cacheBean.get(1L);
              print(p3);
      
              print("\n******\n");
      
              print("Put second object in cache");
              cacheBean.put(2L, new Person(2L));
      
              // Get id:2
              final Person p4 = cacheBean.get(2L);
              print(p4);
              // Get id:2
              final Person p5 = cacheBean.get(2L);
              print(p5);
      
              print("\n******\n");
      
              // Invalidate one
              print("Invalidate first object in cache");
              cacheBean.invalidate(1L);
      
              print(cacheBean.get(1L));
              print(cacheBean.get(2L));
      
              print("\n******\n");
      
              // Invalidate all
              print("Invalidate all in cache");
              cacheBean.invalidateAll();
      
              print(cacheBean.get(1L));
              print(cacheBean.get(2L));
      
          }
      
      
          private static void print(Object object) {
              System.out.println(object);
          }
      
      }
      1 Initializes CDI container
      2 Gets CDI Bean which is instance of App class.
      3 Injects CacheBean CDI Bean

       

      After running App#main method, we will see following result.

      INFO: WELD-ENV-002003: Weld SE container STATIC_INSTANCE initialized
      Kas 02, 2015 11:37:44 PM org.jsr107.ri.annotations.DefaultCacheResolverFactory getCacheResolver
      WARNING: No Cache named 'personCacher' was found in the CacheManager, a default cache will be created.
      
      Put first object in cache (1)
      Get Person{id=1, name='Person-1', status='Cached 5859'} // Cache hit
      Get Person{id=1, name='Person-1', status='Cached 5859'} // Cache hit
      Get Person{id=1, name='Person-1', status='Cached 5859'} // Cache hit
      ******
      Put second object in cache (2)
      Get Person{id=2, name='Person-2', status='Cached 3832'} // Cache hit
      Get Person{id=2, name='Person-2', status='Cached 3832'} // Cache hit
      ******
      Invalidate first object in cache (3)
      Get Person{id=1, name='Person-1', status='Cached 19'} // Cache miss
      Get Person{id=2, name='Person-2', status='Cached 3832'} // Cache hit
      ******
      Invalidate all in cache (4)
      Get Person{id=1, name='Person-1', status='Cached 9648'} // Cache miss
      Get Person{id=2, name='Person-2', status='Cached 9072'} // Cache miss
      Weld SE container STATIC_INSTANCE shut down by shutdown hook
      1 Puts first Person object into the cache, then gets same object until it is removed from cache.
      2 Puts second Person object into the cache, then gets same object until it is removed from cache.
      3 Removes first object from the cache, then returns new cached object.
      4 Removes first and second object from the cache, then returns new cached objects.

You can access demo project from Adop-a-JSR Github page. Download

Hope to see you again.

2 thoughts on “Using JCache Annotations with CDI

  1. Hi!
    Great post!
    Is there any way to setup the expiry police through annotation?
    Would it be nice to have a way to setup of the expiry policy through the @CacheResult or @CachePut?

Bir Cevap Yazın