MVC模式與Servlet執行流程


Servlet生命周期

五個部分,從加載到卸載,如同人類的出生到死亡

  1. 加載:Servlet容器自動處理
  2. 初始化:init方法 該方法會在Servlet被加載並實例化后執行
  3. 服務:service抽象方法:具體實現是doGet(),doPost()方法
  4. 銷毀:destroy(),Servlet被系統回收時執行
  5. 卸載: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

簡潔的頁面

登陸成功,你能猜到密碼嗎?😜


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM