2.3 Cookies

URL重写隐藏域仅适合保存无须跨越太多页面的信息。如果需要在多个页面间传递信息,则以上两种技术实现成本高昂,因为你不得不在每个页面都进行相应处理。幸运的是,Cookies技术可以帮助我们。
Cookies是一个很少的信息片段,可自动地在浏览器和Web服务器间交互,因此cookies可存储在多个页面间传递的信息。Cookie作为HTTP header的一部分,其传输由HTTP协议控制。此外,你可以控制cookies的有效时间。浏览器通常支持每个网站高达20个cookies
Cookies的问题在于用户可以通过改变其浏览器设置来拒绝接受cookies
要使用cookies,需要熟悉javax.servlet.http.Cookie 类以及HttpServletRequestHttpServletResponse两个接口。

创建Cookies

可以通过传递namevalue两个参数给Cookie 类的构造函数来创建一个cookies

1
Cookie cookie = new Cookie(name, value);

如下是一个创建语言选择的cookie示例:

1
Cookie languageSelectionCookie = new Cookie("language", "Italian");

Cookie属性

创建完一个Cookie对象后,你可以设置domainpathmaxAge属性。其中,maxAge 属性决定cookie何时过期

服务器如何发送Cookie

要将cookie发送到浏览器,需要调用 HttpServletResponseadd方法:

1
httpServletResponse.addCookie(cookie);

浏览器如何发送Cookie

浏览器在访问同一Web服务器时,会将之前收到的 cookie一并发送。 此外,Cookies也可以通过客户端的javascript脚本创建和删除。

服务器端如何读取浏览器提交的Cookie

服务端若要读取浏览器提交的cookie,可以通过 HttpServletRequest接口的getCookies方法,该方法返回一个Cookie数组,若没有Cookies则返回null.

查找Cookie

你需要遍历整个数组来查询某个特定名称的cookie。如下为查询名为maxRecordscookie的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取浏览器提交的所有Cookie数组
Cookie[] cookies = request.getCookies();
//引用,用来保存你想要得到的某个cookie对象
Cookie maxRecordsCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies)
{
//查找名称为maxRecords的cookie对象
if (cookie.getName().equals("maxRecords"))
{
//保存找到的这个cookie对象
maxRecordsCookie = cookie;
break;
}
}
}

目前,还没有类似于getCookieByName这样的方法来帮助简化工作。

删除Cookie

没有一个直接的方法来删除一个cookie,你只能创建一个同名的Cookie,并将maxAge属性设置为0,并添加到HttpServletResponse接口中,覆盖掉原来的Cookie即可。如下为删除一个名为userNamecookie代码:

1
2
3
Cookie cookie = new Cookie("userName", "");
cookie.setMaxAge(0);
response.addCookie(cookie);

demo

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package session.management.cookies;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
public class CookieTools
{
/**
* 从浏览器提交的Cookie中找到名称为name
* 的Cookie对象.
*
* @param request
* 请求.
* @param name
* 要查找的Cookie的名称.
* @return Cookie对象,如果找到该对象,
* 则返回该对象,没找到则返回null.
*/
public static Cookie findCookieByName(
HttpServletRequest request,String name)
{
Cookie cookies[] = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie;
}
}
return null;
}
}

设置Cookie的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "SetCookieByForm",
urlPatterns =
{"/setcookie"}
)
public class SetCookieByForm extends HttpServlet
{
private static final long serialVersionUID = 1L;
// 设置一个菜单栏
public static final String MENU =
"<div style='background:#e8e8e8;"
+ "padding:15px'>"
+ "<a href='setcookie'>Set Cookie</a>&nbsp;&nbsp;"
+ "<a href='showfruits'>Show Fruits</a>&nbsp;&nbsp;"
+ "<a href='showcss'>Show CSS</a>&nbsp;&nbsp;"
+ "</div>";
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
// 以UTF-8编码响应
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<html>\r\n"
+ "<head>\r\n"
+ "<title>Preference</title>\r\n"
+ "</head>\r\n"
+ "<body>"
+ MENU
+ "<form method=\"post\">\r\n"
+ "<hr>\r\n"
+ "<table>\r\n"
+ "<tbody>\r\n"
+ "<tr>\r\n"
+ "<td>设置字体大小: </td>\r\n"
+ "<td>\r\n"
+ "<select name=\"cookieFontSize\">\r\n"
+ "<option>large</option>\r\n"
+ "<option>x-large</option>\r\n"
+ "<option>xx-large</option>\r\n"
+ "</select>\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "</table><hr><table>\r\n"
+ "<tbody>\r\n"
+ "<tr>\r\n"
+ "<td>显示的水果数量:</td>\r\n"
+ "<td>\r\n"
+ "<select name=\"fruitNum\">\r\n");
for (int i = 0;
i < ShowFruitsByCookie.fruits.length;
i++)
{
writer.print(
"<option>" + (i + 1)
+ "</option>\r\n");
}
writer.print(
"</select>\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "<tr>\r\n"
+ "<td rowspan=\"2\">\r\n"
+ "<input type=\"submit\" value=\"Set\">\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "</tbody>\r\n"
+ "</table>\r\n"
+ "</form>\r\n"
+ "</body>\r\n"
+ "</html>");
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 以utf-8发送响应
response.setCharacterEncoding("utf-8");
// 读取表单提交的值
String fruitNum =
request.getParameter("fruitNum");
// 创建一个Cookie,设置为用户在表单中输入的值,
// 然后发送给浏览器
response.addCookie(
new Cookie("fruitNum", fruitNum));
// 获取表单提交的值
String cookieFontSize =
request.getParameter("cookieFontSize");
// 存储到Cookie中.
response.addCookie(
new Cookie("cookieFontSize", cookieFontSize));
// 响应post请求
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println(
"<html><head>"
+ "<title>Preference</title>"
+ "</head><body>"
+ MENU
+ "<ul><li>你想要显示 <strong>"
+ fruitNum
+ "</strong> 种水果。</li>"
+ "<li>你想要设置CSS选择器<strong>"
+".cookieStyle</strong>的字体大小为:<strong>"
+ cookieFontSize
+ "</strong></li></ul></body></html>");
}
}

这个Servlet会把表单输入转换为Cookie发送到浏览器,对应的URL为http://localhost:8080/SessionManagement/setcookie,
当浏览器再次访问http://localhost:8080/SessionManagement/这个Java项目下的资源时,浏览器会把之前服务器发给他的Cookie,再原封不动的提交给服务器。这样服务器就可以根据Cookie来进行响应的操作。
在上面代码中,服务器分别发送fruitNumcookieFontSize这两个cookie给浏览器,下面将再创建两个Servlet来使用者连个Cookie。
填写表单,如下所示:
这里有一张图片
然后点击Set按钮,提交表单,得到提示如下:
这里有一张图片

根据Cookie显示数组中的元素

