主頁 > 後端開發 > 如何讓Java編譯器幫你寫代碼

如何讓Java編譯器幫你寫代碼

2023-01-30 07:04:12 後端開發

作者:京東零售 劉世杰

導讀

本文結合京東監控埋點場景,對解決樣板代碼的技術選型方案進行分析,給出最終解決方案后,結合理論和實踐進一步展開,通過關注文中的技術分析程序和技術場景,讀者可識訓一種樣板代碼思想程序和解決思路,并對Java編譯器底層有初步了解,

一、背景

監控是服務端應用需要具備的一個非常重要的能力,通過監控可以直觀的看到核心業務指標、服務運行質量等,而要做到可監控就需要進行相應的監控埋點,大家在埋點程序中經常會撰寫大量重復代碼,雖能實作基本功能,但耗時耗力,不夠優雅,根據“DRY(Don't Repeat Yourself)"原則,這是代碼中的“壞味道”,對有代碼潔癖的人來講,這種重復是不可接受的,

那有什么方法解決這種“重復”嗎?經過綜合調研,基于前端編譯器插樁技術,實作了一個埋點組件,通過織入埋點邏輯,讓Java 編譯器幫我們寫代碼,經過不斷打磨,已經被包括京東APP主站服務端在內的很多團隊廣泛使用,

本文主要是結合監控埋點這個場景分享一種解決樣板化代碼的思路,希望能起到拋磚引玉的作用,下面將從組件介紹技術選型程序實作原理部分原始碼實作逐步展開講解,

二、組件介紹

京東內部監控系統叫UMP,與所有的監控系統一樣,核心部分有埋點、上報、分析整合、報警、看板等等,本文講的組件主要是為對監控埋點原生能力的增強,提供一種更優雅簡潔的實作,

下面先來看下傳統硬編碼的埋點方式,主要分為創建埋點物件、可用率記錄、提交埋點 3 個步驟:

通過上圖可以看到,真正的邏輯只有紅框中的范圍,為了完成埋點要把這段代碼都圍繞起來,代碼層級變深,可讀性差,所有埋點都是這樣的樣板代碼,

下面來看下使用組件后的埋點方式:

通過對比很容易看到,使用組件后的方式只要在方法上加一個注解就可以了,代碼可讀性有明顯的提升,

組件由埋點封裝API和AST操作處理器 2 部分組成,

埋點API封裝:在運行時被呼叫,對原生埋點做了封裝和抽象,方便使用者進行監控KEY的擴展,

AST操作處理器:在編譯期呼叫,它將根據注解@UMP把埋點封裝API按照規則織入方法體內,

(注:結合京東實際業務場景,組件實作了fallback、自定義可用率、重名方法區分、配套的IDE插件、監控key自定義生成規則等細節功能,由于本文主要是講解底層實作原理,詳細功能不在此贅述)

三、技術選型程序

通過上面的示例代碼,相信很多人覺得這個功能很簡單,用 Spring AOP 很快就能搞定了,的確很多團隊也是這么做的,不過這個方案并不是那么完美,下面的選型分析中會有相關的解釋,請耐心往下看,如下圖,從軟體的開發周期來看,可織入埋點的時機主要有 3 個階段:編譯期、編譯后和運行期,

3.1 編譯前

這里的編譯期指將Java源檔案編譯為class位元組碼的程序,Java編譯器提供了基于 JSR 269 規范[1]的注解處理器機制,通過操作AST (抽象語法樹,Abstract Syntax Tree,下同)實作邏輯的織入,業內有不少基于此機制的應用,比如Lombok 、MapStruct 、JPA 等;此機制的優點是因為在編譯期執行,可以將問題前置,沒有多余依賴,因此做出來的工具使用起來比較方便,缺點也很明顯,要熟練操作 AST并不是想的那么簡單,不理解前后關聯的流程寫出來的代碼不夠穩定,因此要花大量時間熟悉編譯器底層原理,當然這個程序對使用者來講是沒有感知的,

3.2 編譯后

編譯后是指編譯成 class 位元組碼之后,通過位元組碼進行增強的程序,此階段插樁需要適配不同的構建工具:Maven、Gradle、Ant、Ivy等,也需要使用方增加額外的構建配置,因此存在開發量大和使用不夠方便的問題,首先要排除掉此選項,可能只有極少數場景下才會需要在此階段插樁,

3.3 運行期

運行期是指在程式啟動后,在運行時進行增強的程序,這個階段有 3 種方式可以織入邏輯,按照啟動順序,可以分為:靜態 Agent、AOP 和動態 Agent,

3.3-1 靜態 Agent

JVM 啟動時使用 -javaagent 載入指定 jar 包,呼叫 MANIFEST.MF 檔案里的 Premain-Class 類的 premain 方法觸發織入邏輯,是技術中間件最常使用的方式,借助位元組碼工具完成相關作業,應用此機制的中間件有很多,比如:京東內部的鏈路監控 pfinder、外部開源的 skywalking 的探針、阿里的 TTL 等等,這種方式優點是整體比較成熟,缺點主要是兼容性問題,要測驗不同的 JDK 版本代價較大,出現問題只能在線上發現,同時如果不是專業的中間件團隊,還是存在一定的技術門檻,維護成本比較高;

3.3-2 Spring AOP

Spring AOP大家都不陌生,通過 Spring 代理機制,可以在方法呼叫前后織入邏輯,AOP 最大的優點是使用簡單,同樣存在不少缺點:

1) 同一類內方法A呼叫方法B時,是無法走到切面的,這是Spring 官方檔案的解釋[2] “However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy”,這個問題會導致內部方法呼叫的邏輯執行不到,在監控埋點這個場景下就會出現丟資料的情況;

