知識點梳理:2020還看Servlet?

Servlet

前言

一個禿了頭的大師託夢給我說要想稱爲大神就要有一個完善的我的知識體系,我從夢中驚醒,打開筆記本開始了整理。。。php

image-20200620165241171

0 爲何還看Servlet

2020年了,爲何仍是看Servlet???首先這是一個必經的階段,當初開始學習Java Web的時候,這部分就是重點,第二就是在學習了一些更加高級的框架時,仍是時不時會看到它的身影,像Spring等,在學習他的源碼的時候就能夠看到它維護的DispatcherServlet,因此不要再問爲何2020還看這麼土的東西??html

固然還有一個問題就是要不要看JSP,這個我的認爲簡單瞭解便可,如今企業中的開發基本都是先後端分離的模式,就算是簡單的搭建,Freemarker、Thymeleaf模板語言也是更加的方便,so 若是你有空,那麼你能夠去看看,可是不須要太過深究java

1 基本概念

Servlet是一個接口,定義了servlet容器識別Java程序的規範web

2 基礎實現

實現Servlet接口

最基礎的servlet實現,直接實現servlet接口,重寫他的5個方法,同時須要在web.xml中配置這個servlet,servlet是根據web.xml中的配置找到對應url的處理者後端

public class HelloServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init ...");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service ...");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("destroy ...");
    }
}
複製代碼
<?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_3_1.xsd" version="3.1">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.learn.servlet.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>
複製代碼

繼承HttpServlet

Servlet3.0

注意⚠️:Servlet3.0是從J2EE 6開始才支持的哦,主要是用於簡化開發,不用再繁瑣的xml配置了,因此能夠直接不使用web.xml數組

使用方法很簡單,直接在servlet上嗎使用@WebServlet註解即刻,經過查看源碼能夠知道,原來配置在web.xml中的配置所有均可以挪到註解的值中瀏覽器

@WebServlet(name = "hello3", urlPatterns = {"/hello3"})
public class HelloServlet3 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet 3.0 ...");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
複製代碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}
複製代碼

3 深刻學習

生命週期

Servlet中有五個方法,其中有3個和生命週期有關緩存

  1. init 初始化:Servlet被建立的時候執行,只執行一次
  2. service 服務:Servlet被訪問時執行,每次訪問都會執行
  3. destroy 摧毀:當容器正常關閉時執行,只執行一次

Servlet默認狀況下,是在初次被訪問時建立,也能夠經過配置load-on-startup(默認狀況爲負數,開啓則配置爲0或正整數)來使其隨服務器的啓動而建立,建立時會執行init方法,由此也能夠看出Servlet是一個單例對象,因此在多個用戶訪問的時候會存在線程安全問題,因此儘可能不要在Servlet中使用成員變量或儘可能不要修改Servlet的成員變量tomcat

destroy方法是隻有在容器正常關閉時纔會去執行,注意是正常關閉,且它是先於容器關閉而執行,因此通常用它來釋放資源。安全

繼承體系

image-20200528224632146

  • GenericServlet實現了Servlet接口,是對於Servlet接口基礎的封裝抽象類,繼承者必須須要重寫service方法,同時能夠根據自身須要重寫其餘4個方法

  • HttpServlet繼承自GenericServlet,是在http協議的基礎上對Servlet進行進一步封裝,其中經常使用的doGet和doPost根據http method的不一樣按需使用,簡化開發

    @WebServlet("/http")
    public class HelloHttpServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("do get ...");
        }
    
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("do post ...");
        }
    
    }
    複製代碼

mapping的通配符

  1. 一個serlvet能夠配置多個映射
  2. 規則:
    1. /xxx
    2. /xxx/xxx
    3. xxx.xxx
    4. 其中( * )是表明任意,注意( * ). xxx 這種寫法不能和 / 一塊兒用
    5. 訪問的優先級是越匹配越優先
    6. 只有(/)的匹配稱爲缺省匹配,其餘都找不到了就匹配這種

4 Request

Request對象的本質

Servlet在tomcat中是經過反射機制建立的對象,傳遞來的Request對象其實是實現了HttpRequestServlet的RequestFacade,此對象由tomcat提供(門面模式)

