項目是書城項目,感覺不太完整。在講各種web技術的同時,順帶做下項目。該項目沒有使用SSM、SSH等框架,Javaweb三層框架都是直接寫的代碼。 大致熟悉一下流程即可,在真正開發的時候會用框架來做。
B站地址:https://www.bilibili.com/video/BV1Y7411K7zz?p=1
網盤視頻和相應資料文件地址:尚硅谷公眾號回復 Java 獲取。在第一個Java基礎文件夾中,找尚硅谷JavaWeb_2020idea新版。
前置知識:Java入門、數據庫和JDBC。同樣方式獲取資料。
Java地址:https://www.bilibili.com/video/BV1Kb411W75N
數據庫地址:https://www.bilibili.com/video/BV1xW411u7ax?spm_id_from=333.788.b_636f6d6d656e74.8
JDBC地址:https://www.bilibili.com/video/BV1eJ411c7rf
另一個只教項目的地址(網上在線書城-JavaWeb教程案例-尚硅谷_佟剛):https://www.bilibili.com/video/BV1Vt411T7v5?p=1
首先介紹一下資料的使用:
先去上面網盤下載相關資料。導入的jar包來源:尚硅谷JavaWeb_2020idea新版\資料\05-XML & Tomcat\筆記\JavaWeb需要用到的jar包
;如果有些無法識別,需要將tomcat\lib
中的所有jar也導入。視頻中使用的是 JDK8/tomcat-8.0.50,建議使用相同版本。
在尚硅谷JavaWeb_2020idea新版\資料\01-html&CSS\筆記
中有一個項目實戰:尚硅谷商城.rar,里面有各個階段的代碼和一個項目說明。
另外,我對視頻中對於基本知識介紹的pdf(包含一部分項目階段)進行了整合,可以關注我的公眾號 Java與大數據進階,回復pdf獲取。
基本操作
我使用的idea版本是ultimate 2020.3,和視頻中版本不同。這里創建Module方式如下。第一步,File-New-Module;第二步,選擇 Java EE(Legacy)-Web Application(4.0),下面 create web.xml 打鈎,Next。
視頻中的在Project Structure中配置jar這種我不會。配置的時候經常出現關不掉的情況。這里建議如下圖,shift選中全部,右鍵-add As Library,在窗口中Level 選擇 Module Library,成功后包左邊會出現展開符號,可以查看里面的class,在idea中默認查看的是反編譯后的結果。
階段一、使用JS正則表達式檢查輸入
使用JS正則表達式在前端檢查注冊狀態,在pages/user/regist.html進行修改。
注意,在前端HTML/JS中,出錯不會提示,如果沒有效果可以看看是否是單詞寫錯。
在事件加載完成之后寫代碼,$(function(){...})
text/val/html 區別:val用於input,text只輸出文本,html輸出全部內容。具體區別可見下方代碼
<script type="text/javascript" src="../../static/script/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function () {
console.log($("div").text());//divText::pText
console.log($("p").text());//pText
console.log($("input").text());//
console.log($("div").html());//divText::<p>pText</p>
console.log($("p").html());//pText
console.log($("input").html());//
console.log($("div").val());//
console.log($("p").val());//
console.log($("input").val());//text
})
</script>
<body>
<div>divText::<p>pText</p></div>
<input type="text" value="text"/>
</body>
return false 阻止默認的事件行為
階段二、實現登陸和注冊功能
下圖為Java EE三層架構,視頻中從數據庫寫起,從右到左。
下圖是三層架構對應的包結構,注意到對於service/dao是先有接口,后有實現類。
創建了工具類utils.JdbcUtils,在static塊中初始化相應的Druid數據庫連接池,並提供創建連接和關閉連接的方法。在dao.impl.BaseDao中實現了對數據庫的各種操作的一般方法,將該類設定為abstract class。
登錄和注冊只和用戶有關。所以接下來,在數據庫中創建一個用戶表。bean.User是對應的實體bean對象,接口dao.BookDao定義對用戶表需要執行哪些操作。
實現了BookDao的類dao.impl.BookDaoImpl需要調用操作數據庫的一般方法來完成相應功能,所以該類extends BaseDao implements BookDao
。
接下來會創建接口service.UserService和實現類,該類的方法就是需要處理的業務邏輯。
最后寫web層,web.RegistServlet實現注冊邏輯,web.LoginServlet實現登錄邏輯。注意:req.getParameter的參數是表單項的name屬性值,跳轉方式是請求轉發,將對應Servlet寫入WEB-INF/web.xml。
在html頁面使用base標簽和相對路徑。
另外,在完成一定功能后,比如DAO/service后需要測試,防止錯誤范圍太大難以查找,在接口中生成測試類快捷鍵 ctrl+shift+T。
階段三、做一些優化
jsp已經淘汰,大致了解即可。
主要工作:修改html為jsp,抽取頁面中重復的內容,錯誤提示及表單回顯(在Servlet中setAttribute,在jsp中getAttribute並判斷是否為空),使用BaseServlet,BeanUtils一次性將Map中的值注入JavaBean。
BaseServlet首先獲取隱藏域表單項中name="action"的value,並通過反射調用同名方法。具體的Servlet只需要繼承BaseServlet,並寫出對應的同名方法即可。
還有一種方法是將參數放入href中,具體如下。
//在jsp的表單中添加一個隱藏域,name="action",在Servlet中獲取對應的value為regist
//<input type="hidden" name="action" value="regist" />
//獲取地址中action的值為list
//<a href="manager/bookServlet?action=list">圖書管理</a>
public abstract class BaseServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取對應表單項中action的value
String action = req.getParameter("action");
try {
// 獲取action業務鑒別字符串,獲取相應的業務方法反射對象
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
// System.out.println(method);
// 調用目標業務 方法
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
階段四、使用EL表達式修改表單回顯
階段五、圖書模塊
1.MVC 全稱:Model 模型、 View 視圖、 Controller 控制器。
MVC 最早出現在 JavaEE 三層中的 Web 層,它可以有效的指導 Web 層的代碼如何有效分離,單獨工作。
View 視圖:只負責數據和界面的顯示,不接受任何與顯示數據無關的代碼,便於程序員和美工的分工合作—— JSP/HTML。
Controller 控制器:只負責接收請求,調用業務層的代碼處理請求,然后派發頁面,是一個“調度者”的角色——Servlet。 轉到某個頁面。或者是重定向到某個頁面。
Model 模型:將與業務邏輯相關的數據封裝為具體的 JavaBean 類,其中不摻雜任何與數據處理相關的代碼—— JavaBean/domain/entity/pojo。
在圖書模塊,先寫DAO,service,然后實現查看所有書籍,添加修改和刪除書籍的功能,由於要使用到數據庫中的數據,需要Servlet處理,然后請求轉發/重定向到相應位置。
由於提交請求后,瀏覽器記錄了最后一次請求的全部信息,按下F5,會再次請求,這是表單重復提交的問題。所以,增刪改都需要使用重定向。
①對於刪除操作,一般需要提示用戶是否確認刪除,通過JS實現。
②對於添加和修改操作,均使用的是book_edit.jsp,為了將兩者區分,有三種方法。
解決方案一:可以請求發起時,附帶上要操作的方法名,並注入隱藏域
<%--在book_manager.jsp中添加圖書和修改圖書的標簽中的href添加對應參數--%>
<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>
...
<td><a href="pages/manager/book_edit.jsp?method=add">添加圖書</a></td>
<%--在book_edit.jsp中添加--%>
<input type="hidden" name="action" value="${param.method}">
解決方案二:可以判斷當前請求參數中是否包含有id,有說明是修改,否則是添加.${empty param.id?"add":"update"}
解決方案三:可以通過判斷,Request域中是否包含有修改的圖書信息對象,沒有說明是添加,有說明是修改。
<input type="hidden" name="action" value="${ empty param.book ? "add" : "update" }" />
③對於修改操作,修改需要id值,所以在book_edit.jsp中放一個隱藏域保存,代碼不用動,會被BeanUtils自動封裝到Book中。
<input type="hidden" name="id" value="${ requestScope.book.id }" />
。
階段五、下、分頁的實現
實現分頁功能,需要新建一個Page類,需要傳入pageNo和pageSize。每個頁面的數據通過limit語句獲取。在jsp的相應跳轉地址中,會添加pageNo字段。
修改index.jsp,請求轉發到一個Servlet,獲取分頁數據后,再跳轉到其他jsp頁面輸出。
階段六、登錄/注銷/驗證碼
登錄使用Session保存,注銷是銷毀Session然后重定向。為了防止表單重復提交,使用kaptcha圖片驗證碼,在獲取驗證碼后刪除,這樣重定向時,驗證碼的地方為空,重復提交失敗。
表單重復提交有三種常見的情況:
一:提交完表單。服務器使用請求轉來進行頁面跳轉。這個時候,用戶按下功能鍵 F5,就會發起最后一次的請求。造成表單重復提交問題。解決方法:使用重定向來進行跳轉 .
二:用戶正常提交服務器,但是由於網絡延遲等原因,遲遲未收到服務器的響應,這個時候,用戶以為提交失敗,就會着急,然后多點了幾次提交操作,也會造成表單重復提交。
三:用戶正常提交服務器。服務器也沒有延遲,但是提交完成后,用戶回退瀏覽器。重新提交。也會造成表單重復提交。
階段六、購物車模塊
購物車里面有兩個類,購物車Cart,購物車項CartItem。
購物車實現版本有三種:
1、Session版本(把購物車信息保存在Session域中) 本次使用的版本
2、數據庫版本(把購物車信息,保存到數據庫)
3、redis+數據庫+Cookie(使用Cookie+Redis緩存,和數據庫)
具體來說,每次添加時,會先獲取Session中的Cart,如果為空就新建一個並放入Session,然后執行相應的添加操作。
在HTTP協議中有一個請求頭,叫Referer,它可以把請求發起時,瀏覽器地址欄中的地址發送給服務器。
回顯最后一個添加的商品,只需要每次添加時在Session中覆蓋"lastName"對應的購物車項的名字即可。
階段七、訂單
同樣有兩個類,訂單Order和訂單項OrderItem,對應數據庫的兩個表t_order/t_order_item。t_order和用戶表t_user可以通過userId關聯,t_order和t_order_item通過orderId關聯。
這里只實現了生成訂單功能,生成訂單會在兩個表中分別保存訂單和每個訂單項。
階段八、權限檢查和事務管理
1、使用Filter過濾器攔截/pages/manager/所有內容,實現權限檢查。
2、ThreadLocal的使用,其實ThreadLocal底層是ThreadLocalMap,每個線程有一個ThreadLocalMap,存着所有使用過的ThreadLocal,這樣同一個ThreadLocal在不同線程中結果就可以不同。具體可見我的這篇文章........................
3、使用 Filter 和 ThreadLocal 組合管理事務
修改JdbcUtils,使用ThreadLocal<Connection>來表示當前線程的連接,在getConnection中,如果存在則獲取,否則生成。在提交和回滾事務后,關閉連接並刪除當前ThreadLocal。
在BaseDao的相應方法中,去掉關閉連接的部分,並拋出異常。在BaseServlet中同樣拋出異常。在Filter的doFilter方法中用try-catch包裹相應代碼。
public class JdbcUtils {
private static DruidDataSource dataSource;
private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
static {
try {
Properties properties = new Properties();
// 讀取 jdbc.properties屬性配置文件
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 從流中加載數據
properties.load(inputStream);
// 創建 數據庫連接 池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取數據庫連接池中的連接
* @return 如果返回null,說明獲取連接失敗<br/>有值就是獲取連接成功
*/
public static Connection getConnection(){
Connection conn = conns.get();
if (conn == null) {
try {
conn = dataSource.getConnection();//從數據庫連接池中獲取連接
conns.set(conn); // 保存到ThreadLocal對象中,供后面的jdbc操作使用
conn.setAutoCommit(false); // 設置為手動管理事務
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
/**
* 提交事務,並關閉釋放連接
*/
public static void commitAndClose(){
Connection connection = conns.get();
if (connection != null) { // 如果不等於null,說明 之前使用過連接,操作過數據庫
try {
connection.commit(); // 提交 事務
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close(); // 關閉連接,資源資源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 一定要執行remove操作,否則就會出錯。(因為Tomcat服務器底層使用了線程池技術)
conns.remove();
}
/**
* 回滾事務,並關閉釋放連接
*/
public static void rollbackAndClose(){
Connection connection = conns.get();
if (connection != null) { // 如果不等於null,說明 之前使用過連接,操作過數據庫
try {
connection.rollback();//回滾事務
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close(); // 關閉連接,資源資源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 一定要執行remove操作,否則就會出錯。(因為Tomcat服務器底層使用了線程池技術)
conns.remove();
}
}
階段九、Ajax實現相應功能
Ajax 是一種瀏覽器通過 js 異步發起請求,局部更新頁面的技術。