您的位置:首页 > 职场人生

黑马程序员_Java高新技术——类加载器和动态代理(第9篇)

2014-08-13 12:56 585 查看
----------------------
ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

一、类加载器

    类加载器,顾名思义就是加载类的工具。类加载器首先也是一个类,那么有同学肯定会问,那么谁去加载类加载器呢?其实Java中最底层的一个类加载器BootStrap是用C++写的,嵌入在虚拟机中的,所以你不能去获取这个类加载器的一些信息。Java虚拟机中可以安装多个类加载器,系统默认的有三个主要的类加载器,每个负责加载特定位置的类。这三个类加载器分别是BootStrap、ExtClassLoader、AppClassLoader。

二、类加载器的委托机制

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

步骤:1.首先当前线程的类加载器去加载线程中的第一个类;2.如果类A中引用了类B,那么jvm将使用加载类A的加载器来加载类B;3.还可以直接调用ClassLoader.loadClass()方法制定某个类加载器去加载某个类。

    其中,每个类加载器加载类时,又会委托给其上级的类加载器,这会一直到最顶层的类加载器,当所有的上层类加载器都没有加载到类时,则会回到发起者的类加载器,要是还是加载不到的话,则会抛出ClassNotFoundException异常,而不会让下层的类加载器去加载。下图为类加载的树形结构图。



//得到类加载的小例子

class ClassLoaderTest
{
public static void main(String[] args)
{
//得到ClassLoaderTest这个类的加载器的类
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//根据加载器类的字节码文件得到加载器类的名字
String loaderName = loader.getClass().getName();
//sun.misc.Launcher$AppClassLoader
System.out.println(loaderName);
//不加注释前,这一行会报出空指针异常,为什么呢?因为System类由BootStrap加载器加载,
//这个加载器为嵌入在jvm中的,由C++写的,不是Java类,因而得不到所以会报空指针异常
//null
System.out.println(System.class.getClassLoader()/*.getClass().getName()*/);

}
}
二、自定义类加载器
1.我们先对demo.class加密,加密方法如下:

private static void jiaJieMi(InputStream ips, OutputStream ops)throws IOException
{
int by = -1;
while((by = ips.read()) != -1)
{
ops.write(by ^ 0xff);
}
}//这是一个加密的方法,也是解密的方式
我们将加密后的class文件存放在F盘的根目录下,等会儿在设置自定义的类加载器的时候将寻找目录设为F:\\

2.编写自定义类加载器MyClassLoader.java