經常使用的API

  • 獲取請求行

    假設一個請求的請求行是 GET /hello/abc?name=123 HTTP/1.1

    • request.getMethod 獲取GET
    • request.getContextPath 獲取/hello
    • request.getServletPath 獲取/abc
    • request.getQueryString 獲取name=123
    • request.getRequestURI 獲取/hello/abc
    • request.getRequestURL 獲取http://localhost:8080/hello/abc
    • request.getProtocal 獲取 HTTP/1.1
    • request.getRemoteAddr 獲取客戶機的ip地址
  • 獲取請求頭

    • request.getHeader(key) 經過請求頭名稱獲取請求頭信息

      • 防盜鏈

        • 所謂防盜鏈是指防止其餘web站點頁面經過鏈接本站點的頁面來訪問本站點內容,這樣對於本站點來講侵犯了本站點的版權

        • request.getHeader("referer")能夠獲取來源,當值爲null表明瀏覽器地址欄直接輸入訪問,經過對於域名的匹配能夠達到防盜鏈的效果

          @Override
              protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  System.out.println("do get ...");
                  System.out.println(req.getHeader("referer"));
                  String referer = req.getHeader("referer");
                  if (referer == null)  {
                      System.out.println("來自瀏覽器的地址直接訪問");
                  }else if (referer.contains("localhost:8080")) {
                      System.out.println("本地訪問");
                  }else {
                      System.out.println("不知道什麼鬼地方訪問的");
                  }
              }
          複製代碼
    • request.getHeaderNames 獲取全部請求頭的名稱
  • 獲取請求體
    • request.getReader 獲取請求體(字符流)
    • request.getInputStream 獲取請求體(字節流)用於文件上傳
  • 獲取請求參數
    • request.getRequestParam(name) 根據名字獲取參數
      • request.getParameterValues(name) 根據名字獲取參數數組
    • request.getParameterNames 獲取全部請求參數的名字
    • request.getParamterMap 獲取全部請求參數 k-v,封進一個map
      • 中文亂碼是由於頁面傳輸的流編碼格式通過tomcat的接受轉換不一致致使的,頁面傳上來是utf-8,tomcat內部是iso編碼,再輸出到控制檯utf-8就亂了,因此須要在讀取參數前先轉換一下request的編碼,直接使用req.setCharacterEncoding("utf-8");便可
  • 請求轉發
    • request..getRequestDispatcher("目標路徑").forward(req, resp);
    • 特色 一、服務器重定向瀏覽器無感 二、一次請求 三、只能重定向到內部資源 四、訪問的http method = 轉發的 http method
  • 域對象
    • 範圍就是一次請求,服務器內請求轉發的時候能夠傳遞數據
    • request.setAttribute("key", value) 向域對象裏面存值
    • request.getAttribute("key") 從域對象裏面取值,取不到就是null
    • request.removeAttribute("key") 從域對象裏面移除key對應的值
  • 獲取ServletContext對象
    • request.getServletContext

5 Response

經常使用API

  • 設置響應頭
    • response.setHeader(key, value)
  • 設置響應體
    • 獲取輸出流
      • 字節流 response.getWriter
      • 字符流 response.getOutputStream
    • 使用輸出流輸出數據

重定向

  • 設置 http code 302,提示瀏覽器進行客戶端重定向
  • 設置重定向地址
  • 特色:一、在客戶端瀏覽器進行 二、實際發起了兩次請求 三、能夠訪問任意資源

實戰-驗證碼

其實就是利用繪圖工具繪製一張二維碼的圖片,再經過response的字節輸出流,輸出圖片對象

@WebServlet("/verify")
public class VerifyCodeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BufferedImage bufferedImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
        Graphics graphics = bufferedImage.getGraphics();
        graphics.setColor(Color.PINK);
        graphics.fillRect(0,0,100, 50);
        graphics.setColor(Color.BLUE);
        graphics.drawRect(0,0,100-1,50-1);
        // 這裏能夠自定義驗證碼內容
        String i = String.valueOf(new Random().nextInt(100));
        graphics.drawString(i, 50, 25);
        ImageIO.write(bufferedImage, "jpg", resp.getOutputStream());
    }
}
複製代碼

6 ServletContext

域對象

ServletContext域對象是在容器啓動時便建立,對於全部項目中的Servlet共享,不管是request對象獲取的servlet context仍是GenericServlet抽象父類提供的方法,獲取的都是同一個對象,域對象存取數據方法和request對象同樣

  • servletContext.setAttribute("key", value) 向域對象裏面存值
  • servletContext.getAttribute("key") 從域對象裏面取值,取不到就是null
  • servletContext.removeAttribute("key") 從域對象裏面移除key對應的值

獲取MIME-TYPE

  • servletContext.getMimeType(文件絕對路徑)
  • Mime-type的格式:大類型/小類型
  • 做用:瀏覽器一般使用MIME類型(而不是文件擴展名)來肯定如何處理URL,所以Web服務器在響應頭中添加正確的MIME類型很是重要。若是配置不正確,瀏覽器可能會曲解文件內容,網站將沒法正常工做,而且下載的文件也會被錯誤處理。
@WebServlet("/context")
public class ContextServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 這兩個獲取的是同一個
        ServletContext servletContext = req.getServletContext();
// ServletContext servletContext1 = getServletContext();
        File file = new File(servletContext.getRealPath("/hehe.html"));
        String mimeType = servletContext.getMimeType(file.getName()); // 輸出:text/html
        System.out.println(mimeType);
    }
}
複製代碼

獲取文件正式路徑

  • servletContext.getRealPath(項目文件相對路徑)
  • 其實就是tomcat的虛擬目錄地址+相對地址

實戰-文件下載

其實步驟很簡單

一、告訴瀏覽器要下載一個文件 resp.setHeader("content-disposition", "attachment;filename=你好.jpg");

二、把要下載的文件加載進入resp的字節輸出流中

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        String fileName = req.getParameter("fileName");
        String realPath = getServletContext().getRealPath(fileName);
        resp.setHeader("content-disposition", "attachment;filename=你好.jpg");
        ImageIO.write(ImageIO.read(new File(realPath)), "jpg", resp.getOutputStream());
    }
}
複製代碼

7 ServletConfig

運行servlet時可能會要一些輔助的信息,ServletConfig提供了這個空間去存這些信息

對於傳統的xml方式的配置,在servlet標籤中添加init-param標籤便可

<servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.learn.servlet.HelloServlet</servlet-class>
        <init-param>
            <param-name>hello</param-name>
            <param-value>this is hello value</param-value>
        </init-param>
    </servlet>
複製代碼

對於更加簡單方便的註解,使用方式也是更加簡單

@WebServlet(urlPatterns = "/config",  initParams = {@WebInitParam(name = "abc", value = "123")})
複製代碼

注意⚠️:servlet config的配置僅僅在配置的servlet中有效哦

servlet獲取配置的方法和servlet context相似

@WebServlet(urlPatterns = "/config",  initParams = {@WebInitParam(name = "abc", value = "123")})
public class ConfigServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        String abc = servletConfig.getInitParameter("abc");
        System.out.println(abc);
    }
}
複製代碼

8 會話技術

一次會話中包含着屢次請求和響應,所謂一次會話就是瀏覽器第一次和服務端發請求,此時會話創建,知道有一方關閉,這次會話才中止。會話技術就是在此次會話內共享數據,方式主要有二 一、cookie 二、session

9 cookie

基本操做

  • 新建cookie對象

    Cookie cookie = new Cookie("abc", "123");
    複製代碼
  • 向客戶端設置cookie

    resp.addCookie(cookie);
    複製代碼
  • 獲取客戶端cookie

    Cookie[] cookies = req.getCookies();
    複製代碼

cookie的本質

本質就是利用了http header的add-cookie和cookie,服務端設置cookie本質就是在header中加上add-cookie,服務端取出cookie也就是解析header中cookie的值

多個cookie

容許同時存在多個cookie,只要屢次addCookie便可

Cookie的有效時間

  • cookie.setMaxAge(seconds) 存放秒值
  • seconds爲正數表明存活的時間,負數爲默認即關閉會話就清楚,0表明當即清除

Cookie共享問題

  • 默認狀況下cookie是不共享的
  • cookie.setPath 能夠設置項目內的cookie共享,如setPath("/abc"),那麼uri中/abc下的均可以共享
  • cookie.setDomain 能夠設置域名共享,如設置了 .baidu.com 那麼,xxx.baidu.com均可以共享這個cookie

Cookie特殊符號問題

  • 有個特別的地方,cookie不支持特殊符號,因此使用時最好先對其進行轉碼 URLEncoder.encode,取值時別忘了解碼URLDecoder.decode

實戰-記住上次登陸時間

實現思路比較簡單,程序開始直接獲取cookie,並檢索獲取到的cookie中是否存在咱們設置過的cookie,有表明曾經登陸過,沒有表明初次登陸

