java类加载器和动态代理

本文是对java高新技术-类加载器及动态代理技术的学习总结。这部分内容以前基本没接触过,总结中尽量将涉及的所有知识描述清楚,并记录张老师所讲的代码示例。
 
类加载器

将.class文件从硬盘装载到内存,并进行一些处理,得到类的字节码文件,这些就是类加载器的工作
java虚拟机中可以安装多个类加载器,系统默认有3个主要的类加载器,每个类加载器负责加载特定位置的java类:BootStrap、ExtClassLoader、AppClassLoader。
类加载器也是java类,也需要被类加载器加载,所以必然有一个类加载器不是java类,这就是BootStrap,它是用C++写的二进制代码,是嵌套到JVM内核中的,JVM启动时就存在。

类加载器之间的父子关系和管辖范围


图解:
1. java类加载器是一个树状结构,BootStrap、ExtClassLoader、AppClassLoader之间依次有父子关系,用户还可以定义自己的类加载器。
2. 自定义类加载器通过指定一个父加载器而挂接到类加载器树上。创建空参数ClassLoader对象时,默认使用ClassLoader.getSystemClassLoader()方法返回值作为父加载器,带参数的ClassLoader构造函数可以直接接收父加载器对象。
3. 每个层次的类加载器都有自己负责加载的类所在的范围。

jre/lib/rt.jar中是java系统自带的基础类,如java.utils中的类、System类、String类和集合类等;
jre/lib/ext是java的扩展功能包,用户也可以将自定义的类放到这个目录;
CLASSPATH就是操作系统设置的CLASSPATH环境变量,一般是“.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar”,特别注意的是最前面的"."是必须的,代表当前路径,最后面不需要再加";"(末尾的分号表示在前面的目录找不到时又在当前目录找)。
注:dt.jar是java运行环境的类库,tool.jar是工具类库(编译java类是用到)。
环境变量Path中存放的是一些可执行文件的路径,这样就可以在任意目录执行这些可执行文件,Path中有%JAVA_HOME%\bin,这样可以在任何地方执行javac等命令。

/*
 类加载器的一个简单演示
 */
public class ClassLoaderDemo {
	public static void main(String[] args) {
		//直接运行时可以看到ClassLoaderDemo是由AppClassLoader加载,而如果用eclipse的打包工具将ClassLoaderDemo输出成jre/lib/ext目录下的itcast.jar包,再在eclipse中运行这个类,运行结果显示为ExtClassLoader.
		System.out.println(ClassLoaderDemo.class.getClassLoader().getClass().getName());
		//System类的类加载器是BootStrap,这个是C++类,不是java类,所以返回null
		System.out.println(System.class.getClassLoader());
		//可以用ClassLoader引用变量指向系统默认的3个主要类加载器对象,ClassLoader中一个抽象基类
		ClassLoader loader=ClassLoaderDemo.class.getClassLoader();
		//打印类加载器的继承关系
		while(loader!=null){
			System.out.println(loader.getClass().getName());
			loader=loader.getParent();
		}
		System.out.println(loader);
		//当前线程的类加载器
		System.out.println(Thread.currentThread().getContextClassLoader().getClass().getName());
	    //ClassLoader对象的默认父加载器
		System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
	}
}
/*
 运行结果:
sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader
*/ 
类加载器的委托机制

1. java虚拟机加载一个类的方式:
当前线程的类加载器去加载线程中的第一个类;
如果类A中引用了类B(如A继承B),jvm将使用加载类A的类加载器去加载类B;
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

2. 每个类加载器加载类时,又先委托其上级类加载器
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式
类加载器一级级委托到BootStrap类加载器,当BootStrap加载不了当前所要加载的类时,然后才一级级回退到子孙类加载器去进行真正的加载;当回退到最初的发起者类加载器时,如果它自己也不能完成类的装载,就抛出ClassNotFoundException异常。

可以通过自定义类加载器来演示委托机制,先说明下自定义类加载器的编写原理
1. 自定义类加载器必须继承抽象类ClassLoader,ClassLoader中加载类的方法有loadClass()和findClass()。
2. loadClass(String name)方法执行原理是先找父类加载器去加载类,所有父类加载器都找不到时子类再调用findClass(String name)方法来加载类。
3. 自定义MyClassLoader时,只需要复写findClass()方法,不需要复写loadClass(),这样可以不破坏类加载器的委托机制,也可以让子类按照自己定义的方式去加载类,这也是模板方法设计模式的体现。
4. findClass()方法可以得到Class实例对象,方法内部一般是通过调用defineClass方法来实现,defineClass()方法可以将一个字节数组转换成Class实例。
findClass()可能抛出ClassNotFound异常,defineClass()抛出ClassFormatError错误。

/*
自定义类加载器,包含对.class文件进行加密的操作,加密后的类只能由MyClassLoader来加载,系统自带的类加载器无法加载
*/
import java.io.*;
public class MyClassLoader extends ClassLoader
{
	public static void main(String[] args) throws IOException
	{
		String srcPath=args[0];
		String destDir=args[1];
		String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);
		String destPath=destDir+"\\"+destFileName;
		System.out.println(srcPath+"..."+destPath);
		FileInputStream fis=new FileInputStream(srcPath);
		FileOutputStream fos=new FileOutputStream(destPath);
		cypher(fis,fos);
		fis.close();
		fos.close();
	}
	private static void cypher(InputStream ips,OutputStream ops) throws IOException
	{
		int b=-1;
		//对.class文件的每个二进制位与1进行异或,实现加密
		while((b=ips.read())!=-1){
			ops.write(b^0xff);
		}		
	}
	//复写findClass()方法
	private String classDir;
	protected Class<?> findClass(String name) throws ClassNotFoundException{
		String classFileName=classDir+"\\"+name+".class";
		System.out.println(classFileName);
		try{
			//读取.class文件到字节数组中,并再次执行异或操作,对.class文件解密
			FileInputStream fis=new FileInputStream(classFileName);
			ByteArrayOutputStream bos=new ByteArrayOutputStream();
			cypher(fis,bos);
			fis.close();
			//调用defineClass()方法,将字节数组转换成Class对象
			byte[] bytes=bos.toByteArray();
			return defineClass(bytes,0,bytes.length);
		}
		catch(Exception e){
			e.printStackTrace();
		}
		return super.findClass(name);
	}
	public MyClassLoader(){
		
	}
	public MyClassLoader(String classDir){
		this.classDir=classDir;
	}
	
}

/*
 编写一个用于测试的java类,让它继承Date类,用这个类的.class文件测试自定义的MyClassLoader
 */
import java.util.Date;
public class ClassLoaderAttachment extends Date {
	public String toString(){
		return "hello itcast!!!";
	}	
}
/*
测试MyClassLoader是否可以加载特定目录下的java类。
*/
import java.util.Date;
public class MyClassLoaderTest {
	public static void main(String[] args) throws Exception
	{
		/*
		bin目录下有未加密的ClassLoaderAttachment().class文件时,下面2句可正常运行,ClassLoaderAttachment类由AppClassLoader加载器加载
		System.out.println(new ClassLoaderAttachment().toString());
		System.out.println(ClassLoaderAttachment.class.getClassLoader().getClass().getName());
		*/
		//CLASSPATH目录下,也即bin目录下有ClassLoaderAttachment.class文件时,由于类加载器的委托机制,如果class文件未加密,下面的代码可正常运行,由AppClassLoader加载,如果已用MyClassLoader加密,则运行时会报ClassFormatError.
		//删除bin目录下的ClassLoaderAttachment.class文件后,运行下面的代码,仍可以正常运行,此时ClassLoaderAttachment类就是由MyClassLoader来加载的
		Class clazz=new MyClassLoader("lib").loadClass("ClassLoaderAttachment");
		//不能写ClassLoaderAttachment类型的引用变量,否则编译时不通过
		Date d1=(Date)clazz.newInstance(); 
		System.out.println(d1);
	}
}
代理
程序中的代理
为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志打印(这些功能贯穿到系统的各个模块中,称为交叉业务)等,可以采取这种做法:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码(这就是静态代理实现方式)。
代理思想的图示如下

代理类可以用来隐藏对外的原始代码体现,只提供方法即可,提高了安全性。

动态代理技术:
1. 要为系统中的各种接口的类增加系统功能,那将需要太多的代理类,全部采用静态代理方法太麻烦。
2. JVM可以在运行期间动态生成出类的字节码,这样动态生成的类往往被用作代理类,即动态代理类。
3. JVM生成的动态类必须实现一个或多个接口,所以JVM的动态类只能用作具有相同接口的目标类的代理。
4. CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类动态生成代理类,可以使用CGLIB库。
5. 代理类的各个方法中通常除了要调用目标类的相应方法和对外返回目标类返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
(1) 在调用目标方法之前
(2) 在调用目标方法之后
(3) 在调用目标方法之前后
(4) 在处理目标方法异常的catch块中

AOP
AOP(Aspect oritented program)即面向方面的编程,AOP的目标是使交叉业务模块化,可以采用将切面代码移动到原始方法的周围。如下图所示,左边是交叉业务结构,右边是用AOP实现的交叉业务模块化。


代理是实现AOP功能的关键和核心技术。

动态代理的基本用法演示:

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
	public static void main(String[] args) throws Exception 
	{
		//Proxy的静态方法getProxyClass获取动态代理Class对象
		Class clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy1.getName());
		//列出动态代理类的所有构造方法和参数签名
		System.out.println(".....begin constructors list.....");
		Constructor[] constructors=clazzProxy1.getConstructors();
		for(Constructor constructor:constructors){
			String name=constructor.getName();
			StringBuilder sbu=new StringBuilder(name);
			sbu.append("(");
			Class[] clazzParams=constructor.getParameterTypes();
			for(Class clazzParam:clazzParams){
				sbu.append(clazzParam.getName()).append(",");
			}
			if(clazzParams!=null&&clazzParams.length!=0){
				sbu.deleteCharAt(sbu.length()-1);
			}
			sbu.append(")");
			System.out.println(sbu.toString());
		}
		//列出动态代理类的所有方法和参数签名
		System.out.println(".....begin Methods list.....");
		Method[] methods=clazzProxy1.getMethods();
		for(Method method:methods){
			String name=method.getName();
			StringBuilder sbu=new StringBuilder(name);
			sbu.append("(");
			Class[] clazzParams=method.getParameterTypes();
			for(Class clazzParam:clazzParams){
				sbu.append(clazzParam.getName()).append(",");
			}
			if(clazzParams!=null&&clazzParams.length!=0){
				sbu.deleteCharAt(sbu.length()-1);
			}
			sbu.append(")");
			System.out.println(sbu.toString());
		}
		//创建动态代理类的实例对象
		//方式一:
		System.out.println(".....begin create instance object.....");
		Constructor constructor=clazzProxy1.getConstructor(InvocationHandler.class);		
		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)constructor.newInstance(new MyInvocationHandler1());
		//代理类中从java.lang.Object类继承的方法中,只有hashCode(),equals()和toString()方法会委托给Invocationhandler执行,运行时会执行InvocationHandler中的invoke()方法,其它从Object类中继承的方法不会委托给Invocationhandle.
		System.out.println(proxy1);
		//没有返回值的clear()方法可正常执行,因为InvocationHandler返回null,与clear()原先的返回类型void不矛盾。
		proxy1.clear();
		//运行时报错,因为size()应该返回整数值,但现在委托给InvocationHandler后返回的是null,不匹配
		//System.out.println(proxy1.size());
		//方式二:匿名内部类方式
		Collection proxy2=(Collection)constructor.newInstance(new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}			
		});
		//方式三:直接使用Proxy的静态方法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();
						//执行目标对象的method方法
						Object retVal=method.invoke(target, args);
						//在目标类的基础功能后进行功能扩展
						long endTime=System.currentTimeMillis();
						System.out.println(method.getName()+" running time of "+(endTime-beginTime));
						return retVal;
					}			
		        }
		);
		//每次调用proxy3的add方法,其实都是执行InvocationHandler子类中的invoke()方法,将proxy3赋给invoke()方法的第一个参数,add代表的Method对象赋经第从此参数,add()的参数"sss"赋给invoke()的第3个参数args
		proxy3.add("sss");
		proxy3.add("wtet");
		Object retVal=(Object)proxy3.add("gfdh");
		System.out.println(retVal);
		System.out.println(proxy3.size());
		//动态代理类的getClass()方法不会委托给InvocationHandler执行,所以仍打印是的$Proxy0,而不是ArrayList
		System.out.println(proxy3.getClass().getName());
	}
}
/*运行结果:
com.sun.proxy.$Proxy0
.....begin constructors list.....
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
.....begin Methods list.....
add(java.lang.Object)
remove(java.lang.Object)
equals(java.lang.Object)
toString()
hashCode()
clear()
contains(java.lang.Object)
isEmpty()
size()
toArray()
toArray([Ljava.lang.Object;)
addAll(java.util.Collection)
iterator()
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getInvocationHandler(java.lang.Object)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait(long,int)
wait(long)
wait()
getClass()
notify()
notifyAll()
.....begin create instance object.....
null
add running time of 1
add running time of 0
add running time of 0
true
size running time of 0
3
com.sun.proxy.$Proxy0
*/
上面的代码在Invocation实现类中创建了目标类实例对象,并在该目标类对象基础功能前后添加了扩展功能代码,完成了代理类的基本思想,但没有实际意义,实际应用代理类时需要从以下2个方面进行扩展:
1. 目标实例对象的注入,不能直接在Invocation实现类中创建,应该作为参数传入
2. 代理类所实现的系统功能,不能在Invocation实现类中写死,应该抽取出接口,将实现接口的实例对象作为参数传入。
/*
代理类的通用方法 
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest2 {
	public static void main(String[] args) {
		final ArrayList target=new ArrayList();
		Collection proxy3 =(Collection)getProxy(target,new MyAdvice());
		proxy3.add("sss");
		proxy3.add("wtet");
		Object retVal=(Object)proxy3.add("gfdh");
		System.out.println(retVal);
		System.out.println(proxy3.size());
	}
	//将目标类实例对象的final引用变量target作为参数注入,供Invocation实现类访问
	//将代理类要实现的扩展功能抽取到一个对象,并把这个对象作为参数传递,Invocation实现类中调用了该对象的方法,就相当于执行了外界提供的扩展功能
	private static Object getProxy(final Object target,final Advice advice) {
		Object proxy3=Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						//在目标类的基础功能前进行功能扩展
						advice.beforeMethod(method);;
						//执行目标对象的method方法
						Object retVal=method.invoke(target, args);
						//在目标类的基础功能后进行功能扩展
						advice.afterMethod(method);
						return retVal;
					}			
		        }
		);
		return proxy3;
	}
}
/*
 将代理类实现的系统功能抽取到Advice接口中
 */