import java.io.*;
class MyClassLoader extends ClassLoader//继承ClassLoader成为一个类加载器类
{
private String classDir;//初始化类加载器的时候设定寻找目录
MyClassLoader(String classDir)
{
this.classDir = classDir;
}
private static void jiaJieMi(InputStream ips, OutputStream ops)throws IOException
{
int by = -1;
while((by = ips.read()) != -1)
{
ops.write(by ^ 0xff);
}
}
protected Class<?> findClass(String name)
{
try
{
String classFileName = classDir + name + ".class";
//读.class文件
FileInputStream fis = new FileInputStream(classFileName);
//将.class文件写入数组中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//对加密的.class文件进行解密
jiaJieMi(fis, bos);
//关闭写入流
fis.close();
//解码
byte[] buf = bos.toByteArray();
//返回字节码文件
return defineClass(buf, 0, buf.length);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
/*
public static void main(String[] args)throws Exception
{
String fromPath = "E:\\Code\\Demo.class";//从哪儿读,为需要加密的文件所在的全路径
String toPath = "E:\\Demo.class";//写到哪个文件中去
FileInputStream fis = new FileInputStream(fromPath);
FileOutputStream fos = new FileOutputStream(toPath);
jiaJieMi(fis, fos);
fis.close();
fos.close();
}
*/
}
3.编写测试类
import java.lang.reflect.*;
class Test
{
public static void main(String[] args)throws Exception
{
//设定寻找目录,加载到demo.class,并解码
Class d1 = new MyClassLoader("F:\\").loadClass("Demo");

System.out.println(d1.getClassLoader().getClass().getName());
//Object d = d1.newInstance();
//反射demo的Main方法
Method mainMethod = d1.getMethod("main", String[].class);
//执行main方法
mainMethod.invoke(null, new Object[]{new String[]{}});
}
}执行结果:

MyClassLoader

NIMAD!

//demo.java的源代码为:

import java.util.*;
public class Demo
{
public static void main(String[] args)throws Exception
{
System.out.println("NIMAD!");
}
}
三、代理
1.代理是什么?

    根据生活中的经验,我们可以举个例子,比如说,买电脑这件事,我们可以直接去厂家买,也可以去商场买,这时候通过商场我们买到了电脑,在客户和厂家之间多了一个中间层,生活中我们将其称为代理商。通过代理商我们可以额外的获得一些好的体验和实惠。

    而程序中的代理也如出一辙,通过代理我们可以在获得主要功能的同时加上一些系统功能代码。假如现在有一个需求,要为已存在的多个具有相同接口的目标类的各个方法增加一些功能,例如,异常处理、日志,计算方法的运行时间等,如何做?

--编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上一些系统功能代码。

--如果采用工厂模式和配置文件的方式进行管理,那么就不需要修改客户端程序,直接在配置文件中配置实用目标类、还是代理类,这样以后很容易切换,比如,测试的时候可以使用代理类,加上一些调试的功能,实际部署的时候可以使用目标类,再比如,想要日志功能的时候可以配置代理类,不需要的时候就使用目标类。这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

这就是程序中的代理。下图简单的描述了代理类与目标类之间的简单关系。



2.说到这里不得不说下另一个方面,那就是面向切面编程(AOP)

    系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个面,如图所示:有学生StudentService、课程管理CourseService、其他管理MiscService三个模块,每个模块中都有安全、事务和日志的功能,那么安全、事务、日志贯穿了多个模块,他们就是几个交叉业务,术语就是切面。

    那么将这些交叉业务的模块化的编程就是切面编程。而我们所谓的切面编程就要用代理来实现交叉业务的模块化。代理是实现AOP功能的核心和关键技术。

3.动态代理

--要为系统中的各种接口的类增加代理功能,那将需要实现太多的代理类,全部采用静态的的代理方式,将会是一件非常麻烦的事情,而且很不现实。

--那么Jvm为我们提供的解决方案就是在运行时期动态的生成类的字节码,这种动态生成的类往往被用作代理类,即所谓的动态代理。

--Jvm生成的动态类必须实现一个或多个接口,所以,Jvm生成的动态类只能用作具有相同接口的目标类的代理。

--那么如何实现没有接口的类的代理呢?如今可以使用CGLIB库。CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理。

--代理类的各个方法中通常除了要调用目标的相应方法和对外返回的结果外,还可以在代理方法中的如下位置加上系统功能代码:1.在调用目标方法之前;2.在调用目标方法之后;3.在调用目标方法的前后;4.在处理目标方法的catch块中。

4.分析Jvm动态生成的类

//动态生成一个Collection的代理类,并打印出方法列表

import java.lang.reflect.*;
class ProxyDemo
{
public static void main(String[] args)throws Throwable
{
//动态生成代理类,实现Collection接口,使用Collection同样的加载器
Class classClazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//得到类所有的Method对象
Method[] methods = classClazz.getMethods();
//方法名
String methodName = null;
//参数名
String parName = null;
//返回值类型名
String methodReturnTypeName = null;
//遍历方法
for(Method method : methods)
{
StringBuilder sb = new StringBuilder();
methodName = method.getName();
methodReturnTypeName = method.getReturnType().getName();
Class methodPars[] = method.getParameterTypes();
sb.append((methodReturnTypeName + " " + methodName + "("));
//遍历参数列表
for(Class methodPar : methodPars)
{
parName = methodPar.getName();
sb.append((parName + ","));
}
if(methodPars.length != 0)
sb.deleteCharAt((sb.length() - 1));
sb.append(")");
System.out.println(sb.toString());
}
}
}

//直接返回一个代理类的实例
import java.lang.reflect.*;
import java.util.*;
class ProxyDemo
{
public static void main(String[] args)throws Throwable
{
Collection coll2 =
(Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
ArrayList al = new ArrayList();
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
{
System.out.println("前");
Object retVal = method.invoke(al, args);//拿到方法,到目标对象上去执行
System.out.println("后");
return retVal;
}
});
coll2.add("sds");
coll2.add("sd2s");
coll2.add("sd3s");

System.out.println(coll2.size());
}
}
}执行结果:

3

--动态生成的类实现了Collection接口(可以实现若干接口,可以接受若干接口字节码的参数),生成的类

有Collection接口中的所有的方法和一个如下接受InvocationHandler参数的构造方法。

--构造方法接受一个InvocationHandler对象,该对象在当你调用代理类的方法时,将会每次都执行InvocationHandler中的Object invoke(Object proxy, Method method, Object[] args)方法,而这个方法接受的3个参数分别是代理类的对象,执行的方法和方法的参数,然后可以根据方法和参数调用目标对象的该方法,最后将结果返回给代理类。


----------------------
ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: