内省引出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要比使用普通的方式更方便。
示例:
package cn.itheima.demo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpectorDemo {
public static void main(String[] args) throws Exception {
HashCodeTest hct=new HashCodeTest(2,3);
String propertyName="x";
//"x"-->"X"-->"getX"-->Method getX-->
//用内省的方式
//获取并getX方法
Object retval = getProperty1(hct, propertyName);
System.out.println(retval);
Object value=5;
//获取并调用setX方法
setProperty(hct, propertyName, value);
System.out.println(hct.getX());
}
//获取并调用setX方法
private static void setProperty(HashCodeTest hct, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联
Method methodSetX=pd.getWriteMethod();//获取JavaBean类中的setX方法
methodSetX.invoke(hct,value);//调用setX方法
}
//获取并getX方法
private static Object getProperty1(HashCodeTest hct, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor pd=new PropertyDescriptor(propertyName,hct.getClass());//创建对象关联
Method methodGetX=pd.getReadMethod();//获取JavaBean类中的getX方法
Object retval=methodGetX.invoke(hct);//调用getX方法
return retval;
}
}
Eclipse小技巧:
选择要变为方法的代码,右键——>Refactor——>Extract Method,然后就会生成一个方法了。
三、对JavaBean的复杂内省操作
1、在IntroSpector类中有getBeanInfo(Class cls)的方法,通过此方法获取BeanInfo实例。参数是相应对象的字节码,即Class对象。
2、BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的JavaBean类中的属性信息,返回一个PropertyDescriptor[]。
3、在通过遍历的形式,获取与想要的那个属性信息。
如:获取并调用getX方法
//第二种较复杂的获取并调用JavaBean中的getX方法
private static Object getProperty2(HashCodeTest hct, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
BeanInfo beanInfo=Introspector.getBeanInfo(hct.getClass());//创建对象关联
PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();//获取所有的属性描述
Object retval=null;
//遍历
for (PropertyDescriptor pd : pds) {
//如果属性跟参数的属性相等,就获取它的getX方法
if (pd.getName().equals(propertyName)) {
Method methodGetX=pd.getReadMethod();//获取getX方法
retval=methodGetX.invoke(hct);
break;
}
}
return retval;
}
四、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随项目移动。
示例:
//用BeanUtils工具包的工具类BeanUtils方法
BeanUtils.setProperty(hct, propertyName, "9");//set,9是String
System.out.println(BeanUtils.getProperty(hct, propertyName));//get
System.out.println(BeanUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.String
//BeanUtils工具包中的PropertyUtils的操作
PropertyUtils.setProperty(hct, propertyName, 9);//9是Integer
System.out.println(PropertyUtils.getProperty(hct, propertyName));
System.out.println(PropertyUtils.getProperty(hct, propertyName).getClass().getName());//java.lang.Integer
一、概述
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、类加载器之间的父子关系和管辖范围图
示例:
package cn.itheima.demo;
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println(
ClassLoaderDemo.class.getClassLoader().getClass().getName()
);//sun.misc.Launcher$AppClassLoader,表示由AppClassLoader加载
System.out.println(System.class.getClassLoader());//null,表示System这个类时由RootStrap加载的
}
}
二、类加载器的委托机制
1、每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。
2、加载类的方式
当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?
1)首先,当前线程的类加载器去加载线程中的第一个类。
2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。
3)还可直接调用ClassLoader的LoaderClass()方法,来指定某个类加载器去加载某个类。
2、每个类加载器加载类时,又先委托给上级类加载器。
类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行加载。当回退到最初的发起者类装载器时,如果它自己也不能完成类的装载,那就会抛出ClassNotFoundException异常。这时就不会再委托发起者加载器的子类去加载了,如果它还有子类的话。
简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再助剂让其子级找,直到发起者,再没找到就报异常。
3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。
补充:面试题
可不可以自己写个类为:java.lang.System呢?
回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。
第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。
体现委托机制的示例:
package cn.itheima.demo;
public class ClassLoaderDemo {
public static void main(String[] args) {
/*
* 用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itheima.jar包
* 此时再在eclipse中运行这个类时,下面代码的while循环内的运行结果显示为ExtClassLoadr。
* 这就表示,AppClassLoader在加载这个类时,会先委托给其上一级ExtClassLoader加载器去加载,而上级又委托上级
* 但是ExtClassloader的上级没有找到要加载的类,就回到ExtClassLoader,此时它在jre/lib/ext中找到了,所以就结果就显示它了。
* */
ClassLoader loader=ClassLoaderDemo.class.getClassLoader();
while (loader!=null) {
System.out.println(loader.getClass().getName());
loader=loader.getParent();//将此loader的上级赋给loader
}
System.out.println(loader);
}
}
三、自定义类加载器
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)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可使用ClassLoader的loadClass方法外,还可以使用设置线程的上下文类加载器或系统类加载器,然后再使用Class.forName。
4、编码步骤:
1)对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
示例:
package cn.itheima.demo;
import java.util.Date;
//定义一个测试类,继承Date,便于使用时加载
public class ClassLoaderAttachment extends Date{
//复写toString方法
public String toString(){
return "Hello World!";
}
}
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) throws Exception {
String srcPath=args[0];//文件源
String destDir=args[1];//文件目的
InputStream ips=new FileInputStream(srcPath);
String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);
String destFilePath=destDir+"\\"+destFileName;
OutputStream ops=new FileOutputStream(destFilePath);
cypher(ips,ops);//加密class字节码
ips.close();
ops.close();
}
//加密方法
private static void cypher(InputStream ips,OutputStream ops) throws Exception{
int b=-1;
while((b=ips.read())!=-1){
ops.write(b^0xff);
}
}
@Override
//覆盖ClassLoader的findClass方法
protected Class> findClass(String name) throws ClassNotFoundException {
name=name.substring(name.lastIndexOf(".")+1);
String classFileName=classDir+"\\"+name+".class";//获取class文件名
InputStream ips=null;
try {
ips=new FileInputStream(classFileName);
ByteArrayOutputStream bos=new ByteArrayOutputStream();//定义字节数组流
cypher(ips,bos);//解密
ips.close();
byte[] buf=bos.toByteArray();//取出字节数组流中的数据
return defineClass(null, buf,0,buf.length);//加载进内存
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
//return super.findClass(name);
}
private String classDir;
public MyClassLoader(){}
//带参数的构造函数
public MyClassLoader(String classDir){
this.classDir=classDir;
}
}
import java.util.Date;
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception {
//将用自定义的类加载器加载.class文件
Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment");
Date d1 = (Date)clazz.newInstance();//获取Class类的实例对象
System.out.println(d1);
}
}