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());		
	}
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章