@WebServlet("/ct")
public class CookieTestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        Cookie[] cookies = req.getCookies();
        if (cookies != null) {
            for (Cookie item: cookies) {
                if (item.getName().equals("loginTime")) {
                    // 取出上次的時間
                    String lastLoginTime = URLDecoder.decode(item.getValue(), "utf-8");
                    // 存入當前的時間
                    item.setMaxAge(60 * 60 * 24 * 30);
                    item.setValue(URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")\
                                                    .format(new Date()),"utf-8"));
                    resp.addCookie(item);
                    resp.getWriter().println("上次登陸時間:" + lastLoginTime);
                    return;
                }
            }
        }
        Cookie cookie = new Cookie("loginTime", 
                                   URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                                                     .format(new Date()),"utf-8"));
        cookie.setMaxAge(60 * 60 * 24 * 30);
        resp.addCookie(cookie);
        resp.getWriter().println("初次登陸,歡迎您");
    }

}
複製代碼

10 Session

基本操做

和cookie相似

  • 獲取session對象:request.getSession
  • session域對象使用:session.setAttribute | session.getAttribute | session.removeAttribute

session實現原理

session實現本質是經過cookie來實現的,能夠看到會話創建完成後,服務器會在內存中建立session對象,並會在響應中添加name爲JSESSIONID的cookie,表明本次會話的對象標識。在同一次會話過程當中,瀏覽器訪問就會帶上這個JSESSIONID進行請求,服務端接受以後經過對比ID與內存中的對象,判斷本次會話是否和以前的相同。

session存活時間

使用tomcat容器,session的默認存活時間時30分鐘,默認的配置是在tomcat/conf/web.xml中進行配置,用戶能夠修改其中的session-config手動對其修改

客戶端關閉後,下次請求的session是否還相同

默認狀況下,客戶端關閉,因爲cookie的特性,cookie會隨着瀏覽器關閉而清除,因此不相同;可是咱們瞭解了session的基本原理以後,能夠經過手動設置JSESSIONID cookie的存活時間,達到緩存cookie的效果,那麼下次在打開瀏覽器的請求就能夠保持同一個session

服務端關閉後,下次請求的session是否還相同

不作任何處理的狀況下,服務端關閉,內存中的session對象天然就不存在,因此不可能相同。要保持session,須要進行session的鈍化,即在服務端關閉以前持久化存儲session對象信息,並在服務端重啓時進行session活化,即將鈍化的session從新加載進入內存。

咱們經常使用的tomcat容器爲咱們提供了這一個功能

session和cookie的對比

  1. session是服務端會話技術,cookie是客戶端會話技術
  2. session中的值類型能夠任意,大小能夠任意;cookie只能存字符值,大小受到瀏覽器的限制
  3. session因爲存放在服務端內存中相對安全;cookie直接由客戶端瀏覽器進行管理不太安全

11 Filter

基本使用

用法十分簡單,經過實現Filter接口,重寫三個方法便可,其中主要的過濾方法就是doFilter

@WebFilter(urlPatterns = "/hello3")
public class FilterDemo implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 隨服務器啓動而建立
        System.out.println("init ... ");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter ...");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        // 隨服務器關閉而銷燬
        System.out.println("destroy ...");
    }

}
複製代碼

生命週期

  • init 隨服務器啓動而建立
  • destroy 隨服務器關閉而銷燬

多個Filter

能夠對於同一個訪問地址配置多個Filter,構成所謂的過濾器鏈,在訪問到達servlet的具體方法前,先依次由外而內的經過filter的過濾(filterChain.doFilter以前的語句),結束servlet的service方法後,再由內而外的執行一次filter過濾(filterChain.doFilter以後的語句)

攔截順序

分爲兩種狀況:

  1. xml配置,則根據配置順序的不一樣,配置在上面的先執行,再一次向下
  2. 註解配置,根據Filter類的類名的首字母排序按順序執行(有點坑)

FilterConfig

做用同ServletConfig,用法更是同樣,再也不過多贅述,都是在xml中配置init-param或者在註解中配置,並只能在對應的filter中才能獲取。

Dispatcher

默認狀況下,過濾器只過濾請求,若是要過濾轉發或其餘的一些狀況,也想攔截時須要配置這個參數

  • REUQEST 默認,表明過濾請求
  • FORWARD 表明過濾轉發
  • INCLUDE 表明若是頁面被include標籤引用,會進行過濾(用的少)
  • ERROR 表明出現全局錯誤須要跳轉至錯誤頁時會進行攔截(用的少)

12 JSP & Listener等等。。。

關於JSP&Listener或者一些細枝末節我不作過多的探究,由於這部分對於後續的學習沒有更多的幫助,由於確實用的實在太少了,後續研究框架的源碼也不多會看到他們,因此算了算了,哈哈哈哈主要仍是懶

相關文章
相關標籤/搜索