Java利用自定义注解+aop实现日志记录

一、自定义注解

package com.spring.zhujie;
import java.lang.annotation.*;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ControllerAop {
    String url() default "";
    String action() default "";
}

二、aop

1.相关概念

1、切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

2、连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

3、通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。

4、切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。

2.五种通知方式

1.前置通知(@Before):在执行目标方法之前运行

2.后置通知(@After):在目标方法运行结束之后,不管有没有异常

3.返回通知(@AfterReturning):在目标方法正常返回值后运行

4.异常通知(@AfterThrowing):在目标方法出现异常后运行

5.环绕通知(@Around):目标方法的调用由环绕通知决定,即可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象。

三、代码实现

1.controller

package com.spring.controller;

import com.spring.bean.SysUserlog;
import com.spring.zhujie.ControllerAop;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.web.bind.annotation.*;

@EnableAspectJAutoProxy
@RestController
@RequestMapping("/aop")
public class AopController {
    @PostMapping("/a")
    //自定义注解
    @ControllerAop(action = "log")
    public void log(@RequestBody SysUserlog sysUserlog) {
        System.out.println("登录信息");
    }
}

2.创建切面

package com.spring.aop;

import com.alibaba.fastjson.JSONObject;
import com.spring.bean.SysUserlog;
import com.spring.zhujie.ControllerAop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Aspect
@Component
public class ControllerAopOperator {
    private static final Logger log = LoggerFactory.getLogger(ControllerAopOperator.class);

    //切入点(此处表示监听自定义注解)
    @Pointcut("@annotation(com.spring.zhujie.ControllerAop)")
    //切入点(此处表示监听controller下所有)
    //@Pointcut("execution(public * com.spring.controller..*.*(..))")
    public void log() {}

    //前置通知:在执行目标方法之前运行
    @Before("log()")
    public void doBeforeInServiceLayer(JoinPoint joinPoint) {
        //获取入参详细信息并打印
        String controllerIn = getControllerIn(joinPoint);
        log.info(controllerIn);
    }

    //后置通知,在执行目标方法后执行
    @After("log()")
    public void doAfterInServiceLayer(JoinPoint joinPoint) {
        System.out.println("After=================" + joinPoint.getSignature());
    }


    //返回通知:在目标方法正常返回值后运行
    @AfterReturning(pointcut = "log()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("AfterReturning=================" + joinPoint.getSignature());
    }

    //异常通知:在我们的目标方法出现异常后运行
    @AfterThrowing(value = "log()", throwing = "e")
    public void doAfter(JoinPoint joinPoint, Exception e) {

    }

    /**
     * 环绕通知
     * 目标方法的调用由环绕通知决定,即可以决定是否调用目标方法
     * joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象
     */
    @Around("log()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = pjp.proceed(); //执行目标代码
        return proceed;
    }

    /**
     * 是否存在注解,如果存在就记录日志
     *
     * @param joinPoint
     * @param //controllerAop
     * @return
     * @throws Exception
     */
    private static ControllerAop giveController(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(ControllerAop.class);
        }
        return null;
    }


    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param /joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static void getControllerMethodDescription(ControllerAop controllerAop, SysUserlog sysUserlog,
                                                      HttpServletRequest request) throws Exception {
        // 设置action动作
        System.out.println(controllerAop.action());
    }

    /**
     * 获取入参详细信息
     *
     * @param joinPoint
     * @return
     */
    public static String getControllerIn(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        StringBuilder requestLog = new StringBuilder();
        Signature signature = joinPoint.getSignature();
        requestLog.append("请求信息:").append("URL = {").append(request.getRequestURI()).append("},\t")
                .append("请求方式 = {").append(request.getMethod()).append("},\t")
                .append("请求IP = {").append(request.getRemoteAddr()).append("},\t")
                .append("类方法 = {").append(signature.getDeclaringTypeName()).append(".")
                .append(signature.getName()).append("},\t");

        // 处理请求参数
        String[] paramNames = ((MethodSignature) signature).getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        int paramLength = null == paramNames ? 0 : paramNames.length;
        if (paramLength == 0) {
            requestLog.append("请求参数 = {} ");
        } else {
            requestLog.append("请求参数 = [");
            for (int i = 0; i < paramLength - 1; i++) {
                requestLog.append(paramNames[i]).append("=").append(JSONObject.toJSONString(paramValues[i])).append(",");
            }
            requestLog.append(paramNames[paramLength - 1]).append("=").append(JSONObject.toJSONString(paramValues[paramLength - 1])).append("]");
        }
        return requestLog.toString();
    }
}

四、配置

1.pom

		<!--aspect-->
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        
        <!--利用log4j进行日志打印-->
		<dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>

2.application.properties配置log4j

### set log levels ###
log4j.rootLogger=DEBUG

### direct log messages to stdout ###
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Target=System.out
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-2p %m%n

### direct messages to file framework.log ###
log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
##log4j.appender.A2.File=D:/logs/resmanm.log
log4j.appender.A2.DatePattern='.'yyyy-MM-dd
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-5p(%10c{1}) %m%n

### application log config ###
#log4j.logger.com.linkage=ERROR,A2
log4j.logger.com.ch1=DEBUG,A1,A2
##log4j.logger.org.quartz.impl.StdSchedulerFactory=DEBUG,A1,A2

测试结果

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值