Java学习笔记十二

今日整理记录内容:
1、类加载器(classLoader)
2、代理类(proxy)

一、类加载器(classLoader)
1、类加载器的作用就是将硬盘中的.class文件中的二进制数据加载进内存并将二进制数据转换为字节码,判断某个.class文件是否被加载过是基于类加载器的,也就是说不同的类加载器可以加载同一个.class文件,这时候内存中就有同一个类的两份字节码。
2、BootStrap—>(Jar/lib/rt.jar) 注意: 这不是一个类,而是C语言编写的底层代码
ExtClassLoader—>(Jar/lib/ext/*.jar)
AppClassLoader—>(ClassPath指定的所有jar或目录)
3、类加载器的向上委托机制:当我们用AppClassLoader加载器加载某个类时,这时AppClassLoader会先委托父类ExtClassLoader加载器去加载,而ExtClassLoader又会委托其父BootStrap去加载,如果BootStrap能加载这个类,就让BootStrap这个加载器去加载,如果不能就让其子类ExtClassLoader去加载,同理是否让AppClassLoader加载。
4、如果A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
还可以直接调用ClassLoadeer.loadClass()方法来指定某个类加载器去加载某个类。
5、自定义类加载器的用处:
当class文件不在ClassPath路径下默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。
当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。
当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。
6、类加载器间的关系(并非指继承关系):
启动类加载器(BootStrap),由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
系统类加载器(AppClassLoader),**由Java语言实现,父类加载器为**ExtClassLoader
7、注意这里所指的父类并不是Java继承关系中的那种父子关系
8、自定义类加载器,父类加载器肯定为AppClassLoader。
解释:

自定义加载器类:
public class MyClassLoaderTest extends ClassLoader{
     private String rootDir;

        public MyClassLoaderTest(String rootDir) {
            this.rootDir = rootDir;
        }

        /**
         * 编写findClass方法的逻辑
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 获取类的class文件字节数组
            System.out.println("调用了findClass()");
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                //直接生成class对象
                return defineClass(name, classData, 0, classData.length);
            }
        }

        /**
         * 编写获取class文件并转换为字节码流的逻辑
         * @param className
         * @return
         */
        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;
        }

        /**
         * 类文件的完全路径
         * @param className
         * @return
         */
        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }

        public static void main(String[] args) throws IllegalAccessException, InstantiationException{
            MyClassLoaderTest myClazzLoader1 = new MyClassLoaderTest("F:/");
            MyClassLoaderTest myClazzLoader2 = new MyClassLoaderTest("F:/");
                try {
                    //直接调用findClass方法可以避免向上委托机制的发生和读取内存的过程;
                    Class clazz1 = myClazzLoader1.findClass("com.hbbfxy.ClassTest");
                    Class clazz2 = myClazzLoader2.findClass("com.hbbfxy.ClassTest");
                    //Class clazz3 = myClazzLoader1.findClass("com.hbbfxy.ClassTest"); //这里会报错,因为内存中已经有了myClazzLoader1对象的类对象,报告重复定义错误。
                    System.out.println("clazz1:"+clazz1.hashCode());
                    System.out.println("clazz2:"+clazz2.hashCode());
                    //调用loadClass方法会执行向上委托机制和读取内存过程
                    Class clazz4 = myClazzLoader1.loadClass("com.hbbfxy.ClassTest");
                    Class clazz5 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");
                    //Class clazz6 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");//这里不会报错,因为这是从内存中直接取出,没有向内存中存储。
                    System.out.println("clazz4:"+clazz4.hashCode());
                    System.out.println("clazz5:"+clazz5.hashCode());
                } catch (ClassNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

        }
}
输出结果:
调用了findClass()
调用了findClass()
clazz1:118352462
clazz2:1550089733
clazz4:118352462   //因为之前myClazzLoader1调用findClass()已经将myClazzLoader1类的类对象存储到内存,所以这里直接获取。
clazz5:1550089733 //因为之前myClazzLoader2调用findClass()已经将myClazzLoader2类的类对象存储到内存,所以这里直接获取。

上述自定义加载器类(摘自深入理解Java类加载器(ClassLoader))

测试类加载器之间的关系:
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        /*获得Bootstrap类加载器*/
        ClassLoader clazzLoader = System.class.getClassLoader();
        System.out.println("返回null就说明使用的是BootStrap类加载器:"+clazzLoader);

        /*查看自定义加载器的相关加载器之间的关系*/
        MyClassLoaderTest myClazzLoader = new MyClassLoaderTest(" ");
        System.out.println("加载自定义加载器类的加载器:"+MyClassLoaderTest.class.getClassLoader());
        System.out.println("自定义类加载器的父类:"+myClazzLoader.getParent());    
        System.out.println("自定义类加载器的父类的父类:"+myClazzLoader.getParent().getParent());
        System.out.println("自定义类加载器的父类的父类的父类:"+myClazzLoader.getParent().getParent().getParent());
    }
输出结果:
返回null就说明使用的是BootStrap类加载器:null
加载自定义加载器类的加载器:sun.misc.Launcher$AppClassLoader@73d16e93
自定义类加载器的父类:sun.misc.Launcher$AppClassLoader@73d16e93
自定义类加载器的父类的父类:sun.misc.Launcher$ExtClassLoader@6d06d69c
自定义类加载器的父类的父类的父类:null

二、代理Proxy
JVM动态生成的类所用的接口生成的类所代理的类用的接口一致,这样保证方法存在主要用于想在调用一个方法的前后自动完成某件事情。
解释:

动态生成代理类:
1、创建功能扩展方法的接口类
publicinterface Advice{
        void before();
        void after();
    }
2、创建代理框架
    /*生成代理框架*/
    private static Object getProxy(Object target, Advice advice){
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // TODO Auto-generated method stub
                advice.before();
                Object retVal = method.invoke(target, args);
                advice.after();
                return retVal;
            }
        });             
        return proxy;
    }
3、使用代理类
        Collection<String> list = (Collection)getProxy(new ArrayList(), new Advice(){
            @Override
            public void before() {
                // TODO Auto-generated method stub
                 System.out.println("执行方法前调用的代码。");
            }

            @Override
            public void after() {
                // TODO Auto-generated method stub
                 System.out.println("执行方法后调用的代码。");
            }

        });
        list.add("1");
输出结果:
执行方法前调用的代码。
执行方法后调用的代码。

这里我们需要明白动态生成代理类需要类加载器和要实现的接口,类加载器和要实现的接口和被代理的类相同。
Object invoke(Object proxy, Method method, Object[] args)三个参数:
proxy: 动态生成的代理类;
method:调用的方法;
args:方法中的参数。
注意:被代理的类一定要有接口。

学习心得:观看资料–》思考问题–》实践证明–》整理记录 = 有思想的技术大牛!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值