手动部署Servlet

创建项目结构

java web应用部署路径

java web项目会部署到Tomcat的安装目录下的webapps目录中:E:\apache-tomcat-8.5.35\webapps\

创建项目目录

webapps目录下新建一个文件夹,取名为app01a,这个目录表示一个名为app01aJava web项目。
这里有一张图片

项目目录下创建WEB-INF目录

在项目目录app01a下创建WEB-INF目录,WEB-INF目录是无法通过浏览器进行访问的,WEB-INF之外的其他路径都可以通过浏览器来访问到。

WEB-INF下创建lib目录

WEB-INF目录下创建lib目录

WEB-INF下创建classes目录

在项目目录WEB-INF下创建classes目录,classes目录用来存放编译好的Servlet(.class):

编写Servlet

随便找个地方创建一个名为MyServlet.java的文件,然后在该文件中粘贴如下测试代码:

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 app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
@WebServlet(
name = "MyServlet",
urlPatterns ={"/my"}
)
public class MyServlet
implements
Servlet
{
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig)
throws ServletException
{
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig()
{
return servletConfig;
}
@Override
public String getServletInfo()
{
return "My Servlet";
}
@Override
public void service(ServletRequest request,
ServletResponse response)
throws ServletException,IOException
{
String servletName =
servletConfig.getServletName();
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head>" +
"<body>Hello from " +
servletName +
"</body></html>");
}
@Override
public void destroy()
{}
}

编译Servlet

配置编译环境

这个MyServlet.java源文件是不能直接用javac编译的,错误输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
E:\workspace_web\app01a\src\app01a>javac -encoding utf-8 MyServlet.java
MyServlet.java:4: 错误: 程序包javax.servlet不存在
import javax.servlet.Servlet;
^
MyServlet.java:5: 错误: 程序包javax.servlet不存在
import javax.servlet.ServletConfig;
^
MyServlet.java:6: 错误: 程序包javax.servlet不存在
import javax.servlet.ServletException;
^
MyServlet.java:7: 错误: 程序包javax.servlet不存在
import javax.servlet.ServletRequest;
^
MyServlet.java:8: 错误: 程序包javax.servlet不存在
import javax.servlet.ServletResponse;
^
MyServlet.java:9: 错误: 程序包javax.servlet.annotation不存在
import javax.servlet.annotation.WebServlet;

这是因为javac命令找不到servlet-api.jar这个jar包,这个servlet-api.jar可以在Tomcat的安装路径下的lib目录中找到:
这里有一张图片
复制这个jar文件,粘贴到%JAVA_HOME%\jre\lib\ext\目录下,如下图所示:
这里有一张图片
这样我们就可以直接编译java源文件了.

使用javac命令编译

我使用的编码是UTF-8的,所以用:javac -encoding utf-8 MyServlet.java进行编译:

1
2
3
4
Microsoft Windows [版本 10.0.17134.407]
(c) 2018 Microsoft Corporation。保留所有权利。
E:\workspace_web\app01a\src\app01a>javac -encoding utf-8 MyServlet.java
E:\workspace_web\app01a\src\app01a>

这样会在当前目录下生成一个MyServlet.class文件,如下图所示:
这里有一张图片

部署Servlet

创建包目录

WEB-INF/classes/目录下创建对应的包目录:app01a

粘贴字节码文件到包目录中

复制生成的MyServlet.class,然后粘贴到包目录WEB-INF/classes/app01a/下,这样MyServlet就部署完毕了,最终的目录结构如下图所示:
这里有一张图片

访问Servlet

通过下面的URL即可访问到该Servlet,
http://localhost:8080/app01a/my
浏览器显示效果如下图所示:
这里有一张图片

问题描述

