最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

@Async導(dǎo)致controller?404及失效原因解決分析

 更新時(shí)間:2022年07月22日 10:16:33   作者:linyb極客之路  
這篇文章主要為大家介紹了@Async導(dǎo)致controller?404失效問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

事情的起因是微服務(wù)A通過(guò)feign調(diào)用微服務(wù)B的某個(gè)接口,報(bào)了形如下的異常

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"}]

負(fù)責(zé)微服務(wù)A的工程師小張就找到負(fù)責(zé)提供該接口的工程師小李,問(wèn)小李是不是改動(dòng)了接口,小李一臉無(wú)辜說(shuō)他最近沒(méi)對(duì)這個(gè)接口做任何改動(dòng),不過(guò)小李還是說(shuō)道他排查一下。

排查過(guò)程

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

小林的排查思路如下,他先走查一下小李的接口代碼,發(fā)現(xiàn)他提供的接口實(shí)現(xiàn)層的方法上加了一個(gè)@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.....");
        }
    }

小林憑多年的經(jīng)驗(yàn)直覺(jué)告訴小李說(shuō),應(yīng)該是@Async引起。小李很斬釘截鐵的說(shuō)不可能啊,他@Async很早就加了,之前接口都可以訪(fǎng)問(wèn)的,小林一看小李說(shuō)得那么肯定,他也不好打擊小李。于是他接下來(lái)做了如下操作,先在項(xiàng)目中yml配置如下參數(shù),開(kāi)啟springweb日志

logging:
  level:
    org.springframework.web: trace

然后在項(xiàng)目中加了形如下代碼,來(lái)跟蹤接口bean的類(lèi)型

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

啟動(dòng)控制臺(tái),看日志形如下

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)

發(fā)現(xiàn)確實(shí)沒(méi)打印出相關(guān)requestMapping映射信息,這可以說(shuō)明一點(diǎn)就是小李那個(gè)接口沒(méi)有綁定到springmvc映射,也就是出現(xiàn)404的原因。接著觀(guān)察控制臺(tái)打印的bean,內(nèi)容形如下

asyncTestServiceImpl=class com.sun.proxy.$Proxy127

這很明顯這個(gè)接口bean已經(jīng)被jdk動(dòng)態(tài)代理給替換。小李看到控制臺(tái)打印的信息,若有所思,然后說(shuō),我把@Async去掉試下。小李把@Async去掉后,再觀(guān)察下控制臺(tái)

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

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

接著小李說(shuō)那在controller是不是就不能使用@Async注解了?,小林說(shuō)最好是把加@Async的邏輯挪到service層去處理,不過(guò)也不是controller就不能使用@Async注解了,接著小林為了驗(yàn)證這個(gè)想法,他把原來(lái)實(shí)現(xiàn)的接口類(lèi)去掉,形如下

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

啟動(dòng)后,查看控制臺(tái)

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 -

此時(shí)bean的類(lèi)型如下

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

訪(fǎng)問(wèn)接口,打印內(nèi)容如下

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

從控制臺(tái)可以發(fā)現(xiàn),都是http-nio-8080-exec-1線(xiàn)程觸發(fā),說(shuō)明異步?jīng)]生效,即@Async失效。后面對(duì)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.....");
        }
    }

訪(fǎng)問(wèn)接口,打印內(nèi)容如下

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

這說(shuō)明在controller其實(shí)也是可以用@Async,只是要額外做處理。所以建議是把@Async從controller中抽離出去,在新類(lèi)中進(jìn)行處理,示例如下

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

訪(fǎng)問(wèn)接口,打印內(nèi)容

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

說(shuō)明異步生效

排查結(jié)果分析

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 這個(gè)類(lèi)中,但具體是哪個(gè)方法進(jìn)行處理呢,我們可以通過(guò)日志打印的信息,進(jìn)行倒推,也可以基于spring的特性加斷點(diǎn)調(diào)試,比如通過(guò)afterPropertiesSet這一啟動(dòng)擴(kuò)展點(diǎn)調(diào)試起,就會(huì)發(fā)現(xiàn)RequestMappingHandlerMapping的映射處理是在

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

進(jìn)行處理,具體是通過(guò)processCandidateBean進(jìn)行處理

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

最終是通過(guò)detectHandlerMethods進(jìn)行處理

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

這個(gè)里面就是做了實(shí)際注冊(cè)。而執(zhí)行detectHandlerMethods的前提是

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

即只有加了@Controller或者@RequestMapping的類(lèi)會(huì)進(jìn)行處理,而@RestController為啥也處理,點(diǎn)擊
@RestController發(fā)現(xiàn)

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

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

AsynTestController.class.getAnnotation(RestController.class)

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

當(dāng)我們使用jdk動(dòng)態(tài)代理時(shí),因?yàn)楦割?lèi)上沒(méi)加@Controller或者@RequestMapping,因此他不會(huì)被mvc進(jìn)行映射處理,導(dǎo)致404。而使用cglib時(shí),因?yàn)樗亲鳛樽宇?lèi)繼承了目標(biāo)類(lèi),因此他會(huì)繼承目標(biāo)類(lèi)上的注解,因此當(dāng)為cglib代理時(shí),他會(huì)正常被mvc進(jìn)行映射處理

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

