Maison >Java >javaDidacticiel >Comment résoudre le problème de l'utilisation de l'interface d'écoute des événements SpringBoot ApplicationListener

Comment résoudre le problème de l'utilisation de l'interface d'écoute des événements SpringBoot ApplicationListener

王林
王林avant
2023-05-26 10:13:151129parcourir

Reproduisons le problème. Regardez le code ci-dessous pour voir s'il y a un problème et comment le résoudre :

@RequestMapping("verify")
@RestController
@DependsOn({"DingAppInfoService","CloudChatAppInfoService"})
public class LoginAction {
    @Qualifier("ElderSonService")
    @Autowired
    private ElderSonService elderSonService;
    @Qualifier("EmployeeService")
    @Autowired
    private EmployeeService employeeService;
    @Qualifier("UserThreadPoolTaskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor userThreadPoolTaskExecutor;
    private static AuthRequest ding_request = null;
    private static RongCloud cloud_chat = null;
    private static TokenResult register = null;
    private static final ThreadLocal<String> USER_TYPE = new ThreadLocal<>();
    /**
     * 注意不能在bean的生命周期方法上添注@CheckAppContext注解
     */
    @PostConstruct
    public void beforeVerifySetContext() {
        AppContext.fillLoginContext();
        Assert.hasText(AppContext.getAppLoginDingId(), "初始化app_login_ding_id错误");
        Assert.hasText(AppContext.getAppLoginDingSecret(), "初始化app_login_ding_secret错误");
        Assert.hasText(AppContext.getAppLoginReturnUrl(), "初始化app_login_return_url错误");
        Assert.hasText(AppContext.getCloudChatKey(), "初始化cloud_chat_key错误");
        Assert.hasText(AppContext.getCloudChatSecret(), "初始化cloud_chat_secret错误");
        if (!(StringUtils.hasText(AppContext.getCloudNetUri()) || StringUtils.hasText(AppContext.getCloudNetUriReserve()))) {
            throw new IllegalArgumentException("初始化cloud_net_uri与cloud_net_uri_reserve错误");
        }
        ding_request = new AuthDingTalkRequest(
                AuthConfig.builder().
                        clientId(AppContext.getAppLoginDingId()).
                        clientSecret(AppContext.getAppLoginDingSecret()).
                        redirectUri(AppContext.getAppLoginReturnUrl()).build());
        cloud_chat = RongCloud.getInstance(AppContext.getCloudChatKey(), AppContext.getCloudChatSecret());
    }
.....以下API方法无所影响......
}

Ce qui peut être déroutant, c'est la méthode d'initialisation dans le code du composant du contrôleur :

