转--黑马程序员-Java学习笔记(类加载器&代理)

  1. 类加载器及其委托机制

    类加载器: 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。

    BootStrap不是Java类,其他的类加载器都是Java类

    ExtClassLoader,AppClassLoader都是Java类

package com.itheima.day2;

 

public class ClassLoaderTest {

    public static void main(String[] args)

    {

        System.out.println(

                ClassLoaderTest.class.getClassLoader().getClass()

                .getName()    

//打印结果为AppClassLoader,说明普通类的加载器一般都是它

        

                );

        System.out.println(

                System.class.getClassLoader()                

//打印结果为Null,可以看出System类的加载器其并不是一个Java类

                );

        ClassLoader loader = ClassLoaderTest.class.getClassLoader();

        while(loader != null)

        {            

            System.out.println(loader.getClass().getName());

            loader = loader.getParent();

        }

        System.out.println(loader);

        

        //打印结果为AppClassLoader,ExtClassLoader,null

        //说明ExtClassLoader为AppClassLoader的父类,BootStrap是ExtClassLoader的父类

    }

}

打印结果:

 

他们有各自的管辖范围,如下图:

委托加载机制:

AppClassLoader发起加载要求,会先委托ExtClassLoader加载,ExtClassLoader再委托BootStrap加载;

如果BootStrap找不到,再退给ExtClassLoader,ExtClassLoader找不到,会再退给AppClassLoader;

注意:AppClassLoader是发起者,因此他不会再找自己的子类去加载

类加载的顺序:BootStrap->ExtClassLoader->AppClassLoader

Java.lang.System能否自己写?答:通常不可以,写完之后,加载时还是会委托给BootStrap加载,这样永远找不到自己写的类加载器

自定义类加载器:

1)必须继承ClassLoader

2)loadClass方法继承父类,findClass重写

3)defineClass方法

模板方法设计方式

loaderClass():该类已经设计好流程,复写会破坏原来的流程,这个方法直接继承

findClass():    该类需要复写,因为找到对应的类之后需要做一些操作,比如解密等等;

操作完之后再将其通过defineClass转换成字节码返回

defineClass():     将class文件转换成字节码:

MyClassLoader范例:

package com.itheima.day2;

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

import java.security.acl.LastOwnerException;

 

public class MyClassLoader extends ClassLoader{

 

    public static void main(String[] args) throws Exception {

        // TODO Auto-generated method stub

        String srcPath = args[0];    //源文件路径

        String destDir = args[1]; //目的文件目录

        FileInputStream fis = new FileInputStream(srcPath);        

        //获取源文件的文件名

        String srcFileName = srcPath.substring(srcPath.lastIndexOf("\\"));        

        String destPath = destDir+srcFileName;//目标文件路径=目录+文件名        

        FileOutputStream fos = new FileOutputStream(destPath);        

        cypher(fis,fos);            //对类文件进行加密

        fis.close();

        fos.close();        

    }

    private static void cypher(InputStream is,OutputStream os) throws Exception

    {        

        int b = -1;

        while((b = is.read())!= -1)

        {

            os.write(b^0xff);    //通过每个字节异或操作,将Class类加密

        }

    }

    @Override

    public Class<?> loadClass(String name) throws ClassNotFoundException {        

        //loadClass直接继承父类,不需要复写!

        return super.loadClass(name);

    }

    private String classDir;

    @Override

    protected Class<?> findClass(String name) throws ClassNotFoundException {        

        try {            

            //获取给定的类

            //需要指定的目录以及类名

//如果name给的是带路径的Class,只取最后的.class

            String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1)+".class";

            FileInputStream fis = new FileInputStream(classFileName);    //读取源类文件

            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            cypher(fis,bos);        //对加密的源类文件解密

            fis.close();

            byte[] bytes = bos.toByteArray();

            // 将byte 数组转换为 Class 类的实例            

            //@SuppressWarnings("deprecation")            

            return defineClass(bytes, 0, bytes.length); //返回class的字节码                

        } catch (Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }

        return super.findClass(name);

    }

    public MyClassLoader(){

    }

    public MyClassLoader(String classDir){

        this.classDir = classDir;        //创建加载器的时候指定类存在的路径

    }    

}

MyClassLoader的调用:

package com.itheima.day2; 

import java.util.Date; 

public class ClassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException

    {                

        //System.out.println(new ClassLoaderAttatchment().toString());

        

        //用自定义的类加载器加载类进行解密

        Class clazz = newMyClassLoader("itheimalib").loadClass("com.itheima.day2.ClassLoaderAttatchment");

        

        //编译器不能加载该类(因为该类用自定义的类加载器加载了!),因此下面这种方法不行!

        //ClassLoaderAttatchment d1 = (ClassLoaderAttatchment)clazz.newInstance();

        

        //但是其父类编译器可以加载,因此用父类获取实例对象

        Date d1 = (Date)clazz.newInstance();

        System.out.println(d1);

    }

}

