黑马程序员-高新技术(类加载器与动态代理)

本文深入探讨Java类加载机制及自定义类加载器的实现原理,并介绍动态代理的多种创建方式及其应用场景。

-----------android培训java培训、java学习型技术博客、期待与您交流! ------------


一、类加载器


1、什么是类加载器


类加载器就是加载类的工具,java虚拟机JVM运行类的第一件事就是将这个类的字节码加载进来,
即类加载器工具类的名称定位和生产类的字节码数据,然后返回给JVM。

java.lang.ClassLoader 类

类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。
如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。
一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。 
数组类的 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。
数组类的类加载器由 Class.getClassLoader() 返回,该加载器与其元素类型的类加载
器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。 

构造方法
protected ClassLoader() 使用getSystemClassLoader()返回的ClassLoader创建一个新的类加载器,将该加载器作为父类加载器。
protected ClassLoader(ClassLoader parent) 使用指定的、用于委托操作的父类加载器创建新的类加载器。 

相关的方法
getClassLoader() 返回该类的类加载器。返回类型 ClassLoader  
getParent()  返回该类加载器的父类加载器。 
loadClass(String name)  加载名称为 name的类,返回的结果是 java.lang.Class类的实例。 
findClass(String name)  查找名称为 name的类,返回的结果是 java.lang.Class类的实例。 
findLoadedClass(String name)  查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。 
defineClass(String name, byte[] b, int off, int len)  把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c)  链接指定的 Java 类。 

2、类加载器的树状组织结构


Java中的类加载器大致可以分成两类:一类是系统提供的,另外一类则是由程序员编写的。

系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):用来加载 Java 的核心库,是用原生代码来实现的
扩展类加载器(extensions class loader):用来加载 Java 的扩展库。
系统类加载器(system class loader):用来加载classpath所指定的位置的类或者是jar文档。

一般来说,Java 应用的类都是由system class loader来完成加载的。也称为应用类加载器。
可以通过 ClassLoader.getSystemClassLoader()来获取它。


除了系统提供的类加载器以外,可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器。

所有的类加载器都有一个父类加载器,通过getParent()方法可以得到。
系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;
对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器Java类的类加载器。

因为类加载器Java类如同其它的Java类一样,也是要由类加载器来加载的。一般来说,
开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,
形成树状结构。树的根节点就是引导类加载器。

Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,
BootStrap,ExtClassLoader,AppClassLoader,每个类负责加载特点位置的类。

类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
所以必须有第一个类加载器,它就是BootStrap

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

代码示例:获取类加载器

public class ClassLoaderTest {
	public static void main(String[] args) {
		//返回类的完整名称
		System.out.println(ClassLoaderTest.class);  
		
		//以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
		System.out.println(ClassLoaderTest.class.getName());  
		
		// 返回源代码中给出的底层类的简称。
		System.out.println(ClassLoaderTest.class.getSimpleName()); 
		
		//返回该类的类加载器。
		System.out.println(ClassLoaderTest.class.getClassLoader());
		
		//返回本类的类加载器的字节码对象
		System.out.println(ClassLoaderTest.class.getClassLoader().getClass());
		
		//返回本类的类加载器的字节码对象的名称
		System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName()); 
		
		//返回系统类的加载器--没有
		System.out.println(System.class.getClassLoader());  
	}
}
/*
结果
class com.wgxin.ClassLoaderTest
com.wgxin.ClassLoaderTest
ClassLoaderTest
sun.misc.Launcher$AppClassLoader@43be2d65
class sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader
null
*/

3、自定义类加载器的编写原理


分析:

虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求,但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输Java类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。

文件系统类加载器,第一个类加载器用来加载存储在文件系统上的Java字节代码

代码示例:

//继承于加载器ClassLoader类
public class FileSystemClassLoader extends ClassLoader{
    private String rootDir; 
	//构造函数
	public FileSystemClassLoader(String rootDir) { 
        this.rootDir = rootDir; 
    } 
	//定义一个保护的函数
    protected Class<?> findClass(String name) throws ClassNotFoundException {
		//调用自定义的getClassData方法
        byte[] classData = getClassData(name); 
        if (classData == null)
            throw new ClassNotFoundException(); 
        else{
			//将数组转换为Class类并返回。
            return defineClass(name, classData, 0, classData.length); 
		}
    } 