在JSP页面中导入了自定义的标签文件出现乱码,这里涉及到四个文件分别是:

  • /app07a/WebContent/includeDemoTagTest.jsp这个jsp是入口页面
  • /app07a/WebContent/WEB-INF/tags/includeDemoTag.tag,includeDemoTagTest.jsp中引用这个个标签文件。
  • /app07a/WebContent/WEB-INF/tags/included.html,includeDemoTag.tag中引用这个html文件。
  • /app07a/WebContent/WEB-INF/tags/included.tagf,includeDemoTag.tag中引用引入这个tagf文件。`

详细代码

/app07a/WebContent/includeDemoTagTest.jsp:

1
2
3
4
5
6
7
8
9
10
11
<html>
<body>
<h2>下面是标签文件includeDemoTag.tag中的内容</h2>
<hr color="green">
<!-- 引入自定义标签目录,取个别名为前缀`easy` -->
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags"%>
<!-- 引入前缀`easy`所表示的标签目录中的`includeDemoTag.tag`文件 -->
<easy:includeDemoTag />
<hr color="green">
</body>
</html>

/app07a/WebContent/WEB-INF/tags/includeDemoTag.tag:

1
2
3
4
5
6
7
8
9
<strong>以下是included.html的内容:</strong>
<hr color="red">
<%@ include file="included2.tagf"%>
<hr color="red">
<hr color="blue">
<strong>下面是included.tagf文件中的内容:</strong>
<%@ include file="included.tagf"%>

<hr color="blue">

/app07a/WebContent/WEB-INF/tags/included.tagf:

1
2
3
<%
out.print("<strong>included.tagf文件中的内容</strong>");
%>

/app07a/WebContent/WEB-INF/tags/included.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<table>
<tr>
<th>水果</th>
<th>单价(斤)</th>
</tr>
<tr>
<td>苹果</td>
<td>4.8</td>
</tr>
<tr>
<td>香蕉</td>
<td>3.6</td>
</tr>
<tr>
<td>草莓</td>
<td>12.5</td>
</tr>
</table>

浏览器显示效果

通过URL:
http://localhost:8080/app07a/includeDemoTagTest.jsp访问,浏览器将显示乱码,如下图所示:
这里有一张图片

解决乱码

解决jsp文件乱码

在JSP文件第一行加入如下page指令:

1
2
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

可以解决JSP文件中的乱码.现在浏览器显示效果如下:
这里有一张图片

解决jsp中引入的tag文件出现的乱码

在tag文件第一行中加入如下tag指令:

1
<%@ tag pageEncoding="utf-8"%>

即可解决乱码,浏览器显示效果:
这里有一张图片

解决tag文件中引入的tagf文件出现的乱码

类似tag文件,在tagf文件中的第一行加入如下tag指令,可以解决乱码:
这里有一张图片

解决tag文件中引入的html文件中出现的乱码

我试了好多办法,都无法修改html文件的乱码,我的做法是:不使用html文件,也就是把included.html这个html文件改成included2.tagftagf文件,然后利用tag指令来设置编码.
这里有一张图片

修改后的代码

includeDemoTagTest.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<body>
<h2>下面是标签文件includeDemoTag.tag中的内容</h2>
<hr color="green">
<!-- 引入自定义标签目录,取个别名为前缀`easy` -->
<%@ taglib prefix="easy" tagdir="/WEB-INF/tags"%>
<!-- 引入前缀`easy`所表示的标签目录中的`includeDemoTag.tag`文件 -->
<easy:includeDemoTag />
<hr color="green">
</body>
</html>

includeDemoTag.tag

1
2
3
4
5
6
7
8
9
10
<!-- 设置tag文件的编码 -->
<%@ tag pageEncoding="utf-8"%>
<strong>以下是included.html的内容:</strong>
<hr color="red">
<%@ include file="included2.tagf"%>
<hr color="red">
<hr color="blue">
<strong>下面是included.tagf文件中的内容:</strong>
<%@ include file="included.tagf"%>
<hr color="blue">

included.tagf

1
2
3
4
<%@ tag pageEncoding="utf-8"%>
<%
out.print("<strong>included.tagf文件中的内容</strong>");
%>

included2.tagf

1
2
3
4
<%@ tag pageEncoding="utf-8"%>
<%
out.print("<strong>included.tagf文件中的内容</strong>");
%>

原理

ASCII码运算

先来看实例代码:

1
2
3
4
System.out.println("a-z:"+(int)'a'+"-"+(int)'z');
System.out.println("A-Z:"+(int)'A'+"-"+(int)'Z');
System.out.println("0-9:"+(int)'0'+"-"+(int)'9');
System.out.println("a减去A="+(int)('a'-'A'));

运行结果:

1
2
3
4
a-z:97-122
A-Z:65-90
0-9:48-57
a减去A=32

结论

  • 小写字母a到zASCII码范围为:97到122
  • 大写字母A到ZASCII码范围为:65到90,
  • 小写字母减去32就得到大写字母,同样的大写字母加上32就得到了小写字母.

代码

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
package first.letter;
public class LowerUpperCaseFirstLetter
{
public static void main(String[] args)
{
// 首字母小写
System.out.println("首字母变小写:" + lowerCaseFirstLetter("Java"));
// 首字母大写
System.out.println("首字母变大写:" + upperCaseFirstLetter("java"));
// System.out.println("a-z:"+(int)'a'+"-"+(int)'z');
// System.out.println("A-Z:"+(int)'A'+"-"+(int)'Z');
// System.out.println("0-9:"+(int)'0'+"-"+(int)'9');
// System.out.println("a减去A="+(int)('a'-'A'));
}
/**
* 首字母变大写.
*
* @param str
* 英文单词.
* @return 首字母大写的英文单词.
*/
public static String upperCaseFirstLetter(String str)
{
char[] chars = str.toCharArray();
// 如果是小写字母
if ('a' <= chars[0] && chars[0] <= 'z')
{
System.out.println(chars[0] + ":" + (int) (chars[0]));
// 小写-32变大写
chars[0] = (char) (chars[0] - 32);
}
return String.valueOf(chars);
}
/**
* 首字母变小写.
* @param str 英文单词.
* @return 首字母变小写的英文单词.
*/
public static String lowerCaseFirstLetter(String str)
{
char[] chars = str.toCharArray();
// 如果是大写字母
if ('A' <= chars[0] && chars[0] <= 'Z')
{
System.out.println(chars[0] + ":" + (int) (chars[0]));
// 大写+32是变小写
chars[0] += 32;
}
return String.valueOf(chars);
}
}

运行结果:

1
2
3
4
J:74
首字母变小写:java
j:106
首字母变大写:Java

问题描述

使用讯飞语音合成服务需要联网,但是,有时候网络断掉了.我并不知道,程序中也没有提醒.等到我发现合成的不对的时候,再检查网络,已经浪费了一段时间.
所以程序应该先判断一下是否能联网,如果能联网再调用SDK合成语音,如果不能联网则及时给出提示.

判断当前是否可以联网的批处理

xunfei.bat:

1
2
3
4
5
6
7
8
9
@echo off
ping www.xfyun.cn > nul
if %errorlevel% leq 0 (
echo 可以连接到www.xfyun.cn,网络链接正常.
pause
) else (
echo 无法连接到www.xfyun.cn,请求检查网络是否连接正常.
pause
)

这里先执行ping命令,并根据ping命令返回的errorlevel来判断是否可以连通,如果可以连接上,errorlevel应该为0
可以联网时输出:

1
2
可以连接到www.xfyun.cn,网络链接正常.
请按任意键继续. . .

无法联网时输出:

1
2
无法连接到www.xfyun.cn,请求检查网络是否连接正常.
请按任意键继续. . .

应用到程序中

安装程序

install.bat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
echo ############ 生成启动程序 xunfei.bat...
::覆盖写入
echo @echo off>xunfei.bat
echo ::切换盘符>>xunfei.bat
echo %~d0>>xunfei.bat
echo :: 进入文件所在路径>>xunfei.bat
echo cd %~dp0>>xunfei.bat
echo :: 判断是否可以联网,为了节省时间,只发送回显请求1次>>xunfei.bat
echo ping www.xfyun.cn -n 1 ^> nul>>xunfei.bat
echo :: 如果可以联网则执行程序>>xunfei.bat
echo if %%errorlevel%% leq 0 (>>xunfei.bat
echo echo 网络链接正常.>>xunfei.bat
echo java -jar "%thispath%\xunfei.jar">>xunfei.bat
echo ) else (>>xunfei.bat
echo :: 如果不可以联网则给出提示>>xunfei.bat
echo echo 无法连接到www.xfyun.cn,请求检查网络是否连接正常.>>xunfei.bat
echo pause>>xunfei.bat
echo exit>>xunfei.bat
echo )>>xunfei.bat
:: ################# 创建启动程序 结束

执行程序

运行安装程序install.bat生成的xunfei.bat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@echo off
::切换盘符
E:
:: 进入文件所在路径
cd E:\workspace\XunFeiTTS\runable\
:: 判断是否可以联网,为了节省时间,只发送回显请求1次
ping www.xfyun.cn -n 1 > nul
:: 如果可以联网则执行程序
if %errorlevel% leq 0 (
echo 网络链接正常.
java -jar "E:\workspace\XunFeiTTS\runable\xunfei.jar"
) else (
:: 如果不可以联网则给出提示
echo 无法连接到www.xfyun.cn,请求检查网络是否连接正常.
pause
exit
)

运行结果

xunfei.bat运行结果如下:

网络连接正常

1
2
3
网络链接正常.
####################################### 讯飞语音合成系统
输入要合成的文字(以: "#"作为结束符):

网络不通时

1
2
无法连接到www.xfyun.cn,请求检查网络是否连接正常.
请按任意键继续. . .

ping命令发送一次请求

正常的ping命令会回显4次,如下所示:

1
2
3
4
5
6
7
8
9
10
G:\Desktop\书籍\批处理\bat>ping www.xfyun.cn
正在 Ping www.xfyun.cn [42.62.43.138] 具有 32 字节的数据:
来自 42.62.43.138 的回复: 字节=32 时间=66ms TTL=237
来自 42.62.43.138 的回复: 字节=32 时间=146ms TTL=237
来自 42.62.43.138 的回复: 字节=32 时间=136ms TTL=237
来自 42.62.43.138 的回复: 字节=32 时间=126ms TTL=237
42.62.43.138 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 66ms,最长 = 146ms,平均 = 118ms

为了节省时间,我这里只发送一次请求:

1
2
3
4
5
6
7
G:\Desktop\书籍\批处理\bat>ping www.xfyun.cn -n 1
正在 Ping www.xfyun.cn [42.62.43.138] 具有 32 字节的数据:
来自 42.62.43.138 的回复: 字节=32 时间=57ms TTL=237
42.62.43.138 的 Ping 统计信息:
数据包: 已发送 = 1,已接收 = 1,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 57ms,最长 = 57ms,平均 = 57ms

这样等待时间可以忽略,毕竟等待默认的ping命令执行完毕还是挺难熬的。

HTML 表格对齐方式

align 属性规定表格相对于周围元素的对齐方式。这个东西有时候会忘掉.还是记录一下,以便下次查找.

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
<table>
<tbody>
<tr>
<td align="right">host:</td>
<td>localhost:8080</td>
</tr>
<tr>
<td align="right">user-agent:</td>
<td>Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0</td>
</tr>
<tr>
<td align="right">accept:</td>
<td>text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8</td>
</tr>
<tr>
<td align="right">accept-language:</td>
<td>zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2</td>
</tr>
<tr>
<td align="right">accept-encoding:</td>
<td>gzip, deflate</td>
</tr>
<tr>
<td align="right">dnt:</td>
<td>1</td>
</tr>
<tr>
<td align="right">connection:</td>
<td>keep-alive</td>
</tr>
<tr>
<td align="right">cookie:</td>
<td>JSESSIONID=440BFA3EA210ECE5A1C1F5A040C52046</td>
</tr>
<tr>
<td align="right">upgrade-insecure-requests:</td>
<td>1</td>
</tr>
<tr>
<td align="right">cache-control:</td>
<td>max-age=0</td>
</tr>
</tbody>
</table>

显示效果:

host:localhost:8080
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language:zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
accept-encoding:gzip, deflate
dnt:1
connection:keep-alive
cookie:JSESSIONID=440BFA3EA210ECE5A1C1F5A040C52046
upgrade-insecure-requests:1
cache-control:max-age=0

参考链接

http://www.w3school.com.cn/tags/att_table_align.asp

下面列举了让我出现问题的HTML转义符

字符 英文代码 数字代码 名称
{ &#123; 左大括号
} &#125; 右大括号
@ &#64; at,邮箱符号
< &lt; &#60; 小于号
> &gt; &#62; 大于号
# &#35; 井号
&nbsp; 不断行的空格符
: &#58; 冒号
& &amp; &#62; 取地址符
" &quot; &#34; 双引号
© &copy; &#169; 版权符号
× &times; &#215; 乘号
÷ &divide; &#247; 除号
|| &verbar &#124; 竖线
¦¦ &brvbar; &#166; 断竖线

就这么多,后面遇到再更新.

Unicode编码转义

找到一些在线的Unicode编码转换网站,例如:https://www.sojson.com/unicode.html
把遇到的特殊字符,转换成Unicode编码即可,转换得到的Unicode同样可以直接放在HTML中显示。

参考链接

http://www.fly63.com/article/detial/4916
http://www.cnblogs.com/xcsn/p/3559624.html
https://www.cnblogs.com/zhxhdean/archive/2011/11/17/2252975.html
https://tool.oschina.net/commons?type=2

https://en.wikipedia.org/wiki/Vertical_bar#Unicode_code_points
U+007C | VERTICAL LINE (HTML &#124; · &verbar;, &vert;, &VerticalLine;) (single vertical line)
U+00A6 ¦ BROKEN BAR (HTML &#166; · &brvbar;) (single broken line)

问题描述

我把git命令写入到批处理文件中,这样我可以少打几个命令.但是在git commit -m 'xxxx'这个命令中要求输入文本,这就需要这个批处理文件有输入功能,不过git commit -m 'xxxx'这个命令本身可以输入多行数据.所以我需要批处理文件有多行输入的功能

批处理 录入多行数据

callinput.bat:

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
@echo off
setlocal enabledelayedexpansion
call :input
echo #################################
echo !inputStr!
pause
goto :eof
::####################### 多行输入子程序input 开始
:input
echo 输入#号表示录入结束.
echo git commit -m '
:nextLine
set /p message=
if not "%message%"=="#" (
set "sum=%sum%#__#%message%"
goto :nextLine
) else (
goto :done
)
:done
::删除前面多加入的分隔符
set "sum=%sum:~4%
::输出处理后的字符
::echo %sum%
::把分隔符`#__#`替换成换行符
set inputStr=!sum:#__#=^

