8.5.2 使用@Cacheable执行缓存
@Cacheable
注解可用于修饰类或修饰方法,
- 当使用
@Cacheable
注解修饰类时,用于告诉Spring
在类级别上进行缓存,此时程序调用该类的实例的任何方法时都需要缓存,而且共享同一个缓存区;
- 当使用
@Cacheable
注解修饰方法时,用于告诉Spring
在方法级别上进行缓存,此时只有当程序调用该方法时才需要缓存。
1. 类级别的缓存
使用@Cacheable
注解修饰类时,就可控制Spring
在类级别进行缓存,这样当程序调用该类的任意方法时,只要传入的参数相同, Spring
就会使用缓存。
程序示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\EhCache └─src\ ├─beans.xml ├─ehcache.xml ├─lee\ │ └─SpringTest.java └─org\ └─crazyit\ └─app\ ├─domain\ │ └─User.java └─service\ ├─impl\ │ └─UserServiceImpl.java └─UserService.java
|
假设本示例有如下UserServiceImpl
组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... @Service("userService")
@Cacheable(value = "users") public class UserServiceImpl implements UserService { public User getUsersByNameAndAge(String name, int age) { System.out.println("--正在执行findUsersByNameAndAge()查询方法--"); return new User(name, age); } public User getAnotherUser(String name, int age) { System.out.println("--正在执行findAnotherUser()查询方法--"); return new User(name, age); } }
|
上面程序中的粗体字代码指定对UserServiceImpl
进行类级别的缓存
,这样程序调用该类的任意方法时,只要传入的参数相同, Spring
就会使用缓存。
此处所指的缓存的意义是:当程序第一次调用该类的实例的某个方法时, Spring
缓存机制会将该方法返回的数据放入指定缓存区——就是@Cacheable
注解的value
属性值所指定的缓存区(注意此处指定将数据放入users
缓存区,这就要求前面为缓存管理器配置过名为users
的缓存区)。以后程序调用该类的实例的任何方法时,只要传入的参数相同, Spring
将不会真正执行该方法,而是直接利用缓存区中的数据。
例如如下程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ... public class SpringTest { public static void main(String[] args) { @SuppressWarnings("resource") ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml"); UserService us = ctx.getBean("userService", UserService.class); User u1 = us.getUsersByNameAndAge("孙悟空", 500); User u2 = us.getAnotherUser("孙悟空", 500); System.out.println(u1 == u2); } }
|
上面程序中的两行粗体字代码先后调用了UserServiceImpl
的两个不同方法,但由于程序传入的方法参数相同,因此Spring
不会真正执行第二次调用的方法,而是直接复用缓存区中的数据。
编译、运行该程序,可以看到如下输出:
1 2
| --正在执行findUsersByNameAndAge()查询方法-- true
|
从上面输出结果可以看出,程序并未真正指定第二次调用getAnotherUser
方法。
类级别缓存默认以方法的参数作为key来缓存方法返回的数据
由此可见,类级别的缓存默认以所有方法参数作为key
来缓存方法返回的数据——同一个类不管调用哪个方法,只要调用方法时传入的参数相同, Spring
都会直接利用缓存区中的数据。
@Cacheable注解的属性
使用@Cacheable
时可指定如下属性。
属性 |
描述 |
value |
必需属性。该属性可指定多个缓存区的名字,用于指定将方法返回值放入指定的缓存区内。 |
key |
通过SpEL 表达式显式指定缓存的key |
condition |
该属性指定一个返回boolean 值的SpEL 表达式,只有当该表达式返回true 时, Spring 才会缓存 方法返回值。 |
unless |
该属性指定一个返回boolean 值的spEL 表达式,当该表达式返回true 时, Spring 就不缓存 方法返回值 |
@CachePut注解
与@Cacheable
注解功能类似的还有一个@CachePut
注解,@CachePut
注解同样会让Spring
将方法返回值放入缓存区。与@Cacheable
不同的是,@CachePut
修饰的方法不会读取缓存区中的数据——这意味着不管缓存区是否已有数据,@CachePut
总会告诉Spring
要重新执行这些方法,并再次将方法返回值放入缓存区.
@Cacheable的key属性详解
程序示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\key └─src\ ├─beans.xml ├─ehcache.xml ├─lee\ │ └─SpringTest.java └─org\ └─crazyit\ └─app\ ├─domain\ │ └─User.java └─service\ ├─impl\ │ └─UserServiceImpl.java └─UserService.java
|
例如,将上面程序中UserServiceImpl
的注解改为如下形式
1 2 3 4 5 6 7 8 9 10
| @Service("userService") ... @Cacheable( key = "#name", value = "users" ) public class UserServiceImpl implements UserService { ... }
|
上面的粗体字代码显式指定以name
参数作为缓存的key
,这样只要调用的方法具有相同的name
参数, Spring
缓存机制就会生效。使用如下主程序来测试它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... public class SpringTest { public static void main(String[] args) { @SuppressWarnings("resource") ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml"); UserService us = ctx.getBean("userService", UserService.class); User u1 = us.getUsersByNameAndAge("孙悟空", 500); User u2 = us.getAnotherUser("孙悟空", 400); System.out.println(u1 == u2); } }
|
上面程序两次调用方法时传入的参数并不完全相同,只有name
参数相同,但由于前面使用@Cacheable
注解时显式指定了key="#name"
,这就意味着缓存使用name
参数作为缓存的key
,因此上面两次调用方法将依然只执行第一次调用,第二次调用将直接使用缓存的数据,不会真正执行该方法。
@Cacheable注解的condition属性unless属性详解
condition
属性与unless
属性的功能基本相似,但规则恰好相反:
- 当
condtion
指定的条件为true
时,Spring
缓存机制才会执行缓存;
- 当
unless
指定的条件为true
时, Spring
缓存机制就不执行缓存。
程序示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\condition └─src\ ├─beans.xml ├─ehcache.xml ├─lee\ │ └─SpringTest.java └─org\ └─crazyit\ └─app\ ├─domain\ │ └─User.java └─service\ ├─impl\ │ └─UserServiceImpl.java └─UserService.java
|
例如,将程序中UserServiceImpl
类中的注解改为如下形式
1 2 3 4 5 6 7
| ... @Service("userService") @Cacheable(value = "users" , condition="#age<100") public class UserServiceImpl implements UserService { ... }
|
上面代码显式指定Spring
缓存生效的条件是#age<100
,这样只要调用方法时age
参数小于, Spring
缓存机制就会生效。使用如下主程序来测试它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ... public class SpringTest { public static void main(String[] args) { @SuppressWarnings("resource") ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml"); UserService us = ctx.getBean("userService", UserService.class); User u1 = us.getUsersByNameAndAge("孙悟空", 500); User u2 = us.getAnotherUser("孙悟空", 500); System.out.println(u1 == u2); User u3 = us.getUsersByNameAndAge("孙悟空", 50); User u4 = us.getAnotherUser("孙悟空", 50); System.out.println(u3 == u4); } }
|
上面程序中前两行代码调用方法时age
参数大于100,因此前两行代码不会使用缓存。但程序后面两行代码调用方法时age
参数小于100,因此后面两行代码会使用缓存。编译、运行该示例,可以看到如下输出:
1 2 3 4 5
| --正在执行findUsersByNameAndAge()查询方法-- --正在执行findAnotherUser()查询方法-- false --正在执行findUsersByNameAndAge()查询方法-- true
|
2. 方法级别的缓存
使用@Cacheable
修饰方法时,就可控制Spring
在方法级别进行缓存,这样当程序调用该方法时,只要传入的参数相同, Spring
就会使用缓存。
例如,将前面的UserDaoImpl
改为如下形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... @Service("userService") public class UserServiceImpl implements UserService { @Cacheable(value = "users1") public User getUsersByNameAndAge(String name, int age) { System.out.println("--正在执行findUsersByNameAndAge()查询方法--"); return new User(name, age); } @Cacheable(value = "users2") public User getAnotherUser(String name, int age) { System.out.println("--正在执行findAnotherUser()查询方法--"); return new User(name, age); } }
|
上面两行粗体字代码指定getUsersByNameAndAge
和getAnotherUser
这两个方法分别使用不同的缓存区,这意味着这两个方法都会缓存,但由于它们使用了不同的缓存区,因此它们不能共享缓存数据。
上面程序需要分别使用users1
、 users2
两个缓存区,因此还需要在ehcache.xml
文件中配置这两个缓存区,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?xml version="1.0" encoding="gbk"?> <ehcache> <diskStore path="java.io.tmpdir" /> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <cache name="users1" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="600" /> <cache name="users2" maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="600" /> </ehcache>
|
使用如下主程序来测试它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ... public class SpringTest { public static void main(String[] args) { @SuppressWarnings("resource") ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml"); UserService us = ctx.getBean("userService", UserService.class); User u1 = us.getUsersByNameAndAge("孙悟空", 500); User u2 = us.getAnotherUser("孙悟空", 500); System.out.println(u1 == u2); User u3 = us.getAnotherUser("孙悟空", 500); System.out.println(u2 == u3); } }
|
上面程序中的代码1和代码2分别调用了不同的方法,由于这两个方法分别使用不同的缓存区,因此它们不能共享缓存,所以代码2也需要真正执行。代码3与代码2调用的是同一个方法,而且方法参数相同,因此代码3会直接使用缓存中保存的方法返回值.
运行上面程序,将看到如下输出:
1 2 3 4
| --正在执行findUsersByNameAndAge()查询方法-- --正在执行findAnotherUser()查询方法-- false true
|