本文节选自《Spring 5核心原理》

接下来我们来完成MVC模块的功能,应该不需要再做说明。Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成了web.xml的基础配置。下面就从DispatcherServlet开始添砖加瓦。

1 MVC顶层设计

1.1 GPDispatcherServlet

我们已经了解到Servlet的生命周期由init()到service()再到destory()组成,destory()方法我们不做实现。前面我们讲过,这是J2EE中模板模式的典型应用。下面先定义好全局变量:


package com.tom.spring.formework.webmvc.servlet; import com.tom.spring.formework.annotation.GPController;
import com.tom.spring.formework.annotation.GPRequestMapping;
import com.tom.spring.formework.context.GPApplicationContext;
import com.tom.spring.formework.webmvc.*;
import lombok.extern.slf4j.Slf4j; import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern; //Servlet只是作为一个MVC的启动入口
@Slf4j
public class GPDispatcherServlet extends HttpServlet { private final String LOCATION = "contextConfigLocation"; //读者可以思考一下这样设计的经典之处
//GPHandlerMapping最核心的设计,也是最经典的
//它直接干掉了Struts、Webwork等MVC框架
private List<GPHandlerMapping> handlerMappings = new ArrayList<GPHandlerMapping>(); private Map<GPHandlerMapping,GPHandlerAdapter> handlerAdapters = new HashMap<GPHandlerMapping, GPHandlerAdapter>(); private List<GPViewResolver> viewResolvers = new ArrayList<GPViewResolver>(); private GPApplicationContext context; } 下面实现init()方法,我们主要完成IoC容器的初始化和Spring MVC九大组件的初始化。
@Override
public void init(ServletConfig config) throws ServletException {
//相当于把IoC容器初始化了
context = new GPApplicationContext(config.getInitParameter(LOCATION));
initStrategies(context);
} protected void initStrategies(GPApplicationContext context) { //有九种策略
//针对每个用户请求,都会经过一些处理策略处理,最终才能有结果输出
//每种策略可以自定义干预,但是最终的结果都一致 // ============= 这里说的就是传说中的九大组件 ================
initMultipartResolver(context);//文件上传解析,如果请求类型是multipart,将通过MultipartResolver进行文件上传解析
initLocaleResolver(context);//本地化解析
initThemeResolver(context);//主题解析 /** 我们自己会实现 */
//GPHandlerMapping 用来保存Controller中配置的RequestMapping和Method的对应关系
initHandlerMappings(context);//通过HandlerMapping将请求映射到处理器
/** 我们自己会实现 */
//HandlerAdapters 用来动态匹配Method参数,包括类转换、动态赋值
initHandlerAdapters(context);//通过HandlerAdapter进行多类型的参数动态匹配 initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析
initRequestToViewNameTranslator(context);//直接将请求解析到视图名 /** 我们自己会实现 */
//通过ViewResolvers实现动态模板的解析
//自己解析一套模板语言
initViewResolvers(context);//通过viewResolver将逻辑视图解析到具体视图实现 initFlashMapManager(context);//Flash映射管理器
} private void initFlashMapManager(GPApplicationContext context) {}
private void initRequestToViewNameTranslator(GPApplicationContext context) {}
private void initHandlerExceptionResolvers(GPApplicationContext context) {}
private void initThemeResolver(GPApplicationContext context) {}
private void initLocaleResolver(GPApplicationContext context) {}
private void initMultipartResolver(GPApplicationContext context) {} //将Controller中配置的RequestMapping和Method进行一一对应
private void initHandlerMappings(GPApplicationContext context) {
//按照我们通常的理解应该是一个Map
//Map<String,Method> map;
//map.put(url,Method) //首先从容器中获取所有的实例
String [] beanNames = context.getBeanDefinitionNames();
try {
for (String beanName : beanNames) {
//到了MVC层,对外提供的方法只有一个getBean()方法
//返回的对象不是BeanWrapper,怎么办?
Object controller = context.getBean(beanName);
//Object controller = GPAopUtils.getTargetObject(proxy);
Class<?> clazz = controller.getClass(); if (!clazz.isAnnotationPresent(GPController.class)) {
continue;
} String baseUrl = ""; if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
baseUrl = requestMapping.value();
} //扫描所有的public类型的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(GPRequestMapping.class)) {
continue;
} GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
String regex = ("/" + baseUrl + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
this.handlerMappings.add(new GPHandlerMapping(pattern, controller, method));
log.info("Mapping: " + regex + " , " + method); } }
}catch (Exception e){
e.printStackTrace();
} } private void initHandlerAdapters(GPApplicationContext context) {
//在初始化阶段,我们能做的就是,将这些参数的名字或者类型按一定的顺序保存下来
//因为后面用反射调用的时候,传的形参是一个数组
//可以通过记录这些参数的位置index,逐个从数组中取值,这样就和参数的顺序无关了
for (GPHandlerMapping handlerMapping : this.handlerMappings){
//每个方法有一个参数列表,这里保存的是形参列表
this.handlerAdapters.put(handlerMapping,new GPHandlerAdapter());
} } private void initViewResolvers(GPApplicationContext context) {
//在页面中输入http://localhost/first.html
//解决页面名字和模板文件关联的问题
String templateRoot = context.getConfig().getProperty("templateRoot");
String templateRootPath = this.getClass().getClassLoader().getResource (templateRoot).getFile(); File templateRootDir = new File(templateRootPath); for (File template : templateRootDir.listFiles()) {
this.viewResolvers.add(new GPViewResolver(templateRoot));
} }

