首页 > 科技 > Java Web轻松学40 - JSP初步使用

Java Web轻松学40 - JSP初步使用

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。

目录

  1. 介绍
  2. 租房网现状
  3. 思路
  4. 共享模拟数据的Filter
  5. 房源列表页面 - houses.jsp
  6. 验证
  7. 房源详情页面 - house-details.jsp
  8. 房源编辑表单页面 - house-form.jsp
  9. 处理房源编辑表单的提交 - HouseFormServlet
  10. 进一步改进 - include.jsp
  11. 总结

介绍

上篇文章介绍了JSP的核心原理,本篇文章准备介绍JSP的初步使用。

大家一定要自己动手编写代码,运行验证,只有这样才能理解的更加透彻,印象也更加深刻。不知道大家有没有这样一种体会,就是明明自己看了不少技术相关的书籍,但是每到用的时候就忘记了相关技术如何使用,还是需要不断的上网搜索。

这还是说明我们动手实践比较少。在我看来,任何一门科学、技术、学问、专业都离不开以下四个环节:

  • 看资料、跟师傅,学习前人发现/发明的正确理论、总结的正确经验,系统化地和碎片化地(比如上课、报班、看书、互联网上的资讯和专栏等等)。
  • 身体力行,动手实践。
  • 经常思考和总结,多问几个为什么,多想想如何能更快更好的解决问题,想想自己是否能发明或发现新概念、新理论、新模型、新体系、新方法等等,可以利用发散思维、逆向思维等等。
  • 不断记录自己的学习体会、实践的过程和结论、思考和总结的过程和成果,可以简单的记录,也可以系统的记录,甚至可以写成书。

任何一门科学、技术、学问、专业都不是一日之功,需要长期的坚持这四个环节,所以必须从小就灌输,养成学、做、思、记的习惯。这四个环节是互相影响、互相交叉、互相促进、需要同时进行,不可偏废的。正所谓“纸上得来终觉浅,绝知此事要躬行”、“学而不思则罔,思而不学则殆”、“好记性不如烂笔头”等等。

虽说这四个环节不可偏废,但各门科学、技术、学问、专业还是有所侧重的,比如科学这个词感觉就偏理论学习和思考多一些,动手实践少一些;而技术这个词就相反。再比如工科偏动手实践多一些,理科偏理论研究多一些等等。

不好意思,又跑题。说那么多无非就是想说我们需要多敲敲代码。软件/编程这一行业被划分到工科,那就说明我们更要多动手实践。话说这应该是实践成本最低的工科行业了,只要一台个人电脑即可,还有各种开源的软件可用,要是建筑、土木、工业制造、各种勘探、化工、电力等等,要么需要去实地、要么需要建个实验室、要么需要买个机床。。。。。。

好了,不说废话了。我们仍然以前面开发的租房网应用(可以参考这篇文章和这篇文章)为基础,使用JSP技术来改造,至少需要达到的一个目标是让代码看起来更加清爽。

租房网现状

工程结构:

静态资源 - 登录页面login.html:





租房网 - 登录


t

tt
tt
tt
t>


房源实体类 - House.java:

package houserenter.entity;
public class House {
tprivate String id;
tprivate String name;
tprivate String detail;
tpublic House(String id, String name, String detail) {
ttsuper();
ttthis.id = id;
ttthis.name = name;
ttthis.detail = detail;
t}
tpublic String getId() {
ttreturn id;
t}
tpublic void setId(String id) {
ttthis.id = id;
t}
tpublic String getName() {
ttreturn name;
t}
tpublic void setName(String name) {
ttthis.name = name;
t}
tpublic String getDetail() {
ttreturn detail;
t}
tpublic void setDetail(String detail) {
ttthis.detail = detail;
t}
t@Override
tpublic String toString() {
ttreturn "House [id=" + id + ", name=" + name + ", detail=" + detail + "]";
t}
}

处理登录请求的Servlet - LoginServlet.java:

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 {
tprivate static final long serialVersionUID = 1L;
tprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ttString userName = request.getParameter("userName");
ttString password = request.getParameter("password");
tt
tt//这里需要验证用户是否已经注册,省略
ttSystem.out.println("userName: " + userName + ", password: " + password);
tt
tt//用户登录成功,重定向到房源列表页面
ttresponse.sendRedirect("house.html?userName=" + userName);
t}
}

