SpringMVC 配置及原理分析

一. 搭建 SpringMVC

1.使用 SpringMVC 需要引入依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
		<!-- servlet api (3.0及以上版本,由于tomcat中已经存在了这个包,所以设置为provided权限)-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- 添加json 依赖jar包 -->
		<dependency>
		  <groupId>com.fasterxml.jackson.core</groupId>
		  <artifactId>jackson-core</artifactId>
		  <version>2.7.0</version>
		</dependency>
		<dependency>
		  <groupId>com.fasterxml.jackson.core</groupId>
		  <artifactId>jackson-databind</artifactId>
		  <version>2.7.0</version>
		</dependency>
		<dependency>
		  <groupId>com.fasterxml.jackson.core</groupId>
		  <artifactId>jackson-annotations</artifactId>
		  <version>2.7.0</version>
		</dependency>

传统方式

  1. 需要在 WEB-INF 包的 web.xml 中,或创建配置类的方式配置 DispatcherServlet , ContextLoaderListener,扫描文件,文件映射等
    web.xml 配置示例
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">

  <display-name>ego-manager-web</display-name>

  <!-- post方式提交乱码解决 begin -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
 <!-- */ -->

  <!-- spring后端配置 begin(通过一个监听器,扫描Spring的配置文件,启动Spring) -->
  <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>
 
 
  <!-- 配置 DispatcherServlet 指定 SpringMvc配置文件的位置,启动SpringMVC-->
  <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/springmvc.xml</param-value>
    </init-param>
  </servlet>
  <!--配置映射-->
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  
</web-app>
  1. resources 文件夹下创建创建配置文件,配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:mvc="http://www.springframework.org/schema/mvc"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

	<!-- 启用spring mvc 功能-->
	<mvc:annotation-driven />
	<!-- 配置访问根路径对应资源操作 -->
	<!-- 其中 path 表示为请求的路径,view-name 表示为你需要做的资源操作 -->
	<mvc:view-controller path="/" view-name="index" />
	<!-- 设置使用注解的类所在的package(基本包)-->
	<context:component-scan base-package="com.ssm.controller"/>

	<!-- 静态资源映射 location是本地静态资源路径 mapping是映射的url地址,访问时就使用该地址 -->
	<mvc:resources location="/WEB-INF/static/" mapping="/**" />

	<!-- 配置视图解析器(使用不同的视图,解析器不同,此处使用jsp) -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 配置前缀 -->
		<property name="prefix" value="/WEB-INF/pages/"/>
		<!-- 配置后缀 -->
		<property name="suffix" value=".jsp"/>
	</bean>
	<!-- 文件上传解析器 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- 默认编码试 -->
		<property name="defaultEncoding" value="UTF-8"/>
		<!-- 最大文件上传大小 -->
		<property name="maxUploadSize" value="10485760"/>
	</bean>


	<!-- 配置拦截器 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<!--注意拦截的什么,放行的什么, 拦截所有,然后指定放行
			放行项目中的所有静态资源,
			放行登入页面,
			放行登入后台
			根据需求定义, 如果是商城,新闻等,
            拦截的 url(必须登录才可以访问的资源)
            /**包括路径及其子路径
            如果是/admin/*-拦截的是/admin/add,/admin/list...但是
           /admin/add/user 不被拦截
            如果是/admin/**-拦截的是/admin 路径及其子路径
            */-->
			<mvc:mapping path="/**"/>
			<!-- 不拦截的 url(不需要登录就可以访问的资源) -->
			<mvc:exclude-mapping path="/Public/**"/>
			<mvc:exclude-mapping path="/Template/**"/>
			<mvc:exclude-mapping path="/login/**"/>
			<mvc:exclude-mapping path="/user/login/**"/>
			<mvc:exclude-mapping path="/user/logout/**"/>
			<!--放行图像验证码请求-->
			<mvc:exclude-mapping path="/image/**"/>
			<!--对应java中自定义的拦截器,此处使用ref->
			<ref bean="managerLoginInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>

</beans>

配置类方式(注意servlet 3.0及以上版本)

  1. 创建关于SpringMVC 的配置类,配置视图解析器,拦截器等等
import com.ssm.interceptor.ManagerInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;

//该配置类只扫描mvc相关的controller
@ComponentScan(value = "com.ssm",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}, useDefaultFilters = false)
//开启SpringMVC(相当于xml中的<mvc:annotation-driven />)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //为什么要继承 WebMvcConfigurerAdapter(实现WebMvcConfigurer接口也可以,是该类的父接口)
    //通过继承该类,重写该类中的定制方法,配置视图解析器,拦截器,些类型转换器,格式化器等功能

    //设置视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //指定jsp页面视图解析器( WEB-INF/pages下的.jsp结尾的)
        //当请求页面是,默认会给放回的页面名字加上前缀,后缀寻找返回
        registry.jsp("/WEB-INF/pages/",".jap");
    }

    //设置静态资源,例如访问项目痛的图片等
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //设置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //ManagerInterceptor 自定义拦截器
        InterceptorRegistration registration = registry.addInterceptor(new ManagerInterceptor());
        //设置拦截的请求
        registration.addPathPatterns("/**");
        //设置放行的请求
        registration.excludePathPatterns("/Public/**");
    }
}
  1. 创建关于Spring 的配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.mvc.Controller;

