使用Springboot自定義注解,支持SPEL表達(dá)式
Springboot自定義注解,支持SPEL表達(dá)式
舉例,自定義redis模糊刪除注解
1.自定義注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CacheEvictFuzzy { /** * redis key集合,模糊刪除 * @return */ String[] key() default ""; }
2.使用AOP攔截方法,解析注解參數(shù)
import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.annotation.Order; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Set; @Aspect @Order(1) @Component public class CacheCleanFuzzyAspect { Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisUtil redis; //指定要執(zhí)行AOP的方法 @Pointcut(value = "@annotation(cacheEvictFuzzy)") public void pointCut(CacheEvictFuzzy cacheEvictFuzzy){} // 設(shè)置切面為加有 @RedisCacheable注解的方法 @Around("@annotation(cacheEvictFuzzy)") public Object around(ProceedingJoinPoint proceedingJoinPoint, CacheEvictFuzzy cacheEvictFuzzy){ return doRedis(proceedingJoinPoint, cacheEvictFuzzy); } @AfterThrowing(pointcut = "@annotation(cacheEvictFuzzy)", throwing = "error") public void afterThrowing (Throwable error, CacheEvictFuzzy cacheEvictFuzzy){ logger.error(error.getMessage()); } /** * 刪除緩存 * @param proceedingJoinPoint * @param cacheEvictFuzzy * @return */ private Object doRedis (ProceedingJoinPoint proceedingJoinPoint, CacheEvictFuzzy cacheEvictFuzzy){ Object result = null; //得到被切面修飾的方法的參數(shù)列表 Object[] args = proceedingJoinPoint.getArgs(); // 得到被代理的方法 Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod(); String[] keys = cacheEvictFuzzy.key(); Set<String> keySet = null; String realkey = ""; for (int i = 0; i < keys.length; i++) { if (StringUtils.isBlank(keys[i])){ continue; } realkey = parseKey(keys[i], method, args); keySet = redis.keys("*"+realkey+"*"); if (null != keySet && keySet.size()>0){ redis.delKeys(keySet); logger.debug("攔截到方法:" + proceedingJoinPoint.getSignature().getName() + "方法"); logger.debug("刪除的數(shù)據(jù)key為:"+keySet.toString()); } } try { result = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); }finally { return result; } } /** * 獲取緩存的key * key 定義在注解上,支持SPEL表達(dá)式 * @return */ private String parseKey(String key, Method method, Object [] args){ if(StringUtils.isEmpty(key)) return null; //獲取被攔截方法參數(shù)名列表(使用Spring支持類庫) LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paraNameArr = u.getParameterNames(method); //使用SPEL進(jìn)行key的解析 ExpressionParser parser = new SpelExpressionParser(); //SPEL上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法參數(shù)放入SPEL上下文中 for(int i=0;i<paraNameArr.length;i++){ context.setVariable(paraNameArr[i], args[i]); } return parser.parseExpression(key).getValue(context,String.class); } }
完事啦!
大家可以注意到關(guān)鍵方法就是parseKey
/** * 獲取緩存的key * key 定義在注解上,支持SPEL表達(dá)式 * @return */ private String parseKey(String key, Method method, Object [] args){ if(StringUtils.isEmpty(key)) return null; //獲取被攔截方法參數(shù)名列表(使用Spring支持類庫) LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paraNameArr = u.getParameterNames(method); //使用SPEL進(jìn)行key的解析 ExpressionParser parser = new SpelExpressionParser(); //SPEL上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法參數(shù)放入SPEL上下文中 for(int i=0;i<paraNameArr.length;i++){ context.setVariable(paraNameArr[i], args[i]); } return parser.parseExpression(key).getValue(context,String.class); }
自定義注解結(jié)合切面和spel表達(dá)式
在我們的實(shí)際開發(fā)中可能存在這么一種情況,當(dāng)方法參數(shù)中的某些條件成立的時(shí)候,需要執(zhí)行一些邏輯處理,比如輸出日志。而這些代碼可能都是差不多的,那么這個(gè)時(shí)候就可以結(jié)合自定義注解加上切面加上spel表達(dá)式進(jìn)行處理。就比如在spring中我們可以使用@Cacheable(key="#xx")實(shí)現(xiàn)緩存,這個(gè)#xx就是一個(gè)spel表達(dá)式。
需求:我們需要將service層方法中方法的某個(gè)參數(shù)的值大于0.5的方法,輸出方法執(zhí)行日志。(需要了解一些spel表達(dá)式的語法)
實(shí)現(xiàn)步驟:
1、自定義一個(gè)注解Log
2、自定義一個(gè)切面,攔截所有方法上存在@Log注解修飾的方法
3、寫一個(gè)service層方法,方法上標(biāo)注@Log注解
難點(diǎn):
在切面中需要拿到具體執(zhí)行方法的方法名,可以使用spring提供的LocalVariableTableParameterNameDiscoverer來獲取到
自定義一個(gè)注解
注意:注解中的spel的值是必須的,且spel表達(dá)式返回的結(jié)果應(yīng)該是一個(gè)布爾值
/** * 記錄日志信息,當(dāng)spel表但是中的值為true時(shí),輸出日志信息 * * @描述 * @作者 huan * @時(shí)間 2017年10月2日 - 上午10:25:39 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String spel(); String desc() default "描述"; }
自定義一個(gè)service類,在需要攔截的方法上加上@Log注解
寫一個(gè)自定義切面
注意一下解析spel表達(dá)式中context的設(shè)值即可
/** * 日志切面,當(dāng)條件滿足時(shí)輸出日志. * * @描述 * @作者 huan * @時(shí)間 2017年10月2日 - 上午10:32:16 */ @Component @Aspect public class LogAspect { ExpressionParser parser = new SpelExpressionParser(); LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Around("@annotation(log)") public Object invoked(ProceedingJoinPoint pjp, Log log) throws Throwable { Object[] args = pjp.getArgs(); Method method = ((MethodSignature) pjp.getSignature()).getMethod(); String spel = log.spel(); String[] params = discoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); for (int len = 0; len < params.length; len++) { context.setVariable(params[len], args[len]); } Expression expression = parser.parseExpression(spel); if (expression.getValue(context, Boolean.class)) { System.out.println(log.desc() + ",在" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "執(zhí)行方法," + pjp.getTarget().getClass() + "." + method.getName() + "(" + convertArgs(args) + ")"); } return pjp.proceed(); } private String convertArgs(Object[] args) { StringBuilder builder = new StringBuilder(); for (Object arg : args) { if (null == arg) { builder.append("null"); } else { builder.append(arg.toString()); } builder.append(','); } builder.setCharAt(builder.length() - 1, ' '); return builder.toString(); } }
pom文件的依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
測(cè)試
增加內(nèi)容
1、當(dāng)我們想在自己寫的spel表達(dá)式中調(diào)用spring bean 管理的方法時(shí),如何寫。spel表達(dá)式支持使用 @來引用bean,但是此時(shí)需要注入BeanFactory
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Spring中@Value使用詳解及SPEL表達(dá)式
- 詳解Spring中Spel表達(dá)式和el表達(dá)式的區(qū)別
- SpringDataElasticsearch與SpEL表達(dá)式實(shí)現(xiàn)ES動(dòng)態(tài)索引
- Spring AOP如何在注解上使用SPEL表達(dá)式注入對(duì)象
- spring之SpEL表達(dá)式詳解
- 基于spring?@Cacheable?注解的spel表達(dá)式解析執(zhí)行邏輯
- Spring?Cache抽象-使用SpEL表達(dá)式解析
- Spring實(shí)戰(zhàn)之Bean定義中的SpEL表達(dá)式語言支持操作示例
- Spring組件開發(fā)模式支持SPEL表達(dá)式
- Spring spel表達(dá)式使用方法示例
- Spring中SpEL表達(dá)式的使用全解
相關(guān)文章
Spring?Boot?+?Spring?Batch?實(shí)現(xiàn)批處理任務(wù)的詳細(xì)教程
這篇文章主要介紹了Spring?Boot+Spring?Batch實(shí)現(xiàn)批處理任務(wù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08springboot整合mybatis分頁攔截器的問題小結(jié)
springboot整合mybatis分頁攔截器,分頁攔截實(shí)際上就是獲取sql后將sql拼接limit,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07淺談Java中ThreadLocal內(nèi)存泄露的原因及處理方式
內(nèi)存泄漏就是我們申請(qǐng)了內(nèi)存,但是該內(nèi)存一直無法釋放,就會(huì)導(dǎo)致內(nèi)存溢出問題,本文詳細(xì)的介紹了ThreadLocal內(nèi)存泄露的原因及處理方式,感興趣的可以了解一下2023-05-05springboot實(shí)現(xiàn)rabbitmq的隊(duì)列初始化和綁定
這篇文章主要介紹了springboot實(shí)現(xiàn)rabbitmq的隊(duì)列初始化和綁定,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10spring aop實(shí)現(xiàn)接口超時(shí)處理組件的代碼詳解
這篇文章給大家介紹了spring aop實(shí)現(xiàn)接口超時(shí)處理組件,文中有詳細(xì)的實(shí)現(xiàn)思路,并通過代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02Java修飾符 abstract,static,final 的區(qū)別詳解
以下是對(duì)Java修飾符abstract,static,final的區(qū)別進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下2013-09-09Spring Boot學(xué)習(xí)入門之AOP處理請(qǐng)求詳解
AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù),下面這篇文章主要給大家介紹了關(guān)于Spring Boot學(xué)習(xí)入門之AOP處理請(qǐng)求的相關(guān)資料,需要的朋友可以參考下。2017-09-09Spring?Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪問控制的操作方法
這篇文章主要介紹了Spring?Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪問控制,資源權(quán)限表達(dá)式動(dòng)態(tài)權(quán)限控制在Spring Security也是可以實(shí)現(xiàn)的,首先開啟方法級(jí)別的注解安全控制,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04java實(shí)現(xiàn)簡易局域網(wǎng)聊天功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡易局域網(wǎng)聊天功能,使用UDP模式編寫一個(gè)聊天程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04idea下載svn的項(xiàng)目并且運(yùn)行操作
這篇文章主要介紹了idea下載svn的項(xiàng)目并且運(yùn)行操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09