一個禿了頭的大師託夢給我說要想稱爲大神就要有一個完善的我的知識體系,我從夢中驚醒,打開筆記本開始了整理。。。php
2020年了,爲何仍是看Servlet???首先這是一個必經的階段,當初開始學習Java Web的時候,這部分就是重點,第二就是在學習了一些更加高級的框架時,仍是時不時會看到它的身影,像Spring等,在學習他的源碼的時候就能夠看到它維護的DispatcherServlet,因此不要再問爲何2020還看這麼土的東西??html
固然還有一個問題就是要不要看JSP,這個我的認爲簡單瞭解便可,如今企業中的開發基本都是先後端分離的模式,就算是簡單的搭建,Freemarker、Thymeleaf模板語言也是更加的方便,so 若是你有空,那麼你能夠去看看,可是不須要太過深究java
Servlet是一個接口,定義了servlet容器識別Java程序的規範web
最基礎的servlet實現,直接實現servlet接口,重寫他的5個方法,同時須要在web.xml中配置這個servlet,servlet是根據web.xml中的配置找到對應url的處理者後端
public class HelloServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init ...");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service ...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy ...");
}
}
複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.learn.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
複製代碼
注意⚠️:Servlet3.0是從J2EE 6開始才支持的哦,主要是用於簡化開發,不用再繁瑣的xml配置了,因此能夠直接不使用web.xml數組
使用方法很簡單,直接在servlet上嗎使用@WebServlet註解即刻,經過查看源碼能夠知道,原來配置在web.xml中的配置所有均可以挪到註解的值中瀏覽器
@WebServlet(name = "hello3", urlPatterns = {"/hello3"})
public class HelloServlet3 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet 3.0 ...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
複製代碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
複製代碼
Servlet中有五個方法,其中有3個和生命週期有關緩存
Servlet默認狀況下,是在初次被訪問時建立,也能夠經過配置load-on-startup(默認狀況爲負數,開啓則配置爲0或正整數)來使其隨服務器的啓動而建立,建立時會執行init方法,由此也能夠看出Servlet是一個單例對象,因此在多個用戶訪問的時候會存在線程安全問題,因此儘可能不要在Servlet中使用成員變量或儘可能不要修改Servlet的成員變量。tomcat
destroy方法是隻有在容器正常關閉時纔會去執行,注意是正常關閉,且它是先於容器關閉而執行,因此通常用它來釋放資源。安全
GenericServlet實現了Servlet接口,是對於Servlet接口基礎的封裝抽象類,繼承者必須須要重寫service方法,同時能夠根據自身須要重寫其餘4個方法
HttpServlet繼承自GenericServlet,是在http協議的基礎上對Servlet進行進一步封裝,其中經常使用的doGet和doPost根據http method的不一樣按需使用,簡化開發
@WebServlet("/http")
public class HelloHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("do get ...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("do post ...");
}
}
複製代碼
Servlet在tomcat中是經過反射機制建立的對象,傳遞來的Request對象其實是實現了HttpRequestServlet的RequestFacade,此對象由tomcat提供(門面模式)
獲取請求行
假設一個請求的請求行是 GET /hello/abc?name=123 HTTP/1.1
獲取請求頭
request.getHeader(key) 經過請求頭名稱獲取請求頭信息
防盜鏈
所謂防盜鏈是指防止其餘web站點頁面經過鏈接本站點的頁面來訪問本站點內容,這樣對於本站點來講侵犯了本站點的版權
request.getHeader("referer")能夠獲取來源,當值爲null表明瀏覽器地址欄直接輸入訪問,經過對於域名的匹配能夠達到防盜鏈的效果
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("do get ...");
System.out.println(req.getHeader("referer"));
String referer = req.getHeader("referer");
if (referer == null) {
System.out.println("來自瀏覽器的地址直接訪問");
}else if (referer.contains("localhost:8080")) {
System.out.println("本地訪問");
}else {
System.out.println("不知道什麼鬼地方訪問的");
}
}
複製代碼
其實就是利用繪圖工具繪製一張二維碼的圖片,再經過response的字節輸出流,輸出圖片對象
@WebServlet("/verify")
public class VerifyCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedImage bufferedImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bufferedImage.getGraphics();
graphics.setColor(Color.PINK);
graphics.fillRect(0,0,100, 50);
graphics.setColor(Color.BLUE);
graphics.drawRect(0,0,100-1,50-1);
// 這裏能夠自定義驗證碼內容
String i = String.valueOf(new Random().nextInt(100));
graphics.drawString(i, 50, 25);
ImageIO.write(bufferedImage, "jpg", resp.getOutputStream());
}
}
複製代碼
ServletContext域對象是在容器啓動時便建立,對於全部項目中的Servlet共享,不管是request對象獲取的servlet context仍是GenericServlet抽象父類提供的方法,獲取的都是同一個對象,域對象存取數據方法和request對象同樣
@WebServlet("/context")
public class ContextServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 這兩個獲取的是同一個
ServletContext servletContext = req.getServletContext();
// ServletContext servletContext1 = getServletContext();
File file = new File(servletContext.getRealPath("/hehe.html"));
String mimeType = servletContext.getMimeType(file.getName()); // 輸出:text/html
System.out.println(mimeType);
}
}
複製代碼
其實步驟很簡單
一、告訴瀏覽器要下載一個文件 resp.setHeader("content-disposition", "attachment;filename=你好.jpg");
二、把要下載的文件加載進入resp的字節輸出流中
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("utf-8");
String fileName = req.getParameter("fileName");
String realPath = getServletContext().getRealPath(fileName);
resp.setHeader("content-disposition", "attachment;filename=你好.jpg");
ImageIO.write(ImageIO.read(new File(realPath)), "jpg", resp.getOutputStream());
}
}
複製代碼
運行servlet時可能會要一些輔助的信息,ServletConfig提供了這個空間去存這些信息
對於傳統的xml方式的配置,在servlet標籤中添加init-param標籤便可
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.learn.servlet.HelloServlet</servlet-class>
<init-param>
<param-name>hello</param-name>
<param-value>this is hello value</param-value>
</init-param>
</servlet>
複製代碼
對於更加簡單方便的註解,使用方式也是更加簡單
@WebServlet(urlPatterns = "/config", initParams = {@WebInitParam(name = "abc", value = "123")})
複製代碼
注意⚠️:servlet config的配置僅僅在配置的servlet中有效哦
servlet獲取配置的方法和servlet context相似
@WebServlet(urlPatterns = "/config", initParams = {@WebInitParam(name = "abc", value = "123")})
public class ConfigServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
String abc = servletConfig.getInitParameter("abc");
System.out.println(abc);
}
}
複製代碼
一次會話中包含着屢次請求和響應,所謂一次會話就是瀏覽器第一次和服務端發請求,此時會話創建,知道有一方關閉,這次會話才中止。會話技術就是在此次會話內共享數據,方式主要有二 一、cookie 二、session
新建cookie對象
Cookie cookie = new Cookie("abc", "123");
複製代碼
向客戶端設置cookie
resp.addCookie(cookie);
複製代碼
獲取客戶端cookie
Cookie[] cookies = req.getCookies();
複製代碼
本質就是利用了http header的add-cookie和cookie,服務端設置cookie本質就是在header中加上add-cookie,服務端取出cookie也就是解析header中cookie的值
容許同時存在多個cookie,只要屢次addCookie便可
實現思路比較簡單,程序開始直接獲取cookie,並檢索獲取到的cookie中是否存在咱們設置過的cookie,有表明曾經登陸過,沒有表明初次登陸
@WebServlet("/ct")
public class CookieTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie item: cookies) {
if (item.getName().equals("loginTime")) {
// 取出上次的時間
String lastLoginTime = URLDecoder.decode(item.getValue(), "utf-8");
// 存入當前的時間
item.setMaxAge(60 * 60 * 24 * 30);
item.setValue(URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")\
.format(new Date()),"utf-8"));
resp.addCookie(item);
resp.getWriter().println("上次登陸時間:" + lastLoginTime);
return;
}
}
}
Cookie cookie = new Cookie("loginTime",
URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()),"utf-8"));
cookie.setMaxAge(60 * 60 * 24 * 30);
resp.addCookie(cookie);
resp.getWriter().println("初次登陸,歡迎您");
}
}
複製代碼
和cookie相似
session實現本質是經過cookie來實現的,能夠看到會話創建完成後,服務器會在內存中建立session對象,並會在響應中添加name爲JSESSIONID的cookie,表明本次會話的對象標識。在同一次會話過程當中,瀏覽器訪問就會帶上這個JSESSIONID進行請求,服務端接受以後經過對比ID與內存中的對象,判斷本次會話是否和以前的相同。
使用tomcat容器,session的默認存活時間時30分鐘,默認的配置是在tomcat/conf/web.xml中進行配置,用戶能夠修改其中的session-config手動對其修改
默認狀況下,客戶端關閉,因爲cookie的特性,cookie會隨着瀏覽器關閉而清除,因此不相同;可是咱們瞭解了session的基本原理以後,能夠經過手動設置JSESSIONID cookie的存活時間,達到緩存cookie的效果,那麼下次在打開瀏覽器的請求就能夠保持同一個session
不作任何處理的狀況下,服務端關閉,內存中的session對象天然就不存在,因此不可能相同。要保持session,須要進行session的鈍化,即在服務端關閉以前持久化存儲session對象信息,並在服務端重啓時進行session活化,即將鈍化的session從新加載進入內存。
咱們經常使用的tomcat容器爲咱們提供了這一個功能
用法十分簡單,經過實現Filter接口,重寫三個方法便可,其中主要的過濾方法就是doFilter
@WebFilter(urlPatterns = "/hello3")
public class FilterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 隨服務器啓動而建立
System.out.println("init ... ");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter ...");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
// 隨服務器關閉而銷燬
System.out.println("destroy ...");
}
}
複製代碼
能夠對於同一個訪問地址配置多個Filter,構成所謂的過濾器鏈,在訪問到達servlet的具體方法前,先依次由外而內的經過filter的過濾(filterChain.doFilter以前的語句),結束servlet的service方法後,再由內而外的執行一次filter過濾(filterChain.doFilter以後的語句)
分爲兩種狀況:
做用同ServletConfig,用法更是同樣,再也不過多贅述,都是在xml中配置init-param或者在註解中配置,並只能在對應的filter中才能獲取。
默認狀況下,過濾器只過濾請求,若是要過濾轉發或其餘的一些狀況,也想攔截時須要配置這個參數
關於JSP&Listener或者一些細枝末節我不作過多的探究,由於這部分對於後續的學習沒有更多的幫助,由於確實用的實在太少了,後續研究框架的源碼也不多會看到他們,因此算了算了,哈哈哈哈主要仍是懶