>Java >java지도 시간 >SpringBoot ApplicationListener 이벤트 수신 인터페이스 사용 문제를 해결하는 방법

SpringBoot ApplicationListener 이벤트 수신 인터페이스 사용 문제를 해결하는 방법

王林
王林앞으로
2023-05-26 10:13:151128검색

문제를 재현하고 문제가 있는지 확인하고 해결 방법을 확인하세요.

@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方法无所影响......
}

헷갈릴 수 있는 것은 컨트롤러 구성 요소의 초기화 방법 코드입니다.

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

찾을 수 있습니다. 사실은 일부 프로젝트 사용자 정의 데이터를 정적 사용자 정의 컨텍스트 AppContext의 로컬 스레드 ThreadLocalb9ce0bbc1c208c036cee4db71a23ea6a> 객체에 붓는 것입니다. 그러나 우리는 이 유형이 스레드 격리되어 있고 서로 다른 스레드가 서로 다른 데이터를 갖는다는 것을 알고 있습니다. 각 요청은 스레드이므로 필연적으로 데이터 손실이 발생하므로 구성 요소가 초기화될 때 데이터를 넣었더라도 다음 요청이 들어올 때 예외가 보고됩니다.

솔루션 아이디어(실제로 이것은 솔루션은 아니지만 고성능을 희생하여 수행할 수도 있음):

리스너 및 게시자를 설계하고 요청 입력 방법에 대한 측면 처리를 수행하고 측면을 확인합니다. AppContext 객체 데이터가 비어 있으면 이벤트가 게시되고 비어 있지 않으면 메서드가 입력됩니다.

Event 프로토타입:

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

그런 다음 AOP Aspect 클래스는 주석을 캡처합니다.

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

여기서는 Aspect의 개선 전후 방법에서 먼저 AppContext 데이터의 무결성을 확인한 다음 데이터를 채운다는 사실을 찾는 것이 어렵지 않습니다. 이런 식으로 모든 요청 메소드에 @CheckAppContextLogin을 추가하면 달성할 수 있습니다. 그러나 문제는 채워진 메소드를 제외한 다른 데이터를 유지하기가 너무 어렵고, 측면 하이재킹 에이전트의 비용이 너무 높으며, 빈도가 높다는 것입니다. 데이터 확인이 너무 높습니다.

올바른 해결 방법:

데이터의 비즈니스 기능에 따라 나눕니다. 이는 주로 두 개체의 채우기를 구현하기 때문입니다. 이러한 데이터가 손실되더라도 동일한 컨트롤러 구성 요소의 멤버 변수는 모두 동일한 개체입니다. 모두 초기화 중에 초기화되므로 후속 전환 요청이 비즈니스 구현 능력에 영향을 미치지 않습니다.

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

인터셉터의 프런트 엔드에 각 요청에 대해 현재 사용자의 사용자 유형과 고유 식별자를 전달하도록 요청할 수 있습니다. 사용자 정의 데이터 캡슐화(요청 내 메소드 체인 라이브러리 확인 작업 감소):

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

마지막으로 ThreadLocal 참조를 제거하는 것을 잊지 마세요:

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

그래서 실제 시나리오는 실제로 해결되었으며 핵심은 비즈니스입니다. , 코드의 단순성은 부수적인 것입니다.

위 내용은 SpringBoot ApplicationListener 이벤트 수신 인터페이스 사용 문제를 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제