本文節選自《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彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。
原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太乾,可以分享轉發給朋友滋潤滋潤!