在上面的代码中,我们只实现了九大组件中的三大核心组件的基本功能,分别是HandlerMapping、HandlerAdapter、ViewResolver,完成MVC最核心的调度功能。其中HandlerMapping就是策略模式的应用,用输入URL间接调用不同的Method已达到获取结果的目的。顾名思义,HandlerAdapter应用的是适配器模式,将Request的字符型参数自动适配为Method的Java实参,主要实现参数列表自动适配和类型转换功能。ViewResolver也算一种策略,根据不同的请求选择不同的模板引擎来进行页面的渲染。

接下来看service()方法,它主要负责接收请求,得到Request和Response对象。在Servlet子类中service()方法被拆分成doGet()方法和doPost()方法。我们在doGet()方法中直接调用doPost()方法,在doPost()方法中调用doDispatch()方法,真正的调用逻辑由doDispatch()来执行。


@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
}catch (Exception e){
resp.getWriter().write("<font size='25' color='blue'>500 Exception</font><br/>Details: <br/>" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","")
.replaceAll("\\s","\r\n") + "<font color='green'><i>Copyright@GupaoEDU </i></font>");
e.printStackTrace();
}
} private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{ //根据用户请求的URL来获得一个Handler
GPHandlerMapping handler = getHandler(req);
if(handler == null){
processDispatchResult(req,resp,new GPModelAndView("404"));
return;
} GPHandlerAdapter ha = getHandlerAdapter(handler); //这一步只是调用方法,得到返回值
GPModelAndView mv = ha.handle(req, resp, handler); //这一步才是真的输出
processDispatchResult(req,resp, mv); } private void processDispatchResult(HttpServletRequest request,HttpServletResponse response, GPModelAndView mv) throws Exception {
//调用viewResolver的resolveViewName()方法
if(null == mv){ return;} if(this.viewResolvers.isEmpty()){ return;} if (this.viewResolvers != null) {
for (GPViewResolver viewResolver : this.viewResolvers) {
GPView view = viewResolver.resolveViewName(mv.getViewName(), null);
if (view != null) {
view.render(mv.getModel(),request,response);
return;
}
}
} } private GPHandlerAdapter getHandlerAdapter(GPHandlerMapping handler) {
if(this.handlerAdapters.isEmpty()){return null;}
GPHandlerAdapter ha = this.handlerAdapters.get(handler);
if (ha.supports(handler)) {
return ha;
}
return null;
} private GPHandlerMapping getHandler(HttpServletRequest req) { if(this.handlerMappings.isEmpty()){ return null;} String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/"); for (GPHandlerMapping handler : this.handlerMappings) {
Matcher matcher = handler.getPattern().matcher(url);
if(!matcher.matches()){ continue;}
return handler;
} return null;
}

