欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot使用AOP實(shí)現(xiàn)日志記錄功能詳解

 更新時(shí)間:2023年07月23日 10:06:50   作者:zero  
這篇文章主要為大家介紹了SpringBoot使用AOP實(shí)現(xiàn)日志記錄功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

項(xiàng)目背景

在進(jìn)行開發(fā)時(shí),會(huì)遇到以下問題

要記錄請求參數(shù),在每個(gè)接口中加打印和記錄數(shù)據(jù)庫日志操作,影響代碼質(zhì)量,也不利于修改

@PostMapping(value = "/userValidPost")
    public Response queryUserPost(@Valid @RequestBody UserInfo userInfo, BindingResult bindingResult) {
        try {
            //打印請求參數(shù)
            log.info("Request : {}", JSON.toJSONString(userInfo));
            //獲取返回?cái)?shù)據(jù)
            String result = "Hello " + userInfo.toString();
            //打印返回結(jié)果
            log.info("Response : {}", result);
            //記錄數(shù)據(jù)庫日志
            this.insertLog();
            return Response.ok().setData(result);
        } catch (Exception ex) {
            //打印
            log.info("Error : {}", ex.getMessage());
            //記錄數(shù)據(jù)庫日志
            this.insertLog();
            return Response.error(ex.getMessage());
        }

解決方案

使用AOP記錄日志

1.切片配置

為解決這類問題,這里使用AOP進(jìn)行日志記錄

/**
     * 定義切點(diǎn),切點(diǎn)為com.zero.check.controller包和子包里任意方法的執(zhí)行
     */
    @Pointcut("execution(* com.zero.check.controller..*(..))")
    public void webLog() {

    }
    
    /**
     * 前置通知,在切點(diǎn)之前執(zhí)行的通知
     *
     * @param joinPoint 切點(diǎn)
     */
    @Before("webLog() &&args(..,bindingResult)")
    public void doBefore(JoinPoint joinPoint, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError error = bindingResult.getFieldError();
            throw new UserInfoException(Response.error(error.getDefaultMessage()).setData(error));
        }
        //獲取請求參數(shù)
        try {
            String reqBody = this.getReqBody();
            logger.info("REQUEST: " + reqBody);
        } catch (Exception ex) {
            logger.info("get Request Error: " + ex.getMessage());
        }

    }

    /**
     * 后置通知,切點(diǎn)后執(zhí)行
     *
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        //處理完請求,返回內(nèi)容
        try {
            logger.info("RESPONSE: " + JSON.toJSONString(ret));
        } catch (Exception ex) {
            logger.info("get Response Error: " + ex.getMessage());
        }

    }

然后在執(zhí)行時(shí)就會(huì)發(fā)現(xiàn),前置通知沒有打印內(nèi)容

2019-12-25 22:08:27.875  INFO 4728 --- [nio-9004-exec-1] com.zero.check.aspect.WebLogAspect       : get Post Request Parameter err : Stream closed
2019-12-25 22:08:27.875  INFO 4728 --- [nio-9004-exec-1] com.zero.check.aspect.WebLogAspect       : REQUEST: 
2019-12-25 22:08:27.922  INFO 4728 --- [nio-9004-exec-1] c.z.c.controller.DataCheckController     : Response : {"id":"1","roleId":2,"userList":[{"userId":"1","userName":"2"}]}
2019-12-25 22:08:27.937  INFO 4728 --- [nio-9004-exec-1] com.zero.check.aspect.WebLogAspect       : RESPONSE: {"code":"ok","data":"Hello UserInfo(id=1, userList=[User(userId=1, userName=2)], roleId=2)","requestid":"bbac6b56722040acb224f61a75af70ec"}

原因在ByteArrayInputStream的read方法中,其中有一個(gè)參數(shù)pos是讀取的起點(diǎn),在接口使用了@RequestBody獲取參數(shù),就會(huì)導(dǎo)致AOP中獲取到的InputStream為空

/**
     * The index of the next character to read from the input stream buffer.
     * This value should always be nonnegative
     * and not larger than the value of <code>count</code>.
     * The next byte to be read from the input stream buffer
     * will be <code>buf[pos]</code>.
     */
    protected int pos;
    /**
     * Reads the next byte of data from this input stream. The value
     * byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available
     * because the end of the stream has been reached, the value
     * <code>-1</code> is returned.
     * <p>
     * This <code>read</code> method
     * cannot block.
     *
     * @return  the next byte of data, or <code>-1</code> if the end of the
     *          stream has been reached.
     */
    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }

以下代碼用于測試讀取InputStream

@Test
    public void testRequestInputStream()  throws Exception {
        request = new MockHttpServletRequest();
        request.setCharacterEncoding("UTF-8");
        request.setRequestURI("/ts/post");
        request.setMethod("POST");
        request.setContent("1234567890".getBytes());
        InputStream inputStream = request.getInputStream();

        //調(diào)用這個(gè)方法,會(huì)影響到下次讀取,下次再調(diào)用這個(gè)方法,讀取的起始點(diǎn)會(huì)后移6個(gè)byte
        //inputStream.read(new byte[6]);
        
        //ByteArrayOutputStream生成對象的時(shí)候,是生成一個(gè)100大小的byte的緩沖區(qū),寫入的時(shí)候,是把內(nèi)容寫入內(nèi)存中的一個(gè)緩沖區(qū)
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);

        int i = 0;
        byte[] b = new byte[100];
        while ((i = inputStream.read(b)) != -1) {
            byteOutput.write(b, 0, i);
        }
        System.out.println(new String(byteOutput.toByteArray()));
        inputStream.close();
    }

調(diào)用inputStream.read(new byte[6]);打印結(jié)果

7890

不調(diào)用inputStream.read(new byte[6]);打印結(jié)果

1234567890

正常情況下,可以使用InputStream的reset()方法重置讀取的起始點(diǎn),但ServletInputStream不支持這個(gè)方法,所以ServletInputStream只能讀取一次。

2.RequestWrapper

要多次讀取ServletInputStream的內(nèi)容,可以實(shí)現(xiàn)一個(gè)繼承HttpServletRequestWrapper的方法RequestWrapper,并重寫里面的getInputStream方法,這樣就可以多次獲取輸入流,如果要對請求對象進(jìn)行封裝,可以在這里進(jìn)行。

package com.zero.check.wrapper;
import com.alibaba.fastjson.util.IOUtils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/12/23 8:24
 * @history: 1.2019/12/23 created by wei.wang
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
    private final String body;
    /**
     * 獲取HttpServletRequest內(nèi)容
     *
     * @param request
     */
    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        try (InputStream inputStream = request.getInputStream();
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, IOUtils.UTF8))) {
            char[] charBuffer = new char[128];
            int bytesRead = -1;
            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } catch (IOException ex) {
            log.info("RequestWrapper error : {}", ex.getMessage());
        }
        body = stringBuilder.toString();
    }
    /**
     * 獲取輸入流
     *
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(IOUtils.UTF8));
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream(), IOUtils.UTF8));
    }
}

3.ChannelFilter

實(shí)現(xiàn)一個(gè)新的過濾器,在里面使用復(fù)寫后的requestWrapper,就可以實(shí)現(xiàn)ServletInputStream的多次讀取,如果要對請求對象進(jìn)行鑒權(quán),可以在這里進(jìn)行。

package com.zero.check.filter;

import com.zero.check.wrapper.RequestWrapper;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 15:07
 * @history: 1.2019/11/21 created by wei.wang
 */
@Component
@WebFilter(urlPatterns = "/*",filterName = "filter")
public class ChannelFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        ServletRequest requestWrapper = null;
        if(servletRequest instanceof HttpServletRequest) {
            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        if(requestWrapper == null) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            //使用復(fù)寫后的wrapper
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

4.測試

POSTMAN

接口

localhost:9004/check/userValidPost

請求方式 post

請求參數(shù)

{
    "id": "1",
    "roleId": 2,
    "userList": [
        {
            "userId": "1",
            "userName": "2"
        }
    ]
}

AOP打印日志

可以看到WebLogAspect成功打印了請求和返回結(jié)果

2019-12-25 23:48:45.047  INFO 17236 --- [nio-9004-exec-5] com.zero.check.aspect.WebLogAspect       : REQUEST: {
    "id": "1",
    "roleId": 2,
    "userList": [
        {
            "userId": "1",
            "userName": "2"
        }
    ]
}
2019-12-25 23:48:45.047  INFO 17236 --- [nio-9004-exec-5] com.zero.check.aspect.WebLogAspect       : RESPONSE: {"code":"ok","data":"Hello UserInfo(id=1, userList=[User(userId=1, userName=2)], roleId=2)","requestid":"3a219b741a704f95a844faa10c3968f8"}

返回參數(shù)

{
    "code": "ok",
    "data": "Hello UserInfo(id=1, userList=[User(userId=1, userName=2)], roleId=2)",
    "requestid": "3a219b741a704f95a844faa10c3968f8"
}

JUNIT

DateCheckServiceApplicationTests

package com.zero.check;


import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import java.beans.Transient;

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class DateCheckServiceApplicationTests {

    //聲明request變量
    private MockHttpServletRequest request;

    @Before
    public void init() throws IllegalAccessException, NoSuchFieldException {
        System.out.println("開始測試-----------------");
        request = new MockHttpServletRequest();
    }

    @Test
    public void test() {

    }

    public MockHttpServletRequest getRequest() {
        return request;
    }
}

DataCheckControllerTest

package com.zero.check.controller;

import com.zero.check.DateCheckServiceApplicationTests;
import com.zero.check.filter.ChannelFilter;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;

import static org.junit.Assert.*;

/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/12/26 0:11
 * @history: 1.2019/12/26 created by wei.wang
 */
@Slf4j
public class DataCheckControllerTest extends DateCheckServiceApplicationTests {

    private MockMvc mockMvc;

    @Autowired
    private DataCheckController dataCheckController;

    
    //測試前執(zhí)行,加載dataCheckController,并添加Filter
    @Before
    public void init() {
        mockMvc = MockMvcBuilders.standaloneSetup(dataCheckController).addFilter(new ChannelFilter()).build();
    }

    @Test
    public void userValidPost() throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders
                .post("/check/userValidPost")
                .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .content(String.valueOf("{\n" +
                        "    \"id\": \"1\",\n" +
                        "    \"roleId\": 2,\n" +
                        "    \"userList\": [\n" +
                        "        {\n" +
                        "            \"userId\": \"1\",\n" +
                        "            \"userName\": \"2\"\n" +
                        "        }\n" +
                        "    ]\n" +
                        "}")))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))// 預(yù)期返回值的媒體類型text/plain;charset=UTF-8
                .andReturn();
    }

    @Test
    public void userValidGet() {
    }

    @Test
    public void testAspectQueryUserPost() {
    }

    @Test
    public void testInputStream() throws Exception {
        String str = "1234567890";
        //ByteArrayInputStream是把一個(gè)byte數(shù)組轉(zhuǎn)換成一個(gè)字節(jié)流
        InputStream inputStream = new FileInputStream("src/main/resources/data/demo.txt");

        //調(diào)用這個(gè)方法,會(huì)影響到下次讀取,下次再調(diào)用這個(gè)方法,讀取的起始點(diǎn)會(huì)后移5個(gè)byte
        inputStream.read(new byte[5]);

        //ByteArrayOutputStream生成對象的時(shí)候,是生成一個(gè)100大小的byte的緩沖區(qū),寫入的時(shí)候,是把內(nèi)容寫入內(nèi)存中的一個(gè)緩沖區(qū)
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);

        int i = 0;
        byte[] b = new byte[100];
        while ((i = inputStream.read(b)) != -1) {
            byteOutput.write(b, 0, i);
        }
        System.out.println(new String(byteOutput.toByteArray()));
        inputStream.close();
    }

    @Test
    public void testRequestInputStream() throws Exception {
        MockHttpServletRequest request = getRequest();
        request.setCharacterEncoding("UTF-8");
        request.setRequestURI("/ts/post");
        request.setMethod("POST");
        request.setContent("1234567890".getBytes());
        InputStream inputStream = request.getInputStream();

        //調(diào)用這個(gè)方法,會(huì)影響到下次讀取,下次再調(diào)用這個(gè)方法,讀取的起始點(diǎn)會(huì)后移6個(gè)byte
        inputStream.read(new byte[6]);
        inputStream.reset();
        //ByteArrayOutputStream生成對象的時(shí)候,是生成一個(gè)100大小的byte的緩沖區(qū),寫入的時(shí)候,是把內(nèi)容寫入內(nèi)存中的一個(gè)緩沖區(qū)
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);

        int i = 0;
        byte[] b = new byte[100];
        while ((i = inputStream.read(b)) != -1) {
            byteOutput.write(b, 0, i);
        }
        System.out.println(new String(byteOutput.toByteArray()));
        inputStream.close();
    }
}

