深度長文回顧web基礎組件

  • 2019 年 10 月 21 日
  • 筆記

什麼是Serlvet ?

全稱 server applet 運行在服務端的小程序:

首先來說,這個servlet是java語言編寫的出來的應用程序,換句話說servlet擁有java語言全部的優點,比如跨越平台,一次編譯到處運行

其次: 相對於CGI(common gateway interface)規範而言,CGI是針對每一個用戶的請求創建一個進程處理,而servlet所在的服務器會對每一個請求創建一個線程來處理,雖然線程數量有上限,但是相對於創建進程來說,後者對系統資源的開銷更小

然後就是: 現在盛行javaWeb服務器Tomcat也是java語言編寫的,畢竟Tomcat有Serlvet容器支持,所以servlet和web服務器之間無縫連接

Servlet其實一個接口,一套規範,不同的廠家對它有不同的實現,tomcat也是如此,
web服務器會把解析http協議信息的邏輯封裝進他們的Servlet中,比如將用戶發送的請求(request) HttpRequestServlet,
把響應給用戶http報文的邏輯封裝進HttpResponseServlet中, 然後web服務器負責不同組件,不同servlet之間的調度關係,
什麼是調度呢? 比如說: 通過某個URL找到指定的Servlet,回調Servlet的service()方法處理請求

Servlet的體系結構

servlet接口的實現類

servlet接口的實現類如上圖

Servlet在java中是一個接口,封裝了被瀏覽器訪問到服務器(tomcat)的規則

添加serlvet

通過web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>  <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">      <display-name>Camel Routes</display-name>        <!-- Camel servlet -->      <servlet>          <servlet-name>app1</servlet-name>          <servlet-class>com.changwu.web.MyServlet</servlet-class>          <load-on-startup>1</load-on-startup>      </servlet>        <!-- Camel servlet mapping -->      <servlet-mapping>          <servlet-name>app1</servlet-name>          <url-pattern>/app1</url-pattern>      </servlet-mapping>    </web-app>  

通過註解

捨棄web.xml是serlet3.0添加全註解技術, 這個註解的屬性和需要在xml中配置的對應的

需要Tomcat7及以上才支持

@Target({ElementType.TYPE})  @Retention(RetentionPolicy.RUNTIME)  @Documented  public @interface WebServlet {        /**       * servlet-name       */      String name() default "";        /**       * The URL patterns of the servlet       */      String[] value() default {};        /**       * servlet的資源路徑, 可以為一個servlet配置多個訪問路徑       */      String[] urlPatterns() default {};        /**       * 啟動級別默認是-1,同樣意味着依然是第一次訪問時初始化       */      int loadOnStartup() default -1;        /**       * The init parameters of the servlet       */      WebInitParam [] initParams() default {};        /**       * Declares whether the servlet supports asynchronous operation mode.       *       * @see javax.servlet.ServletRequest#startAsync       * @see javax.servlet.ServletRequest#startAsync(ServletRequest,       * ServletResponse)       */      boolean asyncSupported() default false;        /**       * The small-icon of the servlet       */      String smallIcon() default "";         /**        * The large-icon of the servlet        */      String largeIcon() default "";        /**       * The description of the servlet       */      String description() default "";        /**       * The display name of the servlet       */      String displayName() default "";    }

servlet的路徑定義規則

  • /xxx
@WebServlet(urlPatterns = {"/app1","/app2"})
  • /xxx/yyy
