Servlet实践--留言板-v1
功能介绍:
由三个jsp页面组成,在doGet中根据请求URL中的请求参数不同,跳转到不同的页面:
页面1:显示整个留言板列表
页面2:创建留言页面(包括用户、主题、内容和上传文件)
页面3:在查看单个留言的详细内容(包括提供下载附件)
在doPost中处理创建留言的逻辑
如何实现这些功能:
1.使用什么来保存用户创建的留言(数据存储):
使用一个Ticket类对象来保存用户创建的留言,包括用户名、评论主题、评论内容和附件。附件是用一个Attachment类的实例来表示,该类中包含附件名和附件内容的二进制表示。
当需要查看某个留言详细信息的时候,可以通过一个存储在内存中的哈希map(ticketDatabase)(以一个唯一标识某个留言的id为键,以Ticket类对象为值)来找到该Ticket类对象,通过该对象就可以得到详细信息。
当需要浏览整个留言列表时,可以通过遍历该ticketDatabase得到整个留言列表。
当需要下载某个留言中的附件时,可以先通过该ticketDatabase找到该Ticket类对象,而在Ticket类对象中也同样保存着一个哈希map(attachments)(以附件名为键,以Attachment类对象为值),通过该哈希map就可以找到某个特定的附件。
2.如何让servlet处理不同的请求(逻辑处理):
根据请求URL中的请求参数不同,让处理请求的doGet()和doPost()方法调用不同功能的方法实现。例如:
当不带任何请求参数的请求URL,默认其行为为浏览整个留言列表(action=list);
当携带请求参数时,若action=create并以Get方式提交请求,则执行将请求和响应转发到ticketForm.jsp页面;
若action=view并以Get方式提交请求,则先从请求参数中获得ticketId(唯一标识某个特定的Ticket对象),然后通过ticketDatabase获得该ticketId对应的Ticket对象,把该ticketId和Ticket对象保存在请求中,然后转发到viewTicket.jsp页面
当携带请求参数时,若action=download并以Get方式提交请求,则执行下载附件的相关操作:
+先获得该ticketId和Ticket对象,然后通过请求参数中的attachment获得附件名,得到Ticket对象和附件名,就可以得到Attachment对象
+通过Attachment对象,就可以得到附件内容的二进制表示(一个二进制数组)
+强制浏览器询问用户是保存还是下载文件,设置附件的内容类型是通用的二进制内容类型
+将附件内容的二进制数组写入ServletOutputStream输出流中
当携带请求参数时,若action=list并以Get方式提交请求时,将ticketDatabase保存在请求中,然后转发到listTickets.jsp页面;
当携带请求参数时,若action=createt并以Post方式提交请求时,将从表单中获取Ticket对象的相关属性(用户名、留言主题、留言内容、上传的文件),通过setXXX()方法分别设置Ticket对象的属性。其中文件上传时,需要将该文件转换中Part对象(filePart),Part对象可以表示一个上传的文件或者表单数据。然后生成一个唯一的ticketId,将附件内容通过IO读取的方式保存在Attachment对象中的二进制数组中,最后将ticketId和Ticket对象组成键值对添加到ticketDatabase中,把页面重定向到浏览单个留言详细内容的页面中
3.显示:
jsp页面处理显示操作
部署文件web.xml:
<jsp-config>
<!-- jsp组属性,不同的jsp组可以设置不同的属性,若不同属性组发送匹配冲突时,遵循匹配精确优先原则 -->
<jsp-property-group>
<!-- 该jsp组属性将应用于哪些文件,在这里它将匹配在Web应用程序中所有以jsp和jspf文件结尾的文件-->
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspf</url-pattern>
<!-- jsp页面编码,它和page指令中的pagaEncoding特性一致-->
<page-encoding>UTF-8</page-encoding>
<!-- 允许使用jsp中的java,若为true,则禁止在jsp中使用java -->
<scripting-invalid>false</scripting-invalid>
<!-- 告诉web容器在所有属于该属性组的jsp的头部添加文件/WEB-INF/jsp/base.jspf -->
<include-prelude>/WEB-INF/jsp/base.jspf</include-prelude>
<!-- 告诉jsp转换器删除响应输出中的空白,只保留由指令、声明、脚本和其他JSP标签创建的文本,即可以产生干净的代码 -->
<trim-directive-whitespaces>true</trim-directive-whitespaces>
<!-- 默认的内容类型是text/html -->
<default-content-type>text/html</default-content-type>
</jsp-property-group>
</jsp-config>
base.jspf:
<%@ page contentType="text/html; charset=utf-8" language="java"%>
<%@ page import="cn.example.Ticket, cn.example.Attachment" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
listTickets.jsp:
<%@ page session="false" import="java.util.Map" %>
<%
@SuppressWarnings("unchecked")
Map<Integer,Ticket> ticketDatabase = (Map<Integer, Ticket>)request.getAttribute("ticketDatabase");
%> <!DOCTYPE html>
<html>
<head>
<title>留言板</title>
</head>
<body>
<h2>留言板</h2>
<a href="
<c:url value="/tickets">
<c:param name="action" value="create"/>
</c:url>
">创建留言</a><br/><br/>
<%
if(ticketDatabase.size() == 0){
%><i>留言板中没有留言。</i><%
}
else{
for(int id : ticketDatabase.keySet()){
String idString = Integer.toString(id);
Ticket ticket = ticketDatabase.get(id);
%>留言 #<%= idString %> : <a href="
<c:url value="/tickets">
<c:param name="action" value="view"/>
<c:param name="ticketId" value="<%= idString %>"/>
</c:url>
"><%=ticket.getSubject() %></a>(用户: <%= ticket.getCustomerName() %>) <br/>
<%
}
}
%>
</body>
</html>
ticketForm.jsp:
package cn.example;
/*
* 一个简单的POJO,表示着一个附件类
*/
public class Attachment {
private String name; // 附件名
private byte[] contents; // 附件的内容以字节数组的形式保存 public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getContents() {
return contents;
}
public void setContents(byte[] contents) {
this.contents = contents;
}
}
viewTicket.jsp:
<%
String ticketId = (String) request.getAttribute("ticketId");
Ticket ticket = (Ticket) request.getAttribute("ticket");
%>
<!DOCTYPE html>
<html>
<head>
<title>留言版</title>
</head>
<body>
<h2>留言 #<%=ticketId %>: <%= ticket.getSubject() %></h2>
<i>用户 - <%=ticket.getCustomerName() %></i> <br/><br/>
<i>内容:</i><br/>
<%= ticket.getBody() %> <br/><br/>
<%
if(ticket.getNumberOfAttachments() > 0){
%>附件:<%
int i = 0;
for(Attachment a:ticket.getAttachments()){
if(i++ > 0)
out.print(", ");
%>
<a href="
<c:url value="/tickets">
<c:param name="action" value="download"/>\
<c:param name="ticketId" value="<%= ticketId %>"/>
<c:param name="attachment" value="<%=a.getName() %>"/>
</c:url>
"><%=a.getName() %> </a><%
}
}
%><br/>
<a href="<c:url value="/tickets"/>">返回留言板主页</a>
</body>
</html>
Attachment.java
package cn.example;
/*
* 一个简单的POJO,表示着一个附件类
*/
public class Attachment {
private String name; // 附件名
private byte[] contents; // 附件的内容以字节数组的形式保存 public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getContents() {
return contents;
}
public void setContents(byte[] contents) {
this.contents = contents;
}
}
Ticket.java:
package cn.example;
/*
* 一个简单的POJO,表示一个票据类
*/ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map; public class Ticket {
private String customerName; // 用户名
private String subject; // 评论内容的主题
private String body; // 评论内容的主体
// 使用哈希map表示附件数据库,以附件名为键,以附件为值
private Map<String, Attachment> attachments = new LinkedHashMap<String, Attachment>(); public String getCustomerName() {
return customerName;
} public void setCustomerName(String customerName) {
this.customerName = customerName;
} public String getBody() {
return body;
} public void setBody(String body) {
this.body = body;
} public Attachment getAttachment(String name){
return this.attachments.get(name);
} public Collection<Attachment> getAttachments() {
return this.attachments.values();
} public void addAttachments(Attachment attachment) {
this.attachments.put(attachment.getName(), attachment);
} public int getNumberOfAttachments(){
return this.attachments.size();
} public String getSubject() {
return subject;
} public void setSubject(String subject) {
this.subject = subject;
} }
TicketServlet.java:
package cn.example; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map; import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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; import jdk.nashorn.internal.ir.RuntimeNode.Request;
@WebServlet(
name = "ticketServlet",
urlPatterns = {"/tickets"},
loadOnStartup = 1
)
//告诉web容器为该servlet提供文件上传支持
@MultipartConfig(
// 告诉web容器文件必须达到5MB时才写入临时目录
fileSizeThreshold = 5_242_800, // 5MB
// 上传的文件不能超过20MB
maxFileSize = 20_971_520L, // 20MB
// 不能接收超过40MB的请求
maxRequestSize = 41_942_040L // 40MB
)
public class TicketServlet extends HttpServlet{
private volatile int TICKET_ID_SEQUENCE = 1;
// 使用哈希map作为票据数据库
private Map<Integer, Ticket> ticketDatabase = new LinkedHashMap<>(); /*
* 在doGet()方法中,根据请求参数的不同,把任务委托给相应的执行器
* 功能:
* 显示创建票据页面
* 查看单个票据内容
* 下载附件
* 显示票据列表
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8"); String action = req.getParameter("action");
// 若不带action请求参数,设置默认值,即默认的行为是显示票据列表
if(action == null)
action = "list";
switch (action) {
case "create":
this.showTicketForm(req,resp);
break;
case "view":
this.viewTicket(req, resp);
break;
case "download":
this.downloadAttachment(req, resp);
break;
default:
this.listTickets(req, resp);
break;
}
} /*
* 创建新的票据
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8"); String action = req.getParameter("action");
if(action == null)
action = "list";
switch(action){
case "create":
this.createTicket(req, resp);
break;
case "list":
default:
resp.sendRedirect("tickets");
break;
}
} private void createTicket(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException{ //1.新建一个票据对象
Ticket ticket = new Ticket(); //2.以表单数据为源,设置票据相应的成员属性
ticket.setCustomerName(req.getParameter("customerName"));
ticket.setSubject(req.getParameter("subject"));
ticket.setBody(req.getParameter("body")); //3.处理文件上传
Part filePart = req.getPart("file1");
if(filePart != null && filePart.getSize() > 0){
Attachment attachment = this.processAttachment(filePart);
if(attachment != null)
ticket.addAttachments(attachment);
} int id;
synchronized (this) {
id = this.TICKET_ID_SEQUENCE++;
this.ticketDatabase.put(id, ticket);
} resp.sendRedirect("tickets?action=view&ticketId=" + id);
} private Attachment processAttachment(Part filePart) throws IOException{
InputStream inputStream = filePart.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int read;
final byte[] bytes = new byte[1024]; while((read = inputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, read);
} Attachment attachment = new Attachment();
attachment.setName(filePart.getSubmittedFileName());
attachment.setContents(outputStream.toByteArray()); return attachment;
} private void listTickets(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
request.setAttribute("ticketDatabase", this.ticketDatabase);
request.getRequestDispatcher("/WEB-INF/jsp/view/listTickets.jsp").forward(request, response);
} private void downloadAttachment(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String idString = req.getParameter("ticketId");
Ticket ticket = this.getTicket(idString, resp);
if(ticket == null)
return; String name = req.getParameter("attachment");
if(name == null){
resp.sendRedirect("tickets?action=view&tickedId=" + idString);
return;
} Attachment attachment = ticket.getAttachment(name);
if(attachment == null){
resp.sendRedirect("tickets?action=view&tickedId=" + idString);
return;
} // 强制浏览器询问用户是保存还是下载文件,而不是在浏览器打开该文件
resp.setHeader("Content-Disposition", "attachment; filename = " + attachment.getName());
// 设置内容类型是通用的、二进制内容类型,这样容器就不会使用字符编码对该数据进行处理
// 更加准确的应该使用附件的MIME内容类型
resp.setContentType("application/octet-stream"); // 使用ServletOutputStream将附件内容输出到响应中
ServletOutputStream stream = resp.getOutputStream();
stream.write(attachment.getContents());
} private void viewTicket(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
String idString = req.getParameter("ticketId");
Ticket ticket = this.getTicket(idString, resp);
if(ticket == null)
return; req.setAttribute("ticketId", idString);
req.setAttribute("ticket", ticket); RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/jsp/view/viewTicket.jsp");
dispatcher.forward(req, resp);
} private Ticket getTicket(String idString, HttpServletResponse resp) throws IOException{
if(idString == null || idString.length() == 0){
resp.sendRedirect("tickets");
return null;
}
try{
Ticket ticket = this.ticketDatabase.get(Integer.parseInt(idString));
if(ticket == null){
resp.sendRedirect("tickets");
return null;
}
return ticket;
}catch(Exception e){
resp.sendRedirect("tickets");
return null;
}
} private void showTicketForm(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/view/ticketForm.jsp");
dispatcher.forward(request, response);
}
}
运行结果:
空白的留言板:

创建留言:

单个留言的详细信息:

附件下载:

非空白的留言板:

分析:
1.j在jsp可以结合java和html,方便编写动态页面
2.在jsp中,可以做几乎所有java类可以完成的事情,这会带来不安全的操作。若jsp开发者不熟悉java,而他们无限制地在jsp中使用java,会带来安全隐患。
3.表示层(jsp用于开发表示层)需要和业务逻辑层、数据持久层分割。
4.在jsp中显示动态内容,应该尽量避免使用java代码,可以使用jsp标签库替代java代码
Servlet实践--留言板-v1的更多相关文章
- JSP简易留言板
写在前面 在上篇博文JSP内置对象中介绍JSP的9个内置对象的含义和常用方法,但都是比较理论的知识.今天为大家带来一个小应用,用application制作的简易留言板. 包括三个功能模块:留言提交.留 ...
- 欣欣的留言板项目====超级触动的dbUtil实现留言板
留言板管理系统 我的完成效果图: 提交后: 我的留言板基本架构如图: 创建留言板数据库: 刚开始我的前台主页中写留言信息表单: <body> <h1>留言板</h1> ...
- php实现简易留言板效果
首先是Index页面效果图 index.php <?php header('content-type:text/html;charset=utf-8'); date_default_timezo ...
- Operator 示例:使用 Redis 部署 PHP 留言板应用程序
「Kubernetes 官方示例:使用 Redis 部署 PHP 留言板应用程序」Operator 化. 源码仓库:https://github.com/jxlwqq/guestbook-operat ...
- AngularJs学习笔记(制作留言板)
原文地址:http://www.jmingzi.cn/?post=13 初学Anjularjs两天了,一边学一边写的留言板,只有一级回复嵌套.演示地址 这里总结一下学习的过程和笔记.另外,看看这篇文章 ...
- dd——留言板再加验证码功能
1.找到后台-核心-频道模型-自定义表单 2.然后点击增加新的自定义表单 diyid 这个,不管他,默认就好 自定义表单名称 这个的话,比如你要加个留言板还是投诉建议?写上去呗 数据表 这个不要碰, ...
- asp.net留言板项目源代码下载
HoverTree是一个asp.net开源项目,实现了留言板功能. 前台体验网址:http://hovertree.com/guestbook/ 后台请下载源代码安装. 默认用户名:keleyi 默认 ...
- html的留言板制作(js)
这次留言板运用到了最基础的localstorage的本地存储,展现的效果主要有: 1.编写留言2.留言前可以编辑自己的留言昵称.不足之处: 1.未能做出我喜欢的类似于网易的叠楼功能. 2.未能显示评论 ...
- 11月8日PHP练习《留言板》
一.要求 二.示例页面 三.网页代码及网页显示 1.denglu.php 登录页面 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tran ...
随机推荐
- HBase数据备份及恢复(导入导出)的常用方法
一.说明 随着HBase在重要的商业系统中应用的大量增加,许多企业需要通过对它们的HBase集群建立健壮的备份和故障恢复机制来保证它们的企业(数据)资产.备份Hbase时的难点是其待备份的数据集可能非 ...
- Codeforces Round #203 (Div. 2)B Resort
Resort Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d & %I64u Submit Stat ...
- Milking Time
Description Bessie is such a hard-working cow. In fact, she is so focused on maximizing her producti ...
- Increasing Speed Limits
Increasing Speed Limits Time Limit: 2000/10000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- 2016 湖南省省赛 Problem A: 2016
Problem A: 2016 Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 296 Solved: 171 Description 给出正整数 n ...
- Azkaban 2.5.0 搭建和一些小问题
安装环境: 系统环境: ubuntu-12.04.2-server-amd64 安装目录: /usr/local/ae/ankaban JDK 安装目录: export JAVA_HOME=/usr/ ...
- css超过一定长度显示省略号
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
- javascript创建css、js,onload触发callback兼容主流浏览器的实现
http://www.fantxi.com/blog/archives/load-css-js-callback/ 由于需要写个函数,既可以加载css,又可以加载js,所以对各主流浏览器对加载js.c ...
- Nginx-动态路由升级版
前几篇文章我们介绍了Nginx的配置.OpenResty安装配置.基于Redis的动态路由以及Nginx的监控. Nginx-OpenResty安装配置 Nginx配置详解 Nginx技术研究系列1- ...
- 一个非常有用的函数—COALESCE
很多人知道ISNULL函数,但是很少人知道Coalesce函数,人们会无意中使用到Coalesce函数,并且发现它比ISNULL更加强大,不用再像以前 IsNull 又 IsNull(SqlServe ...