转--黑马程序员-Java学习笔记(类加载器&代理)
-
类加载器及其委托机制
类加载器: 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。
BootStrap不是Java类,其他的类加载器都是Java类
ExtClassLoader,AppClassLoader都是Java类
package com.itheima.day2;
public class ClassLoaderTest {
public static void main(String[] args)
{
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass()
.getName()
//打印结果为AppClassLoader,说明普通类的加载器一般都是它
);
System.out.println(
System.class.getClassLoader()
//打印结果为Null,可以看出System类的加载器其并不是一个Java类
);
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null)
{
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
//打印结果为AppClassLoader,ExtClassLoader,null
//说明ExtClassLoader为AppClassLoader的父类,BootStrap是ExtClassLoader的父类
}
}
打印结果:

他们有各自的管辖范围,如下图:

委托加载机制:
AppClassLoader发起加载要求,会先委托ExtClassLoader加载,ExtClassLoader再委托BootStrap加载;
如果BootStrap找不到,再退给ExtClassLoader,ExtClassLoader找不到,会再退给AppClassLoader;
注意:AppClassLoader是发起者,因此他不会再找自己的子类去加载
类加载的顺序:BootStrap->ExtClassLoader->AppClassLoader
Java.lang.System能否自己写?答:通常不可以,写完之后,加载时还是会委托给BootStrap加载,这样永远找不到自己写的类加载器
自定义类加载器:
1)必须继承ClassLoader
2)loadClass方法继承父类,findClass重写
3)defineClass方法
模板方法设计方式
loaderClass():该类已经设计好流程,复写会破坏原来的流程,这个方法直接继承
findClass(): 该类需要复写,因为找到对应的类之后需要做一些操作,比如解密等等;
操作完之后再将其通过defineClass转换成字节码返回
defineClass(): 将class文件转换成字节码:
MyClassLoader范例:
package com.itheima.day2;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.acl.LastOwnerException;
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String srcPath = args[0]; //源文件路径
String destDir = args[1]; //目的文件目录
FileInputStream fis = new FileInputStream(srcPath);
//获取源文件的文件名
String srcFileName = srcPath.substring(srcPath.lastIndexOf("\\"));
String destPath = destDir+srcFileName;//目标文件路径=目录+文件名
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos); //对类文件进行加密
fis.close();
fos.close();
}
private static void cypher(InputStream is,OutputStream os) throws Exception
{
int b = -1;
while((b = is.read())!= -1)
{
os.write(b^0xff); //通过每个字节异或操作,将Class类加密
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
//loadClass直接继承父类,不需要复写!
return super.loadClass(name);
}
private String classDir;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//获取给定的类
//需要指定的目录以及类名
//如果name给的是带路径的Class,只取最后的.class
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1)+".class";
FileInputStream fis = new FileInputStream(classFileName); //读取源类文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos); //对加密的源类文件解密
fis.close();
byte[] bytes = bos.toByteArray();
// 将byte 数组转换为 Class 类的实例
//@SuppressWarnings("deprecation")
return defineClass(bytes, 0, bytes.length); //返回class的字节码
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir; //创建加载器的时候指定类存在的路径
}
}
MyClassLoader的调用:
package com.itheima.day2;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
//System.out.println(new ClassLoaderAttatchment().toString());
//用自定义的类加载器加载类进行解密
Class clazz = newMyClassLoader("itheimalib").loadClass("com.itheima.day2.ClassLoaderAttatchment");
//编译器不能加载该类(因为该类用自定义的类加载器加载了!),因此下面这种方法不行!
//ClassLoaderAttatchment d1 = (ClassLoaderAttatchment)clazz.newInstance();
//但是其父类编译器可以加载,因此用父类获取实例对象
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
}
}
加密的简单类:
package com.itheima.day2;
import java.util.Date;
public class ClassLoaderAttatchment extends Date {
public String toString()
{
return "Hello,ItHeiMa";
}
}
当父类AppClassLoader默认的文件夹下没有对应的class文件时:
通过自定义的MyClassLoader加载类可以对加密的类文件解密,结果如下:

当父类AppClassLoader默认的文件夹下有对应的class文件时,委托父类加载,不能对加密的类解密,因此会报错:
结果如下:

代理:

AOP:Aspect Oriented Program 面向方面的编程
交叉业务:一个功能穿插到系统的好多个模块当中
AOP的目标就是要使交叉业务模块化
代理是实现AOP功能的核心和关键技术
动态代理:
如果一个类没有接口,CGLIB库可以动态生成一个类的子类,子类也可以作为该类的代理
创建实现Collection接口的动态类,分析Proxy.getProxyClass方法的各个参数:
package com.itheima.day3;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) {
//创建Collection接口的动态类
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
//得到该类的构造函数列表
//以Constructor(para1,para2)的形式打印
System.out.println("----------Proxy ConStructors Begin--------");
Constructor[] constructors = clazzProxy1.getConstructors();
for(Constructor constructor : constructors)
{
StringBuilder sb = new StringBuilder();
sb.append(constructor.getName());
sb.append('(');
//得到构造函数的参数
Class [] clazzParams = constructor.getParameterTypes();
for(Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName());
sb.append(',');
}
if(clazzParams !=null && clazzParams.length !=0)
sb.deleteCharAt(sb.length()-1);
sb.append(')');
System.out.println(sb.toString());
}
//得到该类的方法列表
//以Method(para1,para2)的形式打印
System.out.println("----------Proxy Methods Begin--------");
Method[] methods = clazzProxy1.getMethods();
for(Method method : methods)
{
StringBuilder sb = new StringBuilder();
sb.append(method.getName());
sb.append('(');
//得到构造函数的参数
Class [] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams)
{
sb.append(clazzParam.getName());
sb.append(',');
}
if(clazzParams !=null && clazzParams.length !=0)
sb.deleteCharAt(sb.length()-1);
sb.append(')');
System.out.println(sb.toString());
}
}
}
打印结果如下:

