JSP&Servlet学习笔记_ch03_关于HttpServletRequest

本文详细介绍了HttpServletRequest对象在处理HTTP请求中的作用,包括获取请求参数、处理请求头、防止注入攻击、读取请求内容以及文件上传。特别强调了安全性问题,如对请求参数进行过滤以防止XSS和SQL注入,并展示了如何使用Servlet 3.0的Part接口简化文件上传。同时,通过示例展示了Model2架构在Web应用中的基本流程,强调了控制器、视图和模型的职责分离。
摘要由CSDN通过智能技术生成

3.2 关于HttpServletRequest

        当HTTP请求交给Web容器处理时,Web容器会收集相关信息,并产产生HttpServletRequest对象,可以使用这个对象取得HTTP请求中的信息。可以在Servlet中进行请求的处理,或是转发(包含)另一个Servlet/JSP进行处理。各个Servlet/JSP在同一请求周期中要有共用的资料,可以设置在请求对象中成为属性。

3.2.1处理请求参数

请求来到服务器时,Web容器会创建HttpServleRequest实例来包装请求中的相关信息,HttpServletRequest定义了取得请求信息的方法。例如,可以使用以下方法来取得请求参数。

getParameter():指定请求参数名称来取得对应的值。例如:

        String username = request.GetParameter("name");

        在2.1.2节中就看过取得请求参数的范例,就API而言,请求参数的取得本身不是难事,然而在考虑Web应用程序安全性时,出发点就是:"永远别假设使用者会按照你的期望提供请求信息。"

例如,2.1.2节中的第一个Servlet范例中,苦使用浏览器请求:

http://localhost:8080/FirstServlet/hello?name=%3Csmall%3EJustin%3C/smal%3E

那么响应画面中,可以看到显示的字变小了,如图3.6所示:

 图3.6 未经过滤的请求

        这是因为name=%3Csmall%3EJustin%3C/smal%3E其实是name=<small>Justin</smal>经过URI编码后的结果,也就是说Servlet取得了name请求参数值,等同于"<small>Justin</small>",这个值未任何处理就输出至浏览器。结果是:

<!DOCTYPE html>

<html>

<head>

<title>Hello</title>

</head>

<body>

<h1> Hello! <small>Justin</smal>!</h1>

</body></html>

也就是说<small>Justin<small>成为了HTML的一部分了,单就这个简单范例来说,可以注入任何信息 ,甚至是JavaScript。例如,指定请求参数name=%3Cscript%3Ealert(%22Atack%22)%3C/script%3E,也就是name=<script>alert("Atack")</script>的URI编码,就会发现注入的JavaScript程序代码也输出至浏览器执行了,如图3.7所示 。

图3.7 浏览器执行了注入的JavaScript

        简单来说,未经过滤的请求参数值会形成Web网站的弱点,引发各种可能注入(Injection)攻击可能,刚才的例子就有可能进一步发展成某种XSS(Cross Site Script)攻击。如果请求参数值未经过滤,且进一步成为后端SQL查询语句的一部分,就有可能发生SQL注入(SQL Injection)安全问题,若请求参数未经过滤就成为网址重导的一部分,就有可以成为攻击的跳板,这也是为什么在图1.12中2013年、2017年OWASP TOP里,第一名都是Injection。

        要防止注入的弱点发生,必须对请求进行验证,只不过注入的模式多而复杂,该做什么验证,实际上得视应用程序的设计而定,这部分应该从专门讨论完全的书籍中学习。

        单就这简单的范例来说,基本的防范方式可以将使用者输入的"<"、“>”过滤,并将其转换为对应的HTML实例名称(Entity name)&lt;、%gt;,例如:

Request Hello.java

package com.tjjingpan.Request;

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;
import java.io.PrintWriter;
import java.util.Optional;

