自定义文件类加载器和网络类加载器

参考: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
相关文章
相关标签/搜索