!
echo '
goto :eof
::####################### 多行输入子程序 结束

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
输入#号表示录入结束.
git commit -m '
1
2
3
4
5
#
'
#################################
1
2
3
4
5
请按任意键继续. . .

因为,批处理文件中创建的变量在整个文件中都有效,所以就算子程序结束后,依然可以通过echo !inputStr!这样的代码来访问录入的结果.

代码详解

开启变量延迟

1
setlocal enabledelayedexpansion

如果不开启,变量操作时结果可能不正确.

循环录入功能

1
2
3
4
5
6
7
8
9
:nextLine
set /p message=
if not "%message%"=="#" (
set "sum=%sum%#__#%message%"
goto :nextLine
) else (
goto :done
)
:done

上面的程序可从键盘录入数据,遇到单独的一行#作为结束符.这样只能录入数据而已,是没有多行的效果的,

用换行符替换标记的字符

在之前录入的字符串中,我使用看#__#这个字符作为分隔符了.下面的代码就是把这个分隔符,替换成换行符.

1
2
3
4
::把分隔符`#__#`替换成换行符
set inputStr=!sum:#__#=^

!

要注意的是,感叹号之前的空行不要删掉,这样写是没有问题的.
还有就是想输出换行符,必须使用!inputStr!这样的形式,使用%inputStr%来访问时不会有换行符的.

应用到版本控制批处理文件中

首先调用上面的:input子程序来读入数据,最后用读取到的数据,提交给git commit -m即可,如下所示:

1
2
3
4
5
6
7
......
call :input
echo #################################
echo !inputStr!
git add .
git commit -m !inputStr!
......

字符串替换

语法格式

将字符串变量%StrName%中的str1全部替换为str2.

1
%StrName:str1=str2%

实例

1
2
3
4
5
6
@echo off
set StrName=www_google_com
echo 替换前的值:"%StrName%"
set Var=%StrName:_=.%
echo 替换后的值:"%Var%"
pause

运行结果:

1
2
3
4
5
6
@echo off
set StrName=www_google_com
echo 替换前的值:"%StrName%"
set Var=%StrName:_=.%
echo 替换后的值:"%Var%"
pause

实例2

1
2
3
4
5
6
@echo off
set StrName=www_google_com_hk
echo 替换前的值:"%StrName%"
set Var=%StrName:_=被墙了%
echo 替换后的值:"%Var%"
pause

运行结果:

1
2
3
4
G:\Desktop\书籍\批处理\bat>字符串替换2.bat
替换前的值:"www_google_com_hk"
替换后的值:"www被墙了google被墙了com被墙了hk"
请按任意键继续. . .

字符串截取

正序截取 从左向右截取

1
%StrName:~[m[,n]]%
  • 方括号表示可选项
  • %为变量标识符,
  • StrName为变量名,不可省略
  • 冒号:用于分隔变量名和说明部分
  • ~可以简单理解为偏移
  • m为偏移量,缺省为0偏移量
  • n为截取长度,缺省为截取到最后.

实例

1
2
3
4
5
6
@echo off 
set a=www.google.com
:: 截取出google
set var=%a:~4,6%
echo %var%
pause

运行结果:

1
2
3
G:\Desktop\书籍\批处理\bat>字符串截取.bat
google
请按任意键继续. . .

分析
如果用数字1表示第一个字符w,则%a:~4,6%表示从第4个字符(不包括该字符)开始,往后截取6个字符,也就是从.这个字符开始往后截取6字符,得到google.用数学中的区间可以很好的理解,截取的区间为:(4,4+6]

逆序截取

1
2
3
4
5
6
@echo off 
set a=www.google.com
::倒数截取三位
set var=%a:~-3%
echo %var%
pause

运行结果

1
2
3
G:\Desktop\书籍\批处理\bat>字符串逆序截取.bat
com
请按任意键继续. . .

2.5小结

URL重写和隐藏域是轻量级的会话跟踪技术,适用于那些仅跨少量页面的数据。而cookiesHttpSession对象,更加灵活但也有限制,尤其是在应用HttpSession时会消耗服务器内存。

2.4 Java Servlet 会话管理 通过HttpSession对象

在所有的会话跟踪技术中,HttpSession 对象是最强大和最通用的。一个用户可以有且最多有一个HttpSession,并且不会被其他用户访问到。

谁创建HttpSession对象

HttpSession对象在用户第一次访问网站的时候自动被创建。

如何获取HttpSession对象

你可以通过调用HttpServletRequestgetSession方法获取该对象。getSession有两个重载方法:

1
2
HttpSession getSession()
HttpSession getSession(boolean create)

没有参数的getSession方法会返回当前的HttpSession,若当前没有,则创建一个并返回。
getSession(false)返回当前HttpSession,如当前不存在,则返回null
getSession(true)返回当前HttpSession,若当前没有,则创建一个并返回,getSession(true)getSession()的效果一致.

如何往HttpSession中存入数据

可以通过HttpSessionsetAttribute方法将值放入HttpSession,该方法的方法签名如下:

1
void setAttribute(java.lang.String name, java.lang.Object value)

请注意,不同于URL重新、隐藏域或cookie放入到HttpSession 的值,是存储在内存中的,因此,不要往HttpSession放入太多对象或放入大对象。
尽管现代的Servlet容器在内存不够用的时候会将保存在HttpSessions的对象转储到二级存储上,但这样有性能问题,因此小心存储。

HttpSession中的数据要满足的条件

此外,放到HttpSession的值不限于String类型,可以是任意实现java.io.Serializablejava对象,因为在内存不够用的时候,Servlet容器会将这些对象放入文件或数据库中,当然你也可以将不支持序列化的对象放入HttpSession,只是这样,当Servlet容器进行序列化的时候会失败并报错。
调用setAttribute方法时,若传入的name参数此前已经使用过,则会用新值覆盖旧值。

从HttpSession对象中取出数据

