深度長文回顧web基礎組件 深度長文回顧web基礎組件

摘自:https://www.cnblogs.com/ZhuChangwu/p/11712899.html

 

深度長文回顧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接口的實現類如上圖

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可以存儲任意類型的數據沒有大小限制
 
 
分類: SpringMvc
標籤: SpringMvc
好文要頂關注我收藏該文
0
0
 
 
 
« 上一篇: Spring 註冊BeanPostProcessor 源碼閱讀
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章