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

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器镜像服务 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使用同一个容器管理的原因吧)


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



相关文章
|
1月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
85 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
24天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
49 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
54 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
115 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
网络协议 Ubuntu 前端开发
好好的容器突然起不来,经定位是容器内无法访问外网了?测试又说没改网络配置,该如何定位网络问题
本文记录了一次解决前端应用集成到主应用后出现502错误的问题。通过与测试人员的沟通,最终发现是DNS配置问题导致的。文章详细描述了问题的背景、沟通过程、解决方案,并总结了相关知识点和经验教训,帮助读者学习如何分析和定位网络问题。
101 0
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
8天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
33 2
|
18天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。