通过调用HttpSessiongetAttribute方法可以取回之前放入HttpSession中的对象,该方法的签名如下:

1
java.lang.Object getAttribute(java.lang.String name)

从HttpSession对象中批量取出数据

HttpSession还有一个非常有用的方法,名为getAttributeNames,该方法会返回一个Enumeration对象来迭代访问保存在HttpSession中的所有值:

1
java.util.Enumeration<java.lang.String> getAttributeNames()

注意,所有保存在HttpSession的数据不会被发送到客户端,不同于其他会话管理技术,**Servlet容器为每个HttpSession生成唯一的标识,并将该标识发送给浏览器**,或创建一个名为JSESSIONIDcookie,或者在URL后附加一个名为jsessionid 的参数。在后续的请求中,浏览器会将标识提交给服务端,这样服务器就可以识别该请求是由哪个用户发起的。Servlet容器会自动选择一种方式传递会话标识,无须开发人员介入。
提示

  • 隐藏域技术会往浏览器发送带隐藏域的表单.
  • Cookie技术会往浏览器发送Cookie.

获取Http对象的标识

可以通过调用 HttpSessiongetId方法来读取该标识:

1
java.lang.String getId()

此外,HttpSession.还定义了一个名为invalidate 的方法。该方法强制会话过期,并清空其保存的对象。默认情况下,HttpSession 会在用户不活动一段时间后自动过期,该时间可以通过部署描述符的 session-timeout元素配置,若设置为30,则会话对象会在用户最后一次访问30分钟后过期,如果部署描述符没有配置,则该值取决于Servlet容器的设定。
大部分情况下,你应该主动销毁无用的HttpSession,以便释放相应的内存。

查看设置超时时间

可以通过调用HttpSessiongetMaxInactiveInterval方法来查看会话多久会过期。该方法返回一个数字类型,单位为秒。调用setMaxInactiveInterval 方法来单独对某个HttpSession 设定其超时时间:

1
void setMaxInactiveInterval(int seconds)

若设置为0,则该HttpSession 永不过期。通常这不是一个好的设计,因为该 HttpSession 所占用的堆内存将永不释放,直到应用重加载或Servlet容器关闭。

Demo

一个简单的购物Demo,具有显示商品列表,商品详情页面,购物车页面.并且在商品详情页面提供添加到购物车的功能.

Product

