【小家Spring】如何证明Spring是存在父子容器的?顺便解决Spring MVC访问一直404问题(配置文件没问题)(下)

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】如何证明Spring是存在父子容器的?顺便解决Spring MVC访问一直404问题(配置文件没问题)(下)

关于@EnableWebMvc和RequestMappingHandlerMapping和RequestMappingHandlerAdapter


我们知道,在我们使用xml配置的时候,我们获取这样配置过


<!-- 注解的处理器映射器,他来解析RequestMapping,然后和处理器对应起来 -->
<bean   class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> 
<!-- 注解的处理器适配器 结合上面一起协作-->
<bean   class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> 


简单的从源码里看一眼:以RequestMappingHandlerMapping为例:它间接的实现了InitializingBean接口:


  @Override
  public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    super.afterPropertiesSet();
  }
  //super.afterPropertiesSet();如下
  @Override
  public void afterPropertiesSet() {
    initHandlerMethods();
  }
  // 初始化处理器
  protected void initHandlerMethods() {
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
        obtainApplicationContext().getBeanNamesForType(Object.class));
    //拿到所有的beanNames
    for (String beanName : beanNames) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
        Class<?> beanType = null;
        try {
          beanType = obtainApplicationContext().getType(beanName);
        }
        //这里很关键,这里表面 只处理标注了注解@Controller或者@RequestMapping的  就认为才是处理器  然后拿到里面的方法,每一个方法就是一个处理器
        if (beanType != null && isHandler(beanType)) {
          detectHandlerMethods(beanName);
        }
      }
    }
    handlerMethodsInitialized(getHandlerMethods());
  }
  @Override
  protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
        AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
  }


这两个Bean很重要,在初始化的时候能吧url和Controller里的handler对应起来。但是在我们使用了Spring3.0以后,这两个Bean可以不用再显示的配置出来了,而是使用下面依据配置可以代替:

<!-- 备注:此句只写在Spring MVC的配置文件里,否则出问题  Handler映射不上 -->
<mvc:annotation-driven></mvc:annotation-driven>


它的作用是启动的时候会自动注册上面两个Bean。然后这句xml的配置,效果同@EnableWebMvc注解。同样的,这个注解只能写在Spring MVC的配置文件里,而不能写在别处(主要是要保证不能被Root容器扫描进去了~)


image.png


比如我现在的配置,就出过问题:它是个单独的配置文件,就出问题了。Controller正常加入到子容器里了,但是映射都木有了,导致请求直接404了。


跟踪源码分析原因,终于找到了原因:因为@EnableWebMvc写在单独配置文件了,而Spring根容器在初始化的时候,扫描到了这个配置类因此解析了此注解。然后类RequestMappingHandlerMapping等核心处理类就被正常加载进了Spring根容器里。


接下来初始化Spring MVC的子容器的时候,也会解析此注解。然后在创建Bean的时候,发现此Bean已经存在了,所以不会再创建了。因此最终的结果是:这两个Bean都创建了,只是它不在Spring MVC的容器了,而是在父容器了。


但是,但是,但是 【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)这篇文章里我讲到过,这种MVC的处理器如果交给父容器去管理,会直接404报错的。


只要找到了原因,就很好解决了,两种方案:

一:根容器的配置文件里排除掉就行了

@ComponentScan(value = "com.fsx", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
        //排除掉web容器的配置文件,否则会重复扫描
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {AppConfig.class}),
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {WebMvcConfig.class}),
})
@Configuration
public class RootConfig { ... }


二:写在字容器里**(推荐做法)** 这样可以删除掉WebMvcConfig这个配置文件了


@ComponentScan(value = "com.fsx", useDefaultFilters = false,
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
)
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    @Bean
    public Child child() {
        return new Child();
    }
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8));
        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        converters.add(messageConverter);
    }
}

备注:因为Spring Boot不存在父子容器概念,因此都不存在这类似的问题


如何在Controller中获取到Spring子容器?如何获取到Controller这个Bean呢?


从上面的知识中,我们可以知道,下面这是会报错的:


    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() {
        ApplicationContext ctx1 = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
        //从controller从获取这个Bean 能获取到controller这个Bean吗
        // 用跟容器或者bean 直接报错org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type
        HelloController bean = ctx1.getBean(HelloController.class);
        System.out.println(bean);
}


这个不用解释了,因为我们平时通过工具类等获取到的容器都是根容器(其实99.99%的情况下,我们也只需要获取到根容器就够了),所以找不到Controller这个Bean是正常的。


那么接下来问题虽然没大作用,但是对小伙伴是否对父子容器原理熟悉,是个考验。


问:我就要在Controller这里获取到自己(或者别的Controller),怎么办?


方案一:注入@Autowired

    @Autowired
    private HelloController helloController;
    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() {
        System.out.println(helloController == this); //true
        return "hello...Get";
    }

方案二:实现ApplicationContextAware接口,把子容器注入进来再getBean


    private ApplicationContext context;
    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() {
        System.out.println(context.getParent()); //Root WebApplicationContext: startup date [Sun Feb 24 21:49:25 CST 2019]; root of context hierarchy
        System.out.println(context.getBean(HelloController.class)); //com.fsx.controller.HelloController@32a2d798
        return "hello...Get";
    }


方案三:从ServletContext上下文/request请求域里拿到子容器,然后在getBean()


    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() {
        //方法一
        ApplicationContext context = (ApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        ApplicationContext context1 = RequestContextUtils.findWebApplicationContext(request);
        //此处dispatcher为dispatcherServlet的默认名称
        ApplicationContext context2 = (ApplicationContext) request.getServletContext().getAttribute(FrameworkServlet.SERVLET_CONTEXT_PREFIX + "dispatcher");
        System.out.println(context == context1); //true
        System.out.println(context == context2); //true
        System.out.println(context.getBean(HelloController.class));
        return "hello...Get";
    }


至于为何能从ServletContext中获取到这个子容器,原理请参考:【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)


总结


Spring MVC父子容器的设计对隔离性非常的好,但同时也经常带来一些我们认为莫名其妙的问题,增大了使用了复杂度(这也就是为何Spring Boot使用同一个容器管理的原因吧)


只有知己知彼,从原理的层面去了解了。出现了问题才能迅速定位,从而以最快最好的方式去解决。做到心中有数,才能更容易决胜千里



相关文章
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
7天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
2月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
8天前
|
消息中间件 NoSQL 安全
(转)Spring Boot加载 不同位置的 application.properties配置文件顺序规则
这篇文章介绍了Spring Boot加载配置文件的顺序规则,包括不同位置的application.properties文件的加载优先级,以及如何通过命令行参数或环境变量来指定配置文件的名称和位置。
|
2月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
2月前
|
Java Spring 传感器
AI 浪潮席卷,Spring 框架配置文件管理与环境感知,为软件稳定护航,你还在等什么?
【8月更文挑战第31天】在软件开发中,配置文件管理至关重要。Spring框架提供强大支持,便于应对不同环境需求,如电商项目的开发、测试与生产环境。它支持多种格式的配置文件(如properties和YAML),并能根据环境加载不同配置,如数据库连接信息。通过`@Profile`注解可指定特定环境下的配置生效,同时支持通过命令行参数或环境变量覆盖配置值,确保应用稳定性和可靠性。
41 0
|
2月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
36 0
|
5月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
157 0
|
5月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
68 0
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
154 0
下一篇
无影云桌面