這是因?yàn)榧恿薂Async后,controller變成代理了,而當(dāng)要異步處理方法,用this時(shí),他使用的是目標(biāo)對(duì)象,而非代理對(duì)象。這跟現(xiàn)在面試事務(wù)為啥事務(wù)失效的八股文基本是一個(gè)套路

總結(jié)

本文主要講@Async導(dǎo)致controller 404,同時(shí)也使@Async失效的原因。解決的推薦方法就是將@Async抽離出controller,新建一個(gè)service類(lèi)進(jìn)行處理,更多關(guān)于@Async導(dǎo)致controller 404的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot+vue實(shí)現(xiàn)websocket配置過(guò)程解析

    springboot+vue實(shí)現(xiàn)websocket配置過(guò)程解析

    這篇文章主要介紹了springboot+vue實(shí)現(xiàn)websocket配置過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 如何解決使用restTemplate進(jìn)行feign調(diào)用new HttpEntity<>報(bào)錯(cuò)問(wèn)題

    如何解決使用restTemplate進(jìn)行feign調(diào)用new HttpEntity<>報(bào)錯(cuò)問(wèn)題

    這篇文章主要介紹了如何解決使用restTemplate進(jìn)行feign調(diào)用new HttpEntity<>報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • java使用spring實(shí)現(xiàn)發(fā)送mail的方法

    java使用spring實(shí)現(xiàn)發(fā)送mail的方法

    這篇文章主要介紹了java使用spring實(shí)現(xiàn)發(fā)送mail的方法,涉及java基于spring框架發(fā)送郵件的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10
  • Java String源碼分析并介紹Sting 為什么不可變

    Java String源碼分析并介紹Sting 為什么不可變

    這篇文章主要介紹了Java String源碼分析并介紹Sting 為什么不可變的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • SpringBoot 集成 Nebula的操作過(guò)程

    SpringBoot 集成 Nebula的操作過(guò)程

    這篇文章主要介紹了SpringBoot 集成 Nebula的操作過(guò)程,通過(guò)示例代碼介紹了java 環(huán)境下如何對(duì) Nebula Graph 進(jìn)行操作,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • Java Benchmark 基準(zhǔn)測(cè)試的實(shí)例詳解

    Java Benchmark 基準(zhǔn)測(cè)試的實(shí)例詳解

    這篇文章主要介紹了Java Benchmark 基準(zhǔn)測(cè)試的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下
    2017-08-08
  • Spring中的refresh方法分析

    Spring中的refresh方法分析

    這篇文章主要介紹了Spring中的refresh方法分析,文章圍繞主題展開(kāi)詳細(xì)的refresh方法相關(guān)資料介紹,需要的小伙伴可以參考一下
    2022-05-05
  • SpringCloud hystrix服務(wù)降級(jí)學(xué)習(xí)筆記

    SpringCloud hystrix服務(wù)降級(jí)學(xué)習(xí)筆記

    什么是服務(wù)降級(jí)?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實(shí)際業(yè)務(wù)情況及流量,對(duì)一些服務(wù)和頁(yè)面有策略的不處理或換種簡(jiǎn)單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運(yùn)作或高效運(yùn)作
    2022-10-10
  • IDEA中Spring Initializr沒(méi)有Java8選項(xiàng)的解決辦法

    IDEA中Spring Initializr沒(méi)有Java8選項(xiàng)的解決辦法

    在使用IDEA中的Spring Initializr創(chuàng)建新項(xiàng)目時(shí),Java 版本近可選擇Java17,21 ,不能選擇Java8;SpringBoot 版本也只有 3.x,所以本文給大家介紹了IDEA中Spring Initializr沒(méi)有Java8選項(xiàng)的解決辦法,需要的朋友可以參考下
    2024-06-06
  • try catch finally的執(zhí)行順序深入分析

    try catch finally的執(zhí)行順序深入分析

    首先執(zhí)行try,如果有異常執(zhí)行catch,無(wú)論如何都會(huì)執(zhí)行finally,當(dāng)有return以后,函數(shù)就會(huì)把這個(gè)數(shù)據(jù)存儲(chǔ)在某個(gè)位置,然后告訴主函數(shù),我不執(zhí)行了,接下來(lái)你執(zhí)行吧,所以函數(shù)就會(huì)推出
    2013-09-09

最新評(píng)論

佛坪县| 晋城| 博湖县| 汾西县| 资溪县| 砚山县| 弥勒县| 镇巴县| 格尔木市| 永善县| 凯里市| 河池市| 西青区| 天峻县| 故城县| 玉田县| 开鲁县| 黔南| 唐山市| 金沙县| 南城县| 二手房| 察雅县| 罗田县| 仙居县| 嘉黎县| 丹阳市| 都江堰市| 花垣县| 雷州市| 承德县| 绵阳市| 应用必备| 蕉岭县| 句容市| 彭山县| 军事| 英山县| 古蔺县| 安塞县| 泰宁县|