import java.lang.reflect.Method;
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
/*
 实现具体的扩展功能类MyAdvice
 */
import java.lang.reflect.Method;

public class MyAdvice implements Advice
{
	long beginTime=0;
	@Override
	public void beforeMethod(Method method) {
		beginTime=System.currentTimeMillis();		
	}
	@Override
	public void afterMethod(Method method) {	
		long endTime=System.currentTimeMillis();
		System.out.println(method.getName()+" running time of "+(endTime-beginTime));
	}
}
采用工厂模式和配置文件实现AOP框架,不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样很容易按实际需要切换:
/*
采用工厂模式和配置文件实现类似spring的AOP框架。
类中的getBean()方法,根据配置文件可以选择获取javaBean类还是其代理类
*/
package aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
	Properties props=new Properties();
	public BeanFactory(InputStream ips){
		try {
			props.load(ips);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public Object getBean(String name){
		String className=props.getProperty(name);
		Object bean=null;
		try {
			Class clazz=Class.forName(className);
			bean = clazz.newInstance();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		//如果Bean
		if(bean instanceof ProxyFactoryBean){
			Object proxy=null;
			ProxyFactoryBean proxyFactoryBean=(ProxyFactoryBean)bean;			
			try {
				Advice advice=(Advice)Class.forName(props.getProperty(name+".advice")).newInstance();
				Object target=Class.forName(props.getProperty(name+".target")).newInstance();
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTarget(target);
				proxy=proxyFactoryBean.getProxy();
			} catch (Exception e) {
				e.printStackTrace();
			}
			return proxy;
		}
		return bean;
	}
}
/*
 *定义通用代理类,由成员变量advice决定代理类的扩展功能,由target代表目标类实例
 */
package aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactoryBean {
	private Advice advice;//有包名的类不能调用无包名的类,需要把Advice.java文件复制或移到当前包中
	private Object target;
	public Advice getAdvice() {
		return advice;
	}
	public void setAdvice(Advice advice) {
		this.advice = advice;
	}
	public Object getTarget() {
		return target;
	}
	public void setTarget(Object target) {
		this.target = target;
	}
	public Object getProxy(){
		Object proxy3=Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						//在目标类的基础功能前进行功能扩展
						advice.beforeMethod(method);
						//执行目标对象的method方法
						Object retVal=method.invoke(target, args);
						//在目标类的基础功能后进行功能扩展
						advice.afterMethod(method);						
						return retVal;
					}			
		        }
		);
		return proxy3;
	}
}
/*
 测试实现的AOP框架
 */
package aopframework;
import java.io.InputStream;
public class AopFrameworkTest {
	public static void main(String[] args) throws Exception
	{
		InputStream ips=AopFrameworkTest.class.getResourceAsStream("config.properties");
		Object bean=new BeanFactory(ips).getBean("sss");
		System.out.println(bean.hashCode());		
	}
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值