//该配置类是 Spring 相关配置类,只扫描Spring相关的,排除mvc相关的
@ComponentScan(value = "com.ssm", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})})
public class RootConfig {
}
  1. 创建 SpringMVC 启动时自动加载执行的类,该类继承 AbstractAnnotationConfigDispatcherServletInitializer 抽象类,重写抽象类中的接口,获取上面配置的两个配置类,与被 DispatcherServlet 监管的请求路径
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
//当启动web容器时,创建该类对象,调用方法初始化容器,前端控制器等
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //获取根容器方法,需要创建配置类
    //(相当于前面的获取Spring 的配置文件)
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    //获取web容器配置类(相当于前面的加载 mvc相关文件的配置)
    //需要创建配置类
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    //获取 DispatcherServlet 的映射信息
    //"/" 代表拦截所有请求(包括静态资源css,html等,但是不包括页面)
    //"/*" 会拦截页面
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

SpringMVC 接收请求执行流程

1. 源码分析

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //1.获取 HandlerExecutionChain 
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
					//2.调用getHandlerAdapter()方法传递获取到的 HandlerExecutionChain 
					//获取HandlerAdapter 处理器映射器(通过处理器映射器,执行处理器的方法)
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }

                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
					//3.执行拦截器中的方法,获取当前请求匹配到的所有的interceptor拦截器
					//遍历执行每一个拦截器的拦截preHandle()方法,返回放行或拦截的布尔值
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
					//4.首先执行通过获取到的HandlerExecutionChain 调用 getHandler() 获取到
					//当前请求的 Handler处理器,
					//然后执行此处的调用 handle() 方法,传递处理器对象,通过处理器对象调用执行处理方法,
					//也就是controller,执行完毕后返回ModelAndView
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    //5.获取当前请求对应的拦截器,执行拦截器中的postHandle()方法(注意此处时反向执行的)
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
				//6.处理 ModelAndView,该方法中会调用一个 render()方法,在render()方法中,会通过
				//获取到的 ModelAndView 调用getView()方法,获取一个 View 对象
				//然后通过获取到的 View 对象调用了另一个 render() 方法
				//在该方法中调用了renderMergedOutputModel() 方法
				//renderMergedOutputModel()方法中会获取 RequestDispatcher 转发处理器
				//通过转发处理器调用 forward(request, response) 最终实现转发,相当于渲染视图
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
		
		
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                //7.此处是在finally块中,最终逆向执行interceptor中的afterCompletion()方法
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

2. 步骤总结

  • 首先 DispatcherServlet 监控拦截到客户端发送的请求,如果匹配不成功404页面报错,如果匹配成功
  • 调用getHandler() 获取 HandlerExecutionChain
  • 传递获取到的 HandlerExecutionChain 调用getHandlerAdapter()方法获取HandlerAdapter 处理器映射器
  • 通过获取到的 HandlerExecutionChain 调用 applyPreHandle()方法,获取当前请求匹配到的所有的interceptor拦截器,遍历执行每一个拦截器的preHandle()方法,返回放行或拦截的布尔值
  • 通过获取到的HandlerExecutionChain 调用 getHandler() 获取到当前请求的 Handler处理器,也就是对应当前请求的Controller
  • 通过获取到的HandlerAdapter 调用 handle() 方法,传递处理器对象,通过处理器对象调用执行处理方法controller,执行完毕后返回ModelAndView
  • 通过HandlerExecutionChain 调用 applyPostHandle() 方法,获取当前请求对应的拦截器,执行拦截器中的postHandle()方法(注意此处时反向执行的)
  • 调用 processDispatchResult()方法, 处理 ModelAndView,该方法中会调用一个 render()方法,通过 ModelAndView 调用getView(),获取一个 View 对象,调用 View对象的 render() 方法,在View对象的 render() 方法中调用了renderMergedOutputModel() 方法,获取 RequestDispatcher 转发处理器,通过转发处理器执行 forward® 最终实现转发,相当于渲染视图
  • 最终HandlerExecutionChain 调用 applyAfterConcurrentHandlingStarted() 逆向执行interceptor中的afterCompletion()方法

二. 异步请求

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
@Controller
public class AsyncController {

