一、内省IntroSpector
1.概述
内省(IntroSpector),是对内部进行检查,了解更多的底层细节,主要是对JavaBean进行操作。
2.JavaBean类
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则。只要一个类中含有get或is和set打头的方法,就可以将其当做JavaBean使用。
1)字段和属性
①字段就是我们定义的一些成员变量,如private String name;
②JavaBean的属性是根据其中的setter和getter方法来确定的,而不是依据其中的变量,如方法名为setAge,则为设置age,getAge也是如此;去掉set或者get前缀,剩余部分就是属性名称。
属性的获取规则:如果剩余部分的第二个字母小写,则把剩余部分改为小写,否则保留为大写。如:
getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成
员变量。
2)作用
如果要在两个模板之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常
称之为值对象(Value Object,简称VO),这些信息在类中用私有字段来储存,如果读取或设置这些字段的值,
则需要通过一些相应的方法来访问。
3)JavaBean的好处
- 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地。
- JDK中提供了对JavaBean进行操作的API,这套API称为内省,若要自己通过getX的方式来访问私有x,可用内省这套API,操作JavaBean要比使用普通的方式更方便。
代码示例:
<span style="font-size:18px;">
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"-->MethodGetX-->
//用内省的方式
//获取并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;
}
}
</span>
3.对JavaBean类的复杂内省操作
1)在IntroSpector类中有getBeanInfo(Class cls)的方法,通过此方法获取BeanInfo实例。参数是相应对象的字节码,
即Class对象。
2)BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的JavaBean类中的属性信息,返回一个
PropertyDescriptor[]。
3)在通过遍历的形式,获取与想要的那个属性信息。
示例:获取并调用getX方法
<span style="font-size:18px;">//第二种较复杂的获取并调用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;
}
</span>
4.BeanUtils工具包
BeanUtils工具包是由Apache公司提供,为了方便开发。BeanUtils可以将8种基本数据类型进行自动的转换,但是对于非基本数据类型,就需要注册转换器Converter,这就需要ConverUtils包。
1)好处
a)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操作。
b)支持属性的级联操作,即支持属性链。如可以设置:人的脑袋上的眼睛的眼珠的颜色。这种级联属性的属性连如果自己用反射, 那就很困难了,通过这个工具包就可以轻松调用。
提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操作。
2)可以和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。
3)小知识点补充:导入工具jar包
导入jar包一般有两种方式:
第一种:右键项目--选择Properties---Java Build Path--选择Liberiers标签。AddExternal Jars--选择要导入的jar
包。即可。这样做有个问题就是如果jar路径发生变化。项目就不能使用到这个jar包。
第二种:在项目中建立一个lib目录,专门用于存放项目所使用到的jar工具包。将要使用到jar包复制粘贴进来,
并在jar上点右键--选择Builder Path---Add to BiuldPath,即可。这时jar包中的对象,就可以使用了。
这种方法能实现jar包随项目移动而移动。
代码示例:
<span style="font-size:18px;">
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorDemo {
public static void main(String[] args) throws Exception {
HashCodeTest hct=new HashCodeTest(2,3);
String propertyName="x";
//"x"-->"X"-->"getX"-->MethodGetX-->
//用BeanUtils工具包的方法
System.out.println(BeanUtils.getProperty(hct, propertyName));//get
BeanUtils.setProperty(hct, propertyName, "9");//set
System.out.println(hct.getX());
//对于JavaBean中的属性是对象的操作
BeanUtils.setProperty(hct, "birthday.time", "10");//set
System.out.println(BeanUtils.getProperty(hct, "birthday.time"));//get
}
}
</span>
二、类加载器
1.概述
所谓类加载器,就是加载类的工具。如果程序中调用到一个类,那么虚拟机会将该类源文件编译, 并将编译后的字节码文件(.class)文件存放在classpath指定的目录下。类加载器会将这些文件加载到内存中,进行进一步处理。
2.类加载器的作用
类加载器可以加载类文件。如果不是字节码文件,如配置文件,如果需要用类加载器加载配置文件,需要将配置文件放在classpath目录下。类加载器也会将文件原封不动的加载到指定的classpath目录下。
3.默认类加载器
Java虚拟机可以安装多个类加载器,每个类负责加载特定位置的类。系统默认三个主要的类加载器。
BootStrap
ExtClassLoader
AppClassLoader
类加载器本身也是Java类,因为它是Java类的加载器,本身也需要被类加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。
如果是自定义的类加载器,必须明确指定继承哪个父类加载器。否则就是系统默认的父类加载器。例如:
Person.class.getClassLoader().getResourceAsStream("p1.config.properties");
或者直接类文件调用
Person.getResourceAsStream("config.properties");
4.类加载器的树状结构图
<span style="font-size:18px;">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加载的
}
}
</span>
5.类加载器的委托机制
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。
1)委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器呢?
①首先,当前线程的类加载器去加载线程中的第一个类。
②如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
③还可以调用ClassLoader.LoadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给某上级类加载器
当所有祖宗类加载器没有加载到类时,回到发起者类加载器,还加载不了,则抛出
ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getClass方法,即使有,有多个儿
子的话,也不知道找哪一个。
示例:
<span style="font-size:18px;">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);
}
}
</span>
6.自定义类加载器
自定义类加载器需要继承ClassLoader类,类中的loadClass()会先找上级类加载器,上级返回时是让findClass()自己实现加载类。
-
覆写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文件传入,就可以将其变为字节码。
-
自定义类加载器的应用:对一个类进行加密
步骤:
1)编写一个对文件内容进行简单加盟的程序
2)编写好了一个自己的类加载器,可实现对加密过来的类进行装载和解密。
3)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别
这个类,程序中除了可使用ClassLoader的load方法外,还能使用放置线程的上线文类加载器加载或系
统类加载器,然后在使用forName得到字节码文件。
//加密文件
package cn.itcast.text2;
import java.util.Date;
public class ClassLoaderAttachment extends Date {
//对此类进行加密
public String toString(){
return "hello world";
}
public static void main(String [] args){
}
}
//自定义类加载器
package cn.itcast.text2;
import java.io.*;
//继承抽象类ClassLoader
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception {
//传入两个参数,源和目标
String scrPath = args[0];
String destDir = args[1];
//将数据读取到输入流中,并写入到输出流中
FileInputStream fis = new FileInputStream(scrPath);
String destFileName =
scrPath.substring(scrPath.lastIndexOf('\\')+1);
String destPath = destDir + "\\" + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
//加密数据
cypher(fis,fos);
fis.close();
fos.close();
}
//定义加密数据的方法
private static void cypher(InputStream ips,OutputStream ops)throws Exception{
int b = 0;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);
}
}
//定义全局变量
private String classDir;
@Override//覆写findClass方法,自定义类加载器
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
//将要加载的文件读取到流中,并写入字节流中
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//如果没找到类,则用父级类加载器加载
return super.findClass(name);
}
//构造函数
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
7.模板方法设计模式:
1.定义:把一些不变的步骤,不知道具体实现的方法封装成抽象方法,
然后按正确顺序调用他们的具体方法,这些具体方法称为模板方法,构成一个抽象基类。
子类通过继承这个抽象基类去 实现各个步骤的抽象方法,工作流程却由父类来控制。
简单说,就是定义一个操作中的算法的框架,将一些步骤声明为抽象方法迫使子类去实现,不同的子类可以
不同的方式去实现这些抽象方法。
2.适用情况
1)一次性实现算法的不变部分,并将可变的行为交给子类来实现。
2)各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
3)控制子类扩展。模板方法只在特定点调用操作。这样就只允许在这些点进行扩展。
3.好处:
使用模板方法模式可以将代码的公共行为提取出来,达到复用的目的。
而且在模板方法模式中,是由父类的模板方法来控制子类中的具体实现。
这样在实现子类时,就不需要对业务流程有太多的了解。
模式例子:ClassLoader就是抽象基类,流程定义在loadClass()中,具体实现需要重写findClass();
8.面试题:能不能自己写一个java.lang.System类?
通常不行,因为自己写的类存储在classpath指定目录中。
根据委托机制,加载System类时,父类类加载器BootStrap会直接加载rt.jar中的System类。
不会加载自己写的System类。
如果要加载自己写的System类,需要写自己的类加载器去加载,使其不访问父类加载器。