初识springmvc

240 阅读5分钟

正是因为有了Springmvc的出现,所以我们不需要再去手动写servlet了,但是如果不了解其中的发展过程的话,出了一些奇奇怪怪的问题的时候就无从下手,所以从本篇开始进入springmvc的世界,去看一看他到底干了啥。

一个简单的例子

首先还是用前面使用到的项目文件,还记得我只引入了javax.servlet-api这个依赖吧,现在到了Springmvc这部分,我再引入spring-webmvc这个依赖,他会额外再引入spring bean相关的依赖,但是在这里我们暂时不考虑他们,必须要清楚spring 和springmvc是两个完全不同的东西。

 <!-- springmvc 不包含servlet-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.2.1.RELEASE</version>
 </dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>javax.servlet-api</artifactId>
 <version>4.0.1</version>
</dependency>

然后在之前的web.xml文件里再加一个servlet的配置

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>

解释一下这里面声明了两个东西,一个是Springmvc为我们实现好的DispatcherServlet,另外一个就是我们需要在springmvc中需要使用到的自己那些乱七八遭的bean的配置,把它单独放在一个配置文件里,param-name是固定的, <param-value>默认就是WEB-INF/servlet-name-servlet.xml,比如这里默认就是dispatcherServlet-servlet.xml,当然完全可以放在其他地方,比如我就放在了resources目录下,然后这里改成自己的位置就好了,里面的配置就是各种bean的声明,当然可以直接利用spring提供的包扫描机制进行简写,比如我这样<context:component-scan base-package="me.ppx.mvc.springmvc.controller"></context:component-scan>,然后把自己的代码写在这个包里,此时用上了spring提供的bean相关的注解声明的类,比如@Controller,@Service,@Configuration等都会在DispatcherServlet初始化的过程中被实例化。在Springmvc中,每个DispatcherServlet都持有一个自己的上下文对象WebApplicationContext,它又继承了根(root)容器,WebApplicationContext对象中已经定义了所有bean,同时还能拿到root里面的bean。此时springmvc的配置就算是完成了,然后在配置的那个包及其子包下就可以随便写代码了。

@RestController
@RequestMapping("/mvc")
public class Controller {
    @GetMapping("/date")
    public String getDate() {
        System.out.println("通过mvc请求到controller");
        return LocalDateTime.now().toString();
    }

启动之后就能正常访问了,同时各位可以看看启动日志 06-Nov-2021 11:55:40.149 信息 [main] org.springframework.web.servlet.FrameworkServlet.initServletBean Initializing Servlet 'dispatcherServlet' 06-Nov-2021 11:55:40.768 信息 [main] org.springframework.web.servlet.FrameworkServlet.initServletBean Completed initialization in 618 ms

DispatcherServlet

前面多次提到了DispatcherServlet,同时web.xml里面也只配置了DispatcherServlet,那这就来看一看他到底是个什么东西。点进去发现他是属于spring-webmvc包下的,继承了FrameworkServlet,最终是继承了我们熟悉的httpServlet,所以去看看init方法,这里就是springmvc启动流程的入口

// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

这里要求必须给DispatcherServlet配置初始化参数,无论是什么(这里只负责检验有没有) 然后就是给包装成BeanWrapper,核心就是最后一步的initServletBean,他的实现在FrameworkServlet里,前面那两行日志就是这里打印的,意思是这里才算是真正的初始化,该方法执行之前,所有属性都已经设置好了,接着初始化根上下文

WebApplicationContext rootContext =
     WebApplicationContextUtils.getWebApplicationContext(getServletContext());

这一步就是去当前这个ServletContext中去获取一个叫做WebApplicationContext.class.getName() + ".ROOT"的属性,他就是所有前面说的DispatcherServlet持有的那个当前上下文的父上下文,也叫根容器rootWebApplciationContext,仅以我们刚刚的那点配置,这个root就是null,后面会介绍怎么让他不为null,然后就会判断当前的这个WebApplicationContext属性是不是null,还是一样的,以我们前面的配置启动,这里就是null,当然同样有让他不为null的方式,后面再说,然后执行findWebApplicationContext(),也是null,因为没看到哪里调用setContextAttribute(),所以继续往下走,执行createWebApplicationContext(),获取不到现成的就自己创建一个的,默认就是XmlWebApplicationContext,同时设置Application的parent(就是前面那个root),只不过此时是null,就是说没有可以继承的bean。然后就在这里获取我们在web.xml里面配置的那个contextConfigLocation了,用来读取bean,最后进入关键一步,

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }
// 关联Servlet上下文
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   // 添加 配置的ApplicationContextInitializer
   applyInitializers(wac);
   // 这个方法就不说了,懂得都懂
   wac.refresh();
}

这里面主要是对自己这个WebApplicationContext属性进行了设置,并扫描初始化了所有自己定义的bean等基础操作,接下来等他refresh完之后,就会进入onRefresh方法,这个实现在DispatcherServlet,直白说就是处理了什么样的请求到哪一个方法上去这样的操作等策略,东西还是挺多的。到此自己的上下文环境就搞定了。

总结

其实上面的这点配置就足以完成大部分web开发需求了,当然这里我从头到尾都没提及页面和静态资源以及编码之类的问题,因为我前面也说了整个专栏并不会具体去讲如何如何使用某一门技术,而是想把整个web开发的进化过程给串联起来,所以就默认大家都会了,至少在这里他们不重要。然后我们完全把所有的层级划分涉及到的bean全部都放在这个包里面让他为这个DispatcherServlet服务,因为一般项目也就只有一个DispatcherServlet,所以这样并没有问题,整个应用就只有一个spring容器,所有的bean都在这里。但是为了方便扩展,方便灵活定制,一般还是会再去额外处理web层以外的bean,mvc这里只处理请求和视图等,那些额外的处理自然就是单独在创建一个ioc容器管理所用公共的bean,这个我们后面再说。