AOP支持spEL表达式
在使用spring-data-cache
时,我们使用aop注解是支持spEL(Spring Expression Language)
的,
demo
@Override@Cacheable(value = "#id",cacheNames = "CACHE1")public UserInfo getById(String id) {return userMappger.getById(id);}
自定义的AOP如何支持支持spEL表达式呢?
背景
AclCheckPoint
假设有切入点
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AclCheckPoint {/*** id-expression* if "#paramX.name " means paramX.getName().* if null, means args[0]* if "#paramX" has no "." ,means paramX .** @return*/String expression() default "";
}
业务代码
@Acl //自定义注解,后面会有说明
public class UserServiceImpl implements IUserService {@Override@AclCheckPoint(expression = "#dto.id")public int update(UserDto dto) {//do nothing}
}
Aspect切面
@Aspect
public class AclCheckAspect {@Before("@annotation(aclAnno)")public void before(JoinPoint joinPoint, AclCheckPoint aclAnno) throws Exception {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Object args[] = joinPoint.getArgs(); //String expression = aclAnno.getExpression(); // "#dto.id"}
}
如何才能通过#dto.id
解析出来请求我们真正需要的参数呢?
基本解决
回顾一下被拦截的方法
@Override@AclCheckPoint(expression = "#dto.id")public int update(UserDto dto) {//do nothing}
我们已知请求参数args[]
和表达式#dto.id
,接下来的几个问题
- 首先我们要将表达式
split
为参数名称
和属性名称
两个部分 - 获取被拦截的方法的
参数名称
列表,匹配找到目标参数 - 最后反射执行对应属性的
getter
解析表达式
void findParamPair(Method method, String expression) {String[] arr = expression.split("\\.");String paramName = arr[0].replace("#", "");String field = arr[1];//todo xxxx}
解析参数名称
JDK8 —不建议
public int getParam(String paramName,MethodSignature signature){String[] parameterNames = signature.getParameterNames(); //依赖jdk8int index = -1;for(int i = 0 ; i < parameterNames.length ; i++){if(parameterNames[i].equals(paramName)){index = i;break;}}if(index == -1){throw new RuntimeException("method [" + signature.getMethod().getName() + "] does not have parameter name:" + paramName);}return index; // param = args[index]}
spring:LocalVariableTableParameterNameDiscoverer
public int getParam(String paramName,MethodSignature signature){LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();String[] definedParamNames = discoverer.getParameterNames(signature.getMethod());int index = definedParamNames.length;while (index >= 0) {index--;if (paramName.equals(definedParamNames[index])) {break;}}if (index < 0) {throw new RuntimeException("method [" + signature.getMethod().getName() + "] does not have parameter name:" + paramName);}return index; // param = args[index]}
获取getter
public Method getter(int index ,MethodSignature signature,String field) throws NoSuchMethodException {Class parameterType = signature.getMethod().getParameterTypes()[index];String getterName = "getter" + field.substring(0, 1).toUpperCase() + field.substring(1);Method getter = parameterType.getMethod(getterName,null);return getter;}
AOP拦截获取expression-value
@Before("@annotation(aclAnno)")public void before(JoinPoint joinPoint, AclCheckPoint aclAnno) throws Exception {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Object[] args = joinPoint.getArgs();int index = getParam(paramName, signature, args);Object param = args[index];Method getter = getter(index, signature, field);Object expressionValue = getter.invoke(param); //获取value}
到此为止,似乎问题全部解决了,也能达到预期的目标, 那么这个解决方案还有优化的点吗?
优化解决
在使用spring-data-cache
时,在启动时会检测@Cacheable
的注解是否合法有效,那么在上面的解决方案中(饿汉
),只有在方法被AOP切面拦截的时候候才能发现问题(懒汉
),这就给发现问题带来了一定难度以及带来一定的性能损耗(上述的解析过程,反射等),如何实现饿汉
模式呢?
- 如果在Spring容器初始化的时候,就进行扫描出目标方法
- 对带有
AclCheckPoint
的方法,预先解析,并将解析方法存放于缓存之中 - AOP切面直接从缓存中寻找对应的解析方法,并执行
扫描目标方法
Acl
为了增加扫描的效率 我们新增一个注解,只对带有该注解的方法进行拦截
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Acl {}
basepackage
为了更进一步加快扫描效率,可以只对目标路径进行扫描。
扫描: ClassPathScanningCandidateComponentProvider
private Set<BeanDefinition> scan() {ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);scanner.addIncludeFilter(new AnnotationTypeFilter(Acl.class));String[] packages = StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n");Set<BeanDefinition> definitions = new HashSet<>();for (String pkg : packages) {definitions.addAll(scanner.findCandidateComponents(pkg));}return definitions;
}
初始化并缓存
扫描并初始化
public class CacheableIdDetectorFactory<T> implements I InitializingBean {private static final Map<Method, IdDetector> cache = new HashedMap(); //自定义对象IdDetectorpublic IdDetector getIdDetector(Method method) { //供外部调用,从cache获取IdDetector idDetector = cache.get(method);if (idDetector == null) {throw new RuntimeException("Did not find IdDetector for [" + method.getName() + "] ,maybe method's target-class does not have @Acl?");}return idDetector;}@Overridepublic void afterPropertiesSet() throws Exception {Set<BeanDefinition> definitions = scan();definitions.stream().map(bd -> {String className = bd.getBeanClassName();try {Class clazz = Class.forName(className);return clazz.getMethods();} catch (ClassNotFoundException e) {logger.error("[" + className + "] not found :", e);System.exit(-1);}return null;}).filter(Objects::nonNull).flatMap(Arrays::stream).forEach(this::initCache); }
}
切面获取cache的处理方法
@Before("@annotation(aclAnno)")public void before(JoinPoint joinPoint, AclCheckPoint aclAnno) throws Exception {MethodSignature signature = (MethodSignature) joinPoint.getSignature();IdDetector<String> idDetector = idDetectorFactory.getIdDetector(signature.getMethod());Object[] args = joinPoint.getArgs();String expressValue = idDetector.detect(args);}
完整代码
自定义IdDetector
public class IdDetector<T> {private final Function<Object[], T> function;IdDetector(Function<Object[], T> f) {this.function = f;}public T detect(Object[] args) {return function.apply(args);}
}
IdDetectorFactory
public interface IdDetectorFactory<T> {IdDetector<T> getIdDetector(Method method) throws Exception;String EXPRESSION_PREFIX = "#";String EXPRESSION_SPLIT = "\\.";String GETTER_PREFIX = "get";default String getterMethodName(String field) {return GETTER_PREFIX + field.substring(0, 1).toUpperCase() + field.substring(1);}default Method getterMethod(Class clazz, String field) throws NoSuchMethodException {String fieldGetterName = getterMethodName(field);Method getter = clazz.getDeclaredMethod(fieldGetterName);return getter;}LocalVariableTableParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();default Pair findParamPair(Method method, String expression) {String[] arr = expression.split(EXPRESSION_SPLIT);String paramName = arr[0].replace(EXPRESSION_PREFIX, "");String[] definedParamNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);int index = definedParamNames.length;while (index >= 0) {index--;if (paramName.equals(definedParamNames[index])) {break;}}if (index < 0) {throw new RuntimeException("method [" + method.getName() + "] does not have parameter name:" + paramName);} if(arr.length == 1){return new Pair(index, null);}return new Pair(index, arr[1]);}@Getter@AllArgsConstructorclass Pair {int index;String field;}
}
CacheableIdDetectorFactory
public class CacheableIdDetectorFactory<T> implements IdDetectorFactory, InitializingBean {private static final Logger logger = LoggerFactory.getLogger(CacheableIdDetectorFactory.class);private static final Map<Method, IdDetector> cache = new HashedMap();public IdDetector getIdDetector(Method method) {IdDetector idDetector = cache.get(method);if (idDetector == null) {throw new RuntimeException("Did not find IdDetector for [" + method.getName() + "] ,maybe method's target-class does not have @Acl?");}return idDetector;}private void initCache(Method method) throws RuntimeException {AclCheckPoint aclAnno = method.getAnnotation(AclCheckPoint.class);if (aclAnno == null) {return;}String expression = aclAnno.expression();Function<Object[], T> function;build_function_label:{if (Strings.isNullOrEmpty(expression)) {//args[0]function = (objs) -> (T) objs[0];break build_function_label;}Pair pair = findParamPair(method, expression);int index = pair.getIndex();String field = pair.getField();if (Strings.isNullOrEmpty(field)) {//args[x]function = (objs) -> (T) objs[index];break build_function_label;}Class clazz = method.getParameterTypes()[index];//gettertry {Method fieldGetter = getterMethod(clazz, field);function = (objs) -> {try {//args[x].getterField()return (T) fieldGetter.invoke(objs[index]);} catch (IllegalAccessException | InvocationTargetException e) {throw new RuntimeException(e);}};} catch (NoSuchMethodException e) {function = (objs) -> (T) new RuntimeException("throwable IdDetector --- pls check your code");}}cache.put(method, new IdDetector(function));logger.info("Generate a id-detector for : {}.{} ", method.getDeclaringClass().getSimpleName(), method.getName());}private final String basePackage;public CacheableIdDetectorFactory(String basePackage) {this.basePackage = basePackage;}private Set<BeanDefinition> scan() {ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);scanner.addIncludeFilter(new AnnotationTypeFilter(Acl.class));String[] packages = StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n");Set<BeanDefinition> definitions = new HashSet<>();for (String pkg : packages) {definitions.addAll(scanner.findCandidateComponents(pkg));}return definitions;}@Overridepublic void afterPropertiesSet() throws Exception {Set<BeanDefinition> definitions = scan();definitions.stream().map(bd -> {String className = bd.getBeanClassName();try {Class clazz = Class.forName(className);return clazz.getMethods();} catch (ClassNotFoundException e) {logger.error("[" + className + "] not found :", e);System.exit(-1);}return null;}).filter(Objects::nonNull).flatMap(Arrays::stream).forEach(this::initCache);}}
Aspect
@Aspect
public class AclCheckAspect {private final IdDetectorFactory idDetectorFactory;public AclCheckAspect(IdDetectorFactory idDetectorFactory) {this.idDetectorFactory = idDetectorFactory;}@Before("@annotation(aclAnno)")public void before(JoinPoint joinPoint, AclCheckPoint aclAnno) throws Exception {MethodSignature signature = (MethodSignature) joinPoint.getSignature();IdDetector<String> idDetector = idDetectorFactory.getIdDetector(signature.getMethod());Object[] args = joinPoint.getArgs();String expressionValue = idDetector.detect(args);//todo }}
AOP支持spEL表达式相关推荐
- Spring中自定义注解支持SpEl表达式(仅限在AOP中使用)
大家平时在写代码的时候,安全方面一般都会考虑使用Shiro或者SpringSecurity,他们其中提供了很多注解可以直接使用,很方便,那么今天就来重复造个小轮子,如果不用他们的,自己在项目中如何基于 ...
- Spring Data JPA 从入门到精通~SpEL表达式的支持
在 Spring Data JPA 1.4 以后,支持在 @Query 中使用 SpEL 表达式(简介)来接收变量. SpEL 支持的变量 变量名 使用方式 描述 entityName select ...
- Spring Aop中解析spel表达式,实现更灵活的功能
前言 在Spring Aop中,我们可以拿到拦截方法的参数,如果能结合spel表达式,就能实现更加灵活的功能.典型的实现有Spring的缓存注解: @Cacheable(value = "u ...
- 从头认识Spring-1.14 SpEl表达式(1)-简单介绍与嵌入值
这一章节我们来讨论一下SpEl表达式的简单介绍与嵌入值. 1.SpEl表达式简单介绍 Spring Excpression Language (SpEL)语言支持在执行时操作和查询对象 事实上就是在执 ...
- Spring SpEL表达式的使用
文章目录 一.SpEL介绍 二.SpEL用法 1. 在@Value注解中使用 2. 在XML配置中使用 3. 在代码中创建Expression对象 三.SpEL原理 1. 解析器:Expression ...
- Spring SpEL表达式
spring expression language 是在spring3.0以后的版本提供 它类似于ognl或el表达式,它可以提供在程序运行时构造复杂表达式来完成对象属性存储及方法调用等. Spel ...
- spring中的spel表达式语言
spring in action第三版读书笔记 spring3.0引入了spring expression language(spel)语言,通过spel我们可以实现 1.通过bean的id对bean ...
- spring配置详解-属性注入(p名称空间SPEL表达式)
所谓了解的话讲了一般是不会用的,看一下了解的注入方式,还有一种叫p名称空间注入方式,是Spring最近发出来的,因为Bean注入方式已经深入人心了,所以后面这两种注入方式,哪怕是好,也不愿意去用,因为 ...
- Spring中常用的SpEl表达式
Java 社区交流群 添加微信: 372787553 备注进群 SpEl 表达式语法 文字表达 支持的文字表达式类型是字符串.数值(整数.实数.十六进制).布尔值和空值.字符串由单引号分隔.要将单引号 ...
最新文章
- RabbitMQ 高频考点
- 汇编:call指令的应用
- ubuntu远程登陆windows
- 去除导航栏的背景色和底部1像素黑色线条or隐藏导航栏
- bzoj 2142 礼物
- C#规范整理·语言要素
- CentOS 7添加开机启动服务/脚本
- 心疼还在用Facebook的你一秒,Snapchat才是未来
- [HDU 4842]--过河(dp+状态压缩)
- 使用CablleStatement调用存储过程
- XP照片缩略图和照片本身显示不一致,如何解决防范?
- nfc修改饭卡软件下载_NFC卡模拟,从此打卡刷电梯,饭卡工卡各种IC卡都用手机搞定!...
- 我们都被监控了?揭秘全球电信网络7号信令(SS7)漏洞
- 用Java代码实现一个简单的聊天室功能
- 路易斯安那州立大学计算机科学,路易斯安那州立大学
- 微信开发工具BUG(漏洞),魔法加法
- 周转时间和带权周转时间的计算
- Linux 浏览器下安装 Flash
- 使用opencv的透视变换裁剪倾斜人脸
- bzoj-1565 植物大战僵尸