亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JDK1.6“新“特性Instrumentation之JavaAgent(推薦)

 更新時(shí)間:2020年08月04日 08:30:28   作者:大火yzs  
這篇文章主要介紹了JDK1.6“新“特性Instrumentation之JavaAgent,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

簡(jiǎn)介

Java Agent是在JDK1.5以后,我們可以使用agent技術(shù)構(gòu)建一個(gè)獨(dú)立于應(yīng)用程序的代理程序(即為Agent),用來協(xié)助監(jiān)測(cè)、運(yùn)行甚至替換其他JVM上的程序。使用它可以實(shí)現(xiàn)虛擬機(jī)級(jí)別的AOP功能。

Agent分為兩種,一種是在主程序之前運(yùn)行的Agent,一種是在主程序之后運(yùn)行的Agent(前者的升級(jí)版,1.6以后提供)。

JavaAgent的作用Agent給我們程序帶來的影響

Java-Agent

使用Agent-premain方法影響的程序效果圖

image-20200802151946748

使用Agent-agentmain方法影響的程序效果圖

image-20200802153238246

JavaAgent相關(guān)的API

在java.lang.instrument包下 給我們提供了相關(guān)的API

而最為主要的就是Instrumentation這個(gè)接口中的幾個(gè)方法

image-20200802151335773

public interface Instrumentation {
 
 /**
 * 添加Transformer(轉(zhuǎn)換器) 
 * ClassFileTransformer類是一個(gè)接口,通常用戶只需實(shí)現(xiàn)這個(gè)接口的 byte[] transform()方法即可;
 * transform這個(gè)方法會(huì)返回一個(gè)已經(jīng)轉(zhuǎn)換過的對(duì)象的byte[]數(shù)組
 * @param transformer 攔截器
 * @return canRetransform 是否能重新轉(zhuǎn)換
 */
 	void addTransformer(ClassFileTransformer transformer, boolean canRetransform); 

 /**
 * 重新觸發(fā)類加載,
 * 該方法可以修改方法體、常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名
 * @param classes Class對(duì)象
 * @throws UnmodifiableClassException 異常
 */
 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

 /**
 * 直接替換類的定義
 * 重新轉(zhuǎn)換某個(gè)對(duì)象,并已一個(gè)新的class格式,進(jìn)行轉(zhuǎn)化。
 * 該方法可以修改方法體、常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名
 * @param definitions ClassDefinition對(duì)象[Class定義對(duì)象]
 * @throws ClassNotFoundException,UnmodifiableClassException 異常
 */
 void redefineClasses(ClassDefinition... definitions)throws ClassNotFoundException, UnmodifiableClassException;

 /**
 * 獲取當(dāng)前被JVM加載的所有類對(duì)象
 * @return Class[] class數(shù)組
 */
 Class[] getAllLoadedClasses();
}

后面我們會(huì)在代碼中具體用到這些方法。再詳細(xì)說明。

JavaAgent-premain方法1-初探效果:

實(shí)現(xiàn)main方法前執(zhí)行業(yè)務(wù)邏輯

Agent1.java

public class Agent1 {
 public static void premain(String agent){
 System.out.println("Agent1 premain :" + agent);
 }
}

Demo1.java

public class Demo1 {

 /**
 * VM參數(shù)
 * -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
 * */
 public static void main(String[] args) throws Exception {
 System.out.println("demo1");
 }
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent1
Can-Retransform-Classes: true

運(yùn)行效果

Agent1 premain :input
demo1

JavaAgent-premain方法2-實(shí)現(xiàn)修改代碼邏輯效果:

實(shí)現(xiàn) 修改 程序源代碼 hello -> hello agented

Agent2.java

public class Agent2 {
 /**
 * 可以運(yùn)行在main方法啟動(dòng)前
 * @param agent 輸入的參數(shù)
 * @param instrumentation 輸入的參數(shù)
 */
 public static void premain(String agent, Instrumentation instrumentation){
 System.out.println("Agent2 premain 2param :" + agent);
 instrumentation.addTransformer(new ConsoleTransformer(),true);
 }

}

ConsoleTransformer.java

public class ConsoleTransformer implements ClassFileTransformer {
 @Override
 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
 if (className.equals("cn/bigfire/Console")){
 String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
 String classFile = root + "JavaAgentDemo/agent/src/main/resources/Console.class";
 return FileUtil.readBytes(classFile);
 }
 return classfileBuffer;
 }
}

Demo2.java

public class Demo2 {

