欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Javassist用法詳解

 更新時(shí)間:2021年02月08日 09:51:06   作者:蒼耳  
這篇文章主要介紹了Javassist用法的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下

概述

Java字節(jié)碼以二進(jìn)制的形式存儲(chǔ)在.class文件中,每一個(gè).class文件包含一個(gè)Java類或接口。Javaassist就是一個(gè)用來(lái)處理Java字節(jié)碼的類庫(kù)。它可以在一個(gè)已經(jīng)編譯好的類中添加新的方法,或者是修改已有的方法,并且不需要對(duì)字節(jié)碼方面有深入的了解。同時(shí)也可以通過(guò)完全手動(dòng)的方式生成一個(gè)新的類對(duì)象。

Maven依賴方式:

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

Gradle依賴方式:

implementation 'org.javassist:javassist:3.27.0-GA'

ClassPool

ClassPool是CtClass對(duì)象的容器,它按需讀取類文件來(lái)構(gòu)造CtClass對(duì)象,并且保存CtClass對(duì)象以便以后使用。

從實(shí)現(xiàn)的角度來(lái)看,ClassPool 是一個(gè)存儲(chǔ) CtClass 的 Hash 表,類的名稱作為 Hash 表的 key。ClassPool 的 get() 函數(shù)用于從 Hash 表中查找 key 對(duì)應(yīng)的 CtClass 對(duì)象。如果沒(méi)有找到,get() 函數(shù)會(huì)創(chuàng)建并返回一個(gè)新的 CtClass 對(duì)象,這個(gè)新對(duì)象會(huì)保存在 Hash 表中。

需要注意的是ClassPool會(huì)在內(nèi)存中維護(hù)所有被它創(chuàng)建過(guò)的CtClass,當(dāng)CtClass數(shù)量過(guò)多時(shí),會(huì)占用大量的內(nèi)存,API中給出的解決方案是重新創(chuàng)建ClassPool 或 有意識(shí)的調(diào)用CtClass的detach()方法以釋放內(nèi)存。

ClassPool需要關(guān)注的方法:

  • getDefault:返回默認(rèn)的ClassPool,一般通過(guò)該方法創(chuàng)建我們的ClassPool;
  • appendClassPath, insertClassPath:將一個(gè)ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。通常通過(guò)該方法寫(xiě)入額外的類搜索路徑,以解決多個(gè)類加載器環(huán)境中找不到類的尷尬;
  • toClass:將修改后的CtClass加載至當(dāng)前線程的上下文類加載器中,CtClass的toClass方法是通過(guò)調(diào)用本方法實(shí)現(xiàn)。需要注意的是一旦調(diào)用該方法,則無(wú)法繼續(xù)修改已經(jīng)被加載的class;
  • makeClass:根據(jù)類名創(chuàng)建新的CtClass對(duì)象;
  • get,getCtClass:根據(jù)類路徑名獲取該類的CtClass對(duì)象,用于后續(xù)的編輯。

可以使用toBytecode()函數(shù)來(lái)獲取修改過(guò)的字節(jié)碼:

byte[] b = cc.toBytecode();

也可以通過(guò)toClass()函數(shù)直接將CtClass轉(zhuǎn)換成Class對(duì)象:

Class clazz = cc.toClass();

toClass()會(huì)請(qǐng)求當(dāng)前線程的ClassLoader加載CtClass所代表的類文件,它返回此類文件的java.lang.Class對(duì)象。

CtClass

CtClass類表示一個(gè)class文件,每個(gè)CtClass對(duì)象都必須從ClassPool中獲取,CtClass需要關(guān)注的方法:

  • freeze:凍結(jié)一個(gè)類,使其不可修改;
  • isFrozen:判斷一個(gè)類是否已被凍結(jié);
  • defrost:解凍一個(gè)類,使其可以被修改;
  • prune:刪除類不必要的屬性,以減少內(nèi)存占用。調(diào)用該方法后,許多方法無(wú)法將無(wú)法正常使用,慎用;
  • detach:將該class從ClassPool中刪除;
  • writeFile:根據(jù)CtClass生成.class文件;
  • toClass:通過(guò)類加載器加載該CtClass;
  • addField,removeField:添加/移除一個(gè)CtField;
  • addMethod,removeMethod:添加/移除一個(gè)CtMethod;
  • addConstructor,removeConstructor:添加/移除一個(gè)CtConstructor。

