Spring?Cache抽象-使用SpEL表達式解析
Spring Cache抽象-使用SpEL表達式
概述
在Spring Cache注解屬性中(比如key,condition和unless),Spring的緩存抽象使用了SpEl表達式,從而提供了屬性值的動態(tài)生成及足夠的靈活性。
下面的代碼根據(jù)用戶的userCode進行緩存,對于key屬性,使用了表達式自定義鍵的生成。
public class UserService { private Map<Integer, User> users = new HashMap<Integer, User>(); { users.put(1, new User("1", "w1",37)); users.put(2, new User("2", "w2", 34)); } @Cacheable(value = "users", key = "#user.userCode" condition = "#user.age < 35") public User getUser(User user) { System.out.println("User with id " + user.getUserId() + " requested."); return users.get(Integer.valueOf(user.getUserId())); }
SpEl表達式
SpEL表達式可基于上下文并通過使用緩存抽象,提供與root獨享相關(guān)聯(lián)的緩存特定的內(nèi)置參數(shù)。
名稱 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root對象 | 當前被調(diào)用的方法名 | #root.methodname |
method | root對象 | 當前被調(diào)用的方法 | #root.method.name |
target | root對象 | 當前被調(diào)用的目標對象實例 | #root.target |
targetClass | root對象 | 當前被調(diào)用的目標對象的類 | #root.targetClass |
args | root對象 | 當前被調(diào)用的方法的參數(shù)列表 | #root.args[0] |
caches | root對象 | 當前方法調(diào)用使用的緩存列表 | #root.caches[0].name |
Argument Name | 執(zhí)行上下文 | 當前被調(diào)用的方法的參數(shù),如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數(shù) | #artsian.id |
result | 執(zhí)行上下文 | 方法執(zhí)行后的返回值(僅當方法執(zhí)行后的判斷有效,如 unless cacheEvict的beforeInvocation=false) | #result |
如何讓自定義注解支持SpEL表達式
SpEL
:即Spring Expression Language,是一種強大的表達式語言。在Spring產(chǎn)品組合中,它是表達式計算的基礎。它支持在運行時查詢和操作對象圖,它可以與基于XML和基于注解的Spring配置還有bean定義一起使用。由于它能夠在運行時動態(tài)分配值,因此可以為我們節(jié)省大量Java代碼??梢杂糜诮馕鎏厥庾址ū热鏐ean的屬性可以直接在字符串中的點出來)。SpEL的應用
:常見的應用,如注解上的使用,Spring緩存中使用的注解
@cachable(key="#user.uId") public User createUser(User user) { return user; }
SpEL還可以用在xml等等上面的解析,大家可以去查閱相關(guān)資料。本文主要介紹如果將SpEl與自定義注解相結(jié)合,從而解析出自定義注解value的實際值。
- Spring緩存操作起來非常方便,只需要加上注解便可實現(xiàn),Spring也提供的CacheManager,使用者可以配置Redis使用Redis緩存。Spring注解也支持自定義的Key命名,功能已經(jīng)挺齊全了。
- 但是,如果想要更多的自定義緩存數(shù)據(jù)存儲格式,比如說緩存的數(shù)據(jù)之間是有層次關(guān)系的(比如視頻稿件包含視頻,視頻下面又包含了視頻彈幕),此時想要在更新最頂層的視頻彈幕時,不刪除整個緩存,而只是更新某個視頻稿件下的某個視頻的這一條視頻彈幕,Spring提供的緩存注解似乎有點不夠用。
- 此時有的開發(fā)者可能會想到使用自定義注解+AOP+Jedis來更加細分緩存的存儲結(jié)構(gòu),但是又想用到強大的SpEL表達式來為自定義注解的值賦值(不使用SpEL的話,需要在AOP中獲取入?yún)⒒蛘叻祷刂?,但是每個方法的數(shù)據(jù)類型又不相同,想要拿到特定的值,便需要類型判斷-轉(zhuǎn)換),此時便可以使用SpEL提供的SpelExpressionParser工具來進行解析注解的值,使用十分方便,只需按照SpEL的規(guī)則(#)來書寫即可。
使用方法
generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint)方法封裝了SpelExpressionParser解析SpEL的方法,使用時只需要傳入spELString:注解的值以及AOP的 joinPoint即可,SpelExpressionParser便會自動的為我們解析出注解的實際值
/** * 用于SpEL表達式解析. */ private SpelExpressionParser parser = new SpelExpressionParser(); /** * 用于獲取方法參數(shù)定義名字. */ private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); public String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) { // 通過joinPoint獲取被注解方法 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); // 使用spring的DefaultParameterNameDiscoverer獲取方法形參名數(shù)組 String[] paramNames = nameDiscoverer.getParameterNames(method); // 解析過后的Spring表達式對象 Expression expression = parser.parseExpression(spELString); // spring的表達式上下文對象 EvaluationContext context = new StandardEvaluationContext(); // 通過joinPoint獲取被注解方法的形參 Object[] args = joinPoint.getArgs(); // 給上下文賦值 for(int i = 0 ; i < args.length ; i++) { context.setVariable(paramNames[i], args[i]); } // 表達式從上下文中計算出實際參數(shù)值 /*如: @annotation(key="#student.name") method(Student student) 那么就可以解析出方法形參的某屬性值,return “xiaoming”; */ return expression.getValue(context).toString(); }
使用案例
1.準備
①.SpringAop相關(guān)jar包,
②.Spring-expression
2.自定義注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SelectRedisCache { String key(); //Redis緩存的HK String fieldKey() ; //Redis緩存的K //默認十個小時清空 int expireTime() default 36000; }
3.定義AOP攔截注解對方法增強進行讀寫緩存
@Aspect public class SelectRedisCacheAop extends SPELUtil { private Map<String,Map<String,Object>> redisMap = new HashMap(); @Around("execution(@com.XliXli.annotation.consuRedisCache.SelectRedisCache * *.*(..)) && @annotation(cacheable)") public Object aroundCacheable(ProceedingJoinPoint joinPoint, SelectRedisCache cacheable) throws Throwable { //首先獲取注解的實際值,如果是SpEl表達式則進行解析 String key = ""; String fieldKey = ""; Object redisObj = null; try { if (!cacheable.key().contains("#")) { //注解的值非SPEL表達式,直接解析就好 key = cacheable.key(); } else { //使用注解中的key, 支持SpEL表達式 String spEL = cacheable.key(); //調(diào)用SpelExpressionParser方法解析出注解的實際值 key = generateKeyBySpEL(spEL, joinPoint); System.out.println("key=" +key); } //獲取fieldKey,同上面的key一樣 if (cacheable.fieldKey().equals("")) { //等于空,則查詢整個大Key fieldKey = "SelectString"; } else { //使用注解中的key, 支持SpEL表達式 String spEL = cacheable.fieldKey(); fieldKey = generateKeyBySpEL(spEL, joinPoint); } //如果注解的fieldKey值為"",則查詢大Key if (fieldKey.equals("SelectString")) { //直接查詢的是大Key Set keys = redisMap.get(key).keySet(); //使用集合來接收查詢出來的對象 List<Object> redisList = new ArrayList<>(); //遍歷緩存fieldKey,查出緩存中每一個對象,放入redisList中 for (Object fieldKey2 : keys) { Object innerObj = redisMap.get(key).get(fieldKey2); redisList.add(innerObj); } redisObj = redisList; if (redisList == null || redisList.size() <1) { redisObj = null; } } else { //否則,查詢的是單個對象 redisObj =redisMap.get(key).get(fieldKey); } if (redisObj !=null) { return redisObj; } }catch (Exception e) { Exception e2 = new Exception("查詢不到緩存異常"); e.printStackTrace(); e2.printStackTrace(); } //以上,是使用AOP攔截查詢方法,如果緩存中存在,則直接返回緩存結(jié)果, //減少數(shù)據(jù)庫查詢壓力。 //沒有緩存則讀取MySQL System.out.println("查詢不到緩存"); //執(zhí)行方法 Object resultOld = joinPoint.proceed(); //查詢結(jié)果不為空,則存入緩存,便于下次直接從緩存中查詢數(shù)據(jù) if (resultOld != null) { try { // 然后將讀取的結(jié)果保存至Redis緩存 boolean resultRow = false; if (fieldKey.equals("SelectString")) { //保存的是集合,需要遍歷存儲 //先類型強轉(zhuǎn) List<Object> objectList = (List<Object>) resultOld; //遍歷返回值集合,進行緩存 for (Object o : objectList) { //由于不同對象存儲緩存時,使用的key、fieldKey都不相同, //本次模擬都是以數(shù)據(jù)表的主鍵值作為fieldKey來存儲,然后用不同的key作為區(qū)分。 //因此需要進行類型轉(zhuǎn)換來獲取每個不同對象的不同主鍵調(diào)用方法。 //當然,如果你所有的對象獲取主鍵的方法名都一樣的話, //完全可以使用反射中的【使用方法名獲取方法】來調(diào)用對象返回主鍵值。 if (o instanceof Barrage) { //緩存彈幕對象 Barrage barrageO = (Barrage) o; fieldKey = barrageO.getBaId() + ""; //增加單個 redisMap.put(key, new HashMap<>().put(fieldKey,barrageO )) } else if (o instanceof Video){//緩存視頻對象 Video videoO = (Video) o; fieldKey = barrageO.getvId() + ""; //增加單個 redisMap.put(key, new HashMap<>().put(fieldKey,videoO )) } else { //TODO 繼續(xù)增 } } } else { //增加單個 redisTemplate.opsForHash().put(key, fieldKey, resultOld); } } catch (Exception e) { Exception e2 = new Exception("查詢后添加緩存異常"); e.printStackTrace(); e2.printStackTrace(); } } return resultOld; } public String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod()); Expression expression = parser.parseExpression(spELString); EvaluationContext context = new StandardEvaluationContext(); Object[] args = joinPoint.getArgs(); for(int i = 0 ; i < args.length ; i++) { context.setVariable(paramNames[i], args[i]); } return expression.getValue(context).toString(); } }
4.測試
緩存視頻稿件:存儲數(shù)據(jù)格式為:
- VideoByVSIdXXX - VideoId-Video
- VideoByVSId+視頻稿件Id—視頻Id—視頻
@SelectRedisCache(key = "'VideoByVSId' + #V_OriginId", fieldKey = "") public List<Video> findByOrigin(Long V_OriginType, Long V_OriginId) { List<Video> videoList = videoMapper.selectList(new EntityWrapper<Video>().eq("V_OriginType",V_OriginType).eq("V_OriginId",V_OriginId)); for (Video video:videoList) { video.setBarrages(barrageService.findByVId(video.getvId())); } return videoList; }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- Spring中@Value使用詳解及SPEL表達式
- 詳解Spring中Spel表達式和el表達式的區(qū)別
- SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引
- Spring AOP如何在注解上使用SPEL表達式注入對象
- spring之SpEL表達式詳解
- 使用Springboot自定義注解,支持SPEL表達式
- 基于spring?@Cacheable?注解的spel表達式解析執(zhí)行邏輯
- Spring實戰(zhàn)之Bean定義中的SpEL表達式語言支持操作示例
- Spring組件開發(fā)模式支持SPEL表達式
- Spring spel表達式使用方法示例
- Spring中SpEL表達式的使用全解
相關(guān)文章
Spring Boot配置線程池拒絕策略的場景分析(妥善處理好溢出的任務)
本文通過實例代碼給大家介紹下如何為線程池配置拒絕策略、如何自定義拒絕策略。對Spring Boot配置線程池拒絕策略的相關(guān)知識感興趣的朋友一起看看吧2021-09-09Java項目中大批量數(shù)據(jù)查詢導致OOM的解決
本文主要介紹了Java項目中大批量數(shù)據(jù)查詢導致OOM的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06Mybatis批量插入Oracle數(shù)據(jù)的方法實例
在開發(fā)中或多或少都會遇到數(shù)據(jù)批量插入的功能,最近我在做項目的過程中就遇到了這樣一個問題,下面這篇文章主要給大家介紹了關(guān)于Mybatis批量插入Oracle數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2022-01-01使用jackson實現(xiàn)對象json之間的相互轉(zhuǎn)換(spring boot)
這篇文章主要介紹了使用jackson實現(xiàn)對象json之間的相互轉(zhuǎn)換(spring boot),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09