    //方式一: 当前端控制器返回的是 Callable 类型的数据时
    //Spring会将Callable提交到一个TaskExecutor任务执行器中
    //使用一个隔离线程进行执行,DispatcherServlet和所有的Filter退出
    //response还保持打开状态,
    //当Callable产生结果状态时,SpringMVC会将这个结果重新派发给Servlet容器
    //恢复处理过程
    @RequestMapping("/asyncTest")
    @ResponseBody
    public Callable<String> asyncTest(){
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "异步方法执行返回的数据";
            }
        };
        //这个数据是上面call方法执行完毕后返回的
        return callable;
    }


    //方式二: 另一种处理方式,给前端返回 DeferredResult 类型对象
    //这个接收请求的方法是在方法中创建的 DeferredResult对象
    //调用了setResult() 方法后执行的
    //可以创建这个对象放入一个容器中,指定这个对象的id,要进行异步处理
    //的方法在容器中根据id去获取这个对象,调用setResult()方法处理
    @RequestMapping("/deferredResult")
    @ResponseBody
    public DeferredResult<String> deferredResultTest(){
        //构造器中第一个参数为设置超时时间,与超时后的报错提示信息
        DeferredResult<String> deferredResult = new DeferredResult((long)3000,"失败提示信息");
        //当这个 deferredResult 对象调用了 setResult() 方法后,才会给前端真正的响应
        //deferredResult.setResult("异步执行完毕");

        Map<String,DeferredResult> resultMap = new HashMap<>();
        resultMap.put("aaa",deferredResult);

        //调用处理方法处理
        this.dispose(resultMap);
        return deferredResult;
    }

    //处理方法
    public void dispose(Map<String,DeferredResult> resultMap){
        for(Map.Entry<String,DeferredResult> entry : resultMap.entrySet()){
            if(entry.getKey().equals("aaa")){
                entry.getValue().setResult("处理完毕");
            }
        }

    }

}

三. 请求转发与重定向

1. 请求转发,只发送一次请求,地址栏不会变,数据不会丢失,是服务器行为
2. 重定向,做了两次请求,地址栏会变,数据会丢失,是客户端行为
3. 重定向域名不同的网站,例如重定向到其它服务,重定向到第三方服务是,域名可能不同,会触发浏览器的安全策略问题

请求转发

		//请求转发到通过request域对象携带User对象到WEB-INF文件夹下的page页面,
		// 需要先转发到后台test07中,test07后台获取域对象中的名为user的参数
		//返回个page.jsp页面
		@RequestMapping("test05")
		public String test05(HttpServletRequest request){
		    //创建User对象
		    User user =new User();
		    user.setName("小明");
		    //将User对象存入request域对象中
		    request.setAttribute("user",user);
		    //请求转发到后台test07
		    return "forward:test07";
		}
		
		
		//请求转发到webapp根目录下的v1.jsp页面,并携带参数,
		//使用Model对象存储数据
		@RequestMapping("test10")
		public String test10(Model model){
		    //创建User对象
		    User user=new User();
		    user.setName("小黑");
		//将user对象添加到model对象中
		    model.addAttribute("user",user);
		//返回v1.jsp页面
		    return"forward:v1.jsp";
		}

重定向示例代码

		//直接重定向到webapp根目录下的.jsp结尾的页面
		@RequestMapping("test03")
		public String test03(){
		   System.out.println("test03");
		   //直接重定向到webapp根目录下.jsp结尾的页面,
		   //由于直接重定向到页面不会经过视图解析器,
		   //需要书写"页面名.jsp页面类型"
		   return "redirect:v1.jsp";
		}
		//如果想要重定向到WEB-INF下视图解析器解析的文件夹下的页面
		//需要重定向请求后台,通过后台返回请求页面
		@RequestMapping("test04")
		public String test04(){
		   System.out.println("test04");
		   //重定向到后台test07请求,
		   //test07后台请求返回给一个 WEB-INF文件夹下 page.jsp页面
		   return "redirect:test07";
		  }

前端 jsonp 重定向跨域

	$("#btn").click(function(){

            $.ajax({
                async : true,
                url : "https://api.douban.com/v2/book/search",
                type : "GET",
                dataType : "jsonp", // 返回的数据类型,设置为JSONP方式
                jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback
                jsonpCallback: 'handleResponse', //设置回调函数名
                data : {
                    q : "javascript", 
                    count : 1
                }, 
                success: function(response, status, xhr){
                    console.log('状态为:' + status + ',状态是:' + xhr.statusText);
                    console.log(response);
                }
            });

后端可以利用nginx反向代理解决

user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen 80;
        server_name upload.myshop.com;
        add_header 'Access-Control-Allow-Origin'  '*';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
        location / {
            proxy_pass  http://192.168.0.104:8888;
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin  *;
                add_header Access-Control-Allow-Headers X-Requested-With;
                add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,PATCH,OPTIONS;
                # 解决假请求问题,如果是简单请求则没有这个问题,但这里是上传文件,首次请求为 OPTIONS 方式,实际请求为 POST 方式
                # Provisional headers are shown.
                # Request header field Cache-Control is not allowed by Access-Control-Allow-Headers in preflight response.
                add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range;
                return 200;
            }
        }
    }
}
发布了48 篇原创文章 · 获赞 0 · 访问量 576

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105408812