Java基础---内省、注解、类加载器

第一讲     内省引出JavaBean

一、内省

        1、内省对应的英文单词为IntroSpector,英文意思是检查、视察、体检之意,对于程序即对内部进行检查,了解更多的底层细节。

        2、内省的作用:主要针对JavaBean进行操作。

 

二、JavaBean

1、简述:

        1)JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则。

        2)它是一种特殊的Java类,其中的方法符合特殊的规则。只要一个类中含有get或is和set打头的方法,就可以将其当做JavaBean使用。

        3)字段和属性:

             字段就是我们定义的一些成员变量,如private String name;等

             JavaBean的属性是根据其中的setter和getter方法来确定的,而不是依据其中的变量,如方法名为setId,则中文意思是设置Id,getId也是如此;去掉set或者get前缀,剩余部分就是属性名称。如果剩余部分的第二个字母小写,则把剩余部分改为小写。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。

        总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

2、作用:

        如果要在两个模板之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO),这些信息在类中用私有字段来储存,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。

3、JavaBean的好处:

        一个符合JavaBean特点的类当做普通类一样可以使用,但是把它当做JavaBean类用会带来一些额外的好处:

        1)在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!

        2)JDK中提供了对JavaBean进行操作的API,这套API称为内省,若要自己通过getX的方式来访问私有x,可用内省这套API,操作JavaBean要比使用普通的方式更方便。

示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. import java.beans.IntrospectionException;  
  4. import java.beans.Introspector;  
  5. import java.beans.PropertyDescriptor;  
  6. import java.lang.reflect.InvocationTargetException;  
  7. import java.lang.reflect.Method;  
  8.   
  9. public class IntroSpectorDemo {  
  10.     public static void main(String[] args) throws Exception {  
  11.         HashCodeTest hct=new HashCodeTest(2,3);  
  12.         String propertyName="x";  
  13.         //"x"-->"X"-->"getX"-->MethodGetX-->  
  14.           
  15.         //用内省的方式  
  16.         //获取并getX方法  
  17.         Object retval = getProperty1(hct, propertyName);  
  18.         System.out.println(retval);  
  19.           
  20.         Object value=5;  
  21.         //获取并调用setX方法  
  22.         setProperty(hct, propertyName, value);  
  23.           
  24.         System.out.println(hct.getX());   
  25.     }  
  26.     //获取并调用setX方法  
  27.     private static void setProperty(HashCodeTest hct, String propertyName,  
  28.             Object value) throws IntrospectionException,  
  29.             IllegalAccessException, InvocationTargetException {  
  30.         PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联  
  31.         Method methodSetX=pd.getWriteMethod();//获取JavaBean类中的setX方法  
  32.         methodSetX.invoke(hct,value);//调用setX方法  
  33.     }  
  34.     //获取并getX方法  
  35.     private static Object getProperty1(HashCodeTest hct, String propertyName)  
  36.             throws IntrospectionException, IllegalAccessException,  
  37.             InvocationTargetException {  
  38.         PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联  
  39.         Method methodGetX=pd.getReadMethod();//获取JavaBean类中的getX方法  
  40.         Object retval=methodGetX.invoke(hct);//调用getX方法  
  41.         return retval;  
  42.     }  
  43. }  

Eclipse小技巧:

        选择要变为方法的代码,右键——>Refactor——>Extract Method,然后就会生成一个方法了。

 

三、对JavaBean的复杂内省操作

        1、在IntroSpector类中有getBeanInfo(Class cls)的方法,通过此方法获取BeanInfo实例。参数是相应对象的字节码,即Class对象。

        2BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的JavaBean类中的属性信息,返回一个PropertyDescriptor[]

        3、在通过遍历的形式,获取与想要的那个属性信息。

如:获取并调用getX方法

[java]  view plain copy
  1. //第二种较复杂的获取并调用JavaBean中的getX方法  
  2. private static Object getProperty2(HashCodeTest hct, String propertyName)  
  3.         throws IntrospectionException, IllegalAccessException,  
  4.         InvocationTargetException {  
  5.     BeanInfo beanInfo=Introspector.getBeanInfo(hct.getClass());//创建对象关联  
  6.     PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();//获取所有的属性描述  
  7.     Object retval=null;  
  8.           
  9.     //遍历  
  10.     for (PropertyDescriptor pd : pds) {  
  11.         //如果属性跟参数的属性相等,就获取它的getX方法  
  12.         if (pd.getName().equals(propertyName)) {  
  13.             Method methodGetX=pd.getReadMethod();//获取getX方法  
  14.             retval=methodGetX.invoke(hct);  
  15.             break;  
  16.         }  
  17.     }  
  18.     return retval;    
  19. }  

 