ShowFruitsByCookie这个Servlet会根据fruitNum这个Cookie的值,来显示打印数组中的元素,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "ShowFruitByCookie",
urlPatterns =
{"/showfruits"}
)
public class ShowFruitsByCookie extends HttpServlet
{
private static final long serialVersionUID =
7275643908439277071L;
static String[] fruits = {
"苹果", "葡萄", "芒果", "草莓",
"荔枝", "香蕉", "乌梅", "菠萝",
"樱桃", "枇杷", "芒果", "橙子",
"桔子", "柠檬", "柚子", "杨梅",
"木瓜", "桂圆", "榴莲", "西瓜",
"石榴", "山楂", "椰子", "山竹",
"橄榄", "柿子","李子", "桑葚",
"红枣", "柑橘", "话梅", "乌梅",
"红提", "桃子", "甜瓜", "香瓜",
"荸荠","龙眼", "沙果", "佛手",
"芭乐", "蓝莓", "西梅", "释迦",
"黄皮", "莲雾", "杏子", "槟榔",
"酸橙", "黑莓", "栗子", "板栗",
"金桔", "山梨", "毛桃", "白果",
"银杏", "青梅", "蜜桃","脐橙",
"沙枣", "凤梨", "椰枣", "油桃",
"鳄梨", "酸莓", "蛇果", "鸭梨"};
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
Cookie fruitNumCookie =
CookieTools.findCookieByName(request, "fruitNum");
if (fruitNumCookie != null) {
// 取出Cookie中的值.
String cookieValue = fruitNumCookie.getValue();
int fruitNum = Integer.parseInt(cookieValue);
if (fruitNum <= fruits.length) {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print(
"<html>\r\n"
+ "<head>\r\n"
+ "<title>showFruits</title>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ SetCookieByForm.MENU
+ "为你显示 <strong>"
+ fruitNum
+ "</strong> 种水果,如下所示:\r\n"
+ "<ul>");
for (int i = 0; i < fruitNum; i++) {
writer.print("<li>" + fruits[i] + "</li>");
}
writer.println("</ul>\r\n"
+ "</body>\r\n" + "</html>");
}
}
}
}

点击工具条上的Show Fruits 超链接,效果如下:
这里有一张图片

根据Cookie设置CSS

SetCSSByCookie这个Servlet根据cookieFontSize这个Cookie中的值来来设置元素的CSS样式,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "SetCSSByCookie",
urlPatterns =
{"/showcss"}
)
public class SetCSSByCookie extends HttpServlet
{
private static final long serialVersionUID =
-8190275536448562340L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
Cookie cookieFontSize =
CookieTools.findCookieByName(request,
"cookieFontSize");
writer.print(
"<html>\r\n"
+ "<head>\r\n"
+ "<title>根据Cookie设置CSS</title>\r\n"
+ "<style>");
writer.print(".cookieStyle{");
if (cookieFontSize != null) {
writer.print("font-size: "
+ cookieFontSize.getValue() + ";");
}
writer.print("}");
writer.println(
"</style>\r\n"
+ "</head>\r\n"
+ "<body>"
+ SetCookieByForm.MENU);
writer.println(
"<div class=\"cookieStyle\">这句话的样式通过Cookie设置.</div>");
writer.println("</body>\r\n"
+ "</html>");
}
}

点击工具条上的Show CSS超链接,显示效果如下:
这里有一张图片

2.2 隐藏域

使用隐藏域来保持状态类似于URL重写技术,但不是将值附加到URL上,而是放到HTML表单的隐藏域中。当表单提交时,隐藏域的值也同时提交到服务器端。隐藏域技术只在网页有表单时有效。隐藏域技术相对于 URL重写的优势在于:没有字符数限制,同时无须额外的编码。但隐藏域技术同URL重写一样,不适合跨越多个界面。

Demo

完整代码

User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package session.management.hiddenfields;
public class User
{
//编号
String id;
//姓名
String name;
//所在地
String city;
//姓名
String sex;
//getter setter方法
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getCity()
{
return city;
}
public void setCity(String city)
{
this.city = city;
}
public String getSex()
{
return sex;
}
public void setSex(String sex)
{
this.sex = sex;
}
}

这里的User类用来存放我们要操作的数据.

