主頁 >  其他 > 一起單測引起的專案加載失敗慘案

一起單測引起的專案加載失敗慘案

2023-05-07 08:12:40 其他

作者:京東科技 宋慧超

一、前言

最近在開發一個功能模塊時,在功能自測階段,通過使用單測測驗功能的完整性,在測驗單測聯通性使用到靜態方法測驗時,發現單測報錯,通過查閱解決方案發現需要對Javaassist包進行排包或者升版本處理,通過排包解決掉單測報錯,在部署專案時發現頻繁報bean注入失敗問題,最終定位發現是因為對Javaassist包排包引起的bean加載失敗,故而對Javaassist包相關知識進行學習整理文章如下,

單測相關報錯資訊如下:

Powermock - java.lang.IllegalStateException: Failed to transform class

解決單測報錯的文章鏈接:

https://stackoverflow.com/questions/32854688/powermock-java-lang-illegalstateexception-failed-to-transform-class

二、問題復現

1、前期準備

首先使用了Spring框架新建一個demo,并寫一個簡單測驗類對問題進行復現,

UserService的定義:

public interface UserService {
    void save(User user);
}

UserServiceImpl的實作代碼:

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save(User user) {
        userDao.save(user);
    }
}



這里我們使用了Spring框架的@Service@Autowired注解,以便讓Spring框架自動裝配UserDao實體,

但是,在我們的POM檔案中,雖然我們添加了對Spring框架的依賴,但是并沒有添加Javaassist庫的依賴,而UserServiceImpl中確實使用了Javaassist庫來進行位元組碼操作, UserServiceImpl的具體實作代碼:

public class UserServiceImpl implements UserService {
    // ...
    private static final String USER_CLASS_NAME = "com.example.User";

    private static final Class<?> USER_CLASS;

