Il existe environ trois façons d'implémenter les journaux de module :
AOP + implémentation d'annotations personnalisées
sortie du journal au format spécifié + implémentation de l'analyse des journaux
via l'intrusion de code dans l'interface, après traitement de la logique métier, appelez la méthode pour enregistrer le journal.
Ici, nous discutons principalement de la troisième méthode de mise en œuvre.
Supposons que nous devions implémenter une opération d'enregistrement des journaux de connexion après la connexion d'un utilisateur.
La relation d'appel est la suivante :
Le code principal ici est défini dans la méthode LoginService.login() à exécuter une fois la transaction terminée :
// 指定事务提交后执行 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { // 不需要事务提交前的操作,可以不用重写这个方法 @Override public void beforeCommit(boolean readOnly) { System.out.println("事务提交前执行"); } @Override public void afterCommit() { System.out.println("事务提交后执行"); } });
Ici, nous encapsulons ce code dans une classe d'outils, reportez-vous à :4.TransactionUtils.
Si vous activez une transaction dans la méthode LoginService.login() et ne la spécifiez pas après la soumission de la transaction, il y aura des problèmes avec la méthode de traitement asynchrone des journaux et les nouvelles transactions :
Asynchrone : car la transaction principale ne peut pas être exécuté Terminé, les informations de données nouvellement ajoutées ou modifiées dans la transaction principale peuvent ne pas être lues
Faites de nouvelles choses : vous pouvez créer une nouvelle transaction via le comportement de propagation de la transaction Propagation.REQUIRES_NEW et effectuer l'opération de journalisation ; dans la nouvelle transaction. Cela peut entraîner les problèmes suivants :
Étant donné que le niveau d'isolement des transactions par défaut de la base de données est une lecture répétable, cela signifie que le contenu non validé ne peut pas être lu entre les transactions, ce qui entraînera également l'échec de la lecture. lire des données nouvelles ou nouvelles dans la transaction principale. Informations sur les données modifiées
Si la nouvelle transaction ouverte exploite la même table que la transaction précédente, la table sera verrouillée.
Ne rien faire, appeler directement de manière synchrone : le plus problématique, qui peut entraîner les problèmes suivants :
Ne détecte pas les exceptions, ce qui entraîne directement le rollback de toutes les opérations sur l'interface ; Attrapez les exceptions, certaines bases de données, telles que : PostgreSQL, dans la même transaction, tant qu'une exécution échoue, même si l'exception est interceptée, toutes les opérations de base de données restantes échoueront et des exceptions seront levées
Cela prend du temps ; la journalisation augmente le temps de réponse de l’interface et affecte l’expérience utilisateur.
@RestController public class LoginController { @Autowired private LoginService loginService; @RequestMapping("/login") public String login(String username, String pwd) { loginService.login(username, pwd); return "succeed"; } }
/** * <p> @Title Action * <p> @Description 自定义动作函数式接口 * * @author ACGkaka * @date 2023/4/26 13:55 */ public interface Action { /** * 执行动作 */ void doSomething(); }
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(); } }); } }
@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); }); }); } }
@Service public class LoginLogService { /** 记录日志 */ @Async @Transactional(rollbackFor = Exception.class) public void recordLog(String username) { // TODO: 实现记录日志逻辑... } }
6.2 Le pool de threads personnalisés implémente l'asynchrone
1) Le pool de threads personnalisé
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) La demande de contexte de copie
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) Le pool de threads personnalisé implémente le LoginService
r asynchrone rreee7. Autres solutions
7.1 Utiliser des transactions programmatiques pour remplacer @Transactional
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); }); }); } }
Après que cette implémentation ait généré une exception, la transaction peut également être annulée normalement.
Exécuter normalement Vous pouvez également lire le contenu une fois la transaction exécutée ultérieurement, ce qui est faisable.
Même s'il est facile de mettre en œuvre la journalisation, il existe de nombreux pièges. Ce qui est enregistré ici ne concerne que les problèmes rencontrés jusqu'à présent.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!