java进阶(六):Java类加载器

来源:互联网 发布:警察局里有编程的吗 编辑:程序博客网 时间:2024/05/23 14:52

一、java默认的三大类加载器


1、java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类。

三个类加载器分别为:

1) BootStrap:最基本的classLoader,本地语言编写(c++),不需要被加载,直接被嵌套在java虚拟机中。使用BootStrap加载JDK的核心class——(JRE/lib/rt.jar),一般拿不到BootStrap的名字,是空值。
2) ExtClassLoader: 扩展classloader,负责加载JDK的扩展类(jre/lib/ext下面的类-JRE/lib/ext/*.jar)
3) AppClassLoader:负责我们自己定义的类,也叫系统classLoader。(ClassPath 指定的所有jar或目录)。


2、类加载器也是java类,因为加载java类的类加载器本身也要被加载,显然必须有一个类加载器不是java类。这正是BootStrap。


3、java虚拟机中的所有类装载器采用父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要对其指定一个父级类装载器对象或者默认采用系统类装载器为其父类级加载。

4、类的继承树,如下:



package com.onhance.classload;/** * JDK内置的ClassLoader *  * @author SAM-SHO *  * 1. bootstrap class loader 最基本的classloader,本地语言编写(c++),使用他加载JDK的核心class——rt.jar,一般拿不到名字,是空值 * 2. extesion class loader 扩展classloader,负责加载JDK的扩展类(jre/lib/ext下面的类) * 3. application class loader 负责我们自己定义的类,也叫系统class loader *  *  */public class TestJDKClassLoader {public static void main(String[] args){//1. bootstrap class loader打印不出来;System.out.println(String.class.getClassLoader());//null,拿不到BootStrap的名字//2. extesion class loader 负责扩展包中的类(jre/lib/ext下面的类)System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());//sun.misc.Launcher$ExtClassLoader//3. application class loader 负责自己写的类,也称为系统classLoaderSystem.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());//sun.misc.Launcher$AppClassLoaderSystem.out.println(ClassLoader.getSystemClassLoader());//sun.misc.Launcher$AppClassLoader@19821fSystem.out.println("-----------------------------------------------------------------------");/* * classLoader之间的关系,不是继承 * 加载过程:先找上一层(parent,但不是继承)classloader,如果已经被加载了,不会被再加载 * 顺序应该为:AppClassLoader---> ExtClassLoader--->BootStrap */ClassLoader loader = TestJDKClassLoader.class.getClassLoader();while(loader != null) {System.out.println("=======" +loader.getClass().getName());loader = loader.getParent();}System.out.println("-------" + loader);}}/*【输出】nullsun.misc.Launcher$ExtClassLoadersun.misc.Launcher$AppClassLoadersun.misc.Launcher$AppClassLoader@19821f-----------------------------------------------------------------------=======sun.misc.Launcher$AppClassLoader=======sun.misc.Launcher$ExtClassLoader-------null*/

二、类加载器的委托机制

简单例子:

System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName()//AppClassLoader);
以上代码打印出来为AppClassLoader。

现在我们把ClassLoaderTest.java打包,放入dk1.6.0_10\jre\lib\ext\ClassLoaderTest.jar目录,即本地JDK安装目录的ext目录下。重新运行,则输出ExtClassLoader。

1、当JAVA虚拟机要加载一个类时,到底是哪个类加载器去加载呢?

1)、首先当前线程的类加载器去加载线程中的第一个类。

2)、如果类A中引用了类B,JAVA虚拟机将使用加载类A的类加载器来加载类B。

3)、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器加载某个类。


2、每个类加载器加载类时,又先委托给上级类加载器。

1)、当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不会再去找类发起者的儿子,因为没有getChild()方法。即使有,那么多儿子也不知道具体找哪一个。

2)、委托机制的顺序:先BootStrapClassLoader-->后ExtClassLoader-->再AppClassLoader。

3)、解释上面的例子:

A、试验1:当前线程的类加载器(假如为AppClassLoader)去加载ClassLoaderTest时,会先委托给上级的类记载ExtClassLoader,然后又会委托给顶层BootStrap加载器,然后开始加载。BootStrap没有加载到,然后轮到ExtClassLoader加载,第一次试验的时候没有加载到,就会回到发起者类加载器AppClassLoader,这样加载到了。输出。如果找不到就会报错。

B、试验2:因为放入dk1.6.0_10\jre\lib\ext\后,ExtClassLoader加载器就加载到了。所以输出。


三、自定义类加载器。

问题:自己可以写System类吗?