2) AOP只能環繞方法,方法體內部的邏輯沒有辦法干預,靠捕捉例外判斷邏輯是不夠的,有些場景需要是通過回傳值狀態來判斷邏輯是否正常,使用介紹里面的示例代碼就是此種情況,這在 RPC 呼叫決議里是很平常的操作,

3) 私有方法、靜態方法、final class和方法等場景無法走切面

3.3-3 動態 Agent

動態加載jar包,呼叫MANIFEST.MF檔案中宣告的Agent-Class類的agentmain方法觸發織入邏輯,這種方式主要用來線上動態除錯,使用此機制的中間件也有很多,比如:Btrace、Arthas等,此方式不適合常駐記憶體使用,因此要排除掉,

3.4 最終方案選擇

通過上面的分析梳理可知,要實作重復代碼的抽象有 3 種方式:基于JSR 269 的插樁、基于 Java Agent 的位元組碼增強、基于Spring AOP的自定義切面,接下來進一步的對比:

如上表所示,從實作成本上來看,AOP 最簡單,但這個方案不能覆寫所有場景,存在一定的局限性,不符合我們追求極致的調性,因此首先排除,Java Agent 能達到的效果與 JSR 269 相同,但是啟動引數里需要增加 -javaagent 配置,有少量的運維作業,同時還有 JDK 兼容性的坑需要趟,對非中間件團隊來說,這種方式從長久看會帶來負擔,因此也要排除,基于 JSR 269 的插樁方式,對Java編譯器作業流程的理解和 AST 的操作會帶來實作上的復雜性,前期投入比較大,但是組件一旦成型,會帶來一勞永逸的解決方案,可以很自信的講,插樁實作的組件是監控埋點場景里的銀彈(事實證明了這點,不然也不敢這么吹),

冰山之上,此組件給使用者帶來了簡潔優雅的體驗,一個jar包,一行代碼,妙筆生花,那冰山之下是如何實作的呢?那就要從原理說起了,

四、插樁實作原理

簡單來講,插樁是在編譯期基于 JSR 269的注解處理器中操作AST的方式操縱語法節點,最終編譯到class檔案中,要做好插樁理解相關的底層原理是必要的,大多數讀者對編譯器相關內容比較陌生,這里會用較大的篇幅做個相對系統的介紹,

Java編譯器是將原始碼翻譯成 class 位元組碼的工具,Java編譯器有多種實作:Open JDK的javac、Eclipse的ecj和ajc、IBM的jikes等,javac是公司內主要的編譯器,本文是基于Open JDK 1.8 講解,

作為一款工業級編譯器內部實作比較復雜,其涵蓋的內容足夠寫一本書了,結合本人對javac原始碼的理解,嘗試通俗易懂的講清楚插樁涉及到的知識,有不盡之處歡迎指正,有興趣進一步研究的讀者建議閱讀 javac原始碼[6],

