可能很多人看到標題都不會點進來,因爲 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 地址