本文是對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());
}
}