你還在使用getParameter嗎?使用反射自定義一個簡易的類似與Spring MVC的引數自動對映

2020-10-05 01:00:20

SpringMVC頁面向controller層傳值的自動對映條件,

表單的name值與引數名對應,或使用註解@RequestParam進行物件,或與物件中的屬性名對應,並且需要提供setter方法,才能完成引數的賦值,其中是如何進行賦值的過程我們並不知道嗎、,也不關心,只知道、如何去使用它就夠了,這也就是我們學習效率不高的原因,只知其然,不知其所以然

下面是模擬springMVC向入參為物件中對映值的過程,從此不在Servlet中使用request.getParameter;

條件

  1. 表單name屬性的值必須與物件屬性名相同
  2. 必須提供屬性公共的setter方法

在學習的過程中我們要常保持疑問,帶著問題去學習,如果你連疑問都沒有,那你自然就不知道你想要的答案是什麼,沒有想要的答案,也就沒有學習的價值,先宣告你的疑問,再去學習,
疑問:就是為什麼需要這兩個條件成立

準備程式碼:一個實體類,一個表單,一個servlet,

package com.entity;

import java.util.Date;
/**
*實體類,將引數值儲存在此物件中
*/
public class Student {
    private String name;
    private Integer age;
    private Double score;
    private Date birthDay;
    public Date getBirthDay() {
        return birthDay;
    }
    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }
    public Student() {
        System.out.println("Student的無參構造");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("setName--------》");
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Double getScore() {
        return score;
    }
    public void setScore(Double score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                ", birthDay=" + birthDay +
                '}';
    }
}

表單 ,注:此處使用的是jsp,name值都與實體類中的屬性名對應

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="studentServlet" method="post">
    name: <input type="text" name="name"><br>
    age:<input type="text" name="age"><br>
    score:<input type="text" name="score"><br>
    birthDay: <input type="text" name="birthDay"><br>
    <input type="submit" value="提交"><br>
  </form>
  </body>
</html>

在引數自動對映中,我們也只能在Servlet中進行做文章了,為了程式碼的重用性,我將引數自動對映功能,封裝成一個方法,想要使用只需要將此類拷貝即可,方法的具體內容:

package com.utils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Enumeration;

public class BeanOperateTools {
	/**
     * 該方法是由Servlet呼叫
     * 具體實現的引數封裝方法
     * @param obj 要將表單值封裝至的物件
     * @param req request物件,包含我們表單請求來的資料
     * @throws Exception 可能丟擲的異常
     */
    public static void setValueSimple(Object obj, HttpServletRequest req) throws Exception {
    	//得到實體類的類資訊
        Class<?> cls = obj.getClass();
        //獲取所有的引數名稱
        Enumeration<String> enu = req.getParameterNames();
        //遍歷引數名稱
        while (enu.hasMoreElements()){
   		    //獲取引數名稱
            String paramName = enu.nextElement();
            //獲取對應的引數名稱的值
            String paramValue = req.getParameter(paramName);
            //進行判斷當前文字方塊是否填寫值,沒有值就跳過此屬性值的封裝
            if("".equals(paramValue) || null==paramValue){
                continue;
            }
            /*根據表單的name值類取得對應的屬性,這就是
            為什麼需要我們表單的name值要與實體類的屬性
            名對應的原因,我們可以根據這個屬性的型別,來
            確定setter方法的引數型別,並確定是否需要進
            行型別裝換*/
            Field field = cls.getDeclaredField(paramName);
            //獲取引數型別,沒有包名,getSimpleName()此方法是去除包名的
            String fieldType 
            =field.getType().getSimpleName();
            /*獲取指定的操作方法,以滿足反射的呼叫
            使用字串set+屬性名的方式,並把屬性的首字母
            大寫,正好對應我們屬相的setter方法,引數型別
            就是上面的field
            */
            Method method = cls.getMethod("set"+initcap(paramName), field.getType());
            //此處進行判斷是否需要進行型別的轉換
            if("String".equals(fieldType)){
           		//執行具體set方法,並傳入具體值
                method.invoke(obj, paramValue);
            }else if("Integer".equals(fieldType) || "int".equals(fieldType)){
                method.invoke(obj,Integer.parseInt(paramValue));
            }else if("Double".equals(fieldType) || "double".equals(fieldType)){
                method.invoke(obj,Double.parseDouble(paramValue));
            }else if("Date".equals(fieldType)){
                method.invoke(obj,new SimpleDateFormat("yyyy-MM-dd").parse(paramValue));
            }
        }
    }
    //將屬性名的首字母大寫
    private static String initcap(String value){
        return value.substring(0,1).toUpperCase().concat(value.substring(1));
    }
}

引數封裝方法已完成,只需要在Servlet中呼叫即可

package com.servlet;

import com.entity.Student;
import com.utils.BeanOperateTools;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "StudentServlet", urlPatterns = {"/studentServlet"})
public class StudentServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //要儲存表單資料的物件
        Student stu = new Student();
        try {
        	//直接呼叫,傳入要儲存表單資料的物件,和request
            BeanOperateTools.setValueSimple(stu,req);
        } catch (Exception e){
            e.printStackTrace();
        }
        //將封好的資料存入request中,
        req.setAttribute("stu",stu);
        //跳轉頁面,檢視封裝的資料是否正確
        req.getRequestDispatcher("show.jsp").forward(req,resp);
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
}

show.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${stu}
</body>
</html>

寫完這些我們在看一下springMVC的引數自動對映的條件:

  1. 表單name屬性的值必須與物件屬性名相同
  2. 必須提供屬性公共的setter方法
    現在知道為什麼了嗎!

引數自動對映的具體過程,大致就這些,不知道你看明白了嗎,當然,我們所寫的與springMVC的相比,那就是,小巫見大巫、木棍挑戰坦克,重申:此文章,只是讓我們能夠了解springMVC在進行引數封裝的過程,對框架能夠有一個更加深層的認知