在上一篇 【一步一个脚印】Tomcat+MySQL为自己的APP打造服务器(2-1)Servlet 的使用 中我们只是简单的对 Servlet 要做的任务、在服务器中的地位有了一个大概的了解,完成了在一个全新的 WorkSpace 中创建第一个 Dynamic Web Project ,并创建第一个 Servlet ,解决期间可能遇到的常见问题,还留了不少的内容重要内容没有完成,今天我们来继续解决这几个问题:
我们来回顾上一篇篇末时在浏览器中访问 FirstServlet 时用的地址 http://localhost:8080/ServletTest/Home/FirstServlet,我们还特地用不同颜色标注了这个URL的不同部分,下面我们来看看这各个部分都是什么:
http://localhost:8080/ServletTest/Home/FirstServlet
http:// 是一种网络通信协议,HTTP是客户端浏览器或其他程序与Web服务器之间的应用层通信协议,比如还有 FTP ,HTTPS...等,你可以理解成就是网络通信的规则,你要用 http 就要遵循它下边指定的各种规则(这个具体我也不大懂,不妄加解释,以免误人子弟)。
localhost:8080 是你访问的服务程序所在主机的地址及开放端口,即服务器的IP地址和对应的端口地址。这里我的服务器就是自己的电脑,直接用浏览器访问,所以我用的就是 localhost(默认的 localhost 是127.0.0.1,就是指本地);如果你经过网络(不管是局域网、或者是 Internet),就需要用真正的服务器 IP 地址了(命令行使用 ipconfig 命令即可查看电脑IP),*注意* 只有具有公网IP 的才能通过 Internet 访问到,否则只能通过局域网使用,关于这个问题,我们后边还会涉及到,到时候再详细解释。
/ServletTest/Home/FirstServlet 是你所访问的程序在服务器上部署的路径。其实这里这个路径是两个部分,/ServletTest/Home/FirstServlet 中/ServletTest 是工程项目名称(和自己的项目名称一致,你和我不一定一样),/Home/FirstServlet 是在创建Servlet的时候指定的 URL-mapping。项目名和url-mapping组合成你的 Servlet 路径。
当我们请求这样一个地址时(之前的例子是在浏览器中通过 GET 方式访问这个地址,之后我们加上 POST 请求的例子),根据 Http 协议的规则会去访问对应的 IP 的主机,启动后的 Tomcat 会监听设置的端口(如果没有更改,默认的端口号就是8080),监听到对本地主机这个端口的访问后根据路径——项目工程名 + url-mapping映射 来匹配所请求地址对应的 Servlet。这样,我们的请求就发送到了想要到达的位置,接下来看 Servlet 怎么响应这个请求。
我们最常用的 Servlet 都是继承自HttpServlet,这种 Servlet 能够响应 GET、POST两种请求 。还记得我们上一篇中创建 FirstServlet 时候提醒你留意看一下的那个图吗,再贴一下:
创建 Servlet 时 Eclipse 默认要重写父类方法 doGet、doPost。doGet 方法就是专门用来响应 GET 请求(从我们开始说 Servlet 开始,一直用的都是 GET 请求,POST 请求我们会在之后出现,并给出我的一个 Android 和服务器进行POST交互的完整例子);doPost 方法专门用来响应 POST 请求的。GET、POST是两种最常用的网络请求方式,只是使用的方法不同,各有优劣罢了,没有这方面基础知识的同学直接去百度,比较容易理解。
由于还没有说到 POST,我们就先以 GET 请求为例来说说 Servlet 是怎么响应我们的请求的:
在上边的问题中我们已经知道怎么请求到一个特定的 Servlet,下面我们来完成 Servlet 的响应。接着上一篇中的工程 ServletTest 继续,我们新创建的 FirstServlet 的原始doGet 方法如下:
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
可以看到,doGet 方法有两个入参(doPost方法也一样):一个 HttpServletRequest 的实例对象、一个 HttpServletResponse 的实例对象。其中 HttpServletRequest 就是我们发到 Servlet 的 GET请求(同理,doPost 方法的入参 HttpServletRequest 对象就是发送的 POST 请求),从中可以获取我们发起请求时设置的参数;HttpServletResponse 就是将要返回给客户端或浏览器的响应(至于这个 response 怎么就从这里返回给了请求源,我们在这个问题完了再说),我们在 Servlet 中要做的就是从 request 中获取请求参数,根据业务逻辑进行计算处理,得出结论后把结果赋值到 response 中返回给客户端。至此,一次完整的网络交互就完成了,下面简单举个例子吧:
在下面的代码中,我们模拟了一个最简单的登陆验证的处理过程:
/**
* Servlet implementation class FirstServlet
*/
@WebServlet("/Home/FirstServlet")
public class FirstServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public FirstServlet() {
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String account = request.getParameter("account"); // 从 request 中获取名为 account 的参数的值
String password = request.getParameter("password"); // 从 request 中获取名为 password 的参数的值
System.out.println("account:" + account + "\npassword:" + password); // 打印出来看一看
String result = "";
if("abc".equals(account)
&& "123".equals(password)){ // 模拟登陆账号、密码验证
result = "Login Success!";
}else {
result = "Sorry! Account or password error.";
}
/* 这里我们只是模拟了一个最简单的业务逻辑,当然,你的实际业务可以相当复杂 */
PrintWriter pw = response.getWriter(); // 获取 response 的输出流
pw.println(result); // 通过输出流把业务逻辑的结果输出
pw.flush();
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response); // 默认的代码,意思就是说做doPost和doGet一样
// ***但实质上可行不可行呢?我们接下来或者下一篇就会说到
}
}
Run AS > Run on Server 运行在 Tomcat 中。
接下来,我们在浏览器中拼接一个理想的 GET 请求:http://localhost:8080/ServletTest/Home/FirstServlet?account=abc&password=123
运行结果:
终端 Console 记录的请求参数:
我们再来一个反例:
终端记录:
怎么样,你成功了吗?磨刀不误砍柴工,我们要认真践行标题上说的一步一个脚印,POST 方法放到这个话题完了再详细说。
如果你是一个完全的服务端的小白,做到这里是不是有点小激动呢?终于打通了任督二脉!但是作为一个需要不断学习不断思考的程序猿,你有没有想到 Servlet 怎么就能接收 GET 请求,怎么就能接收 POST 请求呢?怎么就能把 response 返回给请求源呢?下面我们就来看 Servlet 的工作流程。
HttpServlet 中有一个极其重要的方法—— service(HttpServletRequest request, HttpServletResponse response),在 service 方法中获取 request 的请求方式,将 GET 、POST 不同的请求分发给对应的 doGet、doPost 方法,Servlet 的一般流程如下图:
需要注意的是:两个参数HttpServletRequest request 和 HttpServletResponse response 都是 Server 持有的对象,doGet、doPost 方法都是在更改它的内容(所以这两个方法的返回类型都是 void),完成后 Server 将操作完成的 response 返回给请求源。这是 Servlet 的一般流程!
不同的如下图:
Servlet 有一个创建过程,会激发其 init() (这个是 HttpServlet 父类 GenericServlet 的方法)进行初始化。有两个条件都可以激发,这个是我们可以自己设定的,默认是(2)第一次请求 Servlet 并且该 Servlet 还未有实例时进行 init(),也可以在在 web.xml 中
对于更多的客户端请求,Server 创建新的请求和响应对象,仍然激活此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方法。
在上一篇最后的例子中,我们留了一个问题,就是中文乱码(如果你是处女座,不折腾你了,贴心的给你个链接,不用费劲找了)的问题,还记得吗?有没有发现本文在此之前都在避免用中文?下面我们就来搞搞这个问题:
我们把 doGet 中账号密码加点中文,在响应中也加点中文;
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String account = request.getParameter("account"); // 从 request 中获取名为 account 的参数的值
String password = request.getParameter("password"); // 从 request 中获取名为 password 的参数的值
System.out.println("account:" + account + "\npassword:" + password); // 打印出来看一看
String result = "";
if("王x".equals(account)
&& "杰x".equals(password)){ // 添加中文
result = "Login Success!" + "成功了!"; // 响应也加点中文
}else {
result = "Sorry! Account or password error." + "有点问题!"; // 响应也加点中文
}
/* 这里我们只是模拟了一个最简单的业务逻辑,当然,你的实际业务可以相当复杂 */
PrintWriter pw = response.getWriter(); // 获取 response 的输出流
pw.println(result); // 通过输出流把业务逻辑的结果输出
pw.flush();
}
确保服务器程序更新(一般情况下第一次运行成功后,之后的代码更改在保存后会自动执行)后,在浏览器请求
http://localhost:8080/ServletTest/Home/FirstServlet?account=王x&password=杰x
我们可以看到,相应结果英文部分是对的——判断的结果是 true,说明从请求到逻辑处理是理想的,作为辅证,我们看服务端收到的请求参数记录:
到这里,我们就找出了乱码问题的根源——就是响应过程中出现问题,而中文乱码问题一般都是编码格式引发的。所以我们这里在添加一行:
// ......之前的代码片就不用贴了
response.setContentType("text/html;charset=utf-8"); // 设置响应报文的编码格式
PrintWriter pw = response.getWriter(); // 获取 response 的输出流
pw.println(result); // 通过输出流把业务逻辑的结果输出
pw.flush();
更改成功生效后,再次请求:
*注意* 一般我们在正常使用中,都会直接设置 request、response 的编码格式,以防由于编码问题引发的不必要的麻烦,如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/* 先设置请求、响应报文的编码格式 */
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// .....再进行我们的逻辑处理
}
是不是有同学会想到,如果每个 Servlet 都要这样写,doGet、doPost 都要手动写,是不是很麻烦也很烦人呐?是的!是挺烦人的。有没有什么好的办法解决这个问题呢?答案肯定是有的,那就是 Servlet 过滤器 Filter(包 javax.servlet.Filter 下),这个我们后边会说到。本篇先到这里啦!
水平有限,如有不足和错误,敬请斧正,_程序猿大人_在此谢过!