@WebServlet("/hello")
public class Hello extends HttpServlet {
    @Override
    protected void doGet(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        String name = Optional.ofNullable(request.getParameter("name"))
                .map(value -> value.replaceAll("<", "&lt;"))
                .map(value -> value.replaceAll(">", "&gt;"))
                .orElse("Guest");

        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("<head>");
        out.print("<title>Hello</title>");
        out.print("</head>");
        out.print("<body>");
        out.printf("<h1> Hello! %s!</h1>", name);
        out.print("</body>");
        out.print("</html>");
    }
}

        由于没有指定的请求参数名称时,getParameter()会传回null,基本上可以使用if来检查是否为null。Servlet4.0是基于Java EE 8的,而Java EE 8是基本Java SE 8,在这里使用Java SE 8的Optiona API来提高代码撰写的流畅度与可读性。

        如果请求参数确实存丰,map()方法会传入参数值,此时将"<"">"转换为对应的%lt;、&gt;,最后的orElse()在请求参数存在时,会返回转换后的字符串,若请求参数不存在,会返回指定的字符串值,在范例中指定为"Guest"。

        HTML实例名称只会在浏览器上显示对应的字符,因此同样的注入模式,现在只会看到如图3.8所示的结果。

 图3.8 基本的注入防范

不过,这类为了安全而撰写的程序代码,若夹杂在一般的商务流程之中,会使商务流程程序代码不易阅读,为了安全而撰写的程序代码比较适合设计为拦截器(Interceptor)之类的组件,适时地设定给应用程序,例如在第5章介绍到的过滤器(Filter),会比较适合用来设计这类安全组件。

3.2.2 处理请求标头

Request Header.java

package com.tjjingpan.Request;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/header")
public class Header extends HttpServlet {
    @Override
    protected void doGet(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("<head>");
        out.print("<title>Show Headers</title>");
        out.print("</head>");
        out.print("<body>");

        Collections.list(request.getHeaderNames())
                .forEach(name -> {
                    out.printf("%s: %s<br>",
                            name, request.getHeader(name));
                });

        out.print("</body>");
        out.print("</html>");
    }
}

 这个范例坠子介绍getHeaderNames()与getHeader()方法的使用外,还示范了如何将Enumeration转为ArrayList,以便进一步使用forEach()方法,结果如图3.9所示.

 图3.9 查看浏览器所送出的标头

3.2.3 请求参数编码处理

3.2.4  getReader()、getInputStream()读取内容

        HttpServletRequest定义有getReader()方法,可以取得一个BufferedReader,通过该对象可以读取请求的Body数据。例如,使用下面这个范例来读取请求Body内容:

Request BodyBody.java

package cc.openhome;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/postbody")
public class BodyBody extends HttpServlet {
	
	protected void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
		PrintWriter out  = response.getWriter();
		out.print("<!DOCTYPE html>");
		out.print("<html>");
		out.print("<head>");
		out.print("<title>Hello</title>");
		out.print("</head>");
		out.print("<body>");
		out.println(bodyContent(request.getReader())); //取得BufferedReader
		out.print("</body>");
		out.print("</html>");
	}
	
	private String bodyContent(BufferedReader reader) throws IOException {
		String input = null;
		StringBuilder requestBody = new StringBuilder();
		while((input = reader.readLine()) != null) {
			requestBody.append(input).append("<br>");
		}
		return requestBody.toString();
	}
}

可试着对这个Servlet以下窗体发现请求:

Request postbody.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="postbody" method="post">
	UserName:<input type="text" name="User"><br>
	PassWord:<input type="password" name="passwd"><br>
	<input type="submit" name="发送查询">
</form>
</body>
</html>

如果在"名称"字段输入"良葛格",在"密码"字段输入123456,单击"发送"按钮后,则会看到图3.10所示的内容:

 图3.10 查看浏览器送出的请求Body

回忆第1章介绍的URI编码,可以看到"良葛格"三字的编码是%E8%89%AF%E8%91%9B%E6%A0%BC,而发送查询的编码是%E5%8F%91%E9%80%81%E6%9F%A5%E8%AF%A2,提交的URI编码是%E6%8F%90%E4%BA%A4

