Rumah  >  Artikel  >  Java  >  Bagaimana SpringBoot melaksanakan storan log modul

Bagaimana SpringBoot melaksanakan storan log modul

WBOY
WBOYke hadapan
2023-05-11 09:37:05968semak imbas

1. Penerangan ringkas

Terdapat kira-kira tiga cara untuk melaksanakan log modul:

  • AOP + pelaksanaan anotasi tersuai

  • Keluarkan log format yang ditentukan + pelaksanaan pengimbasan log

  • Dalam antara muka, melalui pencerobohan kod, selepas logik perniagaan diproses, kaedah dipanggil untuk merekodkan log.

Di sini kita membincangkan kaedah pelaksanaan ketiga.

Andaikan kita perlu melaksanakan operasi merekod log masuk selepas pengguna log masuk.

Hubungan panggilan adalah seperti berikut:

Bagaimana SpringBoot melaksanakan storan log modul

Kod teras di sini ditetapkan dalam kaedah LoginService.login() untuk dilaksanakan selepas transaksi tamat:

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

Di sini, kami merangkum kod ini ke dalam kelas alat, rujukan: 4.TransactionUtils.

Jika transaksi didayakan dalam kaedah LoginService.login() dan tidak dinyatakan selepas transaksi diserahkan, akan ada masalah dengan kaedah pemprosesan log tak segerak dan transaksi baharu:

  • Melakukan secara tak segerak: Memandangkan transaksi utama mungkin tidak selesai, maklumat data yang baru ditambah atau diubah suai dalam transaksi utama mungkin tidak dibaca; boleh menggunakan Propagation .REQUIRES_NEW gelagat penyebaran transaksi untuk mencipta transaksi baharu dan melaksanakan operasi pengelogan dalam transaksi baharu boleh menyebabkan masalah berikut:

  • Memandangkan tahap pengasingan transaksi lalai pangkalan data adalah Bacaan boleh diulang bermaksud kandungan yang tidak terikat tidak boleh dibaca antara perkara, jadi maklumat data baharu atau yang diubah suai dalam transaksi utama tidak boleh dibaca
    • Jika ia dihidupkan Jika transaksi baharu beroperasi; jadual yang sama seperti transaksi sebelumnya, ia akan menyebabkan jadual dikunci.

    • Buat apa-apa, terus panggil serentak: masalah paling bermasalah, yang mungkin membawa kepada masalah berikut:
  • Tiada pengecualian Tangkapan akan secara langsung membawa kepada pemulangan semula semua operasi pada antara muka; walaupun pengecualian itu ditangkap, pangkalan data yang selebihnya akan Semua operasi akan gagal dan pengecualian akan dilemparkan; .
    • 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 melaksanakan asynchronous
    @Service
    public class LoginLogService {
        /** 记录日志 */
        @Async
        @Transactional(rollbackFor = Exception.class)
        public void recordLog(String username) {
            // TODO: 实现记录日志逻辑...
        }
    }
Nota: @Async perlu digunakan bersama-sama dengan @EnableAsync @EnableAsync ditambahkan pada kelas permulaan, konfigurasi kelas, dan kumpulan benang tersuai Semua kategori tersedia.

Tambahan: Memandangkan anotasi @Async akan mencipta kelas yang diwarisi secara dinamik untuk melanjutkan pelaksanaan kaedah, ia mungkin menyebabkan kelas semasa gagal disuntik ke dalam bekas Bean dengan BeanCurrentlyInCreationException Anda boleh menggunakan kaedah berikut: kumpulan benang tersuai + @Autowired

6.2 Kumpulan urutan tersuai untuk melaksanakan tak segerak

1) Kumpulan urutan tersuai

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) Salin konteks permintaan

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) Kumpulan urutan tersuai untuk melaksanakan Perkhidmatan Masuk tak segerak

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. Penyelesaian lain

7.1 Gunakan transaksi terprogram dan bukannya @. Transactional

Kami juga boleh menggunakan TransactionTemplate dan bukannya @Transactional Annotation:

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);
        });
    }
}

Diuji:

Selepas pelaksanaan ini memberikan pengecualian, transaksi juga boleh digulung semula seperti biasa

Selepas pelaksanaan biasa, anda juga boleh membaca kandungan selepas pelaksanaan transaksi, yang boleh dilaksanakan.

Tidak kira betapa mudahnya untuk melaksanakan pembalakan, sebenarnya terdapat banyak masalah yang tercatat di sini hanyalah masalah yang dihadapi setakat ini.

Atas ialah kandungan terperinci Bagaimana SpringBoot melaksanakan storan log modul. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam