最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實現(xiàn)模塊日志入庫的項目實踐

 更新時間:2023年04月30日 09:32:05   作者:ACGkaka_  
本文主要介紹了SpringBoot實現(xiàn)模塊日志入庫的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

模塊調(diào)用之后,記錄模塊的相關(guān)日志,看似簡單,其實暗藏玄機。

1.簡述

模塊日志的實現(xiàn)方式大致有三種:

  • AOP + 自定義注解實現(xiàn)
  • 輸出指定格式日志 + 日志掃描實現(xiàn)
  • 在接口中通過代碼侵入的方式,在業(yè)務(wù)邏輯處理之后,調(diào)用方法記錄日志。

這里我們主要討論下第3種實現(xiàn)方式。

假設(shè)我們需要實現(xiàn)一個用戶登錄之后記錄登錄日志的操作。

調(diào)用關(guān)系如下:

這里的核心代碼是在 LoginService.login() 方法中設(shè)置了在事務(wù)結(jié)束后執(zhí)行:

// 指定事務(wù)提交后執(zhí)行
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
? ? // 不需要事務(wù)提交前的操作,可以不用重寫這個方法
? ? @Override
? ? public void beforeCommit(boolean readOnly) {
? ? ? ? System.out.println("事務(wù)提交前執(zhí)行");
? ? }
? ? @Override
? ? public void afterCommit() {
? ? ? ? System.out.println("事務(wù)提交后執(zhí)行");
? ? }
});

在這里,我們把這段代碼封裝成了工具類,參考:4.TransactionUtils。

如果在 LoginService.login() 方法中開啟了事務(wù),不指定事務(wù)提交后指定的話,日志處理的方法做異步和做新事務(wù)都會有問題:

  • 做異步:由于主事務(wù)可能沒有執(zhí)行完畢,導致可能讀取不到主事務(wù)中新增或修改的數(shù)據(jù)信息;
  • 做新事物:可以通過 Propagation.REQUIRES_NEW 事務(wù)傳播行為來創(chuàng)建新事務(wù),在新事務(wù)中執(zhí)行記錄日志的操作,可能會導致如下問題:
    • 由于數(shù)據(jù)庫默認事務(wù)隔離級別是可重復讀,意味著事物之間讀取不到未提交的內(nèi)容,所以也會導致讀取不到主事務(wù)中新增或修改的數(shù)據(jù)信息;
    • 如果開啟的新事務(wù)和之前的事務(wù)操作了同一個表,就會導致鎖表。
  • 什么都不做,直接同步調(diào)用:問題最多,可能導致如下幾個問題:
    • 不捕獲異常,直接導致接口所有操作回滾;
    • 捕獲異常,部分數(shù)據(jù)庫,如:PostgreSQL,同一事務(wù)中,只要有一次執(zhí)行失敗,就算捕獲異常,剩余的數(shù)據(jù)庫操作也會全部失敗,拋出異常;
    • 日志記錄耗時增加接口響應時間,影響用戶體驗。

2.LoginController

@RestController
public class LoginController {
? ? @Autowired
? ? private LoginService loginService;
? ? @RequestMapping("/login")
? ? public String login(String username, String pwd) {
? ? ? ? loginService.login(username, pwd);
? ? ? ? return "succeed";
? ? }
}

3.Action

/**
?* <p> @Title Action
?* <p> @Description 自定義動作函數(shù)式接口
?*
?* @author ACGkaka
?* @date 2023/4/26 13:55
?*/
public interface Action {
? ? ? ? /**
? ? ? ? * 執(zhí)行動作
? ? ? ? */
? ? ? ? void doSomething();
}

4.TransactionUtils

import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
?* <p> @Title TransactionUtils
?* <p> @Description 事務(wù)同步工具類
?*
?* @author ACGkaka
?* @date 2023/4/26 13:45
?*/
public class TransactionUtils {
? ? /**
? ? ?* 提交事務(wù)前執(zhí)行
? ? ?*/
? ? public static void beforeTransactionCommit(Action action) {
? ? ? ? TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void beforeCommit(boolean readOnly) {
? ? ? ? ? ? ? ? // 異步執(zhí)行
? ? ? ? ? ? ? ? action.doSomething();
? ? ? ? ? ? }
? ? ? ? });
? ? }
? ? /**
? ? ?* 提交事務(wù)后異步執(zhí)行
? ? ?*/
? ? public static void afterTransactionCommit(Action action) {
? ? ? ? TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void afterCommit() {
? ? ? ? ? ? ? ? // 異步執(zhí)行
? ? ? ? ? ? ? ? action.doSomething();
? ? ? ? ? ? }
? ? ? ? });
? ? }
}

5.LoginService

@Service
public class LoginService {
? ? @Autowired
? ? private LoginLogService loginLogService;
? ? /** 登錄 */
? ? @Transactional(rollbackFor = Exception.class)
? ? public void login(String username, String pwd) {
? ? ? ? // 用戶登錄
? ? ? ? // TODO: 實現(xiàn)登錄邏輯..
? ? ? ? // 事務(wù)提交后執(zhí)行
? ? ? ? TransactionUtil.afterTransactionCommit(() -> {
? ? ? ? ? ? // 異步執(zhí)行
? ? ? ? ? ? taskExecutor.execute(() -> {
? ? ? ? ?? ??? ?// 記錄日志
? ? ? ? ?? ??? ?loginLogService.recordLog(username);
? ? ? ? ? ? });
? ? ? ? });
? ? }
}

6.LoginLogService

6.1 @Async實現(xiàn)異步

@Service
public class LoginLogService {
    /** 記錄日志 */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void recordLog(String username) {
        // TODO: 實現(xiàn)記錄日志邏輯...
    }
}

注意:@Async 需要配合 @EnableAsync 使用,@EnableAsync 添加到啟動類、配置類、自定義線程池類上均可。

補充:由于 @Async 注解會動態(tài)創(chuàng)建一個繼承類來擴展方法的實現(xiàn),所以可能會導致當前類注入Bean容器失敗 BeanCurrentlyInCreationException,可以使用如下方式:自定義線程池 + @Autowired

6.2 自定義線程池實現(xiàn)異步

1)自定義線程池

AsyncTaskExecutorConfig.java

import com.demo.async.ContextCopyingDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
?* <p> @Title AsyncTaskExecutorConfig
?* <p> @Description 異步線程池配置
?*
?* @author ACGkaka
?* @date 2023/4/24 19:48
?*/
@EnableAsync
@Configuration
public class AsyncTaskExecutorConfig {
? ? /**
? ? ?* 核心線程數(shù)(線程池維護線程的最小數(shù)量)
? ? ?*/
? ? private int corePoolSize = 10;
? ? /**
? ? ?* 最大線程數(shù)(線程池維護線程的最大數(shù)量)
? ? ?*/
? ? private int maxPoolSize = 200;
? ? /**
? ? ?* 隊列最大長度
? ? ?*/
? ? private int queueCapacity = 10;
? ? @Bean
? ? public TaskExecutor taskExecutor() {
? ? ? ? ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
? ? ? ? executor.setCorePoolSize(corePoolSize);
? ? ? ? executor.setMaxPoolSize(maxPoolSize);
? ? ? ? executor.setQueueCapacity(queueCapacity);
? ? ? ? executor.setThreadNamePrefix("MyExecutor-");
? ? ? ? // for passing in request scope context 轉(zhuǎn)換請求范圍的上下文
? ? ? ? executor.setTaskDecorator(new ContextCopyingDecorator());
? ? ? ? // rejection-policy:當pool已經(jīng)達到max size的時候,如何處理新任務(wù)
? ? ? ? // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來執(zhí)行
? ? ? ? executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
? ? ? ? executor.setWaitForTasksToCompleteOnShutdown(true);
? ? ? ? executor.initialize();
? ? ? ? return executor;
? ? }
}

2)復制上下文請求

ContextCopyingDecorator.java

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.Map;
/**
?* <p> @Title ContextCopyingDecorator
?* <p> @Description 上下文拷貝裝飾者模式
?*
?* @author ACGkaka
?* @date 2023/4/24 20:20
?*/
public class ContextCopyingDecorator implements TaskDecorator {
? ? @Override
? ? public Runnable decorate(Runnable runnable) {
? ? ? ? try {
? ? ? ? ? ? // 從父線程中獲取上下文,然后應用到子線程中
? ? ? ? ? ? RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
? ? ? ? ? ? Map<String, String> previous = MDC.getCopyOfContextMap();
? ? ? ? ? ? SecurityContext securityContext = SecurityContextHolder.getContext();
? ? ? ? ? ? return () -> {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? if (previous == null) {
? ? ? ? ? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? MDC.setContextMap(previous);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? RequestContextHolder.setRequestAttributes(requestAttributes);
? ? ? ? ? ? ? ? ? ? SecurityContextHolder.setContext(securityContext);
? ? ? ? ? ? ? ? ? ? runnable.run();
? ? ? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? ? ? // 清除請求數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? ? ? ? ? RequestContextHolder.resetRequestAttributes();
? ? ? ? ? ? ? ? ? ? SecurityContextHolder.clearContext();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? } catch (IllegalStateException e) {
? ? ? ? ? ? return runnable;
? ? ? ? }
? ? }
}

3)自定義線程池實現(xiàn)異步 LoginService

import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Service
public class LoginService {
? ? @Autowired
? ? private LoginLogService loginLogService;
? ? @Qualifier("taskExecutor")
? ? @Autowired
? ? private TaskExecutor taskExecutor;
? ? /** 登錄 */
? ? @Transactional(rollbackFor = Exception.class)
? ? public void login(String username, String pwd) {
? ? ? ? // 用戶登錄
? ? ? ? // TODO: 實現(xiàn)登錄邏輯..
? ? ? ? // 事務(wù)提交后執(zhí)行
? ? ? ? TransactionUtil.afterTransactionCommit(() -> {
? ? ? ? ? ? // 異步執(zhí)行
? ? ? ? ? ? taskExecutor.execute(() -> {
? ? ? ? ?? ??? ?// 記錄日志
? ? ? ? ?? ??? ?loginLogService.recordLog(username);
? ? ? ? ? ? });
? ? ? ? });
? ? }
}

7.其他解決方案

7.1 使用編程式事務(wù)來代替@Transactional

我們還可以使用TransactionTemplate來代替 @Transactional 注解:

import org.springframework.transaction.support.TransactionTemplate;
@Service
public class LoginService {
? ? @Autowired
? ? private LoginLogService loginLogService;
? ? @Autowired
? ? private TransactionTemplate transactionTemplate;
? ? /** 登錄 */
? ? public void login(String username, String pwd) {
? ? ? ? // 用戶登錄
? ? ? ? transactionTemplate.execute(status->{
?? ??? ??? ?// TODO: 實現(xiàn)登錄邏輯..
? ? ? ? });
? ? ? ? // 事務(wù)提交后異步執(zhí)行
? ? ? ? taskExecutor.execute(() -> {
? ? ?? ??? ?// 記錄日志
? ? ?? ??? ?loginLogService.recordLog(username);
? ? ? ? });
? ? }
}

經(jīng)測試:

這種實現(xiàn)方式拋出異常后,事務(wù)也可以正?;貪L

正常執(zhí)行之后也可以讀取到事務(wù)執(zhí)行后的內(nèi)容,可行。

別看日志記錄好實現(xiàn),坑是真的多,這里記錄的只是目前遇到的問題。

參考地址:

1.SpringBoot 關(guān)于異步與事務(wù)一起使用的問題

到此這篇關(guān)于SpringBoot實現(xiàn)模塊日志入庫的項目實踐的文章就介紹到這了,更多相關(guān)SpringBoot 模塊日志入庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用MDC快速查詢應用接口全部執(zhí)行日志

    使用MDC快速查詢應用接口全部執(zhí)行日志

    這篇文章主要為大家介紹了使用MDC快速查詢應用接口全部執(zhí)行日志的方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • 高效的java版排列組合算法

    高效的java版排列組合算法

    這篇文章主要為大家詳細介紹了高效的java版排列組合算法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • 如何在32位Windows系統(tǒng)下安裝Java

    如何在32位Windows系統(tǒng)下安裝Java

    這篇文章主要介紹了如何在32位Windows系統(tǒng)下安裝Java,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Spring Boot如何使用HikariCP連接池詳解

    Spring Boot如何使用HikariCP連接池詳解

    這篇文章主要給大家介紹了關(guān)于Spring Boot如何使用HikariCP連接池的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用springboot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-03-03
  • 關(guān)于spring中事務(wù)的傳播機制

    關(guān)于spring中事務(wù)的傳播機制

    這篇文章主要介紹了關(guān)于spring中事務(wù)的傳播機制,所謂事務(wù)傳播機制,也就是在事務(wù)在多個方法的調(diào)用中是如何傳遞的,是重新創(chuàng)建事務(wù)還是使用父方法的事務(wù),需要的朋友可以參考下
    2023-05-05
  • Java+OpenCV調(diào)用攝像頭實現(xiàn)拍照功能

    Java+OpenCV調(diào)用攝像頭實現(xiàn)拍照功能

    隨著我們對環(huán)境、Mat基本使用越來越熟練、Java Swing也逐步熟悉了起來。本文將通過OpenCV驅(qū)動攝像頭實現(xiàn)識臉和拍照功能,需要的可以參考一下
    2022-03-03
  • SpringBoot使用Shiro實現(xiàn)動態(tài)加載權(quán)限詳解流程

    SpringBoot使用Shiro實現(xiàn)動態(tài)加載權(quán)限詳解流程

    本文小編將基于?SpringBoot?集成?Shiro?實現(xiàn)動態(tài)uri權(quán)限,由前端vue在頁面配置uri,Java后端動態(tài)刷新權(quán)限,不用重啟項目,以及在頁面分配給用戶?角色?、?按鈕?、uri?權(quán)限后,后端動態(tài)分配權(quán)限,用戶無需在頁面重新登錄才能獲取最新權(quán)限,一切權(quán)限動態(tài)加載,靈活配置
    2022-07-07
  • 淺談java Properties類的使用基礎(chǔ)

    淺談java Properties類的使用基礎(chǔ)

    下面小編就為大家分享一篇淺談java Properties類的使用基礎(chǔ),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • 深入理解ThreadLocal工作原理及使用示例

    深入理解ThreadLocal工作原理及使用示例

    這篇文章主要介紹了深入理解ThreadLocal工作原理及使用示例,涉及ThreadLocal<T> 簡介和使用示例及ThreadLocal<T>的原理等相關(guān)內(nèi)容,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • Java?方法的定義與調(diào)用詳解

    Java?方法的定義與調(diào)用詳解

    在java中,方法就是用來完成解決某件事情或?qū)崿F(xiàn)某個功能的辦法。方法實現(xiàn)的過程中,會包含很多條語句用于完成某些有意義的功能——通常是處理文本,控制輸入或計算數(shù)值,這篇文章我們來探究一下方法的定義與調(diào)用
    2022-04-04

最新評論

苏尼特左旗| 神农架林区| 宜宾县| 布尔津县| 正阳县| 老河口市| 三台县| 休宁县| 冕宁县| 绩溪县| 苏尼特右旗| 武冈市| 图木舒克市| 盘山县| 新蔡县| 石景山区| 崇左市| 千阳县| 德清县| 临朐县| 恩施市| 金堂县| 彰化县| 息烽县| 沧州市| 子长县| 久治县| 金沙县| 佳木斯市| 双鸭山市| 家居| 汤阴县| 乌兰察布市| 南丰县| 察隅县| 建水县| 义马市| 达州市| 宁武县| 余干县| 湖南省|