----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------
类加载器 三种类加载器之间关系 类加载器之间委托机制 自定义类加载器 当我们调用java命令运行某个java程序时,该命令将会启动一个java虚拟机进程,不管该java程序有多复杂,该程序启动了多少个线程,它们都处于该java虚拟机进程里。 当出现下面情况时JVM进程将被终止: 1.程序运行到最后正常结束。 2.程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。 3.程序执行过程中遇到未捕获的异常或错误而结束。 4.程序所在平台强制结束了JVM进程。 当程序主动使用某个类时,如果该类还未被加载进内存中,则系统会通过加载,连接,初始化3个步骤来对该类进行初始化。 (1)类的加载: 类的加载指的是将class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后再堆中创建一个这个类的java.lang.Class对象,用来封装类在方法区中的字节码。类的加载的最终产品是位于堆中的Class对象。 (2)类的连接 当类被加载之后,系统会为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3各阶段: 1.验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。 2.准备:类准备阶段则负责为类的静态Field分配内存,并设置默认初始值。 3.解析:将类的二进制数据中的符号引用替换成直接引用。 类加载器:用于在虚拟机运行时把指定class文件加载进内存 getClassLoader():返回该类的类加载器。 ClassLoader:抽象类,类加载器的根类,自定义类加载器时需要继承此类 功能: getParent(): 返回委托的父类加载器。 defineClass(String name, byte[] b, int off, int len): 将一个 byte 数组转换为 Class 类的实例。 Class<?> findClass(String name): 使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。 Class<?> loadClass(String name,boolean resolve): 使用指定的二进制名称来加载类。此方法的默认实现将按以下顺序搜索类: 调用 findLoadedClass(String) 来检查是否已经加载类。在父类加载器上调用 loadClass 方法。如果父类加载器为 null,则使用虚拟机的内置类加载器。调用 findClass(String) 方法查找类。鼓励用ClassLoader的子类重写 findClass(String),而不是使用此方法。 特点: 1、类加载器也是java类,需要被加载器加载,根加载器BootStrap嵌入在JVM内核中,每次启动JVM时自动启动加载类。 2、Java虚拟机可以安装多个类加载器,系统默认三个主要类加载器:BootStrap、ExtClassLoader、AppClassLoader。每个类加载器加载特定位置的类。 Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其制定一个父级类装载器对象或者默认采用系统类装载器为其父级加载。 结构与加载范围 三个加载器是父子关系的树形结构,BootStrap为根节点,AppClassLoader为叶子节点。类加载器加载类的过程: 1、 首先,当前线程的类加载器(可以通过Thread中的静态方法:getContextClassLoader()获得类加载器ClassLoader的对象)去加载线程的第一个类,如果第一个类类A引用了类B,虚拟机将使用加载类A的加载器去加载类B。 2、 当前类加载器不直接加载指定类class文件,而是委托父加载器(通过ClassLoader对象调用getParent()方法获得)去对应目录加载指定类,一直委托(通过loadClass(String name)方法委托)到BootStrap加载器。 3、 BootStrap加载器才开始到对应目录查找加载指定类class文件(通过findClass(String name)方法查找),当前加载器找不到,回到子类加载器继续查找加载,一直回到发起者,找不到则抛出ClassNotFoundException异常。 类加载器的委托机制: 当前类加载器不查找加载类,交给父类加载器,父类加载器不查找,交给上级,一直到BootStrap,BootStrap查找,找不到就返回下级查找,一直到发起者,如果发起者找不到,报出异常。 自定义类加载器: 1、继承ClassLoader类。 2、覆盖重写findClass(String name)方法。不要覆盖loadClass方法,这样可以保留原来的委托机制 3、自定义实现findClass方法的功能,获得Class对象。 实现findClass方法的功能步骤: 1、 使用输入流读取指定目录指定名称class文件。 2、 写入ByteArrayOutputStream字节数组输出流把class文件字节保存到数组中。 3、 通过toByteArray方法获得字节输出流中的字节数组。 4、 通过defineClass方法把字节数组转换成Class对象,findClass方法返回Class对象。 自定义类加载器:/* * 自定义类加载器加载自定义目录下的class文件 */ ublicclass MyClassLoader extends ClassLoader { // class所在的自定义目录 private String classDir; public MyClassLoader() { } // 通过构造方法获得指定目录 public MyClassLoader(String classDir) { this.classDir = classDir; } //重写父类的findClass方法 @Override public Class<?> findClass(String name) { //调用方法获得字节数组 byte[] data = findClassData(name); //调用方法把字节数组转成Class对象返回 return defineClass(name, data, 0, data.length); } //定义把class文件转成字节数组的方法 privatebyte[] findClassData(String name) { // 获得指定目录下指定名称的class文件 String classFileName = classDir + "\\" + name + ".class"; //定义字节数组 byte[] data = null; try { // 定义输入流读取文件 FileInputStream fis = new FileInputStream(classFileName); // 定义字节数组输出流获得字节数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int buf = 0; // 循环读取,写入输出流数组 while ((buf = fis.read()) != -1) { bos.write(buf); } //获得字节数组 data = bos.toByteArray(); } catch (Exception e) { thrownew RuntimeException(e); } return data; } publicstaticvoid main(String[] args) { String classDir = "E:\\myJava\\高新技术\\lib\\heima"; Class clazz = new MyClassLoader(classDir).findClass("HelloHeiMa"); System.out.println(clazz); }
代理类的概念与作用:
代理:一个角色代表别一个角色来完成某些特定的功能。
生活中的代理
到商店买东西,商店就是代理。
明星为什么需要经纪人来代理他呢?因为明星的专职是唱歌或演戏,如果把除此以外的其他事情比如演出费用谈判等等都揽在身上,他会累死。这就是体现一个思想:专业分工,用面向对象术语说:就是职责分明。
所以,代理类一般是做些除原始类核心功能以外的其他功能,比如权限,事务等等都要专门的代理来实现。
1,作用:
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,
例如:异常处理,日志,计算方法的运行时间,事务管理等等。
2,代理类实现方式:
编写一个与目标类具有相同接口的代理类(容易实现已实现类的切换), 代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。(参考下面的原理图)
代理架构图
3,应用:
当采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置使用目标类,或者代理类,这样以后就很容易切换。
譬如,想要日志功能时就配置代理类,否则配置目标类,增加系统功能就很容易,去掉也很容易这样提高了灵活性。
AOP(Aspect oriented program):面向方面的编程
安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
使用代理技术正好可以解决这种问题,代理是是实现AOP功能的核心和关键技术。
3.动态代理技术
要为系统中的各种借口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情,写成百上千个代理类,是不是很累?
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类: JVM可以再运行期间动态生成出类的字节码,这种动态生成的类往往被用作代理类。
1.JVM生成的动态类必须实现一个或多个接口(因为要委托JVM生成动态代理类,为了明确生成类中的方法,通过接口可直接告诉虚拟机要生成类中所含的方法),所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
2. CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1. 在调用目标方法之前
2. 在调用目标方法之后
3. 在调用目标方法前后
4. 在处理目标方法异常的catch块中
/** * 静态代理 */ public interface Person { //吃饭 void eat(); } //被代理的类 public class Student implements Person { publicvoid eat(){ System.out.println("吃饭了!"); } } [java] view plaincopy /** * 代理类,可以为实现相同接口的目标类增加系统功能 * 代理类与目标类实现相同的接口 */ public class ProxyPerson implements Person { //目标类对象,用父类类型接收 Person target; ProxyPerson(Person target) { this.target = target; } public void eat() { System.out.println("餐前点心"); long startTime = System.currentTimeMillis(); //调用目标类相同功能 this.target.eat(); System.out.println("餐后茶水"); long costTime = System.currentTimeMillis()-startTime; System.out.println("吃完饭,一共用了"+costTime+"毫秒"); } } public static void main(String[] args) { //只实现Student的功能 Person s = new Student(); s.eat(); //想要实现Student的功能并增加系统功能,使用代理类 Person p = new ProxyPerson(s); //调用相同的方法 p.eat(); }
4.创建动态代理实例
java动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。
4.1 java.lang.reflect.Proxy类
Proxy类的直接父类就是java.lang.Object类
作用 :
a.为创建动态代理类和实例提供了相应的静态方法
b.也是通过其自身的静态方法创建的动态代理类的父类
构造方法:
private Proxy();私有化构造方法目的是为了让这个Proxy类起到工具类的作用。工具类要求私有化构造方法
protected Proxy(InvocationHandler h);
Proxy提供了一个protected的构造方法来为动态生成的代理类 (也是Proxy的子类)所调用。
常用方法:
getProxyClass获取动态代理类的Class对象 -----获取
//Proxy.getProxyClass()方法获取的动态代理类的类名都是“$Proxy数字”
getProxyClass(ClassLoader loader,Class<?>... interfaces);
Class<?>… intefaces:指定动态生成的代理类要实现哪些接口
ClassLoader loader这个类加载器参数负责做两件事情:
a. 使用这个指定的类加载器loader来加载Class<?>…intefaces中指定的所有接口
b. 加载要生成的动态代理类
【一句话】loader起到的作用是双加载
loader既加载传入的接口对应的Class对象,又加载返回的动态代理类的Class对象。
4.2 java.lang.reflect.InvocationHandler接口--方法调用句柄
对代理类实例(proxyinstance)方法调用就会被指派到和这个代理类实例相关联的方法调用句柄中的invoke()去执行。
invoke( ):
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable;
Object proxy:在其上调用方法的代理实例
Method method:要调用某个动态代理类对象的哪个方法
Object[] args:为要调用的该方法传入哪些实参,如果接口方法不使用参数,则为null
4.3创建动态类的实例对象的方式有两种:
1、通过构造函数的方式创建
1> 通过Proxy.getProxyClass()方法获取一个动态代理类
2> 通过代理类调用getConstructor( )获取代理类的构造函数
3> 通过构造方法实例的newInstance( )方法得到代理实例
2、直接调用Proxy类的静态方法newProxyInstance( )获取字节码和实例对象
直接返回一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
该方法的参数列表是:
ClassLoader loader, 指定一个类加载器用于加载指定的接口
Class<?>[] interfaces, 给出需要创建代理实例的接口
InvocationHandlerh, 给出代理实例的调用处理程序实现的接口
//方式一 public void test1() throws Exception { //获得同接口代理类的字节码 claas对象 Class clazz = Proxy.getProxyClass(Person.class.getClassLoader(), Person.class); //打印其全部构造函数 for(Constructor c : clazz.getConstructors()) { System.out.println(c); } Constructor c = clazz.getConstructor(InvocationHandler.class); //传递参数,获得代理类实例对象 内部类 Person proxy1 = (Person) c.newInstance(new InvocationHandler(){ //目标类,需要被代理的类的实例 Person target = new Student(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("餐前点心"); long startTime = System.currentTimeMillis(); //调用目标类相同功能 target.eat(); System.out.println("餐后茶水"); long costTime = System.currentTimeMillis()-startTime; System.out.println("吃完饭,一共用了"+costTime+"毫秒"); return target; } }); //调用代理类方法 proxy1.eat(); } //方式二 publicvoid test2() throws Exception { //直接获得代理类的对象 Person proxy2 = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler(){ //目标类,需要被代理的类的实例 Person target = new Student(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("餐前点心"); long startTime = System.currentTimeMillis(); //调用目标类相同功能 target.eat(); System.out.println("餐后茶水"); long costTime = System.currentTimeMillis()-startTime; System.out.println("吃完饭,一共用了"+costTime+"毫秒"); returntarget; } }); //调用代理类方法 proxy2.eat(); }
注:代理类中,对于从Object中继承的方法,只把hashCode、equals、toString三个方法委托为invoke方法做。
5.分析JVM动态生成的类
总结思考:让JVM创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
1.生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
2.产生的类字节码必须有一个关联的类加载器对象;
3.生成的类中的方法的代码是怎样的,也得由我们提供,把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码,提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的Incoke方法中加一点代码,就可以看到这些代码被调用运行了。
----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------