User=%E8%89%AF%E8%91%9B%E6%A0%BC&passwd=123456&%E5%8F%91%E9%80%81%E6%9F%A5%E8%AF%A2=%E6%8F%90%E4%BA%A4

User=良葛格&passwd=123456&发送查询=提交

        窗体发送时,如果<form>标签没有设置enctype属性,则默认值就是application/x-www-form-urlencode.如果要上传文件,enctype属性要设为multipart/form-data。如果使用 下窗体选择一个文件发送:

Request upload.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
	<form action="postbody" method="post" enctype="multipart/form-data">
		选择文件:<input type="file" name="filename" value=""/><br>
		<input type="submit" value="Upload" name="upload"/>
	</form>
</body>
</html>

 例如,使用Chrome浏览器发送一个JPG图片,则在网页上会看到:

------WebKitFormBoundarycYavuQ0Bs5PdVNaR
Content-Disposition: form-data; name="filename"; filename="upload.jpeg"
Content-Type: image/jpeg

ÿØÿàJFIFHHÿÛC  




! #'2*#%/%+;,/35888!*=A<6A2785ÿÛC



5$$55555555555555555555555555555555555555555555555555ÿÀ‹Š"ÿÄÿÄS
!1AQaq¡±"2‘Á#BRrÑ3báCS‚¢²ð$4’“%56DTcsƒÂEUdÒâñ&t£ÿÄÿÄ81!AQ"2aðBq¡±3‘ÁÑá#Rñ4C$b‚ÿÚ?í(ˆ€""ˆˆ" Š ·i;‚Ò´7ksÎYÝž®ÇkK¹{(°½íª˜£,“w;RUh „¢"ˆˆ" ˆ€""ˆˆ" ˆ€"(&úÆÈq#uõTó±×u‡Vöjl9 
.
.
.省略
À"ˆˆ" ˆ€""ˆˆ" ˆ€""ˆˆò”‚€n¦ü{§Kõ{AÜPÆþ·]Û?uTÖlHHøN¶íTÈ-FÜ›í¥±7¶îåLcfw ßeê€""ˆˆ" ˆ€""ˆˆÿÙ
------WebKitFormBoundarycYavuQ0Bs5PdVNaR
Content-Disposition: form-data; name="upload"

Upload
------WebKitFormBoundarycYavuQ0Bs5PdVNaR--

总之是一堆奇奇怪怪的字符,这些字符是实际的文件内容。

提示>>> 接下来的范例内容是进阶内容,可以跳过不看,不影响之后的内容了解。如果想要了解接下来这些进阶内容,可以到JWork@TW(www.javaworld.com.tw)论坛全文搜索"HTTP文件上传机制解析"。

例如,可能使用以下Servlet来处理一个上传的文件:

Request Upload.java

package com.tjjingpan.Request;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/upload")
public class Upload extends HttpServlet {
    private final Pattern fileNameRegex = Pattern.compile("filename=\"(.*)\"");
    private final Pattern fileRangeRegex = Pattern.compile("filename=\".*\"\\r\\n.*\\r\\n\\r\\n(.*+)");

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        byte[] content = bodyContent(request);
        String contentAsTxt = new String(content, "ISO-8859-1");

        String filename = filename(contentAsTxt);
        Range fileRange = fileRange(contentAsTxt, request.getContentType());