@WebServlet(urlPatterns = {"/app1/app2"})
  • /xxx/*
@WebServlet(urlPatterns = {"/app1/*"})
  • *.do
@WebServlet(urlPatterns = {"*.do"})

執行原理:

  1. tomcat讀取xml配置文件中配置servlet,根據用戶配置的加載時機,通過反射技術創建出對象實例
  2. 用戶的請求報文經過tomcat的解析,分發到的Servlet下面,進行不同的回調處理

Servlet接口的方法

  • 初始化方法, 創建servlet時執行一次
  • 什麼時候被創建: 默認情況下 第一次訪問時被創建
    • 一般我們都在web.xml配置,讓Servlet在啟動時完成加載 <load-on-startup>1</load-on-startup>默認這個值是-1, 表示第一次訪問時被創建, 整數表示啟動時初始化
  • 此外: Servlet的init()方法僅僅被執行一次,說明serlet是單例的,那麼在並發的情況的就可能出現線程安全問題 , 解決: 盡量不要在serlvet中定義成員變量,我們最好去成員方法中定義變量,即使定義了, 不要提供set()方法,僅僅提供的get()
    @Override      public void init(ServletConfig servletConfig) throws ServletException {          System.out.println("init.....");      }
  • 獲取serlvet config 配置對象
    //      @Override      public ServletConfig getServletConfig() {          return null;      }
  • 提供服務的方法, 每次serlvet被訪問都會執行一次
    @Override      public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {          System.out.println("--------------------service------------");      }
  • 獲取serlvet 的信息, 版本等
    @Override      public String getServletInfo() {          return null;      }
  • 服務器正常關閉前, 銷毀servlet時 回調
  • 服務器非正常關閉,不會執行
    @Override      public void destroy() {          System.out.println("destroy");      }

Servlet3.0新特性

Servlet3.0中的重大升級是ServletContainerInitializer,通過這個技術使我們可以為現有的組件寫出可插拔的組件,與之相對應的是Servlet的新規範如下:

在執行的路徑下面創建指定的文件

/classpath:      --META-INF    (目錄)         --services   (目錄)            --javax.servlet.ServletContainerInitializer (文件)

我們可以在上面的文件中配置一個類的全類名,這個類是誰無所謂,但是只要它實現了這個ServletContainnerInitializer接口,並重寫它的onStart()方法,於是當容器(tomcat)啟動的時候就會調用這個類的 onStart()方法

這個規範帶來的革命決定是歷史性的,有了它我們的代碼就有了可插拔的能力,不信可以回想一下傳統的配置文件,如果想給項目進行升級,還不想改動xml文件,那是不可能的,但是現在不同了,只要讓我們的類實現這個ServletContainnerInitializer,重寫它的方法,它的onStart()就會被回調,而其他的功能不受響應,去掉這個類,項目整體也不受響應

示例:

容器啟動的時候,會把容器中,被@HandlerTypes(value={Test.class}) 中指定的所有Test.class實現類(子類,子接口)的實例傳遞進下面的set集合

@HandlesTypes(Test.class)  public class ChangWuInitializer implements ServletContainerInitializer {        @Override      public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {          System.out.println(set);      }  }

通過上面onstart()方法可以看到,第二個參數位置上是 ServletContext, 這個對象是什麼?有啥用? 在下文中單獨開一個模塊說

ServletContext

tomcat會為每一個web項目創建一個全局唯一的ServeltContext,這個對象裏面封裝着整個應用的信息,常用的當作域對象,所有的servlet之間共享數據,同時他還可以獲取出web.xml文件中的數據

功能:

  • 獲取MIME類型
    • MIME類型是互聯網通信中定義的文件數據類型
    • 格式: 大類型/小類型 如: test/html

      在tomcat的配置文件目錄中存在web.xml ,裏面的存在大量的MEMI類型的數據,都可以從ServletContext中獲取出來

    String getMimeType(String file)
  • 域對象(共享數據)

範圍: 類似於Session,通過ServletContext對象我們也可以實現數據共享,但值得注意的是,Session是只能在一個客戶端中共享數據,而ServletContext中的數據是在所有客戶端中都可以實現數據共享的。

方法:

setAttribute(String name,Onject obj);  getAttribute(String name);  removeAttribute(String name);
  • 獲取文件真實的文件路徑
    方法
this.getServletContext().getRealPath("/");  // 現在訪問的目錄是tomcat中和WEB-INF同級目錄
  • 實現請求轉發
// 方式1:  request.getRequestDispatcher("/url").forward(req,res);    // 方式2:  this.getServletContext().getRequestDispatcher("/url").forward(req,res);
  • 獲取web應用的初始化參數

我們可以用標籤為servlet配置初始化參數,然後使用ServletConfig對象獲取這些參數,假如有如下的MyServlet,它的配置為:

<servlet>      <servlet-name>MyServlet</servlet-name>      <servlet-class>com.gavin.servlet.MyServlet</servlet-class>      <init-param>          <param-name>encoding</param-name>          <param-value>utf-8</param-value>      </init-param>  </servlet> 

獲取:

  String encoding = this.getServletConfig().getInitParameter("encoding");

如何獲取:

ServletContext在web應用上下文中以單例的形式存在,下面兩種獲取方式得到的ServletContext是同一個對象

ServletContext servlet1 = request.getServletContext();  ServletContext servlet2 = this.getServletContext();  this.getServletConfig().getServletContext();

生命周期

服務器一啟動就創建,服務器關閉時才銷毀

註冊三大web組件(servlet filter listener)

  • Servlet
addServlet、createServlet、getServletRegistration、getServletRegistrations
  • Filter
addFilter、createFilter、getFilterRegistration、getFilterRegistrations
  • 監聽器
addListener、createListener

Spring-web對Servlet3.0的應用

spring-web-Init體系圖

先上一張繼承體系圖,下面圍繞這張圖片展開

@HandlesTypes({WebApplicationInitializer.class})  public class SpringServletContainerInitializer implements ServletContainerInitializer {      public SpringServletContainerInitializer() {      }        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {          List<WebApplicationInitializer> initializers = new LinkedList();          Iterator var4;          if (webAppInitializerClasses != null) {              var4 = webAppInitializerClasses.iterator();                while(var4.hasNext()) {                  Class<?> waiClass = (Class)var4.next();                  if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {                      try {                          initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());                      } catch (Throwable var7) {                          throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);                      }                  }              }          }  ...  }

可以看到,Spring應用一啟動就會加載WebApplicationInitializer接口下的所有組件,並且,只要這些組件不是接口,不是抽象類,Spring就為它們創建實例

更進一步看一下上下文中WebApplicationInitializer接口的實現類

AbstractContextLoaderInitializer

看他對onstart()方法的重寫, 主要幹了什麼呢? 註冊了一個上下文的監聽器(藉助這個監聽器讀取SpringMvc的配置文件),初始化應用的上下文

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {      protected final Log logger = LogFactory.getLog(this.getClass());        public AbstractContextLoaderInitializer() {      }        public void onStartup(ServletContext servletContext) throws ServletException {          this.registerContextLoaderListener(servletContext);      }        protected void registerContextLoaderListener(ServletContext servletContext) {          WebApplicationContext rootAppContext = this.createRootApplicationContext();          if (rootAppContext != null) {              ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);              listener.setContextInitializers(this.getRootApplicationContextInitializers());              servletContext.addListener(listener);          } else {              this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");          }        }

AbstractDispatcherServletInitializer

見名知意,他是DispatcherServlet的初始化器,他主要做了什麼事呢?

  • 上面看了,它的父類初始化上下文,於是它調用父類的構造,往上傳遞web環境的上下文
  • 緊接着添加DispatcherServlet
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {  public static final String DEFAULT_SERVLET_NAME = "dispatcher";    public AbstractDispatcherServletInitializer() {  }    public void onStartup(ServletContext servletContext) throws ServletException {      super.onStartup(servletContext);      this.registerDispatcherServlet(servletContext);  }    protected void registerDispatcherServlet(ServletContext servletContext) {      String servletName = this.getServletName();      Assert.hasLength(servletName, "getServletName() must not return null or empty");        // 可以看一下,它創建的是web的容器      WebApplicationContext servletAppContext = this.createServletApplicationContext();      Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");        // 創建負責調度的 DispatcherServlet      FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);      Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");      dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());        // 添加Servlet      Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);      if (registration == null) {          throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");      } else {            // 添加servlet的mapping信息          registration.setLoadOnStartup(1);          registration.addMapping(this.getServletMappings());          registration.setAsyncSupported(this.isAsyncSupported());          Filter[] filters = this.getServletFilters();          if (!ObjectUtils.isEmpty(filters)) {              Filter[] var7 = filters;              int var8 = filters.length;                for(int var9 = 0; var9 < var8; ++var9) {                  Filter filter = var7[var9];                  this.registerServletFilter(servletContext, filter);              }          }            this.customizeRegistration(registration);      }    ...    }

AbstractAnnotationConfigDispatcherServletInitializer

createRootApplicationContext重寫了父類的創建上下文的方法,我覺得這算是一個高潮吧, 因為啥呢,AnnotationConfigWebApplicationContext是SpringMvc使用的應用的上下文,怎麼創建的源碼在下面,其實我有在Spring源碼閱讀中寫過這個方面的筆記,下面僅僅是將配置類傳遞給Spring的bean工廠,並沒有對配置類進行其他方面的解析,或者是掃描包啥的

createServletApplicationContext()重寫了它父類的創建serlvet上下文的方法,

有個點,大家有沒有發現,SpringMvc的上下文和Servlet的上下文是同一個對象,都是AnnotationConfigWebApplicationContext,不同點就是添加了if-else分支判斷,防止重複創建

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {      public AbstractAnnotationConfigDispatcherServletInitializer() {      }        @Nullable      protected WebApplicationContext createRootApplicationContext() {          Class<?>[] configClasses = this.getRootConfigClasses();          if (!ObjectUtils.isEmpty(configClasses)) {              AnnotationConfigWebApplicationContext context = new 有沒有大神了解這個情況, SpringMvc的應用上下文和Servlet應用上下文竟然是同一個();              context.register(configClasses);              return context;          } else {              return null;          }      }        protected WebApplicationContext createServletApplicationContext() {          AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();          Class<?>[] configClasses = this.getServletConfigClasses();          if (!ObjectUtils.isEmpty(configClasses)) {              context.register(configClasses);          }            return context;      }

對比官網推薦的啟動案例:

下面的是Spring官網推薦是通過註解的配置方法,仔細看看,其實和上面的Spring-Web模塊的做法是一樣的

public class MyWebApplicationInitializer implements WebApplicationInitializer {        @Override      public void onStartup(ServletContext servletCxt) {            // Load Spring web application configuration          AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();          ac.register(AppConfig.class);          ac.refresh();            // Create and register the DispatcherServlet          DispatcherServlet servlet = new DispatcherServlet(ac);          ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);          registration.setLoadOnStartup(1);          registration.addMapping("/app/*");      }  }

基於Servlet3.0全註解方式整合SpringMvc

經過前面的分析,第一個結論是:服務器一啟動,經過自上而下的繼承體系AbstractAnnotationConfigDispacherServletInitializer會被加載執行,所以,當我們想使用全註解方式完成繼承SpringMVC時,繼承AbstractAnnotationConfigDispacherServletInitializer就好

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {      // 獲取Spring容器的配置類      @Override      protected Class<?>[] getRootConfigClasses() {          return new Class[]{RootConfig.class};      }      // 獲取web容器的配置類      @Override      protected Class<?>[] getServletConfigClasses() {          return new Class[]{WebConfig.class};      }        /**       * 獲取DispatcherSerlvet的映射信息       * / : 表示攔截所有請求(包含靜態資源 XXX.js  XXX.jpg) 但是不包含 XXX.jsp       * /* : 表示攔截所有請求(包含靜態資源 XXX.js  XXX.jpg) 包含 XXX.jsp       * @return       */      @Override      protected String[] getServletMappings() {          return new String[]{"/"};      }  }  

下面的兩個配置類, 按照他的意思,分成了兩個配置類,一個是web上下文中的配置類,另一個是Spring原生環境的配置類

但是吧,看看下面的配置真的是特別麻煩,一個得排除@Controller,完事另一個得包含@Controller,其實Spring原生上下文都認識這些通用註解,倒不如直接就一個配置類,還省事

@ComponentScan(value = "com.changwu",includeFilters = {          @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})  },useDefaultFilters = false)  public class WebConfig {  }    @ComponentScan(value = "com.changwu",excludeFilters = {          @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})  })  public class RootConfig {  }

Servlet3.0 異步請求處理器

早前的前後端請求響應的模型是怎樣的呢? 用戶發送的請求經過網絡傳輸到Tomcat,Tomcat中存在一個線程池,這時Tomcat會從線程池中取出一條線程專門處理這個請求,一直到處理完畢,給了用戶響應之後才將此線程回收到線程池,但是線程池中的線程終究是有限的,一旦同時好幾百的連接進來,Tomcat的壓力驟然上升,難免會出現阻塞的現象

Serlet3.0引入的異步處理,讓主線程擁有非阻塞的特性,這樣tomcat接收請求訪問的吞吐量就會增加

示例:

// 啟動異步  @WebServlet(value = "/async",asyncSupported = true)  public class MyAsyncServlet extends HttpServlet {      @Override      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {          doGet(req,resp);      }        @Override      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {          // 開啟異步處理          AsyncContext asyncContext = req.startAsync();            asyncContext.start(new Runnable() {              @Override              public void run() {                  // do other things                  // 結束                  asyncContext.complete();                  // 響應                  ServletResponse response = asyncContext.getResponse();                  try {                      response.getWriter().write("123");                  } catch (IOException e) {                      e.printStackTrace();                  }              }          });      }  }

