1. 程式人生 > 實用技巧 >Servlet實戰(2)

Servlet實戰(2)

Servlet架構圖

在實戰1中自己對Servlet的使用已經慢慢的熟悉了很多,而且自己在學習中有對比、有發散,也學到了很多東西。在實戰2中,我拋棄了Servlet3.0之前的方式,開始全面使用Servlet3.0的規則,即使用註解方式。注意Servlet3.0中Filter上的註解無法排序問題,排序的話可以根據Filter名進行排序A-Z

Servlet Cookie處理

實戰操作cookie

cookie中文編碼和解碼

cookie中文測試

有關cookie的更多設定

cookie的獲取

Servlet Session跟蹤

維持session會話的三種方式

Cookies

URL重寫

隱藏表單欄位

Servlet 資料庫訪問

在普通的java類中使用mysql

web中使用mysql

預編譯、返回主鍵

Servlet 檔案上傳和下載

Servlet3.0之前上傳

Servlet3.0上傳

大坑

總結

專案的部署路徑

總結

eclipse中的Server工程和tomcat的關係

源伺服器和克隆伺服器

優點

Servlet Cookie處理

在閱讀了大量的cookie與session的文章後,在回過頭來看以下菜鳥教程的cookie介紹,就顯得簡單許多了,cookie並不是在伺服器端建立的,伺服器端只是向客戶端傳送建立指令(Set-Cookie),將要建立的cookie放在請求頭中,傳送(多個cookie,則是傳送一組cookie)到客戶端(一般是瀏覽器),瀏覽器解析後建立cookie,如果這些cookie聲明瞭存活時間,則會被寫入客戶端文字檔案中,如果沒有宣告則僅僅存在於一次對話中,當客戶端瀏覽器關閉cookie失效。

實戰操作cookie

cookie中文編碼和解碼

如果要想客戶端存入中文或者獲取客戶端存入的中文,都需要進行處理。

String str1 = java.net.URLEncoder.encode("中文","UTF-8"); // 轉碼
String str2 = java.net.URLDecoder.decode("%E4%B8%AD%E6%96%87","UTF-8"); // 解碼

cookie中文測試

總結:cookie的name、value、path、domain都不可以使用中文!對於name不能使用TSPECIALS宣告的字元。如果需要使用中文就像上一節那樣進行轉碼。

其實我在想,在

tomcat7以後,tomcat的編碼格式預設都使用UTF-8了,還有必要這樣在設定一遍嗎?之前在Servet實戰(1)中也有說到編碼問題,如果依賴的是tomcat,我想設定返回頁面的編碼格式應該也是可以的,測一測吧:

新建 SetCookieServlet

package servlet;

import java.io.IOException;
import java.io.PrintWriter;

import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/SetCookieServlet")
public class SetCookieServlet extends HttpServlet{

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        
        String name = req.getParameter("name");
        String site = req.getParameter("site");
        
        Cookie nameCookie = new Cookie("name", name);//URLEncoder.encode(name, "UTF-8")
        Cookie siteCookie = new Cookie("site", site);
        resp.addCookie(nameCookie);
        resp.addCookie(siteCookie);
        
        out.println(name);
        out.println(site);
    }
    
}

WebContent下新建cookie.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<style>
    div {
        width:200px;
        height:200px;
        border:1px solid black;
        margin:10px;
    }
</style>
<body>
<div>
<!-- 這裡的action就是web.xml裡配置的url-pattern,不加/,如果是註解,則直接些Servlet名 -->
<form action="SetCookieServlet" method="post">
    網站名:<input type="text" name="name"><br>
    站點:<input type="text" name="site">
    <input type="submit">
</form>
</div>
</body>
</html>

啟動專案,在網站名中輸入中文,會發現後臺報錯了,跟蹤後發現在第28行丟擲了異常。

java.lang.IllegalArgumentException: Control character in cookie value or attribute.
    at org.apache.tomcat.util.http.LegacyCookieProcessor.needsQuotes(LegacyCookieProcessor.java:412)
    at org.apache.tomcat.util.http.LegacyCookieProcessor.generateHeader(LegacyCookieProcessor.java:284)
    at org.apache.catalina.connector.Response.generateCookieString(Response.java:940)
    at org.apache.catalina.connector.Response.addCookie(Response.java:888)
    ...

我們將異常資訊定位一下,從上面列印的堆疊資訊可以看出,異常是在response.addCookie導致的,繼續向上定位:

// org.apache.catalina.connector.Response
 
@Override
    public void addCookie(final Cookie cookie) {

        // Ignore any call from an included servlet
        if (included || isCommitted()) {
            return;
        }

        cookies.add(cookie);

        String header = generateCookieString(cookie);

        addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
    }
    
    public String generateCookieString(final Cookie cookie) {
        // Web application code can receive a IllegalArgumentException
        // from the generateHeader() invocation
        if (SecurityUtil.isPackageProtectionEnabled()) {
            return AccessController.doPrivileged(new PrivilegedAction<String>() {
                @Override
                public String run(){
                    return getContext().getCookieProcessor().generateHeader(cookie);
                }
            });
        } else {
            return getContext().getCookieProcessor().generateHeader(cookie);
        }
    }

response的addCookie方法裡可以看出使用了流的方式對cookie進行了處理,繼續跟蹤到generateCookieString(cookie)方法在到generateHeader:

// org.apache.tomcat.util.http.LegacyCookieProcessor

public String generateHeader(Cookie cookie) {
        int version = cookie.getVersion();
        String value = cookie.getValue();
        String path = cookie.getPath();
        String domain = cookie.getDomain();
        String comment = cookie.getComment();

        if (version == 0) {
            // Check for the things that require a v1 cookie
            if (needsQuotes(value, 0) || comment != null || needsQuotes(path, 0) || needsQuotes(domain, 0)) {
                version = 1;
            }
        }
        ...
    }
    
    private boolean needsQuotes(String value, int version) {
        if (value == null) {
            return false;
        }

        int i = 0;
        int len = value.length();

        if (alreadyQuoted(value)) {
            i++;
            len--;
        }

        for (; i < len; i++) {
            char c = value.charAt(i);
            if ((c < 0x20 && c != '\t') || c >= 0x7f) {
                throw new IllegalArgumentException(
                        "Control character in cookie value or attribute.");
            }
            if (version == 0 && !allowedWithoutQuotes.get(c) ||
                    version == 1 && isHttpSeparator(c)) {
                return true;
            }
        }
        return false;
    }

閱讀needsQuotes原始碼可以發現,這是一個對value引數進行中文校驗的函式,如果value引數是中文就會丟擲IllegalArgumentException,在回到generateHeader方法,查詢對needsQuotes方法的呼叫,就會發現在generateHeader方法,對cookie的value、path、domain都進行了中文校驗。

這我們就明白了,在cookie的value、path、domain都是不能使用中文的,那cookie的name能不能使用中文呢?我們來看以下cookie的建構函式就知道了:

// javax.servlet.http.Cookie

public Cookie(String name, String value) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException(
                    lStrings.getString("err.cookie_name_blank"));
        }
        if (!isToken(name) ||
                name.equalsIgnoreCase("Comment") || // rfc2019
                name.equalsIgnoreCase("Discard") || // 2019++
                name.equalsIgnoreCase("Domain") ||
                name.equalsIgnoreCase("Expires") || // (old cookies)
                name.equalsIgnoreCase("Max-Age") || // rfc2019
                name.equalsIgnoreCase("Path") ||
                name.equalsIgnoreCase("Secure") ||
                name.equalsIgnoreCase("Version") ||
                name.startsWith("$")) {
            String errMsg = lStrings.getString("err.cookie_name_is_token");
            Object[] errArgs = new Object[1];
            errArgs[0] = name;
            errMsg = MessageFormat.format(errMsg, errArgs);
            throw new IllegalArgumentException(errMsg);
        }

        this.name = name;
        this.value = value;
    }
    
     private boolean isToken(String value) {
        int len = value.length();
        for (int i = 0; i < len; i++) {
            char c = value.charAt(i);
            if (c < 0x20 || c >= 0x7f || TSPECIALS.indexOf(c) != -1) {
                return false;
            }
        }

        return true;
    }

在上面的isToken方法裡我想你應該看到了有一段if判斷和needsQuotes方法是差不多的,也是進行中文校驗,校驗不通過也會丟擲異常。除此之外在isToken方法裡還有TSPECIALS這個final 常量需要注意:

static {
        if (Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true"))) {
            TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
        } else {
            TSPECIALS = ",; ";
        }
    }

它規定了cookie的name值不能包含TSPECIALS所宣告的這些字元。菜鳥教程這一點是有誤的,在此測試記錄。

總結:cookie的name、value、path、domain都不可以使用中文!對於name不能使用TSPECIALS宣告的字元。如果需要使用中文就上上一節那樣進行轉碼。

有關cookie的更多設定

轉碼+設定存活時間

@WebServlet("/SetCookieServlet")
public class SetCookieServlet extends HttpServlet {

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
            
            // 轉碼
        String name = URLEncoder.encode(req.getParameter("name"), "UTF-8");
        String site = URLEncoder.encode(req.getParameter("site"), "UTF-8");

        Cookie nameCookie = new Cookie("name", name);
        Cookie siteCookie = new Cookie("site", site);
        
        // 設定過期時間,以秒為單位,下面是一個有效期為1小時的cookie
        nameCookie.setMaxAge(60*60*1);
  
        resp.addCookie(nameCookie);
        resp.addCookie(siteCookie);

    }

}

cookie的獲取

 1 package servlet;
 2 
 3 import java.io.IOException;
 4 import java.io.PrintWriter;
 5 import java.net.URLDecoder;
 6 import javax.servlet.ServletException;
 7 import javax.servlet.annotation.WebServlet;
 8 import javax.servlet.http.Cookie;
 9 import javax.servlet.http.HttpServlet;
10 import javax.servlet.http.HttpServletRequest;
11 import javax.servlet.http.HttpServletResponse;
12 
13 @WebServlet("/GetCookieServlet")
14 public class getCookieServlet extends HttpServlet {
15 
16     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
17         req.setCharacterEncoding("UTF-8");
18         resp.setContentType("text/html;charset=UTF-8");
19         PrintWriter out = resp.getWriter();
20             
21         Cookie[] cookies = req.getCookies();
22         if(cookies != null) {
23             for (Cookie cookie : cookies) {
24                 out.println(URLDecoder.decode(cookie.getName(), "UTF-8") + ":" + URLDecoder.decode(cookie.getValue(), "UTF-8") + ",expire:" + cookie.getMaxAge());
25             }
26         }else {
27             out.println("請先設定cookie");
28         }
29         
30     }
31 
32 }

當關閉瀏覽器後siteCookie為過期,但是nameCookie依然存在,因為我們設定了1個小時的存活時間。

cookie的更多操作,可參見【Session Cookie筆記】

Servlet Session跟蹤

由於HTTP是一種"無狀態"協議,這就意味著伺服器端不會保留之前客戶端請求的任何記錄。如果是來自同一使用者短時間內的相同請求,伺服器也無法判斷是否是同一使用者的操作,那到底該怎麼樣才能識別使用者和保持使用者資訊呢?這就是要說的session。

維持session會話的三種方式

Cookies

一個Web伺服器可以分配一個唯一的session會話ID作為每個Web客戶端的cookie,對於客戶端的後續請求可以使用接收到的cookie來識別。

Cookies小測試

這種方式的實現要分析一下,如果客戶端請求的是一個JSP檔案:

WebContent下新建index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Hello
</body>
</html>

啟動專案,直接在客戶端訪問index.jsp,我們知道,對於這個JSP檔案我們並沒有在web.xml裡做任何load的配置,也就是說,只有在第一次訪問這個index.jsp時才會編譯它,編譯請求這個index_jsp.java的service方法,返回結果,你可以在瀏覽器端看到"Hello"。

現在開啟EditThisCookie外掛,即可看到有一個name為JSESSIONID的cookie,其值為一段碼。如果你的客戶端禁止了cookie的話,就不會出現JSESSIONID這個cookie。

Cookies模擬登陸

如果客戶端可以使用cookie,我們來寫一個簡單的登陸:

新建 LoginServlet.java

package servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet{

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 設定post請求編碼問題
        req.setCharacterEncoding("UTF-8");
        // 設定返回頁面的格式和編碼
        resp.setContentType("text/html; charset=UTF-8");
        HttpSession session = req.getSession();
        session.setMaxInactiveInterval(60*60*1);
        
        if(req.getCookies() != null) {
            System.out.println("瀏覽器端可以使用cookie!");
        }else {
            System.out.println("瀏覽器端不能使用cookie!");
        }
        
        String username = req.getParameter("username");
        String pwd = req.getParameter("pwd");
        // 校驗使用者名稱密碼
        if(username.equals("毛毛") && pwd.equals("123456")) {
            System.out.println("登陸成功");
            req.setAttribute("username", username);
            req.getRequestDispatcher("/welcome.jsp").forward(req, resp);
        }else {
            System.out.println("登陸失敗,返回重新登陸!");
            req.getRequestDispatcher("/index.jsp").forward(req, resp);
        }
        
    }
    
}

WebContent下新建welcome.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
Hello! <%=request.getAttribute("username") %>
</body>
</html>

啟動專案,上面執行的程式碼,我在測,我也在想,上面的程式碼並不能滿足讓伺服器記住我的功能,雖然伺服器關閉後,我的JSESSIONID被寫入了.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\Servlet目錄下的SESSIONS.ser裡,但是我再次開啟頁面訪問還是要輸入使用者名稱和密碼,哦哦,好像儲存使用者名稱和密碼這是cookie要乾的事情,使用者在第一次登陸後,建立2個長久的cookie即使用者名稱和密碼,在建立一個短暫的cookie(session),這個cookie要和伺服器端session存活時間保持一致才行(session的存活時間可以被重新整理,但是cookie不行,所以每次重新整理session時,cookie也要手動重新整理)。但有個問題就是當客戶端關閉在次從開,訪問首頁,伺服器根據客戶端的JSESSIONID在次訪問伺服器,伺服器認識不認識這個JSESSIONID呢?我們來測試一下:

新建SessionAtBrower.java

package servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/SessionAtBrower")
public class SessionAtBrower extends HelloServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
           System.out.println(session);
        Cookie scookie = new Cookie("JSESSIONID", session.getId());
        scookie.setMaxAge(60 * 60 * 1);
        resp.addCookie(scookie);
    }

}

在這個SessionAtBrower.java裡我們將第一次請求時建立的JSESSIONID寫入了cookie中,而且設定了這個cookie的過期時間為一個小時後,訪問得到JSESSIONID=4EF6EE406AA8EA47A6AC7C036AE68B76。猜想,我現在關閉chrome瀏覽器,再次開啟它訪問這個地址,要麼再次獲取的cookie裡的和上面這個相等,相等就說明了一個問題:伺服器端是先根據客戶端中cookie為JSESSIONID新建或獲取已存在的session,如果cookie中JSESSIONID存在,就去伺服器端找這個session.id = JSESSIONID的session,找到了(代表存活)將這個session返回,沒找到就新建一個session並將該session.id = JSESSIONID將這個新建的session返回。不相等就伺服器端使用session並不能儲存會話!!!

測試發現,兩次瀏覽器端cookie裡的JSESSIONID是一致的,都是4EF6EE406AA8EA47A6AC7C036AE68B76,而且伺服器端列印的session物件也是一樣的!

總結:伺服器端是先根據客戶端中cookie為JSESSIONID新建或獲取已存在的session。

1.如果cookie中JSESSIONID不存在:第一次訪問伺服器如果呼叫了request.getSession()則會在伺服器端生成一個session然後將這個session物件的id傳送的瀏覽器的cookei中(JSESSIONID=session.id)。

2.如果cookie中JESSIONID存在:就去伺服器端找這個session.id = JESSIONID的session,找到了(代表存活)將這個session返回,沒找到就新建一個session並將該session.id = JESSIONID將這個新建的session返回。

經過上面的測試發現當兩次使用相同瀏覽器去訪問伺服器時(第一次關閉瀏覽器後第二次在啟動訪問),伺服器時認是你的,認識你就好辦多了,這樣我們寫登陸這塊首先在第一次訪問時要往客戶端儲存3個cookie,username,pwd,JSESSIONID,有了JSESSIONID我在關閉開啟瀏覽器訪問時,就用這個JESSIONID去校驗一下子,這樣校驗,還是request.getSession()返回一個session然後呼叫這個session.isNew()判斷這個session是不是新建的,如果是說明之前的session過期了,過期了的話就重新登陸校驗一下,沒過期就不在登陸校驗了,直接變為已登入。

@WebServlet("/SessionAtBrower")
public class SessionAtBrower extends HelloServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        if(session.isNew()) {
            System.out.println("上次訪問的session過期啦,要再次驗證一下使用者名稱密碼,成功後轉到首頁");
        }else {
            System.out.println("重定向到首頁");
        }
        System.out.println(session);
        Cookie scookie = new Cookie("JSESSIONID", session.getId());
        scookie.setMaxAge(60*60*1);
        resp.addCookie(scookie);
    }

}

在測一測,第一次訪問,關閉瀏覽器,再次開啟瀏覽器訪問得到結果如下:

資訊: 攔截的地址:http://localhost:8080/Servlet/SessionAtBrower
上次訪問的session過期啦,要再次驗證一下使用者名稱密碼,成功後轉到首頁
org.apache.catalina.session.StandardSessionFacade@1f9b3ca8
五月 08, 2019 8:05:47 下午 filter.LoggerFilter doFilter
資訊: 攔截的地址:http://localhost:8080/Servlet/SessionAtBrower
重定向到首頁
org.apache.catalina.session.StandardSessionFacade@1f9b3ca8

哈哈哈哈!按照這個思路我們的登陸模組終於可以完成啦!

Cookies登陸實戰

從新修改一下我們之前的index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%@ page import="java.net.*" %>
<%
if(session.isNew()) {
%>
<form action="LoginServlet" method="post">
使用者名稱:<input type="text" name="username" />
密碼:<input type="password" name="pwd" />
<input type="submit" />
</form>
<%
}else {
    System.out.println("重定向到首頁");
    Cookie[] cookies = request.getCookies();
    String username = "";
    if(cookies != null) {
        for (Cookie cookie : cookies) {
            if(URLDecoder.decode(cookie.getName(), "UTF-8").equals("username")){
                username = URLDecoder.decode(cookie.getValue(), "UTF-8");
            }
        }
    }
    request.setAttribute("username", username);
    request.getRequestDispatcher("/welcome.jsp").forward(request, response);
}
%>
</body>
</html>

如果是初次登陸,直接訪問http://localhost:8080/Servlet/tomcat會自動找到我們專案下的index檔案,解析JSP後這個session肯定是new的,因為它之前沒有登陸過,然後就跳轉到登陸頁面。當它點選登陸提交後,就是我們LoginServlet要乾的事了:

package servlet;

import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet{

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 設定post請求編碼問題
        req.setCharacterEncoding("UTF-8");
        // 設定返回頁面的格式和編碼
        resp.setContentType("text/html; charset=UTF-8");
        HttpSession session = req.getSession();
        System.out.println(session.getId());

        String username = req.getParameter("username");
        String pwd = req.getParameter("pwd");
        // 校驗使用者名稱密碼
        if(username.equals("毛毛") && pwd.equals("123456")) {
            System.out.println("登陸成功");
            req.setAttribute("username", username);
            
            // 第一次登陸,將cookie儲存在瀏覽器
            Cookie[] cookies = req.getCookies();
            if(cookies != null) {
                for (Cookie cookie : cookies) {
                    if(URLDecoder.decode(cookie.getName(), "UTF-8").equals("JSESSIONID")) {
                        cookie.setMaxAge(60*60*1);
                        resp.addCookie(cookie);
                    }
                }
            }
            Cookie usernameC = new Cookie("username",URLEncoder.encode(username, "UTF-8"));
            Cookie pwdC = new Cookie("pwd",URLEncoder.encode(pwd, "UTF-8"));
            usernameC.setMaxAge(60*60*1);
            pwdC.setMaxAge(60*60*1);
            resp.addCookie(usernameC);
            resp.addCookie(pwdC);
            
            req.getRequestDispatcher("/welcome.jsp").forward(req, resp);
        }else {
            System.out.println("登陸失敗,返回重新登陸!");
            req.getRequestDispatcher("/index.jsp").forward(req, resp);
        }
        
    }
    
}

登陸成功,將使用者名稱,密碼重寫到cookie,並將JSESSIONID的過期時間更新,將請求轉發到welcome.jsp,這個檔案還是我們之前的那個,沒有任何改動。登陸成功後頁面會顯示:Hello 毛毛

現在我們關閉瀏覽器,再次開啟,再次訪問http://localhost:8080/Servlet/,你會在頁面上看到:Hello 毛毛。這段處理邏輯在index.jsp裡,如果session不是新的,就說明剛登陸過,那獲取cookie裡的資訊,直接顯示出來即可。

URL重寫

你可以在每一個URL末尾追加一個額外的標識session會話,伺服器會把該session會話識別符號與已儲存的有關session會話的資料相關聯【菜鳥教程】(補充:要現有這個session才行)

session一般是要和cookie一起工作的,如果瀏覽器不支援cookie怎麼辦?當我嘗試了將chrome瀏覽器的cookie禁用後,在執行上面的例項,在瀏覽器端就讀取不到任何的cookie了。

URL重寫和直接使用cookie類似,使用cookie的方式是瀏覽器幫我們加的(當且僅當瀏覽器支援cookie),如果瀏覽器不知道cookie,我們可以手動加在URL上,格式如下:

"/ProjectName/Servlet;JSESSIONID=***;key1=value1?id=10"

也能達到和使用cookie一樣的效果。

按照菜鳥教程的說法我們來測試一下:

一開始我使用的還是之前用過的getCookieServlet.java

package servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/GetCookieServlet")
public class getCookieServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        System.out.println(req.getRequestURL());
        PrintWriter out = resp.getWriter();
        System.out.println(req.getSession().getId());
            
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                out.println(URLDecoder.decode(cookie.getName(), "UTF-8") + ":" + URLDecoder.decode(cookie.getValue(), "UTF-8") + ",expire:" + cookie.getMaxAge());
            }
        }else {
            out.println("請先設定cookie");
        }
        
    }

}

啟動專案,訪問:

http://localhost:8080/Servlet/GetCookieServlet;JSESSIONID=4EF6EE406AA8EA47A6AC7C036AE68B76;

結果並不像菜鳥教程裡說的那樣,伺服器並沒有按照我傳入的這個sessionid幫我關聯一個session,而是建立了一個新的session。原因可想而知,就是我理解錯誤,菜鳥教程上面說的那句話,還有一點就是它關聯的是一個已存在的session,首先我是第一次訪問,伺服器端肯定是沒有我之前的任何session的,所以即使我傳入了一個新的sessionid,它也關聯不到某個seesion.id==sessionid的session,所以,我上面這樣測試是不對的。

按照思路,是要現有一個session,然後我拿著這個session的id在URL地址裡訪問,才能被關聯,那就簡單了。我們先寫一個index.jsp,讓它幫我們生成一個sessionid

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="<%=response.encodeURL("welcome.jsp") %>" method="post">
使用者名稱:<input type="text" name="username" />
<input type="submit" />
</form>
</body>
</html>

welcome.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
Hello!<%=request.getParameter("username") %>
<a href="<%=response.encodeURL("index.jsp") %>" >重新登陸</a>
<a href="<%=response.encodeURL("logout.jsp") %>" >登出</a>
</body>
</html>

logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
session.invalidate();
%>
登出成功!
<a href="<%=response.encodeURL("index.jsp") %>" >返回登陸</a>
</body>
</html>

用這三個靜態頁面即可測試,首先在載入index.jsp的時候,隱式物件session已經存在,encodURL方法的作用是將session.id包含在url地址中,並進行編碼,也就是說在index.jsp被生成時表單的action已經確定

在表單提交到welcome.jsp時,session.id=F4BD08A13DA7BE1B3E7B8B6C16238A84session已經存在,所以url位址列中的sessionid還是F4BD0...,當在welcome.jsp中點選重新登陸或登出時還是在當前會話中,再次重新登陸(沒登出)session沒過期時還是當前這個session,如果session過期了會生成新的sessionid。點選登出時,就是將該session設定為過期,這和上一句是一樣的,會生成新的sessionid,不在是當前會話了。

隱藏表單欄位

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="GetCookieServlet" method="post">
<input type="hidden" value="<%=session.getId() %>>" />
使用者名稱:<input type="text" name="username" />
<input type="submit" />
</form>
</body>
</html>

在第一次訪問首頁jsp檔案時,就將生成的sessionid儲存。但是我們不可能在每個頁面裡都寫一個這個隱藏的input吧?如果請求資源是一個超文字連結,點選的時候,並不會導致表單的提交,所以使用隱藏表單欄位你的形式並不支援常規的session會話跟蹤。

Servlet 資料庫訪問

在普通的java類中使用mysql

好久沒寫過最原始的資料庫連結和使用了,趁此回顧一下,使用mysql連線操作資料庫,我只記得在普通的java類中寫資料庫連線基本是下面這樣的:

1.引包,目前我還是在Servlet專案裡寫,所以我就用pom檔案幫我下載最新的mysql驅動包了:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>

2.寫測試程式碼,我在main函式裡寫一個測測:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBTest {
    public static final String url = "jdbc:mysql://localhost:3306/test";
    public static final String user = "root";
    public static final String password = "root";
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Connection conn = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url, user, password);
            statement = conn.createStatement();
            resultSet = statement.executeQuery("SELECT * FROM book");
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                int user_id = resultSet.getInt("user_id");
                String name = resultSet.getString("name");
                System.out.println("id:" + id + " user_id:" + user_id + " name:" + name);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(resultSet != null) {
                    resultSet.close();
                }
                if(statement != null) {
                    statement.close();
                }
                if(conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

執行,報了三個異常:

1.Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
2.java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
    ...
    at DBTest.main(DBTest.java:17)
3.Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
    ...
    ... 6 more

第一個異常是說,使用”com.mysql.jdbc.Driver“載入驅動的方式已經被棄用了,新的載入驅動方式是”com.mysql.cj.jdbc.Driver“,雖然被棄用了,但是mysql目前依舊支持者,只是提倡你是要新方式。

第二個和第三個異常是說:mysql伺服器時區有問題,要麼你去配置一下mysql伺服器的時區:

// 在mysql中執行命令
set global time_zone='+8:00'

或者是在JDBC驅動的url地址加上serverTimezone引數指明詳細的時區,通常是serverTimezone=UTC。

好久沒使用的mysql驅動了,mysql驅動也更新了,自己也隨即更新了一下程式碼:

public static final String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";

Class.forName("com.mysql.cj.jdbc.Driver");

好了,接下來就是把上面的程式碼遷移到我們的Servlet就行。

web中使用mysql

新建DBServlet.java

package servlet;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/DBServlet")
public class DBServlet extends HttpServlet {
    public static final String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
    public static final String user = "root";
    public static final String password = "root";

    @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 {
        // TODO Auto-generated method stub
                Connection conn = null;
                Statement statement = null;
                ResultSet resultSet = null;
                try {
                    Class.forName("com.mysql.cj.jdbc.Driver");
                    conn = DriverManager.getConnection(url, user, password);
                    statement = conn.createStatement();
                    resultSet = statement.executeQuery("SELECT * FROM book");
                    while (resultSet.next()) {
                        int id = resultSet.getInt("id");
                        int user_id = resultSet.getInt("user_id");
                        String name = resultSet.getString("name");
                        System.out.println("id:" + id + " user_id:" + user_id + " name:" + name);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }catch (SQLException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        if(resultSet != null) {
                            resultSet.close();
                        }
                        if(statement != null) {
                            statement.close();
                        }
                        if(conn != null) {
                            conn.close();
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    }
    
    

}

直接啟動專案,訪問,報錯?

資訊: 攔截的地址:http://localhost:8080/Servlet/DBServlet
java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1352)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1180)
    ...
    at java.lang.Thread.run(Thread.java:748)

說找不到資料庫驅動???怎麼回事?我的資料庫驅動明明在啊,剛才通過普通的java類測試的時候沒問題,怎麼到web專案裡就報找不到資料庫驅動了?在網上找到答案,

普通的java專案,可以把資料庫驅動放在專案裡就能使用到。對於web專案,並不是依賴在專案裡的資料庫驅動,而是要把資料庫驅動放在tomcat的lib目錄下。

預編譯、返回主鍵

try {
            statement = conn.prepareStatement("insert into comment(pid,title,comment) values(?,?,?)",PreparedStatement.RETURN_GENERATED_KEYS);
            statement.setInt(1, 1);
            statement.setString(2, title);
            statement.setString(3, comment);
            statement.executeUpdate();
            resultSet = statement.getGeneratedKeys();
            if(resultSet.next()) {
                id = resultSet.getInt(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

在建立Statement(或PreparedStatement)是加入Statement(或PreparedStatement).RETURN_GENERATED_KEY即可,獲取結果集,在結果集中拿到主鍵。

Servlet 檔案上傳和下載

Servlet3.0之前上傳

我本來還想使用最原始的方式通過流去讀取request請求裡面的檔案,上傳到本地呢,但是後來才發現,在Servlet3.0之前中根本不提供上傳的功能,要想上傳檔案需要依賴第三方框架。

pom.xml:

<dependency>
    <groupId>com.liferay</groupId>
    <artifactId>org.apache.commons.fileupload</artifactId>
    <version>1.2.2.LIFERAY-PATCHED-1</version>
</dependency>

這個包依賴的commons.io.jar也會新增到專案裡,你也可以去官網下,官網上也有使用說明。

Servlet3.0上傳

Servlet3.0中,已經內建了上傳的功能,我們主要以Servlet3.0做開發,在搞這個上傳檔案的時候路徑是個問題,我們來看一下:

新建WebContent/upload.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>

新建UploadServlet.java:

package servlet;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet("/UploadServlet")
@MultipartConfig(location = "", maxFileSize = 1024 * 1024 * 20)
public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
        PrintWriter out = null;
        Part part = null;
        String uploadpath = getServletConfig().getServletContext().getRealPath("/");
        System.out.println(uploadpath);

        try {
            out = resp.getWriter();
            part = req.getPart("file");
            if (part != null) {
                System.out.println(part.getName());
                part.write("123.md");
            }
            out.println("上傳成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

啟動專案,訪問upload.html,提交一個檔案測試,發現儲存的路徑不是在我上面列印的uploadpath裡

// uploadpath
C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\Servlet

// 實際儲存目錄
C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\Servlet

而且如果我在Multipart註解的location裡寫任何路徑,比如:location="/upload",都會報錯說找不到這個路徑。找不到那簡單啊,我獲取這個實際儲存路徑的地址,然後新建一個upload不就行了,但是我怎麼獲取這個實際儲存路徑的地址?在一系列路徑混亂的情況下,我寫了一個專門列印路徑的RoadServlet.java

package servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/RoadServlet")
public class RoadServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<table border='1px'>");
        out.println("        <tr><td>req.getContextPath()</td><td>"+req.getContextPath()+"</td></tr>");
        out.println("        <tr><td>req.getRequestURI()</td><td>"+req.getRequestURI()+"</td></tr>");
        out.println("        <tr><td>req.getRequestURL()</td><td>"+req.getRequestURL()+"</td></tr>");
        out.println("        <tr><td>req.getServletPath()</td><td>"+req.getServletPath()+"</td></tr>");
        out.println("        <tr><td>req.getSession().getServletContext().getContextPath()</td><td>"+req.getSession().getServletContext().getContextPath()+"</td></tr>");
        out.println("        <tr><td>getServletContext().getContextPath()</td><td>"+getServletContext().getContextPath()+"</td></tr>");
        out.println("        <tr><td>getServletConfig().getServletContext().getContextPath()</td><td>"+getServletConfig().getServletContext().getContextPath()+"</td></tr>");
        out.println("        <tr><td>getServletConfig().getServletContext().getRealPath(\"/\")</td><td>"+getServletConfig().getServletContext().getRealPath("/")+"</td></tr>");
        out.println("        <tr><td>getServletConfig().getServletContext().getRealPath(\"\")</td><td>"+getServletConfig().getServletContext().getRealPath("")+"</td></tr>");
        out.println("        <tr><td>getServletConfig().getServletContext().getRealPath(\"/../../temp\") </td><td>"+getServletConfig().getServletContext().getRealPath("/../../temp") +"</td></tr>");
        out.println("        <tr><td>getServletConfig().getServletContext().getRealPath(\"/../../temp\") </td><td>"+ Thread.currentThread().getContextClassLoader().getResource("").getPath() +"</td></tr>");
        
        
        out.println("</table>");
    }
}

訪問:

到此,我們先不在追究檔案上傳路徑的問題,我們來看下一節【專案部署路徑】,一個重要的知識點。

在閱讀過專案部署路徑和eclipse中的Server工程和tomcat的關係這兩節後,我們可以繼續我們的上傳了。

改寫程式碼如下:

package servlet;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import org.apache.commons.lang3.StringUtils;

@WebServlet("/UploadServlet")
@MultipartConfig(maxFileSize = 1024 * 1024 * 20)
public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
        PrintWriter out = null;
        Part part = null;
        String uploadpath = getServletConfig().getServletContext().getRealPath("/WEB-INF/upload");
        try {
            out = resp.getWriter();
            part = req.getPart("file");
            if (part != null) {
                String fileName = getFileName(part);
                part.write(uploadpath + File.separator +  fileName);
            }
            out.println("上傳成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
      * 如何得到上傳的檔名, API沒有提供直接的方法,只能從content-disposition屬性中獲取
      * 
      * @param part
      * @return
      */
     protected static String getFileName(Part part) {
      if (part == null)
       return null;

      String fileName = part.getHeader("content-disposition");
      if (StringUtils.isBlank(fileName)) {
       return null;
      }

      return StringUtils.substringBetween(fileName, "filename=\"", "\"");
     }

}

pom.xml引入:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

啟動專案,訪問upload.html,在這裡我遇到了一個大坑。

大坑

那就是專案一直報StringUtils類ClassNotFoundException!我以為是因為我使用了源伺服器部署導致了,但是我把他改成克隆伺服器部署還是報這個錯誤,後來到網上找到了原因,我把它記錄在【工具-eclipse筆記中了】,主要是因為我的專案所依賴的包,也要一起釋出到專案裡,也就是放到WEB-INF下的lib目錄下,你可以手動去一個個拷貝,也可以到【eclipse筆記中看一下快捷新增方式】。

現在再次訪問upload.html上傳,你就會在你的***\Servlet\WEB-INF\upload目錄下看到你上傳的檔案了(***取決於你使用的是哪種tomcat伺服器)。

總結

到此我們大概也就知道了,使用註解方式不說明上傳地址時,不管我們把部署路徑配置在哪,預設時都是上傳到克隆伺服器的work目錄中。但是如果我們不使用預設上傳地址的方式時,我們可以使用下面方式來指定上傳地址。

getServletConfig().getServletContext().getRealPath("/")

獲取克隆伺服器/源伺服器中專案地址(到底是哪個,取決你eclipse中server的配置),不管使用哪個,遷移到源伺服器是不影響的!

專案的部署路徑

說到上面那一堆路徑,我不得不說一下我們的專案部署路徑。預設的我們在eclipse新建的web專案,如果使用eclipse整合tomcat,那當你在new一個tomcat Server後,開啟Server會看到這麼一張圖:

在這裡有一個Deploy path=wtpwebapps,這是eclipse整合tomcat後的預設部署位置

也就是在你的eclipse的工作目錄下該專案的部署位置。但是,你細想我們之前沒有使用eclipse整合tomcat時是怎麼把專案部署到tomcat裡的?之前是直接把專案的war包,放到tomcat的E:\apache-tomcat-8.0.52\webapps目錄下,然後啟動tomcat即可。最開始時我們是修改了E:\apache-tomcat-8.0.52\conf\server.xml改成了下面這樣:

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>
    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost">
        <Context path="" docBase="C:\Users\admin\Desktop\ServletBase\ServletDemo\webapp" reloadable="true"/>
      </Host>
    </Engine>
  </Service>
</Server>

上面這種都是在tomcat自身的部署目錄裡部署的,但是用eclipse整合tomcat的話預設就是在eclipse專案檔案下部署專案。我們可以在新建tomcat Server時修改【只有新建時能修改,當新增專案進去後就不能修改了】,Deploy path為tomcat自身的部署目錄即:

好了,現在我們更新了專案的部署路徑,再次去訪問RoadServlet,得到的圖是下面這樣的:

而且如果我們使用tomcat自身的部署環境而不是eclipse整合tomcat的部署環境,那麼檔案的上傳和下載就簡單多了!因為我們現在使用的是tomcat自身的部署環境,你在去C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core\tmp1下就再也找不到讓人頭疼的wtpwebapps檔案夾了,而且你也不用在關心temp1下的work,webapps這些東西,因為你現在是在tomcat自身的部署環境了,這個環境很乾淨。

你可以很明確的知道你要上傳到哪?還有就是我們再來測一下我們使用預設不配置@MultipartConfig中location時也不寫其他路徑,它會存在哪裡?結果是:

C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\work\Catalina\localhost\Servlet。你會意外,噯?我們不是切換成tomcat自身的部署環境了嗎?而且當你把eclipse整合的tomcat刪除之後org.eclipse.wst.server.core下面就沒有temp1檔案夾了啊,但是為什麼我們在新建一個部署在tomcat環境裡的Server,org.eclipse.wst.server.core下又會生成temp1,而且之前的哪些檔案除了wtpwebapps資料夾沒有了,其他的依舊還在。想想你也應該明白,雖然我們在eclipse中切換了tomcat的部署環境,但是我們實際上使用的還是eclipse中整合的tomcat。我們配置eclipse中的Deploy path只是把專案部署在tomcat裡,但是執行時依靠的tomcat還是eclipse中的tomcat,而不是E:\apache-tomcat-8.0.52這個。說到這可能已經暈的不行了。重啟一段來說明一下eclipse中的Server工程和tomcat的關係。【請閱讀下一節:eclipse中的Server工程和tomcat的關係】

看過下一節後,我們就可以清楚的定義源伺服器和克隆伺服器了,上面說的就是在eclipse中使用的時克隆伺服器,但是專案的部署地址放到了源伺服器下,但是檔案上傳註解location引數不寫時,它上傳在了克隆伺服器下org.eclipse.wst.server.core\tmp1\work\Catalina\localhost\Servlet內。【這裡的temp0和temp1請不要糾結,它們只是我之前建立的克隆伺服器沒刪掉而已,不是重點】。

eclipse中的Server工程和tomcat的關係

源伺服器和克隆伺服器

最開始的時候,如果我們的eclipse中沒有任何專案,也沒new server時,在我們的C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core資料夾下是這樣的:

但當我們在eclipse中建立一個web專案後,new server並新增一個server伺服器即tomcat然後指定tomcat目錄和JRE環境點選next-finish,此時我們只是建立了一個空的server,還沒向裡面新增任何web專案。現在我們執行一下這個空的server,看看org.eclipse.wst.server.core資料夾下會發生什麼變化。

temp0

當啟動server服務時,在

C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core

目錄下建立了一個temp0,如果你仔細對比E:\apache-tomcat-8.0.52資料夾下內容和temp0下內容你會發現

這兩個目錄下的內容基本時相似的。只是temp0更多了一個wptwebapps資料夾,而bin、lib只有E:\apache-tomcat-8.0.52才有。

其實

C:\Users\admin\Desktop\ServletBase\.metadata\.plugins\org.eclipse.wst.server.core\temp0

只是E:\apache-tomcat-8.0.52目錄的一個克隆,所以上面程式碼段所描述的這個目錄也就具備了源伺服器的功能。如果你在new幾個server,就會在org.eclipse.wst.server.core目錄下依次出現temp1,temp2,temp3等多個克隆伺服器,但是這裡每次只能啟動上面一個克隆伺服器,因為它們都是使用相同的啟動埠。

優點

這樣的機制就保證了你eclipse裡的專案不會影響原先tomcat裡的配置,每次都用不同的引數來啟動tomcat。這樣會有一個問題,就是如果你原先的tomcat配置檔案有錯的話,eclipse會先拷貝你原有的tomcat下的配置,然後在這個配置的基礎上修改。所以,遇到這種問題,先保證原有的配置沒問題,然後再去修改eclipse新生成的,或者直接刪除重配。