SpringMVC(4.x) 从搭建到放弃(含源码分析)——一

1. Spring MVC处理流程

DispatcherServlet -> 处理器映射 -(找到处理器)->
DispatcherServlet -> 控制器 -(逻辑视图名+Model)-> DispatcherServlet -> 视图解析器 -(渲染了Model数据的视图View对象)-> 响应

Created with Raphaël 2.1.0 请求 请求 DispatcherServlet DispatcherServlet 处理器映射 处理器映射 控制器 控制器 视图解析器 视图解析器 响应 响应 1. 请求被拦截 2. 根据url寻找对应处理器 3. 找到对应的控制器`getHandler(..)` 4. 交给控制器处理 5. 返回逻辑视图名和模型 6. 找到配置的视图解析器 7. 渲染后的视图`render(..)` 8. 获取响应

2. 配置Spring MVC

2.1 搭建项目

2.1.1 maven项目骨架

使用IDEA

File-New Module-Maven(勾选 create from archetype)-org.apache.maven.archetype:maven-archetype-webapp 然后next到底

使用Maven
$ cd YourWorkspace
$ mvn archetype:generate \
-DgroupId=com.mywebapp \
-DartifactId=mywebapp \
-Dversion=1.0.0 \
-DarchetypeArtifactId=maven-archetype-webapp \
-DarchetypeVersion=RELEASE \
-DinteractiveMode=false \ //不需要使用maven的交互模式来创建项目

2.1.2 pom.xml文件

定义常量
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <org.slf4j-version>1.7.12</org.slf4j-version>
    <org.springframework-version>4.3.9.RELEASE</org.springframework-version>
</properties>
引入spring MVC
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
引入thymleaf模板引擎
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring4</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
引入Servlet api和JSP api
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.1</version>
    <scope>provided</scope>
</dependency>
<!-- JSTL标签库 用于支持在jsp中使用标签-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
引入JSR303 校验库(api 与 实现)
<dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.1.0.Final</version>
</dependency>
<!-- 其实和hibernate数据库支持没什么关系 只是validation-api实现-->
<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.3.5.Final</version>
</dependency>
引入日志系统

使用为SLF4J作为api log4j 1.x日志系统为实现 jcl-over-slf4j为桥接器将使用其他日志系统api(如apache commons-logging)的jar掉入到为SLF4J“陷阱”中 slf4j-log4j12为SLF4J与log4j相连接的接口

此处使用了log4j 1.x版本也是传统使用最多的版本,但是现在log4j已经推出了2.x版本,并把项目进行了重构,增添了log4j对web的支持

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${org.slf4j-version}</version>
</dependency>
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>${org.slf4j-version}</version>
      <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>${org.slf4j-version}</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.16</version>
    <scope>runtime</scope>
</dependency>

或将log4j的实现改为2.x版本

<dependency>  
    <groupId>org.apache.logging.log4j</groupId>  
    <artifactId>log4j-api</artifactId>  
    <version>2.5</version>
    <scope>runtime</scope>
</dependency>  
<dependency>  
    <groupId>org.apache.logging.log4j</groupId>  
    <artifactId>log4j-core</artifactId>  
    <version>2.5</version>  
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.5</version>
    <scope>runtime</scope>
</dependency>
引入单元测试
<!-- JunitTest -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!-- 可以不需要启动spring和web上下文来进行测试 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework-version}</version>
    <scope>test</scope>
</dependency>
<!-- mock产生测试对象 -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.7.22</version>
    <scope>test</scope>
</dependency>

2.1.3 目录结构

项目源文件目录和项目目录, 这两者是不同的

源文件目录是组织源代码的目录结构, 一般如下

-mywebapp
--src/main/java                 //*.java
--src/main/resources            //*.properties或*.xml等配置文件
--src/main/webapp               //web资源
----WEB-INF                     //web资源, 但浏览器不能用url方式直接访问到
------jsp
------html
------template
------res/css
------res/js
------res/img
------web.xml                  //可以没有哦!
--src/test/java
--src/test/resources
--target
--other dir you like

