1. 程式人生 > >深度長文回顧web基礎元件

深度長文回顧web基礎元件

什麼是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在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的應用

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

@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的安全性更好一點

繼承圖如下:

上圖中我們最常使用的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的請求轉發

當用戶的某一個請求需要通過多個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");
  • 重定向

// 實現,從 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可以儲存任意型別的資料沒有大小限制