回答:通常是不可以的。因为类加载器的委托机制,System总是在rt.jar中被BootStrapClassLoader加载,自己写的              System类在classpath中,永远不会被加载;但是我可以自己写个类加载器,加载自己的固定的类或者包。这              样就可以被加载了。

1、自定义classLoader必须继承抽象类ClassLoader,


2、覆盖父类方法的2种情况:

1)、覆盖loadClass()方法:loadClass()内部会去找父类委托,然后再找自己,调用findClass()方法。

所以,写自定义的类加载器时,如果覆盖了loadClass()方法,这样就打破了原来的委托机制(不然你难道还重写委托机制?),不用再去找父类树,都自己来,打破委托机制。
2)、覆盖findClass()方法:先让父类树处理,然后自己干,继承了委托机制


3、这是模板方法设计模式:

1)子类1(自己干)和子类2(自己干),他们自己的方法(自己干的代码)不一样,但是子类1和子类2找父类机制的流程是一致的。就可以放在父类中编写,这样所有子类都继承父类的方法。如果父类中有不能实现的方法,则定义为静态方法,让子类实现。 

2)这样,整个流程,父类已经定义好了,但是流程的一些细节父类没办法确认完成,就留给子类实现去完成。


4、类加载器中有个固定方法definClass()实现把得到的class文件转换成字节码。父类--> loadClass/findClass/得到class文件的转换成字节码-->definClass()。