 /**
 * VM參數(shù)
 * -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
 * */
 public static void main(String[] args) throws Exception {
 new Thread(()->{
 while (true){
 Console.hello();// public static void hello(){System.out.println("hello"); }
 ThreadUtil.sleep(2000);
 }
 }).start();
 }
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent2
Can-Retransform-Classes: true

運(yùn)行效果

Agent2 premain 2param :input
滿足條件
hello  agented
hello  agented
hello  agented
hello  agented

JavaAgent-premain方法3-無侵入動(dòng)態(tài)修改程序源代碼實(shí)現(xiàn)方法耗時(shí)統(tǒng)計(jì)效果:

實(shí)現(xiàn)main方法外的所有方法統(tǒng)計(jì)時(shí)間

Agent3.java

public class Agent3 {
 /**
 * 可以運(yùn)行在main方法啟動(dòng)前
 * @param agent  輸入的參數(shù)
 * @param instrumentation instrumentation對(duì)象由JVM提供并傳入
 */
 public static void premain(String agent, Instrumentation instrumentation) {
 System.out.println("Agent3 premain :" + agent);
 instrumentation.addTransformer(new TimeCountTransformer());
 }

 /**
 * 時(shí)間統(tǒng)計(jì)Transformer 給要代理的方法添加時(shí)間統(tǒng)計(jì)
 */
 private static class TimeCountTransformer implements ClassFileTransformer {
 @Override
 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
 try {
 className = className.replace("/", ".");
 if (className.equals("cn.bigfire.Demo3")) {
  //使用全稱,用于取得字節(jié)碼類<使用javassist>
  CtClass ctclass = ClassPool.getDefault().get(className);
  //獲得方法列表
  CtMethod[] methods = ctclass.getDeclaredMethods();
  //給方法設(shè)置代理
  Stream.of(methods).forEach(method-> agentMethod(ctclass,method));
  //CtClass轉(zhuǎn)byte[]數(shù)組
  return ctclass.toBytecode();
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 return null;
 }
 }

 /**
 * 代理方法,把傳入的方法經(jīng)寫代理,并生成帶時(shí)間統(tǒng)計(jì)的方法,
 * @param ctClass  javassist的Class類
 * @param ctMethod  javassist的ctMethod方法
 * */
 public static void agentMethod(CtClass ctClass,CtMethod ctMethod){
 try {
 String mName = ctMethod.getName();
 if (!mName.equals("main")){//代理除了main方法以外的所有方法
 String newName = mName + "$Agent";
 ctMethod.setName(newName);
 CtMethod newMethod = CtNewMethod.copy(ctMethod, mName, ctClass, null);
 // 構(gòu)建新的方法體
 String bodyStr = "{\n" +
  "long startTime = System.currentTimeMillis();\n" +
  newName + "();\n" +
  "long endTime = System.currentTimeMillis();\n" +
  "System.out.println(\""+newName+"() cost:\" +(endTime - startTime));\n" +
  "}";
 newMethod.setBody(bodyStr);// 替換新方法
 ctClass.addMethod(newMethod);// 增加新方法
 }
 }catch (Exception e){
 e.printStackTrace();
 }
 }

}

Demo3.java

public class Demo3 {
 
 /**
 * VM參數(shù)
 * -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
 */
 public static void main(String[] args) throws Exception {
 sleep1();
 sleep2();
 }

 public static void sleep1(){
 ThreadUtil.sleep(1000);
 }

 public static void sleep2(){
 ThreadUtil.sleep(2000);
 }

}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Class-Path: ../javassist-3.12.1.GA.jar
Premain-Class: cn.bigfire.Agent3
Can-Retransform-Classes: true

運(yùn)行效果

Agent3 premain :input
sleep1$Agent() cost:1005
sleep2$Agent() cost:2001

JavaAgent-agentmain方法1-實(shí)現(xiàn)運(yùn)行時(shí)修改程序效果:

實(shí)現(xiàn)運(yùn)行時(shí) 修改程序 hello -> hello agented

Agent4.java

public class Agent4 {

 public static void premain(String agent){
 System.out.println("Agent4 premain 1param:" + agent);
 }

 public static void premain(String agent, Instrumentation instrumentation) {
 System.out.println("Agent4 premain 2param:" + agent);
 //premain時(shí),由于堆里還沒有相應(yīng)的Class。所以直接addTransformer,程序就會(huì)生效。
// instrumentation.addTransformer(new ConsoleTransformer(),true);
 }

 public static void agentmain(String agent, Instrumentation instrumentation){
 System.out.println("Agent4 agentmain 2param :" + agent);
 instrumentation.addTransformer(new ConsoleTransformer(),true);
 //agentmain運(yùn)行時(shí) 由于堆里已經(jīng)存在Class文件,所以新添加Transformer后
 // 還要再調(diào)用一個(gè) inst.retransformClasses(clazz); 方法來更新Class文件
 for (Class clazz:instrumentation.getAllLoadedClasses()) {
 if (clazz.getName().contains("cn.bigfire.Console")){
 try {
  instrumentation.retransformClasses(clazz);
 } catch (Exception e) {
  e.printStackTrace();
 }
 }
 }
 }

 public static void agentmain(String agent){
 System.out.println("Agent4 agentmain 1param :" + agent);
 }

}

Demo4

public class Demo4 {
 /**
 * 打包agent4 -> 先運(yùn)行demo2 -> 運(yùn)行demo4 ->選擇程序demo2結(jié)尾的程序,即可運(yùn)行時(shí)修改文件
 * VM參數(shù)
 * -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
 * */
 public static void main(String[] args) throws Exception {
 while (true){
 List<VirtualMachineDescriptor> list = VirtualMachine.list();
 for (int i = 0; i < list.size(); i++) {
 VirtualMachineDescriptor jvm = list.get(i);;
 System.out.println("[" +i+ "]ID:"+jvm.id()+",Name:"+jvm.displayName());
 }
 System.out.println("請(qǐng)選擇第幾個(gè)");
 Scanner scanner = new Scanner(System.in);
 int s = scanner.nextInt();
 VirtualMachineDescriptor virtualMachineDescriptor = list.get(s);
 VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor.id());
 String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
 String agentJar = root + "JavaAgentDemo\\agent\\target\\agent.jar";
 File file = new File(agentJar);
 System.out.println(file.exists());
 attach.loadAgent(agentJar,"param");
 attach.detach();
 }
 }
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent4
Agent-Class: cn.bigfire.Agent4
Can-Retransform-Classes: true
Can-Redefine-Classes: true

此時(shí)的運(yùn)行順序
打包agent4 -> 先運(yùn)行demo2 -> 運(yùn)行demo4 ->選擇程序demo2結(jié)尾的程序,即可運(yùn)行時(shí)修改文件

運(yùn)行效果
Demo2

Agent4 premain 2param:input
hello
hello

Demo4

[0]ID:12480,Name:cn.bigfire.Demo2
[1]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx
[2]ID:14864,Name:
[3]ID:3952,Name:cn.bigfire.Demo4
[4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36
[5]ID:11928,Name:org.jetbrains.jps.cmdline.Launcher xxx
請(qǐng)選擇第幾個(gè)
0
true

Demo2

Agent4 premain 2param:input
hello
hello
Agent4 agentmain 2param :param
hello agented
hello agented
hello agented

JavaAgent-agentmain方法2-實(shí)現(xiàn)動(dòng)態(tài)修改日志級(jí)別效果:

實(shí)現(xiàn)運(yùn)行時(shí) 修改程序 模擬項(xiàng)目中的動(dòng)態(tài)日志 info <-> debug

Agent5.java

public class Agent5 {

 public static void premain(String agent, Instrumentation instrumentation){
 System.out.println("Agent5 premain 2param :" + agent);
 instrumentation.addTransformer(new StartTransformer(),true);

 //這個(gè)方式不行。因?yàn)閱?dòng)時(shí)Class都還沒有呢。
// for (Class clazz:inst.getAllLoadedClasses()) {
// if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){
// try {
//  switchDebug(clazz);
//  instrumentation.retransformClasses(clazz);
// } catch (Exception e) {
//  e.printStackTrace();
// }
// }
// }
 }

 public static void agentmain(String agent, Instrumentation instrumentation){
 System.out.println("Agent5 agentmain 2param :" + agent);
 for (Class clazz:instrumentation.getAllLoadedClasses()) {
 if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){
 try {
  switchAtomicDebug(clazz);
  instrumentation.retransformClasses(clazz);
 } catch (Exception e) {
  e.printStackTrace();
 }
 }
 }
 }

 public static class StartTransformer implements ClassFileTransformer {
 @Override
 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
 //此時(shí)由于classBeingRedefined是空,所以還是不能用這個(gè)Class修改屬性呢,只能通過 讀取byte[]往堆里丟,才能用。
 if (className.equals("cn/bigfire/LogLevelStarter")){
 //【這是一個(gè)錯(cuò)誤的思路】 premain的時(shí)候 classBeingRedefined是空的因?yàn)楹芏嗟腃lass還沒加載到堆中
// if (classBeingRedefined!=null){
//  switchDebug(classBeingRedefined);
//  return toBytes(classBeingRedefined);
// }
 //正常的讀取一共文件byte[]數(shù)組
 String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
 String classFile = root + "JavaAgentDemo/agent/src/main/resources/LogLevelStarter.class";
 return FileUtil.readBytes(classFile);
 }
 return classfileBuffer;
 }
 }