四、BeanUtils工具包

1、BeanUtils等工具包都是由阿帕奇提供的,为了便于开发。

2、BeanUtils可以将8种基本数据类型进行自动的转换,因此对于非基本数据类型,就需要注册转换器Converter,这就需要ConverUtils包。

3、好处:

        1)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操作。

        2)支持属性的级联操作,即支持属性链。如可以设置:人的脑袋上的眼睛的眼珠的颜色。这种级联属性的属性连如果自己用反射,那就很困难了,通过这个工具包就可以轻松调用。

4、可以和Map集合进行相互转换:可将属性信息通过键值对的形式作为Map集合存储(通过static java.util.Mapdescribe(java.lang.Object bean)的方法)。也可以将Map集合转换为JavaBean中的属性信息(通过static void populate(java.lang.Objectbean, java.util.Map properties)的方法)。

注:要正常使用BeanUtils工具,还要将Apache公司的logging(日志)的jar包也添加进Build Path。

Eclipse小知识:

        在工程中导入工具jar包。

两种方式:

        1,右键项目--选择Properties---Java Build Path--选择Liberiers标签。AddExternal Jars--选择要导入的jar包。即可。

        这样做有个问题就是如果jar路径发生变化。项目就不能使用到这个jar包。

        2,在项目中建立一个lib目录,专门用于存放项目所使用到的jar工具包。将要使用到jar包复制粘贴进来,并在jar上点右键--选择Builder Path---Add to BiuldPath,即可。这时jar包中的对象,就可以使用了。

        这样做的好处:项目移动,jar随项目移动。

示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. import org.apache.commons.beanutils.BeanUtils;  
  4. import org.apache.commons.beanutils.PropertyUtils;  
  5.   
  6. public class IntroSpectorDemo {  
  7.     public static void main(String[] args) throws Exception {  
  8.         HashCodeTest hct=new HashCodeTest(2,3);  
  9.         String propertyName="x";  
  10.         //"x"-->"X"-->"getX"-->MethodGetX-->  
  11.           
  12.         //用BeanUtils工具包的方法  
  13.         System.out.println(BeanUtils.getProperty(hct, propertyName));//get  
  14.         BeanUtils.setProperty(hct, propertyName, "9");//set  
  15.           
  16.         System.out.println(hct.getX());   
  17.           
  18.         //对于JavaBean中的属性是对象的操作  
  19.         BeanUtils.setProperty(hct, "birthday.time""10");//set  
  20.         System.out.println(BeanUtils.getProperty(hct, "birthday.time"));//get  
  21.     }  
  22. }  

5BeanUtils工具包中还有一个工具类PropertyUtils,用法跟BeanUtils一样。区别:

        1BeanUtils会对JavaBean的属性的类型进行转换,如属性本身是integer,会转换为String

        2PropertyUtils以属性本身的类型进行操作。

如:

[java]  view plain copy
  1. //用BeanUtils工具包的工具类BeanUtils方法  
  2.     BeanUtils.setProperty(hct, propertyName, "9");//set,9是String  
  3.     System.out.println(BeanUtils.getProperty(hct, propertyName));//get  
  4.     System.out.println(BeanUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.String  
  5.           
  6. //BeanUtils工具包中的PropertyUtils的操作  
  7.     PropertyUtils.setProperty(hct, propertyName, 9);//9是Integer  
  8.     System.out.println(PropertyUtils.getProperty(hct, propertyName));  
  9. System.out.println(PropertyUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.Integer  

 

第二讲     注解——JDK.1.5新特性

一、概述

        1、注解(Annotation)相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。以后,javac编译器、开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。

        2、标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

        3、在java.lang包中提供了最基本的注解(annotation)

        4、格式:@注解类名。

             如果只有一个value名称的属性或其他属性缺省,则可@注解名(属性值);

             如果有多个或不缺省或者需重新赋值,则@注解名(属性名=”属性值,…)。

 

二、java中三种最基本的注解

1、如@SuppressWarning(deprecation):表示压制过时警告;或者说不要警告过时提示了

              SupressWarning是告知编译器或开发工具等不需要再提示指定的警告了;

             “deprecation”是警告的信息,即过时警告。

2@Deprecated:表示告知调用者,该成员函数、字段等已经过时,不再推荐使用。

        源代码标记@Deprecated是在JDK1.5中作为内置的annotation引入的,用于表明类(class)、方法(method)、字段(field)已经不再推荐使用,并且在以后的JDK版本中可能将其删除,编译器在默认情况下检测到有此标记的时候会提示警告信息。

        例如:假定之前的某个类升级了,其中的某个方法已经过时了,不能够将过时的方法删除,因为可能会影响到之前调用此这个方法的某些程序,这时就可以通过在方法上加这个注解来标记。

3@Override:表示下面的方法是在覆盖(父类方法),如果不存在覆盖,就会报错。

        加上此注解,可对类中的方法判断是否是要覆盖的父类的方法。典型的例子即在类中覆盖equals(Object obj)方法时,其中的参数类型必须是Object,才能被覆盖;若不是,则不存在覆盖。此时如果加上了此注解就会提示警告。

 

三、注解的应用结构图

      

        注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。

 

四、自定义注解及其应用

1、定义格式:@interface名称{statement}

      如:

           最简单的注解类:public @interface MyAnnotation{}

2、元注解(注解的注解)

        即在定义注解类的时候加注解。如两个常用于元注解的注解:RetentionTarget

        1Retetion:用于说明注解保留在哪个阶段(即注解的生命周期)。

        一个注解的生命周期包含:java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码

        分别对应Retetion这个枚举类的值:

                  RetetionPolicy.SOURSEjava源文件时期,如@Overried@SuppressWarning

                  RetetionPolicy.CLASS class文件时期(默认阶段)

                  RetetionPolicy.RUNTIME:运行时期,如@Deprecated

        如:在某注解类上加@Retention(RetentionPolicy.RUNTIME),表示此注解会一直存在。

注:

        1、当在源程序上加了注解,javacjava源程序编译为class文件时,会对注解的生命周期进行判断。如果该注解只保留在源程序,则编译时会将该注解进行相应的处理操作,如去掉。其他类推。

         2class文件中不是字节码,只有把class文件中的内容加载进内存,用类加载器加载处理后(进行完整的检查等处理),最终得到的二进制内容才是字节码。

       

        2Target:用于说明注解类的使用范围。如在方法上还是类上,默认值是任何地方。

       其值可设置为枚举类ElementType类中的任何一个,包括:包、字段、方法、方法参数、构造器、类等值。取值为:

                 PACKAGE(包声明)

                 FIELD(字段声明)

                 ANNOTATION_TYPE(注释类型声明)

                 CONSIRUCTOR(构造器声明)

                 METHOD(方法声明)

                 PARAMETER(参数声明)

                 TYPE(类、接口(包含注释类型)或枚举声明)

                 LOCAL_VARIABLE(局部变量声明)

注意:其中代表类的值是TYPE。因为classenuminterface@interface等都是平级的,所以统属于Type。不可用CLASS表示。

3、注解的应用

        通过反射方式来获取自定义的注解类,步骤跟注解的应用结构一致,

        如:

             第一、定义注解类:@interfaceA{}

             第二、应用了“注释类”的类:@A  class B{}

             第三、对“应用注释类的类”进行反射操作的类:class c{...},操作如下:

                           B.class.isAnnotionPresent(A.class);//判断是否存在此注解类

                           A a = B.class.getAnnotation(a.class);//存在的话则得到这个注释类的对象 

示例:

[java]  view plain copy
  1. package cn.itheima.Demo;  
  2.   
  3. import java.lang.annotation.Retention;  
  4. import java.lang.annotation.RetentionPolicy;  
  5.   
  6. @Retention(RetentionPolicy.RUNTIME)//元注释  
  7. //注解类  
  8. public @interface MyAnnotation {  
  9. }  
  10.   
  11. @MyAnnotation  
  12. //应用类  
  13. public class AnnotationDemo {  
  14.     @SuppressWarnings("deprecation")//此注解用于抑制过时信息的提示  
  15.     public static void main(String[] args) {  
  16.           
  17.         System.runFinalizersOnExit(true); //这是一个过时了的方法 ,如果没有注解就会有警告提示  
  18.         //判断此类是否有MyAnnotation注解  
  19.         if (AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)) {  
  20.             //如果有,则获取该注解  
  21.             MyAnnotation annotation =AnnotationDemo.class.getAnnotation(MyAnnotation.class);  
  22.             System.out.println(annotation);  
  23.         }  
  24.     }  
  25. }  

 

五、为注解添加基本属性

1、属性:一个注解相当于一个胸牌,但仅通过胸牌还不足以区别带胸牌的两个人,这时就需要给胸牌增加一个属性来区分,如颜色等。

2、定义格式:同接口中的方法一样:String color();

     定义缺省格式:String value() default “heima”;

3、应用:直接在注解的括号中添加自身的属性,如:

               @MyAnnotation(color=”red”)

        1)如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@SuppressWarnings("deprecation")。

        2)可以为属性值指定缺省值(default),应用时同样可以重新设置属性值。

        3)用反射方式获得注解对应的实例对象后,可以通过该对象调用属性对应的方法来获取属性值。

 