測試結(jié)果

AOP打印參數(shù)

2019-12-26 00:18:11.136  INFO 13016 --- [           main] com.zero.check.aspect.WebLogAspect       : REQUEST: {
    "id": "1",
    "roleId": 2,
    "userList": [
        {
            "userId": "1",
            "userName": "2"
        }
    ]
}
2019-12-26 00:18:11.542  INFO 13016 --- [           main] com.zero.check.aspect.WebLogAspect       : RESPONSE: {"code":"ok","data":"Hello UserInfo(id=1, userList=[User(userId=1, userName=2)], roleId=2)","requestid":"68c469d15724474a937ef39d3c6ceccf"}


2019-12-26 00:18:11.579  INFO 13016 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

Process finished with exit code 0

代碼Git地址

git@github.com:A-mantis/SpringBootDataCheck.git

以上就是SpringBoot使用AOP實(shí)現(xiàn)日志記錄功能詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot AOP日志記錄的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java的HashMap源碼解析

    Java的HashMap源碼解析

    這篇文章主要介紹了Java的HashMap源碼解析,HashMap是一個(gè)用于存儲Key-Value鍵值對的集合,每一個(gè)鍵值對是一個(gè)Node,后臺是用一個(gè)Node數(shù)組來存放數(shù)據(jù),這個(gè)Node數(shù)組就是HashMap的主干,需要的朋友可以參考下
    2023-11-11
  • 學(xué)習(xí)Java之IO流的基礎(chǔ)概念詳解

    學(xué)習(xí)Java之IO流的基礎(chǔ)概念詳解

    這篇文章主要給大家介紹了Java中的IO流,我們首先要搞清楚一件事,就是為什么需要IO流這個(gè)東西,但在正式學(xué)習(xí)IO流的使用之前,小編有必要帶大家先了解一下IO流的基本概念,需要的朋友可以參考下
    2023-09-09
  • springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題

    springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題

    這篇文章主要介紹了springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題,需要的朋友可以參考下
    2017-06-06
  • Java中RedisUtils工具類的使用

    Java中RedisUtils工具類的使用

    本文主要介紹了Java中RedisUtils工具類的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 聊聊spring @Transactional 事務(wù)無法使用的可能原因

    聊聊spring @Transactional 事務(wù)無法使用的可能原因

    這篇文章主要介紹了spring @Transactional 事務(wù)無法使用的可能原因,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java使用Log4j記錄日志的方法詳解

    Java使用Log4j記錄日志的方法詳解

    log4j是一個(gè)常用的日志框架,用于記錄應(yīng)用程序的執(zhí)行過程或異常等記錄在日志文件中。本文將利用Log4j記錄日志,感興趣的可以了解一下
    2022-03-03
  • Java 圖解Spring啟動(dòng)時(shí)的后置處理器工作流程是怎樣的

    Java 圖解Spring啟動(dòng)時(shí)的后置處理器工作流程是怎樣的

    spring的后置處理器有兩類,bean后置處理器,bf(BeanFactory)后置處理器。bean后置處理器作用于bean的生命周期,bf的后置處理器作用于bean工廠的生命周期
    2021-10-10
  • Java?@Validated遇到的大坑與處理

    Java?@Validated遇到的大坑與處理

    這篇文章主要介紹了Java?@Validated遇到的大坑與處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java中內(nèi)存溢出和內(nèi)存泄漏如何解決

    Java中內(nèi)存溢出和內(nèi)存泄漏如何解決

    ?內(nèi)存溢出?和?內(nèi)存泄漏?是兩種常見的內(nèi)存管理問題,它們都會(huì)對程序的性能產(chǎn)生負(fù)面影響,本文主要介紹了Java中的內(nèi)存溢出和內(nèi)存泄漏問題解決,感興趣的可以了解一下
    2024-12-12
  • java用戶管理注冊功能 含前后臺代碼

    java用戶管理注冊功能 含前后臺代碼

    這篇文章主要介紹了java用戶管理注冊功能,含前端和后臺代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10

最新評論