中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

@Async導致controller?404如何解決

發布時間:2022-07-22 13:56:53 來源:億速云 閱讀:126 作者:iii 欄目:開發技術

這篇文章主要介紹“@Async導致controller 404如何解決”,在日常操作中,相信很多人在@Async導致controller 404如何解決問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”@Async導致controller 404如何解決”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    前言

    事情的起因是微服務A通過feign調用微服務B的某個接口,報了形如下的異常

    feign.FeignException$NotFound: [404] during [GET] to [http://feign-provider/test/async] [AyncTestServiceClient#testAsync()]: [{"timestamp":"2022-05-28T01:16:36.283+0000","status":404,"error":"Not Found","message":"No message available","path":"/test/async"}]

    排查過程

    小李排查的過程如下,他先通過swagger查看他提供給A服務接口是否存在,他一查發現他在swagger上看不到他提供給A服務的接口。于是他懷疑是不是有人動了他的代碼,他就去查找最近的git提交記錄,發現沒人動他的代碼,因為項目還沒發布,都在測試階段,他就根據項目集成的git-commit-id-maven-plugin插件定位到測試目前發布具體是哪個版本。(ps:對
    git-commit-id-maven-plugin感興趣的朋友,可以查看之前的文章聊聊如何驗證線上的版本是符合預期的版本)。然后他將該版本的代碼下到本地進行調試,他發現代碼中提供給A的接口還在,target下的class也有提供給A的接口class,但詭異的是swagger就是沒顯示他提供出去的接口,他一度以為是swagger出了問題,于是他用postman直接請求他提供A的接口,發現報了404。然后他就叫負責同個微服務B的同事小王,也幫忙試一下,發現結果就是404。后面沒招,小李就去求助他們項目資深同事小林。

    小林的排查思路如下,他先走查一下小李的接口代碼,發現他提供的接口實現層的方法上加了一個@Async,示例形如下

    @RestController
    @RequestMapping(AsyncTestService.INTER_NAME)
    public class AsyncTestServiceImpl implements AsyncTestService{
        @GetMapping("async")
        @Override
        public String testAsync() {
            System.out.println("testAsync start....");
            this.doAsynBiz();
            System.out.println("testAsync end....");
            return "hello async";
        }
        @Async
        public void doAsynBiz(){
                System.out.println("doAsynBiz.....");
            }
        }

    小林憑多年的經驗直覺告訴小李說,應該是@Async引起。小李很斬釘截鐵的說不可能啊,他@Async很早就加了,之前接口都可以訪問的,小林一看小李說得那么肯定,他也不好打擊小李。于是他接下來做了如下操作,先在項目中yml配置如下參數,開啟springweb日志

    logging:
      level:
        org.springframework.web: trace

    然后在項目中加了形如下代碼,來跟蹤接口bean的類型

    for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
                if(beanDefinitionName.toLowerCase().startsWith("AsyncTestService".toLowerCase())){
                    System.err.println(beanDefinitionName + "=" + applicationContext.getBean(beanDefinitionName).getClass());
                }
            }

    啟動控制臺,看日志形如下

    c.d.f.c.ConfigController:
        {GET /config/test}: test()
    09:15:04 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.c.ConfigController:
        {GET /config/test}: test()
    2022-05-28 09:15:04.564 TRACE 10120 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.i.UserServiceImpl:
        {GET /user/{id}}: getUserById(Long)
    09:15:04 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.i.UserServiceImpl:
        {GET /user/{id}}: getUserById(Long)
    2022-05-28 09:15:04.577 TRACE 10120 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        s.d.s.w.ApiResourceController:
        { /swagger-resources/configuration/ui}: uiConfiguration()
        { /swagger-resources}: swaggerResources()
        { /swagger-resources/configuration/security}: securityConfiguration()
    09:15:04 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        s.d.s.w.ApiResourceController:
        { /swagger-resources/configuration/ui}: uiConfiguration()
        { /swagger-resources}: swaggerResources()
        { /swagger-resources/configuration/security}: securityConfiguration()
    2022-05-28 09:15:04.590 TRACE 10120 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        o.s.b.a.w.s.e.BasicErrorController:
        { /error}: error(HttpServletRequest)
        { /error, produces [text/html]}: errorHtml(HttpServletRequest,HttpServletResponse)
    09:15:04 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        o.s.b.a.w.s.e.BasicErrorController:
        { /error}: error(HttpServletRequest)
        { /error, produces [text/html]}: errorHtml(HttpServletRequest,HttpServletResponse)

    發現確實沒打印出相關requestMapping映射信息,這可以說明一點就是小李那個接口沒有綁定到springmvc映射,也就是出現404的原因。接著觀察控制臺打印的bean,內容形如下

    asyncTestServiceImpl=class com.sun.proxy.$Proxy127

    這很明顯這個接口bean已經被jdk動態代理給替換。小李看到控制臺打印的信息,若有所思,然后說,我把@Async去掉試下。小李把@Async去掉后,再觀察下控制臺

    2022-05-28 10:09:40.814 TRACE 13028 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    10:09:40 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    2022-05-28 10:09:40.817 TRACE 13028 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.c.ConfigController:
        {GET /config/test}: test()
    10:09:40 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.c.ConfigController:
        {GET /config/test}: test()
    2022-05-28 10:09:40.820 TRACE 13028 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.i.UserServiceImpl:
        {GET /user/{id}}: getUserById(Long)
    asyncTestServiceImpl=class com.demo.feign.controller.AsyncTestServiceImpl

    通過控制臺可以發現,此時接口已經綁定到springmvc映射,而且打印出bean類型是真實對象bean。小李看到這個現象,也百思不得其解,他說道他之前確實是加了@Async,接口也能正常訪問。于是小林就問一句,你確定你加了@Async,異步生效了嗎,小李說開啟spring異步,不都是加@Async嗎。小林又問了一句,你在項目中開啟異步,除了加@Async,還有做什么處理嗎,小李說沒了,他之前在項目使用異步就都是加了@Async,也能用了好好的,小林一聽,基本上知道為什么小李之前@Async,接口還能正常訪問了,小林為了驗證想法,就問同負責該項目的小王,說你最近有加什么異步操作嗎,小王說有,小林進一步問,你是怎么做的,小王說,他先加@EnabledAsyn,開啟異步,然后在業務邏輯層上的方法上加@Async注解。小李一聽,說原來使用@Async還要配合@EnabledAsyn啊,他之前都不知道

    接著小李說那在controller是不是就不能使用@Async注解了?,小林說最好是把加@Async的邏輯挪到service層去處理,不過也不是controller就不能使用@Async注解了,接著小林為了驗證這個想法,他把原來實現的接口類去掉,形如下

    @RestController
    @RequestMapping(AsyncTestService.INTER_NAME)
    public class AsyncTestServiceImpl{
        @GetMapping("async")
        public String testAsync() {
            System.out.println(Thread.currentThread().toString() + "-----testAsync start....");
            this.doAsynBiz();
            System.out.println(Thread.currentThread().toString() + "-----testAsync end....");
            return "hello async";
        }
        @Async
        public void doAsynBiz(){
                System.out.println(Thread.currentThread().toString() + "-----doAsynBiz.....");
            }
        }

    啟動后,查看控制臺

    2022-05-28 10:41:31.624 TRACE 5068 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    10:41:31 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    2022-05-28 10:41:31.627 TRACE 5068 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.c.ConfigController:
        {GET /config/test}: test()
    10:41:31 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping -

    此時bean的類型如下

    asyncTestServiceImpl=class com.demo.feign.controller.AsyncTestServiceImpl$$EnhancerBySpringCGLIB$$a285a21c

    訪問接口,打印內容如下

    Thread[http-nio-8080-exec-1,5,main]-----testAsync start....
    Thread[http-nio-8080-exec-1,5,main]-----doAsynBiz.....
    Thread[http-nio-8080-exec-1,5,main]-----testAsync end....

    從控制臺可以發現,都是http-nio-8080-exec-1線程觸發,說明異步沒生效,即@Async失效。后面對controller做了如下改造

    @RestController
    @RequestMapping(AsyncTestService.INTER_NAME)
    public class AsyncTestServiceImpl{
        @Autowired
        private ObjectProvider<AsyncTestServiceImpl> asyncTestServices;
        @GetMapping("async")
        public String testAsync() {
            System.out.println(Thread.currentThread().toString() + "-----testAsync start....");
            asyncTestServices.getIfAvailable().doAsynBiz();
            System.out.println(Thread.currentThread().toString() + "-----testAsync end....");
            return "hello async";
        }
        @Async
        public void doAsynBiz(){
                System.out.println(Thread.currentThread().toString() + "-----doAsynBiz.....");
            }
        }

    訪問接口,打印內容如下

    Thread[http-nio-8080-exec-2,5,main]-----testAsync start....
    Thread[http-nio-8080-exec-2,5,main]-----testAsync end....
    Thread[task-1,5,main]-----doAsynBiz.....

    這說明在controller其實也是可以用@Async,只是要額外做處理。所以建議是把@Async從controller中抽離出去,在新類中進行處理,示例如下

    @Service
    public class AysncService {
        @Async
        public void doAsynBiz(){
            System.out.println(Thread.currentThread().getName() + "-----doAsynBiz.....");
        }
    }
    @RestController
    @RequestMapping(AsyncTestService.INTER_NAME)
    @RequiredArgsConstructor
    public class AsyncTestServiceImpl implements AsyncTestService {
        private final AysncService aysncService;
        @Override
        public String testAsync() {
            System.out.println(Thread.currentThread().getName() + "-----testAsync start....");
            aysncService.doAsynBiz();
            System.out.println(Thread.currentThread().getName() + "-----testAsync end....");
            return "hello async";
        }
    }

    訪問接口,打印內容

    http-nio-8080-exec-1-----testAsync start....
    http-nio-8080-exec-1-----testAsync end....
    task-1-----doAsynBiz.....

    說明異步生效

    排查結果分析

    1、接口404

    從mvc日志

    2022-05-28 10:59:50.394 TRACE 14152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    10:59:50 [main] TRACE o.s.w.s.m.m.a.RequestMappingHandlerMapping - 
        c.d.f.c.AsyncTestServiceImpl:
        {GET /test/async}: testAsync()
    2022-05-28 10:59:50.397 TRACE 14152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping :

    我們可以知道,controller映射處理是在RequestMappingHandlerMapping 這個類中,但具體是哪個方法進行處理呢,我們可以通過日志打印的信息,進行倒推,也可以基于spring的特性加斷點調試,比如通過afterPropertiesSet這一啟動擴展點調試起,就會發現RequestMappingHandlerMapping的映射處理是在

    protected void initHandlerMethods() {
            for (String beanName : getCandidateBeanNames()) {
                if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                    processCandidateBean(beanName);
                }
            }
            handlerMethodsInitialized(getHandlerMethods());
        }

    進行處理,具體是通過processCandidateBean進行處理

    protected void processCandidateBean(String beanName) {
            Class<?> beanType = null;
            try {
                beanType = obtainApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isTraceEnabled()) {
                    logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
                }
            }
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }

    最終是通過detectHandlerMethods進行處理

    protected void detectHandlerMethods(Object handler) {
            Class<?> handlerType = (handler instanceof String ?
                    obtainApplicationContext().getType((String) handler) : handler.getClass());
            if (handlerType != null) {
                Class<?> userType = ClassUtils.getUserClass(handlerType);
                Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                        (MethodIntrospector.MetadataLookup<T>) method -> {
                            try {
                                return getMappingForMethod(method, userType);
                            }
                            catch (Throwable ex) {
                                throw new IllegalStateException("Invalid mapping on handler class [" +
                                        userType.getName() + "]: " + method, ex);
                            }
                        });
                if (logger.isTraceEnabled()) {
                    logger.trace(formatMappings(userType, methods));
                }
                methods.forEach((method, mapping) -> {
                    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                    registerHandlerMethod(handler, invocableMethod, mapping);
                });
            }
        }

    這個里面就是做了實際注冊。而執行detectHandlerMethods的前提是

    beanType != null && isHandler(beanType)
    @Override
        protected boolean isHandler(Class<?> beanType) {
            return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                    AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
        }

    即只有加了@Controller或者@RequestMapping的類會進行處理,而@RestController為啥也處理,點擊
    @RestController發現

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {

    他本質就是@Controller。但我們通過反射查找注解,正常只會查找一層,比如

    AsynTestController.class.getAnnotation(RestController.class)

    他找到@RestController這一層,而不會找繼續再找@RestController里面的@Controller,而AnnotatedElementUtils.hasAnnotation,這個注解方法就不一樣,他是可以找到合并注解,即使是使用
    @RestController,他還會繼續找到里面的@Controller。因此這個方法對于找復合型注解很有用

    當我們使用jdk動態代理時,因為父類上沒加@Controller或者@RequestMapping,因此他不會被mvc進行映射處理,導致404。而使用cglib時,因為他是作為子類繼承了目標類,因此他會繼承目標類上的注解,因此當為cglib代理時,他會正常被mvc進行映射處理

    2、為何controller里面加了@Asyn異步就失效了

    這是因為加了@Async后,controller變成代理了,而當要異步處理方法,用this時,他使用的是目標對象,而非代理對象。這跟現在面試事務為啥事務失效的八股文基本是一個套路

    到此,關于“@Async導致controller 404如何解決”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    临汾市| 丰原市| 博白县| 城口县| 垣曲县| 兴国县| 蒙山县| 贵德县| 佛学| 庆云县| 竹溪县| 罗田县| 英德市| 阳西县| 荔浦县| 佛山市| 新河县| 西吉县| 新津县| 金沙县| 新邵县| 和龙市| 甘南县| 治多县| 满洲里市| 化隆| 疏附县| 保靖县| 东阳市| 双桥区| 古田县| 渭南市| 嘉义县| 临泉县| 临潭县| 瓮安县| 通榆县| 固原市| 望奎县| 平顶山市| 保亭|