自定义注解实现日志操作

 利用@annotation创建一个自定义注解,定义生命周期,以及支持在方法上执行

/**
 * 自定义操作日志记录注解
 * 
 * @author zsy
 *
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD }) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface Log
{
    /**
     * 描述
     */
    public String title() default "";

    /**
     * 类型
     * @return
     */
    public String type() default "";//这些根据自己的需求进行修改

}

可根据自己的需求进行变更。

然后就是定义切入点,书写流程,这个切点是指定到了自定义注解上,只要使用该注解,就调用

/**
 * 操作日志记录处理
 *
 * @author zsy
 */
@Slf4j
@Aspect
@Component//如果在写完整个流程之后,发现无论使用around还是其他都执行了两次,那就去掉@component注解
public class OperLogAspect {

    // 配置织入点
    @Pointcut("@annotation(org.jeecg.modules.log.annotation.Log)")
    public void logPointCut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @Before("logPointCut()")
    public void doAfterReturning(JoinPoint joinPoint) {
        handleLog(joinPoint);
    }



    /**
     * LOG注解实现方式
     * @param joinPoint
     */
    protected void handleLog(final JoinPoint  joinPoint) {
        try {
            // 获得注解
            Log controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null) {
                return;
            }
            // *========数据库日志=========*//
            //SysLogUtils.getSysLog(controllerLog, joinPoint);自定义方法进行获取参数,可以往下看文章
            IClCaseQueueInfo operLog = SysLogUtils.getSysLog(controllerLog, joinPoint);
            // 保存数据库  这里是用到了大佬给的方法,其实这里可以直接进行保存数据库的操作,我这里是记录一下我的使用过程
            SpringBeansUtils.publishEvent(new LogEvent(operLog));
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
/**
     * 是否存在注解,如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                if (ObjectUtils.isNotEmpty(paramsArray[i]) && !isFilterObject(paramsArray[i])) {
                    try {
                        Object jsonObj = JSON.toJSON(paramsArray[i]);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象,则返回true;否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

如果在写完整个流程之后,发现无论使用around还是其他都执行了两次,那就去掉@component注解,我本身是使用的around注解,但是他的本身特性是相当于before和after结合,执行方法前后都会执行,记得调process方法进行原有方法的执行。如果是直接使用保存数据库,基本看到这里就结束了,一下是参考大佬代码做的:

/**
 * @author lengleng 系统日志事件
 */
public class LogEvent extends ApplicationEvent {
    //将自己定义的实体类参数填入,注意这里继承了ApplicationEvent类,主要是做自定义监听事件
	public LogEvent(IClCaseQueueInfo source) {
		super(source);
	}

}
/**
 * @author lengleng 异步监听日志事件
 */
@Slf4j
@RequiredArgsConstructor(onConstructor_= @Autowired)
public class LogListener {
	private final IClCaseQueueInfoService clCaseInfoService;

//	@Async  监听者模式  观察者模式  两种模式  此处请忽略...
	@Order
	@EventListener(LogEvent.class)
	public void saveSysLog(LogEvent event) {
		IClCaseQueueInfo sysLog = (IClCaseQueueInfo) event.getSource();
		clCaseInfoService.save(sysLog);
	}

}

到这里,大致流程已经基本结束了,其中缺少配置相关的东西,以及参数补全的东西。

1:解析参数工具类

/**
 * @author wangdi
 * @date 21-9-2
 */
@Component
@Slf4j
public class ResolverKit {

    private static final String PREFIX = "#{";
    private static final String SUBFIX = "}";


    /**
     * @param contextEL           带表达式的内容
     *                            举例:"用户名:+#{user.username}+,用户密码:+#{user.password}+,用户年龄:+#{user.age}+,手机号:+#{user.phone.number}"
     *                            注意:支持map对象获取key(map.key),但是不支持在对象中嵌套map(user.map.key)
     * @param proceedingJoinPoint
     * @return
     */
    public StringBuffer resolverContent(String contextEL, JoinPoint proceedingJoinPoint) {
        try {
            StringBuffer context = new StringBuffer();
            /**
             *  文字+#{map.name}+,+#{map.age}+#{map.abc}+.
             *  拆分
             *
             *  文字
             *  #{map.name}
             *  ,
             *  #{map.age}
             *  #{map.abc}
             *  .
             */
            if (contextEL.contains(PREFIX) && contextEL.contains(SUBFIX) && contextEL.contains("+")) {

                List<String> strings = Arrays.asList(contextEL.split("\\+"));
                for (String s : strings) {
                    if (s.contains(PREFIX) && s.contains(SUBFIX)) {
                        // 解析表达式
                        String spel = s.substring(s.indexOf(PREFIX) + PREFIX.length(), s.lastIndexOf(SUBFIX));
                        context.append(resolverExpression(spel, proceedingJoinPoint));
                    } else {
                        // 不需要解析
                        context.append(s);
                    }
                }

            } else {
                context.append(contextEL);
            }
            return context;
        } catch (Exception e) {
            log.error("表达式解析错误, 请检查@Log注解");
            return null;
        }
    }


    /**
     * 解析表达式(可嵌套两层)
     *
     * @param expression          举例: #{user.username}  #{map.key}  #{user.phone.number}
     * @param proceedingJoinPoint
     * @return
     */
    private String resolverExpression(String expression, JoinPoint proceedingJoinPoint) throws NoSuchFieldException, IllegalAccessException {
        String first = null;
        String second = null;
        String third = null;
        if (expression.contains(".")) {
            List<String> list = Arrays.asList(expression.split("\\."));
            if (list.size() > 3) {
                throw new RuntimeException("不支持三个变量以上的表达式: " + expression);
            }
            for (int i = 0; i < list.size(); i++) {
                if (0 == i) {
                    first = list.get(i);
                }
                if (1 == i) {
                    second = list.get(i);
                }
                if (2 == i) {
                    third = list.get(i);
                }
            }
        } else {
            first = expression;
        }

        //获取参数值
        Object[] args = proceedingJoinPoint.getArgs();
        //获取运行时参数的名称
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        //获取参数值类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        //获取参数值的数据
        String[] parameterNames = new DefaultParameterNameDiscoverer().getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            String parameterName = parameterNames[i];
            Object parameterValue = args[i];

            if (first.equals(parameterName)) {
                if (null == second) {
                    return (String) parameterValue;
                }
                if (null != second) {
                    // map.name      user.username
                    if (parameterValue instanceof Map<?, ?>) {
                        return (String) ((Map) parameterValue).get(second);
                    } else {
                        return (String) getFieldValue(parameterValue, second + (null != third ? "." + third : ""));
                    }
                }
            }
        }
        return null;
    }


    /**
     * 通过反射取对象指定字段(属性)的值
     *
     * @param target    目标对象
     * @param fieldName 字段的名字
     * @return 字段的值
     */
    private Object getFieldValue(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = target.getClass();
        String[] fs = fieldName.split("\\.");

        try {
            for (int i = 0; i < fs.length - 1; i++) {
                Field f = clazz.getDeclaredField(fs[i]);
                f.setAccessible(true);
                target = f.get(target);
                 if (null == target) {
                    // 嵌套内容中为null的返回null, 防止报错
                    return null;
                }
                clazz = target.getClass();
            }

            Field f = clazz.getDeclaredField(fs[fs.length - 1]);
            f.setAccessible(true);
            return f.get(target);
        } catch (Exception e) {
            throw e;
        }
    }
}

2:spring工具类

/**
 * @author dl
 * @date 2021/08/13 Spring 工具类
 */
@Slf4j
@Component
@Lazy(false)
public class SpringBeansUtils implements ApplicationContextAware, DisposableBean {

	private static ApplicationContext applicationContext = null;

	/**
	 * 取得存储在静态变量中的ApplicationContext.
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}



    /**
	 * 实现ApplicationContextAware接口, 注入Context到静态变量中.
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
        if(SpringBeansUtils.getApplicationContext() == null) {
            SpringBeansUtils.applicationContext = applicationContext;
        }
        log.info("ApplicationContext配置成功,applicationContext对象:"+ SpringBeansUtils.applicationContext);
	}

	/**
	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) {
		return (T) applicationContext.getBean(name);
	}

    public static <T> T getBean(String name, Class<T> requiredType) {
	    return applicationContext.getBean(name,requiredType);
    }
	/**
	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
	 */
	public static <T> T getBean(Class<T> requiredType) {
		return applicationContext.getBean(requiredType);
	}

	/**
	 * 清除SpringContextHolder中的ApplicationContext为Null.
	 */
	public static void clearHolder() {
		if (log.isDebugEnabled()) {
			log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
		}
		applicationContext = null;
	}

	/**
	 * 发布事件
	 * @param event
	 */
	public static void publishEvent(ApplicationEvent event) {
		if (applicationContext == null) {
			return;
		}
		applicationContext.publishEvent(event);
	}

	/**
	 * 实现DisposableBean接口, 在Context关闭时清理静态变量.
	 */
	@Override
	@SneakyThrows
	public void destroy() {
		SpringBeansUtils.clearHolder();
	}

}

3:自定义补全实体类参数方法

/**
 * 系统日志工具类
 *
 * @author duliang
 */
@UtilityClass
@Slf4j
public class SysLogUtils {

   /**
    * 写入日志文件
    *
    * @param controllerLog
    * @return
    */
   public static IClCaseQueueInfo getSysLog(Log controllerLog,JoinPoint joinPoint) {

      IClCaseQueueInfo clCaseQueueInfo = new IClCaseQueueInfo();
       //注入自己要用到的bean对象,之前自己用的注解自动注入,但是大佬不建议,然后就用了getbean的方式
    //参数解析  获取到方法传入参数的值
    //ResolverKit是用到的解析参数的bean
      ResolverKit resolverKit = SpringBeansUtils.getBean(ResolverKit.class);
      IClCaseQueueInfoService iClCaseQueueInfoService = SpringBeansUtils.getBean(IClCaseQueueInfoService.class);
      IClCaseInfoService clCaseInfoService = SpringBeansUtils.getBean(IClCaseInfoService.class);
      //解析数据
      String currentQueue = controllerLog.currentQueue();
      currentQueue = resolverKit.resolverContent(currentQueue, joinPoint).toString();
...省略一些业务逻辑代码

         return clCaseQueueInfo;
   }

最后就是配置类

/**
 * @author lengleng
 * @date 2019/2/1 日志自动配置
 */
@EnableAsync
@RequiredArgsConstructor
@ConditionalOnWebApplication
@Configuration(proxyBeanMethods = false)
public class LogAutoConfiguration {
//如果是使用异步监听  这个必须有
	@Bean
	public LogListener sysLogListener(IClCaseQueueInfoService clCaseInfoService) {
		return new LogListener(clCaseInfoService);
	}

	@Bean
	public OperLogAspect logAspect() {
		return new OperLogAspect();
	}

}

 然后就是使用了,我放一下我使用的方式,因为当时取值是map集合,所以是这样的:

//+{传入的map名字.具体的字段名字}是解析方法定义的格式
@Log(title = "自定义一些信息",currentQueue = "+#{传入的map名字.具体的字段名字}"")

最后就是,咱是个小萌新,慢慢学...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值