项目目录是最终发布到tomcat等容器上的目录结构,即maven等工具编译后的目录结构

-mywebapp                       //所谓的web项目的根目录 webroot
--META-INF
----MANIFEST.MF                 //记录build工具打包等相关数据
--WEB-INF
----classes                     //项目自身的编译后的src/main/java和src/main/resources的文件 
                                //所谓的classpath的根目录
----lib                         //项目所需的依赖
----jsp
----html
----template
----res/css
----res/js
----web.xml

2.2. 使用javaconfig配置web.xml

2.2.1 配置方法

一般的web项目都是要有web.xml 其作用是作为该web应用的上下文(Servlet Context)所需的配置文件

在Servlet3.0环境中, 容器启动时就会在类路径下查找实现javax.servlet.ServletContainerInitializer接口的类, Spring提供了这个类的实现org.springframework.web.SpringServletContainerInitializer, 在这个类中会反过来查找实现了org.springframework.web.WebApplicationInitializer的类们并将配置任务交给它们来实现.

在Spring3.2中引入了一个便利的简单WebApplicationInitializer基础实现,
也就是AbstractAnnotationConfigDispatcherServletInitializer
(从名字上看它是一个用annotation配置DispatcherServlet的抽象类),
因此我们只要新建一个自定义配置类实现AbstractAnnotationConfigDispatcherServletInitializer即可

public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {RootConfig.class};  //相当于配置dao service bean的spring的xml门
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {WebConfig.class}; //相当于只配置controller的spring的xml们
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"}; //DispatcherServlet的处理的url映射
    }
}

以上相当于

<listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>
<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath:spring/applicationContext.xml</param-value>  
</context-param>

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- init-param 其实就是初始化这个bean需要注入的属性-->
    <!--contextConfigLocation 是-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