六、为注解增加高级属性

1、可以为注解增加的高级属性的返回值类型有:

        1)八种基本数据类型   

        2)String类型 

        3)Class类型

        4)枚举类型  

        5)注解类型  

        6)前五种类型的数组

2、数组类型的属性:

        如:int[]arrayArr() default {1,2,3};//可不定义默认值

        应用:@MyAnnotation(arrayArr={2,3,4}) //可重新赋值

注:若数组属性中只有一个元素(或重新赋值为一个元素),这时属性值部分可省略大括号。

3、枚举类型的属性:

        假设定义了一个枚举类TrafficLamp,它是EnumTest的内部类,其值是交通灯的三色。

        定义:EnumTest.TrafficLamplamp();

        应用:@MyAnnotation(lamp=EnumTestTrafficLamp.GREEN)

4、注解类型的属性:

        假定有个注解类:MetaAnnotation,其中定义了一个属性:String value()

        定义:MetaAnnotation annotation() default @MetaAnnotation(”xxx”);

        应用:@MyAnnotation(annotation=@MetaAnnotation(”yyy”))//重新赋值

可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:

        MetaAnnotationma =MyAnnotation.annotation();

        System.out.println(ma.value());

5、Class类型的属性:

        定义:Class cls();

        应用:@MyAnnotation(cls=AnnotationDemo.class)

注:这里的.class必须是已定义的类,或是已有的字节码对象

6、注解的详细语法可通过查看java语言规范了解即javaLanguage Specification

示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. import java.lang.annotation.ElementType;  
  4. import java.lang.annotation.Retention;  
  5. import java.lang.annotation.RetentionPolicy;  
  6. import java.lang.annotation.Target;  
  7.   
  8. @Retention(RetentionPolicy.RUNTIME)//元注释  
  9. @Target({ElementType.METHOD,ElementType.TYPE})//元注解,指定使用范围  
  10. //注解类  
  11. public @interface MyAnnotation {  
  12.     String color() default "red" ;  
  13.     String value();  
  14.     //数组  
  15.     int[] arr() default {1,2,3};  
  16.     //枚举  
  17.     EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.GREEN;  
  18.     //注解类  
  19.     MetaAnnotation annotation() default @MetaAnnotation("heima");  
  20.     //Class类  
  21.     Class clazz() default System.class;  
  22. }  
  23.   
  24. import java.lang.reflect.Method;  
  25.   
  26. //注解类的应用,给属性赋值或者重新赋值  
  27. @MyAnnotation(lamp=EnumTest.TrafficLamp.YELLOW,value="heima",  
  28.             clazz=AnnotationDemo.class,annotation=@MetaAnnotation("itheima"))  
  29. //应用类  
  30. public class AnnotationDemo {  
  31.     @SuppressWarnings("deprecation")//此注解用于抑制过时信息的提示  
  32.     @MyAnnotation("Method")//自定义注解应用在方法上  
  33.     public static void main(String[] args) throws NoSuchMethodException, SecurityException {  
  34.           
  35.         System.runFinalizersOnExit(true); //这是一个过时了的方法 ,如果没有注解就会有警告提示  
  36.         //判断此类是否有MyAnnotation注解  
  37.         if (AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)) {  
  38.             //如果有,则获取该注解  
  39.             MyAnnotation annotation =AnnotationDemo.class.getAnnotation(MyAnnotation.class);  
  40.             System.out.println(annotation);//@cn.itheima.Demo.MyAnnotation()  
  41.             System.out.println(annotation.color());//red  
  42.             System.out.println(annotation.value());//heima  
  43.             System.out.println(annotation.arr().length);//3  
  44.             System.out.println(annotation.lamp());//YEllOW  
  45.             System.out.println(annotation.annotation().value());//itheima  
  46.             System.out.println(annotation.clazz());//class cn.itheima.demo.AnnotationDemo  
  47.         }  
  48.           
  49.         //获取方法上的注解  
  50.         Method mainMethod=AnnotationDemo.class.getMethod("main",String[].class);  
  51.         MyAnnotation annotationMethod=(MyAnnotation) mainMethod.getAnnotation(MetaAnnotation.class);  
  52.         SuppressWarnings sw=mainMethod.getAnnotation(SuppressWarnings.class);  
  53.         System.out.println(sw);//null  
  54.         System.out.println(annotationMethod);//null   
  55.     }  
  56. }  

 

第三讲      类加载器

 

一、概述

1、定义:简单说,类加载器就是加载类的工具。

        在java程序中用到一个类,出现了这个类的名字。java虚拟机首先将这个类的字节码加载到内存中,通常这个类的字节码的原始信息放在硬盘上的classpath指定的目录下,把.class文件的内容加载到内存里面来,再对它进行处理,处理之后的结果就是字节码。这些工作就是类加载器在操作。

2、类加载器作用:将.class文件中的内容变为字节码加载进内存。

3、默认类加载器:

        1)Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader

        2)类加载器本身也是Java类,因为它是Java类的加载器,本身也需要被类加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。

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

5、类加载器之间的父子关系和管辖范围图

示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. public class ClassLoaderDemo {  
  4.   
  5.     public static void main(String[] args) {  
  6.         System.out.println(  
  7.                 ClassLoaderDemo.class.getClassLoader().getClass().getName()  
  8.                 );//sun.misc.Launcher$AppClassLoader,表示由AppClassLoader加载  
  9.         System.out.println(System.class.getClassLoader());//null,表示System这个类时由RootStrap加载的  
  10.     }  
  11. }  

 

二、类加载器的委托机制

1、每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。

2、加载类的方式

        当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?

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

         2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B

         3)还可直接调用ClassLoaderLoaderClass()方法,来指定某个类加载器去加载某个类。

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

        类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行加载。当回退到最初的发起者类装载器时,如果它自己也不能完成类的装载,那就会抛出ClassNotFoundException异常。这时就不会再委托发起者加载器的子类去加载了,如果它还有子类的话。

        简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再助剂让其子级找,直到发起者,再没找到就报异常。

3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。

补充:面试题

        可不可以自己写个类为:java.lang.System呢?

        回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System

        第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。

体现委托机制的示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. public class ClassLoaderDemo {  
  4.   
  5.     public static void main(String[] args) {  
  6.         /* 
  7.          * 用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itheima.jar包 
  8.          * 此时再在eclipse中运行这个类时,下面代码的while循环内的运行结果显示为ExtClassLoadr。 
  9.          * 这就表示,AppClassLoader在加载这个类时,会先委托给其上一级ExtClassLoader加载器去加载,而上级又委托上级 
  10.          * 但是ExtClassloader的上级没有找到要加载的类,就回到ExtClassLoader,此时它在jre/lib/ext中找到了,所以就结果就显示它了。 
  11.          * */  
  12.         ClassLoader loader=ClassLoaderDemo.class.getClassLoader();  
  13.         while (loader!=null) {  
  14.             System.out.println(loader.getClass().getName());  
  15.             loader=loader.getParent();//将此loader的上级赋给loader  
  16.         }  
  17.         System.out.println(loader);  
  18.     }  
  19. }  

 

三、自定义类加载器

1、自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。