package com.onhance.classload;import java.util.Date;/** *  * ClassLoaderAttachment.java * * @title 被加载的类  * @description * @author SAM-SHO  * @Date 2014-9-15 */public class ClassLoaderAttachment extends Date {public String toString(){return "hello,自定义类加载器";} }
package com.onhance.classload;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;/** *  * MyClassLoader.java * @title 自定义类加载器   * @description   * @author SAM-SHO  * @Date 2014-8-10 */public class MyClassLoader extends ClassLoader{private String classDir;public MyClassLoader(){}public MyClassLoader(String classDir){this.classDir = classDir;} /* 自定义类加载器的方式有2种: * 1-覆盖loadClass()方法,不用找父类树,都自己来,打破委托机制。 * 2-覆盖findCladd()方法,先让父类树处理,继承了委托机制。 *  * 模板方法设计模式 * 父类--> loadClass/findClass/得到class文件的转换成字节码-->definClass() *     子类1(自己干的代码不一样) * 子类2(自己干的代码不一样) */@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String classFileName = classDir + "\\"  + name.substring(name.lastIndexOf('.')+1) + ".class";try {FileInputStream fis = new FileInputStream(classFileName);ByteArrayOutputStream  bos = new ByteArrayOutputStream();cypher(fis,bos);fis.close();System.out.println("aaa");byte[] bytes = bos.toByteArray();return defineClass(bytes, 0, bytes.length);} catch (Exception e) {e.printStackTrace();}return null;}/** * 加密解密类 * @param ips * @param ops */private static void  cypher(InputStream ips, OutputStream ops) {int b = -1;try {while((b = ips.read()) != -1) {ops.write(b ^ 0xff);//异或处理,原来是1则为0,原来是0则为1}} catch (IOException e) {e.printStackTrace();}}/** * 测试 * @param args */public static void main(String[] args) throws Exception {//String srcPath = "D:\\apache-worksapce\\MyEclipseNewWork2014\\JavaOnhance\\bin\\com\\onhance\\classload\\ClassLoaderAttachment.class";//String destPath = "F:\\MyClassLoader\\ClassLoaderAttachment.class";String srcPath = args[0];String destDir = args[1];FileInputStream fis = new FileInputStream(srcPath);String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);String destPath = destDir + "\\" + destFileName;FileOutputStream fos = new FileOutputStream(destPath);cypher(fis,fos);fis.close();fos.close();}}
以上代码在运行的死后配置tomcat的运行参数args[0]和args[1]即可。

//测试//System.out.println(new ClassLoaderAttachment().toString());System.out.println("xxx2");Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");//自定义加载Class clazz = new MyClassLoader("itcastlib").loadClass("com.onhance.classload.ClassLoaderAttachment");//父类加载,报错Date d1 =  (Date)clazz.newInstance();System.out.println(d1);

四、Tomcat的类加载器。

1、写一个简单地servlet:

package com.Onhance.classLoader;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** *  * MyServletForClassLoader.java * * @title Tomcat的类加载器  * @description * @author SAM-SHO  * @Date 2014-9-17 */public class MyServletForClassLoader extends HttpServlet {private static final long serialVersionUID = 1L;@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html");PrintWriter out = response.getWriter();out.println("----"+ClassLoader.getSystemClassLoader().getClass().getName());//系统加载器//循环遍历加载器ClassLoader loader = this.getClass().getClassLoader();while(loader!=null){out.println(loader.getClass().getName()+ "<br/>");loader = loader.getParent();}out.println(loader);out.flush();out.close();}}
【输出】----sun.misc.Launcher$AppClassLoader org.apache.catalina.loader.WebappClassLoaderorg.apache.catalina.loader.StandardClassLoadersun.misc.Launcher$AppClassLoadersun.misc.Launcher$ExtClassLoadernull

2、依次输出的类加载器为:WebappClassLoader--StandardClassLoader--AppClassLoader--ExtClassLoader--null

3、类似前面的试验,把这个MyServletForClassLoader打包放到运行的JDK的dk1.6.0_10\jre\lib\ext\目录,即本地JDK安装目录的ext目录下。重新运行,则报错了:HttpServlet找不到。原因在于ExtClassLoader找到了MyServletForClassLoader,然后看到继承了HttpServlet就会去找HttpServlet,然后没找到。

4、如果类A中引用了类B,JAVA虚拟机将使用加载类A的类加载器来加载类B。所以上面的例子:ExtClassLoader找到了MyServletForClassLoader,就会继续去找HttpServlet,找不到也不会给儿子,因为它是发起者。如下图:





5、解决这个问题:把HttpServlet的jar包,servlet-api.jar放到JDK的dk1.6.0_10\jre\lib\ext\目录,即和MyServletForClassLoader在同一个ext目录下,再运行显示:ExtClassLoader。


五、利用ClassLoader读取配置文件。

package com.onhance.reflection;import java.io.FileInputStream;import java.io.InputStream;import java.util.Collection;import java.util.Properties;/** *  * ReflectTest3.java * @title 框架反射 * @description 读取配置文件 * @author SAM-SHO  * @Date 2014-7-19 */public class ReflectTest3 {/** * @param args * @throws Exception */public static void main(String[] args) throws Exception {// 相对路径(相对于工程),但是,配置文件必须与src同一级别InputStream ips = new FileInputStream("config.properties");Properties properties = new Properties();properties.load(ips);ips.close();String className = properties.getProperty("className");Collection <Object>collection =  (Collection<Object>) Class.forName(className).newInstance();ReflectPoint pt1 = new ReflectPoint(3, 3);ReflectPoint pt2 = new ReflectPoint(5, 5);ReflectPoint pt3 = new ReflectPoint(7, 7);ReflectPoint pt4 = new ReflectPoint(7, 7);//在ReflectPoint类重写hashcode与equals方法前为 : falseSystem.out.println(pt3.equals(pt4));collection.add(pt1);collection.add(pt2);collection.add(pt3);collection.add(pt4);collection.add(pt1);System.out.println("ArrayList的长度 : "+collection.size());//5//// 但是用类加载器又不一样 绝对路径 classpath环境下的那些文件。注意:直接使用类加载器时,不能以 /打头。InputStream ips2 = ReflectTest3.class.getClassLoader().getResourceAsStream("com/onhance/reflection/config2.properties");//和src同级别,新建一个名为comfig的Source Folder. 日常项目中最常用InputStream ips99 = ReflectTest3.class.getClassLoader().getResourceAsStream("config4.properties");////这时候相对路径,就是针对当前的类,然后跟它在同一个文件下的配置文件,spring文件经常用这种InputStream ips3 = ReflectTest3.class.getResourceAsStream("config2.properties");}}

六、实战中的代码。

以前的一个项目,有一个读取的公用类。如下:

package com.taiping.common;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Properties;import java.util.Set;import org.apache.commons.lang3.ArrayUtils;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.Resource;import org.springframework.core.io.ResourceLoader;import org.springframework.util.DefaultPropertiesPersister;import org.springframework.util.PropertiesPersister;/** * 系统属性工具类 * *  */public class PropertyFileUtil {    private static final String DEFAULT_ENCODING = "UTF-8";    private static Logger logger = LoggerFactory.getLogger(PropertyFileUtil.class);    private static Properties properties;    private static PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();    private static ResourceLoader resourceLoader = new DefaultResourceLoader();    private static Properties activePropertyFiles = null;    private static String PROFILE_ID = StringUtils.EMPTY;    public static boolean INITIALIZED = false; // 是否已初始化    /**     * 初始化读取配置文件,读取的文件列表位于classpath下面的application-files.properties<br/>     * <p/>     * 多个配置文件会用最后面的覆盖相同属性值     *     * @throws IOException 读取属性文件时     */    public static void init() throws IOException {        String fileNames = "application-files.properties";        PROFILE_ID = StringUtils.EMPTY;        innerInit(fileNames);        activePropertyFiles(fileNames);        INITIALIZED = true;    }    /**     * 初始化读取配置文件,读取的文件列表位于classpath下面的application-[type]-files.properties<br/>     * <p/>     * 多个配置文件会用最后面的覆盖相同属性值     *     * @param profile 配置文件类型,application-[profile]-files.properties     * @throws IOException 读取属性文件时     */    public static void init(String profile) throws IOException {        if (StringUtils.isBlank(profile)) {            init();        } else {            PROFILE_ID = profile;            String fileNames = "application-" + profile + "-files.properties";            innerInit(fileNames);        }        INITIALIZED = true;    }    /**     * 内部处理     *     * @param fileName     * @throws IOException     */    private static void innerInit(String fileName) throws IOException {        String[] propFiles = activePropertyFiles(fileName);        logger.debug("读取属性文件:{}", ArrayUtils.toString(propFiles));        properties = loadProperties(propFiles);        Set<Object> keySet = properties.keySet();        for (Object key : keySet) {            logger.debug("property: {}, value: {}", key, properties.getProperty(key.toString()));        }    }    /**     * 获取读取的资源文件列表     *     * @param fileName     * @return     * @throws IOException     */    private static String[] activePropertyFiles(String fileName) throws IOException {        logger.info("读取" + fileName);        ClassLoader loader = Thread.currentThread().getContextClassLoader();//利用ClassLoader        InputStream resourceAsStream = loader.getResourceAsStream(fileName);        // 默认的Properties实现使用HashMap算法,为了保持原有顺序使用有序Map        activePropertyFiles = new LinkedProperties();        activePropertyFiles.load(resourceAsStream);        Set<Object> fileKeySet = activePropertyFiles.keySet();        String[] propFiles = new String[fileKeySet.size()];        List<Object> fileList = new ArrayList<Object>();        fileList.addAll(activePropertyFiles.keySet());        for (int i = 0; i < propFiles.length; i++) {            String fileKey = fileList.get(i).toString();            propFiles[i] = activePropertyFiles.getProperty(fileKey);        }        return propFiles;    }    /**     * 载入多个properties文件, 相同的属性在最后载入的文件中的值将会覆盖之前的载入.     * 文件路径使用Spring Resource格式, 文件编码使用UTF-8.     *     * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer     */    public static Properties loadProperties(String... resourcesPaths) throws IOException {        Properties props = new Properties();        for (String location : resourcesPaths) {            logger.debug("Loading properties file from:" + location);            InputStream is = null;            try {                Resource resource = resourceLoader.getResource(location);                is = resource.getInputStream();                propertiesPersister.load(props, new InputStreamReader(is, DEFAULT_ENCODING));            } catch (IOException ex) {                logger.info("Could not load properties from classpath:" + location + ": " + ex.getMessage());            } finally {                if (is != null) {                    is.close();                }            }        }        return props;    }    /**     * 获取所有的key     *     * @return     */    public static Set<String> getKeys() {        return properties.stringPropertyNames();    }    /**     * 获取键值对Map     *     * @return     */    public static Map<String, String> getKeyValueMap() {        Set<String> keys = getKeys();        Map<String, String> values = new HashMap<String, String>();        for (String key : keys) {            values.put(key, get(key));        }        return values;    }    /**     * 获取属性值     *     * @param key 键     * @return 值     */    public static String get(String key) {        String propertyValue = properties.getProperty(key);        logger.debug("获取属性:{},值:{}", key, propertyValue);        return propertyValue;    }    /**     * 获取属性值     *     * @param key          键     * @param defaultValue 默认值     * @return 值     */    public static String get(String key, String defaultValue) {        String propertyValue = properties.getProperty(key);        String value = StringUtils.defaultString(propertyValue, defaultValue);        logger.debug("获取属性:{},值:{}", key, value);        return value;    }    /**     * 向内存添加属性     *     * @param key   键     * @param value 值     */    public static void add(String key, String value) {        properties.put(key, value);        logger.debug("通过方法添加属性到内存:{},值:{}", key, value);    }    public static Properties getActivePropertyFiles() {        return activePropertyFiles;    }    public static String getProfile() {        return PROFILE_ID;    }}

0 0
原创粉丝点击