可能很多人看到标题都不会点进来,因为 JSP 这种老掉牙的技术很多人根本不学,所以我有些感想写在下面。
这是在学校选课老师让做的实验报告,可能大家会觉得这些东西毫无意义,因为 JSP 早就没人使用了,原因是因为写页面太繁琐,执行速度慢,消耗内存,响应速度慢不能处理高并发等原因;但是我想觉得不能因为他现在被淘汰了就不去学他,更不能抱着轻蔑的态度去学习这门技术,我自己在学习的过程中一直在惊叹 JSP 太强了,真的简化了很多后台开发,还有那些标签技术,真的是独具匠心,发明出这项技术的人真的非常厉害。现在越来越多的 Java 开发相关人员上来直接学习 SpringBoot 等框架,然后快速开发出一个网页,看起来很厉害,但这是不对的,也是错误的,我也学过 Spring 和 SpringBoot 等流行框架,但是现在还是在老老实实的跟着学校的进度学习 JSP,因为我觉得经典的东西一定有学习的价值,只有搞懂基础才能提高境界,这些高大上的框架确实极大的简化了我们的开发,但是有没有想过,如果你一直学习这些别人封装好的框架,其实你根本没有一点核心竞争力,这些东西你会,别人一样也可以学习,而且也是速成,所以我们要掌握基础,学习底层,这样就算别人想超过你也要付出很长时间,久而久之你就有你的核心竞争力了,所以,不要看不起任何一项技术,每一项技术都有他存在的意义。其实仔细想想,我们到底会什么?全都是用的别人封装好的框架,我们只会调用接口,我们写 Java 程序调用 JDK 的接口,然后 Servlet 封装了 JDK,接着 Spring 封装了 Servlet ,简化我们的开发,后来 SpringBoot 进而封装了 Spring 框架,让我们开发网页触手可得······我相信将来还会有一层一层的封装,到最后我们写网页可能是几行代码就搞定了,那个时候可能有的 Java 程序员看似写了一个网页,他可能都不知道 JDK 是什么(夸张的比喻一下),因为封装太多太多层了。换个视角,我甚至觉得人类本来就是调用接口生活,比如我们想吃饭,不想自己做,怎么办?好办,调用美团外卖的接口,传入参数20元,返回一份盖浇饭。说了那么多,就想表明一个观点,就是调用接口虽然方便,但是谁都会调,请问你的核心竞争力是什么?当然我也是个巨菜,没有核心竞争力可言,目前在阅读 JDK 源码和学习算法,感兴趣的朋友可以一起阅读 源码 和算法 交流。
下面开始正文。
实验一 Servlet基础操作
先来看一下最终效果:
一、基础功能
1、项目结构
首先来看一下项目的整体结构:
2、数据初始化
首先是数据的初始化,这里为了使 Servlet 容器能在一开始就加载数据,我选择在注解中进行了如下配置:
@WebServlet(name = "ShoppingCartServlet",
urlPatterns = {
"/shop/products",
"/shop/details",
"/shop/addCart",
"/shop/deleteItem",
"/shop/clearCart"},
loadOnStartup = 1)
其中 urlPatterns 为匹配的路径。
这样在一开始就可以加载在 init
方法中的数据了。
/** * 数据的初始化 * * @param config 配置参数,可以获取应用作用域 * @throws ServletException 抛出异常 */
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 装载数据
Product p1 = new Product
(1, "单反相机", "最没有性价比的单反相机", 3306f);
Product p2 = new Product
(2, "双反相机", "最不值的双反相机", 3307f);
Product p3 = new Product
(3, "三反相机", "最难看的三反相机", 3308f);
Product p4 = new Product
(4, "四反相机", "最花里胡哨的四反相机", 3309f);
List<Product> list = new ArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
// 保存到应用作用域中
config
.getServletContext()
.setAttribute("products", list);
}
因为这些数据是所有的应用都会用到的,所以把它放在 context
上下文域中,代替了从数据库查询数据。
3、主页
然后我们访问页面,会自动跳转到 productList
页面,来看一下 index
页面的代码:
<body>
<c:redirect url="shop/productList.jsp"/>
</body>
就只有一行代码,是一个重定向。
访问之后的页面如图:
我们可以选择喜欢的商品,也可以查看购物车。
4、商品详情
这里我们先点进去商品详情:
请求地址为:
http://localhost:8080/WsShoppingCart/shop/details?id=1
这里拼接了一个 id ,我们可以在 Servlet
中取出来,里看代码:
首先是根据请求路径,我们把不同的请求交给不同的方法来处理:
/** * 根据请求参数后缀来分别处理请求 * * @param request 请求 * @param response 响应 * @throws ServletException Servlet异常 * @throws IOException IO异常 */
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
if (uri.endsWith("products")) displayProducts(request, response);
else if (uri.endsWith("details")) displayGoods(request, response);
else if (uri.endsWith("addCart")) addCart(request, response);
else if (uri.endsWith("deleteItem")) deleteCard(request, response);
else if (uri.endsWith("clearCart")) clearCart(request, response);
}
比如我们这次请求就会转发到 displayGoods
方法中去处理,下面来看一下该方法:
/** * 根据 ID 查询用户点击的是哪一个商品,然后跳转到商品详情页面 * 响应请求: /shop/details * * 注意: 只接受同级目录下的页面请求,所以 ./ 或者不写都可以 * * @param request 请求 * @param response 响应 * @throws ServletException Servlet 异常 * @throws IOException IO 异常 */
private void displayGoods(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Integer id = Integer.valueOf(request.getParameter("id"));
List<Product> products = (List<Product>) getServletContext()
.getAttribute("products");
Product product = findById(id, products);
if (product != null) request.setAttribute("product", product);
request.getRequestDispatcher("./productDetails.jsp")
.forward(request, response);
}
我们首先从请求路径中获取到了请求参数 id,然后从 context
中获取到了之前存进去的商品,这里调用了一个 findById
方法,来看一下这个方法:
/** * 根据 id 查询相关内容是否在作用域中 * */
private Product findById(Integer id, List<Product> products) {
for (Product product : products) {
if (id.equals(product.getId())) {
return product;
}
}
return null;
}
它的作用就是根据 id 查询商品,如果查到了就返回,否则返回为空。
这样我们获取这个对象之后就再把它存放到请求作用域中,然后将请求转发到 productDetails
页面。也就是之前我们看到的页面。
5、添加商品到购物车
然后我们可以在文本框中输入加入购物车的商品的数量:
如果我们点击按钮,他会发送一个请求,我们使用这个方法来处理这个请求:
/** * 添加到购物车 * 响应请求: /shop/addCart * * @param request 请求 * @param response 响应 * @throws IOException 异常 */
private void addCart(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletContext context = getServletContext();
HttpSession session = request.getSession();
Integer id = Integer.parseInt(request.getParameter("id"));
Integer quality = Integer.parseInt(request.getParameter("quality"));
List<Product> products = (List<Product>) context
.getAttribute("products");
// 初始化购物车
Map<Product, Integer> cart = (Map<Product, Integer>) session
.getAttribute("cart");
if (cart == null) {
cart = new HashMap<>();
}
Product product = findById(id, products);
if (product != null) {
cart.put(product, quality);
session.setAttribute("cart", cart);
}
displayProducts(request, response);
}
那他会发送什么请求呢?
<form action="addCart" method="post">
<input type="hidden" name="id" value="${requestScope.product.id}">
我们可以查看 form 表单,而且我们加了一个隐藏域,这样在 Servlet 中才可以获取商品的 id 然后放入 session
域中。并且重定向到 productList 页面中。
我们可以点击查看购物车:
那么 cart
页面是怎么获取数据的呢?
这里用到了 jstl
的标签库以及 el
表达式:
<c:forEach items="${sessionScope.cart}" var="cart">
<tr>
<td>📌${cart.value}</td>
<td>${cart.key.name}</td>
<td><span style="color: darkorange">${cart.key.price}</span>💰</td>
<td><span style="color: darkorange">${cart.key.price * cart.value}</span>💰</td>
<c:if test="${sessionScope.cart.size() >= 2}">
<td>
<%-- <a href="deleteItem?id=${cart.key.id}">删除💨</a>--%>
<input type="button" value="删除" onclick="confirmDel(${cart.key.id})">
</td>
</c:if>
</tr>
<c:set var="total" value="${cart.value * cart.key.price + total}"/>
<c:set var="sum" value="${sum + cart.value}"/>
</c:forEach>
这样就可以输出到页面上了,而且我们还可以删除商品。
6、从购物车中删除商品
从购物车中删除商品需要 cart
页面发送一个请求,然后在 Servlet
页面中处理请求。
/** * 删除商品 * /shop/deleteItem * * @param request 请求 * @param response 响应 */
private void deleteCard(HttpServletRequest request, HttpServletResponse response) throws IOException {
Integer id = Integer.parseInt(request.getParameter("id"));
ServletContext context = getServletContext();
HttpSession session = request.getSession();
List<Product> products = (List<Product>) context
.getAttribute("products");
Map<Product, Integer> cart = (HashMap) session
.getAttribute("cart");
Product product = findById(id, products);
if (product != null) {
cart.remove(product);
session.setAttribute("cart", cart);
}
response.sendRedirect("./cart.jsp");
}
我们从 session
域中获取对象之后再删除该对象,因为它本身是一个 Map 集合,最后重新存到 session
域中,然后重定向到他自己实现刷新效果。
删除之后:
7、总体的流程演示
下面通过 GIF 演示一下:
二、扩展功能
下面实现拓展功能:
1、清空购物车
在 cart
页面:
<a href="./clearCart">清空购物车❗</a>
Servlet
中的代码:
/** * 清空购物车 * 响应请求: /shop/clearCart * * @param request * @param response */
private void clearCart(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.getSession().removeAttribute("cart");
response.sendRedirect("./cart.jsp");
}
就是从会话作用域中移除掉所有数据。
2、显示购物车中的商品种类数量和商品总数量
我们先在 forEach
循环中设置一个值 sum 和 total
,用于记录商品数量与总数量。
<c:set var="total" value="${cart.value * cart.key.price + total}"/>
<c:set var="sum" value="${sum + cart.value}"/>
</c:forEach>
<tr>
<td>✍总计 <span style="color: darkorange">${total}</span>💰</td>
<td>总数量 ${sum}</td>
<td></td>
<td></td>
<td><a href="./clearCart">清空购物车❗</a></td>
</tr>
3、购物车为空的提示
使用 jstl
实现,使用相关标签:
<c:choose>
<c:when test="${sessionScope.cart != null}">
</c:when>
<c:otherwise>
<h2>购物车中没有商品 🙁</h2>
</c:otherwise
</c:choose>
这样一旦购物车为空就会进入 otherwise 语句。
4、删除时提示是否确认删认
使用 JavaScript
实现:
<script>
function confirmDel(param) {
if (window.confirm("您确定要删除这件美丽的商品吗?")) {
document.location = "deleteItem?id=" + param;
}
}
</script>
<input type="button" value="删除" onclick="confirmDel(${cart.key.id})">
5、在输入数量时,如果不是数字,要提示
使用 JavaScript
的正则表达式实现:
<script>
test = function () {
var quality = document.getElementById("quality");
var reg = /^[0-9]*$/g;
if (!reg.test(quality.value)) {
alert("请输入数字");
}
};
</script>
<input type="text" name="quality" id="quality" onkeyup="test()">
这样就可以了。
三、关于数据源
由于我们没有使用数据库,所以自己造了数据,但是不太真实也很麻烦,所以我后来使用爬虫爬了京东的数据,然后模仿他的页面写了一个 jsp:
首先来看一下如何爬取数据? 我这里使用的是 jsoup 包,代码如下:
private void initProduct(String keyWords) {
String url = "https://search.jd.com/Search?keyword=" + keyWords + "&enc=utf-8";
List<Product> list = new ArrayList<>();
try {
Document parse = Jsoup.parse(new URL(url), 30000);
Element element = parse.getElementById("J_goodsList");
Elements li = element.getElementsByTag("li");
for (Element el : li) {
count++;
String img = el.getElementsByTag("img").eq(0).attr("source-data-lazy-img");
String price = el.getElementsByClass("p-price").eq(0).text();
String title = el.getElementsByClass("p-name").eq(0).text();
Product product =
new Product(count, title, img, Float.valueOf(price.split("¥")[1]));
list.add(product);
}
} catch (IOException e) {
e.printStackTrace();
}
this
.getServletContext()
.setAttribute("products", list);
}
然后写一个接口,当有请求过来的时候就可以从请求中获取关键字,然后查询,再重定向到首页,实现展示商品的功能:
/** * 搜索功能 * /shop/search * */
private void doSearch(
HttpServletRequest request,
HttpServletResponse response) throws IOException {
String keyWords = request.getParameter("keyword");
System.out.println("数据初始化");
this.getServletContext().removeAttribute("products");
initProduct(keyWords);
System.out.println("转发到商品首页");
displayProducts(request, response);
}
感兴趣的同学可以去 Github
上面查看相关代码。
四、总结
- 通过这次实验,巩固了
Servlet
的基本操作,以及JSP
的操作,体会了JSP
的页面强大之处,JSP
太强了!!!(逃 - 还使用了
jstl
的表达式,用起来很方便,就算不会java
的人也能轻松实现java
服务端和客户端代码的编写,太强了! - 各个域之间的存储数据,让我更清楚的明白了域的区别的与联系,适合什么样的场景就用什么样的域。
相关源码已上传至 Github 地址