        write(
                content,
                contentAsTxt.substring(0, fileRange.start)
                        .getBytes("ISO-8859-1")
                        .length,
                contentAsTxt.substring(0, fileRange.end)
                        .getBytes("ISO-8859-1")
                        .length,
                String.format("d:/javaprojects/jspservlet/chapt03/Request/%s", filename)
        );
    }

    private byte[] bodyContent(HttpServletRequest request) throws IOException {
        try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            InputStream in = request.getInputStream();
            byte[] buffer = new byte[1024];
            int length = -1;
            while((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        }
    }

    private String filename(String contentTxt) throws UnsupportedEncodingException {
        Matcher matcher = fileNameRegex.matcher(contentTxt);
        matcher.find();

        String filename =  matcher.group(1);
        if(filename.contains("\\")) {
            return filename.substring(filename.lastIndexOf("\\") + 1);
        }
        return filename;
    }

    private static class Range {
        final int start;
        final int end;
        public Range(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    private Range fileRange(String content, String contentType) {
        Matcher matcher = fileRangeRegex.matcher(content);
        matcher.find();
        int start = matcher.start(1);

        String boundary = contentType.substring(
                contentType.lastIndexOf("=") + 1, contentType.length());
        int end = content.indexOf(boundary, start) - 4;

        return new Range(start, end);
    }

    private void write(byte[] content, int start, int end, String file) throws IOException {
        try(FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            fileOutputStream.write(content, start, (end - start));
        }
    }
}

        这里的代码比较冗长,主要概念是使用规则表示(Regular expression)来判断文件名与文件内容边界,程序将流程切割为数个子方法,每个方法的作用均以批注说明。可以将前面upload.html中的<form>的action属性改为upload,就可以上传文件了。

3.2.5 getPart()、getParts()取得上传文件

        从上一节使用getInputStream()处理文件上传相关事宜可以看到,处理过程比琐碎,在Servlet3.0中,新增了Part接口,可以方便地进行文件上传处理。可以通过HttpServletRequest的getPart()方法取得Part对象。例如,有个上传窗体内容如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<form action="photo" method="post"
      enctype="multipart/form-data">
  上传相片:<input type="file" name="photo" /><br><br>
  <input type="submit" value="上传" name="upload" />
</form>
</body>
</html>

        可以编写一个Servlet来进行文件上传的处理,这次使用getPart()来处理上传的文件:

Request Photo.java

package com.tjjingpan.Request;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@MultipartConfig//必须设置此标注才能使用getPart()相关API
@WebServlet("/photo")
public class Photo extends HttpServlet {
    private final Pattern fileNameRegex =
            Pattern.compile("filename=\"(.*)\"");

    @Override
    protected void doPost(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Part photo = request.getPart("photo");//使用getPart()取得Part对象
        String filename = getSubmittedFileName(photo);
        write(photo, filename);
    }

    private String getSubmittedFileName(Part part) {//取得上传文件名
        String header = part.getHeader("Content-Disposition");
        Matcher matcher = fileNameRegex.matcher(header);
        matcher.find();

        String filename =  matcher.group(1);
        if(filename.contains("\\")) {
            return filename.substring(filename.lastIndexOf("\\") + 1);
        }
        return filename;
    }

    //存储文件
    private void write(Part photo, String filename) throws IOException, FileNotFoundException {
        try(InputStream in = photo.getInputStream();
            OutputStream out = new FileOutputStream(
                    String.format("d:/javaprojects/jspservlet/chapt03/Request/%s", filename))) {
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
        }
    }
}

@MultipartConfig标注可用来设置Servlet处理上传文件的相关信息,在上例中仅标注@MultipartConfig而没有设置任何属性,这表示相关属性采用默认值。@MultipartConfig的可用属性如下。

  •         fileSizeThreshoud
  •         location
  •         maxFileSize
  •         maxRequestSize

Part有个方便的write()方法,可以直接将上传文件指定文件名写入磁盘中,write()可指定文件名,写入的路径是相对于@MultipartConfig的location设置的路径。例如,上例可以修改为:

Request Photo2

package com.tjjingpan.Request;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@MultipartConfig(location="d:/javaprojects") //设定location属性
@WebServlet("/photo2")
public class Photo2 extends HttpServlet {
    private final Pattern fileNameRegex =
            Pattern.compile("filename=\"(.*)\"");

    @Override
    protected void doPost(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");  //为了处理中文文件名
        Part photo = request.getPart("photo");
        String filename = getSubmittedFileName(photo);
        photo.write(filename);  //将文件写入location指定目录
    }

    private String getSubmittedFileName(Part part) {
        String header = part.getHeader("Content-Disposition");
        Matcher matcher = fileNameRegex.matcher(header);
        matcher.find();

        String filename =  matcher.group(1);
        if(filename.contains("\\")) {
            return filename.substring(filename.lastIndexOf("\\") + 1);
        }
        return filename;
    }
}

        在这个范例中,设置了@MultiPartConfig的location属性。由于上传的文件名可能会有中文,所以调用setCharacterEncoding()设置正确的编码。最后使用Part的write()直接将文件写入location属性指定的目录,这可以简化文件IO的处理。

如果有多个文件要上传 ,可以使用getParts()方法,会返回一个Collection<Part>,其中有每个上传文件的Part对象。例如,有如下窗体:

Request uploads.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<form action="uploads" method="post"
      enctype="multipart/form-data">
    文件 1:<input type="file" name="file1" /><br>
    文件 2:<input type="file" name="file2" /><br>
    文件 3:<input type="file" name="file3" /><br><br>
    <input type="submit" value="上传" name="upload" />
</form>
</body>
</html>

则可台使用以下Servlet来处理文件上传请求:

Request Uploads.java

package com.tjjingpan.Request;

import java.io.*;
import java.time.Instant;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@MultipartConfig(location="d:/javaprojects")
@WebServlet("/uploads")
public class Uploads extends HttpServlet {
    @Override
    protected void doPost(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");
        request.getParts()
                .stream()//使用Stream
                .filter(part -> part.getName().startsWith("file"))//只处理上传文件区段
                .forEach(this::write);
    }

    private void write(Part part) {
        String submittedFileName = part.getSubmittedFileName();
        //取得扩展名
        String ext = submittedFileName.substring(submittedFileName.lastIndexOf('.'));
        try {
            //使用时间毫秒数为主文件名
            part.write(String.format("%s%s", Instant.now().toEpochMilli(), ext));
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}

        在这个范例中,使用Java 8 Stream API,由于"上传"按钮也会是其中一个Part对象,先判断Part的名称是不是以file开头,可以使用Part的getName()来取得名称。进一步过滤出文件上传区段的Part对象,然后取得扩展名。为了避免上传后可能发生的文件名重复问题,以获取系统时间之毫秒数作为主文件名写入文件。

        如果要使用web.xml设置@multipartConfig对应的信息,则可以如下:

...
    <servlet>
        <servlet-name>uploads</servlet-name>
        <servlet-class>com.tjjingpan.Request.Uploads</servlet-class>
        <multipart-config>
            <location>d:/javaprojects</location>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>uploads</servlet-name>
        <url-pattern>/uploadsfiles</url-pattern>
    </servlet-mapping>
...

3.2.6 使用RequestDispatcher调派请求

1.使用include()方法

        RequestDispatcher的include()方法,可以将另一个Servlet的操作流程包括到目前Servlet操作流程之中。例如:

Request Some.java

package com.tjjingpan.Request;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/some")
public class Some extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("Some do one...");
        RequestDispatcher dispatcher = request.getRequestDispatcher("other");
        dispatcher.include(request, response);
        out.println("Some do two...");
    }
}

         other.view实际上会依URI模式取得对应的Servlet。调用include()时,必须分别传入实现ServletRequest、RequestResponse接口对象,可以是service()方法传入的对象或者是自定义的对象或封装器(之后章节介绍封装器的编写)。如果被include()的Servlet是这么编写的:

Request Other.java

package com.tjjingpan.Request;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/other")
public class Other extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().println("Other do one...");
    }
}

        则网页上见到的响应顺序是Some do one... Other do one ... Some do two...。

在取得RequestDispatcher时,也可以包括查询字符串。例如:

RequestDispatcher dispatcher = request.getRequestDispatcher("other?data=123456")

         那么在被包含(或转发,如果使用的是forward())的Servlet中就可以使用getParameter("data")取得请求参数值。

2.请求范围属性

        在include()或forward()时包括请求参数的做法,仅适用于传递字符串值给另一个Servlet,在调派请求的过程中,如果有必须共享的"对象",可以设置给请求对象成为属性,称为请求范围属性(Request Scope Attribute)。

3.使用forward()方法

        RequestDispatcher有个forward()方法,调用时同样传入请求与响应对象,这表示要将请求处理转发给别的Servlet,“对浏览器的响应同时也转发给另一个Servlet”。

        由于请求的include()或forward(),是属性容器内部流程的调派,而不是在响应中要求浏览器重新请求某些URI,因此浏览器不会知道实际上的流程调派,也就是说浏览器的地址栏不会有任何变化。

        第1章曾经介绍过Model 2,在了解请求调派的处理方式之后,这里先做一个简单的Model 2架构应用程序,一方面应用刚才学习到的请求调派处理,另一方面初步了解Model 2的基本流程。首先看控制器(Controller)。它通常由一个Servlet为实现:

Model2 HelloController.java