下面將講解編譯器執行流程,相關javac原始碼導航,以及注解處理器如何運作,

4.1 編譯器執行流程

根據官網資料[3]javac 處理流程可以粗略的分為 3個部分:Parse and Enter、Annotation Processing、Analyse and Generate,如下圖:

Parse and Enter

Parse階段主要通過詞法分析器(Scanner)讀取原始碼生產 token 流,被語法分析器(JavacParser)消費構造出AST,Java代碼都可以通過AST表達出來,讀者可以通過JCTree查看相關的實作,為了讓讀者能更直觀的理解AST,本人做了一個原始碼決議成AST后的圖形化展示:

(注:AST圖形生成通過IDEA插件JavaParser-AST-Inspector生成dot格式文本,并使用線上工具GraphvizOnline轉換為圖片,見參考資料5、7)

示例原始碼:

token流:

[ package ] <- [ com ] <- [ . ] <- …... <- [ } ]

決議成AST后如下:

Enter階段主要是根據AST填充符號表,此處為插樁之后的流程,因此不再展開,

Annotation Processing

注解處理階段,此處會呼叫基于 JSR269 規范的注解處理器,是javac對外的擴展,通過注解處理器讓開發者(指非javac開發者,下同)具備自定義執行邏輯的能力,這就是插樁的關鍵,在這個階段,可以獲取到前一階段生成的AST,從而進行操作,

Analyse and Generate

分析AST并生成class位元組碼,此處為插樁之后的流程,不再展開,

4.2 相關javac原始碼導航

javac觸發入口類路徑是:com. sun. tools. javac. Main,代碼如下:

經驗證Maven 執行構建調的是此類中的main方法,其他構建工具未做驗證,猜測類似的,在JDK內部也提供了javax. tools. Tool Provider# get System Java Compiler的入口,實際上內部實作也是調的這個類里的compile方法,

經過一系列的命令引數決議和初始化操作,最終調到真正的核心入口,方法是com. sun. tools. javac. main. Java Compiler# compile,如下圖:

這里有3個關鍵呼叫:

852行:初始化注解處理器,通過Main入口的呼叫是通過JDK SPI的方式收集,

855–858行:對應前面流程圖里的Parse and Enter和Annotation Processing兩個階段的流程,其中方法processAnnotations便是執行注解處理器的觸發入口,

860行:對應Analyse and Generate階段的流程,

4.3 注解處理器

Java從JDK 1.6 開始,引入了基于JSR 269 規范的注解處理器,允許開發者在編譯期間執行自己的代碼邏輯,如本文講的UMP監控埋點插樁組件一樣,由此衍生出了很多優秀的技術組件,如前面提到的Lombok、Mapstruct等,注解處理器使用比較簡單,后面示例代碼有注解處理器簡單實作也可以參考,這里重點講一下注解處理器整體執行原理:

1、編譯開始的時候,會執行方法init Process Annotations (compile的截圖852行),以SPI的方式收集到所有的注解處理器,SPI對應介面:javax. annotation. processing. Processor,

2、在方法process Annotations中執行注解處理器呼叫方法Javac Processing Environment# do Processing,

3、所有的注解處理器處理完畢一次,稱為一輪(round),每輪開始會執行一次Processor# init方法以便開發者自定義初始化資訊,如快取背景關系等,初始化完成后,javac會根據注解、版本等條件過濾出符合條件的注解處理器,并呼叫其介面方法Processor# process,即開發者自定義的實作,

4、在開發者自定義的注解處理器里,實作AST操作的邏輯,

5、一輪執行完成后,發現新的Java源檔案或者class檔案,則開啟新的一輪,直到不再產生Java或者class檔案為止,有的開源專案實作注解處理器時,為了保證自身可以繼續執行,會通過這個機制創建一個空白的Java檔案達到目的,其實這也是理解原理的好處,

6、如果在一輪中未發現新的Java源檔案和class檔案產生則執行最后一輪(last Round),最后一輪執行完畢后,如果有新的Java源檔案生成,則進行Parse and Enter 流程處理,到這里,整個注解處理器的流程就結束了,

