使用Springboot自定义注解,支持SPEL表达式
程序员文章站
2022-06-09 16:46:41
目录springboot自定义注解,支持spel表达式1.自定义注解2.使用aop拦截方法,解析注解参数自定义注解结合切面和spel表达式自定义一个注解自定义一个service类,在需要拦截的方法上加...
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; //指定要执行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表达式
在我们的实际开发中可能存在这么一种情况,当方法参数中的某些条件成立的时候,需要执行一些逻辑处理,比如输出日志。而这些代码可能都是差不多的,那么这个时候就可以结合自定义注解加上切面加上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 @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()) + "执行方法," + 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
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
C#_Excel数据读取与写入_自定义解析封装类_支持设置标题行位置&使用excel表达式收集数据&单元格映射&标题映射&模板文件的参数数据替换(第二版-增加深度读取和更新功能)
-
Spring AOP +自定义注解 + Spel表达式 实现审计日志
-
springboot使用aop的学习--结合自定义注解校验登入
-
SpringBoot使用自定义注解实现简单参数加密解密(注解+HandlerMethodArgumentResolver)
-
使用Springboot自定义注解,支持SPEL表达式
-
Springboot + Freemarker项目中使用自定义注解
-
使用Springboot自定义注解,支持SPEL表达式
-
自定义注解结合切面和spel表达式
-
C#_Excel数据读取与写入_自定义解析封装类_支持设置标题行位置&使用excel表达式收集数据&单元格映射&标题映射&模板文件的参数数据替换(第二版-增加深度读取和更新功能)
-
Spring AOP +自定义注解 + Spel表达式 实现审计日志