Spring 表达式语言 SpEL 是一种非常强大的表达式语言,它支持在运行时查询和操作对象图。 它提供了许多高级功能,例如方法调用和基本的字符串模板功能。表达式语言给静态Java语言增加了动态功能。
Spring 表达式语言最初是为 Spring 社区创建的,它拥有一种受良好支持的表达式语言,可用于 Spring 产品组合中的所有产品。 虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它不直接与 Spring 绑定,可以独立使用
Spring Security框架中启用prePost注解的支持就是使用SpEL表达式实现的权限控制
SpEL支持如下表达式:
注:SpEL表达式中的关键字是不区分大小写的。
/** * 基本表达式 : 字面量表达式 */ @Test public void test01() { // 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现; ExpressionParser parser = new SpelExpressionParser(); // 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。 Expression expression = parser.parseExpression("1+2"); // 3)求值:通过Expression接口的getValue方法根据上下文获得表达式值。 System.out.println(expression.getValue()); String str1 = parser.parseExpression("'Hello World!'").getValue(String.class); int int1 = parser.parseExpression("1").getValue(Integer.class); long long1 = parser.parseExpression("-1L").getValue(long.class); float float1 = parser.parseExpression("1.1").getValue(Float.class); double double1 = parser.parseExpression("1.1E+2").getValue(double.class); int hex1 = parser.parseExpression("0xa").getValue(Integer.class); long hex2 = parser.parseExpression("0xaL").getValue(long.class); boolean true1 = parser.parseExpression("true").getValue(boolean.class); boolean false1 = parser.parseExpression("false").getValue(boolean.class); Object null1 = parser.parseExpression("null").getValue(Object.class); System.out.println("str1=" + str1); System.out.println("int1=" + int1); System.out.println("long1=" + long1); System.out.println("float1=" + float1); System.out.println("double1=" + double1); System.out.println("hex1=" + hex1); System.out.println("hex2=" + hex2); System.out.println("true1=" + true1); System.out.println("false1=" + false1); System.out.println("null1=" + null1); }
/** * 基本表达式 : 字面量表达式 */ @Test public void test02() { // 1)创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现; ExpressionParser parser = new SpelExpressionParser(); // 2)解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。 Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)"); // 3)构造上下文:准备比如变量定义等等表达式需要的上下文数据。 EvaluationContext context = new StandardEvaluationContext(); context.setVariable("end", "!"); // 4)求值:通过Expression接口的getValue方法根据上下文获得表达式值。 System.out.println(expression.getValue(context)); }
@Data @Component("ss") public class User { private String username; private String address; private Integer age; public String sayHello(String username) { return "hello " + username; } public String sayHello(Integer age) { return "hello " + username + ";age=" + age; } public String sayHello() { return "hello " + username; } }
/** * 类相关表达式: 变量定义及引用 */ @Test public void test03() { String expression = "#user.username"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); User user = new User(); user.setAddress("长沙"); user.setUsername("zysheep"); user.setAge(24);; ctx.setVariable("user", user); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value); }
@Test public void test04() { String expression = "username"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); User user = new User(); user.setAddress("长沙"); user.setUsername("zysheep"); user.setAge(24); // user 对象设置为 rootObject,那么表达式中就不需要 #user. ctx.setRootObject(user); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value); }
/** * 类相关表达式: 对象方法调用 */ @Test public void test05() { String expression = "sayHello(99)"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); User user = new User(); user.setAddress("长沙"); user.setUsername("zysheep"); user.setAge(24); ctx.setRootObject(user); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value); }
/** * 类相关表达式: 对象方法调用 * * 调用无参的 sayHello */ @Test public void test06() { String expression = "sayHello()"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); User user = new User(); user.setAddress("长沙"); user.setUsername("zysheep"); user.setAge(24); ctx.setRootObject(user); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value); }
/** * 类相关表达式: Bean引用 */ @Test public void test07() { // 通过 SpEL 表达式来调用这个名为 ss 的 bean 中的 sayHello 方法 String expression = "@ss.sayHello('spel bean引用')"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); // 给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法 ctx.setBeanResolver(new BeanFactoryResolver(beanFactory)); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value); }
/** * 表达式模板: * 模板表达式就是由字面量与一个或多个表达式块组成。每个表达式块由“前缀+表达式+后缀”形式组成,如“${1+2}”即表达式块 * */ @Test public void test08() { //创建解析器 SpelExpressionParser parser = new SpelExpressionParser(); //创建解析器上下文 ParserContext context = new TemplateParserContext("%{", "}"); Expression expression = parser.parseExpression("你好:%{#name},我们正在学习:%{#lesson}", context); //创建表达式计算上下文 EvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.setVariable("name", "zysheep"); evaluationContext.setVariable("lesson", "SpEL表达式!"); //获取值 String value = expression.getValue(evaluationContext, String.class); System.out.println(value); }
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PreAuth { /** * * * permissionAll()-----只要配置了角色就可以访问 * hasPermission("MENU.QUERY")-----有MENU.QUERY操作权限的角色可以访问 * permitAll()-----放行所有请求 * denyAll()-----只有超级管理员角色才可访问 * hasAuth()-----只有登录后才可访问 * hasTimeAuth(1,,10)-----只有在1-10点间访问 * hasRole(‘管理员’)-----具有管理员角色的人才能访问 * hasAllRole(‘管理员’,'总工程师')-----同时具有管理员、总工程师角色的人才能访问 * * Spring el * 文档地址:https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions */ String value(); }
/** ** 类相关表达式: Bean引用,参数要用单引号包括 * * @PreAuth("@af.hasPermission('ADMIN, USER')") *
* * @author : lyw * @since : 2023/11/23 16:01 */ @Component("af") public class AuthFun { /** * 判断角色是否具有接口权限 * * @return {boolean} */ public boolean permissionAll() { //TODO 读取数据库权限数据 return true; } /** * 判断角色是否具有接口权限 * * @param permission 权限编号,对应菜单的MENU_CODE * @return {boolean} */ public boolean hasPermission(String permission) { return hasRole(permission); } /** * 放行所有请求 * * @return {boolean} */ public boolean permitAll() { return true; } /** * 只有超管角色才可访问 * * @return {boolean} */ public boolean denyAll() { return hasRole("ADMIN"); } /** * 是否已授权 * * @return {boolean} */ public boolean hasAuth() { return true; } /** * 是否有时间授权 * * @param start 开始时间 * @param end 结束时间 * @return {boolean} */ public boolean hasTimeAuth(Integer start, Integer end) { Integer hour = DateUtil.hour(new Date(), true); return hour >= start && hour <= end; } /** * 判断是否有该角色权限 * * @param role 单角色 * @return {boolean} */ public boolean hasRole(String role) { return hasAnyRole(role); } /** * 判断是否具有所有角色权限 * * @param role 角色集合 * @return {boolean} */ public boolean hasAllRole(String... role) { for (String r : role) { if (!hasRole(r)) { return false; } } return true; } /** * 判断是否有该角色权限 * * @param role 角色集合 * @return {boolean} */ public boolean hasAnyRole(String... role) { return Arrays.stream(role).anyMatch(item -> StringUtils.equals("ADMIN", item)); } }
@Aspect @Component public class PreAuthAspect { @Autowired private BeanFactory applicationContext; private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); @Pointcut("@annotation(cn.zysheep.annotation.PreAuth) || @within(cn.zysheep.annotation.PreAuth)") public void pointcut() {} @Around("pointcut()") public Object preAuth(ProceedingJoinPoint point) throws Throwable { if (handleAuth(point)) { return point.proceed(); } throw new BizException("用户无权限,非法用户!"); } private boolean handleAuth(ProceedingJoinPoint point) { MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null; Method method = ms.getMethod(); // 读取权限注解,优先方法上,没有则读取类 PreAuth preAuth = method.getAnnotation( PreAuth.class); // 判断表达式 String condition = preAuth.value(); if (StringUtils.isNotBlank(condition)) { // @PreAuth("@af.hasPermission('ADMIN, USER')") Expression expression = EXPRESSION_PARSER.parseExpression(condition); // 方法参数值 Object[] args = point.getArgs(); StandardEvaluationContext context = getEvaluationContext(method, args); // 获取解析计算的结果 return expression.getValue(context, Boolean.class); } return false; } /** * 获取方法上的参数 * * @param method 方法 * @param args 变量 * @return {SimpleEvaluationContext} */ private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) { // 初始化Spel表达式上下文,并设置 AuthFun StandardEvaluationContext context = new StandardEvaluationContext(); // 设置表达式支持spring bean context.setBeanResolver(new BeanFactoryResolver(applicationContext)); // 可以从session中获取登录用户的权限 // for (int i = 0; i < args.length; i++) { // // 读取方法参数 // MethodParameter methodParam = ClassUtil.getMethodParameter(method, i); // // 设置方法 参数名和值 为spel变量 // context.setVariable(methodParam.getParameterName(), args[i]); // } return context; } }
@RestController @RequestMapping("/api") @Slf4j public class UserController { @GetMapping("/save") @PreAuth("@af.hasPermission('ADMIN')") public R save() { log.info("====执行保存业务逻辑====="); return R.success(); } @GetMapping("/get") @PreAuth("@af.hasPermission('USER')") public R get() { log.info("====执行保存业务逻辑====="); return R.success(); } }
public final class SpelParserUtils { private static final String EXPRESSION_PREFIX = "#{"; private static final String EXPRESSION_SUFFIX = "}"; /** * 表达式解析器 */ private static ExpressionParser expressionParser = new SpelExpressionParser(); /** * 参数名解析器,用于获取参数名 */ private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); private SpelParserUtils(){} /** * 解析spel表达式 * * @param method 方法 * @param args 参数值 * @param spelExpression 表达式 * @param clz 返回结果的类型 * @param defaultResult 默认结果 * @return 执行spel表达式后的结果 */ public staticT parse(Method method, Object[] args, String spelExpression, Class clz, T defaultResult) { String[] params = parameterNameDiscoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); //设置上下文变量 for (int i = 0; i < params.length; i++) { context.setVariable(params[i], args[i]); } T result = getResult(context,spelExpression,clz); if(Objects.isNull(result)){ return defaultResult; } return result; } /** * 解析spel表达式 * * @param method 方法 * @param args 参数值 * @param spelExpression 表达式 * @param clz 返回结果的类型 * @return 执行spel表达式后的结果 */ public static T parse(Method method, Object[] args, String spelExpression, Class clz) { String[] params = parameterNameDiscoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); //设置上下文变量 for (int i = 0; i < params.length; i++) { context.setVariable(params[i], args[i]); } return getResult(context,spelExpression,clz); } /** * 解析spel表达式 * * @param param 参数名 * @param paramValue 参数值 * @param spelExpression 表达式 * @param clz 返回结果的类型 * @return 执行spel表达式后的结果 */ public static T parse(String param, Object paramValue, String spelExpression, Class clz) { EvaluationContext context = new StandardEvaluationContext(); //设置上下文变量 context.setVariable(param, paramValue); return getResult(context,spelExpression,clz); } /** * 解析spel表达式 * * @param param 参数名 * @param paramValue 参数值 * @param spelExpression 表达式 * @param clz 返回结果的类型 * @param defaultResult 默认结果 * @return 执行spel表达式后的结果 */ public static T parse(String param, Object paramValue,String spelExpression, Class clz, T defaultResult) { EvaluationContext context = new StandardEvaluationContext(); //设置上下文变量 context.setVariable(param, paramValue); T result = getResult(context,spelExpression,clz); if(Objects.isNull(result)){ return defaultResult; } return result; } /** * 获取spel表达式后的结果 * * @param context 解析器上下文接口 * @param spelExpression 表达式 * @param clz 返回结果的类型 * @return 执行spel表达式后的结果 */ private static T getResult(EvaluationContext context,String spelExpression, Class clz){ try { //解析表达式 Expression expression = parseExpression(spelExpression); //获取表达式的值 return expression.getValue(context, clz); } catch (Exception e) { log.error(e.getMessage(),e); } return null; } /** * 解析表达式 * @param spelExpression spel表达式 * @return */ private static Expression parseExpression(String spelExpression){ // 如果表达式是一个#{}表达式,需要为解析传入模板解析器上下文 if(spelExpression.startsWith(EXPRESSION_PREFIX) && spelExpression.endsWith(EXPRESSION_SUFFIX)){ return expressionParser.parseExpression(spelExpression,new TemplateParserContext()); } return expressionParser.parseExpression(spelExpression); } }