本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多码农和想成为码农的人。
本文转发自头条号【普通的码农】的文章,大家可以关注一下,直接在今日头条的移动端APP中阅读。因为平台不同,会出现有些格式、图片、链接无效方面的问题,我尽量保持一致。
文章目录
介绍
上篇文章介绍了JSP的核心原理,本篇文章准备介绍JSP的初步使用。
大家一定要自己动手编写代码,运行验证,只有这样才能理解的更加透彻,印象也更加深刻。不知道大家有没有这样一种体会,就是明明自己看了不少技术相关的书籍,但是每到用的时候就忘记了相关技术如何使用,还是需要不断的上网搜索。
这还是说明我们动手实践比较少。在我看来,任何一门科学、技术、学问、专业都离不开以下四个环节:
• 看资料、跟师傅,学习前人发现/发明的正确理论、总结的正确经验,系统化地和碎片化地(比如上课、报班、看书、互联网上的资讯和专栏等等)。
• 身体力行,动手实践。
• 经常思考和总结,多问几个为什么,多想想如何能更快更好的解决问题,想想自己是否能发明或发现新概念、新理论、新模型、新体系、新方法等等,可以利用发散思维、逆向思维等等。
• 不断记录自己的学习体会、实践的过程和结论、思考和总结的过程和成果,可以简单的记录,也可以系统的记录,甚至可以写成书。
任何一门科学、技术、学问、专业都不是一日之功,需要长期的坚持这四个环节,所以必须从小就灌输,养成学、做、思、记的习惯。这四个环节是互相影响、互相交叉、互相促进、需要同时进行,不可偏废的。正所谓“纸上得来终觉浅,绝知此事要躬行”、“学而不思则罔,思而不学则殆”、“好记性不如烂笔头”等等。
虽说这四个环节不可偏废,但各门科学、技术、学问、专业还是有所侧重的,比如科学这个词感觉就偏理论学习和思考多一些,动手实践少一些;而技术这个词就相反。再比如工科偏动手实践多一些,理科偏理论研究多一些等等。
不好意思,又跑题。说那么多无非就是想说我们需要多敲敲代码。软件/编程这一行业被划分到工科,那就说明我们更要多动手实践。话说这应该是实践成本最低的工科行业了,只要一台个人电脑即可,还有各种开源的软件可用,要是建筑、土木、工业制造、各种勘探、化工、电力等等,要么需要去实地、要么需要建个实验室、要么需要买个机床。。。。。。
好了,不说废话了。我们仍然以前面开发的租房网应用(可以参考这篇文章和这篇文章)为基础,使用JSP技术来改造,至少需要达到的一个目标是让代码看起来更加清爽。
租房网现状
工程结构:
静态资源 - 登录页面login.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网 - 登录</title>
</head>
<body>
<form action="login.servlet" method="post">
<label for="user_name">用户名:</label><input type="text" id="user_name" name="userName" />
<label for="password">密码:</label><input type="password" id="password" name="password" />
<input type="submit" value="登录" />
</form>
</body>
</html>
房源实体类 - House.java:
package houserenter.entity;
public class House {
private String id;
private String name;
private String detail;
public House(String id, String name, String detail) {
super();
this.id = id;
this.name = name;
this.detail = detail;
}
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 getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
@Override
public String toString() {
return "House [id=" + id + ", name=" + name + ", detail=" + detail + "]";
}
}
处理登录请求的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 {
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);
}
}
处理房源相关请求(房源查找、房源详情、房源编辑)的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 {
private static final long serialVersionUID = 1L;
private List<House> mockHouses;
@Override
public void init() {
mockHouses = new ArrayList<House>();
mockHouses.add(new House("1", "金科嘉苑3-2-1201", "详细信息"));
mockHouses.add(new House("2", "万科橙9-1-501", "详细信息"));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");
PrintWriter writer = response.getWriter();
writer.println("<!DOCTYPE html>");
writer.println("<html>");
writer.println("<head>");
writer.println("<meta charset=\"UTF-8\">");
writer.println("<title>租房网</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("<h1>你好,"+userName+"!欢迎来到租房网! <a href=\"login.html\">退出</a></h1>");
writer.println("<br><br>");
String houseId = request.getParameter("houseId");
String editHouse = request.getParameter("editHouse");
if (houseId == null || houseId.isEmpty()) {
//查找该用户感兴趣的房源,这里省略
System.out.println("userName: " + userName + " access house.html!");
writer.println("<h6>共找到你感兴趣的房源 "+mockHouses.size()+" 条</h6>");
writer.println("<ul>");
for (House house : mockHouses) {
writer.println("<li><h2><a href=\"house.html?userName="+userName+"&houseId="+house.getId()+"\">"+house.getName()+"</a></h2></li>");
}
writer.println("</ul>");
} else if (editHouse == null) {
//根据houseId查找该房源的详细信息
System.out.println("userName: " + userName + " access house.html for house detail!");
House target = null;
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
target = house;
break;
}
}
writer.println("<h2>"+target.getName()+"<a href=\"house.html?userName="+userName+"&houseId="+houseId+"&editHouse=true\">编辑</a></h2>");
writer.println("<h3>"+target.getDetail()+"</h3>");
writer.println("<h4><a href=\"house.html?userName="+userName+"\">回到列表</a></h4>");
} else {
//存在editHouse参数,返回指定房源的编辑页面
System.out.println("userName: " + userName + " access house.html to edit house!");
House target = null;
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
target = house;
break;
}
}
//writer.println("<form action=\"house.html?userName="+userName+"&houseId="+houseId+"\" method=\"post\">");
writer.println("<form action=\"house.html\" method=\"post\">");
writer.println("<input type=\"hidden\" name=\"userName\" value=\""+userName+"\"/>");
writer.println("<input type=\"hidden\" name=\"houseId\" value=\""+houseId+"\"/>");
writer.println("<label for=\"house_name\">房源名字:</label><input type=\"text\" id=\"house_name\" name=\"houseName\" value=\""+target.getName()+"\" />");
writer.println("<label for=\"house_detail\">房源详细信息:</label><input type=\"text\" id=\"house_detail\" name=\"houseDetail\" value=\""+target.getDetail()+"\" />");
writer.println("<input type=\"submit\" value=\"提交\" />");
writer.println("</form>");
}
writer.println("</body>");
writer.println("</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");
String houseId = request.getParameter("houseId");
//获取提交的房源信息,并保存
System.out.println("userName: " + userName + " access house.html to save house detail!");
String houseName = request.getParameter("houseName");
String houseDetail = request.getParameter("houseDetail");
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
house.setName(houseName);
house.setDetail(houseDetail);
break;
}
}
response.sendRedirect("house.html?userName="+userName+"&houseId="+houseId);
}
}
设置请求响应编码、登录验证的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 {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String userName = httpServletRequest.getParameter("userName");
if (userName == null || userName.isEmpty()) {
System.out.println("invalid user!");
httpServletResponse.sendRedirect("login.html");
} else {
chain.doFilter(request, response);
}
}
}
思路
登录页面和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(
urlPatterns = {
"/houses.jsp",
"/house-details.jsp",
"/house-form.jsp",
"house-form.servlet"
})
public class MySecondFilter implements Filter {
private List<House> mockHouses;
@Override
public void init(FilterConfig filterConfig) {
System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
mockHouses = new ArrayList<House>();
mockHouses.add(new House("1", "金科嘉苑3-2-1201", "详细信息"));
mockHouses.add(new House("2", "万科橙9-1-501", "详细信息"));
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("BBBBBBBBBBBBBBBBBBBBBBBBBBBB");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Object obj = httpServletRequest.getAttribute("mockHouses");
if (obj == null) {
httpServletRequest.setAttribute("mockHouses", mockHouses);
}
String userName = httpServletRequest.getParameter("userName");
if (userName == null || userName.isEmpty()) {
System.out.println("invalid user!");
httpServletResponse.sendRedirect("login.html");
} else {
chain.doFilter(request, response);
}
}
}
上面的代码是我用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的映射配置是一致的。
这里有一个小诀窍,就是写一部分代码验证一部分代码,不必整个页面编写完毕才验证,当然我们这几个页面都比较简单,也可以全部编写完毕再验证。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="houserenter.entity.House" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网</title>
</head>
<body>
<h1>你好,${param.userName}!欢迎来到租房网! <a href="login.html">退出</a></h1>
<br><br>
<%
List<House> mockHouses = (List<House>) request.getAttribute("mockHouses");
System.out.println(mockHouses);
%>
<h6>共找到你感兴趣的房源 <%=mockHouses.size() %> 条</h6>
<ul>
<%for (House house : mockHouses) {
System.out.println(house); %>
<li><h2><a href="house-details.jsp?userName=${param.userName}&houseId=<%=house.getId() %>"><%=house.getName() %></a></h2></li>
<%} %>
</ul>
</body>
</html>
需要提一下:
• 我把编码格式都改成了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代码了,那就直接上代码吧。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="houserenter.entity.House" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网</title>
</head>
<body>
<h1>你好,${param.userName}!欢迎来到租房网! <a href="login.html">退出</a></h1>
<br><br>
<%
List<House> mockHouses = (List<House>) request.getAttribute("mockHouses");
String houseId = request.getParameter("houseId");
House target = null;
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
target = house;
break;
}
}
%>
<h2><%=target.getName() %><a href="house-form.jsp?userName=${param.userName }&houseId=<%=target.getId() %>">编辑</a></h2>
<h3><%=target.getDetail() %></h3>
<h4><a href="houses.jsp?userName=${param.userName }">回到列表</a></h4>
</body>
</html>
还是主要用了JSP脚本、JSP表达式和EL表达式三个技术。
不过,JSP脚本看着似乎有点长。
房源编辑表单页面 - house-form.jsp
还是直接上代码。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="houserenter.entity.House" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网</title>
</head>
<body>
<h1>你好,${param.userName}!欢迎来到租房网! <a href="login.html">退出</a></h1>
<br><br>
<%
List<House> mockHouses = (List<House>) request.getAttribute("mockHouses");
String houseId = request.getParameter("houseId");
House target = null;
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
target = house;
break;
}
}
%>
<form action="house-form.servlet" method="post">
<input type="hidden" name="userName" value="${param.userName}"/>
<input type="hidden" name="houseId" value="<%=target.getId() %>"/>
<label for="house_name">房源名字:</label><input type="text" id="house_name" name="houseName" value="<%=target.getName() %>" />
<label for="house_detail">房源详细信息:</label><input type="text" id="house_detail" name="houseDetail" value="<%=target.getDetail() %>" />
<input type="submit" value="提交" />
</form>
</body>
</html>
注意,此时表单的提交路径是: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 {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<House> mockHouses = (List<House>) request.getAttribute("mockHouses");
String houseId = request.getParameter("houseId");
House target = null;
for (House house : mockHouses) {
if (houseId.equals(house.getId())) {
target = house;
break;
}
}
String houseName = request.getParameter("houseName");
target.setName(houseName);
String houseDetail = request.getParameter("houseDetail");
target.setDetail(houseDetail);
String userName = request.getParameter("userName");
response.sendRedirect("house-details.jsp?userName=" + userName + "&houseId=" + houseId);
}
}
至此,全部JSP页面和Servlet代码已经编写完毕,大家可以自行验证,应该没有问题。
进一步改进 - include.jsp
虽然已经完成了全部工作,运行也没有问题,但是很明显,三个JSP页面中开头的很大一部分都是重复的,这时候JSP中的include指令就排上用场了。
我们把这部分重复的提取出来,形成一个独立的JSP文件:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="houserenter.entity.House" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网</title>
</head>
<body>
<h1>你好,${param.userName}!欢迎来到租房网! <a href="login.html">退出</a></h1>
<br><br>
然后改造三个JSP页面,比如房源列表页面houses.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="include.jsp"%>
<%
List<House> mockHouses = (List<House>) request.getAttribute("mockHouses");
System.out.println(mockHouses);
%>
<h6>共找到你感兴趣的房源 <%=mockHouses.size() %> 条</h6>
<ul>
<%for (House house : mockHouses) {
System.out.println(house); %>
<li><h2><a href="house-details.jsp?userName=${param.userName}&houseId=<%=house.getId() %>"><%=house.getName() %></a></h2></li>
<%} %>
</ul>
</body>
</html>
直接使用include指令:
<%@ include file="include.jsp"%>
但是,要注意,房源列表页面houses.jsp中仍然需要下面的page指令,否则会有中文乱码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
实际上,include指令也是由Servlet容器在将JSP页面生成Servlet代码的过程中解析,并用包含的JSP页面的内容替换该指令,完全是解析阶段发生的故事。
最后,我们的租房网的工程结构变成:
总结
虽然我们的租房网还不够完美,但总算比之前纯粹使用Servlet的时候干净清爽多了,页面结构也比较清晰合理。
当然,还有很多需要改进的地方,读者朋友们自己可以思考思考。
• 养成学、做、思、记的习惯;我写这些文章,也就是记,鼓励大家也多记;
• JSP的本质就是由Servlet/JSP容器转换成Servlet代码;
• Servlet代码中HTML内容需要writer括起来;而JSP页面中Java代码需要<%%>括起来;
• 加打印日志的代码,便于调试;
• 一边开发、一边测试/验证;
• EL表达式的基本语法是用 ${ 和 } 把内容括起来;
• JSP和EL都有隐式对象,实际上是Servlet/JSP容器生成并传进来的;
• 要有演化思维,或迭代思维,或最小可用产品思维;
• 再次运用消除重复思维,重复的地方总是可以改进的。