加密的简单类

package com.itheima.day2;

import java.util.Date;

 

public class ClassLoaderAttatchment extends Date {

    public String toString()

    {

        return "Hello,ItHeiMa";

    }

}

 

当父类AppClassLoader默认的文件夹下没有对应的class文件时:

通过自定义的MyClassLoader加载类可以对加密的类文件解密,结果如下:

当父类AppClassLoader默认的文件夹下有对应的class文件时,委托父类加载,不能对加密的类解密,因此会报错:

结果如下:

代理:

AOP:Aspect Oriented Program 面向方面的编程

交叉业务:一个功能穿插到系统的好多个模块当中

AOP的目标就是要使交叉业务模块化

代理是实现AOP功能的核心和关键技术

动态代理:

如果一个类没有接口,CGLIB库可以动态生成一个类的子类,子类也可以作为该类的代理

创建实现Collection接口的动态类,分析Proxy.getProxyClass方法的各个参数:

package com.itheima.day3; 

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.Collection; 

public class ProxyTest { 

    public static void main(String[] args) {        

            //创建Collection接口的动态类

            Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

            System.out.println(clazzProxy1.getName());

            //得到该类的构造函数列表

//以Constructor(para1,para2)的形式打印

            System.out.println("----------Proxy ConStructors Begin--------");

            Constructor[] constructors = clazzProxy1.getConstructors();

            for(Constructor constructor : constructors)

            {

                

                StringBuilder sb = new StringBuilder();

                sb.append(constructor.getName());

                

                sb.append('(');

                //得到构造函数的参数

                Class [] clazzParams = constructor.getParameterTypes();

                for(Class clazzParam : clazzParams)

                {

                    sb.append(clazzParam.getName());

                    sb.append(',');                    

                }

                if(clazzParams !=null && clazzParams.length !=0)

                    sb.deleteCharAt(sb.length()-1);                

                sb.append(')');

                System.out.println(sb.toString());

                

            }

            

            //得到该类的方法列表

//以Method(para1,para2)的形式打印

 

            System.out.println("----------Proxy Methods Begin--------");

            Method[] methods = clazzProxy1.getMethods();            

            for(Method method : methods)

            {                

                StringBuilder sb = new StringBuilder();

                sb.append(method.getName());

                

                sb.append('(');

                //得到构造函数的参数

                Class [] clazzParams = method.getParameterTypes();

                for(Class clazzParam : clazzParams)

                {

                    sb.append(clazzParam.getName());

                    sb.append(',');                    

                }

                if(clazzParams !=null && clazzParams.length !=0)

                    sb.deleteCharAt(sb.length()-1);                

                sb.append(')');

                System.out.println(sb.toString());                

            }

    }

}

打印结果如下:

可以看到该动态类只有一个带参数InvocationHandler的构造方法;

可以看到动态类的方法都是继承了Collection和Object

Proxy实例的创建:

{

        System.out.println("----------Create Instance Object--------");

            //因为clazzProxy1只有一个带InvocationHandler的构造方法,

            //因此下面这个方法无法创建实例对象!

            //Collection cl = (Collection)clazzProxy1.newInstance();

            

            //需要得到clazzProxy1的构造方法

            Constructor cons =clazzProxy1.getConstructor(InvocationHandler.class);

            //InvocationHandler是一个接口,因此需要通过自定义一个子类来创建实例对象

            class MyInvocationHandler1 implements InvocationHandler{

 

                @Override

                public Object invoke(Object proxy, Method method, Object[] args)

                        throws Throwable {

                    // TODO Auto-generated method stub

                    return null;

                }

                

            }            

            Collection proxy1 = (Collection)cons.newInstance(newMyInvocationHandler1());

            System.out.println(proxy1); //直接打印结果为null

            proxy1.clear();         //调用无返回值类型方法成功,说明已经成功创建了对象

 

            //因为代理调用方法都会调用Handler的invoke方法,返回了null值,而size()方法返回值是int类型

//proxy1.size();             //调用有返回值类型的方法失败

            

            //通过匿名内部类创建实例对象

            Collection proxy2= (Collection)cons.newInstance(newInvocationHandler(){

 

                @Override

                public Object invoke(Object proxy, Method method, Object[] args)

                        throws Throwable {

                    // TODO Auto-generated method stub

                    return null;

                }                

            });

            

            //上面是通过多个步骤来创建Collection动态类的实例对象

            //也可以一步创建动态类的实例对象

            Collection proxy3 = (Collection)Proxy.newProxyInstance(

                    Collection.class.getClassLoader(),                     

                    new Class[]{Collection.class},

                    new InvocationHandler(){

                        ArrayList target = new ArrayList();

                        @Override

                        public Object invoke(Object proxy, Method method, Object[] args)

                                throws Throwable {

                            // TODO Auto-generated method stub

                            long beginTime = System.currentTimeMillis();

                            Object retVal = method.invoke(target, args);

                            long endTime = System.currentTimeMillis();

                            System.out.println(method.getName()+" runtime = "+(endTime-beginTime));

                            return retVal;

                        }    

                    }

                    );

            

            proxy3.add("zxx");//调用一次就会找一次InvocationHandler的invoke方法

            proxy3.add("bxd");

            

            System.out.println(proxy3.size());

    }