如果一個(gè) CtClass 對(duì)象通過(guò) writeFile(), toClass(), toBytecode() 被轉(zhuǎn)換成一個(gè)類文件,此 CtClass 對(duì)象會(huì)被凍結(jié)起來(lái),不允許再修改,因?yàn)橐粋€(gè)類只能被 JVM 加載一次。

但是,一個(gè)冷凍的 CtClass 也可以被解凍,例如:

CtClasss cc = ...;
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // 因?yàn)轭愐呀?jīng)被解凍,所以這里可以調(diào)用成功

調(diào)用 defrost() 之后,此 CtClass 對(duì)象又可以被修改了。

如果 ClassPool.doPruning 被設(shè)置為 true,Javassist 在凍結(jié) CtClass 時(shí),會(huì)修剪 CtClass 的數(shù)據(jù)結(jié)構(gòu)。為了減少內(nèi)存的消耗,修剪操作會(huì)丟棄 CtClass 對(duì)象中不必要的屬性。例如,Code_attribute 結(jié)構(gòu)會(huì)被丟棄。一個(gè) CtClass 對(duì)象被修改之后,方法的字節(jié)碼是不可訪問(wèn)的,但是方法名稱、方法簽名、注解信息可以被訪問(wèn)。修剪過(guò)的 CtClass 對(duì)象不能再次被解凍。ClassPool.doPruning 的默認(rèn)值為 false。

stopPruning() 可以用來(lái)駁回修剪操作。

CtClasss cc = ...;
cc.stopPruning(true);
cc.writeFile(); // 轉(zhuǎn)換成一個(gè) class 文件
// cc is not pruned.

這個(gè) CtClass 沒(méi)有被修剪,所以在 writeFile() 之后,可以被解凍。

注意:調(diào)試的時(shí)候可能臨時(shí)需要停止修剪和凍結(jié),然后保存一個(gè)修改過(guò)的類文件到磁盤,debugWriteFile() 方法正是為此準(zhǔn)備的。它停止修剪,然后寫(xiě)類文件,然后解凍并再次打開(kāi)修剪(如果開(kāi)始時(shí)修養(yǎng)是打開(kāi)的)。

CtMthod

CtMthod代表類中的某個(gè)方法,可以通過(guò)CtClass提供的API獲取 或者 構(gòu)造方法 或者 CtNewMethod.make()方法新建,通過(guò)CtMethod對(duì)象可以實(shí)現(xiàn)對(duì)方法的修改。

CtMethod中的一些重要方法:

  • insertBefore:在方法的起始位置插入代碼;
  • insterAfter:在方法的所有 return 語(yǔ)句前插入代碼以確保語(yǔ)句能夠被執(zhí)行,除非遇到exception;
  • insertAt:在指定的位置插入代碼;
  • setBody:將方法的內(nèi)容設(shè)置為要寫(xiě)入的代碼,當(dāng)方法被abstract修飾時(shí),該修飾符被移除;
  • make:創(chuàng)建一個(gè)新的方法。

CtNewMethod是一個(gè)用來(lái)創(chuàng)建CtMethod實(shí)例的類,其一個(gè)make方法如下:

public static CtMethod make(int modifiers, CtClass returnType,
       String mname, CtClass[] parameters,
       CtClass[] exceptions,
       String body, CtClass declaring)
 throws CannotCompileException
{
 try {
  CtMethod cm
   = new CtMethod(returnType, mname, parameters, declaring);
  cm.setModifiers(modifiers);
  cm.setExceptionTypes(exceptions);
  cm.setBody(body);
  return cm;
 }
 catch (NotFoundException e) {
  throw new CannotCompileException(e);
 }
}

也可以通過(guò)CtNewMethod.setter/getter方法為某個(gè)屬性創(chuàng)建get/set方法:

CtClass cc = ...;
CtField param = ...;
cc.addMethod(CtNewMethod.setter("setName", param));
cc.addMethod(CtNewMethod.getter("getName", param));

CtField

CtField代表類中的某個(gè)屬性,可以直接通過(guò)其構(gòu)造方法創(chuàng)建實(shí)例:

CtClass cc = pool.makeClass("com.hearing.demo.Person");
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
param.setModifiers(Modifier.PRIVATE);
cc.addField(param, CtField.Initializer.constant("hearing"));

CtConstructor

