乐趣区

关于java:用-Spring-管理-Controller你觉得可行吗

上篇文章和小伙伴们聊了 Spring 容器中的父子容器问题,也和小伙伴们梳理了 Spring 容器和 SpringMVC 容器之间的关系,其中,Spring 容器是父容器,SpringMVC 是子容器,子容器能够拜访父容器中的 Bean,然而父容器无法访问子容器中的 Bean。

在一个 SSM 我的项目中,你能够单纯应用 SpringMVC 容器,这个没问题,我的项目能够失常运行。然而,有的小伙伴可能要问了,如果把所有的 Bean 都扫描到 Spring 容器中行不行?

先来说论断:能够!然而须要额定配置。

浏览本文须要先理解 Spring 容器的父子容器哦,如果还不理解的话倡议先浏览上篇文章。

为什么不能把所有 Bean 都注册到 Spring 容器中呢?依照咱们上篇文章中的剖析,所有 Bean 都注册到 Spring 容器之后,Spring 容器作为父容器,SpringMVC 作为子容器,按理说,因为子容器能够拜访父容器中的 Bean,所以 SpringMVC 是能够失常拜访 Spring 容器中的 Bean 的,所以,仿佛把所有的 Bean 都扫描到 Spring 容器应该是没有问题的?

其实不然!

问题就出在 SpringMVC 容器查找 Controller 的形式上,SpringMVC 容器查找 Controller,默认状况下,只在以后容器中查找,并不会去父容器中查找,所以如果把 Controller 都扫描到父容器的话,对于 SpringMVC 来说,相当于零碎中就没有 Controller 了,所以你一拜访,间接就 404 了。

接下来,我联合源码和小伙伴们剖析一下。

首先,小伙伴们晓得,在 SpringMVC 中,当申请达到服务端之后,须要由处理器映射器 HandlerMapping 来确定这个申请应该由哪个处理器来解决,所以,按理说,HandlerMapping 中就会记录所有的处理器信息,也就是 Controller 的信息。个别咱们在 SpringMVC 中应用的 HandlerMapping 都是 RequestMappingHandlerMapping,所以这里咱们就通过 RequestMappingHandlerMapping 的初始化来看一下,SpringMVC 到底是如何查找 Controller 的。

在 RequestMappingHandlerMapping#afterPropertiesSet 办法中,调用了父类的 afterPropertiesSet 办法,咱们来看下:

AbstractHandlerMethodMapping#afterPropertiesSet:

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

initHandlerMethods 办法就是初始化处理器的办法,也就是在这个办法中,去尝试找到所有的 Controller,并且把每一个接口办法都封装成 HandlerMethod 对象。

咱们来看下 getCandidateBeanNames 办法,这个办法用来找到所有的候选的 Bean:

protected String[] getCandidateBeanNames() {
    return (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
            obtainApplicationContext().getBeanNamesForType(Object.class));
}

关键点就在这了,这里首先去判断 detectHandlerMethodsInAncestorContexts 变量的值,如果这个变量为 true,则调用 BeanFactoryUtils.beanNamesForTypeIncludingAncestors 办法去查问 Bean,这个办法在上篇文章中松哥和大家分享过,用来查找 Bean 的名称,包含父容器中的 Bean 都会查找到并返回;如果 detectHandlerMethodsInAncestorContexts 变量为 false,则调用 getBeanNamesForType 办法去查找 Bean,getBeanNamesForType 办法咱们上篇文章也讲过,这个办法只找以后容器的 Bean,不会去父容器中查找。

所以当初问题的要害就在于 detectHandlerMethodsInAncestorContexts 变量了,这个变量默认是 false,即,默认状况下,只去以后容器(SpringMVC 容器)查找 Bean。

这里找到的 beanName 是以后容器中所有的 beanName,所以接下来还要去 processCandidateBean 办法走一圈,这个办法会去判断这个 Bean 是否是一个 Controller,如果是就将之收集到一起:

protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    beanType = obtainApplicationContext().getType(beanName);
    if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);
    }
}
@Override
protected boolean isHandler(Class<?> beanType) {return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}

能够看到,只有这类上有 @Controller 注解,这个类才会被留下来。

好啦,剩下的逻辑咱们就不看了。

当初大家曾经理解到这样一个状况:

SpringMVC 容器在初始化 HandlerMapping 的时候,会去查找所有的 Controller 并实现初始化,然而在默认状况下,只会去以后容器中查找,并不会去父容器中查找。

所以,如果把 Controller 让 Spring 容器扫描并治理,那么就会导致在默认状况下,SpringMVC 容器找不到 Controller,进而导致所有的申请 404。

在后面的解说中,松哥都强调了 默认状况,意思就是说这个事件还有转圜的余地,看了后面源码的小伙伴应该也发现了,只有咱们把 detectHandlerMethodsInAncestorContexts 变量改为 true,那么 HandlerMapping 就会去父容器中查找 Bean,这样即便被 Spring 容器扫描并治理的 Bean,也就可能查找到了。

批改形式如下:

spring-servlet.xml:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="detectHandlerMethodsInAncestorContexts" value="true"/>
</bean>

在 Spring 容器中间接扫描所有 Bean:

<context:component-scan base-package="org.javaboy.web"/>

web.xml 中加载这两个配置文件:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

这样配置之后,就能够把所有 Bean 都扫描到 Spring 容器中了。

好啦,明天这篇文章 目标不是为了让小伙伴们去在 Spring 容器中治理 Controller,只是想借这样一个契机,一起来捋一捋 SpringMVC 中 HanderMapping 的原理。

如果感觉本文浏览有点吃力,能够先看看上篇文章哦

退出移动版