欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用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注解

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

写一个自定义切面

注意一下解析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>

测试

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

增加内容

1、当我们想在自己写的spel表达式中调用spring bean 管理的方法时,如何写。spel表达式支持使用 @来引用bean,但是此时需要注入beanfactory

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。