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

目录
  • Springboot自定义注解,支持SPEL表达式
    • 1.自定义注解
    • 2.使用AOP拦截方法,解析注解参数
  • 自定义注解结合切面和spel表达式
    • 自定义一个注解
    • 自定义一个service类,在需要拦截的方法上加上@Log注解
    • 写一个自定义切面
    • pom文件的依赖
    • 测试
    • 增加内容

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

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

(0)

相关推荐

  • Spring实战之Bean定义中的SpEL表达式语言支持操作示例

    本文实例讲述了Spring实战之Bean定义中的SpEL表达式语言支持操作.分享给大家供大家参考,具体如下: 一 配置 <?xml version="1.0" encoding="GBK"?> <!-- 指定Spring配置文件的根元素和Schema 导入p:命名空间和util:命名空间的元素 --> <beans xmlns="http://www.springframework.org/schema/beans"

  • 基于spring @Cacheable 注解的spel表达式解析执行逻辑

    目录 直接进入主题 跟随spring的调用链 直接看 @Cacheable 注解就可以了 接下来看 key获取是在哪里 没有任何逻辑就是一个组装 了解一下@Cacheable的拦截顺序 接下来看 execute方法 再看 重载方法execute 日常使用中spring的 @Cacheable 大家一定不陌生,基于aop机制的缓存实现,并且可以选择cacheManager具体提供缓存的中间件或者进程内缓存,类似于 @Transactional 的transactionManager ,都是提供了一

  • Spring spel表达式使用方法示例

    spring in action第三版读书笔记 spring3.0引入了spring expression language(spel)语言,通过spel我们可以实现 1.通过bean的id对bean进行引用 2.调用方法以及引用对象中的属性 3.计算表达式的值 4.正则表达式的匹配 5.集合的操作 spel最终的目标是得到表达式计算之后的值,这些表达式可能是列举的一些值,引用对象的某些属性,或者是类中的某些常量,复杂的spel表达式通常都是由一些简单的元素构成的.最简单的仅仅是得到一些给出元素

  • spring之SpEL表达式详解

    目录 1.什么是SpEL表达式 2.SpEL表达式语言入门程序 (1)xml配置的方式 (2)采用注解的方式 3.分析器 4.使用SpEL表达式调用方法 (1)使用SpEL调用普通方法 (2)使用SpEL调用构造方法 (3)使用SpEL调用静态方法 6.使用SpEL表达式调用变量和函数 (1)#变量的表达式使用 (2)#root表达式的使用 (3)访问系统的属性和环境 7.使用SpEL表达式中的运算符 1.什么是SpEL表达式 SpEL表达式语言是一种表达式语言,是一种可以与一个基于spring

  • Spring表达式语言SpEL用法详解

    这篇文章主要介绍了spring表达式语言SpEL用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 (1)spring表达式语言是一个支持运行时查询和操作对象图得我强大表达式语言. (2)语言类似于EL:SpEL使用#{...}作为定界符.所有在大括号中的字符串均被认为是SpEL. (3)SpEL为bean的属性进行动态赋值提供了便利. (4)通过SpEL可以实现: 通过Bean的id对Bean进行引用 调用方法及引用对象的属性 计算表达式

  • Spring组件开发模式支持SPEL表达式

    本文是一个 Spring 扩展支持 SPEL 的简单模式,方便第三方通过 Spring 提供额外功能. 简化版方式 这种方式可以在任何能获取ApplicationContext 的地方使用.还可以提取一个方法处理动态 SPEL 表达式. import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.

  • Spring Cache抽象-使用SpEL表达式解析

    目录 Spring Cache抽象-使用SpEL表达式 概述 SpEl表达式 如何让自定义注解支持SpEL表达式 使用方法 使用案例 1.准备 2.自定义注解 3.定义AOP拦截注解对方法增强进行读写缓存 4.测试 Spring Cache抽象-使用SpEL表达式 概述 在Spring Cache注解属性中(比如key,condition和unless),Spring的缓存抽象使用了SpEl表达式,从而提供了属性值的动态生成及足够的灵活性. 下面的代码根据用户的userCode进行缓存,对于ke

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

    目录 Springboot自定义注解,支持SPEL表达式 1.自定义注解 2.使用AOP拦截方法,解析注解参数 自定义注解结合切面和spel表达式 自定义一个注解 自定义一个service类,在需要拦截的方法上加上@Log注解 写一个自定义切面 pom文件的依赖 测试 增加内容 Springboot自定义注解,支持SPEL表达式 举例,自定义redis模糊删除注解 1.自定义注解 import java.lang.annotation.ElementType; import java.lang.

  • java SpringBoot自定义注解,及自定义解析器实现对象自动注入操作

    # java-SpringBoot自定义参数解析器实现对象自动注入 解析器逻辑流程图表 后台解析注解的解析器 首先,我在java后台编写了一个解析器,代码如下 import com.ruoyi.framework.interceptor.annotation.LoginUser; import com.ruoyi.project.WebMoudle.WebUser.domain.WebUser; import com.ruoyi.project.WebMoudle.WebUser.service

  • SpringBoot自定义注解使用读写分离Mysql数据库的实例教程

    需求场景 为了防止代码中有的SQL慢查询,影响我们线上主数据库的性能.我们需要将sql查询操作切换到从库中进行.为了使用方便,将自定义注解的形式使用. mysql导入的依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency&

  • SpringBoot自定义注解实现Token校验的方法

    1.定义Token的注解,需要Token校验的接口,方法上加上此注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementTyp

  • SpringBoot自定义注解API数据加密和签名校验

    api数据数据签名(MD5,SHA1) 签名枚举类SginEnum.java package com.jx.app.api.framework.annotation.enums; /** * @ClassName: SginEnum * @Description: TODO(这是一个签名枚举类) * @author gangyu * @date 2018年11月20日 下午4:30:44 */ public enum SginEnum { //0不需要签名,1使用MD5数据加密 2 使用SHA数

  • SpringBoot自定义注解开发指南

    目录 一.Java注解(Annotation) 1.JDK基本注解 2.JDK元注解 二.自定义注解开发 1.含义 2.演示 三.完成切面日志操作 四.完成前端响应反应 总结 一.Java注解(Annotation) 含义:Java注解是附加在代码中的一些元信息,用于一些工具在编译. 运行时进行解析和使用,起到说明.配置的功能. 1.JDK基本注解 @Override ——>重写 @Deprecated ——>已过时 @SuppressWarnings(value = "unchec

  • SpringBoot 自定义注解异步记录复杂日志详解

    目录 1.背景 2.技术方案-自定义注解 2.1 注解介绍 2.2 元注解 2.3 实现自定义注解 3.技术方案-AOP切面 3.1 AOP术语解析 3.2 切入点表达式 3.3 ADVICE通知类型 3.4 技术实现 3.5 相关操作 4.高级操作 1.背景 最近接手一个任务,需要给当前项目加一个较为复杂的日志.有多复杂呢? 要有日志类型.不同日志类型要有不同的操作和备注等.作为小白的我最开始的做法是在业务层写代码记录日志,好处就是方便,坏处就是这种做法直接侵袭Service层,Service

  • SpringBoot 自定义注解实现涉密字段脱敏

    目录 1. 创建隐私数据类型枚举:PrivacyTypeEnum 2. 创建自定义隐私注解:PrivacyEncrypt 3. 创建自定义序列化器:PrivacySerializer 4. 隐私数据隐藏工具类:PrivacyUtil 5. 注解使用 关于数据脱敏,网上的文章都是硬编码规则,比如对身份证,手机号,邮件地址等固定写法脱敏.本文在此基础上,拓展动态从数据库查出涉密关键字执行脱敏操作. 数据脱敏:把系统里的一些敏感数据进行加密处理后再返回,达到保护隐私作用,实现效果图如下: 其实要实现上

随机推荐