使用Springboot自定義注解,支持SPEL表達式
Springboot自定義注解,支持SPEL表達式
舉例,自定義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攔截方法,解析注解參數
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){}
// 設置切面為加有 @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;
//得到被切面修飾的方法的參數列表
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("刪除的數據key為:"+keySet.toString());
}
}
try {
result = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
return result;
}
}
/**
* 獲取緩存的key
* key 定義在注解上,支持SPEL表達式
* @return
*/
private String parseKey(String key, Method method, Object [] args){
if(StringUtils.isEmpty(key)) return null;
//獲取被攔截方法參數名列表(使用Spring支持類庫)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL進行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法參數放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context,String.class);
}
}完事啦!
大家可以注意到關鍵方法就是parseKey
/**
* 獲取緩存的key
* key 定義在注解上,支持SPEL表達式
* @return
*/
private String parseKey(String key, Method method, Object [] args){
if(StringUtils.isEmpty(key)) return null;
//獲取被攔截方法參數名列表(使用Spring支持類庫)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
//使用SPEL進行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法參數放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context,String.class);
}自定義注解結合切面和spel表達式
在我們的實際開發(fā)中可能存在這么一種情況,當方法參數中的某些條件成立的時候,需要執(zhí)行一些邏輯處理,比如輸出日志。而這些代碼可能都是差不多的,那么這個時候就可以結合自定義注解加上切面加上spel表達式進行處理。就比如在spring中我們可以使用@Cacheable(key="#xx")實現(xiàn)緩存,這個#xx就是一個spel表達式。
需求:我們需要將service層方法中方法的某個參數的值大于0.5的方法,輸出方法執(zhí)行日志。(需要了解一些spel表達式的語法)
實現(xiàn)步驟:
1、自定義一個注解Log
2、自定義一個切面,攔截所有方法上存在@Log注解修飾的方法
3、寫一個service層方法,方法上標注@Log注解
難點:
在切面中需要拿到具體執(zhí)行方法的方法名,可以使用spring提供的LocalVariableTableParameterNameDiscoverer來獲取到
自定義一個注解
注意:注解中的spel的值是必須的,且spel表達式返回的結果應該是一個布爾值
/**
* 記錄日志信息,當spel表但是中的值為true時,輸出日志信息
*
* @描述
* @作者 huan
* @時間 2017年10月2日 - 上午10:25:39
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String spel();
String desc() default "描述";
}自定義一個service類,在需要攔截的方法上加上@Log注解

寫一個自定義切面
注意一下解析spel表達式中context的設值即可
/**
* 日志切面,當條件滿足時輸出日志.
*
* @描述
* @作者 huan
* @時間 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>
測試

增加內容
1、當我們想在自己寫的spel表達式中調用spring bean 管理的方法時,如何寫。spel表達式支持使用 @來引用bean,但是此時需要注入BeanFactory

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- Spring中@Value使用詳解及SPEL表達式
- 詳解Spring中Spel表達式和el表達式的區(qū)別
- SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引
- Spring AOP如何在注解上使用SPEL表達式注入對象
- spring之SpEL表達式詳解
- 基于spring?@Cacheable?注解的spel表達式解析執(zhí)行邏輯
- Spring?Cache抽象-使用SpEL表達式解析
- Spring實戰(zhàn)之Bean定義中的SpEL表達式語言支持操作示例
- Spring組件開發(fā)模式支持SPEL表達式
- Spring spel表達式使用方法示例
- Spring中SpEL表達式的使用全解
相關文章
Spring?Boot?+?Spring?Batch?實現(xiàn)批處理任務的詳細教程
這篇文章主要介紹了Spring?Boot+Spring?Batch實現(xiàn)批處理任務,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
淺談Java中ThreadLocal內存泄露的原因及處理方式
內存泄漏就是我們申請了內存,但是該內存一直無法釋放,就會導致內存溢出問題,本文詳細的介紹了ThreadLocal內存泄露的原因及處理方式,感興趣的可以了解一下2023-05-05
springboot實現(xiàn)rabbitmq的隊列初始化和綁定
這篇文章主要介紹了springboot實現(xiàn)rabbitmq的隊列初始化和綁定,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-10-10
spring aop實現(xiàn)接口超時處理組件的代碼詳解
這篇文章給大家介紹了spring aop實現(xiàn)接口超時處理組件,文中有詳細的實現(xiàn)思路,并通過代碼示例給大家介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-02-02
Java修飾符 abstract,static,final 的區(qū)別詳解
以下是對Java修飾符abstract,static,final的區(qū)別進行了詳細的介紹,需要的朋友可以過來參考下2013-09-09
Spring?Security實現(xiàn)基于RBAC的權限表達式動態(tài)訪問控制的操作方法
這篇文章主要介紹了Spring?Security實現(xiàn)基于RBAC的權限表達式動態(tài)訪問控制,資源權限表達式動態(tài)權限控制在Spring Security也是可以實現(xiàn)的,首先開啟方法級別的注解安全控制,本文結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-04-04