    public static void fillLoginContext() {
        DingAppInfo appInfo = SpringContextHolder.getBean(DingAppInfoService.class).findAppInfo(APP_CODE);
        setDingVerifyInfo(appInfo);
        CloudChatAppInfo cloudChatAppInfo = SpringContextHolder.getBean(CloudChatAppInfoService.class).findAppInfo(APP_CODE);
        setCloudChatInfo(cloudChatAppInfo);
    }
   public static void setDingVerifyInfo(DingAppInfo dingAppInfo){
        if (dingAppInfo.checkKeyWordIsNotNull(dingAppInfo)) {
            put(APP_LOGIN_DING_ID, dingAppInfo.getApp_id());
            put(APP_LOGIN_DING_SECRET, dingAppInfo.getApp_secret());
            put(APP_LOGIN_RETURN_URL, dingAppInfo.getApp_return_url());
        }
    }
    public static void setCloudChatInfo(CloudChatAppInfo cloudChatAppInfo){
        if (cloudChatAppInfo.checkKeyWordIsNotNull(cloudChatAppInfo)){
            put(CLOUD_CHAT_KEY,cloudChatAppInfo.getCloud_key());
            put(CLOUD_CHAT_SECRET,cloudChatAppInfo.getCloud_secret());
            put(CLOUD_NET_URI,cloudChatAppInfo.getCloud_net_uri());
            put(CLOUD_NET_URI_RESERVE,cloudChatAppInfo.getCloud_net_uri_reserve());
        }
    }

Vous pouvez trouver ici que le fait de verser certaines données personnalisées du projet dans le thread local ThreadLocalb9ce0bbc1c208c036cee4db71a23ea6a> -isolés, différents threads ont des données différentes, et chacune de nos requêtes est un thread, ce qui entraînera inévitablement une perte de données, donc même si nous mettons les données lors de l'initialisation du composant, la requête suivante sera une exception sera signalée lorsque entrant.

Solution (en fait ce n'est pas la solution, mais cela peut aussi se faire au détriment de hautes performances) :

Concevoir un auditeur et un éditeur, dans la requête Perform traitement de l'aspect sur la méthode de saisie. L'aspect vérifie les données de l'objet AppContext. S'il est vide, l'événement est publié. S'il n'est pas vide, la méthode est saisie :

Event prototype : #🎜🎜. #

public class AppContextStatusEvent extends ApplicationEvent {
    public AppContextStatusEvent(Object source) {
        super(source);
    }
    public AppContextStatusEvent(Object source, Clock clock) {
        super(source, clock);
    }
}

Listener :

@Component
public class AppContextListener implements ApplicationListener<AppContextStatusEvent> {
    @Override
    public void onApplicationEvent(AppContextStatusEvent event) {
        if ("FillAppContext".equals(event.getSource())) {
            AppContext.fillLoginContext();
        } else if ("CheckAppContextLogin".equals(event.getSource())) {
            boolean checkContext = AppContext.checkLoginContext();
            if (!checkContext) {
                AppContext.fillLoginContext();
            }
        }
    }
}

Publisher (classe d'aspect) :

@Aspect
@Component("AppContextAopAutoSetting")
public class AppContextAopAutoSetting {
    @Before("@annotation(com.lww.live.ApplicationListener.CheckAppContextLogin)")
    public void CheckContextIsNull(JoinPoint joinPoint){
        System.out.println("-----------aop---------CheckAppContextLogin---------start-----");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        boolean value = signature.getMethod().getAnnotation(CheckAppContextLogin.class).value();
        if (value){
            boolean checkContext = AppContext.checkLoginContext();
            if (!checkContext){
                SpringContextHolder.pushEvent(new AppContextStatusEvent("FillAppContext"));
            }
        }
    }
    @After("@annotation(com.lww.live.ApplicationListener.CheckAppContextLogin)")
    public void CheckContextIsNull(){
        System.out.println("-----------aop---------CheckAppContextLogin---------end-----");
        SpringContextHolder.pushEvent(new AppContextStatusEvent("CheckAppContextLogin"));
    }
}

Ensuite, la classe d'aspect AOP capture l'annotation :

@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAppContextLogin {
    boolean value() default false;
    String info() default "";
}

Il n'est pas difficile de constater que nous sommes ici. Dans les méthodes de pré- et post-amélioration des aspects, l'intégrité des données AppContext est d'abord vérifiée, puis les données sont remplies. De cette façon, cela peut être réalisé si nous annotons @CheckAppContextLogin sur chaque méthode de requête. Cependant, le problème est que les autres données, à l'exception des méthodes remplies, sont trop difficiles à conserver, le coût des agents de détournement d'aspect est trop élevé et la fréquence de leur traitement. la vérification des données est trop élevée.

Solution correcte :

Réparties selon les fonctions métier des données, car le but principal est de réaliser le remplissage de deux objets, même si ces données sont perdues, mais le même contrôleur Les variables membres des composants sont toutes le même objet, et elles sont toutes initialisées lors de l'initialisation, donc les demandes de changement ultérieures n'affecteront pas leur capacité à mettre en œuvre les affaires :

 private static AuthRequest ding_request = null;
 private static RongCloud cloud_chat = null;

Nous pouvons le demander dans l'intercepteur Le front-end nous transmet le type d'utilisateur et l'identifiant unique de l'utilisateur actuel pour encapsuler les données personnalisées par l'utilisateur pour chaque requête (afin de réduire l'opération de requête en chaîne de méthodes dans la requête) :

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = (String) request.getSession().getAttribute("token");
        String user_type = (String) request.getSession().getAttribute("user_type");
        if (StringUtils.hasText(token) && StringUtils.hasText(user_type)) {
            Context context = new Context();
            if (Objects.equals(user_type, "elder_son")) {
                ElderSon elderSon = elderSonService.getElderSonByElderSonId(token);
                context.setContextByElderSon(elderSon);
                return true;
            } else if (Objects.equals(user_type, "employee")) {
                Employee employee = employeeService.getEmployeeById(token);
                context.setContextByEmployee(employee);
                return true;
            }
        } else if (StringUtils.hasText(user_type)) {
            response.sendRedirect("/verify/login?user_type=" + user_type);
            return false;
        }
        return false;
    }

Enfin , n'oubliez pas Supprimez la référence à ThreadLocal :

 @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        AppContext.clear();
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

Donc, le scénario réel est réellement résolu, le cœur est l'activité et la simplicité du code n'est qu'une exigence accessoire.

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer