主頁 > 後端開發 > Spring Cloud 專題之四:Zuul網關

Spring Cloud 專題之四:Zuul網關

2021-07-07 06:06:40 後端開發

書接上回:

SpringCloud專題之一:Eureka

Spring Cloud專題之二:OpenFeign

Spring Cloud專題之三:Hystrix

經過前面三章對Spring Cloud的基本組件的介紹,我們可以構建一個簡單的微服務架構系統了,比如,通過使用Spring Cloud Eureka實作高可用的服務注冊中心以及實作微服務的注冊與發現;通過Spring Cloud OpenFeign 實作服務間負載均衡的介面呼叫;同時,為了使分布式系統更為健壯,對于依賴的服務呼叫使用SpringCloud Hystrix來進行包裝,實作執行緒隔離并加入熔斷機制,以避免在微服務架構中因個別服務出現例外而引起級聯故障蔓延,

上面的架構實作系統功能是完全沒有問題,但是還可以進一步思考,這樣的架構還有不足的地方會使運維人員或開發人員感到很痛苦,

? 首先,我們從運維人員的角度來看看,他們平時都需要做一些什么作業來支持這樣的架構,當客戶端應用單擊某個功能的時候往往會發出一些對微服務獲取資源的請求到后端,這些請求通過F5、Nginx等設施的路由和負載均衡分配后,被轉發到各個不同的服務實體上,而為了讓這些設施能夠正確路由與分發請求,運維人員需要手工維護這些路由規則與服務實體串列,當有實體增級訓是地址變動等情況發生的時候,也需要手工地去同步修改這些資訊以保持實體資訊與中間件配置內容的一致性,在系統規模不大的時候,維護這些資訊的作業還不會太過復雜,但是如果當系統規模不斷增大,那么這些看似簡單的維護任務會變得越來越難,并且出現配置錯誤的概率也會逐漸增加,很顯然,這樣的做法并不可取,所以我們需要一套機制來有效降低維護路由規則與服務實體串列的難度,

? 其次,我們再從開發人員的角度來看看,在這樣的架構下,會產生一些怎樣的問題呢?大多數情況下,為了保證對外服務的安全性,我們在服務端實作的微服務介面,往往都會有一定的權限校驗機制,比如對用戶登錄狀態的校驗等;同時為了防止客戶端在發起請求時被篡改等安全方面的考慮,還會有一些簽名校驗的機制存在,這時候,由于使用了微服務架構的理念,我們將原本處于一個應用中的多個模塊拆成了多個應用,但是這些應用提供的介面都需要這些校驗邏輯,我們不得不在這些應用中都實作這樣一套校驗邏輯,隨著微服務規模的擴大,這些校驗邏輯的冗余變得越來越多,突然有一天我們發現這套校驗邏輯有個BUG需要修復,或者需要對其做一些擴展和優化,此時我們就不得不去每個應用里修改這些邏輯,而這樣的修改不僅會引起開發人員的抱怨,更會加重測驗人員的負擔,所以,我們也需要一套機制能夠很好地解決微服務架構中,對于微服務介面訪問時各前置校驗的冗余問題,

為了解決上面的架構問題,API網關應運而生,而Spring Cloud Zuul就是Spring Colud 提供的這樣的一個API網關,Zuul提供了動態路由、監控、彈性負載和安全功能,Zuul底層利用各種filter實作如下功能:

  • 認證和安全:識別每個需要認證的資源,拒絕不符合要求的請求,
  • 性能監測:在服務邊界追蹤并統計資料,提供精確的生產視圖,
  • 動態路由:根據需要將請求動態路由到后端集群,
  • 壓力測驗:逐漸增加對集群的流量以了解其性能,
  • 負載卸載:預先為每種型別的請求分配容量,當請求超過容量時自動丟棄,
  • 靜態資源處理:直接在邊界回傳某些回應,

代碼實踐

本次的代碼實踐還是在前幾篇文章的代碼的基礎上所作的,

1.創建zuul-gateway的工程并引入依賴

<!--zuul的依賴-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2.創建應用主類,使用@EnableZuulProxy注解開啟Zuul的API網關服務功能

@SpringBootApplication
@EnableZuulProxy
public class ZuulGatewayApplication {

	public static void main(String[] args) {
		SpringApplication.run(ZuulGatewayApplication.class, args);
	}

}

3.在組態檔中配置Zuul應用的基礎資訊,這里不像之前的服務使用properties作為組態檔,而是菜用yaml作為配置(后面會講)

server:
  port: 9010

spring:
  application:
    name: zuul-gateway

# 指定Eureka server的注冊中心的位置,出來將Zuul的注冊成服務之外,也讓Zuul能夠獲取注冊中心的實體清單
eureka:
  client:
    service-url:
      defaultZone: http://eureka-server1:9001/eureka/

傳統的路由方式

使用Zuul實作路由的功能非常簡單,之需要對api-gateway服務增加關于路由規則的配置即可,

#Zuul實作的傳統的路由配置
zuul:
  routes:
    hello-server-url:
      path: /hello-server/**
      url: http://localhost:9003

該配置會將所有發往API網關服務的請求中符合/hello-server/**規則的訪問都路由轉發到 http://localhost:9003 這個地址上,也就是:我們在訪問 http://localhost:9010/hello-server/sayHello的時候,API服務網關會將該請求路由到http://localhost:9003/sayHello上,

注意上面一組path和url映射的路由名要相同,

這種方式直觀容易理解,API網關直接根據請求的URL路徑找到最匹配的path運算式,直接轉發給該運算式對應的url以實作外部請求的路由,

面向服務的路由

在properties組態檔中配置路由

# Zuul面向服務的配置服務
zuul:
  routes:
    api-hello-server:
      path: /hello-server/**
      service-id: hello-server
    api-customer-server:
      path: /customer-server/**
      service-id: customer-server

在這里分別使用了api-hello-server和pi-customer-server來映射服務提供者(hello-server)和服務消費者(customer-server)的路由,通過上面的配置方式,我們不足要再為每個路由維護微服務的具體實體的位置,而是通過path和service-id的映射,使得維護作業變得非常簡單,

這種方式,整合了Eureka來實作,將API網關看作Eureka的一個應用服務,除了將自己注冊到Eureka服務注冊中心上之外,也會從注冊中心獲取所有的服務以及他們的實體清單,在Eureka的幫助下,API網關服務就已經維護了所有serviceId與實體地址的映射關系,那么只需要通過Ribbon的負載均衡策略,直接在這些清單種選擇一個具體的實體進行轉發就能完成路由作業了,

為啥選擇yaml作為組態檔

隨著版本的迭代,可能會對服務做一個功能的拆分,將原本屬于hello-service的某些共鞥你拆分到了另一個全新的hello-service-ext服務中,而這些拆分的外部呼叫URL路徑希望能夠符合規則/hello-service/ext/**,所以需要做如下配置:

zuul.routes.hello-service.path=/hello-service/**
zuul.routes.hello-service.serviceId=hello-service
zuul.routes.hello-service-ext.path=/hello-service/ext/**
zuul.routes.hello-service-ext.serviceId=hello-service-ext

此時,呼叫hello-service-ext服務的 URL路徑實際上會同時被/hello-service/** 和/hello-service/ext/** 兩個運算式所匹配,在邏輯上,API網關服務需要優先選擇/hello-service/ext/** 路由,然后再匹配/hello-service/** 路由才能實作上述需求,但是如果使用上面的配置方式,實際上是無法保證這樣的路由優先順序的,

由于properties的配置內容無法保證有序,所以為了保證路由的優先順序,需要使用yaml檔案來配置,這也是為啥配置zuul的時候要選擇使用yaml作為組態檔,

請求過濾

在實作了請求路由功能之后,我們的微服務應用提供的介面就可以通過統一的API網關入口被客戶端訪問到了,但是每個客戶端用戶請求微服務應用提供的介面時,他們的訪問權限往往都有一定的限制,系統并不會將所有的微服務介面都對他們開放,

為了實作對客戶端請求的安全校驗和權限控制,最簡單的方法就是為每個微服務應用都實作一套用于檢驗簽名和鑒別權限的過濾器或者攔截器,但是,因為同一個系統中的各種檢驗邏輯很多情況下都是相同或者類似的,這樣做的話會出現代碼冗余,后期維護例外麻煩,所以比較好的做法時將這些校驗邏輯剝離出去,構建出一個獨立的鑒權服務,

Zuul允許開發者在API網關上通過定義過濾器來實作對請求的攔截與過濾,實作的方法非常簡單,只需要繼承ZuulFilter抽象類并實作他定義的4個抽象函式就可以完成對請求的過濾和攔截了,

在這里我們實作一個簡單的請求過濾功能:登錄系統檢驗token,如果token不為空,則不可以訪問,

/**
 * @className: LoginFilter
 * @description: 實作登錄過濾校驗
 * @author: charon
 * @create: 2021-07-04 22:46
 */
public class LoginFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(LoginFilter.class);

    /**
     * 過濾器的型別,它決定了過濾器在請求的那個生命周期執行,
     * 主要有四種型別:
     * pre: 可以在請求被路由之前呼叫
     * routing: 在路由請求時被呼叫
     * post: 在routing和error過濾器之后被呼叫
     * error: 處理請求時發生錯誤時被呼叫
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 過濾器的執行順序,當請求在一個階段中存在多個過濾器時,需要根據該方法回傳的值來過濾依次執行,數值越小優先級越高
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 判斷該過濾器市夠需要被執行
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 過濾器的具體邏輯這里通過context.setSendZuulResponse(false);令zuul過濾該請求,不對其進行路由,
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        Object token = request.getHeader("token");
        if (Objects.isNull(token)) {
            log.error("token為空,不允許訪問");
            context.setSendZuulResponse(false);
            // 防止回傳給前端時出現中文亂碼
            context.getResponse().setContentType("text/html;charset=utf-8");
            context.setResponseStatusCode(401);
            context.setResponseBody("當前狀態未登錄,請重新登錄,");
            return null;
        }
        log.error("token不為空,允許正常訪問");
        return null;
    }
}

為自定義的過濾器創建具體的bean才能啟動該過濾器,

@Bean
public LoginFilter loginFilter(){
    return new LoginFilter();
}

在完成了上面的改造之后,重啟服務,并使用下面兩種請求對其進行驗證:

  • http://localhost:9010/customer-server/sayHello1?name=chatron1: 如果請求中不帶token的引數,則會報"token為空,不允許訪問"的錯誤,回傳錯誤頁面

  • http://localhost:9010/customer-server/sayHello1?name=chatron1&token=1111: 如果請求中帶有token引數,則可以正常訪問

原始碼分析

在使用zuul的時候,最主要的就是在啟動類上添加@EnableZuulProxy的注解,所以我們先從注解開始看,

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

可以看到,這個注解類引入了ZuulProxyMarkerConfiguration這個類,跟進這個類:

@Configuration(proxyBeanMethods = false)
public class ZuulProxyMarkerConfiguration {

	@Bean
	public Marker zuulProxyMarkerBean() {
		return new Marker();
	}

	class Marker {

	}

}

發現這個類與Eureka的EurekaServerMarkerConfiguration類一樣(作者是同一人),主要就是把Marker類變成了Spring的Bean,作為自動配置Zuul的開關,又了MEurekaServerMarkerConfiguration.Marker這個bean之后,Zuul代理的自動配置類(ZuulProxyAutoConfiguration)就能加載了,

在ZuulProxyAutoConfiguration這個類里注入了一些Filters,

@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
                                               ProxyRequestHelper proxyRequestHelper) {
    return new PreDecorationFilter(routeLocator,
                                   this.server.getServlet().getContextPath(), this.zuulProperties,
                                   proxyRequestHelper);
}

// route filters
@Bean
@ConditionalOnMissingBean(RibbonRoutingFilter.class)
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
                                               RibbonCommandFactory<?> ribbonCommandFactory) {
    RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                                                         this.requestCustomizers);
    return filter;
}

@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
                           CloseableHttpClient.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
                                                       ZuulProperties zuulProperties,
                                                       ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                                                       ApacheHttpClientFactory httpClientFactory) {
    return new SimpleHostRoutingFilter(helper, zuulProperties,
                                       connectionManagerFactory, httpClientFactory);
}

@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                                                        ZuulProperties zuulProperties, CloseableHttpClient httpClient) {
    return new SimpleHostRoutingFilter(helper, zuulProperties, httpClient);
}

而ZuulProxyAutoConfiguration的繼承了ZuulServerAutoConfiguration類,參考了一些相關的配置,在缺失ZuulServletBean的情況下注入ZuulServlet,而這個類是Zuul的核心類:

@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "https://www.cnblogs.com/pluto-charon/p/false",
      matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
   ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean<>(
         new ZuulServlet(), this.zuulProperties.getServletPattern());
   servlet.addInitParameter("buffer-requests", "false");
   return servlet;
}

同時在這個類中,還注入了其他的過濾器,比如:

  • pre型別的過濾器:ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter
  • post型別的過濾器:SendResponseFilter
  • error型別的過濾器:SendErrorFilter
  • route型別的過濾器:SendForwardFilter

跟進ZuulServlet類,可以看到ZuulServlet直接繼承了HttpServlet類,所以ZuulServlet依然是走的http通信協議,跟進ZuulServlet.service方法,這里面清晰的描繪了Zuul的路由程序,

  1. pre、route、post都不拋出例外,順序是:pre->route->post,error不執行,
  2. pre拋出例外,順序是:pre->error->post,
  3. route拋出例外,順序是:pre->route->error->post,
  4. post拋出例外,順序是:pre->route->post->error,
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
    try {
        // 為每個請求生成request和response,存入ConcurrentHashMap中
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
	   // 初始化背景關系
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
	   // 處理pre型別的過濾器
        try {
            preRoute();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        // 處理route型別的過濾器
        try {
            route();
        } catch (ZuulException e) {
            error(e);
            postRoute();
            return;
        }
        // 處理post型別的過濾器
        try {
            postRoute();
        } catch (ZuulException e) {
            error(e);
            return;
        }

    } catch (Throwable e) {
        error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
    } finally {
        RequestContext.getCurrentContext().unset();
    }
}

跟進每種過濾器型別的執行方法,可以發現找到Zuul過濾器的核心處理器:FilterProcessor,在這個類中,主要有兩個方法:

  • runFilters (String sType):該方法會根據傳入的 filterType來呼叫getFiltersByType (String filterType)獲取排序后的過濾器串列,然后輪詢這些過濾器,并呼叫processZuulFilter (ZuulFilter filter)來依次執行它們,
  • processZuulFilter(ZuulFilter filter):該方法定義了用來執行 filter的具體邏輯,包括對請求背景關系的設定,判斷是否應該執行,執行時一些例外的處理等,

在processZuulFilter()這個方法中最后都是呼叫的繼承了ZuulFilter抽象類的過濾器的各自實作的run(),

Zuul作為網關,主要的實作都包含在了ZuulFilter的實作當中,以一個ConcurrentHashMap實作的RequestContext來傳遞節點資料,如果想做一些自定義的處理可以通過繼承ZuulFilter并重寫4個方法即可,

參考文章:

翟永超老師的《Spring Cloud微服務實戰》

https://blog.csdn.net/weixin_38106322/article/details/103457742

https://zhuanlan.zhihu.com/p/28376627

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

標籤:Java

上一篇:一個最簡單的訊息佇列,帶你理解 RabbitMQ!

下一篇:厲害了,Netty 輕松實作檔案上傳!

標籤雲
其他(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