UserServlet类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package session.management.hiddenfields;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
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(
name = "UserServlet",
// 这些URL都用当前这个Servlet来处理
urlPatterns =
{"/showUsers", "/editUser", "/updateUser", "/addUser"}
)
public class UserServlet extends HttpServlet
{
// 数据
ArrayList<User> userList;
private static final long serialVersionUID = 2L;
private User findUserById(String id)
{
for (User person : userList) {
if (person.getId().equals(id)) {
return person;
}
}
return null;
}
// 初始化数据
@Override
public void init() throws ServletException
{
userList = new ArrayList<User>();
User p1 = new User();
User p2 = new User();
p1.setId("1");
p1.setName("小明");
p1.setCity("南京");
p1.setSex("男");
userList.add(p1);
p2.setId("2");
p2.setName("小王");
p2.setCity("南京");
p2.setSex("男");
userList.add(p2);
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
// 用utf-8来发送数据
response.setCharacterEncoding("UTF-8");
// 让浏览器用utf-8解析
response.setHeader("Content-type", "text/html;charset=UTF-8");
// 获取调用带Servlet的请求所用的链接
String url = request.getRequestURI();
if (url.endsWith("/showUsers")) {
// 显示用户列表
showUserList(response);
} else if (url.endsWith("/editUser")) {
// 发送修改表单
sendEditForm(request, response);
} else if (url.endsWith("/addUser")) {
sendAddForm(request, response);
}
}
private void sendAddForm(HttpServletRequest request,
HttpServletResponse response)
{
String html = "<html><head><title>添加User</title><meta charset='utf-8'></head><body><h2>添加User</h2>\r\n"
+ "<form method='post' action='addUser'><table><tbody>"
+ "<tr><td style='text-align:right'>编号:</td><td><input type='text' name='id' value='"
+ (userList.size() + 1)
+ "' readonly='readonly' title='编号由系统指定,无法修改'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>姓名:</td><td><input type='text' name='name'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>城市:</td><td><input type='text' name='city'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>性别:</td><td><input type='text' name='sex'></td></tr>\r\n"
+ "<tr><td colspan='2' style='text-align:right'><input type='submit' value='add'></td></tr>\r\n"
+ "<tr><td colspan='2'><a href='showUsers'>返回用户列表</a></td></tr>"
+ "</tbody></table></form></body></html>";
responseHTML(response, html);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
// 设置请求体的编码,用utf-8来解析表单数据
request.setCharacterEncoding("UTF-8");
// 用utf-8编码来发送数据
response.setCharacterEncoding("UTF-8");
// 让浏览器用utf-8解析数据
response.setHeader("Content-type", "text/html;charset=UTF-8");
String url = request.getRequestURI();
if (url.endsWith("/updateUser")) {
updateUser(request, response);
} else if (url.endsWith("/addUser")) {
addUser(request, response);
}
}
private void addUser(HttpServletRequest request,
HttpServletResponse response)
{
String id = request.getParameter("id");
// 创建一个新用户
User user = findUserById(id);
if (user == null) {
// 创建一个新用户
user = new User();
user.setId(id);
user.setName(request.getParameter("name"));
user.setCity(request.getParameter("city"));
user.setSex(request.getParameter("sex"));
userList.add(user);
}
//请求重定向 显示主界面
try {
response.sendRedirect("showUsers");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void updateUser(HttpServletRequest request,
HttpServletResponse response)
{
String id = request.getParameter("id");
User person = findUserById(id);
if (person != null) {
person.setName(request.getParameter("name"));
person.setSex(request.getParameter("sex"));
person.setCity(request.getParameter("city"));
}
//请求重定向 显示主界面
try {
response.sendRedirect("showUsers");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void sendEditForm(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
// 获取URL中的id参数
String id = request.getParameter("id");
PrintWriter writer;
writer = response.getWriter();
User person = findUserById(id);
if (person != null) {
setEditFormByID(response, person);
} else {
writer.println("<h2>查无此人</h2>");
}
}
private void setEditFormByID(HttpServletResponse response, User person)
{
String htmlFirst = "<html>\r\n" + "<head>\r\n"
+ "<title>更新User</title>\r\n" + "</head>\r\n"
+ "<meta charset=\"utf-8\">\r\n" + "<body>\r\n"
+ "<h2>更新User</h2>\r\n"
+ "<form method='post' action='updateUser'>\r\n" + "<table>";
String htmlTrs = "<tr><td style='text-align:right'>姓名:</td><td><input name='name' value='"
+ person.getName() + "'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>城市:</td><td><input name='city' value='"
+ person.getCity() + "'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>性别:</td><td><input name='sex' value='"
+ person.getSex() + "'></td></tr>\r\n"
+ "<tr><td colspan='2' style='text-align:right'><input type='submit' value='Update'></td></tr>\r\n"
+ "<tr><td colspan='2'><a href='showUsers'>返回用户列表</a></td></tr>";
String htmlAfter = "</table>\r\n"
+ "<input type=\"hidden\" name=\"id\" value=\"" + person.getId()
+ "\">\r\n" + "</form>\r\n" + "</body>\r\n" + "</html>";
String html = htmlFirst + htmlTrs + htmlAfter;
responseHTML(response, html);
}
private void showUserList(HttpServletResponse response)
{
// 拼接HTML代码
String HTML = createShowPersonHTML();
// 响应
responseHTML(response, HTML);
}
private String createShowPersonHTML()
{
// 拼凑html代码
String htmlBefore = "<html>\r\n" + "<head>\r\n"
+ "<title>Person</title>\r\n" + "</head>\r\n" + "<body>\r\n"
+ "<h2>Users:</h2>\r\n" + "<table border=\"1\">\r\n"
+ "<tr><th>编号</th><th>姓名</th><th>城市</th><th>性别</th><th>操作</th></tr>";
String tr;
String htmlAfter = "<tr><td colspan=\"5\" style=\"text-align:right\"><a href=\"addUser\">添加用户</a></td></tr></table>\r\n"
+ "</body>\r\n" + "</html>";
StringBuffer sb = new StringBuffer(htmlBefore);
for (int i = 0, lenght = userList.size(); i < lenght; i++) {
// 从列表中取出一个对象
User person = userList.get(i);
tr = "<tr>\r\n" + "<td>" + person.getId() + "</td>\r\n" + "<td>"
+ person.getName() + "</td>\r\n" + "<td>" + person.getCity()
+ "</td>\r\n" + "<td>" + person.getSex() + "</td>\r\n"
+ "<td>\r\n" + "<a href=\"editUser?id=" + person.getId()
+ "\">编辑</a>\r\n" + "</td>\r\n" + "</tr>";
sb.append(tr);
}
sb.append(htmlAfter);
return sb.toString();
}
private void responseHTML(HttpServletResponse response, String HTML)
{
PrintWriter writer;
try {
writer = response.getWriter();
writer.write(HTML);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

代码详解

URL映射

1
2
3
4
5
6
@WebServlet(
name = "PersonServlet",
// 这些URL都用当前这个Servlet来处理
urlPatterns =
{"/showUsers", "/editUser", "/updateUser"}
)

这里为这个Servlet映射了三个URL,分别用于展示数据http://localhost:8080/SessionManagement/showUsers,修改数据,展示更新后的数据。

初始化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数据
ArrayList<User> userList;
private static final long serialVersionUID = 2L;
// 初始化数据
@Override
public void init() throws ServletException
{
userList = new ArrayList<User>();
User p1 = new User();
User p2 = new User();
p1.setId("1");
p1.setName("小明");
p1.setCity("南京");
p1.setSex("男");
userList.add(p1);
p2.setId("2");
p2.setName("小王");
p2.setCity("南京");
p2.setSex("男");
userList.add(p2);
}

这里首先声明了一个装有User对象的容器,当用户第一次请求页面时,Tomcat调用init()方法,init()方法,创建并初始化两个User对象,并把它们放到userList中。

工具方法

根据用户id获取该用户对象的引用
1
2
3
4
5
6
7
8
9
private User findUserById(String id)
{
for (User person : userList) {
if (person.getId().equals(id)) {
return person;
}
}
return null;
}

这个方法可以根据用户的id,从userList中查找User,如果没有将返回null.

输出HTML字符串到响应对象中
1
2
3
4
5
6
7
8
9
10
11
private void responseHTML(HttpServletResponse response, String HTML)
{
PrintWriter writer;
try {
writer = response.getWriter();
writer.write(HTML);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

显示所有用户数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
// 用utf-8来发送数据
response.setCharacterEncoding("UTF-8");
// 让浏览器用utf-8解析
response.setHeader("Content-type", "text/html;charset=UTF-8");
// 获取调用带Servlet的请求所用的链接
String url = request.getRequestURI();
if (url.endsWith("/showUsers")) {
// 显示用户列表
showUserList(response);
} else if (url.endsWith("/editUser")) {
// 发送修改用户表单
sendEditForm(request, response);
} else if (url.endsWith("/addUser")) {
//发送添加用户表单
sendAddForm(request, response);
}
}
private void showUserList(HttpServletResponse response)
{
// 拼接HTML代码
String HTML = createShowPersonHTML();
// 响应
responseHTML(response, HTML);
}
private String createShowPersonHTML()
{
// 拼凑html代码
String htmlBefore = "<html>\r\n" + "<head>\r\n"
+ "<title>Person</title>\r\n" + "</head>\r\n" + "<body>\r\n"
+ "<h2>Users:</h2>\r\n" + "<table border=\"1\">\r\n"
+ "<tr><th>编号</th><th>姓名</th><th>城市</th><th>性别</th><th>操作</th></tr>";
String tr;
String htmlAfter = "<tr><td colspan=\"5\" style=\"text-align:right\"><a href=\"addUser\">添加用户</a></td></tr></table>\r\n"
+ "</body>\r\n" + "</html>";

StringBuffer sb = new StringBuffer(htmlBefore);
for (int i = 0, lenght = userList.size(); i < lenght; i++) {
// 从列表中取出一个对象
User person = userList.get(i);
tr = "<tr>\r\n" + "<td>" + person.getId() + "</td>\r\n" + "<td>"
+ person.getName() + "</td>\r\n" + "<td>" + person.getCity()
+ "</td>\r\n" + "<td>" + person.getSex() + "</td>\r\n"
+ "<td>\r\n" + "<a href=\"editUser?id=" + person.getId()
+ "\">编辑</a>\r\n" + "</td>\r\n" + "</tr>";
sb.append(tr);
}
sb.append(htmlAfter);
return sb.toString();
}

显示效果如下:
这里有一张图片
其中超链接HTML代码如下:

1
2
3
<a href="editUser?id=1">编辑</a>
<a href="editUser?id=2">编辑</a>
<a href="addUser">添加用户</a>

这是前面介绍的URL重写技术,因为前面路径省略,浏览器会发给父路径http://localhost:8080/SessionManagement/,最终得到URL为:http://localhost:8080/SessionManagement/editUser?id=1,点击这个URL可以可以进入修改页面。

修改用户数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void sendEditForm(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
// 获取URL中的id参数
String id = request.getParameter("id");
PrintWriter writer;
writer = response.getWriter();
User person = findUserById(id);
if (person != null) {
setEditFormByID(response, person);
} else {
writer.println("<h2>查无此人</h2>");
}
}
private void setEditFormByID(HttpServletResponse response, User person)
{
String htmlFirst = "<html>\r\n" + "<head>\r\n"
+ "<title>更新User</title>\r\n" + "</head>\r\n"
+ "<meta charset=\"utf-8\">\r\n" + "<body>\r\n"
+ "<h2>更新User</h2>\r\n"
+ "<form method='post' action='updateUser'>\r\n" + "<table>";
String htmlTrs = "<tr><td style='text-align:right'>姓名:</td><td><input name='name' value='"
+ person.getName() + "'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>城市:</td><td><input name='city' value='"
+ person.getCity() + "'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>性别:</td><td><input name='sex' value='"
+ person.getSex() + "'></td></tr>\r\n"
+ "<tr><td colspan='2' style='text-align:right'><input type='submit' value='Update'></td></tr>\r\n"
+ "<tr><td colspan='2'><a href='showUsers'>返回用户列表</a></td></tr>";
String htmlAfter = "</table>\r\n"
+ "<input type=\"hidden\" name=\"id\" value=\"" + person.getId()
+ "\">\r\n" + "</form>\r\n" + "</body>\r\n" + "</html>";
String html = htmlFirst + htmlTrs + htmlAfter;
responseHTML(response, html);
}

假设用户点击的是第一个超链接,也就是通过http://localhost:8080/SessionManagement/editUser?id=1这个URL访问。
此时,Tomcat将调用doGet方法,doGet方法调用sendEditForm方法,该根据Id找出一个User对象,然后doGet调用setEditFormByID这个方法,把User对象中的数据填入表单,封装成HTML
,发送给浏览器显示,用户在浏览器中修改表单中的数据即可完成修改,然后点击update按钮进行更新。如下图所示:
这里有一张图片
在服务器发送HTML的时候在表单之中偷偷塞了一个如下的隐藏域:

1
<input type="hidden" name="id" value="1">

这个隐藏域存放用户的id信息,这样当用户提交表单的时候,服务器就可根据这id信息,知道要修改的是哪个User对象的数据。
还是有就是表达的两个属性:

1
<form method="post" action="updateUser">

这表明提交方法为post方法,提交给/updateUser这个URL。提交后Tomcat将调用/updateUser这个URL对应的Servlet的doPost()方法。

服务器端更新数据

假设用户在表单中修改好了数据,然后点击update按键提交,调用的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
// 设置请求体的编码,用utf-8来解析表单数据
request.setCharacterEncoding("UTF-8");
// 用utf-8编码来发送数据
response.setCharacterEncoding("UTF-8");

// 让浏览器用utf-8解析数据
response.setHeader("Content-type", "text/html;charset=UTF-8");
String url = request.getRequestURI();
if (url.endsWith("/updateUser")) {
updateUser(request, response);
} else if (url.endsWith("/addUser")) {
addUser(request, response);
}
}
private void updateUser(HttpServletRequest request,
HttpServletResponse response)
{
String id = request.getParameter("id");
User person = findUserById(id);
if (person != null) {
person.setName(request.getParameter("name"));
person.setSex(request.getParameter("sex"));
person.setCity(request.getParameter("city"));
}
//请求重定向 显示主界面
try {
response.sendRedirect("showUsers");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

因为表单提交方式为post,所以Tomcat调用doPost()方法来做出响应。doPost()中做了写编码相关的设置,然后调用updateUser方法,updateUser这个方法,先获取id参数。然后根据id参数找到要修改的User对象。使用文本框中修改过的值更新这个对象的内容。这样就完成了更新数据的操作,最后请求重定向到showUsers这个URL上,也就是显示主界面
显示效果如下:
这里有一张图片

添加用户

在主界面中点击最下方的超链接<a href="addUser">添加用户</a>可以添加一个用户,浏览器请求http://localhost:8080/SessionManagement/addUser这个URL响应,这是一个get请求.Tomcat调用doGet方法响应,doGet方法根据请求的URL,调用sendAddForm方法,这个方法发给浏览器一个表单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void sendAddForm(HttpServletRequest request,
HttpServletResponse response)
{
String html = "<html><head><title>添加User</title><meta charset='utf-8'></head><body><h2>添加User</h2>\r\n"
+ "<form method='post' action='addUser'><table><tbody>"
+ "<tr><td style='text-align:right'>编号:</td><td><input type='text' name='id' value='"
+ (userList.size() + 1)
+ "' readonly='readonly' title='编号由系统指定,无法修改'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>姓名:</td><td><input type='text' name='name'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>城市:</td><td><input type='text' name='city'></td></tr>\r\n"
+ "<tr><td style='text-align:right'>性别:</td><td><input type='text' name='sex'></td></tr>\r\n"
+ "<tr><td colspan='2' style='text-align:right'><input type='submit' value='add'></td></tr>\r\n"
+ "<tr><td colspan='2'><a href='showUsers'>返回用户列表</a></td></tr>"
+ "</tbody></table></form></body></html>";
responseHTML(response, html);
}

需要注意的是id信息,我这里放在隐藏域中:

1
<input type="hidden" name="id" value="3">

需要注意的是表单的属性:

1
<form method='post' action='addUser'>

这表明表单将会发送给addUser这个URL,虽然还是调用的这个URL,但是这次是post请求,而不是get请求。
填写表单如下:
这里有一张图片
所以Tomcat会调用doPost方法来响应。doPost根据请求中的URL,调用addUser方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void addUser(HttpServletRequest request,
HttpServletResponse response)
{
String id = request.getParameter("id");
// 创建一个新用户
User user = findUserById(id);
if (user == null) {
// 创建一个新用户
user = new User();
user.setId(id);
user.setName(request.getParameter("name"));
user.setCity(request.getParameter("city"));
user.setSex(request.getParameter("sex"));
userList.add(user);
}
// 请求重定向 显示主界面
try {
response.sendRedirect("showUsers");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

这个方法,把表单中提交的数据转换成User对象,然后添加到userList中保存。最后重定向到showUser这个URL去显示添加的效果,如下所示:
这里有一张图片

小结

这个demo就到这里了,本来我还想搞个删除的功能,但是懒得搞了,先这样。

URL重写

URL重写是一种会话跟踪技术,它将一个或多个键值对 添加到URL的查询字符串中,每个键值对 通常为 key=value形式,格式如下所示:

1
url?key_1=value_1&key_2=value_2...&key_n=value_n

注意,URL键值对之间用问号?来分割,键值对之间用单与符号&分割。
URL重写适合于在少数URL之间传递信息的情况下,它有如下限制:

  • URL在某些浏览器上最大长度为2000字符,所以URL重写可能无法传递大量数据。
  • 如果要传递信息到下一个资源,需要将信息插入到URL的查询字符串中,这种情况适用于动态页面之间传值,而不适用于静态页面之间传值;
  • URL重写需要在服务端上完成,所有的链接都必须带值,因此当一个页面存在很多链接时,其处理过程会是一个不小的挑战;
  • 对应信息中的某些字符,例如空格、单与符号&和问号?等必须用base64编码,而编码和解码会增加开销;
  • URL的内容会显示在浏览器的地址栏上,所以URL重写不适合用来传递敏感信息。

所以,**URL重写仅适合于在少量页面间 传递不敏感信息的情况。**
下面来写个Demo,实用URL重写在同一个页面中传递信息。

Demo

项目目录

穿件一个名为SessionManagement的动态Java Web项目,然后创建项目结果如下所示:
这里有一张图片

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package session.management;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//配置Servlet映射到URL
@WebServlet(
name = "RewriteURL",
// 为一个servlet匹配多个URL
urlPatterns ={"/test"}
)
public class RewriteURL extends HttpServlet
{
private static final long serialVersionUID = 1L;
private ArrayList<String> fruits = null;
private ArrayList<String> vegetables = null;
// 初始化数据
@Override
public void init() throws ServletException
{
fruits = new ArrayList<String>(5);
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("草莓");
fruits.add("西瓜");
fruits.add("龙眼");
vegetables = new ArrayList<String>(5);
vegetables.add("白菜");
vegetables.add("紫菜");
vegetables.add("菠菜");
vegetables.add("南瓜");
vegetables.add("冬瓜");
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 获取查询字符串
String choice = request.getParameter("choice");
//
if (choice == null) {
showIndex(response);
} else if ("fruits".equals(choice)) {
//如果用户选择了水果的显示水果
showFruits(response);
} else if ("vegetables".equals(choice)) {
//如果用户选择了蔬菜的显示蔬菜
showVegetables(response);
}
}
private void showIndex(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String htmlResponse =
"<html>\r\n" +
" <head>\r\n"+
" <meta charset=\"utf-8\">\r\n"+
" <title>测试URL重写</title>\r\n"+
" </head>\r\n"+
" <body>\r\n"+
" <hr>\r\n" +
" <br><br>"+
" <hr>\r\n"+
" <a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
" <a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
" </body>\r\n" +
"</html>";
// 输出响应
writer.write(htmlResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
private void showFruits(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
//因为响应带有中文,设置编码格式为中文,
//不然会出现中文乱码??????这种情况。
response.setCharacterEncoding("UTF-8");
PrintWriter writer;
try {
// 获取响应输出
writer = response.getWriter();
// 生成水果列表
String ul = "";
for (String string : fruits) {
ul += "<li>" + string + "</li>";
}
//拼接成完整的HTML内容
String responseHTML =
"<html>\r\n" +
" <head>\r\n"+
" <meta charset=\"utf-8\">\r\n"+
" <title>测试URL重写</title>\r\n" +
" </head>\r\n"+
" <body>\r\n" +
" <a href=\"test\">返回首页</a>"+
" <hr>\r\n" + ul +
" <hr>\r\n"+
" <a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
" <a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
" </body>\r\n" +
"</html>";
// 输出响应对象中
writer.write(responseHTML);
//Servlet容器被把响应对象中的响应发给浏览器,我们不用管
} catch (IOException e) {
e.printStackTrace();
}
}
private void showVegetables(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String ul = "";
for (String string : vegetables) {
ul += "<li>" + string + "</li>";
}
String responseHTML =
"<html>\r\n" +
" <head>\r\n"+
" <meta charset=\"utf-8\">\r\n"+
" <title>测试URL重写</title>\r\n" +
" </head>\r\n"+
" <body>\r\n" +
" <a href=\"test\">返回首页</a>"+
" <hr>\r\n" + ul +
" <hr>\r\n"+
" <a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
" <a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
" </body>\r\n" +
"</html>";
// 输出响应
writer.write(responseHTML);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

代码详解

URL映射

Servlet中的URL映射的注解如下所示:

1
2
3
4
5
6
//配置Servlet映射到URL
@WebServlet(
name = "RewriteURL",
// 为一个servlet匹配多个URL
urlPatterns ={"/test"}
)

name表示当前session.management.RewriteURL这个Servlet的部署名。
urlPatterns表示该ServletURL地址,这个是个数字形式,同一个Servlet可以设置不同的URL地址.
映射好URL后,后面就可以通过http://localhost:8080/项目名称/test这样的格式来访问这个Servlet,这里是:
http://localhost:8080/SessionManagement/test

初始化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private ArrayList<String> fruits = null;
private ArrayList<String> vegetables = null;
// 初始化数据
@Override
public void init() throws ServletException
{
fruits = new ArrayList<String>(5);
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("草莓");
fruits.add("西瓜");
fruits.add("龙眼");
vegetables = new ArrayList<String>(5);
vegetables.add("白菜");
vegetables.add("紫菜");
vegetables.add("菠菜");
vegetables.add("南瓜");
vegetables.add("冬瓜");
}

在这个Servlet中有两个成员变量fruitsvegetables用于存放要展示的数据。
当用户第一次通过URL访问到该Servelet时,Web容器(Tomcat)会调用init()方法来初始化数据。在这里,init()方法用来初始化成员变量的数据。

响应get请求 覆盖doGet方法

当直接通过http://localhost:8080/SessionManagement/test这个URL访问的时候,Tomcat会调用init()方法来初始化,初始化只进行一次.后面不再调用init()方法.然后调用doGet()方法来响应请求。doGet方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 获取查询字符串
String choice = request.getParameter("choice");
//
if (choice == null) {
showIndex(response);
} else if ("fruits".equals(choice)) {
//如果用户选择了水果的显示水果
showFruits(response);
} else if ("vegetables".equals(choice)) {
//如果用户选择了蔬菜的显示蔬菜
showVegetables(response);
}
}

doGet方法会获取请求中名称为choice的参数值。然后根据参数值的不同来调用不同的方法进行响应。
当我们请求http://localhost:8080/SessionManagement/test对应的Servlet时,URL中没有带查询字符串,所以不存在choice这个参数。request.getParameter("choice");返回null.这样就会调用showIndex(response);这个方法来响应。

返回初始界面

showIndex()方法如下,所谓的响应,就是往响应对象里写一些字符串。Web容器会把响应对象中的这些字符串处理一下然后发送给浏览器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void showIndex(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String htmlResponse =
"<html>\r\n" +
" <head>\r\n"+
" <meta charset=\"utf-8\">\r\n"+
" <title>测试URL重写</title>\r\n" +
" </head>\r\n"+
" <body>\r\n"+
" <hr>\r\n" +
" <br><br>"+
" <hr>\r\n"+
" <a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
" <a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
" </body>\r\n" +
"</html>";
// 输出响应
writer.write(htmlResponse);
} catch (IOException e) {
e.printStackTrace();
}
}

此时浏览器显示如下:
这里有一张图片
这个页面有两个超链接,如下所示:

1
2
<a href="?choice=fruits">显示水果</a>
<a href="?choice=vegetables">显示蔬菜</a>

响应重写的URL

在刚才的页面中,显示水果的URL为?choice=fruits这是个查询字符串,查询字符串中有一个名为choice的参数,参数值为fruits
如果点击这超链接,浏览器会再次发送一个HTTP请求给服务器。
这里URL中地址缺省,浏览器会默认发给当前的地址。也就是在http://localhost:8080/SessionManagement/test这个地址后面加上查询字符串,得到的URL为:http://localhost:8080/SessionManagement/test?choice=fruits,这相当于重写了URL.

虽然,这个时候请求的Servlet还是http://localhost:8080/SessionManagement/test这个URL对应的Servlet.但是这回跟第一次请求时的情况是不一样的,第一次没有带请求参数,这次带了请求参数。Tomcat还是会调用doGet来生成响应。
不过这次因为带了参数choice=fruits,doGet方法会调用showFruits方法来生成响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void showFruits(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
//因为响应带有中文,设置编码格式为中文,不然会出现中文乱码??????这种情况。
response.setCharacterEncoding("UTF-8");
PrintWriter writer;
try {
// 获取响应输出
writer = response.getWriter();
// 生成水果列表
String ul = "";
for (String string : fruits) {
ul += "<li>" + string + "</li>";
}
//拼接成完整的HTML内容
String responseHTML =
"<html>\r\n" +
" <head>\r\n"+
" <meta charset=\"utf-8\">\r\n"+
" <title>测试URL重写</title>\r\n" +
" </head>\r\n"+
" <body>\r\n" +
" <hr>\r\n" + ul +
" <hr>\r\n"+
" <a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
" <a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
" </body>\r\n" +
"</html>";
// 输出响应对象中
writer.write(responseHTML);
//Servlet容器被把响应对象中的响应发给浏览器,我们不用管
} catch (IOException e) {
e.printStackTrace();
}
}

这个方法会把水果列表中的数据输出。如下所示:
这里有一张图片
同理如果此时点击显示蔬菜链接我们就可以显示蔬菜。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div onchange="show();">
<input type="radio" name="fruits" value="Apple" checked="checked" />Apple
<br>
<input type="radio" name="fruits" value="Banana" />Banana
<br>
<input type="radio" name="fruits" value="Strawberry" />Strawberry
<br>
<input type="radio" name="fruits" value="watermelon" />watermelon
<br>
<input type="text" id="show">
<br>
</div>
<script>
function show() {
document.getElementById('show').value = '你选择了: ' + getRadioValueByName('fruits');
}
// 根据单选按钮的name属性来获取这一组单选按钮的值
function getRadioValueByName(name) {
var value = "";
//某个单选按钮组
var radio = document.getElementsByName(name);
//遍历该组的单选按钮
for (var i = 0; i < radio.length; i++) {
//判断用户选了哪个按钮
if (radio[i].checked == true) {
//找到
value = radio[i].value;
//不用找了
break;
}
}
//返回选择的值
return value;
}
</script>

运行效果

Apple
Banana
Strawberry
watermelon

给所有英文单词添加markdown代码段标记

手动加麻烦,下面写程序来实现:

实现

如果是单纯的匹配英文的话使用(\w+)来匹配即可,但是这样会匹配到数字,我不希望匹配到数字,所以使用([a-zA-Z]\w+)来匹配,然后使用

1
`$1`

来替换,这样所有英文前面都加入了代码段标记了.这样好像能达到我的要求.但是又引入新的问题,对于已经添加了代码段标记单词:

1
`URL`

将会被替换成:

1
``URL``

这样显然不好,为此匹配正则需要修改,不管英文是否加了代码段标记,我都匹配,然后替换。如下所示:

匹配正则

1
`?([a-zA-Z]\w+)`?

替换正则

1
`$1`

批处理删除目录下的所有文件

遍历当前目录树下的所有文件

1
for /r . %i in (*.md) do echo %i

对上述代码,修改一下就可删除掉这些文件,如下所示。

删除当前目录树下的所有markdown文件

1
for /r . %i in (*.md) do del "%i"

但这样不好的地方就是目录没有删除掉,会留下大量的空目录。

遍历某个目录下的所有目录

1
for /d %i in (E:\Blog\blog5MD\C语言\*) do @echo %i

运行结果:

1
2
3
4
5
6
7
E:\Blog\blog5MD\C语言>for /d %i in (E:\Blog\blog5MD\C语言\*) do @echo %i
E:\Blog\blog5MD\C语言\Debug
E:\Blog\blog5MD\C语言\代码规范
E:\Blog\blog5MD\C语言\排序
E:\Blog\blog5MD\C语言\文件
E:\Blog\blog5MD\C语言\结构体
E:\Blog\blog5MD\C语言>

这样就好办了,我只要删除掉这些遍历出来的目录树即可。

1
for /d %i in (E:\Blog\blog5MD\C语言\*) do @rmdir /s %i

运行结果:

1
2
3
4
5
6
7
8
E:\Blog\blog5MD\C语言>for /d %i in (E:\Blog\blog5MD\C语言\*) do @rmdir /s %i
E:\Blog\blog5MD\C语言\Debug, 是否确认(Y/N)? y
E:\Blog\blog5MD\C语言\代码规范, 是否确认(Y/N)? y
E:\Blog\blog5MD\C语言\排序, 是否确认(Y/N)? y
E:\Blog\blog5MD\C语言\文件, 是否确认(Y/N)? y
E:\Blog\blog5MD\C语言\结构体, 是否确认(Y/N)? y

E:\Blog\blog5MD\C语言>

可以看到这样是没有问题的,接下来加上/q参数让删除目录树命令rmdir不询问即可。

1
for /d %i in (E:\Blog\blog5MD\C语言\*) do @rmdir /q /s %i

这样就删除掉了E:\Blog\blog5MD\C语言\这个目录下的所有目录树,但是这个目录E:\Blog\blog5MD\C语言\下的文件就不会被删掉,因为我们只遍历目录,然后删除目录,文件还没有操作。删除文件也简单,直接使用del命令即可。

最终代码

删除E:\Blog\blog5MD\C语言目录下的所有markdown文件。

1
2
for /d %%i in (E:\Blog\blog5MD\C语言\*) do @rmdir /q /s %%i
del del E:\Blog\blog5MD\C语言\*.md

匹配一个中文

匹配中文要使用Unicode代码,一个中文的范围,如下所示:

1
[\u4e00-\u9fa5]

匹配一个英文字母:[a-zA-Z]
匹配一个数字:[0-9]

应用 删除中文之间多余的空格

问题描述

例如有下面的文本:

1
2
3
4
5
6
Servlet和JSP是开发Java Web应用程序的两种基本技术。
Spring MVC是Spring框架中用于Web应用快速开 发的一个模块,是当今最流行的Web开发框架之一。
本书是Servlet、JSP和Spring MVC的学习指南。
全书内容分为两个部分,第一部分主要介绍Servlet和JSP基础知识和技术,包括第1章至第15章;第2部分主要介绍Spring MVC,包括第16章至第24章。
最后,附录部 分给出了Tomcat安装和配置指导,还介绍了Servlet and JSP注解以及SSL证书。
本书内容充实、讲解清晰,非常适合Web开发者尤 其是基于Java的Web应用开发者阅读。

要求去掉开 发,部 分,尤 其,等等这些中文之间的空格.

匹配正则

1
([\u4e00-\u9fa5])\s+([\u4e00-\u9fa5])

替换正则

1
$1$2

匹配效果

这里有一张图片

java方法

1
2
3
4
public static String deletSpacesInChinese(String text)
{
return text.replaceAll("([\\u4e00-\\u9fa5])\\s+([\\u4e00-\\u9fa5])", "$1$2");
}

参考资料

https://www.jb51.net/article/98373.htm

选择图标

http://www.fontawesome.com.cn/faicons/这个地方搜索一个你喜欢的图标.
这里有一张图片
然后点击对应图标,这样会打开一个网页.然后代码块中的复制源码按钮即可得到可使用的代码.
这里有一张图片
复制的话用到flash,如果用不了flash,自己拼接也行.只要知道这个图标等名字即可.使用格式为<i class="fa fa-图标名称" aria-hidden="true"></i>.

NexT主题中使用

NexT(hexo)主题中,是可以直接使用这些图标的

实例

向上箭头图标

例如我想使用arrow-up这个图标,只要这样写即可:

1
<i class="fa fa-arrow-up" aria-hidden="true"></i>

显示效果:

github图标

例如github的图标:

1
<i class="fa fa-github" aria-hidden="true"></i>

显示效果:

设置图标颜色

只要把这个图标(<i>标签),放在一个<font>标签中,然后设置font标签的颜色即可,例如把火狐图标的颜色设置为红色:

1
<font color="red"><i class="fa fa-firefox" aria-hidden="true"></i></font>

显示效果:
当然也可以直接在i标签上设置样式:

1
<i class="fa fa-firefox" aria-hidden="true" style="color:#1fa67a;"></i>

显示效果:

参考资料

1开始使用:http://www.fontawesome.com.cn/get-started/
2使用案例:http://www.fontawesome.com.cn/examples/
3可访问性:http://www.fontawesome.com.cn/accessibility/

讯飞语音合成发音不准确怎么解决

替换要朗读文本中的容易读错的字符

首先讯飞语音合成我们是无法控制的,我们能控制的之后是自己传给他的文本。为了朗读效果,我这里通过替换掉一些容易读错的字符,加入空白符等方式,来提高合成的效果。

点号读错的问题

问题描述

例如java.lang.Object,我们读成java点lang点Object。但是发音人不一定会把.读成

解决方案

合成之前先对字符串进行替换

  • 对于java包名(或类全名),例如:java.lang.Object先自行替换成java点lang点Object
  • 对于文章标题,如5.5.5 XXXXX先自行替换成5点5点5 XXXXX

替换方法

首先通过正则表达式进行匹配,然后匹配到的文本中的点号.替换成中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static String replaceTitle(String text)
{
Pattern titlePattern = Pattern.compile("\\w+(?:(\\.)(?:\\w+))+");
Matcher titleMatcher = titlePattern.matcher(text);
StringBuffer sb = new StringBuffer();
String line = null;
boolean titleFind = false;
// find()方法向前查找匹配的子串
while ((titleFind = titleMatcher.find()))
{
if (titleFind)
{
// 获取位置在匹配子串之前的文本strbefore,获取匹配的子串strmacther,然后把匹配的子串strmacther替换为replacement,
// 然后(strbefore+replacement)追加到StringBuffer中
line = titleMatcher.group();
line = line.replaceAll("\\.", "点");
titleMatcher.appendReplacement(sb, line);
}
}
// 把文本中没有匹配的剩下的文本也加入到StringBuffer中
titleMatcher.appendTail(sb);
return sb.toString();
}

中英混合英文读不清楚的问题

问题描述

中文和英文混合的时候,当从中文读到英文然后读到中文时,发音人可能会把英文读错,经过测试之后.

在英文签名加上空格

经过我的反复实验,发现在每个英文前面加上一个空格,这个时候英文朗读会比较清晰.

替换方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static String replaceEnglish(String text)
{
Pattern englishPattern = Pattern.compile("[a-zA-Z]+");
Matcher englishMatcher = englishPattern.matcher(text);
StringBuffer sb = new StringBuffer();
String line = null;
// find()方法向前查找匹配的子串
boolean englishFind = false;
while ((englishFind = englishMatcher.find()))
{
if (englishFind)
{
// 获取位置在匹配子串之前的文本strbefore,获取匹配的子串strmacther,然后把匹配的子串strmacther替换为replacement,
// 然后(strbefore+replacement)追加到StringBuffer中
// line = "`".concat(englishMatcher.group().concat("`"));
// line = "`".concat(englishMatcher.group());//这个效果还行
line = " ".concat(englishMatcher.group());//这个效果也还行
englishMatcher.appendReplacement(sb, line);
}
}
// 把文本中没有匹配的剩下的文本也加入到StringBuffer中
englishMatcher.appendTail(sb);
return sb.toString();
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package replace;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Replace
{
public static String replaceTitle(String text)
{
Pattern titlePattern = Pattern.compile("\\w+(?:(\\.)(?:\\w+))+");
Matcher titleMatcher = titlePattern.matcher(text);
StringBuffer sb = new StringBuffer();
String line = null;
boolean titleFind = false;
// find()方法向前查找匹配的子串
while ((titleFind = titleMatcher.find()))
{
if (titleFind)
{
// 获取位置在匹配子串之前的文本strbefore,获取匹配的子串strmacther,然后把匹配的子串strmacther替换为replacement,
// 然后(strbefore+replacement)追加到StringBuffer中
line = titleMatcher.group();
line = line.replaceAll("\\.", "点");
titleMatcher.appendReplacement(sb, line);
}
}
// 把文本中没有匹配的剩下的文本也加入到StringBuffer中
titleMatcher.appendTail(sb);
return sb.toString();
}
public static String replaceEnglish(String text)
{
Pattern englishPattern = Pattern.compile("[a-zA-Z]+");
Matcher englishMatcher = englishPattern.matcher(text);
StringBuffer sb = new StringBuffer();
String line = null;
// find()方法向前查找匹配的子串
boolean englishFind = false;
while ((englishFind = englishMatcher.find()))
{
if (englishFind)
{
// 获取位置在匹配子串之前的文本strbefore,获取匹配的子串strmacther,然后把匹配的子串strmacther替换为replacement,
// 然后(strbefore+replacement)追加到StringBuffer中
// line = "`".concat(englishMatcher.group().concat("`"));
// line = "`".concat(englishMatcher.group());//这个效果还行
line = " ".concat(englishMatcher.group());//这个效果也还行
englishMatcher.appendReplacement(sb, line);
}
}
// 把文本中没有匹配的剩下的文本也加入到StringBuffer中
englishMatcher.appendTail(sb);
return sb.toString();
}
public static void main(String[] args)
{
String text = "5.5.2 构造器重载";
// StringBuffer sb=new StringBuffer(text);
// replaceTitle(text);
// System.out.println(replaceTitle(text));
// String text = "java.lang.Object";
System.out.println(replaceTitle(text));
}
}

手动解决

合成音频后,肯定还是有不理想的地方,通过audition等音频编辑软件,做进一步的优化。

问题描述

讯飞语音合成有多个发音人,如何选择一个合适的发音人呢?我的解决方法是,选择符合要求的,然后每个都听一遍.最后选出一个合适的.

我的要求

我是用来朗读技术文章的,文章中要英文也有中文,所以我要选择支持中英文的发音人。发音人列表
这里有一张图片
符合我条件的发音人如下:

名称 属性 语言 参数名称 引擎参数 备注
小燕 青年女声 中英文(普通话) xiaoyan 默认
小宇 青年男声 中英文(普通话) xiaoyu
小研 青年女声 中英文(普通话) vixy
小琪 青年女声 中英文(普通话) vixq xiaoqi
小峰 青年男声 中英文(普通话) vixf

无法直接循环下载

下面写个demo,要求输入一段文字,然后使用上述的多个发音人分别来合成.下载下来我再一个个的听,选出一个英文清晰,阅读节奏合适的发音人.
因为要批量下载,我首先想到的是,循环调用下载方法不就行了如下所示:

1
2
3
4
5
6
7
8
for (......)
{
// 3.创建一个SpeechSynthesizer对象
SpeechSynthesizer mTts = SpeechSynthesizer.createSynthesizer();
// 4.设置合成参数
......
mTts.synthesizeToUri(Input, fileName, synthesizeToUriListener);
}

但是我失败了,这样并不会合成多个音频。只会合成一个。经过测试之后,我发现,调用完毕下载方法后,主线程就结束了.
……最后经过一番思索解决方案如下.

创建下载线程

线程的知识我有点忘了,合成音频的方法最后会回调onSynthesizeCompleted这个方法,我在下载线程DemoListRunable中设置一个isEnd标记,当开始下载一个音频的时候,把isEnd设置为flase,下载完毕后设置为true.
在主线程中,通过不断的来查询这个标记,从而知道是否下载完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package demo;
import com.iflytek.cloud.speech.SpeechConstant;
import com.iflytek.cloud.speech.SpeechError;
import com.iflytek.cloud.speech.SpeechSynthesizer;
import com.iflytek.cloud.speech.SynthesizeToUriListener;
public class DemoListRunable implements Runnable
{
public static boolean isEnd=false;
String fileName;
String Input;
String voiceName;
public DemoListRunable(String voiceName, String fileName, String Input)
{
this.voiceName = voiceName;
this.fileName = fileName;
this.Input = Input;
}
public void run()
{
// 3.创建一个SpeechSynthesizer对象
SpeechSynthesizer mTts = SpeechSynthesizer.createSynthesizer();
// 4.合成参数设置,详见《MSC Reference Manual》SpeechSynthesizer 类
// 设置发音人
// 小燕 青年女声 中英文(普通话) xiaoyan 默认
// 小研 青年女声 中英文(普通话) vixy
// 小琪 青年女声 中英文(普通话) vixq xiaoqi
// 小宇 青年男声 中英文(普通话) xiaoyu
// 小峰 青年男声 中英文(普通话) vixf
// 4-1 设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME, voiceName);//
// 设置要下载的文件名
mTts.setParameter(SpeechConstant.SPEED, "50");
// 设置语调,范围0~100
mTts.setParameter(SpeechConstant.PITCH, "50");
// 设置音量,范围0~100
mTts.setParameter(SpeechConstant.VOLUME, "50");
// TODO Auto-generated method stub
System.out.println(
" 正在合成 " + fileName.substring(0, fileName.lastIndexOf("."))
+ " 朗读样例" + " 下载位置:" + fileName);
mTts.synthesizeToUri(Input, fileName, synthesizeToUriListener);
}
// 1 设置合成监听器
static SynthesizeToUriListener synthesizeToUriListener = new SynthesizeToUriListener()
{
// progress为合成进度0~100
public void onBufferProgress(int progress)
{
}
// 会话合成完成回调接口
// uri为合成保存地址,error为错误信息,为null时表示合成会话成功
public void onSynthesizeCompleted(String uri, SpeechError error)
{
//表明下载完成
isEnd=true;
if(error==null)
System.out.println(" 合成成功");
else
System.out.println(" 合成失败");
}
@Override
public void onEvent(int arg0, int arg1, int arg2, int arg3, Object arg4,
Object arg5)
{
// TODO Auto-generated method stub
}
};
}

主线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package demo;
import java.util.Scanner;
import com.iflytek.cloud.speech.SpeechConstant;
import com.iflytek.cloud.speech.SpeechUtility;
import replace.Replace;
public class DemoList
{
static Scanner scanner = new Scanner(System.in);
public static void main(String[] args)
{
// 2 将“XXXXXXXX”替换成您申请的APPID
SpeechUtility.createUtility(SpeechConstant.APPID + "=XXXXXXXX");
// 5设置要合成的文本
System.out.println(
"######################################## 讯飞语音合成系统 ########################################");
System.out.println("输入要合成的文字(以: \"#exit\"作为结束符):");
StringBuffer sbBuffer = new StringBuffer(10240);
String line = null;
while (scanner.hasNext())
{
line = scanner.nextLine();
if ("#exit".equals(line))
{
break;
}
sbBuffer.append(line.concat("\r\n"));
}
System.out.println(
"---------------------------------- 特殊字符处理 ----------------------------------");
// 处理输入文本,替换容易读错的特殊字符.
String Input = Replace.replaceTitle(sbBuffer.toString());
Input = Replace.replaceEnglish(Input);
System.out.println(Input);
System.out.println(
"---------------------------------- 特殊字符处理 ----------------------------------");
String[] fileNameArr = new String[]
{"小燕xiaoyan.pcm", "小研vixy.pcm", "小琪vixq.pcm", "小宇xiaoyu.pcm",
"小峰vixf.pcm"};
String[] voiceNameArr = new String[]
{"xiaoyan", "vixy", "vixq", "xiaoyu", "vixf"};
for (int i = 0; i < fileNameArr.length; i++)
{
DemoListRunable runable=new DemoListRunable(voiceNameArr[i], fileNameArr[i], Input);
DemoListRunable.isEnd=false;
Thread thread=new Thread(runable);
thread.start();
//等待下载完毕
while(!DemoListRunable.isEnd)
{
try
{
Thread.sleep(1000);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}

其中Replace这个类用来预处理朗读的文本.包括替换容易读错的字符,添加空格以加入停顿等。代码参见讯飞语音合成 发音不准确怎么解决

实验结果

下载好音频后,逐个听一半,比较一番,我的到如下结果:

发音人 连贯性 英文准确性
小燕xiaoyan 可以 英文准确
小研vixy 可以 英文准确
小琪vixq 英文可能不准确
小峰vixf 慢没有节奏 英文发音不全
小宇xiaoyu 可以 英文发音不好

通过比较我发现:

  • 女发音人中,
    • 小燕xiaoyan小研vixy这两个发音人可以用,还有就是小燕xiaoyan(默认)读的时候有写破音,最后我选择小研vixy这个发音人。
    • 小琪vixq读的有点慢,但是中英文之间切换比较好。
  • 而男发音人小峰vixf小宇xiaoyu读英文的时候声音小不清晰,读音不标准。

具体情况还是以实验为主.