CtConstructor代表類中的一個(gè)構(gòu)造器,可以通過(guò)CtConstructor.make方法創(chuàng)建:

public static CtConstructor make(CtClass[] parameters,
         CtClass[] exceptions,
         String body, CtClass declaring)
 throws CannotCompileException
{
 try {
  CtConstructor cc = new CtConstructor(parameters, declaring);
  cc.setExceptionTypes(exceptions);
  cc.setBody(body);
  return cc;
 }
 catch (NotFoundException e) {
  throw new CannotCompileException(e);
 }
}

也可以通過(guò)構(gòu)造方法直接創(chuàng)建:

CtConstructor cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法參數(shù)
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);

ClassPath

ClassPath是一個(gè)接口,代表類的搜索路徑,含有具體的搜索實(shí)現(xiàn)。當(dāng)通過(guò)其它途徑無(wú)法獲取要編輯的類時(shí),可以嘗試定制一個(gè)自己的ClassPath。API提供的實(shí)現(xiàn)中值得關(guān)注的有:

  • ByteArrayClassPath:將類以字節(jié)碼的形式加入到該path中,ClassPool可以從該path中生成所需的CtClass。
  • ClassClassPath:通過(guò)某個(gè)class生成的path,通過(guò)該class的classloader來(lái)嘗試加載指定的類文件。
  • LoaderClassPath:通過(guò)某個(gè)classloader生成path,并通過(guò)該classloader搜索加載指定的類文件。需要注意的是該類加載器以弱引用的方式存在于path中,當(dāng)不存在強(qiáng)引用時(shí),隨時(shí)可能會(huì)被清理。

通過(guò) ClassPool.getDefault() 獲取的 ClassPool 使用 JVM 的類搜索路徑。如果程序運(yùn)行在 JBoss 或者 Tomcat 等 Web 服務(wù)器上,ClassPool 可能無(wú)法找到用戶的類,因?yàn)?Web 服務(wù)器使用多個(gè)類加載器作為系統(tǒng)類加載器。在這種情況下,ClassPool 必須添加額外的類搜索路徑。

下面的例子中,pool 代表一個(gè) ClassPool 對(duì)象:

pool.insertClassPath(new ClassClassPath(this.getClass()));

上面的語(yǔ)句將 this 指向的類添加到 pool 的類加載路徑中。你可以使用任意 Class 對(duì)象來(lái)代替 this.getClass(),從而將 Class 對(duì)象添加到類加載路徑中。

也可以注冊(cè)一個(gè)目錄作為類搜索路徑。下面的例子將 /usr/local/javalib 添加到類搜索路徑中:

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

類搜索路徑不但可以是目錄,還可以是 URL :

ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

上述代碼將 http://www.javassist.org:80/java/ 添加到類搜索路徑。并且這個(gè)URL只能搜索 org.javassist 包里面的類。例如,為了加載 org.javassist.test.Main,它的類文件會(huì)從獲取 http://www.javassist.org:80/java/org/javassist/test/Main.class 獲取。

此外,也可以直接傳遞一個(gè) byte 數(shù)組給 ClassPool 來(lái)構(gòu)造一個(gè) CtClass 對(duì)象,完成這項(xiàng)操作,需要使用 ByteArrayPath 類。示例:

ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);

示例中的 CtClass 對(duì)象表示 b 代表的 class 文件。將對(duì)應(yīng)的類名傳遞給 ClassPool 的 get() 方法,就可以從 ByteArrayClassPath 中讀取到對(duì)應(yīng)的類文件。

如果你不知道類的全名,可以使用 makeClass() 方法:

ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

makeClass() 返回從給定輸入流構(gòu)造的 CtClass 對(duì)象。你可以使用 makeClass() 將類文件提供給 ClassPool 對(duì)象。如果搜索路徑包含大的 jar 文件,這可能會(huì)提高性能。由于 ClassPool 對(duì)象按需讀取類文件,它可能會(huì)重復(fù)搜索整個(gè) jar 文件中的每個(gè)類文件。 makeClass() 可以用于優(yōu)化此搜索。由 makeClass() 構(gòu)造的 CtClass 保存在 ClassPool 對(duì)象中,從而使得類文件不會(huì)再被讀取。

用戶可以通過(guò)實(shí)現(xiàn) ClassPath 接口來(lái)擴(kuò)展類加載路徑,然后調(diào)用 ClassPool 的 insertClassPath() 方法將路徑添加進(jìn)來(lái)。這種技術(shù)主要用于將非標(biāo)準(zhǔn)資源添加到類搜索路徑中。

ClassLoader

CtClass的toClass()方法請(qǐng)求當(dāng)前線程的上下文類加載器,加載CtClass對(duì)象所表示的類:

public class Hello {
 public void say() {
  System.out.println("Hello");
 }
}

public class Test {
 public static void main(String[] args) throws Exception {
  ClassPool cp = ClassPool.getDefault();
  CtClass cc = cp.get("Hello");
  CtMethod m = cc.getDeclaredMethod("say");
  m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
  Class c = cc.toClass();
  Hello h = (Hello) c.newInstance();
  h.say();
 }
}

注意:上面的程序要正常運(yùn)行,Hello 類在調(diào)用 toClass() 之前不能被加載。如果 JVM 在 toClass() 調(diào)用之前加載了原始的 Hello 類,后續(xù)加載修改的 Hello 類將會(huì)失?。↙inkageError 拋出)。例如,如果 Test 中的 main() 是這樣的:

public static void main(String[] args) throws Exception {
 Hello orig = new Hello();
 ClassPool cp = ClassPool.getDefault();
 CtClass cc = cp.get("Hello");
}

那么,原始的 Hello 類在 main 的第一行被加載,toClass() 調(diào)用會(huì)拋出一個(gè)異常,因?yàn)轭惣虞d器不能同時(shí)加載兩個(gè)不同版本的 Hello 類。

如果程序在某些應(yīng)用程序服務(wù)器(如JBoss和Tomcat)上運(yùn)行,toClass()使用的上下文類加載器可能是不合適的。在這種情況下,你會(huì)看到一個(gè)意想不到的 ClassCastException。為了避免這個(gè)異常,必須給 toClass() 指定一個(gè)合適的類加載器。例如:

CtClass cc = ...;
Class c = cc.toClass(bean.getClass().getClassLoader());

應(yīng)該給toClass()傳遞加載了你的程序的類加載器(上例中,bean對(duì)象的類),toClass() 是為了簡(jiǎn)便而提供的方法,如果你需要更復(fù)雜的功能,你應(yīng)該編寫(xiě)自己的類加載器。

Javassit 提供一個(gè)類加載器 javassist.Loader。它使用 javassist.ClassPool 對(duì)象來(lái)讀取類文件。

例如,javassist.Loader 可以用于加載用 Javassist 修改過(guò)的類。

public class Main {
 public static void main(String[] args) throws Throwable {
  ClassPool pool = ClassPool.getDefault();
  Loader cl = new Loader(pool);

  CtClass ct = pool.get("test.Rectangle");
  ct.setSuperclass(pool.get("test.Point"));

  Class c = cl.loadClass("test.Rectangle");
  Object rect = c.newInstance();
 }
}

這個(gè)程序?qū)?test.Rectangle 的超類設(shè)置為 test.Point。然后再加載修改的類,并創(chuàng)建新的 test.Rectangle 類的實(shí)例。

如果用戶希望在加載時(shí)按需修改類,則可以向 javassist.Loader 添加事件監(jiān)聽(tīng)器。當(dāng)類加載器加載類時(shí)會(huì)通知監(jiān)聽(tīng)器。事件監(jiān)聽(tīng)器類必須實(shí)現(xiàn)以下接口:

public interface Translator {
 public void start(ClassPool pool)
  throws NotFoundException, CannotCompileException;
 public void onLoad(ClassPool pool, String classname)
  throws NotFoundException, CannotCompileException;
}

當(dāng)事件監(jiān)聽(tīng)器通過(guò) addTranslator() 添加到 javassist.Loader 對(duì)象時(shí),start() 方法會(huì)被調(diào)用。在 javassist.Loader 加載類之前,會(huì)調(diào)用 onLoad() 方法??梢栽?onLoad() 方法中修改被加載的類的定義。

例如,下面的事件監(jiān)聽(tīng)器在類加載之前,將所有類更改為 public 類。

public class MyTranslator implements Translator {
 void start(ClassPool pool) throws NotFoundException, CannotCompileException {}
 void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException {
  CtClass cc = pool.get(classname);
  cc.setModifiers(Modifier.PUBLIC);
 }
}

