Servlet生命周期
五個部分,從加載到卸載,如同人類的出生到死亡
- 加載:Servlet容器自動處理
- 初始化:init方法 該方法會在Servlet被加載並實例化后執行
- 服務:service抽象方法:具體實現是doGet(),doPost()方法
- 銷毀:destroy(),Servlet被系統回收時執行
- 卸載:Servlet容器自動處理
init():
- 默認第一次訪問Servlet時會被執行(只執行這一次,可以修改為Tomcat啟動時自動執行:
- 2.5:web.xml中
<servlet>
字段添加<load-on-startup>1(代表第1個Servlet)
.. - 3.0:@WebServlet(value = "/Servlet3",loadOnStartup = 1)
service():->doGet() doPost:調用幾次,則執行幾次
destroy():關閉tomcat服務時
Servlet API
由兩個大類四個軟件包組成::
即Servlet API可以適用於任何通信協議。但絕大多數情況下Servlet只用來擴展基於HTTP協議的Web服務器。
我們學習的Servlet,是位於javax.servlet.http
包中的類和接口,是基礎HTTP協議。
Servlet繼承關系
ServletConfig:接口
ServletContext getServletContext():獲取Servlet上下文對象 application
String getInitParameter(String name):在當前Servlet范圍內,獲取名為name的參數值(初始化參數)
a.ServletContext中的常見方法(application):
getContextPath():相對路徑
getRealPath():絕對路徑
setAttribute()、getAttribute()
---->
String getInitParameter(String name);在當前Web容器范圍內,獲取名為name的參數值(初始化參數)
初始化全局參數
<context-param>
<param-name>globalParam</param-name>
<param-value>global value...</param-value>
</context-param>
初始化Servlet參數
- Servlet2.5
<servlet>
<servlet-name>my</servlet-name>
<servlet-class>com.hacker.servlet.MyServlet</servlet-class>
<load-on-startup>2</load-on-startup>
<!--配置當前Servlet初始化參數 -->
<init-param>
<param-name>servletparamname</param-name>
<param-value>servletparamvalue...</param-value>
</init-param>
</servlet>
- Servlet3.0
@WebServlet(value = "/Servlet3",loadOnStartup = 1,initParams = {@WebInitParam(name="servletparamname30",value = "servletparamvalue30")})
注意:此注解只隸屬於某一個具體的Servlet,因此無法為整個Web容器設置初始化參數(如果要通過3.0方式設置,仍需在web.xml中設置)
獲取全局參數
ServletContext對象表示Servlet應用程序。每個Web應用程序都只有一個ServletContext對象。在將一個應用程序同時部署到多個容器的分布式環境中,每台Java虛擬機上的Web應用都會有一個ServletContext對象。
通過在ServletConfig中調用getServletContext方法,也可以獲得ServletContext對象。
@Override
public void init() throws ServletException {
System.out.println("init...");
//獲取整個Web容器的初始化參數
String str=super.getServletContext().getInitParameter("globalParam");
System.out.println("當前Web容器的初始化的參數為"+str);
}
獲取當前Servlet參數
當Servlet容器初始化Servlet時,Servlet容器會給Servlet的init( )方式傳入一個ServletConfig對象
其中幾個方法如下:
@Override
public void init() throws ServletException {
System.out.println("init...");
//獲取當前Servlet的初始化參數
String str=super.getInitParameter("servletparamname");
System.out.println("當前Servlet的初始化參數為"+str);
}
請求與響應
當我們在在請求Servlet容器具體的執行流程的細節是什么呢?一起來看一看
首先我們知道請求的過程最終傳給了名為service的方法,那service方式到底是怎么執行的,我們先來簡單的了解下
首先查看入口類繼承的HTTPServlet類
點進去發現繼承至GenericServlet,繼續跟進
GenericServlet實現了一個Servlet接口
接口中定義了service方法,並且有兩個參數ServletRequest和ServletResponse代表請求和響應,那么我們自定義的Servlet肯定不是實現的這個service方法,因為我們重寫的service方法形參為HttpServletRequest和HttpServletResponse
現在就來找找到底是重寫的那個service方法,首先來看GenericServlet類
在GenericServlet類中發現實現了service的抽象方法,傳入參數為ServletRequest,明顯也不是,繼續跟進HTTPServlet類
在HTTPServlet類中發現兩個service方法,很明顯第二個service方法參數也是ServletRequest,所以第二個service方法為實現方法,下面來看看具體實現
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException(lStrings.getString("http.non_http"));
}
this.service(request, response);
}
可以看到該實現方法將Servlet強轉為了HttpServlet,
HttpServlet繼承自Servlet,將父類變為了子類,把通用的響應,轉換為了特需的HTTP響應,之所以能夠這樣強制的轉換,是因為在調用Servlet的Service方法時,Servlet容器總會傳入一個HttpServletRequest對象和HttpServletResponse對象,預備使用HTTP。
因此,轉換類型當然不會出錯了。
PS:Java中父類想要轉換為子類,父類的實例必須指向子類的應用,形如
public static void main(String[] args) {
//Car為父類,BigCar為子類
Car car=new BigCar();//這里car父類對象的引用為BigCar子類 父類是子類構造出來的實例
BigCar bc=(BigCar)car;//所以這里可以將父類對象car強轉為子類對象BigCar
bc.setName("ssss");//這里就可以調用子類的方法
test(new BigCar());
}
public static void test(Car car) {
BigCar bigCar = (BigCar) car;
if (bigCar instanceof Car) {
System.out.println("1");
}
}
最后調用了當前類中的重載方法service
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
在該方法中,將請求類型進行了划分,判定請求類型調用不同的方法,所以我們重寫的service方法實際上是接收了所有類型的請求,那么可以針對不同請求重寫相應的方法,來簡化我們的操作。
一般裝飾者就是在主體組件擴展到具體的實現類時,會引入一個中間層,把裝飾者的公布部分引入進來,在引入具體的實現時,只需要實現自己特定的部分就行了。公共的就放在上面,中間層中。而GenericServlet類就可以看作是那個中間層,它在空實現Servlet類的方法后,子類在繼承的時候就可以只重寫需要的方法,不必重寫Servlet類的所有方法了。
MVC案例
學了這么多,現在就來動手實現一個MVC簡單登錄案例😀,再來復現一遍什么是MVC模式:
M
:Model ,模型 :一個功能。用JavaBean實現。V
:View,視圖: 用於展示、以及與用戶交互。使用html js css jsp jquery等前端技術實現C
:Controller,控制器 :接受請求,將請求跳轉到模型進行處理;模型處理完畢后,再將處理的結果
返回給 請求處 。 可以用jsp實現, 但是一般建議使用 Servlet實現控制器。
Jsp->Java(Servlet)->JSP
我們首先看到是View那么就先來實現一個View:login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<form method="post" action="/login">
用戶名:<input type="text" name="un"><br/>
密碼:<input type="password" name="pwd"><br/>
<input type="submit" value="登錄">
</form>
</body>
</html>
一個簡單的登錄界面就完成了,登錄后Controller會首先接收到我們的請求,為了方便用戶名和密碼的存取,先新建一個JavaBean
package com.hacker.servlet.login;
public class Login {
String username;
String password;
public Login(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
新建一個登錄控制器
package com.hacker.servlet.login;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 控制器層:接受view請求,並分發給Model處理
* */
public class LoginController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String un=req.getParameter("un");//接收POST參數un
String pwd=req.getParameter("pwd");//接收POST參數pwd
Login login=new Login(un,pwd);//封裝到名為Login的JavaBean中
resp.setCharacterEncoding("UTF-8");//設置返回編碼
resp.setContentType("application/json; charset=utf-8");//設置返回格式和瀏覽器渲染編碼
// resp.setContentType("text/html;; charset=utf-8");
PrintWriter out=resp.getWriter();//獲取輸出對象
LoginDao loginDao=new LoginDao();//獲取一個登錄模型對象
JSONObject jsonObject=new JSONObject();
try {
if (loginDao.login(login)){//直接傳入login對象,用戶名密碼通過該對象的getter方法獲取
jsonObject.put("登錄成功","1");
// out.write("{\"flag\":\"true\"}");
}else {
// out.write("{\"flag\":\"flase\"}");
jsonObject.put("登錄失敗","0");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// out.write(jsonObject);
out.print(jsonObject);//返回Json格式的登陸結果
out.flush();
out.close();
}
}
}
在web.xml中指定該Servlet的路徑
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
<!-- 配置Servlet -->
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.hacker.servlet.login.LoginController</servlet-class>
</servlet>
<!--配置訪問方式 -->
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
編寫LoginDao模型類,首先導入Mysql Jar包
package com.hacker.servlet.login;
import java.sql.*;
/**
* 模型層 用於處理登錄
*/
public class LoginDao {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/test";
static final String USER = "root";
static final String PASS = "root";
Connection conn; //連接對象
PreparedStatement pstmt;//預編譯對象
ResultSet rs;//結果集對象
Boolean flag=false;//登錄結果 默認為false
public Boolean login(Login login) { //登錄操作
try {
Class.forName(JDBC_DRIVER);//反射調用導入驅動,加載具體驅動類
conn = DriverManager.getConnection(DB_URL, USER, PASS);//與數據庫建立連接
String sql = "SELECT * FROM user WHERE username=? AND password=?";//預處理添加數據
pstmt = conn.prepareStatement(sql);//創建預處理執行對象(預編譯SQL)
pstmt.setString(1, login.getUsername());//設置變量參數
pstmt.setString(2, login.getPassword());//設置變量參數
rs = pstmt.executeQuery();//返回查詢到的結果集
if (rs.next()) {//判定結果集中是否能夠讀到數據
flag=true; //登錄成功
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//防止空指針異常
//關閉順序與打開順序相反
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (pstmt != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return flag;//返回結果
}
}
下面來試一試吧😁
成功啟動Tomcat
簡潔的頁面
登陸成功,你能猜到密碼嗎?😜