> Java > java지도 시간 > SpringBoot가 모듈 로그 저장소를 구현하는 방법

SpringBoot가 모듈 로그 저장소를 구현하는 방법

WBOY
풀어 주다: 2023-05-11 09:37:05
앞으로
1011명이 탐색했습니다.

1. 간략한 설명

모듈 로그를 구현하는 방법은 대략 세 가지가 있습니다.

  • AOP + 사용자 정의 주석 구현

  • 지정된 형식의 로그 출력 + 로그 스캔 구현

  • 인터페이스의 코드 침입을 통해 비즈니스 로직 처리 시 로그를 기록하는 메서드를 호출합니다.

여기에서는 주로 세 번째 구현 방법에 대해 논의합니다.

사용자가 로그인한 후 로그인 로그를 기록하는 작업을 구현해야 한다고 가정해 보겠습니다.

호출 관계는 다음과 같습니다.

SpringBoot가 모듈 로그 저장소를 구현하는 방법

여기서 핵심 코드는 Transaction 종료 후 실행되도록 LoginService.login() 메서드에 설정되어 있습니다.

// 指定事务提交后执行
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    // 不需要事务提交前的操作,可以不用重写这个方法
    @Override
    public void beforeCommit(boolean readOnly) {
        System.out.println("事务提交前执行");
    }
    @Override
    public void afterCommit() {
        System.out.println("事务提交后执行");
    }
});
로그인 후 복사

여기서는 이 코드를 도구 클래스로 캡슐화합니다. :4.TransactionUtils를 참조하세요.

LoginService.login() 메서드에서 트랜잭션을 활성화하고 트랜잭션이 제출된 후 이를 지정하지 않으면 비동기 로그 처리 방법 및 새 트랜잭션에 문제가 발생합니다.

  • 비동기: 기본 트랜잭션이 실행되지 않을 수 있습니다. 완료되면 기본 트랜잭션에 새로 추가되거나 수정된 ​​데이터 정보를 읽지 못할 수 있습니다.

  • 새로운 작업 수행: Propagation.REQUIRES_NEW 트랜잭션 전파 동작을 통해 새 트랜잭션을 생성하고 로그 작업을 수행할 수 있습니다. 새 트랜잭션에서는 다음과 같은 문제가 발생할 수 있습니다.

    • 데이터베이스의 기본 트랜잭션 격리 수준은 반복 읽기이므로 트랜잭션 간에 커밋되지 않은 콘텐츠를 읽을 수 없으므로 오류도 발생합니다. 기본 트랜잭션에서 새 데이터 또는 새 데이터를 읽습니다.

    • 새 트랜잭션이 이전 트랜잭션과 동일한 테이블을 운영하는 경우 테이블이 잠깁니다.

  • 아무 것도 하지 않고 동기식으로 직접 호출합니다. 가장 문제가 많으며 다음과 같은 문제가 발생할 수 있습니다.

    • 예외를 포착하지 않고 인터페이스의 모든 작업을 직접 롤백합니다. 일부 데이터베이스(예: PostgreSQL)는 동일한 트랜잭션에서 하나의 실행이 실패하는 한 예외가 포착되더라도 나머지 모든 데이터베이스 작업은 실패하고 예외가 발생합니다.

    • 시간이 많이 걸리는 로깅으로 인해 인터페이스 응답 시간은 사용자 경험에 영향을 미칩니다.

    • 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 自定义动作函数式接口
 *
 * @author ACGkaka
 * @date 2023/4/26 13:55
 */
public interface Action {
        /**
        * 执行动作
        */
        void doSomething();
}
로그인 후 복사

4.TransactionUtils

import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
 * <p> @Title TransactionUtils
 * <p> @Description 事务同步工具类
 *
 * @author ACGkaka
 * @date 2023/4/26 13:45
 */
public class TransactionUtils {
    /**
     * 提交事务前执行
     */
    public static void beforeTransactionCommit(Action action) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void beforeCommit(boolean readOnly) {
                // 异步执行
                action.doSomething();
            }
        });
    }
    /**
     * 提交事务后异步执行
     */
    public static void afterTransactionCommit(Action action) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 异步执行
                action.doSomething();
            }
        });
    }
}
로그인 후 복사

5.LoginService

@Service
public class LoginService {
    @Autowired
    private LoginLogService loginLogService;
    /** 登录 */
    @Transactional(rollbackFor = Exception.class)
    public void login(String username, String pwd) {
        // 用户登录
        // TODO: 实现登录逻辑..
        // 事务提交后执行
        TransactionUtil.afterTransactionCommit(() -> {
            // 异步执行
            taskExecutor.execute(() -> {
                // 记录日志
                loginLogService.recordLog(username);
            });
        });
    }
}
로그인 후 복사

6.LoginLogService

6.1 @Async는 비동기

@Service
public class LoginLogService {
    /** 记录日志 */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void recordLog(String username) {
        // TODO: 实现记录日志逻辑...
    }
}
로그인 후 복사
를 구현합니다. 참고: @Async는 다음과 협력해야 합니다. @EnableAsync 사용, @EnableAsync를 시작 클래스, 구성 클래스 또는 사용자 정의 스레드 풀 클래스에 추가할 수 있습니다.

보충: @Async 주석은 메서드 구현을 확장하기 위해 상속된 클래스를 동적으로 생성하므로 현재 클래스가 Bean 컨테이너에 주입되지 못하고 BeanCurrentlyInCreationException이 발생할 수 있습니다. 다음 메서드를 사용할 수 있습니다. pool + @Autowired

6.2 사용자 정의 스레드 풀은 비동기를 구현합니다

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 {
    /**
     * 核心线程数(线程池维护线程的最小数量)
     */
    private int corePoolSize = 10;
    /**
     * 最大线程数(线程池维护线程的最大数量)
     */
    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 转换请求范围的上下文
        executor.setTaskDecorator(new ContextCopyingDecorator());
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        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 {
                    // 清除请求数据
                    MDC.clear();
                    RequestContextHolder.resetRequestAttributes();
                    SecurityContextHolder.clearContext();
                }
            };
        } catch (IllegalStateException e) {
            return runnable;
        }
    }
}
로그인 후 복사

3) 사용자 정의 스레드 풀은 비동기 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: 实现登录逻辑..
        // 事务提交后执行
        TransactionUtil.afterTransactionCommit(() -> {
            // 异步执行
            taskExecutor.execute(() -> {
                // 记录日志
                loginLogService.recordLog(username);
            });
        });
    }
}
로그인 후 복사

7. 기타 솔루션

7.1 프로그래밍 방식 트랜잭션을 사용하여 @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: 实现登录逻辑..
        });
        // 事务提交后异步执行
        taskExecutor.execute(() -> {
            // 记录日志
            loginLogService.recordLog(username);
        });
    }
}
로그인 후 복사
테스트됨:

이 구현에서 예외가 발생한 후 트랜잭션을 정상적으로 롤백할 수도 있습니다.

정상적으로 실행 나중에 트랜잭션이 실행된 후 내용을 읽을 수도 있습니다.

로그 기록 구현을 보지 마세요. 여기에 기록된 것은 지금까지 겪은 문제들뿐입니다.

위 내용은 SpringBoot가 모듈 로그 저장소를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