商品类用来存储商品的信息,如下所示:

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.httpsession;
//一个商品的信息.
public class Product
{
// 商品编号.
private int id;
// 商品名称.
private String name;
// 商品详细描述.
private String description;
// 商品价钱.
private float price;
public Product( int id,String name,String description,float price)
{
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public float getPrice()
{
return price;
}
public void setPrice(float price)
{
this.price = price;
}
}

购物车中光有商品而已是不行的,还要知道用户买了几个商品,商品和对应的数量构成了购物车中记录的数据结构。

ShoppingItem

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
package session.management.httpsession;
//一次购物元素,对应于购物车中的一个商品.
public class ShoppingItem
{
// 商品
private Product product;
// 数量
private int quantity;
public ShoppingItem(Product product,int quantity)
{
this.product = product;
this.quantity = quantity;
}
public Product getProduct()
{
return product;
}
public void setProduct(Product product)
{
this.product = product;
}
public int getQuantity()
{
return quantity;
}
public void setQuantity(int quantity)
{
this.quantity = quantity;
}
}

有了商品,有了记录,下面就来写功能代码了.

ShoppingCartServlet

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
package session.management.httpsession;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
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 javax.servlet.http.HttpSession;
@WebServlet(
name = "ShoppingCartServlet",
urlPatterns =
{"/products", "/viewProductDetails", "/addToCart", "/viewCart"}
)
public class ShoppingCartServlet extends HttpServlet
{
private static final long serialVersionUID = -20L;
//
private static final String CART_ATTRIBUTE = "cart";
private static final String tableStyle = "<style>table{border-right:1px solid #F00;border-bottom:1px solid #F00}\r\n"
+ "table td{border-left:1px solid #F00;border-top:1px solid #F00}</style>";
// 商品列表
private List<Product> products = new ArrayList<Product>();
private NumberFormat currencyFormat = NumberFormat
.getCurrencyInstance(Locale.CHINA);
@Override
public void init() throws ServletException
{
products.add(new Product(1, "苹果", "新鲜上市的苹果", 5.5F));
products.add(new Product(2, "香蕉", "新鲜上市的香蕉", 6.5F));
products.add(new Product(3, "菠萝", "新鲜上市的菠萝", 4.8F));
products.add(new Product(4, "草莓", "新鲜上市的草莓", 12.5F));
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
String uri = request.getRequestURI();
if (uri.endsWith("/products")) {
sendProductList(response);
} else if (uri.endsWith("/viewProductDetails")) {
sendProductDetails(request, response);
} else if (uri.endsWith("/viewCart")) {
showCart(request, response);
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
// add to cart
int productId = 0;
int quantity = 0;
try {
// 从参数中取出id属性的值
productId = Integer.parseInt(request.getParameter("id"));
// 取出数量属性的值
quantity = Integer.parseInt(request.getParameter("quantity"));
} catch (NumberFormatException e) {}
// 根据Id从商品列表中找出该商品
Product product = getProduct(productId);
if (product != null && quantity >= 0) {
// 创建一个购物记录
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
// 取得session对象
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
// 从session对象中,取出(查找)名称为`CART_ATTRIBUTE`的`List<ShoppingItem>`对象
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
// 如果查找不到,这说明session对象中还没有这个对象.
if (cart == null) {
// 创建对象
cart = new ArrayList<ShoppingItem>();
// 添加到session对象中
session.setAttribute(CART_ATTRIBUTE, cart);
}
// 添加一个商品到购物车中
cart.add(shoppingItem);
}
sendProductList(response);
}
private void sendProductList(HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head><title>商品列表</title>" + tableStyle
+ "</head><body><h2>商品列表</h2>");
writer.println("<ul>");
for (Product product : products) {
writer.println("<li>" + product.getName() + "("
+ currencyFormat.format(product.getPrice()) + ") ("
+ "<a href='viewProductDetails?id=" + product.getId()
+ "'>宝贝详情</a>)");
}
writer.println("</ul>");
writer.println("<a href='viewCart'>查看购物车</a>");
writer.println("</body></html>");
}
// 根据商品ID,从商品列表中查找一个商品.
private Product getProduct(int productId)
{
for (Product product : products) {
if (product.getId() == productId) {
return product;
}
}
return null;
}
// 显示商品列表
private void sendProductDetails(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
int productId = 0;
try {
// 读取商品编号
productId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
// 根据编号查找商品对象
Product product = getProduct(productId);
if (product != null) {
writer.println("<html><head>" + "<title>商品详情</title>" + tableStyle
+ "</head>" + "<body><h2>商品详情</h2>"
+ "<form method='post' action='addToCart'>");
writer.println("<input type='hidden' name='id' " + "value='"
+ productId + "'/>");
writer.println("<table>");
writer.println(
"<tr><td>商品名称:</td><td>" + product.getName() + "</td></tr>");
writer.println("<tr><td>商品描述:</td><td>" + product.getDescription()
+ "</td></tr>");
writer.println(
"<tr><td>购买数量:</td><td><input name='quantity'/>斤</td></tr>"
+ "<tr><td colspan='2'><input type='submit' value='加入购物车'/>"
+ "</td>" + "</tr>");
writer.println("<tr><td colspan='2'>"
+ "<a href='products'>返回商品列表</a>" + "</td></tr>");
writer.println("</table>");
writer.println("</form></body></html>");
} else {
writer.println("<h2>找不到该商品</h2>");
}
}
// 显示购物车
private void showCart(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer
.println("<html><head><title>购物车</title>" + tableStyle + "</head>");
writer.println("<body><a href='products'>" + "返回商品列表</a>");
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
if (cart != null) {
writer.println("<table>");
writer.println("<tr><td style='width:150px'>数量" + "</td>"
+ "<td style='width:150px'>商品名称</td>"
+ "<td style='width:150px'>单品价格</td>" + "<td>合计</td></tr>");
// 总价格
double total = 0.0;
// 遍历购物车ArrayList,取出一次购物项,
for (ShoppingItem shoppingItem : cart) {
// 从购物项中,取出一个商品对象.
Product product = shoppingItem.getProduct();
// 从购物项中,取出该商品的数量
int quantity = shoppingItem.getQuantity();
if (quantity != 0) {
float price = product.getPrice();
writer.println("<tr>");
writer.println("<td>" + quantity + "</td>");
writer.println("<td>" + product.getName() + "</td>");
writer.println(
"<td>" + currencyFormat.format(price) + "</td>");
double subtotal = price * quantity;
writer.println(
"<td>" + currencyFormat.format(subtotal) + "</td>");
total += subtotal;
writer.println("</tr>");
}
}
writer.println("<tr><td colspan='4' " + "style='text-align:right'>"
+ "总价格:" + currencyFormat.format(total) + "</td></tr>");
writer.println("</table>");
}
writer.println("</table></body></html>");
}
}

代码详解

创建并初始化商品列表

1
2
3
4
5
6
7
8
9
10
// 商品列表
private List<Product> products = new ArrayList<Product>();
@Override
public void init() throws ServletException
{
products.add(new Product(1, "苹果", "新鲜上市的苹果", 5.5F));
products.add(new Product(2, "香蕉", "新鲜上市的香蕉", 6.5F));
products.add(new Product(3, "菠萝", "新鲜上市的菠萝", 4.8F));
products.add(new Product(4, "草莓", "新鲜上市的草莓", 12.5F));
}

从商品列表中查找出一个商品

1
2
3
4
5
6
7
8
9
10
// 根据商品ID,从商品列表中查找一个商品.
private Product getProduct(int productId)
{
for (Product product : products) {
if (product.getId() == productId) {
return product;
}
}
return null;
}

匹配URL

1
2
3
4
5
@WebServlet(
name = "ShoppingCartServlet",
urlPatterns =
{"/products", "/viewProductDetails", "/addToCart", "/viewCart"}
)

doGet方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
String uri = request.getRequestURI();
//显示产品列表
if (uri.endsWith("/products")) {
sendProductList(response);
}
//显示产品详情
else if (uri.endsWith("/viewProductDetails")) {
sendProductDetails(request, response);
}
//显示购物车
else if (uri.endsWith("/viewCart")) {
showCart(request, response);
}
}

显示产品列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void sendProductList(HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head><title>商品列表</title>" + tableStyle
+ "</head><body><h2>商品列表</h2>");
writer.println("<ul>");
for (Product product : products) {
writer.println("<li>" + product.getName() + "("
+ currencyFormat.format(product.getPrice()) + ") ("
+ "<a href='viewProductDetails?id=" + product.getId()
+ "'>宝贝详情</a>)");
}
writer.println("</ul>");
writer.println("<a href='viewCart'>查看购物车</a>");
writer.println("</body></html>");
}