 /**
 * 可序列化對(duì)象轉(zhuǎn)byte[]數(shù)組
 * @param clazz 要轉(zhuǎn)byte[]數(shù)組的對(duì)象
 * @return byte[] 返回byte[]數(shù)組
 */
 public static byte[] toBytes(Serializable clazz){
 try {
 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
 stream.writeObject(clazz);
 return byteArrayOutputStream.toByteArray();
 }catch (Exception e){
 e.printStackTrace();
 }
 return null;
 }

 public static void switchDebug(Class clazz){
 try {
 Field field1 = clazz.getDeclaredField("isDebug");
 field1.setAccessible(true);
 boolean debug = field1.getBoolean(clazz);
 field1.setBoolean(clazz,!debug);
 }catch (Exception e){
 e.printStackTrace();
 }
 }

 public static void switchAtomicDebug(Class clazz){
 try {
 Field field2 = clazz.getDeclaredField("atomicDebug");
 field2.setAccessible(true);
 AtomicBoolean atomicDebug = (AtomicBoolean)field2.get(clazz);
 atomicDebug.set(!atomicDebug.get());
 }catch (Exception e){
 e.printStackTrace();
 }
 }

}

注意,需要先把LogLevelStarter.java中的isDebug 改為true編譯一下。放到src/main/resources/目錄下;

LogLevelStarter.java

public class LogLevelStarter {

 public static volatile boolean isDebug = false;
 public static AtomicBoolean atomicDebug = new AtomicBoolean(false);