GPDisptcherServlet的完整代码请关注微信公众号回复“Spring”。下面补充实现上面的代码中缺失的依赖类。

1.2 GPHandlerMapping

我们已经知道HandlerMapping主要用来保存URL和Method的对应关系,这里其实使用的是策略模式。


package com.tom.spring.formework.webmvc; import java.lang.reflect.Method;
import java.util.regex.Pattern; public class GPHandlerMapping {
private Object controller; //目标方法所在的contrller对象
private Method method; //URL对应的目标方法
private Pattern pattern; //URL的封装 public GPHandlerMapping(Pattern pattern,Object controller, Method method) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
} public Object getController() {
return controller;
} public void setController(Object controller) {
this.controller = controller;
} public Method getMethod() {
return method;
} public void setMethod(Method method) {
this.method = method;
} public Pattern getPattern() {
return pattern;
} public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
}

1.3 GPHandlerAdapter

原生Spring的HandlerAdapter主要完成请求传递到服务端的参数列表与Method实参列表的对应关系,完成参数值的类型转换工作。核心方法是handle(),在handle()方法中用反射来调用被适配的目标方法,并将转换包装好的参数列表传递过去。


package com.tom.spring.formework.webmvc; import com.tom.spring.formework.annotation.GPRequestParam; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map; //专人干专事
public class GPHandlerAdapter { public boolean supports(Object handler){
return (handler instanceof GPHandlerMapping);
} public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception{
GPHandlerMapping handlerMapping = (GPHandlerMapping)handler; //每个方法有一个参数列表,这里保存的是形参列表
Map<String,Integer> paramMapping = new HashMap<String, Integer>(); //这里只是给出命名参数
Annotation[][] pa = handlerMapping.getMethod().getParameterAnnotations();
for (int i = 0; i < pa.length ; i ++) {
for (Annotation a : pa[i]) {
if(a instanceof GPRequestParam){
String paramName = ((GPRequestParam) a).value();
if(!"".equals(paramName.trim())){
paramMapping.put(paramName,i);
}
}
}
} //根据用户请求的参数信息,跟Method中的参数信息进行动态匹配
//resp 传进来的目的只有一个:将其赋值给方法参数,仅此而已 //只有当用户传过来的ModelAndView为空的时候,才会新建一个默认的 //1. 要准备好这个方法的形参列表
//方法重载时形参的决定因素:参数的个数、参数的类型、参数顺序、方法的名字
//只处理Request和Response
Class<?>[] paramTypes = handlerMapping.getMethod().getParameterTypes();
for (int i = 0;i < paramTypes.length; i ++) {
Class<?> type = paramTypes[i];
if(type == HttpServletRequest.class ||
type == HttpServletResponse.class){
paramMapping.put(type.getName(),i);
}
} //2. 得到自定义命名参数所在的位置
//用户通过URL传过来的参数列表
Map<String,String[]> reqParameterMap = req.getParameterMap(); //3. 构造实参列表
Object [] paramValues = new Object[paramTypes.length]; for (Map.Entry<String,String[]> param : reqParameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]",""). replaceAll("\\s",""); if(!paramMapping.containsKey(param.getKey())){continue;} int index = paramMapping.get(param.getKey()); //因为页面传过来的值都是String类型的,而在方法中定义的类型是千变万化的
//所以要针对我们传过来的参数进行类型转换
paramValues[index] = caseStringValue(value,paramTypes[index]);
} if(paramMapping.containsKey(HttpServletRequest.class.getName())) {
int reqIndex = paramMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
} if(paramMapping.containsKey(HttpServletResponse.class.getName())) {
int respIndex = paramMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
} //4. 从handler中取出Controller、Method,然后利用反射机制进行调用 Object result = handlerMapping.getMethod().invoke(handlerMapping.getController(), paramValues); if(result == null){ return null; } boolean isModelAndView = handlerMapping.getMethod().getReturnType() == GPModelAndView.class;
if(isModelAndView){
return (GPModelAndView)result;
}else{
return null;
}
} private Object caseStringValue(String value,Class<?> clazz){
if(clazz == String.class){
return value;
}else if(clazz == Integer.class){
return Integer.valueOf(value);
}else if(clazz == int.class){
return Integer.valueOf(value).intValue();
}else {
return null;
}
} }