显示商品详情

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 sendProductDetails(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
int productId = 0;
try {
// 读取商品编号
productId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
// 根据编号查找商品对象
Product product = getProduct(productId);
if (product != null) {
writer.println("<html><head>" + "<title>商品详情</title>" + tableStyle
+ "</head>" + "<body><h2>商品详情</h2>"
+ "<form method='post' action='addToCart'>");
writer.println("<input type='hidden' name='id' " + "value='"
+ productId + "'/>");
writer.println("<table>");
writer.println(
"<tr><td>商品名称:</td><td>" + product.getName() + "</td></tr>");
writer.println("<tr><td>商品描述:</td><td>" + product.getDescription()
+ "</td></tr>");
writer.println(
"<tr><td>购买数量:</td><td><input name='quantity'/>斤</td></tr>"
+ "<tr><td colspan='2'><input type='submit' value='加入购物车'/>"
+ "</td>" + "</tr>");
writer.println("<tr><td colspan='2'>"
+ "<a href='products'>返回商品列表</a>" + "</td></tr>");
writer.println("</table>");
writer.println("</form></body></html>");
} else {
writer.println("<h2>找不到该商品</h2>");
}
}

显示商品详情页,表单属性如下:

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

当用户点击加入购物车时,会使用POST方法把表单发送给.\addToCart这个URL.

doPost方法

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
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
// add to cart
int productId = 0;
int quantity = 0;
try {
// 从参数中取出id属性的值
productId = Integer.parseInt(request.getParameter("id"));
// 取出数量属性的值
quantity = Integer.parseInt(request.getParameter("quantity"));
} catch (NumberFormatException e) {}
// 根据Id从商品列表中找出该商品
Product product = getProduct(productId);
if (product != null && quantity >= 0) {
// 创建一个购物记录
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
// 取得session对象
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
// 从session对象中,取出(查找)名称为`CART_ATTRIBUTE`的`List<ShoppingItem>`对象
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
// 如果查找不到,这说明session对象中还没有这个对象.
if (cart == null) {
// 创建对象
cart = new ArrayList<ShoppingItem>();
// 添加到session对象中
session.setAttribute(CART_ATTRIBUTE, cart);
}
// 添加一个商品到购物车中
cart.add(shoppingItem);
}
sendProductList(response);
}

显示购物车

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
// 显示购物车
private void showCart(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer
.println("<html><head><title>购物车</title>" + tableStyle + "</head>");
writer.println("<body><a href='products'>" + "返回商品列表</a>");
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
if (cart != null) {
writer.println("<table>");
writer.println("<tr><td style='width:150px'>数量" + "</td>"
+ "<td style='width:150px'>商品名称</td>"
+ "<td style='width:150px'>单品价格</td>" + "<td>合计</td></tr>");
// 总价格
double total = 0.0;
// 遍历购物车ArrayList,取出一次购物项,
for (ShoppingItem shoppingItem : cart) {
// 从购物项中,取出一个商品对象.
Product product = shoppingItem.getProduct();
// 从购物项中,取出该商品的数量
int quantity = shoppingItem.getQuantity();
if (quantity != 0) {
float price = product.getPrice();
writer.println("<tr>");
writer.println("<td>" + quantity + "</td>");
writer.println("<td>" + product.getName() + "</td>");
writer.println(
"<td>" + currencyFormat.format(price) + "</td>");
double subtotal = price * quantity;
writer.println(
"<td>" + currencyFormat.format(subtotal) + "</td>");
total += subtotal;
writer.println("</tr>");
}
}
writer.println("<tr><td colspan='4' " + "style='text-align:right'>"
+ "总价格:" + currencyFormat.format(total) + "</td></tr>");
writer.println("</table>");
}
writer.println("</table></body></html>");
}