2、覆写findClass(Stringname)方法的原因:

        1)在loadClass()内部是会先委托给父级,当父级找不到后返回,再调用findClass(String name)方法,也就是你自定义的类加载器去找。所以只需要覆写findClass方法,就能实现用自定义的类加载器加载类的目的。

        因为,一般自定义类加载器,会把需要加载的类放在自己指定的目录中,而java中已有的类加载器是不知道你这个目录的,所以会找不到。这样才会调用你复写的findClass()方法,用你自定义的类加载器去指定的目录加载类。

        2)这是一种模板方法设计模式。这样就保留了loadClass()方法中的流程(这个流程就是先找父级,找不到再调用自定义的类加载器),而我们只需复写findClass方法,实现局部细节就行了。

        ClassLoader提供了一个protected  Class<?>defineClass(String name, byte[] b, int off, int len)方法,只需要将类对应的class文件传入,就可以将其变为字节码。

3、编程步骤:

        1)编写一个对文件内容进行简单加密的程序

        2)编写好了一个自己的类加载器,可实现对加密过来的类进行加载和解密。

        3)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可使用ClassLoaderloadClass方法外,还可以使用设置线程的上下文类加载器或系统类加载器,然后再使用Class.forName

4、编码步骤:

        1)对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast

        2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoaderjava MyClassLoader MyTest F:\itcast

        3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

        4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

示例:

[java]  view plain copy
  1. package cn.itheima.demo;  
  2.   
  3. import java.util.Date;  
  4. //定义一个测试类,继承Date,便于使用时加载  
  5. public class ClassLoaderAttachment extends Date{  
  6.     //复写toString方法  
  7.     public String toString(){  
  8.         return "Hello World!";  
  9.     }  
  10. }  
  11.   
  12. import java.io.ByteArrayOutputStream;  
  13. import java.io.FileInputStream;  
  14. import java.io.FileOutputStream;  
  15. import java.io.InputStream;  
  16. import java.io.OutputStream;  
  17.   
  18. public class MyClassLoader extends ClassLoader{  
  19.   
  20.     public static void main(String[] args) throws Exception {  
  21.         String srcPath=args[0];//文件源  
  22.         String destDir=args[1];//文件目的  
  23.         InputStream ips=new FileInputStream(srcPath);  
  24.         String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);  
  25.         String destFilePath=destDir+"\\"+destFileName;  
  26.         OutputStream ops=new FileOutputStream(destFilePath);  
  27.         cypher(ips,ops);//加密class字节码  
  28.         ips.close();  
  29.         ops.close();  
  30.     }  
  31.     //加密方法  
  32.     private static void cypher(InputStream ips,OutputStream ops) throws Exception{  
  33.         int b=-1;  
  34.         while((b=ips.read())!=-1){  
  35.             ops.write(b^0xff);  
  36.         }  
  37.     }  
  38.   
  39.     @Override  
  40.     //覆盖ClassLoader的findClass方法  
  41.     protected Class<?> findClass(String name) throws ClassNotFoundException {  
  42.         name=name.substring(name.lastIndexOf(".")+1);  
  43.         String classFileName=classDir+"\\"+name+".class";//获取class文件名  
  44.         InputStream ips=null;  
  45.         try {  
  46.             ips=new FileInputStream(classFileName);  
  47.             ByteArrayOutputStream bos=new ByteArrayOutputStream();//定义字节数组流  
  48.             cypher(ips,bos);//解密  
  49.             ips.close();  
  50.             byte[] buf=bos.toByteArray();//取出字节数组流中的数据  
  51.             return defineClass(null, buf,0,buf.length);//加载进内存  
  52.               
  53.         } catch (Exception e) {  
  54.             // TODO: handle exception  
  55.             e.printStackTrace();  
  56.         }  
  57.         return null;  
  58.         //return super.findClass(name);  
  59.     }  
  60.       
  61.     private String classDir;  
  62.     public MyClassLoader(){}  
  63.     //带参数的构造函数  
  64.     public MyClassLoader(String classDir){  
  65.         this.classDir=classDir;  
  66.     }  
  67.   
  68. }  
  69.   
  70. import java.util.Date;  
  71.   
  72. public class ClassLoaderDemo {  
  73.   
  74.     public static void main(String[] args) throws Exception {  
  75.         //将用自定义的类加载器加载.class文件  
  76.         Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment");  
  77.         Date d1 =  (Date)clazz.newInstance();//获取Class类的实例对象  
  78.         System.out.println(d1);  
  79.     }  
  80. }

完整代码示例:https://github.com/jiangcheng1806/startup-jdk

转自https://blog.csdn.net/u011870547/article/details/44937545


猜你喜欢

转载自blog.csdn.net/sky_jiangcheng/article/details/80323325