一. 關于數據庫.
當今的數據處理大致可以分成兩大類:聯機事務處理OLTP(on-line transaction processing)、聯機分析處理OLAP(On-Line Analytical Processing)。OLTP是傳統的關系型數據庫的主要應用,主要是基本的、日常的事務處理,例如銀行交易。OLAP是數據倉庫系統的主要應用,支持復雜的分析操作,側重決策支持,并且提供直觀易懂的查詢結果。下表列出了OLTP與OLAP之間的比較。
OLTP OLAP
用戶 操作人員,低層管理人員 決策人員,高級管理人員
功能 日常操作處理 分析決策
DB 設計 面向應用 面向主題
數據 當前的, 最新的細節的, 二維的分立的 歷史的, 聚集的, 多維的集成的, 統一的
存取 讀/寫數十條記錄 讀上百萬條記錄
工作單位 簡單的事務 復雜的查詢
用戶數 上千個 上百個
DB 大小 100MB-GB 100GB-TB
二. Java中的類反射:
反射就是把Java類中的各種成分映射成相應的java類.
Reflection 是 Java 程序開發語言的特征之一,它允許運行中的 Java 程序對自身進行檢查,或者說“自審”,并能直接操作程序的內部屬性。
1.檢測類:
1.1 reflection的工作機制
考慮下面這個簡單的例子,讓我們看看 reflection 是如何工作的。
import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[]) {
try {
Class c = Class.forName(args[0]);
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
}
catch (Throwable e) {
System.err.println(e);
}
}
}
按如下語句執行:
java DumpMethods java.util.Stack
它的結果輸出為:
public java.lang.Object java.util.Stack.push(java.lang.Object)
public synchronized java.lang.Object java.util.Stack.pop()
public synchronized java.lang.Object java.util.Stack.peek()
public boolean java.util.Stack.empty()
public synchronized int java.util.Stack.search(java.lang.Object)
這樣就列出了java.util.Stack 類的各方法名以及它們的限制符和返回類型。
這個程序使用 Class.forName 載入指定的類,然后調用 getDeclaredMethods 來獲取這個類中定義了的方法列表。java.lang.reflect.Methods 是用來描述某個類中單個方法的一個類。
1.2 Java類反射中的主要方法
對于以下三類組件中的任何一類來說 -- 構造函數、字段和方法 -- java.lang.Class 提供四種獨立的反射調用,以不同的方式來獲得信息。調用都遵循一種標準格式。以下是用于查找構造函數的一組反射調用:
Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數類型的公共構造函數,
Constructor[] getConstructors() -- 獲得類的所有公共構造函數
Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數類型的構造函數(與接入級別無關)
Constructor[] getDeclaredConstructors() -- 獲得類的所有構造函數(與接入級別無關)
獲得字段信息的Class 反射調用不同于那些用于接入構造函數的調用,在參數類型數組中使用了字段名:
Field getField(String name) -- 獲得命名的公共字段
Field[] getFields() -- 獲得類的所有公共字段
Field getDeclaredField(String name) -- 獲得類聲明的命名的字段
Field[] getDeclaredFields() -- 獲得類聲明的所有字段
用于獲得方法信息函數:
Method getMethod(String name, Class[] params) -- 使用特定的參數類型,獲得命名的公共方法
Method[] getMethods() -- 獲得類的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數類型,獲得類聲明的命名的方法
Method[] getDeclaredMethods() -- 獲得類聲明的所有方法
1.3開始使用 Reflection:
用于 reflection 的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的 java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類來描述類和接口等。
下面就是獲得一個 Class 對象的方法之一:
Class c = Class.forName("java.lang.String");
這條語句得到一個 String 類的類對象。還有另一種方法,如下面的語句:
Class c = int.class;
或者
Class c = Integer.TYPE;
它們可獲得基本類型的類信息。其中后一種方法中訪問的是基本類型的封裝類 (如 Integer) 中預先定義好的 TYPE 字段。
第二步是調用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。
一旦取得這個信息,就可以進行第三步了——使用 reflection API 來操作這些信息,如下面這段代碼:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
它將以文本方式打印出 String 中定義的第一個方法的原型。
2.處理對象:
如果要作一個開發工具像debugger之類的,你必須能發現filed values,以下是三個步驟:
a.創建一個Class對象
b.通過getField 創建一個Field對象
c.調用Field.getXXX(Object)方法(XXX是Int,Float等,如果是對象就省略;Object是指實例).
例如:
import java.lang.reflect.*;
import java.awt.*;
class SampleGet {
public static void main(String[] args) {
Rectangle r = new Rectangle(100, 325);
printHeight(r);
}
static void printHeight(Rectangle r) {
Field heightField;
Integer heightValue;
Class c = r.getClass();
try {
heightField = c.getField("height");
heightValue = (Integer) heightField.get(r);
System.out.println("Height: " + heightValue.toString());
} catch (NoSuchFieldException e) {
System.out.println(e);
} catch (SecurityException e) {
System.out.println(e);
} catch (IllegalAccessException e) {
System.out.println(e);
}
}
}
顧名思義,反射 (reflection) 機制就像是在吳承恩所寫的西游記中所提及的「照妖鏡」,可以讓類別或對象 (object) 在執行時期「現出原形」。我們可以利用反射機制來深入了解某類(class) 的構造函數 (constructor)、方法 (method)、字段 (field),甚至可以改變字段的值、呼叫方法、建立新的對象。有了反射機制,程序員即使對所想使用的類別所知不多,也能照樣寫程序。反射機制能夠用來呼叫方法,這正是反射機制能夠取代函數指針的原因。
以 Java 來說,java.lang.reflect.Method (以下簡稱 Method) 類別是用來表示某類別的某方法。我們可以透過 java.lang.Class (以下簡稱 Class) 類別的許多方法來取得 Method 對象。Method 類別提供 invoke() 方法,透過 invoke(),此 Method 對象所表示的方法可以被呼叫,所有的參數則是被組織成一個數組,以方便傳入 invoke()。
舉個例子,下面是一個名為 Invoke 的程序,它會將命令列的 Java 類別名稱和要呼叫的方法名稱作為參數。為了簡單起見,我假定此方法是靜態的,且沒有參數:
import java.lang.reflect.*;
class Invoke {
public static void main(String[] args ) {
try {
Class c = Class.forName( args[0] );
Method m = c.getMethod( args[1], new Class [] { } );
Object ret = m.invoke( null, null );
System.out.println(args[0] + "." + args[1] +"() = " + ret );
} catch ( ClassNotFoundException ex ) {
System.out.println("找不到此類別");
} catch (NoSuchMethodException ex ) {
System.out.println("此方法不存在");
} catch (IllegalAccessException ex ) {
System.out.println("沒有權限調用此方法");
} catch (InvocationTargetException ex ) {
System.out.println("調用此方法時發生下列例外:\n" + ex.getTargetException() );
}
}
}
我們可以執行 Invoke 來取得系統的時間:
java Invoke java.lang.System CurrentTimeMillis執行的結果如下所示:
java.lang.System.currentTimeMillis() = 1049551169474我們的第一步就是用名稱去尋找指定的 Class。我們用類別名稱 (命令列的第一個參數) 去呼叫 forName() 方法,然后用方法名稱 (命令列的第二個參數) 去取得方法。getMethod() 方法有兩個參數:第一個是方法名稱 (命令列的第二個參數),第二個是 Class 對象的數組,這個陣例指明了方法的 signature (任何方法都可能會被多載,所以必須指定 signature 來分辨。) 因為我們的簡單程序只呼叫沒有參數的方法,我們建立一個 Class 對象的匿名空數組。如果我們想要呼叫有參數的方法,我們可以傳遞一個類別數組,數組的內容是各個類別的型態,依順序排列。
一旦我們有了 Method 對象,就呼叫它的 invoke() 方法,這會造成我們的目標方法被調用,并且將結果以 Object 對象傳回。如果要對此對象做其它額外的事,你必須將它轉型為更精確的型態。
invoke() 方法的第一個參數就是我們想要呼叫目標方法的對象,如果該方法是靜態的,就沒有對象,所以我們把第一個參數設為 null,這就是我們范例中的情形。第二個參數是要傳給目標方法作為參數的對象數組,它們的型態要符合呼叫 getMethod() 方法中所指定的型態。因為我們呼叫的方法沒有參數,所以我們傳遞 null 作為 invoke() 的第二個參數。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
public class TestRef {
public static void main(String[] args) throws Exception {
TestRef testRef = new TestRef();
Class clazz = TestRef. class ;
System.out.println( " getPackage() = " + clazz.getPackage().getName());
// getModifiers()的返回值可以包含類的種類信息。比如是否為public,abstract,static
int mod = clazz.getModifiers();
System.out.println( " Modifier.isAbstract(mod) = " + Modifier.isAbstract(mod));
System.out.println( " getName() = " + clazz.getName());
System.out.println( " getSuperclass() = " + clazz.getSuperclass().getName());
System.out.println( " getInterfaces() = " + clazz.getInterfaces()); // 實現了哪些Interface
System.out.println( " clazz.getDeclaredClasses() = " + clazz.getDeclaredClasses()); // 包含哪些內部類
System.out.println( " getDeclaringClass() = " + clazz.getDeclaringClass());
// 如果clazz是inner class 那么返回其outer class
System.out.println( " ---------- " );
Constructor[] constructor = clazz.getDeclaredConstructors(); // 返回一組構造函數 Constructor[]
if (constructor != null ) {
for ( int i = 0 ; i < constructor.length; i ++ ) {
System.out.println(constructor[i].getName());
}
}
System.out.println( " ---------- " );
Method[] method = clazz.getDeclaredMethods(); // Method[]
if (method != null ) {
for ( int i = 0 ; i < method.length; i ++ ) {
System.out.println(method[i].getName());
}
}
System.out.println( " ---------- " );
Field[] field = clazz.getDeclaredFields(); // Field[]
if (field != null ) {
for ( int i = 0 ; i < field.length; i ++ ) {
System.out.println(field[i].getName());
System.out.println(field[i].getType().getName());
System.out.println(field[i].get(testRef));
}
}
// 動態生成instance(無參數)
Class clz = Class.forName( " reflection.TestRef " );
Object obj = clz.newInstance();
System.out.println(((TestRef)obj).getStr());
// 動態生成instance(有參數)
Class[] params = new Class[] {String. class , int . class , double . class } ;
Constructor construct = clz.getConstructor(params);
// JDK1.5的情況下可以直接用{"haha",999,100.01}作為參數
Object obj2 = construct.newInstance(new Object[]{"haha",new Integer( 999 ), new Double( 100.01 )} );
System.out.println(((TestRef)obj2).getStr());
// 動態調用method(public method)
Class[] params2 = new Class[] {String. class } ;
Method methods = clz.getMethod( " setStr " , params2);
methods.invoke(testRef, new Object[] { " invoke method " } );
System.out.println(testRef.getStr());
// 動態改變field內容(public field)
Field fields = clz.getField( " str " );
fields.set(testRef, " set field's value " );
System.out.println(testRef.getStr());
}
public TestRef() {
System.out.println( " --- complete TestRef() --- " );
}
public TestRef(String str, int i, double d) {
this .str = str;
this .i = i;
this .d = d;
System.out.println( " --- complete TestRef(String str, int i, double d) --- " );
}
public String str = " I'm a string " ;
int i = 1 ;
double d = 3.14 ;
HashMap map = new HashMap();
public double getD() {
return d;
}
public void setD( double d) {
this .d = d;
}
public int getI() {
return i;
}
public void setI( int i) {
this .i = i;
}
public HashMap getMap() {
return map;
}
public void setMap(HashMap map) {
this .map = map;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this .str = str;
}
}
三. 代理(Proxy)
1.
我們直接從代碼入手吧,我們可以使用一個動態代理類(Proxy),通過攔截一個對象的行為并添加我們需要的功能來完成。Java中的java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口為我們實現動態代理類提供了一個方案,但是該方案針對的對象要實現某些接口;如果針對的目的是類的話,cglib為我們提供了另外一個實現方案。等下會說明兩者的區別。
一、接口的實現方案:
1)首先編寫我們的業務接口(StudentInfoService.java):
public interface StudentInfoService{
void findInfo(String studentName);
}
及其實現類(StudentInfoServiceImpl.java):
public class StudentInfoServiceImpl implements StudentInfoService{
public void findInfo(String name){
System.out.println("你目前輸入的名字是:"+name);
}
}
2)現在我們需要一個日志功能,在findInfo行為之前執行并記錄其行為,那么我們就首先要攔截該行為。在實際執行的過程中用一個代理類來替我們完成。Java中為我們提供了實現動態代理類的方案:
1'處理攔截目的的類(MyHandler.java)
import org.apache.log4j.Logger;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler{
private Object proxyObj;
private static Logger log=Logger.getLogger(MyHandler.class);
public Object bind(Object obj){
this.proxyObj=obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
Object result=null;
try{
//請在這里插入代碼,在方法前調用
log.info("調用log日志方法"+method.getName());
result=method.invoke(proxyObj,args); //原方法
//請在這里插入代碼,方法后調用
}catch(Exception e){
e.printStackTrace();
}
return result;
}
}
2'我們實現一個工廠,為了方便我們使用該攔截類(AOPFactory.java):
public class AOPFactory{
private static Object getClassInstance(String clzName){
Object obj=null;
try{
Class cls=Class.forName(clzName);
obj=(Object)cls.newInstance();
}catch(ClassNotFoundException cnfe){
System.out.println("ClassNotFoundException:"+cnfe.getMessage());
}catch(Exception e){
e.printStackTrace();
}
return obj;
}
public static Object getAOPProxyedObject(String clzName){
Object proxy=null;
MyHandler handler=new MyHandler();
Object obj=getClassInstance(clzName);
if(obj!=null) {
proxy=handler.bind(obj);
}else{
System.out.println("Can't get the proxyobj");
//throw
}
return proxy;
}
}
3)基本的攔截與其工廠我們都實現了,現在測試(ClientTest.java):
public class ClientTest{
public static void main(String[] args){
StudentInfoService studentInfo=(StudentInfoService)AOPFactory.getAOPProxyedObject("StudentInfoServiceImpl");
studentInfo.findInfo("阿飛");
}
}
輸出結果(看你的log4j設置):
[INFO]調用log日志方法findInfo
你目前輸入的名字是:阿飛
這樣我們需要的效果就出來了,業務處理自己在進行,但是我們實現了日志功能
2.再看一個例子:
假設系統由一系列的BusinessObject所完成業務邏輯功能,系統要求在每一次業務邏輯處理時要做日志記錄。這里我們略去具體的業務邏輯代碼。
public interface BusinessInterface {
public void processBusiness();
}
public class BusinessObject implements BusinessInterface {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void processBusiness(){
try {
logger.info("start to processing...");
//business logic here.
System.out.println(“here is business logic”);
logger.info("end processing...");
} catch (Exception e){
logger.info("exception happends...");
//exception handling
}
}
}
這里處理商業邏輯的代碼和日志記錄代碼混合在一起,這給日后的維護帶來一定的困難,并且也會造成大量的代碼重復。完全相同的log代碼將出現在系統的每一個BusinessObject中。
按照AOP的思想,我們應該把日志記錄代碼分離出來。要將這些代碼分離就涉及到一個問題,我們必須知道商業邏輯代碼何時被調用,這樣我們好插入日志記錄代碼。一般來說要截獲一個方法,我們可以采用回調方法或者動態代理。動態代理一般要更加靈活一些,目前多數的AOP Framework也大都采用了動態代理來實現。這里我們也采用動態代理作為例子。
JDK1.2以后提供了動態代理的支持,程序員通過實現java.lang.reflect.InvocationHandler接口提供一個執行處理器,然后通過java.lang.reflect.Proxy得到一個代理對象,通過這個代理對象來執行商業方法,在商業方法被調用的同時,執行處理器會被自動調用。
有了JDK的這種支持,我們所要做的僅僅是提供一個日志處理器。
public class LogHandler implements InvocationHandler {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Object delegate;
public LogHandler(Object delegate){
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = null;
try {
logger.info("method stats..." + method);
o = method.invoke(delegate,args);
logger.info("method ends..." + method);
} catch (Exception e){
logger.info("Exception happends...");
//excetpion handling.
}
return o;
}
}
現在我們可以把BusinessObject里面的所有日志處理代碼全部去掉了。
public class BusinessObject implements BusinessInterface {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void processBusiness(){
//business processing
System.out.println(“here is business logic”);
}
}
客戶端調用商業方法的代碼如下:
BusinessInterface businessImp = new BusinessObject();
InvocationHandler handler = new LogHandler(businessImp);
BusinessInterface proxy = (BusinessInterface) Proxy.newProxyInstance(
businessImp.getClass().getClassLoader(),
businessImp.getClass().getInterfaces(),
handler);
proxy.processBusiness();
程序輸出如下:
INFO: method stats...
here is business logic
INFO: method ends...
至此我們的第一次小嘗試算是完成了。可以看到,采用AOP之后,日志記錄和業務邏輯代碼完全分開了,以后要改變日志記錄的話只需要修改日志記錄處理器就行了,而業務對象本身(BusinessObject)無需做任何修改。并且這個日志記錄不會造成重復代碼了,所有的商業處理對象都可以重用這個日志處理器。