1.4 GPModelAndView

原生Spring中ModelAndView类主要用于封装页面模板和要往页面传送的参数的对应关系。


package com.tom.spring.formework.webmvc; import java.util.Map; public class GPModelAndView { private String viewName; //页面模板的名称
private Map<String,?> model; //往页面传送的参数 public GPModelAndView(String viewName) {
this(viewName,null);
}
public GPModelAndView(String viewName, Map<String, ?> model) {
this.viewName = viewName;
this.model = model;
} public String getViewName() {
return viewName;
} public void setViewName(String viewName) {
this.viewName = viewName;
} public Map<String, ?> getModel() {
return model;
} public void setModel(Map<String, ?> model) {
this.model = model;
}
}

1.5 GPViewResolver

原生Spring中的ViewResolver主要完成模板名称和模板解析引擎的匹配。通过在Serlvet中调用resolveViewName()方法来获得模板所对应的View。在这个Mini版本中简化了实现,只实现了一套默认的模板引擎,语法也是完全自定义的。


package com.tom.spring.formework.webmvc; import java.io.File;
import java.util.Locale; //设计这个类的主要目的是:
//1. 将一个静态文件变为一个动态文件
//2. 根据用户传送不同的参数,产生不同的结果
//最终输出字符串,交给Response输出
public class GPViewResolver {
private final String DEFAULT_TEMPLATE_SUFFIX = ".html"; private File templateRootDir;
private String viewName; public GPViewResolver(String templateRoot){
String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot). getFile();
this.templateRootDir = new File(templateRootPath);
} public GPView resolveViewName(String viewName, Locale locale) throws Exception {
this.viewName = viewName;
if(null == viewName || "".equals(viewName.trim())){ return null;}
viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);
File templateFile = new File((templateRootDir.getPath() + "/" + viewName).replaceAll ("/+", "/"));
return new GPView(templateFile);
} public String getViewName() {
return viewName;
}
}

1.6 GPView

这里的GPView就是前面所说的自定义模板解析引擎,其核心方法是render()。在render()方法中完成对模板的渲染,最终返回浏览器能识别的字符串,通过Response输出。


package com.tom.spring.formework.webmvc; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.RandomAccessFile;
import java.util.Map;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class GPView { public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=utf-8"; private File viewFile; public GPView(File viewFile){
this.viewFile = viewFile;
} public String getContentType(){
return DEFAULT_CONTENT_TYPE;
} public void render(Map<String, ?> model,HttpServletRequest request, HttpServletResponse response) throws Exception{
StringBuffer sb = new StringBuffer();
RandomAccessFile ra = new RandomAccessFile(this.viewFile,"r"); try {
String line = null;
while (null != (line = ra.readLine())) {
line = new String(line.getBytes("ISO-8859-1"),"utf-8");
Pattern pattern = Pattern.compile("¥\\{[^\\}]+\\}",Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(line); while (matcher.find()) { String paramName = matcher.group();
paramName = paramName.replaceAll("¥\\{|\\}","");
Object paramValue = model.get(paramName);
if (null == paramValue) { continue; }
//要把¥{}中间的这个字符串取出来
line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
matcher = pattern.matcher(line); } sb.append(line);
}
}finally {
ra.close();
}
response.setCharacterEncoding("utf-8");
//response.setContentType(DEFAULT_CONTENT_TYPE);
response.getWriter().write(sb.toString());
} //处理特殊字符
public static String makeStringForRegExp(String str) {
return str.replace("\\", "\\\\").replace("*", "\\*")
.replace("+", "\\+").replace("|", "\\|")
.replace("{", "\\{").replace("}", "\\}")
.replace("(", "\\(").replace(")", "\\)")
.replace("^", "\\^").replace("$", "\\$")
.replace("[", "\\[").replace("]", "\\]")
.replace("?", "\\?").replace(",", "\\,")
.replace(".", "\\.").replace("&", "\\&");
} }

