Java配置日志切面

什么是切面

面向切面编程(Aspect-oriented Programming 简称AOPAOP) ,是相对面向对象编程(Object-oriented Programming 简称OOP)的框架,作为OOP的一种功能补充. OOP主要的模块单元是类(class)。而AOP则是切面(aspect)。切面会将诸如事务管理这样跨越多个类型和对象的关注点模块化(在AOP的语义中,这类关注点被称为横切关注点(crosscutting))

切面能干什么

事务管理、权限控制、缓存控制、日志打印

使用切面的好处

  • 集中处理某一关注点/横切逻辑
  • 可以很方便的添加/删除关注点
  • 侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容* 易想到切面,对控制层代码0侵入

切面的使用(基于注解)

  • @Aspect 声明该类为一个注解类
  • @Pointcut 声明为切点
  • @Before 在切点之前执行代码
  • @After 在切点之后执行代码
  • @AfterReturning 切点返回内容后执行代码,可以对切点的返回值进行封装
  • @AfterThrowing 切点抛出异常后执行
  • @Around 环绕,在切点前后执行代码

配置日志切面

1.指定切点

      /**
     *.AOP切入点表达式
     *
     *execution  * com.bonc.service..*.*(..)
     * execution 为切点修饰符
     *      第一个位 为访问修饰符可以不写
     *      第二个 *  方法返回值
     *      第三位为 报名 *代表所有 ..代表所欲子包
     *      第四位为 方法名
     *      第五位  (..)代表任意参数
     *      日志的入口一般为controller层所以切点为所有的controller
     *
     * */
    @Pointcut("execution(* com.bonc..controller..*(..))")
    public void logPointCut() {
    }

2.构建访问日志实体

@Data
public class RequestInfo {

    private String ip;
    private String url;
    private String httpMethod;
    private String classMethod;
    private Object requestParams;
    private Object result;
    private Long timeCost;
    private RuntimeException exception;

}

3.用@Around注解 并且拼装参数

@Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(request.getRemoteAddr());
        requestInfo.setUrl(request.getRequestURL().toString());
        requestInfo.setHttpMethod(request.getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                proceedingJoinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));

        Object result =null;
        try {
           result = proceedingJoinPoint.proceed();

        }catch ( RuntimeException e){
            requestInfo.setException(e);
        }
        requestInfo.setResult(result);
        requestInfo.setTimeCost(System.currentTimeMillis() - start);
        LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));
        return  result;
    }



    /**
     * 获取入参
     * @param proceedingJoinPoint
     *
     * @return
     * */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }


    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            if (! (value instanceof Serializable)) {
                continue;
            }
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

## 4. 将该类声明为界面 并且将放置到spring容器中

```java
@Component
@Aspect

5.检查是否开启了自动注解

  • 如果是springboot 项目可以跳过这一步
  • 如果是使用sping mvc项目中需要在xml 检查是否有
<aop:aspectj-autoproxy/>
  • 如果是使用JavaConfig 形式的项目中需要在主javaConfig文件中添加
    @EnableAspectJAutoProxy 注解来开启切面

6. 启动项目,本地测试一下

正常结果如下

{
	"classMethod": "*******",
	"httpMethod": "GET",
	"ip": "127.0.0.1",
	"requestParams": {
		"pageSize": 10,
		"pageNum": 1
	},
	"result": {
		"code": 200,
		"msg": "成功"
	},
	"timeCost": 84,
	"url": "http://127.0.0.1:8090/*****"
}

异常结果

{
	"classMethod": "**",
	"exception": {
		"@type": "java.lang.ArithmeticException",
		"localizedMessage": "/ by zero",
		"message": "/ by zero",
		"stackTrace": [
			{
				"className": "com.bonc.huanghai.api.controller.oa.AnnouncementController",
				"fileName": "AnnouncementController.java",
				"lineNumber": 70,
				"methodName": "getAnnouncementList",
				"nativeMethod": false
			}
		]
	},
	"httpMethod": "GET",
	"ip": "127.0.0.1",
	"requestParams": {
		"pageSize": 10,
		"pageNum": 1
	},
	"timeCost": 96,
	"url": "http://127.0.0.1:8090/**"
}

代码

最后贴上所有的代码

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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.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 org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * @author sz
 * @version V1.0
 * @Description: 请求切面(用一句话描述该文件做什么)
 * @date 2021/2/6
 */
@Component
@Aspect
public class RequestLogAspect {

    private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);



    /**
     *.AOP切入点表达式
     *
     *execution  * com.bonc.service..*.*(..)
     * execution 为切点修饰符
     *      第一个位 为访问修饰符可以不写
     *      第二个 *  方法返回值
     *      第三位为 报名 *代表所有 ..代表所欲子包
     *      第四位为 方法名
     *      第五位  (..)代表任意参数
     *      日志的入口一般为controller层所以切点为所有的controller
     *
     * */
    @Pointcut("execution(* com.bonc..controller..*(..))")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(request.getRemoteAddr());
        requestInfo.setUrl(request.getRequestURL().toString());
        requestInfo.setHttpMethod(request.getMethod());
        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                proceedingJoinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));

        Object result =null;
        try {
           result = proceedingJoinPoint.proceed();

        }catch ( RuntimeException e){
            requestInfo.setException(e);
        }
        requestInfo.setResult(result);
        requestInfo.setTimeCost(System.currentTimeMillis() - start);
        LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));
        return  result;
    }



    /**
     * 获取入参
     * @param proceedingJoinPoint
     *
     * @return
     * */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }


    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            if (! (value instanceof Serializable)) {
                continue;
            }
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

}

@Data
public class RequestInfo {

    private String ip;
    private String url;
    private String httpMethod;
    private String classMethod;
    private Object requestParams;
    private Object result;
    private Long timeCost;
    private RuntimeException exception;

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java Spring AOP (Aspect Oriented Programming) 是一种编程范式,它通过将应用的业务逻辑和系统关注点(如日志、事务管理等)分离,提高了代码的可维护性和复用性。下面是一个简单的Spring AOP切面(Aspect)的Demo示例: 首先,你需要在Spring配置文件中启用AOP支持,并定义一个切面(Aspect): ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aspectj="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 启用AOP --> <aop:aspectj-autoproxy/> <!-- 定义切面 --> <bean id="myAspect" class="com.example.MyAspect"> <!-- 配置通知(advice) --> <property name="beforeAdvice" ref="beforeAdvice"/> </bean> <!-- 定义通知 --> <bean id="beforeAdvice" class="com.example.BeforeAdvice"/> </beans> ``` 然后,创建一个`MyAspect`切面类,通常包含通知(advice),例如前置通知(BeforeAdvice): ```java import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class MyAspect { @Before("execution(* com.example.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { // 在方法执行前添加的操作,如日志记录 System.out.println("Method " + joinPoint.getSignature() + " is about to execute."); } } ``` 在这个例子中,`@Before`注解定义了一个前置通知,它将在`com.example.service`包下的所有方法执行前执行。 接下来,创建`BeforeAdvice`类,这是一个具体的通知实现: ```java public class BeforeAdvice { // 可能包含一些自定义逻辑,比如参数检查或资源获取 } ``` 相关问题--: 1. Spring AOP中的通知有哪些类型? 2. `@Aspect`注解在Spring AOP中的作用是什么? 3. 如何在Spring配置切点(execution表达式)?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值