Swagger-ui.html界面打开报404错误
遇到的问题是:访问http://localhost:8080/swagger-ui.html没有打开swagger界面,看服务器日志发现没有找到对应的映射:/sagger-ui.html,但是检查了依赖和配置,都是正常的,而且用同样的依赖和配置在另外一个项目环境下是正常的
所以,很好奇为什么会出现这种情况,于是就开始寻找原因
初次看到问题时
-
猜测一
-
可能是swagger的配置问题
-
于是去网上找正确的配置和依赖,这是和springboot集成的版本
<!-- swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
@Configuration @EnableSwagger2 public class Swagger2 { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors .basePackage("com.onecoderspace.controller")) .paths(PathSelectors.any()).build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("spring boot示例接口API") .description("spring boot示例接口API") .version("1.0").build(); } }
- 去
http://start.spring.io
上下载一个依赖了web的项目,起名为Demo,然后使用这套配置,访问正常
- 去
-
所以配置没有问题
-
-
猜测二
-
好像没有第二种猜测了,直接查看日志了,其实,第一步做的事情就是查看日志
-
用
/sagger-ui.html
搜索日志发现了No mapping found for HTTP request with URI [/swagger-ui.html] in DispatcherServlet with name 'dispatcherServlet'
-
得到结论
- 没有
/sagger-ui.html
的映射
- 没有
-
思考
-
像在普通的配置了RequestMapping的controller类中,每个方法都有一个路径对应,比如
/user/info
对应方法getUserInfo()
,这种映射的信息会在项目启动的时候打印出来 -
另外,还有一种映射是资源的映射,比如
/sagger-ui.html
,也有对应处理机制 -
为什么这个项目没有映射,Demo项目却可以正常访问
-
接下来该怎么走
-
一是:既然有个正常的项目在,就对比这两个项目对
/sagger-ui.html
请求的处理方式即可发现原因,这也是我的第一个想法,也是这么进行的-
于是找到打印日志的代码,搜索到
org.springframework.web.servlet.DispatcherServlet#noHandlerFound
,发现根据/sagger-ui.html
没有获取到对应的HandlerExecutionChain
-
而
HandlerExecutionChain
是通过HandlerMapping
接口获得的- 从这可以猜测,
HandlerMapping
是处理映射的接口,那就可能有多种类型的映射(可能是映射的方式不一样),spring对此做了抽象 RequestMappingHandlerMapping
:请求类型的映射,对应上面举例的/user/info
SimpleUrlHandlerMapping
:url直接映射到handler- 还有很多
- 从这可以猜测,
-
通过断点对比发现,现在的项目没有
SimpleUrlHandlerMapping
这种映射方式,找到了第一个点,继续往下找为什么会没有 -
自然要看
SimpleUrlHandlerMapping
是怎么实例化的,然后发现了org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
,原来是启动时配置的,但是是在context里获得bean实例,还是不知道在哪里配置的,这条路走不通了 -
但是回过头来一想,既然要配置,那肯定是在同一个地方操作,这样方便管理,也方便扩展,那么在spring里面哪个配置类是做这个工作的呢
-
很容易找到
WebMvcConfigurationSupport
,找到org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping
,该方法会通过org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry#getHandlerMapping
获得一个SimpleUrlHandlerMapping
-
但是在
getHandlerMapping
方法中发现,资源处理器注册中心(ResourceHandlerRegistry
)需要注册了资源处理器才会有SimpleUrlHandlerMapping
实例化protected AbstractHandlerMapping getHandlerMapping() { if (this.registrations.isEmpty()) { return null; } ... SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setOrder(order); handlerMapping.setUrlMap(urlMap); return handlerMapping; }
-
而注册资源处理器的动作是在
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping
里面的addResourceHandlers(registry)
这个步骤中 -
到这里可以知道,
WebMvcConfigurationSupport
是配置资源处理器的地方 -
回到
addResourceHandlers(registry)
,发现这个方法是给子类实现的,DelegatingWebMvcConfiguration
实现了它,相当于把注册的工作交给了DelegatingWebMvcConfiguration
,从名称可以看出,它也不干活,只做委托的动作,委托给了它的成员变量WebMvcConfigurerComposite
,注意这个composite
,说明有很多WebMvcConfigurer
-
所以,注册的动作最终还是由实现了
WebMvcConfigurer
接口的实例来实现的,这些实例当中肯定有注册资源处理器的,现在的项目中肯定就是缺少这个实例 -
查看
WebMvcConfigurer
的实现,发现了WebMvcAutoConfigurationAdapter
,这是springboot里的自动装配,而且它有addResourceHandlers
的实现,可能需要它,那接下来就要看这个自动装配为啥没有生效了 -
来看看生效的条件,因为
WebMvcAutoConfigurationAdapter
是一个嵌套的配置,所以看它的依附类WebMvcAutoConfiguration
的一个条件:@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,可能就是WebMvcConfigurationSupport
的实例存在,才导致自动装配失效 -
那么,检查一下
WebMvcConfigurationSupport
是怎么实例化的,发现@EnableWebMvc
会引入它,那找一下哪里使用了这个注解,接着就找到了项目里面使用它的地方,注释掉,一访问就正常了
-
-
-
二是:去网上搜索“访问swagger报404”的信息
-
也搜过,开始不知道为什么要这么配,后来发现他们的问题和现在的问题本质是一样的,都是没有注册资源处理器,只不过是自己配置上去,如:
@Configuration public class WebConfig extends WebMvcConfigurationSupport { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } }
-
在Demo项目中配置,确实有用,但是不放心把这个配置放在现在的项目中,因为还没弄清楚为什么要加上
@EnableWebMvc
-
-
-
-
-
延伸的知识
- Filter和Interceptor的区别
- arraylist和array的区别
- 这个疑问来源于
HandlerExecutionChain
中的两个属性:interceptorList
、interceptors
- 这个疑问来源于
- springboot的两种依赖方式有什么区别,各自的优缺点是什么
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter
这个嵌套配置类为什么要配置成嵌套的
总结
- 猜测对了一个地方,自动装配生效那块,就是因为存在了
WebMvcConfigurationSupport
的实例,导致WebMvcAutoConfiguration
自动装配失效 - 中间太深入细节了,导致精力耗费太多,最后跳出了细节,从整体把握,得到解决
- 有些设计模式不熟悉,像适配器、拦截器这种,往往会纠结在这,导致忽略了主线,耗费精力
- 想想如果没有对比,该怎么进行下去呢
- 那就得很清楚springmvc的原理了,开始的时候什么也不知道,对注册资源处理器没有什么概念,也只是靠一步步调试、对比,才知道了个大概,具体的细节还不是很清楚,于是就有延伸出来的知识
- 一次次记录调试的过程,一次次地调整思路