注意<url-pattern>/*/的区别 /*会覆盖掉所有的Servlet包括web容器(如Tomcat)中预定义的Servlet(如处理jsp 静态资源), 可能会造成循环调用情况(dispatcher覆盖处理jsp默认jsp servlet, dispatcher处理页面渲染时转移给默认jsp servlet, 而现在默认servlet就是dispatcher自身, 死循环), 而/只是覆盖默认servlet(default servlet 该servlet就是处理其他servlet不处理的东西) 比如对于如/test.html(/*.html)的请求, 会自动找处理html的servlet处理, 如果没配置就使用默认的处理html的sevlet, 寻找web根目录下的test.html 记住 servlet不是链式调用, 属于该servlet处理的内容如果找不到, 那么它不会顺着一个链寻找可以处理它的servlet

2.2.2 加载流程

  1. 容器调用SpringServletContainerInitialize.onStartup(Set<Class<?>> ServletContext)
  2. 查找实现WebApplicationInitializer的类MyDispatcherServletInitializer调用onStartup(ServletContext)
  3. 该方法在抽象父类AbstractDispatcherServletInitializer
 public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        this.registerDispatcherServlet(servletContext);
    }
  1. 再调用父类的AbstractContextLoaderInitializer.onStartup(ServletContext)
public void onStartup(ServletContext servletContext) throws ServletException {
        this.registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
    // WebAppcationContext父类是spring的ApplicationContext, 其实就是web版的spring上下文
    // 调用子类AbstractAnnotationConfigDispatcherServletInitializer的createRootApplicationContext()
    WebApplicationContext rootAppContext = this.createRootApplicationContext();
    if (rootAppContext != null) {
        // 创建listener 放入上下文环境
        ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
        // ContextLoaderListener上下文环境的配置 
        listener.setContextInitializers(this.getRootApplicationContextInitializers());
        servletContext.addListener(listener);
    } else {
        this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
    }

}
  1. 回到AbstractDispatcherServletInitializer.onStartup(ServletContext)执行this.registerDispatcherServlet(servletContext)
protected void registerDispatcherServlet(ServletContext servletContext) {
    //...
    //调用子类AbstractAnnotationConfigDispatcherServletInitializer的createServletApplicationContext()
    //创建一个属于DispatcherServlet的上下文环境
    WebApplicationContext servletAppContext = this.createServletApplicationContext();
    FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
    //dispatcherServlet上下文环境的配置
    dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
    //在应用上下文中注册dispatcherServlet
    Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'.Check if there is another servlet registered under the same name.");
    registration.setLoadOnStartup(1);
    registration.addMapping(this.getServletMappings());
    registration.setAsyncSupported(this.isAsyncSupported());
    //过滤器
    Filter[] filters = this.getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        Filter[] var7 = filters;
        int var8 = filters.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            Filter filter = var7[var9];
            //在应用上下文中注册过滤器
            this.registerServletFilter(servletContext, filter);
        }
    }

    this.customizeRegistration(registration);
}
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        return new DispatcherServlet(servletAppContext);
}
  1. 执行一系列初始化DispatcherServlet的行为
public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
    this.setDispatchOptionsRequest(true);
}

在以上流程中初始了两个spring的上下文环境(WebApplicationContext 即ApplicationContext的子), 一个是ContextLoaderListener, 一个是DispatcherServlet(前者是后者的父环境)
this.getRootApplicationContextInitializers()this.getServletApplicationContextInitializers()返回的都是用@Configuration注解的类(配置spring bean的java config类) 一般ContextLoaderListener里配置dao service或其他全局的bean

2.2.3 上下文环境辨析

ServletContext

应用上下文环境, 代表了整个web应用(mywebapp), 由容器(如Tomcat)提供实现, 属于javax.servlet.ServletContext, 可以说是“最顶层的上下文环境” 通常是用来在其中注册listener servlet filter的(无论是用web.xml 还是java config方式), 它的启动过程是最早的.

它有两个地方可以放key-value
1. set/getInitParameter<context-param>里设置的内容, 用于启动
2. set/getAttribute 用于在webapp各个部分共享, 比如webapp的描述 名称等

添加listener的作用是在servlet启动 webapp还未完全执行前启动一些过程,比如启动spring 上下文

获得ServletContext的方法
1. 在javax.servlet.Filter中直接获取ServletContext context = config.getServletContext();

  1. 在HttpServlet中直接获取this.getServletContext()

  2. 在其他地方r通过HttpSession获取session.getServletContext(); 或通过HttpServletRequest获取request.getSession().getServletContext();

  3. 在thymeleaf模板直接${application}(SpEL 模板引擎)或${#servletContext}(OGNL 原生语言)

ServletContext里添加属性servletContext.setAttribute(name, value)

spring 上下文环境

一般指的就是ContextLoaderListener的WebApplicationContext
ContextLoaderListenercontextInitialized(ServletContextEvent)被调用
执行它父类ContextLoader.initWebApplicationContext(ServletContext)时它会在被直接添加到ServletContext的attribute属性中

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

可通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)方法获取到该上下文

dispatcherServlet 上下文

指的是DispatcherServlet的WebApplicationContext

  1. 由于设置了registration.setLoadOnStartup(1); 在容器启动完成后就调用servlet的init() DispatcherServlet 继承FrameworkServlet继承HttpServletBean继承 HttpServletHttpServletBean实现了init()
public final void init() throws ServletException {
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
    }

    PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
            this.initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        } catch (BeansException var4) {
            if (this.logger.isErrorEnabled()) {
                this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
            }

            throw var4;
        }
    }
    // 调用子类FrameworkServlet的initServletBean()
    this.initServletBean();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
    }

}
  1. FrameworkServletinitServletBean()中调用了initWebApplicationContext() 将ContextLoaderListener中搞定的spring上下文设置为Dispatcher中上下文的父容器
protected WebApplicationContext initWebApplicationContext() {
    //...
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    //...
    cwac.setParent(rootContext);
    //...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值