可以看到该动态类只有一个带参数InvocationHandler的构造方法;
可以看到动态类的方法都是继承了Collection和Object
Proxy实例的创建:
{
System.out.println("----------Create Instance Object--------");
//因为clazzProxy1只有一个带InvocationHandler的构造方法,
//因此下面这个方法无法创建实例对象!
//Collection cl = (Collection)clazzProxy1.newInstance();
//需要得到clazzProxy1的构造方法
Constructor cons =clazzProxy1.getConstructor(InvocationHandler.class);
//InvocationHandler是一个接口,因此需要通过自定义一个子类来创建实例对象
class MyInvocationHandler1 implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
Collection proxy1 = (Collection)cons.newInstance(newMyInvocationHandler1());
System.out.println(proxy1); //直接打印结果为null
proxy1.clear(); //调用无返回值类型方法成功,说明已经成功创建了对象
//因为代理调用方法都会调用Handler的invoke方法,返回了null值,而size()方法返回值是int类型
//proxy1.size(); //调用有返回值类型的方法失败
//通过匿名内部类创建实例对象
Collection proxy2= (Collection)cons.newInstance(newInvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
//上面是通过多个步骤来创建Collection动态类的实例对象
//也可以一步创建动态类的实例对象
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" runtime = "+(endTime-beginTime));
return retVal;
}
}
);
proxy3.add("zxx");//调用一次就会找一次InvocationHandler的invoke方法
proxy3.add("bxd");
System.out.println(proxy3.size());
}
运行结果:
可以看出add运行一次,就调用Handler的invoke一次;
调用一次size(),也调用了一次invoke

Handler中的invoke方法参数解析:
public Object invoke(Object proxy, Method method, Object[] args)
比如proxy3.add("zxx");
proxy对应proxy3;
add对应method.invoke;
args对应"zxx";
而Handler的invoke方法的返回值obj,就对应于proxy3.add()的返回值
也就是说,method.invoke(target,args)的返回值等于proxy3.add的返回值

从Object继承的方法,hashCode,toString,equals会委托给Handler,其他的方法不委托!

将切面的系统功能(比如说日志功能log)封装为一个对象,然后将该对象传递给Handler
Advice:契约,将切面的系统功能封装到契约中
{
System.out.println("----------Proxy4 Instance Object--------");
//抽取target和Handler的invoke方法中的额外功能
final ArrayList target = new ArrayList();
//封装完后,只需要传递目标对象和所需添加的系统功能,即可得到一个代理
Collection proxy4 = (Collection)getProxy(target,newMyAdvice());
proxy4.add("zxx");//调用一次就会找一次InvocationHandler的invoke方法
proxy4.add("bxd");
proxy4.add("by");
System.out.println(proxy4.size());
}
private static Object getProxy(final Object target,final Advice advice) {
Object proxy4 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
//new Class[]{Collection.class},
target.getClass().getInterfaces(), //跟目标的接口一致
new InvocationHandler(){
//ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
/*
//封装到advice中的beforeMethod方法中
long beginTime = System.currentTimeMillis();
//留在invoke方法中
Object retVal = method.invoke(target, args);
//封装到advice中的afterMethod方法中
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" runtime = "+(endTime-beginTime));
*/
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy4;
}
简单的Advice接口:
package com.itheima.day3;
import java.lang.reflect.Method;
public interface Advice {//契约,你想添加的切面系统功能封装到其内
void beforeMethod();
void afterMethod(Method method);
}
实现Advice接口的子类:
package com.itheima.day3;
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
long beginTime = 0;
@Override
public void beforeMethod() {
// TODO Auto-generated method stub
beginTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Method method) {
// TODO Auto-generated method stub
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" runtime = "+(endTime-beginTime));
}
}
运行结果:

总结:
类加载器:将class文件的字节码装载到内存中
主要有BootStrap,ExtClassLoader,AppClassLoader三类加载器
BootStrap不是Java类,其他的类加载器都是Java类
AppClassLoader->ExtClassLoader->BootStrap,后者为前者的父类
委托机制:
子类加载器发起加载要求,会先委托自身的父类加载,一直委托到顶层父类加载器为止,如果父类加载器找不到加载内容,会再传递回子类加载;
注意,子类加载器不会再委托自身的子类加载器去加载!
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
自定义类加载器:
1)必须继承ClassLoader
2)loadClass方法继承父类,findClass方法重写
3)defineClass方法返回findClass处理过的class文件
来源: http://blog.163.com/ljj1219@126/blog/static/143633241201461911327/

浙公网安备 33010602011771号