什麼是Servlet?html
Servlet是運行在Web服務器或應用服務器上的程序,它是做爲來自Web瀏覽器或其餘HTTP客戶端的請求和HTTP服務器上的數據庫前端
或應用程序之間的中間層。java
servlet架構:web
Servlet的生命週期:spring
Servlet生命週期可被定義爲從建立直到毀滅的整個過程。數據庫
一、Servlet經過調用init()方法進行初始化編程
二、Servlet調用service()方法來處理客戶端的請求,Servlet容器在執行service方法以前加載Servlet。json
三、Servlet經過調用destory()方法終止(結束)後端
最後,Servlet是由JVM的垃圾回收器進行垃圾回收的。瀏覽器
init方法只在第一次建立Servlet時調用一次
service方法是執行實際任務的主要方法。Servlet容器(即Web服務器)調用service()方法來處理客戶端(瀏覽器)的請求,並把格式化
的響應寫回給客戶端。每次服務器接收到一個Servlet請求時,服務器會產生一個新的線程並調用服務。service()方法由容器調用,service()方法檢查HTTP請求類型(GET、POST、PUT、DELETE等),service方法會在適當的時候調用doGet、doPost、doPut、doDelete等方法,因此,咱們不用對service()方法作任何動做,您只須要根據來自客戶端的請求類型來重寫doGet或doPost便可。
destory方法只會被調用一次,在Servlet聲明週期結束時被調用。在destory方法中能夠關閉數據庫鏈接、中止後臺線程、把Cookie列表或點擊計數器寫入到磁盤,並執行其餘相似的清理活動。
Servlet是服務HTTP請求並實現javax.servlet.Servlet接口的Java類。Web應用程序開發人員一般編寫Servlet來擴展javax.servlet.http.HttpServlet,並實現Servlet接口的抽象類專門用來處理HTTP請求。
讀取HTTP頭的方法,如getSession,getParameter(String name)等,這些方法可在Servlet程序中讀取HTTP頭,經過HttpServlet對象可調用。
過濾器:
Servlet過濾器能夠動態地攔截請求和響應,以變換或使用包含在請求或響應中的信息。
Servlet過濾器是可用於Servlet編程的Java類,能夠實現如下目的:
1):在客戶端的請求訪問後端資源以前,攔截這些請求
2):在服務器的響應發送給客戶端以前,處理這些響應。
如:身份驗證過濾器、加密過濾器、日誌記錄和審覈過濾器等。
過濾器經過Web部署描述符(web.xml)中XML標籤來聲明,而後映射到應用程序的web.xml中的Servlet名稱或URL模式。當Web容器啓動Web應用程序時,它會爲在web.xml中的每個過濾器建立一個實例。
Filter的執行順序與在web.xml配置文件中的配置順序一致,通常把Filter配置在全部的Servlet以前。
過濾器是一個實現了javax.servlet.Filter接口的Java類。javax.servlet.Filter接口定義了三個方法:
1):public void doFilter (ServletRequest, ServletResponse, FilterChain)
完成實際的過濾操做,當客戶端請求的URL和過濾設置的URL匹配時,Servlet先調用過濾器的doFilter方法,FilterChain用於訪問後續過濾器
2):public void init(FilterConfig filterConfig)
Web應用程序啓動時,Web服務器將建立Filter的實例對象,並調用其init方法,讀取web.xml配置,完成對象的初始化功能。由於filter對象只
會建立一次,因此init方法也只會執行一次。在filterConfig中能夠獲取配置信息
3):public void destroy()
Servlet容器在銷燬過濾器實例前調用該方法,在該方法中釋放Servlet過濾器佔用的資源。
Servlet異常處理
當一個Servlet拋出一個異常時,Web容器在使用了exception元素的web.xml中搜索與拋出的異常類型相匹配的配置。所以必須在web.xml中使用error-page
元素來指定對特定異常或HTTP狀態碼做出相應的Servlet調用。如:
<servlet> <servlet-name>ErrorHandler</servlet-name> <servlet-class>com.runoob.test.ErrorHandler</servlet-class> </servlet> <!-- servlet mappings --> <servlet-mapping> <servlet-name>ErrorHandler</servlet-name> <url-pattern>/TomcatTest/ErrorHandler</url-pattern> </servlet-mapping> <error-page> <error-code>404</error-code> <location>/TomcatTest/ErrorHandler</location> </error-page> <!-- 全部的異常的通用的錯誤處理程序 ,與上面的互斥--> <error-page> <exception-type>java.lang.Throwable</exception-type > <location>/ErrorHandler</location> </error-page>
Servlet四大域對象
1)、pageContext:做用域爲page,頁面執行期,只在當前頁面有效
2)、request:是表示一個請求,只要發出一個請求就會建立一個request,它的做用域僅在當前請求中有效
3)、session:服務器爲每個會話建立一個Session對象,因此Session中的數據僅可供當前會話中全部的Servlet共享
4)、Application(ServletContext上下文):全部的用戶均可以取得此信息,此信息在整個服務器端被保留。Application屬性範圍只要設置一次,則
全部的網頁窗口均可以取得數據。ServletContext在服務器啓動時建立,在服務器關閉時銷燬,一個JavaWeb應用只建立一個ServletContext對象。
Servlet3.0
Servlet3.0支持使用註解的方式取代以前在web.xml中的servlet、filter、listener的定義配置。
如:註冊Servlet:
@WebServlet(name = "MyServlet", urlPatterns = {"/foo", "/bar"}, initParams = {@WebInitParam(name = "contextConfigLocation", value = "classpath*:/spring-mvc.xml"),
@WebInitParam(name = "xxx", value = "xxx")})
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}
註冊Filter:
@WebFilter(value = "/foo") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {} @Override public void destroy() {} }
註冊Listener:
@WebListener public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext sc=servletContextEvent.getServletContext();// 一個web應用一個ServletContext
// 在這裏能夠動態往容器中添加Servlet、Filter、Listener等
} @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
也能夠在項目啓動時候添加手動Servlet、Filter、Listener等
package com.atguigu.servlet; import java.util.EnumSet; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.HandlesTypes; import com.atguigu.service.HelloService; //容器啓動的時候會將@HandlesTypes指定的這個類型下面的子類(實現類,子接口等)傳遞過來; //傳入感興趣的類型; @HandlesTypes(value={HelloService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /** * 應用啓動的時候,會運行onStartup方法; * * Set<Class<?>> arg0:感興趣的類型的全部子類型; * ServletContext arg1:表明當前Web應用的ServletContext;一個Web應用一個ServletContext; * * 1)、使用ServletContext註冊Web組件(Servlet、Filter、Listener) * 2)、使用編碼的方式,在項目啓動的時候給ServletContext裏面添加組件; * 必須在項目啓動的時候來添加; * 1)、ServletContainerInitializer獲得的ServletContext; * 2)、ServletContextListener獲得的ServletContext; */ @Override public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException { // TODO Auto-generated method stub System.out.println("感興趣的類型:"); for (Class<?> claz : arg0) { System.out.println(claz); }
// 使用ServletContext註冊Servlet
ServletRegistration.Dynamic servlet = sc.addServlet("myServlet", "com.yang.spbo.servlet.MyServlet");
// Servlet映射關係
servlet.addMapping("/foo");
// 註冊Listener
sc.addListener(MyListener.class);
// 註冊Filter
FilterRegistration.Dynamic filter=sc.addFilter("myFilter",new MyFilter());
// Filter的映射
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
}
}
若是是SpringBoot項目須要啓動類上加上@ServletComponentScan註解;或者在每一個Servlet、Filter、Listener上加上@Component註解
Servlet3.0和SpringMVC整合
Web容器在啓動的時候,會掃描每一個jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,並加載這個文件指定的類
而Spring web包下:
指定的類爲:
org.springframework.web.SpringServletContainerInitializer
所以,在Web容器啓動的時候會加載SpringServletContainerInitializer
SpringServletContainerInitializer:
package org.springframework.web; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes({WebApplicationInitializer.class}) public class SpringServletContainerInitializer implements ServletContainerInitializer { public SpringServletContainerInitializer() { } public void onStartup(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)waiClass.newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); } else { servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); var4 = initializers.iterator(); while(var4.hasNext()) { WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next(); initializer.onStartup(servletContext); } } } }
SpringServletContainerInitializer 實現了ServletContainerInitializer ,而且指定了@HandlesTypes({WebApplicationInitializer.class}),所以在Web容器啓動的時候會加載WebApplicationInitializer
接口下的全部組件,若是組件不是接口也不是抽象類,那麼則爲WebApplicationInitializer組件建立對象,並調用其onStartup方法。
Spring 容器分層結構(父子容器):
WebApplicationInitializer接口的3個抽象子類:
1)、AbstractContextLoaderInitializer:
(1)在onStartup方法中建立根容器【createRootApplicationContext();】
2)、AbstractDispatcherServletInitializer:在onStartup方法中:
(1)建立一個web的ioc容器【createServletApplicationContext();】
(2)建立了DispatcherServlet【createDispatcherServlet();】
(3)將建立的DispatcherServlet添加到ServletContext中【Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(this.getServletMappings());】
(4)【protected abstract String[] getServletMappings();】此方法由子類來實現
3)、AbstractAnnotationConfigDispatcherServletInitializer:【AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer】
繼承自2)中的AbstractDispatcherServletInitializer,是註解方式配置的DispatcherServlet初始化器
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { public AbstractAnnotationConfigDispatcherServletInitializer() { } // 建立根容器(只掃描Services和Repositories組件) protected WebApplicationContext createRootApplicationContext() {
// 獲取根配置文件(Spring配置文件),須要子類提供 Class<?>[] configClasses = this.getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configClasses); return rootAppContext; } else { return null; } } // 建立Web的IOC容器(只掃描controller,ViewResolver,HandlerMapping組件) protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
// 獲取web配置類(Spring MVC配置文件),須要子類提供 Class<?>[] configClasses = this.getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { servletAppContext.register(configClasses); } return servletAppContext; } protected abstract Class<?>[] getRootConfigClasses(); protected abstract Class<?>[] getServletConfigClasses(); }
所以,若是要以註解方式來啓動SpringMVC;就自定義一個類繼承AbstractAnnotationConfigDispatcherServletInitializer ,而後實現其抽象方法
指定配置文件信息。
若不使用註解的方式,原來的web.xml配置文件爲:
<web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> // 即Spring根配置文件 </context-param> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> // 即Spring MVC配置文件 </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
使用註解的方式,省略掉web.xml的自定義web應用啓動類
//web容器啓動的時候建立對象;調用方法來初始化容器以及前端控制器 public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //獲取根容器的配置類;(Spring的配置文件) 父容器; @Override protected Class<?>[] getRootConfigClasses() { // TODO Auto-generated method stub return new Class<?>[]{RootConfig.class}; } //獲取web容器的配置類(SpringMVC配置文件) 子容器; @Override protected Class<?>[] getServletConfigClasses() { // TODO Auto-generated method stub return new Class<?>[]{AppConfig.class}; } //獲取DispatcherServlet的映射信息 // /:攔截全部請求(包括靜態資源(xx.js,xx.png)),可是不包括*.jsp; // /*:攔截全部請求;連*.jsp頁面都攔截;jsp頁面是tomcat的jsp引擎解析的; @Override protected String[] getServletMappings() { // TODO Auto-generated method stub return new String[]{"/"}; } }
至此,即將Spring容器以及前端控制器建立好了。
原來在使用xml配置方式的時候,會在springmvc.xml中配置如下信息
<!--加載屬性配置文件--> <context:property-placeholder ignore-unresolvable="false" location="classpath:spmbs.properties"/> <!-- 註解驅動,能夠代替註解的處理器適配器的配置和處理器映射器的配置,默認還會加載不少其餘配置好比:json轉換器的配置--> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven> <!-- 組件掃描,自動注入--> <context:component-scan base-package="cn.com.yang.modules"></context:component-scan> <!-- 視圖解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!--對靜態資源的訪問,將沒法映射到controller方法的path交給default-servlet-handler處理--> <mvc:default-servlet-handler/> <!--攔截器配置--> <mvc:interceptors> <!--多個攔截器順序執行--> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/sys/login/**"/> <bean class="cn.com.yang.interceptor.LoginHandlerInterceptor"/> </mvc:interceptor> <mvc:interceptor> <!--/**攔截全部的url及其子url,/* 只攔截根url不能攔截子url--> <mvc:mapping path="/**"/> <bean class="cn.com.yang.interceptor.MyHandlerInterceptor1"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.com.yang.interceptor.MyHandlerInterceptor2"/> </mvc:interceptor> </mvc:interceptors>
那麼使用配置類的方式的話怎麼配置這些信息呢?
使用@EnableWebMvc註解在mvc配置類上,開啓註解驅動,至關於xml中的 <mvc:annotation-driven/>
讓配置類實現WebMvcConfigurer接口,實現接口中的方法進行其餘配置;也可讓配置類繼承WebMvcConfigurerAdapter抽象類,其實現了WebMvcConfigurer接口,這樣咱們須要
哪些配置,重寫抽象類中的方法便可
可參考Spring官方文檔:https://docs.spring.io/spring/docs/5.2.0.BUILD-SNAPSHOT/spring-framework-reference/web.html#mvc-config-customize
如:
@Configuration @EnableWebMvc // 至關於xml中的 <mvc:annotation-driven/> public class AppConfig extends WebMvcConfigurerAdapter { /** * 視圖解析器 * * @param registry */ @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views", ".jsp"); } /** * 靜態資源訪問,至關於<mvc:default-servlet-handler/> * * @param configurer */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } /** * 攔截器 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**"); } }
SpringMVC實現異步處理:
@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(data);
Controller返回一個DeferredResult,並將deferredResult對象保存到某處,如消息隊列中,並將deferredResult返回,而後異步進行處理。當有其餘線程處理完成以後
將deferredResult從隊列中取出來而後設置result值時,則會響應給客戶端。