从上面的代码可以看出,GPView是基于HTML文件来对页面进行渲染的。但是加入了一些自定义语法,例如在模板页面中扫描到¥{name}这样的表达式,就会从ModelAndView的Model中找到name所对应的值,并且用正则表达式将其替换(外国人喜欢用美元符号$,我们的模板引擎就用人民币符号¥)。

2 业务代码实现

2.1 IQueryService

定义一个负责查询业务的顶层接口IQueryService,提供一个query()方法:


package com.tom.spring.demo.service; /**
* 查询业务
*
*/
public interface IQueryService { /**
* 查询
*/
public String query(String name); }

2.2 QueryService

查询业务的实现QueryService也非常简单,就是打印一下调用时间和传入的参数,并封装为JSON格式返回:


package com.tom.spring.demo.service.impl; import java.text.SimpleDateFormat;
import java.util.Date; import com.tom.spring.demo.service.IQueryService;
import com.tom.spring.formework.annotation.GPService;
import lombok.extern.slf4j.Slf4j; /**
* 查询业务
*
*/
@GPService
@Slf4j
public class QueryService implements IQueryService { /**
* 查询
*/
public String query(String name) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
String json = "{name:\"" + name + "\",time:\"" + time + "\"}";
log.info("这是在业务方法中打印的:" + json);
return json;
} }

2.3 IModifyService

定义一个增、删、改业务的顶层接口IModifyService:



package com.tom.spring.demo.service;
/**
* 增、删、改业务
*/
public interface IModifyService {
/**
* 增加
*/
public String add(String name, String addr) ;
/**
* 修改
*/
public String edit(Integer id, String name);
/**
* 删除
*/
public String remove(Integer id); }

2.4 ModifyService

增、删、改业务的实现ModifyService也非常简单,主要是打印传过来的参数:


package com.tom.spring.demo.service.impl;
import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.formework.annotation.GPService; /**
* 增、删、改业务
*/
@GPService
public class ModifyService implements IModifyService {
/**
* 增加
*/
public String add(String name,String addr) {
return "modifyService add,name=" + name + ",addr=" + addr;
}
/**
* 修改
*/
public String edit(Integer id,String name) {
return "modifyService edit,id=" + id + ",name=" + name;
}
/**
* 删除
*/
public String remove(Integer id) {
return "modifyService id=" + id;
}
}

2.5 MyAction

Controller的主要功能是负责调度,不做业务实现。业务实现方法全部在Service层,一般我们会将Service实例注入Controller。MyAction中主要实现对IQueryService和IModifyService的调度,统一返回结果:



package com.tom.spring.demo.action;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.demo.service.IQueryService;
import com.tom.spring.formework.annotation.GPAutowired;
import com.tom.spring.formework.annotation.GPController;
import com.tom.spring.formework.annotation.GPRequestMapping;
import com.tom.spring.formework.annotation.GPRequestParam;
import com.tom.spring.formework.webmvc.GPModelAndView; /**
* 公布接口URL
*/
@GPController
@GPRequestMapping("/web")
public class MyAction { @GPAutowired IQueryService queryService;
@GPAutowired IModifyService modifyService; @GPRequestMapping("/query.json")
public GPModelAndView query(HttpServletRequest request, HttpServletResponse response,
@GPRequestParam("name") String name){
String result = queryService.query(name);
return out(response,result);
}
@GPRequestMapping("/add*.json")
public GPModelAndView add(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam("name") String name,@GPRequestParam("addr") String addr){
String result = modifyService.add(name,addr);
return out(response,result);
}
@GPRequestMapping("/remove.json")
public GPModelAndView remove(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam("id") Integer id){
String result = modifyService.remove(id);
return out(response,result);
}
@GPRequestMapping("/edit.json")
public GPModelAndView edit(HttpServletRequest request,HttpServletResponse response,
@GPRequestParam("id") Integer id,
@GPRequestParam("name") String name){
String result = modifyService.edit(id,name);
return out(response,result);
} private GPModelAndView out(HttpServletResponse resp,String str){
try {
resp.getWriter().write(str);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

2.6 PageAction

专门设计PageAction是为了演示Mini版Spring对模板引擎的支持,实现从Controller层到View层的传参,以及对模板的渲染进行最终输出:


package com.tom.spring.demo.action; import java.util.HashMap;
import java.util.Map;
import com.tom.spring.demo.service.IQueryService;
import com.tom.spring.formework.annotation.GPAutowired;
import com.tom.spring.formework.annotation.GPController;
import com.tom.spring.formework.annotation.GPRequestMapping;
import com.tom.spring.formework.annotation.GPRequestParam;
import com.tom.spring.formework.webmvc.GPModelAndView; /**
* 公布接口URL
*/
@GPController
@GPRequestMapping("/")
public class PageAction { @GPAutowired IQueryService queryService; @GPRequestMapping("/first.html")
public GPModelAndView query(@GPRequestParam("teacher") String teacher){
String result = queryService.query(teacher);
Map<String,Object> model = new HashMap<String,Object>();
model.put("teacher", teacher);
model.put("data", result);
model.put("token", "123456");
return new GPModelAndView("first.html",model);
} }

3 定制模板页面

为了更全面地演示页面渲染效果,分别定义了first.html对应PageAction中的first.html请求、404.html默认页和500.html异常默认页。

3.1 first.html

first.html定义如下:


<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>SpringMVC模板引擎演示</title>
</head>
<center>
<h1>大家好,我是¥{teacher}老师<br/>欢迎大家一起来探索Spring的世界</h1>
<h3>Hello,My name is ¥{teacher}</h3>
<div>¥{data}</div>
Token值:¥{token}
</center>
</html>

3.2 404.html

404.html定义如下:


<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>页面去火星了</title>
</head>
<body>
<font size='25' color='red'>404 Not Found</font><br/><font color='green'><i>Copyright @GupaoEDU</i></font>
</body>
</html>

3.3 500.html

500.html定义如下:


<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>服务器好像累了</title>
</head>
<body>
<font size='25' color='blue'>500 服务器好像有点累了,需要休息一下</font><br/>
<b>Message:¥{detail}</b><br/>
<b>StackTrace:¥{stackTrace}</b><br/>
<font color='green'><i>Copyright@GupaoEDU</i></font>
</body>
</html>

4 运行效果演示

在浏览器中输入 http://localhost/web/query.json?name=Tom ,就会映射到MyAction中的@GPRequestMapping(“query.json”)对应的query()方法,得到如下图所示结果。

在浏览器中输入 http://localhost/web/addTom.json?name=tom&addr=HunanChangsha ,就会映射到MyAction中的@GPRequestMapping(“add*.json”)对应的add()方法,得到如下图所示结果。

在浏览器中输入 http://localhost/web/remove.json?id=66 ,就会映射到MyAction中的@GPRequestMapping(“remove.json”)对应的remove()方法,并将id自动转换为int类型,得到如下图所示结果。

在浏览器中输入 http://localhost/web/edit.json?id=666&name=Tom ,就会映射到MyAction中的@GPRequestMapping(“edit.json”)对应的edit()方法,并将id自动转换为int类型,得到如下图所示结果。

在浏览器中输入 http://localhost/first.html?teacher=Tom ,就会映射到PageAction中的@GPRequestMapping(“first.html”)对应的query()方法,得到如下图所示结果。

到这里,已经实现了Spring从IoC、ID到MVC的完整功能。虽然忽略了一些细节,但是我们已经了解到,Spring的核心设计思想其实并没有我们想象得那么神秘。我们已经巧妙地用到了工厂模式、静态代理模式、适配器模式、模板模式、策略模式、委派模式等,使得代码变得非常优雅。

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

30个类手写Spring核心原理之MVC映射功能(4)的更多相关文章

  1. 30个类手写Spring核心原理之依赖注入功能(3)

    本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...

  2. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

  3. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

  4. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  5. 30个类手写Spring核心原理之自定义ORM(上)(6)

    本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...

  6. 30个类手写Spring核心原理之Ioc顶层架构设计(2)

    本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...

  7. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  8. 手写Spring MVC

    闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...

  9. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

随机推荐

  1. Solon 1.5.67 发布,增加 GraalVm Native 支持

    Solon 已有120个生态扩展插件,此次更新主要为细节打磨: 添加 solon.extend.graalvm 插件,用于适配 graalvm native image 模式 从此,solon 进入 ...

  2. Fastjson妙用之@JSONField注解

    在开发的过程中使用json格式的地方非常多,现在前后端分离的项目中,前后端数据交换的格式一般为json,这种格式的优/缺点这里不再赘述,感兴趣的可以百度.把java中的实体类序列化为json的方式也有 ...

  3. 拨开由问题《linux下malloc最大可申请的内存》带来的重重疑云

    今天阅读相关书籍的时候看到 "进程中堆的最大申请数量" 这一问题,我们知道使用malloc分配内存是在堆Heap里面分配的,如果一台机器一共有8GB物理内存,空闲5GB,那么我们使 ...

  4. 面试官又整新活,居然问我for循环用i++和++i哪个效率高?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 前几天,一个小伙伴告诉我,他在面试的时候被面试官问了这么一个问题: 在for循环中,到底应该用 i++ 还是 ++i ? 听到这,我感觉这面试官 ...

  5. vue局部过滤器和全局过滤器

    全局过滤器在main.js中写 //注册全局过滤器 Vue.filter('wholeMoneyFormat',(value)=>{   return '¥'+Number(value).toF ...

  6. 【Azure 环境】在Windows环境中抓取网络包(netsh trace)后,如何转换为Wireshark格式以便进行分析

    问题描述 如何在Windows环境中,不安装第三方软件的情况下(使用Windows内置指令),如何抓取网络包呢?并且如何转换为Wireshark 格式呢? 操作步骤 1) 以管理员模式打开CMD,使用 ...

  7. 面向对象编程之Python学习一

    在实际的程序设计中,使用Java面向对象编程方法编写算法能够很清楚的理解其来龙去脉. 习惯了面向对象思维,学习Python也自然使用这种思维. 目前,由于Python很多软件包能够容易的获取和利用,人 ...

  8. postman自动调用获取token

    Postman不光支持单次请求,还支持环境变量.全局变量.集合变量 本文使用Collection Variable Collection 如下图可以点击Collection然后可以添加请求和文件夹,以 ...

  9. [NOIP2017 提高组] 宝藏

    考虑到这种对于某种操作顺序有一个权值. 且这个权值有一个\(O(n)\)或者更好的复杂度求出. 求最值. 那可以用模拟退火. #include<iostream> #include< ...

  10. Atcoder Grand Contest 020 E - Encoding Subsets(记忆化搜索+复杂度分析)

    Atcoder 题面传送门 & 洛谷题面传送门 首先先考虑如果没有什么子集的限制怎样计算方案数.明显就是一个区间 \(dp\),这个恰好一年前就做过类似的题目了.我们设 \(f_{l,r}\) ...