自定義文件類加載器和網絡類加載器

參考:https://blog.csdn.net/justloveyou_/article/details/72217806java

tomcat類加載詳解:https://blog.csdn.net/justloveyou_/article/details/72231425web

1、文件類加載器spring

  1. 使用的類express

    編譯java類爲class,移動到咱們想要放的目錄,刪除系統資源下的類,避免被appclassloader加載。apache

  2. 參考代碼:數組

    • 待測試類:Sample.java
    package com.example;
    
    public class Sample {
    
    	private Sample instance;
    
    	public void setSample(Object instance) {
    		System.out.println(this.toString());
    		System.out.println(this.getClass().getClassLoader());
    		System.out.println(instance.toString());
    		System.out.println(instance.getClass().getClassLoader());
    		this.instance = (Sample) instance;
    	}
    }
    • 文件加載器 FileSystemClassLoader.java
    package classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    //https://blog.csdn.net/justloveyou_/article/details/72217806
    // 文件系統類加載器
    public class FileSystemClassLoader extends ClassLoader {
    
    	private String rootDir;
    
    	public FileSystemClassLoader(String rootDir) {
    		this.rootDir = rootDir;
    	}
    
    	// 獲取類的字節碼
    	@Override
    	protected Class<?> findClass(String name) throws ClassNotFoundException {
    		byte[] classData = getClassData(name);  // 獲取類的字節數組
    		if (classData == null) {
    			throw new ClassNotFoundException();
    		} else {
    			return defineClass(name, classData, 0, classData.length);
    		}
    	}
    
    	private byte[] getClassData(String className) {
    		// 讀取類文件的字節
    		String path = classNameToPath(className);
    		try {
    			InputStream ins = new FileInputStream(path);
    			ByteArrayOutputStream baos = new ByteArrayOutputStream();
    			int bufferSize = 4096;
    			byte[] buffer = new byte[bufferSize];
    			int bytesNumRead = 0;
    			// 讀取類文件的字節碼
    			while ((bytesNumRead = ins.read(buffer)) != -1) {
    				baos.write(buffer, 0, bytesNumRead);
    			}
    			return baos.toByteArray();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		return null;
    	}
    
    	private String classNameToPath(String className) {
    		// 獲得類文件的徹底路徑
    		return rootDir + File.separatorChar
    				+ className.replace('.', File.separatorChar) + ".class";
    	}
    }
    • 測試類:ClassIdentity.java
    package classloader;
    
    import java.lang.reflect.Method;
    
    public class ClassIdentity {
    
    	public static void main(String[] args) {
    		new ClassIdentity().testClassIdentity();
    	}
    
    	public void testClassIdentity() {
    		String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";
    		classDataRootPath = "D:\\installdata\\indeaprojects\\demo\\src\\main\\java";
    		FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
    		FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
    		String className = "com.example.Sample";
    		try {
    			Class<?> class1 = fscl1.loadClass(className);  // 加載Sample類
    			Object obj1 = class1.newInstance();  // 建立對象
    			Class<?> class2 = fscl2.loadClass(className);//改了fscl2爲fscl1不報錯
    			Object obj2 = class2.newInstance();
    			Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
    			setSampleMethod.invoke(obj1, obj2);//不是一個classloder加載的不能互轉,fscl2改成fscl1就不會報錯
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }

    com.example.Sample@6b884d57
    classloader.FileSystemClassLoader@85ede7b
    com.example.Sample@38af3868
    classloader.FileSystemClassLoader@63961c42
    java.lang.reflect.InvocationTargetException
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:23)
    	at classloader.ClassIdentity.main(ClassIdentity.java:8)
    Caused by: java.lang.ClassCastException: com.example.Sample cannot be cast to com.example.Sample
    	at com.example.Sample.setSample(Sample.java:12)
    	... 6 more
    • 若是改成同一個類加載器,發現類實例對象不同,能夠互轉

    com.example.Sample@65b54208
    classloader.FileSystemClassLoader@85ede7b
    com.example.Sample@1be6f5c3
    classloader.FileSystemClassLoader@85ede7b

2、網絡類加載器tomcat

  1. 使用的類springboot

    我使用的容器是tomcat,框架是springboot2,maven3.6, jdk1.8 構建工程,提供服務網絡

  2. 代碼:app

    個人資源路徑(file:D:/installdata/indeaprojects/demo/target/classes/)

    • WebConfig.java (對外提供本地資源)
    package com.config;
    
    	import org.springframework.boot.web.servlet.MultipartConfigFactory;
    	import org.springframework.context.annotation.Bean;
    	import org.springframework.context.annotation.Configuration;
    	import org.springframework.util.unit.DataSize;
    	import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    	import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    	import javax.servlet.MultipartConfigElement;
    
    	/**
    	 * description: //攔截器
    	 * https://blog.csdn.net/allen_lu_hh/article/details/83218028
    	 * @author: heliming
    	 * @date:2019/04/14 下午 6:04
    	 */
    	@Configuration
    	public class WebConfig implements WebMvcConfigurer {
    		@Override
    		public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    			registry.addResourceHandler("/classes/**").addResourceLocations("file:D:/installdata/indeaprojects/demo/target/classes/");
    
    		}
    
    		/**
    		 * 設置單個文件上傳大小限制爲10M(拍照上傳頭像文件超限)
    		 */
    		@Bean
    		public MultipartConfigElement getMultipartConfigElement() {
    			MultipartConfigFactory factory = new MultipartConfigFactory();
    			factory.setMaxFileSize(DataSize.ofBytes(10 * 1024 * 1024));
    			return factory.createMultipartConfig();
    		}
    	}
  3. 客戶端使用的接口

    • ICalculator.java
    package classloader;
    
    	public interface ICalculator extends Versioned {
    
    		String calculate(String expression);
    	}
    • Versioned.java
    package classloader;
    
    	public interface Versioned {
    
    		String getVersion();
    	}
  4. 服務端對接口實現(須要引用客戶端的接口,jar包方式或者class文件方式)

    • CalculatorAdvanced.java
    package com.example;
    
    	import classloader.ICalculator;
    
    	public class CalculatorAdvanced implements ICalculator {
    
    		@Override
    		public String calculate(String expression) {
    			return "Result is " + expression;
    		}
    
    		@Override
    		public String getVersion() {
    			return "2.0";
    		}
    	}
    • CalculatorBasic.java
    package com.example;
    
    	import classloader.ICalculator;
    
    	public class CalculatorBasic implements ICalculator {
    
    		@Override
    		public String calculate(String expression) {
    			return expression;
    		}
    
    		@Override
    		public String getVersion() {
    			return "1.0";
    		}
    	}
    • HangxinApplication.java(服務端啓動類)
    package com;
    
    	import org.springframework.boot.SpringApplication;
    	import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    	@SpringBootApplication
    	public class HangxinApplication {
    
    		public static void main(String[] args) {
    			SpringApplication.run(HangxinApplication.class, args);
    		}
    
    	}
    • application.yml
    server:
    	  port: 8888
    • pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    	<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    			 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    		<modelVersion>4.0.0</modelVersion>
    		<parent>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-parent</artifactId>
    			<version>2.4.1</version>
    			<relativePath/> <!-- lookup parent from repository -->
    		</parent>
    		<groupId>com.example</groupId>
    		<artifactId>demo</artifactId>
    		<version>0.0.1-SNAPSHOT</version>
    		<name>demo</name>
    		<description>Demo project for Spring Boot</description>
    
    		<properties>
    			<java.version>1.8</java.version>
    		</properties>
    
    		<dependencies>
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-web</artifactId>
    			</dependency>
    
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-test</artifactId>
    				<scope>test</scope>
    			</dependency>
    			<!--thymeleaf-->
    			<dependency>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-starter-thymeleaf</artifactId>
    			</dependency>
    		</dependencies>
    
    		<build>
    			<plugins>
    				<plugin>
    					<groupId>org.springframework.boot</groupId>
    					<artifactId>spring-boot-maven-plugin</artifactId>
    				</plugin>
    			</plugins>
    		</build>
    
    	</project>
  5. 客戶端代碼:

    • 啓動類CalculatorTest.java
    package classloader;
    
    	public class CalculatorTest {
    
    		public static void main(String[] args) {
    
    			String url = "http://10.0.21.168:8888/classes";
    			NetworkClassLoader ncl = new NetworkClassLoader(url);
    			String basicClassName = "com.example.CalculatorBasic";
    			String advancedClassName = "com.example.CalculatorAdvanced";
    			try {
    				Class<?> clazz = ncl.loadClass(basicClassName);  // 加載一個版本的類
    				ICalculator calculator = (ICalculator) clazz.newInstance();  // 建立對象
    				System.out.println(calculator.getVersion());
    				clazz = ncl.loadClass(advancedClassName);  // 加載另外一個版本的類
    				calculator = (ICalculator) clazz.newInstance();
    				System.out.println(calculator.getVersion());
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    • 接口1 ICalculator.java
    package classloader;
    
    
    	public interface ICalculator extends Versioned {
    
    		String calculate(String expression);
    	}
    • 接口2 Versioned.java
    package classloader;
    
    	public interface Versioned {
    
    		String getVersion();
    	}
    • 網絡類加載器 NetworkClassLoader.java
    package classloader;
    
    	import java.io.ByteArrayOutputStream;
    	import java.io.InputStream;
    	import java.net.URL;
    
    	public class NetworkClassLoader extends ClassLoader {
    
    		private String rootUrl;
    
    		public NetworkClassLoader(String rootUrl) {
    			// 指定URL
    			this.rootUrl = rootUrl;
    		}
    
    		// 獲取類的字節碼
    		@Override
    		protected Class<?> findClass(String name) throws ClassNotFoundException {
    			byte[] classData = getClassData(name);
    			if (classData == null) {
    				throw new ClassNotFoundException();
    			} else {
    				return defineClass(name, classData, 0, classData.length);
    			}
    		}
    
    		private byte[] getClassData(String className) {
    			// 從網絡上讀取的類的字節
    			String path = classNameToPath(className);
    			System.out.println(path);
    			try {
    				URL url = new URL(path);
    				InputStream ins = url.openStream();
    				ByteArrayOutputStream baos = new ByteArrayOutputStream();
    				int bufferSize = 4096;
    				byte[] buffer = new byte[bufferSize];
    				int bytesNumRead = 0;
    				// 讀取類文件的字節
    				while ((bytesNumRead = ins.read(buffer)) != -1) {
    					baos.write(buffer, 0, bytesNumRead);
    				}
    				return baos.toByteArray();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			return null;
    		}
    
    		private String classNameToPath(String className) {
    			// 獲得類文件的URL
    			return rootUrl + "/"
    					+ className.replace('.', '/') + ".class";
    		}
    	}
  6. 測試代碼:啓動服務端的springboot,而後啓動客戶端,發現客戶端能夠加載服務端的代碼。

    http://10.0.21.168:8888/classes/com/example/CalculatorBasic.class
    	1.0
    	http://10.0.21.168:8888/classes/com/example/CalculatorAdvanced.class
    	2.0
相關文章
相關標籤/搜索