7、進入Analyse and Generate階段,最終生成class,完成整體編譯,

接下來將通過UMP監控埋點功能來展示怎么在注解處理器中操作AST,

五、原始碼示例

關于AST 操作的探索,早在2008年就有相關資料了[4],Lombok、Mapstruct都是開源的工具,也可以用來參考學習,這里簡單講一個示例,展示如何插樁,

注解處理器使用框架

上圖展示了注解處理器具體的基本使用框架,init、process是注解處理器的核心方法,前者是初始化注解處理器的入口,后者是操作AST的入口,javac還提供了一些有用的工具類,比如:

TreeMaker:創建AST的工廠類,所有的節點都是繼承自JCTree,并通過TreeMaker完成創建,

JavacElements:操作Element的工具類,可以用來定位具體AST,

向類中織入一個import節點

這里舉一個簡單場景,向類中織入一個import節點:

為方便理解對代碼實作做了簡化,可以配合注釋查看如何織入:

總的來說,織入邏輯是通過TreeMaker創建AST 節點,并操作現有AST織入創建的節點,從而達到了織入代碼的目的,

六、反思與總結

到這里,講了埋點組件的使用、技術選型、以及插樁相關的內容,最終開發出來的組件在作業中也起到了很好的效果,但是在這個程序中有一些反思,

1、插樁門檻高

通過前面的內容不難得出一個事實,要實作一個小小的功能,需要開發者花費大量的精力去學習理解編譯器底層的一些原理,從ROI角度看,投入和產出是嚴重不成正比的,為了能提供可靠的實作,個人花費了大量業余時間去做技術選型分析和編譯器相關知識,可以說是純靠個人的興趣和一股倔勁一點點搭建起來的,細節是魔鬼,這個踩坑的程序比較枯燥,實際上插樁機制有很多通用的場景可以探索,之所以一直很少見到此類機制的應用,主要是其門檻較高,對大多數開發者來說比較陌生,因此降低開發者使用門檻才能讓一些想法變成現實,做一把好用的錘子,比砸入一個釘子要更有價值,

在監控埋點插樁組件真正落地時,在專案內做了一定抽象,并支持了一些開關、自定義鏈路跟蹤等功能,但從作用范圍來講是不夠的,所以下一步計劃做一個插樁方面的技術框架,從易用性、可維護性等方面做好進一步的抽象,同時做好可測驗性相關作業,包含驗證各版本JDK的支持、各種Java語法的覆寫等,

2、插樁是把雙刃劍

javac官方對修改AST的方式持保守態度,也存在一些爭議,然而時間是最好的驗證工具,從Lombok 等組件的發展看出,插樁機制是能經住長久考驗的,如何合理利用這種能力是非常重要的,合理使用可使系統簡潔優雅,使用不當就等于在代碼里下毒了,所以要有節制的修改AST,要懂前后運行機制,圍繞通用的場景使用,避免濫用,

3、認識當前背景關系環境的局限性

遇到問題時,如果在當前的背景關系環境里找不到合適的解決方案,從這個環境跳出來換個維度也許能看到不同的風景,就像物理機到虛擬機再到現在的容器,都是打破了原來的規則逐步發展出新的技術生態,大多數的開發作業都是基于一個高層次的封裝上面進行,而突破往往都是從底層開始的,適當的時候也可以向下做一些探索,可能會產生一些有價值的東西,

參考文獻

[1] JSR 269:

https://www.jcp.org/en/jsr/detail?id=269

[2] Understanding AOP Proxies:

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-understanding-aop-proxies

?[3] Compilation Overview:

https://openjdk.org/groups/compiler/doc/compilation-overview/index.html

[4] The Hacker’s Guide to Javac:

http://scg.unibe.ch/archive/projects/Erni08b.pdf

[5] JavaParser-AST-Inspector:

https://github.com/MysterAitch/JavaParser-AST-Inspector

[6] OpenJDK source:

http://hg.openjdk.java.net/jdk8u/jdk8u60/langtools/

[7] Graphviz Online:

https://dreampuf.github.io/GraphvizOnline/#digraph G {}

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

標籤:其他

上一篇:判斷go物件是否能直接賦值進行深拷貝

下一篇:001-nginx基礎

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