【Spring】DispatcherServlet源碼分析

来源:http://www.cnblogs.com/weknow619/archive/2017/08/17/7384607.html
-Advertisement-
Play Games

使用過HttpServlet的都應該用過其doGet和doPost方法,接下來看看DispatcherServlet對這兩個方法的實現(源碼在DispatcherServlet的父類FrameworkServlet中): 方法里又將邏輯交由processRequest(request, respon ...


使用過HttpServlet的都應該用過其doGet和doPost方法,接下來看看DispatcherServlet對這兩個方法的實現(源碼在DispatcherServlet的父類FrameworkServlet中):

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

    processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

方法里又將邏輯交由processRequest(request, response)方法處理,跟進源碼:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // 記錄當前時間,用於計算請求的處理時間
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (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();
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }

        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

從源碼可以看出在該方法中對請求進行處理,處理細節在doService方法中實現,同時在處理請求前後也做了準備及處理工作:

1. 提取LocaleContext及RequestAttributes兩個屬性保證可以在當前請求後還能恢復;

2. 根據當前的request創建對應的LocaleContext及RequestAttributes,並綁定到當前線程;

3. 委托給doService方法進一步處理;

4. 請求結束後恢複線程到原始狀態;

5. 請求處理結束後發佈事件通知。

跟進doService方法源碼:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

從上面源碼可以看出doService方法里也是做了許多準備工作,可以看出Spring將localeResolvder、themeResolver等設置在request中,最後傳入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 {
            // 如果request是MultipartContent類型的話則轉為MultipartHttpServletRequest類型
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 根據request尋找對應的Handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                // 找不到Handler則返回錯誤信息
                noHandlerFound(processedRequest, response);
                return;
            }

            // 根據handler找對應的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // 如果當前handler支持last-modified頭處理
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 激活handler並返回視圖
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(request, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}
根據request信息尋找對應的Handler

先來看看Spring中一個簡單的映射處理器配置:

<bean id="simpleUrlMapping" 
    class="org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/test.html">controller</prop>
        </props>
    </property>
</bean>

在Spring的載入中,會將類型為SimpleUrlHandlerMapping的實例載入到this.handlerMappings中,根據request提取對應的Handler,也就是提取當前實例的controller,這裡的controller是繼承自AbstractController類型實例,看看這步是如何封裝的,跟進getHandler方法源碼:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

在系統啟動時Spring會將映射類型的bean註冊到this.handlerMappings變數中,此方法的目的就是遍歷所有的HandlerMapping,並調用其getHandler方法進行封裝處理,跟進SimpleUrlHandlerMapping的getHandler方法:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 根據request獲取對應的handler
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 如果沒有則使用預設的handler
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }

    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    return getHandlerExecutionChain(handler, request);
}

上面源碼應該很清晰,根據request獲取對應的Handler,如果沒有的話則使用預設的,當查找到的controller為String類型時,就意味著返回的是配置的bean名稱,需要根據bean名稱查找對應的bean,最後通過getHandlerExecutionChain方法對返回的Handler進行封裝,以滿足返回類型的匹配。
接著跟進getHandlerInternal方法源碼來看看怎樣根據request查找對應的Handler:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    // 獲取用於匹配的url有效路徑
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 根據上面的路徑尋找handler
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
        // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if ("/".equals(lookupPath)) {
            // 如果請求的路徑是“/”,則使用RootHandler進行處理
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // 根據beanName獲取對應的bean
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    return handler;
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    // 直接匹配情況的處理
    Object handler = this.handlerMap.get(urlPath);
    if (handler != null) {
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }
    // 通配符匹配的處理
    List<String> matchingPatterns = new ArrayList<String>();
    for (String registeredPattern : this.handlerMap.keySet()) {
        if (getPathMatcher().match(registeredPattern, urlPath)) {
            matchingPatterns.add(registeredPattern);
        }
    }
    String bestPatternMatch = null;
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if (!matchingPatterns.isEmpty()) {
        Collections.sort(matchingPatterns, patternComparator);
        if (logger.isDebugEnabled()) {
            logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
        }
        bestPatternMatch = matchingPatterns.get(0);
    }
    if (bestPatternMatch != null) {
        handler = this.handlerMap.get(bestPatternMatch);
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

        // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
        // for all of them
        Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
        for (String matchingPattern : matchingPatterns) {
            if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
                Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                uriTemplateVariables.putAll(decodedVars);
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
        }
        return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
    }
    // No handler found...
    return null;
}

這裡考慮了直接匹配和通配符兩種情況,其中在buildPathExposingHandler方法里將Handler封裝成了HandlerExecutionChain類型。看buildPathExposingHandler方法源碼:

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
        String pathWithinMapping, Map<String, String> uriTemplateVariables) {

    HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
    chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
    if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
        chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
    }
    return chain;
}

在該方法中可以看到通過將Handler以參數形式傳入,再構建HandlerExecutionChain類型實例,加入了兩個攔截器,這裡也是鏈式處理方式。

根據當前Handler尋找對應的HandlerAdapter

在預設情況下普通web請求會由SimpleControllerHandlerAdapter處理,下麵分析獲取適配器的邏輯:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    for (HandlerAdapter ha : this.handlerAdapters) {
        if (Logger.isTraceEnabled) {
            Logger.trace("Testing handler adapter ["+ha+"]");
        }
        if (ha.supports(handler)) {
            return ha;
        }
    }
}

通過該方法可以看出對於獲取適配器的邏輯就是遍歷所有適配器來選擇合適的並返回它,而某個適配器是否適用於當前的Handler邏輯被封裝在具體的適配器中,看SimpleControllerHandlerAdapter中的supports方法,

public boolean supports(Object handler) {
    return (handler instanceof Controller);
}

SimpleControllerHandlerAdapter就是用於處理普通web請求的,對於SpringMVC來說,一般是把邏輯封裝到Controller的子類中。

繼續返回到doDispatcher方法中的激活handler並返回視圖的代碼,
// 激活handler並返回視圖
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

對於普通的Web請求,Spring預設是使用SimpleControllerHandlerAdapter類進行處理的, 進入SimpleControllerHandlerAdapter類的handle方法如下:

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

之前舉例的controller的邏輯是寫在handleRequestInternal方法中而不是handleRequest方法中的,看看該方法中的處理邏輯:

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    // Delegate to WebContentGenerator for checking and preparing.
    checkAndPrepare(request, response, this instanceof LastModified);

    // 如果要在session內同步執行
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                return handleRequestInternal(request, response);
            }
        }
    }
    // 調用用戶處理邏輯
    return handleRequestInternal(request, response);
}
根據視圖跳轉頁面

使用過SpringMVC的都知道請求經過控制器、適配器等處理後最後還要經過視圖解析器的解析渲染跳轉,跟進DispatcherServlet類的render方法:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    View view;
    if (mv.isReference()) {
        // We need to resolve the view name.
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

DispatcherServlet會根據ModelAndView選擇合適的視圖來渲染,這一功能就是在上面方法里的resolveViewName方法中完成的:

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
        HttpServletRequest request) throws Exception {

    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

跟進resolveViewName(viewName, locale)方法,源碼如下:

public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
        // 不存在緩存的話直接創建視圖
        return createView(viewName, locale);
    }
    else {
        // 直接從緩存提取
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

當通過viewName解析到對應的View後,就可以進行跳轉邏輯的處理了。

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
            " and static attributes " + this.staticAttributes);
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

對於ModelView的使用,可以將一些屬性放入其中,再在頁面上通過JSTL等方式獲取,解析這些屬性的工作就是在上面源碼中的createMergedOutputModel方法完成的。源碼如下:

protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) {

    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
            (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // Consolidate static and dynamic model attributes.
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }

    // Expose RequestContext?
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
}

protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // 將model中的數據以屬性方式設置到request中
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.forward(request, response);
    }
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 回到目錄 DotNetCore里一切都是依賴註入的,對於appsettings這個可擴展的配置對象也不例外,它位於項目根目錄,一般在startup里去註冊它,在類中通過構造方法註入來獲取當前的對象,以便去使用它,當然我們也可以自己去構建和使用它,下麵我就來總結一下。 傳統方法,startup註入,構 ...
  • Overview Generally, one of the first steps when you are trying to work with databases is open it. You can find several types of those, and each have a ...
  • 因為在X86上long會被分割為兩個int進行操作, 那麼Interlocked.Increment的實現成為了一個問題。 在一番搜索後未發現有現成的文章解釋這個問題,於是我就動手分析了。 這篇是筆記,不會做過多的解釋。 首先重現環境是 .Net Core 2.0 Windows (x86) Bin ...
  • 最近工作感覺特別操心,不是因為任務多,也不是因為有解決不了的問題,而是感覺項目團隊太懶散了。大多數員工沒有工作積極性,工作經常拖延,公司沒有獎懲措施,做得好的、及時完成工作的與那些得過且過的、經常拖延工作計劃的人一樣待遇,沒有任何區別。導致一些沒有一定毅力的好員工也逐漸被同化。 公司部門的許可權分配不 ...
  • 網站地址:http://m.3y.uu456.com/mbp_56hl91r1rx5uqa87qrzo_1.html ...
  • 之前就瞭解到了裝飾器, 但是就會點皮毛, 而且對其調用方式感到迷茫,正好現在的項目我想優化,就想到了用裝飾器, 因此深入研究了下裝飾器.先看下代碼: 我的疑惑就是明明return 的是一個函數名,按道理來講,返回的就是一個函數地址啊!我理解有問題?隨後上網查資料,又是閉包....但是我個人對它不感冒 ...
  • 簡介 RabbitMQ是流行的開源消息隊列系統,用erlang語言開發。RabbitMQ是AMQP(高級消息隊列協議)的標準實現。 安裝 首先安裝erlang環境。 官網:http://www.erlang.org/ Windows版下載地址:http://erlang.org/download/o... ...
  • 0x00: 首先聲明一個全局變數。 然後,在滑動處罰ajax請求的代碼處,做一個判斷。 if (control) { $('.get_more').click(); }; 這個地方是獲取數據的函數以及ajax請求的函數 0x01以上原理: 首先聲明一個全局變數設置為true,在觸發滑動時間的時候,判 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...