package com.example.Model2;

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

@WebServlet("/hello")
public class HelloController extends HttpServlet {
    private HelloModel model = new HelloModel();
    @Override
    protected void doGet(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String name = request.getParameter("user");     //收集请求参数
        String message = model.doHello(name);              //委托HelloModel对象处理
        request.setAttribute("message", message);       //将结果信息设置到请求对象成为属性
        request.getRequestDispatcher("hello.view")      //转发给hello.view进行响应
                .forward(request, response);
    }
}

        HelloController会收集请求参数并委托一个HelloModel对象处理,HelloControlller中不会有任何HTML的出现。HelloModel对象处理的结果,会设置为请求对象中的属性,之后呈现画面的Servlet可以从请求对象中取得该属性。接着将请求的响应工作转发给hell.view来负责。

        至于HelloModel类的设计很简单,利用一个HashMap,针对不同的用户设置不同的信息 :

Model2 HelloModel.java

package com.example.Model2;

import java.util.HashMap;
import java.util.Map;

public class HelloModel {
    private Map<String,String> messages = new HashMap<>();

    public HelloModel() {
        messages.put("caterpillar","Hello");
        messages.put("Justin","Welcom");
        messages.put("Momor","Hi");
    }
    public String doHello(String user){
        String message =messages.get(user);
        return String.format("%s,%s!",message,user);
    }
}

        这是一个再简单不过的类。要注意的是,HelloModel对象处理完结果返回给HelloController,

HelloModel类中不会有任何HTML的出现。也没有任何与前端呈现技术或后端存储技术的API出现,是个纯粹的Java对象。

        HelloController得到HelloModel对象的返回值后,将流程转发给HelloView呈现画面;

Model2 HelloVIew.java

package com.example.Model2;

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

@WebServlet("/hello.view")
public class HelloView extends HttpServlet {
    private String htmlTemplate =
            "<!DOCTYPE html>"
                    + "<html>"
                    + "  <head>"
                    + "    <meta charset='UTF-8'>"
                    + "    <title>%s</title>"
                    + "  </head>"
                    + "  <body>"
                    + "    <h1>%s</h1>"
                    + "  </body>"
                    + "</html>";

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {
        String user = request.getParameter("user");
        String message = (String) request.getAttribute("message");
        String html = String.format(htmlTemplate, user, message);
        response.getWriter().print(html);
    }
}

在HelloView中分别取得user请求参数以及先前HelloController中设置在请求对象中的message属性。这里特地使用字符串组成HTML样板,在取得请求参数与属性后,分别设置样板中的两个%s 点位符号,然后再输出至浏览器。

         可以看到,在Model 2架构的实现下,控制器、视图、模型各司其职,该呈现画面的元件就不会有Java代码出现(HelloView),在负责业务逻辑的元件就不会用HTML输出(HelloModel),该处理请求参数的元件不会牵涉业务逻辑代码(HelloController)

        当然,这只是个简单的示范,主要目的在对Model 2 实现有个基本了解。从本章开始,将有个综合练习,以Model 2架构,逐步实现一个功能更完整的应用程序,以便对Model 2架构与实现有更深入的体会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值