注意,onLoad() 不必調(diào)用 toBytecode() 或 writeFile(),因?yàn)?javassist.Loader 會(huì)調(diào)用這些方法來(lái)獲取類文件。

示例

創(chuàng)建Class文件

public class App {
 public static void main(String[] args) {
  try {
   createPerson();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 private static void createPerson() throws Exception {
  ClassPool pool = ClassPool.getDefault();

  // 1. 創(chuàng)建一個(gè)空類
  CtClass cc = pool.makeClass("com.hearing.demo.Person");

  // 2. 新增一個(gè)字段 private String name = "hearing";
  CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
  param.setModifiers(Modifier.PRIVATE);
  cc.addField(param, CtField.Initializer.constant("hearing"));

  // 3. 生成 getter、setter 方法
  cc.addMethod(CtNewMethod.setter("setName", param));
  cc.addMethod(CtNewMethod.getter("getName", param));

  // 4. 添加無(wú)參的構(gòu)造函數(shù)
  CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
  cons.setBody("{name = \"hearing\";}");
  cc.addConstructor(cons);

  // 5. 添加有參的構(gòu)造函數(shù)
  cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
  // $0=this / $1,$2,$3... 代表方法參數(shù)
  cons.setBody("{$0.name = $1;}");
  cc.addConstructor(cons);

  // 6. 創(chuàng)建一個(gè)名為printName方法,無(wú)參數(shù),無(wú)返回值,輸出name值
  CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
  ctMethod.setModifiers(Modifier.PUBLIC);
  ctMethod.setBody("{System.out.println(name);}");
  cc.addMethod(ctMethod);

  //這里會(huì)將這個(gè)創(chuàng)建的類對(duì)象編譯為.class文件
  cc.writeFile("/.../path/");
 }
}

創(chuàng)建的class文件如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.hearing.demo;

public class Person {
 private String name = "hearing";

 public void setName(String var1) {
  this.name = var1;
 }

 public String getName() {
  return this.name;
 }

 public Person() {
  this.name = "hearing";
 }

 public Person(String var1) {
  this.name = var1;
 }

 public void printName() {
  System.out.println(this.name);
 }
}

調(diào)用生成的類對(duì)象

1.通過(guò)反射的方式調(diào)用:

Object person = cc.toClass().newInstance();
Method setName = person.getClass().getMethod("setName", String.class);
setName.invoke(person, "hearing1");
Method execute = person.getClass().getMethod("printName");
execute.invoke(person);

2.通過(guò)讀取class文件的方式調(diào)用:

ClassPool pool = ClassPool.getDefault();
// 設(shè)置類路徑
pool.appendClassPath("/.../path/");
CtClass ctClass = pool.get("com.hearing.demo.Person");
Object person = ctClass.toClass().newInstance();
// 下面和通過(guò)反射的方式一樣去使用

3.通過(guò)接口的方式:

上面兩種其實(shí)都是通過(guò)反射的方式去調(diào)用,問(wèn)題在于我們的工程中其實(shí)并沒(méi)有這個(gè)類對(duì)象,所以反射的方式比較麻煩,并且開(kāi)銷也很大。那么如果你的類對(duì)象可以抽象為一些方法的合集,就可以考慮為該類生成一個(gè)接口類。這樣在newInstance()的時(shí)候我們就可以強(qiáng)轉(zhuǎn)為接口,可以將反射的那一套省略掉了。

還拿上面的Person類來(lái)說(shuō),新建一個(gè)IPerson接口類:

public interface IPerson {
 void setName(String name);
 String getName();
 void printName();
}

實(shí)現(xiàn)部分的代碼如下:

ClassPool pool = ClassPool.getDefault();
pool.appendClassPath("/.../path/");

// 獲取接口
CtClass codeClassI = pool.get("com.hearing.demo.IPerson");
// 獲取上面生成的類
CtClass ctClass = pool.get("com.hearing.demo.Person");
// 使代碼生成的類,實(shí)現(xiàn) IPerson 接口
ctClass.setInterfaces(new CtClass[]{codeClassI});

// 以下通過(guò)接口直接調(diào)用 強(qiáng)轉(zhuǎn)
IPerson person = (IPerson)ctClass.toClass().newInstance();
System.out.println(person.getName());
person.setName("hearing1");
person.printName();

修改現(xiàn)有的類對(duì)象

有如下類對(duì)象:

public class Test {
 public void test1() {
  System.out.println("I am test1");
 }
}

然后進(jìn)行修改:

public class App {
 public static void main(String[] args) {
  try {
   update();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 private static void update() throws Exception {
  ClassPool pool = ClassPool.getDefault();
  CtClass cc = pool.get("com.hearing.demo.Test");

  CtMethod personFly = cc.getDeclaredMethod("test1");
  personFly.insertBefore("System.out.println(\"...before...\");");
  personFly.insertAfter("System.out.println(\"...after...\");");

  //新增一個(gè)方法
  CtMethod ctMethod = new CtMethod(CtClass.voidType, "test2", new CtClass[]{}, cc);
  ctMethod.setModifiers(Modifier.PUBLIC);
  ctMethod.setBody("{System.out.println(\"I am test2\");}");
  cc.addMethod(ctMethod);

  Object test = cc.toClass().newInstance();
  Method personFlyMethod = test.getClass().getMethod("test1");
  personFlyMethod.invoke(test);
  Method execute = test.getClass().getMethod("test2");
  execute.invoke(test);
 }
}

需要注意的是:上面的insertBefore() 和 setBody()中的語(yǔ)句,如果是單行語(yǔ)句可以直接用雙引號(hào),但是有多行語(yǔ)句的情況下,需要將多行語(yǔ)句用{}括起來(lái)。javassist只接受單個(gè)語(yǔ)句或用大括號(hào)括起來(lái)的語(yǔ)句塊。

以上就是Javassist用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Javassist用法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java9以后的垃圾回收的具體用法

    Java9以后的垃圾回收的具體用法

    這篇文章主要介紹了Java9以后的垃圾回收的具體用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java調(diào)用shell腳本解決傳參和權(quán)限問(wèn)題的方法

    Java調(diào)用shell腳本解決傳參和權(quán)限問(wèn)題的方法

    今天小編就為大家分享一篇關(guān)于Java調(diào)用shell腳本解決傳參和權(quán)限問(wèn)題的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-03-03
  • java基本教程之synchronized關(guān)鍵字 java多線程教程

    java基本教程之synchronized關(guān)鍵字 java多線程教程

    這篇文章主要介紹了java的synchronized原理、synchronized基本規(guī)則、synchronized方法 和 synchronized代碼塊、實(shí)例鎖和全局鎖
    2014-01-01
  • Java full gc觸發(fā)情況實(shí)例解析

    Java full gc觸發(fā)情況實(shí)例解析

    這篇文章主要介紹了Java full gc觸發(fā)情況實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Java中常用輸出方式(print() println() printf())

    Java中常用輸出方式(print() println() printf())

    這篇文章主要介紹了Java中常用輸出方式(print() println() printf()),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java回調(diào)方法詳解

    Java回調(diào)方法詳解

    本篇文章主要介紹了Java回調(diào)方法的步驟、回調(diào)的例子、異步回調(diào)與同步回調(diào)、回調(diào)方法在通信中的應(yīng)用等。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-01-01
  • 詳解SpringMVC組件之HandlerMapping(二)

    詳解SpringMVC組件之HandlerMapping(二)

    這篇文章主要介紹了詳解SpringMVC組件之HandlerMapping(二),HandlerMapping組件是Spring?MVC核心組件,用來(lái)根據(jù)請(qǐng)求的request查找對(duì)應(yīng)的Handler,在Spring?MVC中,有各式各樣的Web請(qǐng)求,每個(gè)請(qǐng)求都需要一個(gè)對(duì)應(yīng)的Handler來(lái)處理,需要的朋友可以參考下
    2023-08-08
  • java中有關(guān)構(gòu)造方法中的輸出

    java中有關(guān)構(gòu)造方法中的輸出

    這篇文章主要介紹了java中有關(guān)構(gòu)造方法中的輸出,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • mybatis對(duì)象List<String> List<Integer>屬性映射方式

    mybatis對(duì)象List<String> List<Integer>屬性映射方式

    這篇文章主要介紹了mybatis對(duì)象List<String> List<Integer>屬性映射方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 淺談SpringBoot如何自定義Starters

    淺談SpringBoot如何自定義Starters

    今天帶大家來(lái)學(xué)習(xí)SpringBoot如何自定義Starters,文中有非常詳細(xì)的圖文介紹及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05

最新評(píng)論