1. 导入
Spring web MVC 是基于 Servlet API 构建的原始 Web 框架, 从一开始就包含在 Spring 框架中. 正式的名称 "Spring Web MVC" 来自于它的源模块 (spring-webmvc) 的名称, 常被人们称为 "Spring MVC".
本文通过一个简单的增删改查 demo 切入, 对 SpringMVC 源码进行解读, Spring Framework 版本是 4.3.7
2. 前期准备
2.1 项目组成
IntelliJ IDEA 2017.1,JDK1.8,Spring4.3.7,Hibernate 4.3.8, 其余组件可以在 pom.xml 找到
项目文件目录如下:
2.2 导入 Demo
源码请点击这里 https://github.com/cjy513203427/springmvcdemo , 在 idea 中导入
输入 Git 容器地址, 点击 clone
要让 idea 识别这是 Web 项目, 打开 File->Project Structure, 一般 idea 会自动检测配置文件, 提示你设置为 spring 配置文件, 我们也可以手动添加
再选中 Web, 将 Web-INF 下的 Web.xml 选中, 并识别 webapp 根目录(idea 会帮我们自动配置)
将 Modules 打成 war_exploded
初始化数据, sql 文件在 / sql 下, 先运行 table.sql, 再运行 init.sql
配置 Tomcat, 网上教程很多, 不再赘述
3. 实例
为 Web.xml 配置入口 Servlet
contextConfigLocation 是上下文位置, 读取了我们的 spring 上下文配置文件
设置了编码过滤器 CharacterEncodingFilter
我们还配置了 DispatcherServlet, 这是 SpringMVC 的核心类, 后续我们会详细讲解, url-pattern 配置 "/","/" 代表映射所有请求地址, 如 Controller 请求路径 / user/login, 而不包括 *.jsp,*.ftl 等
- <?xml version="1.0" encoding="UTF-8"?>
- <!--suppress ALL -->
- <Web-App xmlns="http://xmlns.jcp.org/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
- version="3.1">
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:springConfig/applicationContext.xml</param-value>
- </context-param>
- <listener>
- <listener-class>org.springframework.Web.context.ContextLoaderListener</listener-class>
- </listener>
- <filter>
- <filter-name>Set Character Encoding</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>
- <init-param>
- <param-name>forceEncoding</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>Set Character Encoding</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <servlet>
- <servlet-name>dispatcher</servlet-name>
- <servlet-class>org.springframework.Web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:springConfig/dispatcher-servlet.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispatcher</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- </welcome-file-list>
- </Web-App>
写一个简单的 Controller
@Controller 代表这个类是个 Controller, 在 dispatcher-servlet.xml 中被 < context:component-scan base-package="org.format.demo.controller" />扫描到, 并识别成是 "Controller", 在 Spring 容器中初始化
@RequestMapping 意思是请求映射,@RequestMapping("/")中的 "/" 的意义就是 contextPath 后面的路径; 也就是 http://host:port/contextPath 后面的路径
ModelAndView 是一个模型视图类, 由 handler 返回, 并有 DispatcherServlet 解析, 我们往 view 加入了 key 为 "welcome",value 为 "hello" 的 Object
- @Controller
- @RequestMapping("/")
- public class IndexController {
- @RequestMapping
- public ModelAndView index() {
- ModelAndView view = new ModelAndView("index");
- view.addObject("welcome", "hello");
- return view;
- }
- }
在前端页面我们可以看到 key="welcome"
因为在前台我们可以用 EL 表达式取出来
- <!DOCTYPE HTML>
- <HTML>
- <head lang="en">
- <meta charset="UTF-8">
- <title>
- This is freemarker file
- </title>
- </head>
- <body>
- <h2>
- Welcome to user SpringMVC
- </h2>
- <h3>
- your welcome param: ${welcome}
- </h3>
- </body>
- </HTML>
IndexController 中 ModelAndView view = new ModelAndView("viewName"); ,viewName="index"
根据 freemarker.xml 最终解析成 / Web-INF/view/{viewName}.ftl, 所以找到了 / Web-INF/view/index.ftl
简单看下 EmployeeController
- package org.format.demo.controller;
- import org.format.demo.model.Employee;
- import org.format.demo.service.IDeptService;
- import org.format.demo.service.IEmployeeService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.Web.bind.annotation.PathVariable;
- import org.springframework.Web.bind.annotation.RequestMapping;
- import org.springframework.Web.bind.annotation.RequestMethod;
- import org.springframework.Web.bind.annotation.ResponseBody;
- import org.springframework.Web.servlet.ModelAndView;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- @Controller
- @RequestMapping(value = "/employee")
- public class EmployeeController {
- @Autowired
- private IEmployeeService employeeService;
- @Autowired
- private IDeptService deptService;
- @RequestMapping
- public ModelAndView index() {
- ModelAndView view = new ModelAndView("employee/list");
- List<Employee> employees = employeeService.list();
- view.addObject("list", employees);
- return view;
- }
- @RequestMapping(method = RequestMethod.POST, value = "/delete/{employeeId}")
- @ResponseBody
- public String delete(@PathVariable Integer employeeId) {
- employeeService.delete(employeeId);
- return "success";
- }
- @RequestMapping(method = RequestMethod.GET, value = "/add")
- public ModelAndView add(ModelAndView view) {
- view.setViewName("employee/form");
- view.addObject("depts", deptService.listAll());
- return view;
- }
- @RequestMapping(method = RequestMethod.GET, value = "/detail/{employeeId}",
- produces={"application/json; charset=UTF-8"})
- @ResponseBody
- public ModelAndView detail(@PathVariable Integer employeeId, ModelAndView view) {
- view.setViewName("employee/form");
- view.addObject("employee", employeeService.getById(employeeId));
- view.addObject("depts", deptService.listAll());
- return view;
- }
- @RequestMapping(method = RequestMethod.POST, value = "/update")
- public String add(Employee employee) {
- if(employee.getDept().getId() == null) {
- employee.setDept(null);
- }
- employeeService.saveOrUpdate(employee);
- return "redirect:/employee/";
- }
- }
- 1.@RequestMapping
这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上, 它既可以用在类上也可以用在方法上
2.@PathVariable
获取请求参数的值, 这样每个员工都有独立的 URI 资源
3.@ResponseBody
将 Request 的 Accept 设置为 "application/json", 这里同样要将 Response 的 Content-Type 设置成 "application/json", 就是把在 @RequestMapping 中 produces 加入 "application/json"
Accept 和 Content-Type 的区别?
Accept 表示浏览器 / 客户端支持的类型, Content-Type 表示发送端发送的数据类型
3. 如何优雅高效地阅读源码
工欲善其事, 必先利其器, 这里我使用 idea
3.1 官方文档
理论以官方文档和源码为准
3.2 下载源码
在阅读源码前, 请大家下载源码, 可以在 Maven 中下载, 请大家自行百度如何下载源码. 读. class 文件没有意义, 源码有丰富的注释
DispatcherServlet.class, 我觉得我无法读下去
DispatcherServlet.java, 方法和变量都有详细的注释
3.3 常用快捷键
记住快捷键会让你事半功倍
ctrl+n: 快速进入类
ctrl+shift+n: 进入普通文件
ctrl+f12: 查看该类方法
在阅读源码的时候特别方便, 因为你不可能每个方法都细细品读
ctrl+alt+u: 查看类结构图, 这些类都可以点击进入, 我比较喜欢用这个
ctrl+shift+alt+u: 查看类结构图, 这些类不能进入
alt+f7: 查看方法引用位置, 以 doDispatch()为例, 可以看到 DispatcherServlet 897 行被引用, 858 行注释被引用
接下来是我不得不说的 idea 神器 --- 书签, bookmark 可以对代码行进行标记, 并进行快速切换
ctrl+f11: 显示 bookmark 标记情况, 土黄色代表该字符已被占用, 输入或者点击 1 代表在此位置书签为 1
我们以 processDispatchResult()方法为例
ctrl + 标记编号 快速回到标记处, 如我刚才在这留下了书签, ctrl+1,DispatcherServlet 1018 行
shift+f11: 显示所有书签, 左栏是我打过书签的类, 行信息, 右边是代码详情
当你所有书签都用完, 0-9,a-z 全部用完, 可以直接 ctrl+f11, 记录普通书签, 虽然无法用 ctrl 快速跳转, 在 shift+f11 还是可以找到
4. 结语
SpringMVC 是一个优秀的 Web 框架, 简化了开发流程
接下来我们将进入源码分析部分, 待续...
5. 参考
- https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#spring-introduction
- http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html
来源: https://www.cnblogs.com/Java-Starter/p/10304896.html