SpringMvc的異步任務

針對Servlet3.0的異步特性,SpringMvc相關的支持是提供了異步線程池

DeferredResult

我覺得這個異步的實現方式簡直是無與倫比!!!無法言表!!!

@GetMapping("/quotes")  @ResponseBody  public DeferredResult<String> quotes() {      DeferredResult<String> deferredResult = new DeferredResult<String>();      // Save the deferredResult somewhere..      return deferredResult;  }    // From some other thread...  deferredResult.setResult(result);

Callable

  • 方法的最後將Callable返回
  • call()方法中做寫需要異步處理器的邏輯

執行流程:

  • SpringMvc會將這個Callable放到一個叫TaskExcutor中執行
  • DispatcherSerlvet和所有的Filter退出web容器,但是Response保持打開狀態
  • SpringMvc會將Callable的返回結果重寫派發給setlvet恢復之前的處理
  • 根據Callable返回的結果SpringMvc進行渲染
@PostMapping  public Callable<String> processUpload(final MultipartFile file) {      return new Callable<String>() {          public String call() throws Exception {              // ...              return "someView";          }      };  }

Request

請求方式與很多種,get post head trace options 還有put delete

其中get post put delete 是RestfulAPI中推薦,也是現在盛行使用的四種請求方法

get: 最為簡單的請求方式,一般數據添加在url後面一般這樣寫username?張三&password?123123, 由於URL的長度有限制,故能傳輸的數據一般在1M左右, 而且數據明文傳輸,像上面那樣,村咋存在安全隱患

post的數據存放在請求體中,一般沒有大小限制,相對於get而言,post的安全性更好一點

繼承圖如下:

Request繼承圖

上圖中我們最常使用的HttpServletRequest竟然是個接口,當時一開始學web的時候確實覺得很奇怪,但是現在想想其實也還好了,因為Tomcat提供了實現類org.apache.catalina.connector.RequestFacade

獲取請求行數據-GET

點擊查看文檔

請求行: GET /test/app?name=zhangsan http/1.1

  • 獲取請求方法: GET
String getMethod()
  • 獲取虛擬路徑(項目路徑): /test
String getContextPath()
  • 獲取Servlet路徑; /app
String  getServletPath()
  • 獲取get請求的請求參數: name=zhangsan
String getQueryString()
  • 獲取URI : /test/app
String getRequestURI()
  • 獲取URL : http:localhost/test/app
String getRequestURL()
  • 獲取協議版本
String getProtocol()
  • 獲取遠程主機地址
String getRemoteAddr()

獲取請求頭數據

  • 根據名稱獲取請求頭
String getHeader(String name);
  • 獲取所有的請求頭
Enumertion<String> getHeaderNames(); 獲取所有請求頭的名稱

獲取請求體數據

僅僅有post方式,才會有請求體:使用它分成兩步:

  • 從request中獲取流對象
BufferReader getReader(); // 獲取字符輸入流  ServletInputStream getInputStrream(); // 獲取位元組輸入流
  • 從流對象中獲取到需要的數據

通用的方法

  • 根據參數名獲取參數值
String getParamter(String name); 根據參數名獲取參數值
  • 根據參數名,獲取參數值數組
String [] getParameterValues(String name)
  • 獲取所有請求的參數名稱
Enumeration<String> getParamerterNames()
  • 獲取所有參數鍵值對形式的map集合
Map<String,String[]) getParamerterMap()

有了通用的方法特性之後,我們就不跟針對doGet,doPost兩種方式寫兩份代碼, 只要在doGet()或者doPost()中調用另外一個就ok,因為方法針對兩者通用

解決中文亂碼

首先: Tomcat8自身解決了中文亂碼問題

Post方式提交數據依然存在亂碼問題,像下面這樣先設置編碼再使用 req

request.setCharacterEncoding("utf-8")

request的請求轉發

forward

當用戶的某一個請求需要通過多個Servlet協作完成時,請求在Servlet之間跳轉,這種資源跳轉的方式稱為請求轉發

使用方法:通過當前的request獲取出RequestDispacher對象,通過這個對象的forward(req,res)進行轉發的動作

RequestDispatcher getRequestDispacher(String path) // path是另一個Servlet的url-pattern  forward(currentReq,currentRes);

特點:

  • 瀏覽器地址欄路徑沒有發生變化
  • 服務器內部官網的資源跳轉,不能跳往別的站點
  • 一次轉發,對瀏覽器來說,僅僅發送了一次請求

因為我們沒讓瀏覽器發送兩次請求,在服務端完成了請求轉發,所以上面的path僅僅是servlet-url-pattern,而不包含項目路徑

域對象-共享數據

域對象: request域, 既然是域對象,他就有自己的作用範圍,request的作用範圍是什麼呢? 就是一次請求,每次請求都是一個域, 換句話說,如果說客戶端的一次請求經過了AServlet,然後AServlet將請求轉發到了BServlet,name AServlet BServlet就在一個域中,也就可以共享彼此的數據

怎麼玩?

在AServlet  setAttribute(String name,Object obj);  請求轉發到BServlet    在BServlet  getAttribute(String name);    移除  removeAttribute(String name);

Reponse

響應信息格式如下:

HTTP/1.1 200 OK  //響應行    ---------------------------------------    響應頭   // 服務器的類型  Server: server-name  //服務端告訴客戶端,自己推送給它的數據的編碼格式(瀏覽器根據指定的類型進行解碼)  Content-Type: text/html;charset=utf-8  // 響應內容的長度  Content-Length: XXX  // 響應日期  Date: XXX  //服務器告訴瀏覽器用什麼格式打開響應體數據, 默認是in-line 表示在當前頁面中打開, attachment(文件下載)  Content-disposition: in-line    ----------------------------------------    (響應空行)    ----------------------------------------    XXX   // 響應體

常用方法

Response對象就是用來設置響應消息的對象

  • 設置響應行
// 格式: HTTP/1.1 200 ok  setStatus(int status);
  • 設置響應頭
setHeader(String name, String value);
  • 設置響應體
// 0 在往客戶端寫中文前先設置編碼  response.setContentType("text/html;charset=utf-8");  // 1. 獲取到輸出流(位元組流/字符流)    // 2. 往客戶端寫  response.getWriter().write("XXXXXXX");
  • 重定向

redirect

// 實現,從 AServlet 重定向到BServlet    // 兩步實現: 重定向  // 設置狀態碼 / 響應頭  reponse.setStatus(302);  response.setHeader("location","/項目路徑/serlet-url-pattern");    // 單行代理實現重定向  response.sendRedirect("/項目路徑/serlet-url-pattern");

特點:

  • 重定向: 瀏覽器地址欄路徑改變了
  • 重定向: 可以請求其他服務器
  • 重定向: 實際上發起了兩次請求 (不能使用request域共享數據)

因為我們讓瀏覽器發送了兩次請求, 因此重定向的路徑中包含 項目路徑

常用api

客戶端會話技術,將數據保存在瀏覽器本地, 下一次訪問時會攜帶着cookie

  • 創建cookie,綁定數據
new Cookie(String name,String value)
  • 發送cookie
response.addCookie(Cookie cookie);
  • 獲取解析cookie
Cookie [] cookies = request.getCookies();
  • 一次發送多個cookie
Cookie c1 = new Cookie(String name,String value)  Cookie c2 =new Cookie(String name,String value)  reponse.addCookie(c1);  reponse.addCookie(c2);
  • cookie的生命周期

默認cookie存儲在瀏覽器內存中,一旦瀏覽器關閉,cookie銷毀

設置cookie的生命周期

setMaxAge(int seconds);  seconds 為存活時間,  正數: 表示以文件的形式進行持久化,默認單位 s  負數: 表示僅僅存在於內存中  零:   表示讓瀏覽器刪除cookie
  • cookie存儲中文

tomcat8之前,cookie不支持中文,Tomcat8之後cookie支持中文

tomcat8之前需要進行轉碼,一般採用URL編碼

  • cookie獲取的範圍

默認情況下:在一個tomcat中的部署的多個web項目之間cookie是不能共享的

但是可以通過下面的方法設置更大路徑,實現想要的效果

setPath(String path); // 默認是當前的虛擬目錄    // 可以設置成下面這樣  setPath("/"); // 默認是當前的虛擬目錄

跨域tomcat之間cookie共享使用– 根據域名劃分

setDomain(String path); // 只要一級域名相同,則多個服務器之間共享 cookie

特點:

  • 存儲在瀏覽器,不安全
  • 瀏覽器對單個cookie大小(一般都在4kb),對同一個域名下的cookie總數也有限制(一般20個以內)

小場景:

服務器,瀏覽器協作的流程: 比如登錄: 用戶通過瀏覽器往服務端發送登錄請求,服務端驗證用戶名密碼,通過後往客戶端的發送cookie, 其實是設置了響應頭set-cookie=XXX, 瀏覽器碰到這種響應頭,就把這個響應體緩存在本地,再次請求這個網站時,會自動攜帶這個請求頭cookie=XXX , 後端通過解析用戶發送過來的cookie,可以判斷當前請求的用戶是否是合法的

Session

服務端會話技術,在一次會話中多次請求共享數據,將數據保存在服務端的對象–HttpSession

session也是域對象

常用API

  • 獲取session
HttpSession session = request.getSession();
  • 使用session
setAttribute(String name,Object obj);    getAttribute(String name);    romoveAttribute(String name);

如何確保多次會話中,多次獲取到的session是同一個

  1. 用戶通過瀏覽器向服務端發送請求
  2. 服務端驗證用戶的信息,通過驗證後,如果沒有當前的session就為用戶創建session(每個session都有唯一的id),創建cookie,給客戶端返回相應 set-coolkie:JSESSIONID=shdfiuhduifha
  3. 用戶再次訪問服務端,瀏覽器會自動攜帶上請求頭cookie:JSESSIONID=shdfiuhduifha
  4. 服務器解析cookie攜帶的JSESSIONID=shdfiuhduifha便可以找出唯一的session

細節

  • 客戶端關閉,服務端不關閉,兩次獲取到的session一樣嗎?
    • session是依賴cookie的,客戶端關閉,cookie被幹掉了,也就是說本次會話也就結束了,後端的session將被幹掉
    • 但是如果我們發送給瀏覽器一個可以存活很長時間的cookie,再次打開瀏覽器訪問後端,session還是同一個
  • 客戶端不關閉,服務端關閉,兩次獲取到的session一樣嗎?
    • 服務器都沒了,session肯定被幹掉了,session肯定不一樣
    • 補救:鈍化 tomcat在服務器正常關閉前,將session序列化持久化到磁盤上
    • 補救:活化 tomcat在服務器啟動時,將session重新加載進內存

      idea中可以成功完成鈍化,但是不能完成活化

  • session失效時間
    • 服務器關閉
    • 使用api,自動關閉 invalidate()
    • session默認的失效時間30分鐘

過期時間可以在Tomcat中的配置文件目錄下的web.xml中配置

<session-config>      <session-timeout>30</session-timeout>  </session-config>

特點:

  • session用於存儲一次會話的多次請求數據,存儲在服務端
  • session可以存儲任意類型的數據沒有大小限制