    static {
        try {
            USER_CLASS = Class.forName(USER_CLASS_NAME);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void save(User user) {
        try {
            // 創建一個ClassPool物件
            ClassPool cp = ClassPool.getDefault();

            // 從ClassPool中獲取一個CtClass物件
            CtClass ctClass = cp.get(USER_CLASS_NAME);

            // 獲取無參構造器
            CtConstructor ctConstructor = ctClass.getDeclaredConstructor(new CtClass[]{});

            // 獲取save方法
            CtMethod saveMethod = ctClass.getDeclaredMethod("save");

            // 生成代碼
            saveMethod.insertBefore("{System.out.println(\"插入代碼前\");}");
            saveMethod.insertAfter("{System.out.println(\"插入代碼后\");}");

            // 生成新的位元組碼并裝載到記憶體
            Class<?> targetClass = ctClass.toClass();
            Object instance = targetClass.newInstance();

            // 呼叫save方法
            Method method = targetClass.getMethod("save", USER_CLASS);
            method.invoke(instance, user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}



在這段代碼中,我們通過Javaassist庫生成了一個新的位元組碼,并使用反射機制將其實體化,并在呼叫save()方法前后插入了一些代碼,但是,由于Javaassist庫缺失,導致專案在啟動程序中無法正確加載UserServiceImpl的實體,從而出現了下述錯誤資訊,

2、報錯資訊

在部署程式時發現,應用無法正常啟動,并出現如下錯誤資訊:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [C:\workspace\project\target\classes\com\example\UserServiceImpl.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.UserService.<init>()

從錯誤資訊中我們可以看到,應用在創建UserService的實體時遇到了問題,無法實體化成功,

3、解決方案

為了修復這個問題,我們需要在POM檔案中加入對Javaassist庫的依賴:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>



添加依賴后,重新編譯并部署應用程式即可正常運行

三、Javaassist包

1、什么是Javaassist?

Javaassist 是由東京工業大學數學和計算機科學系的 Shigeru Chiba (千葉滋)教授創造的,Javaassist 作為實作動態位元組碼生成的一個開源類別庫,極大地簡化了 Java 開發者對底層位元組碼操作的難度,讓開發者能夠更加輕松地在運行時動態生成類、修改類檔案來達到輕量級 AOP、ORM、基于代理的遠程方法呼叫等功能,

(Javaassist已加入了開放源代碼JBoss 應用服務器專案,通過使用Javaassist對位元組碼操作為JBoss實作動態AOP框架,)

2、什么是動態編程?

動態編程是相對于靜態編程而言的,平時我們討論比較多的就是靜態編程語言,例如Java,與動態編程語言,例如JavaScript,那二者有什么明顯的區別呢?簡單的說就是在靜態編程中,型別檢查是在編譯時完成的,而動態編程中型別檢查是在運行時完成的,所謂動態編程就是繞過編譯程序在運行時進行操作的技術,在Java中有如下幾種方式:

?反射

這個搞Java的應該比較熟悉,原理也就是通過在運行時獲得型別資訊然后做相應的操作,由于Java執行程序中是將型別載入虛擬機中的,在運行時我們就可以動態獲取到所有型別的資訊,只能獲取卻不能修改型別資訊,

?動態編譯

動態編譯是從Java 6開始支持的,主要是通過一個JavaCompiler介面來完成的,通過這種方式我們可以直接編譯一個已經存在的java檔案,也可以在記憶體中動態生成Java代碼,動態編譯執行,

?呼叫JavaScript引擎

早在Java 6就加入了對Script(JSR223)的支持,這是一個腳本框架,提供了讓腳本語言來訪問Java內部的方法,你可以在運行的時候找到腳本引擎,然后呼叫這個引擎去執行腳本,這個腳本API允許你為腳本語言提供Java支持,

?動態生成位元組碼

這種技術通過操作Java位元組碼的方式在JVM中生成新類或者對已經加載的類動態添加元素,

3、動態編程解決什么問題?

在靜態語言中引入動態特性,主要是為了解決一些使用場景的痛點,其實完全使用靜態編程也辦的到,只是付出的代價比較高,沒有動態編程來的優雅,例如依賴注入框架Spring使用了反射,而Dagger2 卻使用了代碼生成的方式(APT),

例如:

a: 在那些依賴關系需要動態確認的場景: b: 需要在運行時動態插入代碼的場景,比如動態代理的實作, c: 通過組態檔來實作相關功能的場景

4、Javassit使用方法

javassistjboss的一個子專案,其主要的優點,在于簡單,而且快速,直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類,

操作java位元組碼的工具有兩個比較流行,一個是ASM,一個是Javassit

?ASM直接操作位元組碼指令,執行效率高,要求使用者掌握Java類位元組碼檔案格式及指令,對使用者的要求比較高,

?Javassit 提供了更高級的API,執行效率相對較,但無需掌握位元組碼指令的知識,對使用者要求較低,

應用層面來講一般使用建議優先選擇Javassit,如果后續發現Javassit 成為了整個應用的效率瓶頸的話可以再考慮ASM,當然如果開發的是一個基礎類別庫,或者基礎平臺,還是直接使用ASM吧,相信從事這方面作業的開發者能力應該比較高,

Javassist中最為重要的是ClassPoolCtClassCtMethod 以及 CtField這幾個類,

?ClassPool:一個基于HashMap實作的CtClass物件容器,其中是類名稱,是表示該類的CtClass物件,默認的ClassPool使用與底層JVM相同的類路徑,因此在某些情況下,可能需要向ClassPool添加類路徑或類位元組,

? getDefault (): 回傳默認的ClassPool ,單例模式,一般通過該方法創建我們的ClassPool

? appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 將一個ClassPath加到類搜索路徑的末尾位置或插入到起始位置,通常通過該方法寫入額外的類搜索路徑,以解決多個類加載器環境中找不到類問題;

? importPackage(String packageName):匯入包;

? makeClass(String classname):創建一個空類,沒有變數和方法,后序通過CtClass的函式進行添加;

? get(String classname)、getCtClass(String classname) : 根據類路徑名獲取該類的CtClass物件,用于后續的編輯,

?CtClass:表示一個類,這些CtClass物件可以從ClassPool獲得,

?debugDump; String型別,如果生成.class檔案,保存在這個目錄下,

?setName(String name): 給類重命名;

?setSuperclass(CtClass clazz): 設定父類;

?addField(CtField f, Initializer init): 添加欄位(屬性),初始值見CtField;

?addMethod(CtMethod m): 添加方法(函式);

?toBytecode(): 回傳修改后的位元組碼,需要注意的是一旦呼叫該方法,則無法繼續修改CtClass

?toClass(): 將修改后的CtClass加載至當前執行緒的背景關系類加載器中,CtClasstoClass方法是通過呼叫本方法實作,需要注意的是一旦呼叫該方法,則無法繼續修改已經被加載的CtClass

?writeFile(String directoryName): 根據CtClass生成 .class 檔案;

?defrost(): 解凍類,用于使用了toclass()toBytecodewriteFile(),類已經被JVM加載,Javassist凍結CtClass后;

?detach(): 避免記憶體溢位,從ClassPool中移除一些不需要的CtClass

?CtMethods:表示類中的方法,

?insertBefore(String src):在方法的起始位置插入代碼;

?insertAfter(String src):在方法的所有 return 陳述句前插入代碼以確保陳述句能夠被執行,除非遇到exception;

?insertAt(int lineNum, String src):在指定的位置插入代碼;

?addCatch(String src, CtClass exceptionType):將方法內陳述句作為try的代碼塊,插入catch代碼塊src;

?setBody(String src):將方法的內容設定為要寫入的代碼,當方法被 abstract修飾時,該修飾符被移除;

?setModifiers(int mod):設定訪問級別,一般使用Modifier呼叫常量;

?invoke(Object obj, Object... args):反射呼叫位元組碼生成類的方法,

?CtFields :表示類中的欄位,

?CtField(CtClass type, String name, CtClass declaring) :建構式,添加欄位型別,名稱,所屬的類;

?CtField.Initializer constant():CtClass使用addField時初始值的設定;

?setModifiers(int mod):設定訪問級別,一般使用Modifier呼叫常量,

?$開頭的特殊字符

符號 具體含義
$0, $1, $2, … | $0=this,$1表示方法的第一個引數,依次類推,如果方法是靜態的,則 $0 不可用
\(args | 方法引數陣列.它的型別為 Object\[\],\)args[0]=1 , 1,1,args[1]=$2
$r 回傳結果的型別,用于強制型別轉換
$w 包裝器型別,用于強制型別轉換,當回傳值是包裝型別時,可以用此來強轉
$_ 回傳值,一般在insertAfter中用到,用于得到原方法的回傳值
\(slg | 引數型別陣列,\)sig[0]表示第一個引數型別
\(type | 回傳值型別,一般在insertAfter中用到,即\)_的型別
$class | $0或this的型別
$e 例外型別

5、常用的Java插樁工具有哪些?

Java 插樁工具是一種能夠修改 Java 位元組碼的工具,通過在應用程式運行時動態修改位元組碼來實作對程式的監控跟蹤除錯優化等功能,

工具 位元組碼抽象級別 具體描述
ASM、BCEL 低級 庫需要直接在位元組碼級別上進行操作,通常,它們提供大多數功能豐富的功能,但與其他位元組碼操作工具相比,它們的使用也最復雜,
Javaassist 中級 庫提供了位元組碼的某種抽象級別,并簡化了其修改,例如,代替修改位元組碼,可以使用類似于Java的語法進行更改,然后將其編譯為位元組碼,然后由使用的庫修改為原始位元組碼,通常,它們缺少修改后的代碼驗證的功能-這意味著,錯誤可能在修改準備程序中被忽略,然后在運行時被發現,
AspectJ、CGLib 高級 庫使用高級指令進行操作,并且通常配備有用于語法驗證的工具集,不幸的是,從修改后的位元組碼進行的最高抽象化通常會導致某些功能的喪失,這些功能僅在直接修改位元組碼時可用,

四、總結

本文通過對由于Javaassist包缺失導致專案啟動程序中bean加載失敗的問題進行復現,并通過demo進行實體分析,解釋了因為缺失Javaassist庫導致的應用程式啟動失敗問題,并對Javaassist包相關知識進行介紹,后續會繼續對Javaassist相關知識進行學習補充,

建議大家在構建Maven專案時,仔細檢查POM檔案中的依賴,確保沒有漏掉任何必要的庫,以免因為遺漏而引起不必要的問題,

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

標籤:其他

上一篇:解密Elasticsearch:深入探究這款搜索和分析引擎

下一篇:返回列表

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

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 一起單測引起的專案加載失敗慘案

    最近在開發一個功能模塊時,在功能自測階段,通過使用單測測驗功能的完整性,在測驗單測聯通性使用到靜態方法測驗時,發現單測報錯,通過查閱解決方案發現需要對Javaassist包進行排包或者升版本處理。通過排包解決掉單測報錯,在部署專案時發現頻繁報bean注入失敗問題,最終定位發現是因為對Javaassi... ......

    uj5u.com 2023-05-07 08:12:40 more
  • 解密Elasticsearch:深入探究這款搜索和分析引擎

    最近使用Elasticsearch實作畫像系統,實作的dmp的資料中臺能力。同時調研了競品的架構選型。以及重溫了redis原理等。特此做一次es的總結和回顧。網上沒看到有人用Elasticsearch來完成畫像的。我來做第一次嘗試。 ......

    uj5u.com 2023-05-07 08:12:34 more
  • CUDA 的亂數演算法 API

    參考自 Nvidia cuRand 官方 API 檔案 一、具體使用場景 如下是是在 dropout 優化中手寫的 uniform_random 的 Kernel: #include <cuda_runtime.h> #include <curand_kernel.h> __device__ inl ......

    uj5u.com 2023-05-07 08:12:30 more
  • 決議智慧園區的發展瓶頸

    智能化工園區通過資訊化實作工業管理的數字化和網路化,實作對生產程序的全面監控和實時資料采集。這使企業能夠更好地管理,及時發現問題并采取相應的措施來降低成本。此外,智慧化管理提高了企頁澩的使用效率,避免浪費和重復利用資源,降低成本。 在當前經濟發展和技術進步的背景下,智慧園區的建設已經成為推進城市數 ......

    uj5u.com 2023-05-07 08:12:23 more
  • 使用Aidlux,輕松落地電力巡檢AI應用

    本專案參考AidLux AI 實戰訓練營內容,3-4個課時落地AI應用 電力線路是電力系統的重要組成部分, 它的安全可靠運行直接關系到一個國家經濟的穩定發展。 電力線路一旦出現故障,則有可能影響到成片區域的供電安全, 嚴重的甚至造成不可估量的損失。 因此, 預防電力線路故障預防歷來是電力系統的一項重 ......

    uj5u.com 2023-05-07 08:11:57 more
  • 倆小伙一晚上寫了個 AI 應用,月入兩萬??(文末附開發教程)

    開發出一款能夠與 AI 對話生成和編輯思維導圖的工具,聽起來似乎只能是一群專業的 AI 背景團隊花費大量的時間和精力訓練模型,打磨應用才能完成的事情。 但是,兩名大學生卻在一夜之間完成了,就像煉金術士將庸俗的材料轉化成黃金一樣,他們將代碼轉化為了神奇的工具,下面我們來一起揭開這個神奇工具背后的秘密。 ......

    uj5u.com 2023-05-07 08:11:45 more
  • CUDA 的亂數演算法 API

    參考自 Nvidia cuRand 官方 API 檔案 一、具體使用場景 如下是是在 dropout 優化中手寫的 uniform_random 的 Kernel: #include <cuda_runtime.h> #include <curand_kernel.h> __device__ inl ......

    uj5u.com 2023-05-07 08:11:34 more
  • 【問題排查篇】一次業務問題對 ES 的 cardinality 原理探究

    小編作業中負責業務的一個服務端系統,使用了 Elasticsearch 服務做資料存盤,業務運營人員反饋,用戶在使用該產品時發現,用戶后臺統計的訂單筆數和匯出的訂單筆數不一致!對此進行排查并進行總結 ......

    uj5u.com 2023-05-07 08:11:10 more
  • 分布式場景下,如何對外提供易變的服務,打造可靠的注冊中心?

    摘要:本文講了關于服務發現的很多干貨內容,核心內容為服務發現組件的選擇、網關的介紹、 客戶端側如何發給已發現的服務。 本文分享自華為云社區《分布式場景下,如何對外提供易變的服務,打造可靠的注冊中心?》,作者:breakDawn。 隨著云原生的概念越來越火,服務的架構應該如何發展和演進,成為很多程式員 ......

    uj5u.com 2023-05-07 08:10:55 more
  • Istio資料面新模式:Ambient Mesh技術決議

    摘要:Ambient Mesh以一種更符合大規模落地要求的形態出現,克服了大多數Sidecar模式的固有缺陷,讓用戶無需再感知網格相關組件,真正將網格下沉為基礎設施。 本文分享自華為云社區《華為云云原生團隊:Istio資料面新模式 Ambient Mesh技術決議》,作者: 云容器大未來。 如果說在 ......

    uj5u.com 2023-05-07 08:10:32 more