目标,创建一个方法注解,我们在执行方法时,并可以给该方法的参数值动态传递给注解。
由于业务需要,需要在自定义注解中将参数中的值传入到注解的指定属性中,这很容易让我联想到 Spring 的 SpEL.
虽然根据反射也能得到相同结果,但是有更好的工具为什么不用呢?因此根据网上大神的攻略,整合出自定义注解伪动态传递参数的 SpEL 使用.
1、自定义注解
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* 自定义注解实现日志保存
* ip 使用SPEL表达式绑定动态变量参数值的形式传递,要求ip参数存在并赋值
*/
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface OperaLog {
String message()default "";
String ip()default "127.0.0.1";
}
2、定义AOP
使用SPEL表达式绑定动态变量参数值
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
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.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@Aspect // 1.日志切面类
@Component
@Slf4j
public class OperaLogAspect {
// 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
// logPointCut()代表切点名称
@Pointcut("@annotation(operaLog)")
public void logPointCut(OperaLog operaLog){}
// 3. 环绕通知
@Around("logPointCut(operaLog)")
public void logAround(ProceedingJoinPoint joinPoint, OperaLog operaLog) {
// 继续执行方法
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
saveLog(joinPoint, operaLog);
}
/**
* 保存日志
* @param joinPoint
* @param operaLog
*/
private void saveLog(ProceedingJoinPoint joinPoint, OperaLog operaLog){
String msg = getMsg(joinPoint, operaLog.message());
String ip = getIp(joinPoint, operaLog);
log.info("msg=" + msg);
log.info("ip =" + ip);
//TODO 保存日志
}
/**
* 通过使用SPEL表达式绑定动态变量参数值的形式获取ip
* @param joinPoint
* @param operaLog
* @return
*/
private String getIp(ProceedingJoinPoint joinPoint, OperaLog operaLog){
//获取方法签名(通过此签名获取目标方法信息)
MethodSignature ms = (MethodSignature)joinPoint.getSignature();
Method method = ms.getMethod();
Object[] args = joinPoint.getArgs();
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = localVariableTable.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]);
}
String ip = "";
String param = operaLog.ip();
// 使用变量方式传入业务动态数据
if(param.matches("^#.*.$")) {
ip = parser.parseExpression(param).getValue(context, String.class);
}
return ip;
}
/**
* 操作详情组装
* @param joinPoint
* @param msg
* @return
*/
private String getMsg(ProceedingJoinPoint joinPoint, String msg){
//获取类的字节码对象,通过字节码对象获取方法信息
Class<?> targetCls= joinPoint.getTarget().getClass();
//获取方法签名(通过此签名获取目标方法信息)
MethodSignature ms=(MethodSignature)joinPoint.getSignature();
String className = ms.getDeclaringType().getSimpleName();
String targetClsName=targetCls.getName();
System.out.println("targetClsName:"+targetClsName);
// 获取方法名称
String methodName = ms.getName();
String[] parameterName = ms.getParameterNames();
Map<String, String> map = new LinkedHashMap<>();
for(String o : parameterName){
map.put(o,"");
}
StringBuilder sb2 = new StringBuilder();
Class[] parameterType = ms.getParameterTypes();
for(Class o : parameterType){
sb2.append(o.getName() + "; ");
}
System.out.println("parameterType[]:"+sb2.toString());
String declaringTypeName =ms.getDeclaringTypeName();
System.out.println("declaringTypeName:"+declaringTypeName);
//获取目标方法上的注解指定的操作名称
Method targetMethod = null;
try {
targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println("targetMethod:"+targetMethod);
ms.getParameterNames();
// 获取入参
Object[] param = joinPoint.getArgs();
for(Object o : param){
for (Map.Entry<String, String> entry : map.entrySet()) {
if(StringUtils.notText(entry.getValue())){
entry.setValue(o.toString());
break;
}
}
}
StringBuffer sb = new StringBuffer(256);
sb.append("进入:").append(className).append("类[").append(methodName).append("]方法->").append(msg);
sb.append(" 参数:{");
for (Map.Entry<String, String> entry : map.entrySet()) {
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
sb.append("}");
return sb.toString();
}
}
3、使用
使用SPEL表达式绑定动态变量参数值 #ip
在需要的方法上加上我们的注解,传递要做的是什么事情,并且运用SPEL表达式把接口参数绑定到注解属性上
//使用SPEL表达式绑定动态变量参数值 #ip
@OperaLog(message = "删除人员", ip = "#ip")
public String delete(String ip, String userId,String userName) {
// TODO 删除人员
}
4、输出结果
msg=进入:Service类[delete]方法->删除人员 参数:{&ip=192.168.122.186&userId=101&userName=张三}
ip =192.168.122.186
写在最后
天下英雄出我辈,一入江湖岁月催
我是「无间行者」,努力把实践过的解决方案分享给大家
码字不易,给点鼓励吧,让我知道你在看