这篇文章主要介绍了如何使用Springboot自定义注解并支持SPEL表达式,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

Springboot自定义注解,支持SPEL表达式

举例,自定义redis模糊删除注解

1.自定义注解

importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceCacheEvictFuzzy{/***rediskey集合,模糊删除*@return*/String[]key()default"";}2.使用AOP拦截方法,解析注解参数

importorg.apache.commons.lang3.StringUtils;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.AfterThrowing;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.aspectj.lang.reflect.MethodSignature;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.core.LocalVariableTableParameterNameDiscoverer;importorg.springframework.core.annotation.Order;importorg.springframework.expression.ExpressionParser;importorg.springframework.expression.spel.standard.SpelExpressionParser;importorg.springframework.expression.spel.support.StandardEvaluationContext;importorg.springframework.stereotype.Component;importjava.lang.reflect.Method;importjava.util.Set;@Aspect@Order(1)@ComponentpublicclassCacheCleanFuzzyAspect{Loggerlogger=LoggerFactory.getLogger(this.getClass());@AutowiredprivateRedisUtilredis;//指定要执行AOP的方法@Pointcut(value="@annotation(cacheEvictFuzzy)")publicvoidpointCut(CacheEvictFuzzycacheEvictFuzzy){}//设置切面为加有@RedisCacheable注解的方法@Around("@annotation(cacheEvictFuzzy)")publicObjectaround(ProceedingJoinPointproceedingJoinPoint,CacheEvictFuzzycacheEvictFuzzy){returndoRedis(proceedingJoinPoint,cacheEvictFuzzy);}@AfterThrowing(pointcut="@annotation(cacheEvictFuzzy)",throwing="error")publicvoidafterThrowing(Throwableerror,CacheEvictFuzzycacheEvictFuzzy){logger.error(error.getMessage());}/***删除缓存*@paramproceedingJoinPoint*@paramcacheEvictFuzzy*@return*/privateObjectdoRedis(ProceedingJoinPointproceedingJoinPoint,CacheEvictFuzzycacheEvictFuzzy){Objectresult=null;//得到被切面修饰的方法的参数列表Object[]args=proceedingJoinPoint.getArgs();//得到被代理的方法Methodmethod=((MethodSignature)proceedingJoinPoint.getSignature()).getMethod();String[]keys=cacheEvictFuzzy.key();Set<String>keySet=null;Stringrealkey="";for(inti=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(Throwablethrowable){throwable.printStackTrace();}finally{returnresult;}}/***获取缓存的key*key定义在注解上,支持SPEL表达式*@return*/privateStringparseKey(Stringkey,Methodmethod,Object[]args){if(StringUtils.isEmpty(key))returnnull;//获取被拦截方法参数名列表(使用Spring支持类库)LocalVariableTableParameterNameDiscovereru=newLocalVariableTableParameterNameDiscoverer();String[]paraNameArr=u.getParameterNames(method);//使用SPEL进行key的解析ExpressionParserparser=newSpelExpressionParser();//SPEL上下文StandardEvaluationContextcontext=newStandardEvaluationContext();//把方法参数放入SPEL上下文中for(inti=0;i<paraNameArr.length;i++){context.setVariable(paraNameArr[i],args[i]);}returnparser.parseExpression(key).getValue(context,String.class);}}

完事啦!

大家可以注意到关键方法就是parseKey

/***获取缓存的key*key定义在注解上,支持SPEL表达式*@return*/privateStringparseKey(Stringkey,Methodmethod,Object[]args){if(StringUtils.isEmpty(key))returnnull;//获取被拦截方法参数名列表(使用Spring支持类库)LocalVariableTableParameterNameDiscovereru=newLocalVariableTableParameterNameDiscoverer();String[]paraNameArr=u.getParameterNames(method);//使用SPEL进行key的解析ExpressionParserparser=newSpelExpressionParser();//SPEL上下文StandardEvaluationContextcontext=newStandardEvaluationContext();//把方法参数放入SPEL上下文中for(inti=0;i<paraNameArr.length;i++){context.setVariable(paraNameArr[i],args[i]);}returnparser.parseExpression(key).getValue(context,String.class);}自定义注解结合切面和spel表达式

在我们的实际开发中可能存在这么一种情况,当方法参数中的某些条件成立的时候,需要执行一些逻辑处理,比如输出日志。而这些代码可能都是差不多的,那么这个时候就可以结合自定义注解加上切面加上spel表达式进行处理。就比如在spring中我们可以使用@Cacheable(key="#xx")实现缓存,这个#xx就是一个spel表达式。

需求:我们需要将service层方法中方法的某个参数的值大于0.5的方法,输出方法执行日志。(需要了解一些spel表达式的语法)

实现步骤:

1、自定义一个注解Log

2、自定义一个切面,拦截所有方法上存在@Log注解修饰的方法

3、写一个service层方法,方法上标注@Log注解

难点:

在切面中需要拿到具体执行方法的方法名,可以使用spring提供的LocalVariableTableParameterNameDiscoverer来获取到

自定义一个注解

注意:注解中的spel的值是必须的,且spel表达式返回的结果应该是一个布尔值

/***记录日志信息,当spel表但是中的值为true时,输出日志信息**@描述*@作者huan*@时间2017年10月2日-上午10:25:39*/@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public@interfaceLog{Stringspel();Stringdesc()default"描述";}自定义一个service类,在需要拦截的方法上加上@Log注解

写一个自定义切面

注意一下解析spel表达式中context的设值即可

/***日志切面,当条件满足时输出日志.**@描述*@作者huan*@时间2017年10月2日-上午10:32:16*/@Component@AspectpublicclassLogAspect{ExpressionParserparser=newSpelExpressionParser();LocalVariableTableParameterNameDiscovererdiscoverer=newLocalVariableTableParameterNameDiscoverer();@Around("@annotation(log)")publicObjectinvoked(ProceedingJoinPointpjp,Loglog)throwsThrowable{Object[]args=pjp.getArgs();Methodmethod=((MethodSignature)pjp.getSignature()).getMethod();Stringspel=log.spel();String[]params=discoverer.getParameterNames(method);EvaluationContextcontext=newStandardEvaluationContext();for(intlen=0;len<params.length;len++){context.setVariable(params[len],args[len]);}Expressionexpression=parser.parseExpression(spel);if(expression.getValue(context,Boolean.class)){System.out.println(log.desc()+",在"+newSimpleDateFormat("yyyy-MM-ddHH:mm:ss").format(newDate())+"执行方法,"+pjp.getTarget().getClass()+"."+method.getName()+"("+convertArgs(args)+")");}returnpjp.proceed();}privateStringconvertArgs(Object[]args){StringBuilderbuilder=newStringBuilder();for(Objectarg:args){if(null==arg){builder.append("null");}else{builder.append(arg.toString());}builder.append(',');}builder.setCharAt(builder.length()-1,'');returnbuilder.toString();}}pom文件的依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.2.RELEASE</version><relativePath/><!--lookupparentfromrepository--></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

感谢你能够认真阅读完这篇文章,希望小编分享的“如何使用Springboot自定义注解并支持SPEL表达式”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!