深度長文回顧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在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"})
執行原理:
- tomcat讀取xml配置文件中配置servlet,根據用戶配置的加載時機,通過反射技術創建出對象實例
- 用戶的請求報文經過tomcat的解析,分發到的Servlet下面,進行不同的回調處理
Servlet接口的方法
- 初始化方法, 創建servlet時執行一次
- 什麼時候被創建: 默認情況下 第一次訪問時被創建
- 一般我們都在web.xml配置,讓Servlet在啟動時完成加載
<load-on-startup>1</load-on-startup>
默認這個值是-1, 表示第一次訪問時被創建, 整數表示啟動時初始化
- 一般我們都在web.xml配置,讓Servlet在啟動時完成加載
- 此外: 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類型的數據,都可以從
ServletContex
t中獲取出來
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> <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域共享數據)
因為我們讓瀏覽器發送了兩次請求, 因此重定向的路徑中包含 項目路徑
Cookie
常用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是同一個
- 用戶通過瀏覽器向服務端發送請求
- 服務端驗證用戶的信息,通過驗證後,如果沒有當前的session就為用戶創建session(每個session都有唯一的id),創建cookie,給客戶端返回相應
set-coolkie:JSESSIONID=shdfiuhduifha
- 用戶再次訪問服務端,瀏覽器會自動攜帶上請求頭
cookie:JSESSIONID=shdfiuhduifha
- 服務器解析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可以存儲任意類型的數據沒有大小限制