 /**
 * VM參數(shù)
 * -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
 */
 public static void main(String[] args) throws Exception {
 new Thread(()->{
 for (;;){
 //死循環(huán),每隔兩秒打印一個(gè)日志。
 System.out.print(isDebug ? "volatile debug" : "volatile info");
 System.out.print("\t");
 System.out.println(atomicDebug.get() ? "atomicDebug debug" : "atomicDebug info");
 ThreadUtil.sleep(2000);
 }
 }).start();
 }
}

resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: dahuoyzs
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_171
Premain-Class: cn.bigfire.Agent5
Agent-Class: cn.bigfire.Agent5
Can-Retransform-Classes: true
Can-Redefine-Classes: true

此時(shí)的運(yùn)行順序
打包agent5 -> 先運(yùn)行LogLevelStarter -> 運(yùn)行demo4 ->選擇程序LogLevelStarter結(jié)尾的程序,即可運(yùn)行時(shí)修改文件

運(yùn)行效果

LogLevelStarter

Agent5 premain 2param :input
volatile debug atomicDebug info
volatile debug atomicDebug info

Demo4

[0]ID:12592,Name:cn.bigfire.LogLevelStarter
[1]ID:12880,Name:cn.bigfire.Demo4
[2]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx
[3]ID:14864,Name:
[4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36
[5]ID:8116,Name:org.jetbrains.jps.cmdline.Launcher xxx
請(qǐng)選擇第幾個(gè)
0
true

LogLevelStarter

Agent5 premain 2param :input
volatile debug	atomicDebug info
volatile debug	atomicDebug info
Agent5 agentmain 2param :param
volatile debug	atomicDebug debug
volatile debug	atomicDebug debug

在Agent5中,其實(shí)使用premain和agentmain。

premain把volatile修飾的isDbug給修改為true了。

而agentmain時(shí)把a(bǔ)tomicDebug的值進(jìn)行多次取反操作。

自己實(shí)現(xiàn)一個(gè)熱部署功能的大致思路

當(dāng)運(yùn)行完本項(xiàng)目中的幾個(gè)demo之后。

讀者可能對(duì)Java Agent有了一些基本的概念

最起碼我們知道了premain是可以運(yùn)行在main函數(shù)前的。

agentmain是可以在程序運(yùn)行時(shí),修改程序內(nèi)的一些類文件的。

那么熱部署很明顯就是使用的agentmain這個(gè)特性了

那么熱部署具體應(yīng)該怎么實(shí)現(xiàn)呢?

這里先有個(gè)大概的思路。后續(xù)如果有經(jīng)歷,可以簡(jiǎn)單按照這個(gè)思路實(shí)現(xiàn)一下

思路

當(dāng)我們文件發(fā)生修改的時(shí)候,項(xiàng)目會(huì)重新加載我們的類。

那么這里肯定會(huì)涉及到文件變化的觀察。 即 觀察者設(shè)計(jì)模式跑不了

首先遞歸當(dāng)前項(xiàng)目目錄。并根據(jù)文件類型,如(.java ,xml,yml等)將此類文件注冊(cè)觀察者模式。

當(dāng)文件內(nèi)容發(fā)生變化時(shí),會(huì)調(diào)用 監(jiān)聽器中的回調(diào)方法;

在回調(diào)中完成如下(具體實(shí)現(xiàn)時(shí)未必需要)

使用Java1.6的JavaCompiler編譯Java文件;
自定義ClassLoader 裝載 編譯好的Class到堆中

使用agentmain修改原Class文件替換成新的Class文件

完成熱加載

JavaAgent的應(yīng)用場(chǎng)景

apm:(Application Performance Management)應(yīng)用性能管理。pinpoint、cat、skywalking等都基于Instrumentation實(shí)現(xiàn)
idea的HotSwap、Jrebel等熱部署工具
應(yīng)用級(jí)故障演練
Java診斷工具Arthas、Btrace等

源代碼

{
	"author": "大火yzs",
	"title": "【JavaAgent】JavaAgent入門教程",
	"tag": "JavaAgent,Instrumentation,運(yùn)行時(shí)動(dòng)態(tài)修改源程序",
	"createTime": "2020-08-02 18:30"
}

總結(jié)

到此這篇關(guān)于JDK1.6“新“特性Instrumentation之JavaAgent的文章就介紹到這了,更多相關(guān)JDK1.6“新“特性Instrumentation內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot+thymeleaf+shiro標(biāo)簽的實(shí)例

    springboot+thymeleaf+shiro標(biāo)簽的實(shí)例

    這篇文章主要介紹了springboot+thymeleaf+shiro標(biāo)簽的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 詳解如何把cmd黑窗口把java文件打包成jar

    詳解如何把cmd黑窗口把java文件打包成jar

    本文主要介紹了如何把cmd黑窗口把java文件打包成jar,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • SpringMVC實(shí)現(xiàn)參數(shù)校驗(yàn)配置方法

    SpringMVC實(shí)現(xiàn)參數(shù)校驗(yàn)配置方法

    這篇文章主要介紹了SpringMVC實(shí)現(xiàn)參數(shù)校驗(yàn)的配置方式,Spring MVC會(huì)拋出MethodArgumentNotValidException異常,并將錯(cuò)誤信息綁定到相應(yīng)的字段上,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Spring Boot 控制層之參數(shù)傳遞方法詳解

    Spring Boot 控制層之參數(shù)傳遞方法詳解

    這篇文章主要介紹了Spring Boot 控制層之參數(shù)傳遞方法詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • SpringBoot自定義注解驗(yàn)證枚舉的實(shí)現(xiàn)

    SpringBoot自定義注解驗(yàn)證枚舉的實(shí)現(xiàn)

    本文主要介紹了SpringBoot自定義注解驗(yàn)證枚舉的實(shí)現(xiàn),數(shù)據(jù)校驗(yàn),需要對(duì)枚舉類型的數(shù)據(jù)傳參,進(jìn)行數(shù)據(jù)校驗(yàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例

    Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例

    本篇文章主要介紹了Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例,具有一定的參加價(jià)值,有興趣的可以了解一下。
    2017-01-01
  • Gradle構(gòu)建多模塊項(xiàng)目的方法步驟

    Gradle構(gòu)建多模塊項(xiàng)目的方法步驟

    這篇文章主要介紹了Gradle構(gòu)建多模塊項(xiàng)目的方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • 前端和后端解決跨域問題的一些方式詳解

    前端和后端解決跨域問題的一些方式詳解

    跨域?qū)τ谡趯W(xué)習(xí)或者已經(jīng)就業(yè)的前端同學(xué)而言,就是老朋友,只要涉及請(qǐng)求,前后端交互,開發(fā)階段等關(guān)鍵字,都避不開跨域,這篇文章主要給大家介紹了關(guān)于前端和后端解決跨域問題的一些方式,需要的朋友可以參考下
    2024-07-07
  • 詳解Spring與MyBatis的整合的方法

    詳解Spring與MyBatis的整合的方法

    這篇文章主要為大家詳細(xì)介紹了Spring與MyBatis的整合,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • SpringBoot手寫自定義starter源碼

    SpringBoot手寫自定義starter源碼

    這篇文章主要介紹了SpringBoot手寫自定義starter源碼,SpringBoot擁有很多方便使用的starter,比如spring-boot-starter-log4j、mybatis-spring-boot-starter.jar等,各自都代表了一個(gè)相對(duì)完整的功能模塊,需要的朋友可以參考下
    2023-10-10

最新評(píng)論