zoukankan      html  css  js  c++  java
  • 自定义类加载器

    为什么要自己定义类加载器

    为什么我们要自定义类加载器?因为虽然Java中给用户提供了很多类加载器,但是和实际使用比起来,功能还是匮乏。举一个例子来说吧,主流的Java Web服务器,比如Tomcat,都实现了自定义的类加载器(一般都不止一个)。因为一个功能健全的Web服务器,要解决如下几个问题:

    1、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。

    2、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享。这个需求也很常见,比如相同的Spring类库10个应用程序在用,不可能分别存放在各个应用程序的隔离目录中

    3、支持热替换,我们知道JSP文件最终要编译成.class文件才能由虚拟机执行,但JSP文件由于其纯文本存储特性,运行时修改的概率远远大于第三方类库或自身.class文件,而且JSP这种网页应用也把修改后无须重启作为一个很大的优势看待

    由于存在上述问题,因此Java提供给用户使用的ClassLoader就无法满足需求了。Tomcat服务器就有自己的ClassLoader架构,当然,还是以双亲委派模型为基础的:

    jdk中classloader

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    自定义类加载器

    从上面对于java.lang.ClassLoader的loadClass(String name, boolean resolve)方法的解析来看,可以得出以下2个结论:

    1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可

    2、如果想打破双亲委派模型,那么就重写整个loadClass方法

    当然,我们自定义的ClassLoader不想打破双亲委派模型,所以自定义的ClassLoader继承自java.lang.ClassLoader并且只重写findClass方法。

     第一步 写一个实体类

    public class Person {
    	private String name;
    
    	public Person() {
    
    	}
    
    	public Person(String name) {
    		this.name = name;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String toString() {
    		return "I am a person, my name is " + name;
    	}
    }
    

     第二步 自定义类加载器,里面是io和nio的东西,defineClass方法可以把二进制流字节组成文件转换为一个java.lang.Class(只要二进制流符合Class文件规范)

    package algorithm;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.nio.ByteBuffer;
    import java.nio.channels.Channels;
    import java.nio.channels.FileChannel;
    import java.nio.channels.WritableByteChannel;
    
    public class MyClassLoader extends ClassLoader {
    
    	public MyClassLoader() {
    	}
    	public MyClassLoader(ClassLoader parent) {
    		super(parent);
    	}
    	protected Class<?> findClass(String name) throws ClassNotFoundException {
    		File file = getClassFile(name);
    		try {
    			byte[] bytes = getClassBytes(file);
    			Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
    			return c;
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return super.findClass(name);
    	}
    	
    	private File getClassFile(String name) {
    		File file = new File("/home/tp/Person.class");
    		return file;
    	}
    	
    	private byte[] getClassBytes(File file) throws Exception {
    		// 这里要读入.class的字节,因此要使用字节流
    		FileInputStream fis = new FileInputStream(file);
    		FileChannel fc = fis.getChannel();
    		ByteArrayOutputStream baos = new ByteArrayOutputStream();
    		WritableByteChannel wbc = Channels.newChannel(baos);
    		ByteBuffer by = ByteBuffer.allocate(1024);
    		while (true) {
    			int i = fc.read(by);
    			if (i == 0 || i == -1)
    				break;
    			by.flip();
    			wbc.write(by);
    			by.clear();
    		}
    		fis.close();
    		return baos.toByteArray();
    	}
    
    }
    

     第三步 Class.forName有三个参数的重载方法,可以指定类加载器,平时使用的Class.forName使用的是Application ClassLoader

    public static void main(String[] args) throws Exception {
    		MyClassLoader mcl = new MyClassLoader();
    		Class<?> c1 = Class.forName("algorithm.Person", true, mcl);//这里也可以用Class<?> c1 = mcl.loadClass("algorithm.Person");
    		Object obj = c1.newInstance();
    		System.out.println(obj);
    		System.out.println(obj.getClass().getClassLoader());
    	}
    

     结果是

    I am a person, my name is null
    sun.misc.Launcher$AppClassLoader@5a74b10b
    

     这是因为MyEclipse会把Person.java文件编译到classpath路径下面去,那么自然会用Application ClassLoader来加载了。可以把classpath下的Person.class删掉。

    1、删除CLASSPATH下的Person.class,CLASSPATH下没有Person.class,Application ClassLoader就把这个.class文件交给下一级用户自定义ClassLoader去加载了

    2、TestMyClassLoader类的第5行这么写"MyClassLoader mcl = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());", 即把自定义ClassLoader的父加载器设置为Extension ClassLoader,这样父加载器加载不到Person.class,就交由子加载器MyClassLoader来加载了

    相当于把Application ClassLoader给替换了。

    如果把第三行修改为Class<?> c1 = Class.forName("algorithm.Person");就会报ClassNotFoundException,因为给出的几种类加载器范围内都没有该class,如果不指定使用怎么样的类加载器,就会导致找不到class。

    ClassLoader.getResourceAsStream(String name)方法作用classloader的getResourceAsStream(String name)方法用来读入指定的资源的输入流,并且把该输入流给用户使用,资源可以是图像,声音,.properties文件等,资源名称是以/分隔的标识资源名称的路径名称。

    class下也有这个方法,区别在于class的这个方法是,参数不以/开头,默认从此类的.class文件所在package下取资源,否则从classpath下取。ClassLoader下默认从classpath开始取,不可以以/开头。

    class调用的还是ClassLoader里的方法。

    .class和getClass()的区别

    两者都可以获得唯一的class对象。但是还是存在一定的区别

    1、.class用于类名,getClass()是一个final native的方法,在类实例中调用。(调用方式不一样)

    2、.class在编译期间就确定了一个类的java.lang.Class对象,但是getClass()方法在运行期间确定一个类实例的java.lang.Class对象

  • 相关阅读:
    Java中测试对象的等价性
    Python文件方法
    Python在windows下的安装与配置
    ubuntu日志清理
    hiho48 : 欧拉路·一
    ADO.NET异步操作测试
    c# 生成二维码
    PowerCmd 2.2 注册码
    IE跨Iframe时Session丢失问题
    MongoDB3.0 创建用户
  • 原文地址:https://www.cnblogs.com/tp123/p/6398890.html
Copyright © 2011-2022 走看看