运行结果:

可以看出add运行一次,就调用Handler的invoke一次;

调用一次size(),也调用了一次invoke

Handler中的invoke方法参数解析:

public Object invoke(Object proxy, Method method, Object[] args)

比如proxy3.add("zxx");

proxy对应proxy3;                                

add对应method.invoke;

args对应"zxx";

而Handler的invoke方法的返回值obj,就对应于proxy3.add()的返回值

也就是说,method.invoke(target,args)的返回值等于proxy3.add的返回值

从Object继承的方法,hashCode,toString,equals会委托给Handler,其他的方法不委托!

将切面的系统功能(比如说日志功能log)封装为一个对象,然后将该对象传递给Handler

Advice:契约,将切面的系统功能封装到契约中

{

System.out.println("----------Proxy4 Instance Object--------");

            

            //抽取target和Handler的invoke方法中的额外功能

            

            final ArrayList target = new ArrayList();

            

            //封装完后,只需要传递目标对象和所需添加的系统功能,即可得到一个代理

            Collection proxy4 = (Collection)getProxy(target,newMyAdvice());

            

            proxy4.add("zxx");//调用一次就会找一次InvocationHandler的invoke方法

            proxy4.add("bxd");

            proxy4.add("by");

            System.out.println(proxy4.size());

            

    }

 

    private static Object getProxy(final Object target,final Advice advice) {

        Object proxy4 = Proxy.newProxyInstance(

                target.getClass().getClassLoader(),                     

                //new Class[]{Collection.class},

                target.getClass().getInterfaces(), //跟目标的接口一致

                new InvocationHandler(){

                    //ArrayList target = new ArrayList();

                    @Override

                    public Object invoke(Object proxy, Method method, Object[] args)

                            throws Throwable {

                        // TODO Auto-generated method stub

/*                        

                        //封装到advice中的beforeMethod方法中

                        long beginTime = System.currentTimeMillis();

                        

                        //留在invoke方法中

                        Object retVal = method.invoke(target, args);

                        

                        //封装到advice中的afterMethod方法中

                        long endTime = System.currentTimeMillis();

                        System.out.println(method.getName()+" runtime = "+(endTime-beginTime));

*/                        

                        

                        advice.beforeMethod();

                        Object retVal = method.invoke(target, args);

                        advice.afterMethod(method);

                        return retVal;

                    }    

                }

                );

        return proxy4;

    }

简单的Advice接口:

package com.itheima.day3;

 

import java.lang.reflect.Method;

 

public interface Advice {//契约,你想添加的切面系统功能封装到其内

     void beforeMethod();

     void afterMethod(Method method);

}

实现Advice接口的子类:

package com.itheima.day3;

 

import java.lang.reflect.Method;

 

public class MyAdvice implements Advice {

    long beginTime = 0;

    @Override

    public void beforeMethod() {

        // TODO Auto-generated method stub

         beginTime = System.currentTimeMillis();

    }

 

    @Override

    public void afterMethod(Method method) {

        // TODO Auto-generated method stub

        long endTime = System.currentTimeMillis();

        System.out.println(method.getName()+" runtime = "+(endTime-beginTime));

 

    }

 

}

运行结果:

  

总结:

类加载器:将class文件的字节码装载到内存中

主要有BootStrap,ExtClassLoader,AppClassLoader三类加载器

BootStrap不是Java类,其他的类加载器都是Java类

AppClassLoader->ExtClassLoader->BootStrap,后者为前者的父类

委托机制:

子类加载器发起加载要求,会先委托自身的父类加载,一直委托到顶层父类加载器为止,如果父类加载器找不到加载内容,会再传递回子类加载;

注意,子类加载器不会再委托自身的子类加载器去加载!

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

自定义类加载器:

1)必须继承ClassLoader

2)loadClass方法继承父类,findClass方法重写

3)defineClass方法返回findClass处理过的class文件

 

 来源: http://blog.163.com/ljj1219@126/blog/static/143633241201461911327/

 

posted @ 2016-12-12 14:17  mayya  阅读(52)  评论(0)    收藏  举报