主頁 > 後端開發 > 30個類手寫Spring核心原理之MVC映射功能(4)

30個類手寫Spring核心原理之MVC映射功能(4)

2021-12-14 06:15:17 後端開發

本文節選自《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 = https://www.cnblogs.com/gupaoedu-tom/p/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 = https://www.cnblogs.com/gupaoedu-tom/p/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()方法,得到如下圖所示結果,

file

在瀏覽器中輸入 http://localhost/web/addTom.json?name=tom&addr=HunanChangsha ,就會映射到MyAction中的@GPRequestMapping(“add*.json”)對應的add()方法,得到如下圖所示結果,

file

在瀏覽器中輸入 http://localhost/web/remove.json?id=66 ,就會映射到MyAction中的@GPRequestMapping(“remove.json”)對應的remove()方法,并將id自動轉換為int型別,得到如下圖所示結果,

file

在瀏覽器中輸入 http://localhost/web/edit.json?id=666&name=Tom ,就會映射到MyAction中的@GPRequestMapping(“edit.json”)對應的edit()方法,并將id自動轉換為int型別,得到如下圖所示結果,

file

在瀏覽器中輸入 http://localhost/first.html?teacher=Tom ,就會映射到PageAction中的@GPRequestMapping(“first.html”)對應的query()方法,得到如下圖所示結果,

file

到這里,已經實作了Spring從IoC、ID到MVC的完整功能,雖然忽略了一些細節,但是我們已經了解到,Spring的核心設計思想其實并沒有我們想象得那么神秘,我們已經巧妙地用到了工廠模式、靜態代理模式、配接器模式、模板模式、策略模式、委派模式等,使得代碼變得非常優雅,

本文為“Tom彈架構”原創,轉載請注明出處,技術在于分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力,

原創不易,堅持很酷,都看到這里了,小伙伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太干,可以分享轉發給朋友滋潤滋潤!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/380176.html

標籤:Java

上一篇:Redis單執行緒為什么這么快?看完秒懂了...

下一篇:java~RMI引起的log4j漏洞

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more