处理房源相关请求(房源查找、房源详情、房源编辑)的Servlet - HouseServlet.java:

package houserenter.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
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 houserenter.entity.House;
@WebServlet("/house.html")
public class HouseServlet extends HttpServlet {
tprivate static final long serialVersionUID = 1L;
t
tprivate List mockHouses;
t@Override
tpublic void init() {
ttmockHouses = new ArrayList();
ttmockHouses.add(new House("1", "金科嘉苑3-2-1201", "详细信息"));
ttmockHouses.add(new House("2", "万科橙9-1-501", "详细信息"));
t}
tprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ttString userName = request.getParameter("userName");
ttPrintWriter writer = response.getWriter();
tt
ttwriter.println("");
ttwriter.println("");
ttwriter.println("");
ttwriter.println("");
ttwriter.println("租房网");
ttwriter.println("");
ttwriter.println("");
tt
ttwriter.println("

你好,"+userName+"!欢迎来到租房网! 退出

");
ttwriter.println("

");
tt
ttString houseId = request.getParameter("houseId");
ttString editHouse = request.getParameter("editHouse");
tt
ttif (houseId == null || houseId.isEmpty()) {
ttt//查找该用户感兴趣的房源,这里省略
tttSystem.out.println("userName: " + userName + " access house.html!");
tttwriter.println("
共找到你感兴趣的房源 "+mockHouses.size()+" 条
");
tttwriter.println("
    ");
    tttfor (House house : mockHouses) {
    ttttwriter.println("
  • "+house.getName()+"

  • ");
    ttt}
    tttwriter.println("
");
tt} else if (editHouse == null) {
ttt//根据houseId查找该房源的详细信息
tttSystem.out.println("userName: " + userName + " access house.html for house detail!");
tttHouse target = null;
tttfor (House house : mockHouses) {
ttttif (houseId.equals(house.getId())) {
ttttttarget = house;
tttttbreak;
tttt}
ttt}
ttt
tttwriter.println("

"+target.getName()+"编辑

");
tttwriter.println("

"+target.getDetail()+"

");
tttwriter.println("

回到列表

");
tt} else {
ttt//存在editHouse参数,返回指定房源的编辑页面
tttSystem.out.println("userName: " + userName + " access house.html to edit house!");
tttHouse target = null;
tttfor (House house : mockHouses) {
ttttif (houseId.equals(house.getId())) {
ttttttarget = house;
tttttbreak;
tttt}
ttt}
ttt//writer.println("");
tttwriter.println("> ");
tttwriter.println("");
tttwriter.println("");
tttwriter.println("");
tttwriter.println("");
tttwriter.println("");
tttwriter.println(">");
tt}
tt
ttwriter.println("");
ttwriter.println("");
t}
t
tprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ttString userName = request.getParameter("userName");
ttString houseId = request.getParameter("houseId");
tt
tt//获取提交的房源信息,并保存
ttSystem.out.println("userName: " + userName + " access house.html to save house detail!");
ttString houseName = request.getParameter("houseName");
ttString houseDetail = request.getParameter("houseDetail");
ttfor (House house : mockHouses) {
tttif (houseId.equals(house.getId())) {
tttthouse.setName(houseName);
tttthouse.setDetail(houseDetail);
ttttbreak;
ttt}
tt}
tt
ttresponse.sendRedirect("house.html?userName="+userName+"&houseId="+houseId);
t}
}

设置请求响应编码、登录验证的Filter - MyFirstFilter.java:

package houserenter.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/house.html")
public class MyFirstFilter implements Filter {
tpublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ttrequest.setCharacterEncoding("UTF-8");
ttresponse.setCharacterEncoding("UTF-8");
tt
ttHttpServletRequest httpServletRequest = (HttpServletRequest) request;
ttHttpServletResponse httpServletResponse = (HttpServletResponse) response;
tt
ttString userName = httpServletRequest.getParameter("userName");
tt
ttif (userName == null || userName.isEmpty()) {
tttSystem.out.println("invalid user!");
ttthttpServletResponse.sendRedirect("login.html");
tt} else {
tttchain.doFilter(request, response);
tt}
t}
}

思路

登录页面和LoginServlet目前还算简单,暂时可以不用改造。

HouseServlet的代码量稍微有点多(也不过一百多行),且夹杂着输出HTML内容,看着比较乱。从功能上来说虽然都与房源有关,但实际上动态生成的呈现给浏览器的是三个页面(房源列表、房源详情、房源编辑),所以可以考虑用三个JSP页面代替,咱就命名为:

  • houses.jsp
  • house-details.jsp
  • house-form.jsp

另外,因为涉及到一个房源编辑的表单,所以需要一个Servlet来处理表单的提交,就叫HouseFormServlet吧。

还有一个要提的是HouseServlet初始化了一些模拟数据,那这部分应该放在哪呢?因为一个JSP页面就相当于一个Servlet,所以原来的HouseServlet现在要改造成三个JSP页面,感觉模拟数据放在哪个JSP页面都不太合适,因为三个JSP页面都要访问同一份模拟数据才行。

于是问题变为:如何在多个JSP页面之间共享访问同一份数据?我先想到的是能不能在一个Filter中初始化模拟数据,然后让它拦截到这三个JSP页面的请求,然后在请求中使用setAttribute()方法将模拟数据挂上去,最后在JSP页面中使用请求的getAttribute()方法取出来。

好了,既然思路有了,那我们就大刀阔斧的干吧。

不过我们可以把之前的代码都保留着,另外编写新添加的JSP页面和Filter即可,当然也可以把之前的都删掉。

共享模拟数据的Filter

这次我们先编写我们的Filter。

在Filter中初始化模拟数据跟之前在HouseServlet中是类似的。Filter也有一个相同的生命周期方法init()(另一个相同的是destroy()方法),大家可以直接看看Filter的源码(如何查看源码可以参考这篇文章)。

不过Filter的这个init()方法是Servlet容器初始化Filter的时候就调用,与Servlet的init()方法在第一个请求到来时才调用是不同的。

Filter的doFilter()方法还是可以跟之前的Filter一样设置请求响应的编码以及进行登录验证,但最主要的是调用请求的setAttribute()方法把模拟数据挂载到该请求中。

在Eclipse中创建Filter还是可以使用New工具(可以参考这篇文章中的新建Java类部分),大家应该很容易知道该怎么填写相关信息,比如Filter的类名、初始化参数、映射(即拦截何种请求)。当然,你也可以新建空白的文件手动敲写所有代码。

package houserenter.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import houserenter.entity.House;
@WebFilter(
tturlPatterns = {
tttt"/houses.jsp",
tttt"/house-details.jsp",
tttt"/house-form.jsp",
tttt"house-form.servlet"
tt})
public class MySecondFilter implements Filter {
tprivate List mockHouses;
t@Override
tpublic void init(FilterConfig filterConfig) {
ttSystem.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
ttmockHouses = new ArrayList();
ttmockHouses.add(new House("1", "金科嘉苑3-2-1201", "详细信息"));
ttmockHouses.add(new House("2", "万科橙9-1-501", "详细信息"));
t}
tpublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ttSystem.out.println("BBBBBBBBBBBBBBBBBBBBBBBBBBBB");
ttrequest.setCharacterEncoding("UTF-8");
ttresponse.setCharacterEncoding("UTF-8");
tt
ttHttpServletRequest httpServletRequest = (HttpServletRequest) request;
ttHttpServletResponse httpServletResponse = (HttpServletResponse) response;
tt
ttObject obj = httpServletRequest.getAttribute("mockHouses");
ttif (obj == null) {
ttthttpServletRequest.setAttribute("mockHouses", mockHouses);
tt}
tt
ttString userName = httpServletRequest.getParameter("userName");
tt
ttif (userName == null || userName.isEmpty()) {
tttSystem.out.println("invalid user!");
ttthttpServletResponse.sendRedirect("login.html");
tt} else {
tttchain.doFilter(request, response);
tt}
t}
}

上面的代码是我用New工具生成之后,把无用的注释、成员方法删掉之后,再加上初始化模拟数据的逻辑、doFilter的逻辑。

因为Filter接口的init()方法和destroy()方法是default的(JDK8才支持),因此可以不必实现。

注意,此Filter拦截的有哪些资源的访问请求!

doFilter的逻辑跟之前的相比,多了一个把mockHouses挂载到请求上去的部分。经过测试,不用强制转换成HttpServletRequest也是可以的。

对了,我在init()方法和doFilter()方法中都加了打印日志的部分,便于调试。

房源列表页面 - houses.jsp

现在开始编写我们的JSP页面,第一个是房源列表。

编写房源列表页面,实际上就是编写Servlet,只不过它们的HTML和Java内容是反着来的。所以你可以参照原来的HouseServlet中是怎样输出HTML的,你就可以把它们拷贝过来,然后把writer.println("")这种去掉,把HTML标签拿出来,而需要Java代码的地方就用JSP语法中的括起来即可。

即编写JSP页面时,你就可以想象着这是在编写Servlet的service()方法(除去JSP的声明,即%>括起来的,和page指令中的import部分以外),只不过HTML内容直接写,而Java代码需要(或)和%>括起来。

但实际上,我们仍然可以使用New工具,从而生成通用的JSP模板页面(工具中可以选择是HTML 5、HTML 4.01、XHTML等等,我选的是HTML5)。

我们把这几个JSP页面都直接放在WebContent节点下,这样跟MySecondFilter的映射配置是一致的。

这里有一个小诀窍,就是写一部分代码验证一部分代码,不必整个页面编写完毕才验证,当然我们这几个页面都比较简单,也可以全部编写完毕再验证。








租房网


你好,${param.userName}!欢迎来到租房网! 退出





tList mockHouses = (List) request.getAttribute("mockHouses");
tSystem.out.println(mockHouses);
%>
共找到你感兴趣的房源 条


    tSystem.out.println(house); %>
    t
  • ">






需要提一下:

  • 我把编码格式都改成了UTF-8,此文件一共三处地方。
  • page指令中导入Java类的部分可以在需要的时候编写,因为开始你也不知道需要什么类啊,后面不能解析类型的变量Eclipse会有提示。
  • 可以看到我用了三种元素:JSP脚本)、JSP表达式)、EL表达式${ }),这回总算清楚它们的基本用法了。
  • 比较奇葩的是for循环部分,尽然可以将前半部分用括起来、中间部分直接写HTML代码、最后一个花括弧也用括起来。不过也容易理解,这些都要被Servlet容器转换成Java代码的。可能看起来有点怪,但你要适应这种风格。
  • JSP表达式可以用在HTML标签的内容上,还可以用在HTML标签属性值的双引号中。EL表达式也是如此。
  • EL表达式的基本语法是用 ${} 把内容括起来,内容一般是访问某个对象的属性,使用点表达式方括号表达式均可,也支持一般的算术运算符、关系运算符、逻辑运算符等等。
  • JSP脚本中的request是JSP的隐式对象,EL表达式中的param是EL中的隐式对象,不过你都可以理解为Servelt容器在转换成Servlet代码时帮你定义的对象。param实际上就等价于request.getParameter()方法,也可以理解为param就是一个包括请求中所有参数的Map。
  • 加了一些打印日志的代码,便于调试。
  • 最后,标签的跳转页面是房源详情页面house.jsp,URL中也可以携带参数。
  • URL中携带的参数与表单中提交的参数,都是使用request.getParameter()来访问的。
  • request.getAttribute()只能访问服务端添加的数据,而不是浏览器端用户发送过来的数据。

验证

我们可以一边开发JSP页面一边进行验证了,Eclispe中启动Tomcat之后也不用关闭,它会自动检测JSP页面和Java代码的变化,进行重新编译和发布,不过有时候修改代码后还是重新发布应用比较踏实。

先别忘了修改我们的LoginServlet重定向的地址:

response.sendRedirect("houses.jsp?userName=" + userName);

OK,从浏览器中访问租房网的登录页面login.html:

随便输入用户名和密码,点击登录:

耶,完全没有问题!继续编写其他两个JSP页面。

房源详情页面 - house-details.jsp

有了前面的分析,相信大家都能够理解一般的JSP代码了,那就直接上代码吧。








租房网


你好,${param.userName}!欢迎来到租房网! 退出





tList mockHouses = (List) request.getAttribute("mockHouses");
tString houseId = request.getParameter("houseId");
tHouse target = null;
tfor (House house : mockHouses) {
ttif (houseId.equals(house.getId())) {
ttttarget = house;
tttbreak;
tt}
t}
%>t

">编辑



回到列表




还是主要用了JSP脚本、JSP表达式和EL表达式三个技术。

不过,JSP脚本看着似乎有点长。

房源编辑表单页面 - house-form.jsp

还是直接上代码。








租房网


你好,${param.userName}!欢迎来到租房网! 退出





tList mockHouses = (List) request.getAttribute("mockHouses");
tString houseId = request.getParameter("houseId");
tHouse target = null;
tfor (House house : mockHouses) {
ttif (houseId.equals(house.getId())) {
ttttarget = house;
tttbreak;
tt}
t}
%>






>


注意,此时表单的提交路径是:house-form.servlet

所以,后面编写HouseFormServlet时,配置其URL映射模式要与此一致。

处理房源编辑表单的提交 - HouseFormServlet

无非就是提取表单提交的数据,然后保存到我们的模拟数据中,最后重定向回该房源的详情页面。

package houserenter.servlet;
import java.io.IOException;
import java.util.List;
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 houserenter.entity.House;
@WebServlet("/house-form.servlet")
public class HouseFormServlet extends HttpServlet {
tprivate static final long serialVersionUID = 1L;
tprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ttList mockHouses = (List) request.getAttribute("mockHouses");
ttString houseId = request.getParameter("houseId");
ttHouse target = null;
ttfor (House house : mockHouses) {
tttif (houseId.equals(house.getId())) {
tttttarget = house;
ttttbreak;
ttt}
tt}
tt
ttString houseName = request.getParameter("houseName");
tttarget.setName(houseName);
tt
ttString houseDetail = request.getParameter("houseDetail");
tttarget.setDetail(houseDetail);
tt
ttString userName = request.getParameter("userName");
ttresponse.sendRedirect("house-details.jsp?userName=" + userName + "&houseId=" + houseId);
t}
}

至此,全部JSP页面和Servlet代码已经编写完毕,大家可以自行验证,应该没有问题。

进一步改进 - include.jsp

虽然已经完成了全部工作,运行也没有问题,但是很明显,三个JSP页面中开头的很大一部分都是重复的,这时候JSP中的include指令就排上用场了。

我们把这部分重复的提取出来,形成一个独立的JSP文件:








租房网


你好,${param.userName}!欢迎来到租房网! 退出





然后改造三个JSP页面,比如房源列表页面houses.jsp



tList mockHouses = (List) request.getAttribute("mockHouses");
tSystem.out.println(mockHouses);
%>
共找到你感兴趣的房源 条


    tSystem.out.println(house); %>
    t
  • ">






直接使用include指令:


但是,要注意,房源列表页面houses.jsp中仍然需要下面的page指令,否则会有中文乱码:


实际上,include指令也是由Servlet容器在将JSP页面生成Servlet代码的过程中解析,并用包含的JSP页面的内容替换该指令,完全是解析阶段发生的故事。

最后,我们的租房网的工程结构变成:

总结

虽然我们的租房网还不够完美,但总算比之前纯粹使用Servlet的时候干净清爽多了,页面结构也比较清晰合理。

当然,还有很多需要改进的地方,读者朋友们自己可以思考思考。

  • 养成学、做、思、记的习惯;我写这些文章,也就是记,鼓励大家也多记;
  • JSP的本质就是由Servlet/JSP容器转换成Servlet代码;
  • Servlet代码中HTML内容需要writer括起来;而JSP页面中Java代码需要括起来;
  • 加打印日志的代码,便于调试;
  • 一边开发、一边测试/验证;
  • EL表达式的基本语法是用 ${} 把内容括起来;
  • JSP和EL都有隐式对象,实际上是Servlet/JSP容器生成并传进来的;
  • 要有演化思维,或迭代思维,或最小可用产品思维;
  • 再次运用消除重复思维,重复的地方总是可以改进的。

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.sosokankan.com/article/2217607.html

setTimeout(function () { fetch('http://www.sosokankan.com/stat/article.html?articleId=' + MIP.getData('articleId')) .then(function () { }) }, 3 * 1000)