主頁 > 後端開發 > JAVA WEB的前世今生

JAVA WEB的前世今生

2020-09-17 20:27:04 後端開發

JAVA WEB的前世今生

1990年伯納斯·李發明了WWW,從此完全改變了數字世界,

早期的網站功能都是很簡單的,那時的網站還是靜態頁面,所謂的資源就是實實在在的檔案,URL里的路徑也是實實在在的檔案路徑,很明顯,這樣的缺點就是不能動態展示資料,如果需要發布一篇文章,就需要將檔案上傳到服務器,再編輯主頁,將文章鏈接進去,如果找不到檔案,那就回傳404,

蒂姆·伯納斯·李

隨著人民日益增長的美好生活需要和發展不平衡不充分之間的矛盾網路規模的發展,人們對動態網頁的訴求和早期的靜態網頁技術的矛盾成為主要矛盾后,就需要一種技術能夠動態生成網頁,這個時候的解決方案就是CGI(Common Gateway Interface),譯作“通用網關介面”,

起:CGI

談到動態網頁開發,一個繞不開的技術就是CGI,正如其名,Common Gateway Interface通用是他的第一個特性,無論是C、C++、Java還是Python、Perl都是支持CGI的,理論上任何一種具有標準輸入輸出和環境變數的語言都可以用來撰寫CGI程式,

我們可以寫一個簡單的腳本hello放到服務器的cgi-bin目錄下,然后訪問http://example.com/cgi-bin/hello就可以看到腳本生效了,當然,這需要你的服務器支持CGI,

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print('Content-type:text/html')
print('')						# blank line, end of headers
print('<html>')
print('<head>')
print('<meta charset="utf-8">')
print('<title>Hello Word!</title>')
print('</head>')
print('<body>')
print('<h2>Hello Word!</h2>')
print('</body>')
print('</html>')

雖然繁瑣,但是屏蔽了底層的TCP連接,HTTP協議決議,開發的作業就只需要拼接字串,做做輸入輸入就可以了,實在是一個飛躍,

CGI的第二個單詞是Gateway網關這個詞在不同的語境里面有不同的含義,例如微服務、網路傳輸層,但是意思其實都差不多,可以理解為協議轉換器,負責將網關兩側的協議轉換,

Interface翻譯成介面,所謂介面,就是需要遵守的規則,比如如何從環境變數獲取資料輸入,如何從標準輸入stdin獲取資料請求,

CGI的弊端也很明顯,那就是他使用的是fork/exec模式,每當發送一次CGI請求都會fork一個行程,這在并發請求下會造成不小的壓力,為了解決這個問題,后面誕生了FastCGI,也就是使用了常駐記憶體的行程池技術,由調度器將請求發送給相應的handler行程處理,請求結束后繼續等待下一個請求,

時至今日,CGI早已淡出人們的視線,仍然在大規模使用的恐怕只有在C/C++了,這里面需要點名的就是鵝廠:

https://mp.weixin.qq.com/cgi-bin/readtemplate?t=business/faq_operation_tmpl&type=info&lang=zh_CN&token=

承:Servlet

JAVA最早是面向嵌入式設備的語言,在CGI蓬勃發展的年代,作為后來者自然而然的站在了前人的肩上,針對CGI的一些缺點,提出了Servlet

Servlet很簡單,只是一個介面,幾行代碼,5個方法,只要實作了Servlet介面就是Servlet,

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

其中最重要的就是三件事情了:

  • init,初始化
  • service,接受到請求
  • destroy,銷毀

加上servlet的加載和創建,他的什么周期共包含5個程序,

以下就是一個很簡單的Servlet了

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = -4159478011804388333L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("Hello World!");
    }
}

web.xml檔案用來描述servlet的映射關系

<?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_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>net.sk32.demo.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

Apache Tomcat

前面說了,Servlet只是一個協議,他并不直接與客戶端打交道,我們寫的這個servlet需要一個容器來執行,稱做Servlet容器,Tomcat就是其中最著名的一個,

Apache Tomcat

Tomcat 的主要目錄結構

?  tomcat tree -L 1 apache-tomcat-9.0.38
├── bin		// 存放可執行的檔案,如 startup 和 shutdown
├── conf	// 存放組態檔,如核心組態檔 server.xml 和應用默認的部署描述檔案 web.xml
├── lib		// 存放 Tomcat 運行需要的jar包
├── logs	// 存放運行的日志檔案
├── webapps	// 存放默認的 web 應用部署目錄
└── work	// 存放 web 應用代碼生成和編譯檔案的臨時目錄

將前面的HelloServlet.java編譯成class檔案,再加上web.xml檔案,拷貝到webapps目錄,然后打開瀏覽器訪問http://localhost:8080/helloWorld/hello就可以看到回傳的頁面了,

?  webapps tree helloWorld
helloWorld
└── WEB-INF
    ├── HelloServlet.class
    └── web.xml

Tomcat的核心原理

這部分先占個坑,講起來還需要一篇文章,

JSP(Java Server Pages)

同一時期PHP憑借良好的用戶體驗(可以在HTML中插入代碼)大行其道,而這時的JAVA程式員畫風還是這樣的:

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
 
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
		out.println("<HTML>");
		out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
		out.println("  <BODY>");
		out.print("    This is ");
		out.print(this.getClass());
		out.println(", using the GET method");
		out.println("  </BODY>");
		out.println("</HTML>");
		out.flush();
		out.close();
	}

一個頁面幾千行println簡直蔚為壯觀,

這個時候很多人紛紛轉向了最好的語言PHP,SUN公司坐不住了,趕緊推出了JSP,畫風就變成這樣了:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  $END$
  </body>
</html>

看起來正常多了,是不是和HTML很像,那他是怎么運作的呢,實際上JSP本質上還是servlet,例如上面這個index.jsp最侄訓生成index.jsp.java檔案:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {
                     ...
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
      throws java.io.IOException, javax.servlet.ServletException {
                         ...
    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\n");
      out.write("\n");
      out.write("<html>\n");
      out.write("  <head>\n");
      out.write("    <title>$Title$</title>\n");
      out.write("  </head>\n");
      out.write("  <body>\n");
      out.write("  $END$\n");
      out.write("  </body>\n");
      out.write("</html>\n");
    } catch (java.lang.Throwable t) {
        ...
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

轉:Spring

嚴格來說,Spring并不僅僅是解決Web開發而產生的,他更多的是一套框架,為了解決企業應用程式開發的復雜性而創建的,這篇文章更多的還是講SpringMVC

MVC并不是JAVA獨有的,Model-View-Controller是一種經典的三層架構,例如PHP、Python都有相應的框架,

M(Model):模型,處理業務邏輯,封裝物體

V(View):視圖,展示內容

C(Controller):控制器,調度分發(接受請求-呼叫模型-轉發視圖)

太陽底下沒有新鮮事,SpringMVC也是基于servlet的,想要使用SpringMVC,我們需要在前面的web.xml中配置DispatcherServlet,將所有請求映射到org.springframework.web.servlet.DispatcherServlet

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

SpringMVC請求流程

默認情況下,SpringMVC會去WEB-INF目錄下尋找[servlet名稱]-servlet.xml檔案,在那里定義相關的Beans,大致的流程如下:

  1. 啟動Servlet容器,例如Tomcat,對DispatcherServlet 進行實體化,然后呼叫它的init()方法進行初始化,這個程序完成:加載web.xml初始化引數;建立WebApplicationContext(SpringMVC的IOC容器);進行組件的初始化;
  2. 客戶端請求由Servlet容器接收并匹配映射路徑,按照我們在web.xml里的配置,轉交給DispatcherServlet
  3. DispatcherServlet從容器中查找能夠處理Request請求的HandlerMapping實體,回傳HandlerExecutionChain
  4. DispatcherServlet查找能夠處理該HandlerHandlerAdapter組件;
  5. 執行HandlerExecutionChain中所有攔截器的preHandler()方法,然后再利用HandlerAdapter執行Handler,執行完成得到ModelAndView,再依次呼叫攔截器的postHandler()方法;
  6. 利用ViewResolverModelAndView或是Exception(可決議成 ModelAndView)決議成View,然后 View 會呼叫render()方法再根據 ModelAndView 中的資料渲染出頁面;
  7. 最后再依次呼叫攔截器的afterCompletion()方法,這一次請求就結束了,

SpringMVC原始碼分析

我們根據這個思路來看代碼:

我們先從核心的DispatcherServlet入手,先全域了解一下繼承關系,

DispatcherServlet

DispatcherServlet初始化

根據前面的描述,servlet在初始化階段會呼叫init()方法,DispatcherServletinit()是繼承自HttpServletBean#init

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {
    // 處理init-param引數
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    // 初始化servlet
    initServletBean();
}

initServletBean這個方法在FrameworkServlet#initServletBean中實作了:

/**
 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();
    try {
        // 初始化WebApplicationContext容器
        this.webApplicationContext = initWebApplicationContext();
        // 掛載點,默認沒有任何實作
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
    if (logger.isDebugEnabled()) {
        String value = https://www.cnblogs.com/edifyX/p/this.enableLoggingRequestDetails ?
                            "shown which may lead to unsafe logging of potentially sensitive data" :
                            "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                            "': request parameters and headers will be " + value);
    }
    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

initWebApplicationContext方法初始化并回傳容器,在FrameworkServlet#initWebApplicationContext實作:

/**
 * Initialize and publish the WebApplicationContext for this servlet.
 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
 * of the context. Can be overridden in subclasses.
 * @return the WebApplicationContext instance
 * @see #FrameworkServlet(WebApplicationContext)
 * @see #setContextClass
 * @see #setContextConfigLocation
 */
protected WebApplicationContext initWebApplicationContext() {
    // ROOT context
    WebApplicationContext rootContext =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // 在創建該Servlet注入的背景關系
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // 注入到 ROOT 背景關系
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 查找已經系結的背景關系
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果沒有找到相應的背景關系,并指定父親為ROOT
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        // 重繪背景關系(執行一些初始化),這里加了同步代碼塊,我是5.2.6版本
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }
    // 推送事件通知
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

DispatcherServlet#onRefresh實作了onRefresh方法,基本是一些常用組件的初始化,我們可以詳細的看一下initHandlerMappingsinitHandlerAdapters

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
    protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

/**
 * Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // 從ApplicationContext容器中獲取所有的HandlerMapping實體
        // 包括祖先contexts,就是一個遞回實作的
        Map<String, HandlerMapping> matchingBeans =
                            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 排序,是OrderComparator的子類,可以支持@Order和@Priority注解
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    // 如果沒有HandlerMapping,加載默認的
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

/**
 * Initialize the HandlerAdapters used by this class.
 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
 * we default to SimpleControllerHandlerAdapter.
 */
private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    if (this.detectAllHandlerAdapters) {
        // HandlerAdapters也是一樣的,就不詳細說明了
        Map<String, HandlerAdapter> matchingBeans =
                            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    } else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }
    // Ensure we have at least some HandlerAdapters, by registering
    // default HandlerAdapters if no other adapters are found.
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
                                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

HandlerMapping和HandlerAdapters

這里需要插入一點,HandlerMappingHandlerAdapters是什么?

HandlerMapping介面主要有兩類,分別繼承自AbstractUrlHandlerMappingAbstractHandlerMethodMapping,都繼承自抽象類AbstractHandlerMappingHandlerMapping介面只有一個方法getHandler,接受request,根據URL和method回傳HandlerExecutionChain也就是handler和一堆HandlerInterceptor攔截器:

HandlerMapping

HandlerAdaptor也是一個介面,3個方法,supportshandlegetLastModified,字面上的意思就是處理配接器,作用就是呼叫具體的方法對用戶發來的請求來進行處理,HandlerMapping獲取到執行請求的controller時,DispatcherServlte會根據controller對應的controller型別來呼叫相應的HandlerAdapter來進行處理,

  • SimpleControllerHandlerAdapter支持org.springframework.web.servlet.mvc.Controller型別的handler
  • SimpleServletHandlerAdapter支持javax.servlet.Servlet
  • HttpRequestHandlerAdapter支持org.springframework.web.HttpRequestHandler

HandlerAdapter

processRequest

我們繼續回到DispatcherServletorg.springframework.web.servlet.FrameworkServlet繼承了doGet,doPost,doPut,doDelete,doOptions,doTrace等喜聞樂見的CRUD方法:

@Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    processRequest(request, response);
}

// 其他幾個方法也是呼叫了processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    // 回傳與當前執行緒相關聯的 LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 根據請求構建 LocaleContext,公開請求的語言環境為當前語言環境
    LocaleContext localeContext = buildLocaleContext(request);
    // 回傳當前系結到執行緒的 RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    // 獲取當前請求的 WebAsyncManager,如果沒有找到則創建
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    // 使 LocaleContext 和 requestAttributes 關聯
    initContextHolders(request, localeContext, requestAttributes);
    try {
        // DispatcherServlet實作
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }
    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

接下來doService呼叫doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    Boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            // Multipart涉及到檔案IO需要單獨處理
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // 這就是前面提到的HandlerMapping了
            // 遍歷所有的 HandlerMapping 找到與請求對應的 Handler
            // 并將其與一堆攔截器封裝到 HandlerExecution 物件中
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 遍歷所有的 HandlerAdapter,找到可以處理該 Handler 的 HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // Last-Modified是一個回應首部,其中包含源頭服務器認定的資源做出修改的日期及時間,
            // 它通常被用作一個驗證器來判斷接收到的或者存盤的資源是否彼此一致,如果一致直接回傳
            // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Last-Modified
            String method = request.getMethod();
            Boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            // 遍歷攔截器,執行它們的 preHandle() 方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 執行實際的處理程式
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // 遍歷攔截器,執行它們的 postHandle() 方法
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 處理執行結果,是一個 ModelAndView 或 Exception,然后進行渲染
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                            new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                // 遍歷攔截器,執行它們的 afterCompletion() 方法
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

processDispatchResult用來渲染mv,或者封裝exception成mv渲染:

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {
    Boolean errorView = false;
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        } else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }
    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

這一階段的SSM、SSH以及眾多的開源框架奠定了JAVA在WEB開發界的領先地位,

合:Microservice

通過整體式架構,所有行程緊密耦合,并可作為單項服務運行,這意味著,如果應用程式的一個行程遇到需求峰值,則必須擴展整個架構,隨著代碼庫的增長,添加或改進整體式應用程式的功能變得更加復雜,這種復雜性限制了試驗的可行性,并使實施新概念變得困難,整體式架構增加了應用程式可用性的風險,因為許多依賴且緊密耦合的行程會擴大單個行程故障的影響,

使用微服務架構,將應用程式構建為獨立的組件,并將每個應用程式行程作為一項服務運行,這些服務使用輕量級 API 通過明確定義的介面進行通信,這些服務是圍繞業務功能構建的,每項服務執行一項功能,由于它們是獨立運行的,因此可以針對各項服務進行更新、部署和擴展,以滿足對應用程式特定功能的需求,

整體式與微服務

Spring Cloud 模塊的相關介紹如下:

  • Eureka/Nacos:服務注冊中心,用于服務管理

  • Ribbon:基于客戶端的負載均衡組件

  • Hystrix/Sentinel:容錯框架,能夠防止服務的雪崩效應

  • Feign:Web 服務客戶端,能夠簡化 HTTP 介面的呼叫

  • Zuul:API 網關,提供路由轉發、請求過濾等功能

  • Config:分布式配置管理

  • Sleuth:服務跟蹤

  • Stream:構建訊息驅動的微服務應用程式的框架

  • Bus:訊息代理的集群訊息總線

    Spring Cloud diagram

    借用一張圖來描述Spring Cloud架構,時間關系先寫這么多,有時間再寫,


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/68300.html

標籤:Java

上一篇:C語言02

下一篇:Java 為 PPT 中的圖形添加陰影效果

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more