10.类加载器
类加载器:加载类的工具
在java程序中用到一个类,出现了这个类的名字。java虚拟机首先将这个类的字节码加载到内存中,通常这个类的字节码的原始信息放在硬盘上的classpath指定的目录下,把.class文件的内容加载到内存里面来,再对它进行处理,处理之后的结果就是字节码。这些工作就是类加载器在操作。
jvm中有多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap(最顶级的类加载器)。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
简单概括:当要加载一个类时,会先检查是否已经加载,若未加载则委托父类加载,当所有长辈(父类,父类的父类....)都无法加载,最后自己加载 ,加载不了就抛异常
classloader部分源码:
<span style="font-size:14px;">public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}</span>
<pre name="code" class="java"><span style="font-size:14px;">protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);//<span style="color:#ff0000;">检查是否已经加载</span>
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//<span style="color:#ff0000;">让父类去加载</span>
} else {
c = findBootstrapClassOrNull(name);//<span style="color:#ff0000;">让</span><span style="font-family: Arial; font-size: 14px; line-height: 26px; text-align: -webkit-center;"><span style="color:#ff0000;">BootStrap去加载</span></span>
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//<strong> If still not found, then invoke findClass in order</strong>
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//<span style="color:#ff0000;">自己去加载类</span>
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);//<span style="color:#ff0000;">对类进行解析 验证</span>
}
return c;
}
}</span>
委托机制的优点:可以集中管理,不会产生多字节码重复的现象。防止一些重要的类被覆盖
补充:面试题
可不可以自己写个类为:java.lang.System呢?
回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。
第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。
体现委托机制的示例:
自定义类加载器
自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而通常不用覆写loadClass()方法。
因为由上面classloader的源代码可以看到 加载器最终是调用findClass(String name)方法去加载类的 所以只需覆盖findClass
保留了原先的委托机制
这是一种模板方法设计模式。这样就保留了loadClass()方法中的流程(这个流程就是先找父级,找不到再调用自定义的类加载器),而我们只需复写findClass方法,实现局部细节就行了。
简单示例:
<span style="font-size:14px;">import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
@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();// 定义字节数组流
int b = -1;
while ((b = ips.read()) != -1) {
bos.write(b);
}
ips.close();
byte[] buf = bos.toByteArray();// 取出字节数组流中的数据
System.out.println("aaaaaa");
return defineClass(null, buf, 0, buf.length);// <span style="color:#ff0000;">加载进内存</span>
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
// return super.findClass(name);
}
private String classDir;
// 带参数的构造函数
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
}
</span>
<span style="font-size:14px;">
</span>
<pre name="code" class="java"><span style="font-size:14px;">public class ClassLoaderDemo {
public static void main(String[] args) throws Exception {
//将用自定义的类加载器加载.class文件
Class clazz=new MyClassLoader("d://").loadClass("SingleInstance");
Object o = clazz.newInstance();//获取Class类的实例对象
System.out.println(o);
}
} </span>
注意:SingleInstance.class这时不能在classPath目录下 否则会被AppClassLoader加载
若要实现class文件加密则添加加密方法 在此就不做演示
11.代理
代理的概念与作用
生活中的代理武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?程序中的代理要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 (参看下页的原理图)如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很
易。
AOP的概念:
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示: 安全 事务 日志StudentService ------|----------|------------|-------------CourseService ------|----------|------------|-------------MiscService ------|----------|------------|-------------用具体的程序代码描述交叉业务:method1 method2 method3{ { { ------------------------------------------------------切面.... .... ......------------------------------------------------------切面} } }交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:------------------------------------------------------切面func1 func2 func3{ { { .... .... ......} } }------------------------------------------------------切面使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术
AOP的应用:struts的拦截器 和spring的Transaction
动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:1.在调用目标方法之前2.在调用目标方法之后3.在调用目标方法前后4.在处理目标方法异常的catch块中
public class tank implements moveable{
@Override
public void run() {
System.out.println("run-----------");
}
}
public interface moveable {
public void run();
}<span style="color:#ff0000;">
</span>
public class timeHandle implements InvocationHandler{
private moveable target;
public timeHandle(moveable m) {
super();
this.target = m;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(df.format(System.currentTimeMillis()));
method.invoke(target,null);
System.out.println(df.format(System.currentTimeMillis()));
return null;
}
}
public class <strong>launch</strong> {
public static void main(String[] args) throws Throwable {
moveable m = new tank();
InvocationHandler h = new timeHandle(m);
<span style="white-space:pre"> </span>//<span style="color:#ff0000;">通过newProxyInstance()方法生成代理类对象<span style="white-space:pre"> </span></span>
Object proxy = Proxy.newProxyInstance(m.getClass().getClassLoader(),
new Class[] { moveable.class }, h);
System.out.println(proxy.getClass());
InvocationHandler invocationHandler=Proxy.getInvocationHandler(proxy);
Method method=Class.forName("proxy.moveable").getMethod("run",null);
invocationHandler.invoke(proxy, method, null);
}
}
<strong>结果:class proxy.$Proxy0
2015-01-30 14:53:59
run-----------
2015-01-30 14:53:59</strong>
<strong>
</strong>
<strong><span style="color:#ff0000;">此时生成的代理类如下$tank</span></strong><pre name="code" class="java">public class $tank implements moveable{
InvocationHandler handler;//<span style="color:#ff0000;">生成代理类过程中 初始化</span>
@Override
public void run() throws Throwable {
Method method;
try {
<span style="white-space:pre"> </span>//<span style="color:#ff0000;">"run"是生成代理类过程中 根据方法名动态生成</span>
method = this.getClass().getMethod("run");
<span style="white-space:pre"> </span><span style="color:#ff0000;">//参数一:代理类对象 参数二:要调用的方法 参数三:方法的参数</span>
<pre name="code" class="java">
handler.invoke(this, method, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
整个过程的时序图如下: