简单的自定义日志功能
前言:一个健硕的系统内日志功能是必不可少的的,那么我们应该如何开发一个简单但又完善的系统日志呢,下面我们会慢慢探索。
那么一个日志功能最初要考虑的一个问题是什么呢?--->我认为是哪些操作需要记录日志,其次是操作的内容是哪些需要被记录的。对于第一个问题,哪些操作需要记录呢,比如我的一个类中是不是所有方法都记录?大家肯定会否定,否则部门老大一定会让你“乖乖♂站好”的。
因此我们需要自定义一个注解,当这个注解放在方法上时,我们才把这个操作记录,下面我们就自定义SysLog注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String value() default ""; }
因为我们是注解在方法上,所以@Target我们用ElementType.METHOD(常用的有TYPE、FIELD、PARAMETER等)。@Retention是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
@Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员
之后我们需要进行AOP的的切面操作
@Aspect @Order(5) @Component public class WebLogAspect { @Pointcut("@annotation(SysLog)") public void webLog(){} //日志bean private Log sysLog = null; @Before("webLog()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpSession session = (HttpSession) attributes.resolveReference(RequestAttributes.REFERENCE_SESSION); sysLog = new Log(); sysLog.setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); sysLog.setHttpMethod(request.getMethod()); //获取传入目标方法的参数 Object[] args = joinPoint.getArgs(); 进行其它操作,如IP解析,操作的用户,操作的参数记录等 } @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { try { Object obj = proceedingJoinPoint.proceed(); return obj; } catch (Exception e) { e.printStackTrace(); sysLog.setException(e.getMessage()); throw e; } } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) { String retString = JSONObject.toJSONString(ret); sysLog.setResponse(retString.length()>5000?JSONObject.toJSONString("请求参数数据过长不与显示"):retString); sysLog.setUseTime(System.currentTimeMillis() - startTime.get()); //这一步就把日志添加到数据库了
sysLog.insert(); } }
@Aspect表明该类为一个切面,@Order表明该类的加载顺序。@Pointcut表明切面的切点在哪方面,标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式
- execution:一般用于指定方法的执行,用的最多。
- within:指定某些类型的全部方法执行,也可用来指定一个包。
- this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- args:当执行的方法的参数是指定类型时生效。
- @target:当代理的目标对象上拥有指定的注解时生效。
- @args:当执行的方法参数类型上拥有指定的注解时生效。
- @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
- @annotation:当执行的方法上拥有指定的注解时生效。
- bean:当调用的方法是指定的bean的方法时生效。
我们使用的@annotation(注解的包名),AspectJ 支持 5 种类型的通知注解:
- @Before: 前置通知, 在方法执行之前执行
- @After: 后置通知, 在方法执行之后执行 。
- @AfterRunning: 返回通知, 在方法返回结果之后执行
- @AfterThrowing: 异常通知, 在方法抛出异常之后
- @Around: 环绕通知, 围绕着方法执行
我们可以在@Before内判断请求的方式是POST或GET,IP的解析,浏览器和当前操作系统的判断等等。@Around返回方法的执行内容(如跳转页面路径,请求是否成功等),@AfterRunning内将@Around的结果组装后插入日志表中等