本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多码农和想成为码农的人。
本文转发自头条号【普通的码农】的文章,大家可以关注一下,直接在今日头条的移动端APP中阅读。因为平台不同,会出现有些格式、图片、链接无效方面的问题,我尽量保持一致。
还记得我们最早的第一个Servlet应用吗?它实在是有点low了。前面两篇文章我总结了一下自己对Servlet核心原理以及Servlet主要接口的心得体会,本篇文章就使用Servlet技术来做一个有实际意义一点的例子。
至于JDK、Eclipse、Tomcat的下载和安装,就不多说了。
然后是在Eclipse中新建Java Web工程以及配置Servlet API库等,可以参考这篇文章。
我比较关注租房中介这一个行业,就做一个租房网吧,项目名就叫house-renter吧。
新建好的工程如下图:
我们的Web应用的上下文根路径是默认的:house-renter。
首先,租房网要有一个用户登录界面,这个界面对所有用户来说都是一样的,所以可以做成静态页面,我们就命名该页面为login.html吧。
然后,用户登录请求需要设计一个Servlet来专门处理,就命名该Servlet为LoginServlet吧(或许也可以叫UserServlet,抑或AccountServlet等等,随你喜欢了)。
用户登录之后,后台系统对该用户进行大数据分析画像、人工智能推荐(总之是采用各种高精尖的前沿技术啦,就这么一说,反正真正的系统是包含这些的,我们这就没有了)、精准匹配到该用户感兴趣的房源,把它们展现给该用户。于是需要一个房源列表页面,但该页面是动态的,即每个用户都有自己感兴趣的房源。所以,我们也需要设计一个Servlet来动态生成该页面,就命名该Servlet为HouseServlet吧。
该用户浏览自己感兴趣的房源列表,可以点击某房源,展示其详细信息,即我们要一个房源详细信息页面。暂时也让HouseServlet来动态生成吧,毕竟是跟房子有关的,以后觉得有问题再逐步优化。
最后,为了演示,我们在房源详细信息页面中增加一个编辑按钮,这样,后台维护人员可以编辑我们的房源详细信息。当然,这样的操作并不是所有用户都有权限执行的,所以需要用户的操作权限认证,不过,我们暂且不实现这个功能。这样,我们还需要房源详细信息的编辑页面以及编辑的提交请求需要处理,暂时也让HouseServlet来动态生成吧,毕竟还是跟房子有关。
然后,我们把房源信息保存在House这个实体类中,即我们需要:
• 静态页面:login.html
• Servlet:LoginServlet、HouseServlet
• 实体类:House
我们可以使用Eclipse的New工具建立HTML文件,这样高效很多,当然也可以建立空白文件然后手动敲所有代码。
租房网 - 登录
我是使用Eclipse的New工具生成HTML 5模板的文件,除了标签和标签的内容作了修改,其他都没有变。
登录页面很简单,就是一个表单,关于HTML的基本知识,参考这篇文章。
将登录页面放在WebContent节点下,这样我们的Web应用可以直接访问到。需要关注的是这个表单提交的路径是login.servlet,这就意味着我们需要将LoginServlet配置成映射到的相对URL为“/login.servlet”。当然,现在我们这个LoginServlet还不存在,因此登录会出错。
在Eclipse中Publish该应用到Tomcat中,并启动Tomcat,然后使用浏览器访问:http://localhost:8080/house-renter/login.html
输入用户名和密码,或者不输入,直接点击登录,提示出错找不到/house-renter/login.servlet对应的展示:
我们也可以使用Eclipse的New工具直接新建一个Servlet,我把无用的代码删除,并添加登录的业务逻辑之后是这样的:
package houserenter.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login.servlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
//这里需要验证用户是否已经注册,省略
System.out.println("userName: " + userName + ", password: " + password);
//用户登录成功,重定向到房源列表页面
response.sendRedirect("house.html?userName=" + userName);
}
}
前面login.html页面中表单有用户名和密码两个参数,我们可以用HttpServletRequest的getParameter()方法获取它们的值。这样我们就能拿它们来做验证了,如果系统数据库中有表示已经注册过,可以登录;反之则不予登录。
不过我这省略了验证这一步,将页面直接重定向到房源列表页面。关于重定向,我们后面再讨论,可以简单理解为,它会通知浏览器,让它从原来页面的基础上再重新访问新的页面,因此,浏览器地址栏中的地址会自动变成新页面的URL。
这里还需要关注的重要的一点就是:我重定向的新页面URL中是带有参数的,就是问号后面的userName这个参数。为什么要这样呢?这就是为了会话跟踪,因为HTTP协议本身是无状态的,它并不清楚各个请求之间有无关联,而现在我需要让房源列表页面的请求知道是哪一个用户的请求,从而该请求提交到HouseServlet的时候能查找该用户感兴趣的房源。当然,这只是会话跟踪的其中一种技术,由于是在URL上做文章,因此它被称作URL重写。
我们现在在Eclipse中重新发布应用并启动Tomcat(实际上可以不用停止Tomcat,Eclipse中只要保存修改,就会自动重新编译并发布应用),像上面那样登录之后,浏览器中提示:
现在提示的是找不到/house-renter/house.html对应的展示,不过浏览器地址栏却已经自动变成了:http://localhost:8080/house-renter/house.html?userName=a
Eclipse控制台中打印出了我们用来调试或者说是测试的日志:
直接上代码:
package houserenter.servlet;
import java.io.IOException;
import java.io.PrintWriter;
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("/house.html")
public class HouseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");
if (userName == null || userName.isEmpty()) {
System.out.println("invalid user!");
response.sendRedirect("login.html");
}
//查找该用户感兴趣的房源,这里省略
System.out.println("userName: " + userName + " access house.html!");
PrintWriter writer = response.getWriter();
//因为我们要返回的是HTML页面
writer.println("");
writer.println("");
writer.println("");
writer.println("");
writer.println("租房网 ");
writer.println("");
writer.println("");
writer.println("你好,"+userName+"!欢迎来到租房网! 退出
");
writer.println("
");
writer.println("共找到你感兴趣的房源2条
");
writer.println("大兴区金科嘉苑3-2-1201
");
writer.println("大兴区万科橙9-1-501
");
writer.println("");
writer.println("");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
注意,这里我们配置HouseServlet的URL映射模式是“/house.html”,是与重定向的URL相匹配的。
前面重定向的URL中由于有了用户信息,所以请求分派到此URL中,我们就能得知是哪个用户要请求感兴趣的房源列表。事实上,这种技术来验证用户和跟踪会话实在是不安全,因为它会把数据暴露在URL上。
当然,我这里只是为简单起见而采用这种方式。接下来是如果URL中没有携带用户名,则视为尚未登录,从而重定向到登录页面。
我省略了查找该用户感兴趣的房源,直接HttpServletResponse的getWriter()方法获取到填充响应内容的IO流,借助它就可以往响应填充HTML页面内容了。
现在在浏览器访问租房网,登录后显示:
虽然能重定向到房源列表页面了,但很不幸,出现了很多乱码,显然这是由于页面中包含中文,是由于字符集的问题引起的。
这里,我们重新捋一下导致中文乱码的几个节点:
• 首先,中文是直接写在代码里的,所以源代码文件(包括任何语言的,Java、HTML、JavaScript等)的存储采用的字符集是一个节点。
• 其次,填充响应的Writer需要从源代码文件读取硬编码的中文字符,因此,它以什么字符集来读取(当然必须是与存储字符集一致才行,除非直接使用面向字节的IO流)又是一个节点。
• 再次,浏览器端收到响应后,如果响应是基于字符的,那响应的报文是采用何种字符集的。这里分两种情况,一种就是Servlet容器直接读取静态资源(比如HTML),Servlet容器读取时采用的字符集就是响应报文的字符集;另一种是上面的Writer动态生成的资源,这时响应报文的字符集就是Writer采用的字符集。
• 最后,浏览器采用何种字符集来解析响应报文。这里又分两种情况,一种是浏览器一般都有自己默认的字符集配置;另一种是响应报文中告诉浏览器该采用何种字符集,比如HTML中的标签,或者HTTP首部Content-Type的值是"text/html;charset=UTF-8"。
好,根据这几个节点因素,我首先确认了自己的源代码文件的存储字符集是UTF-8的,返回的响应报文中已经使用告诉浏览器要用UTF-8字符集来解析。
现在唯一还有疑问的就是响应报文本身是否就是UTF-8来发送的,通过百度查一下HttpServletResponse返回的Writer默认采用的字符集是ISO-8859-1。
所以我们应该在代码中修改Writer的编码格式为UTF-8:
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
好像还必须在getWriter()方法之前设置字符集,否则没有效果,这就有点坑了,大家可以试试。
加了这句代码之后,效果如下:
嗯,还真有点像那么回事了,只不过界面丑陋了一点,不过这个可以使用前端三剑客中的CSS来解决。
大家还可以测试一下退出登录的按钮,以及直接在浏览器中手动输入URL“http://localhost:8080/house-renter/house.html”来访问房源列表的效果。
现在租房网的基本雏形已经有了,还剩下房源详细信息页面、房源信息编辑还没有实现,这些留到下一篇文章再继续介绍,现在的工程结构是这样的:
• 静态页面也是还有用处的,并非所有页面都要动态生成;
• Servlet的业务职责要分开,比如我们设计了登录的和房源的两个Servlet;
• 会话跟踪可以采用URL重写技术;
• 页面跳转可以采用重定向技术;
• 中文乱码问题都是字符集惹的祸,要考虑整个链条的所有节点,从源代码的存储格式,到工具(Tomcat或API库中的Writer)的读取、报文的传输格式,再到浏览器的配置或者资源告诉浏览器要采用何种字符集;
• 一般Web中采用UTF-8。