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

本文涉及的产品
容器镜像服务 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使用同一个容器管理的原因吧)


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



相关文章
|
13天前
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
18天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
54 6
|
3月前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
21天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
33 4
|
1月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
34 1
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
149 2
|
2月前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
2月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
55 0
|
3月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
28天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
196 77