    private byte[] getClassData(String className) { 
        String path = classNameToPath(className); 
        try {
			//创建文件输入流和字节数组输出流
            InputStream ins = new FileInputStream(path); 
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
			//定义一个数组缓冲区
            byte[] buffer = new byte[1024*4]; 
            int bytesNumRead = 0; 
            while ((bytesNumRead = ins.read(buffer)) != -1) { 
				//将缓冲区中的数据全部写入字节数在输出流
                baos.write(buffer, 0, bytesNumRead); 
            } 
			//以字节数组的形式返回此输出流的当前数据
            return baos.toByteArray(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return null; 
    } 
	//构造一个class文件路径的方法
    private String classNameToPath(String className) { 
        return rootDir+File.separatorChar+className.replace('.',File.separatorChar)+".class"; 
    } 
}

二、动态代理


1、怎么理解代理?


代理其实就跟说中的代理商是一样的。假设需要去买一台电脑,一种方法是自己去电脑城购买,
另一种方法是在网上购买,比如X东,下了单后,X东会由他们的快递给你送到家,那么X东就是
一个中介,他帮你完成了你要买电脑的这件事,X东就是一个代理。

那为什么你不自己到电脑城去买?
原因之一:可能你很忙,没时间去。
对应到程序中:客户端无法直接操作实际对象,因为需要调用的对象在另外一台计算机中,
需要跨越网络才能访问,如果直接去调用对象,需要处理网络连接、打包、拆包等复杂动作,
所以为了简化客户端的处理就出现了代理。再由代理去跟实际对象联系。

原因之二:你找不到去电脑城的路
对应到程序中:除了当前类能够提供的功能外,还需要补充一些其他功能,最容易想到的是权限过滤,
如有一个类的某个功能,但由于安全原因只有某些用户才能调用这个类,此时就可以做一个该类的代理类,
要求所有的请求必须通过这个代理类,由该类代理类做权限判断,如果安全,则调用实际类的功能开始处理,

那么为什么要把权限过滤单独做成代理类,而不在原来类的方法里面做权限判断?
因为在程序设计中类具有单一性原则,就是每个能能尽可能的单一,如果把权限判断放在原来的类里面,
那么该类除了有自身的业务逻辑外,还要处理权限判断的业务,如果业务逻辑或者权限判断需要改动,
那么整个类都需要改变,这显然不是一个好的设计。

2、关于动态代理的基础类


java.lang.reflect:
Proxy 类
InvocationHandler 接口
这两个提供了生成动态代理类的功能

方法:
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。 
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表 

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 
h - 指派方法调用的调用处理程序 

方法 static boolean isProxyClass(Class<?> cl) 
当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。 

下面的实例实现获取动态代理类的构造函数和其他函数。

import java.lang.reflect.*;
import java.util.*;
public class ProxyFunction {
	public static void main(String[] args) throws Exception {
		//获取代理类的字节码
		Class proxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
		//打印字节码名字
		System.out.println("字节码的名字:"+proxy1.getName());
		System.out.println("构造函数的列表---------------------------------");
		//创建一个对象集,从字节码中获取构造函数
		Constructor[] constructors = proxy1.getConstructors();
		//遍历对象集,获取构造函数的名字
		for (Constructor constructor : constructors) {
			String name = constructor.getName();
			StringBuilder sBuilder = new StringBuilder(name);
			sBuilder.append('(');

			//获取构造函数的参数类型
			Class[] params = constructor.getParameterTypes();
			for (Class param : params) {
				sBuilder.append(param.getName()).append(',');
			}
			if (params != null && params.length != 0) {
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(')');
			System.out.println(sBuilder.toString());
		}

		System.out.println("方法列表----------------------------------------");
		//创建一个方法数组,获取方法
		Method[] methods = proxy1.getMethods();
		for (Method method : methods) {
			//获取方法名称存入缓冲区
			StringBuilder sBuilder = new StringBuilder(method.getName());
			sBuilder.append('(');
			//将此对象所表示的方法类型封装为一个数组
			Class[] params = method.getParameterTypes();
			
			//遍历方法类型数组
			for(Class param : params){
				sBuilder.append(param.getName()).append(',');
			}
			if(params != null && params.length != 0) {
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(')');
			System.out.println(sBuilder.toString());
		}
	}
}
/*
结果:
字节码的名字:com.sun.proxy.$Proxy0
构造函数的列表--------------------------------------------------
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
方法列表------------------------------------------------
add(java.lang.Object)
equals(java.lang.Object)
toString()
hashCode()
clear()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait(long)
wait()
wait(long,int)
getClass()
notify()
notifyAll()
*/

3、三种创建等待代理类的方法


import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
	public static void main(String[] args) throws Exception{
		//创建动态代理类的三种方式
		//方式一:通过接口的子类创建对象
		Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler());
		System.out.println(proxy1);//null
		System.out.println(proxy1.toString());//null
		proxy1.clear();		//无异常
		//proxy1.size();	//异常		
		
		//方式二:匿名内部类
		Collection proxy2 = (Collection)constructor.newInstance(
			new InvocationHandler(){
			public Object invoke(Object proxy, Method method,Object[] args)throws Throwable{
				// TODO Auto-generated method stub
				return null;
			}
		});
		
		//方式三:
		//通过代理类的newProxyInstance方法直接创建对象
		Collection proxy3 = (Collection)Proxy.newProxyInstance(
			//定义代理类的类加载器
			Collection.class.getClassLoader(),
			//代理类要实现的接口列表
			new Class[]{Collection.class},
			//指派方法调用的调用处理程序
			new InvocationHandler() {
				//创建集合,制定一个目标
				ArrayList target = new ArrayList();
				public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
					//测试程序运行时间
					long beginTime = System.currentTimeMillis();
					//调用目标方法,将其从return抽出来,加入代理所需的代码
					Object retVal = method.invoke(target, args);
					long endTime = System.currentTimeMillis();
					//测试
					System.out.println(method.getName()+" run time of "+(endTime - beginTime));
					return retVal;
				}
			}
			);
		//通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法
		//当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法
		proxy3.add("sdfd");
		proxy3.add("shrt");
		proxy3.add("rtbv");
		System.out.println(proxy3.size());
		System.out.println(proxy3.getClass().getName());
	}
}

三、实现类似spring的可配置的AOP框架


1、工厂类 BeanFactory


工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。
getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中
对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对
象的getProxy方法返回的对象。

2、BeanFactory 的构造方法接收代表配置文件的输入流对象,配置文件格式如下:


#xxx=java.util.ArrayList  
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList

xxx:是getBean的名字,就是工具这个名字参加哪一个Java Bean对象。
#:是注释当前行

3、ProxyFactoryBean充当封装成动态的工厂,需为工厂类提供的配置参数信息有:


目标:target
通告:advice

4、BeanFactory和ProxyFactoryBean

BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。
ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

5、实现类似spring的可配置的AOP框架的思路:

(1)创建BeanFactory类:
      构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。
      创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。
      通过其字节码对象创建实例对象bean。
      判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。
(2)创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。
(3)对配置文件进行配置,如上面配置一样。
(4)作一个测试类:AopFrameworkTest进行测试。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值