wang123

          2009年3月19日

          Hibernate Shard簡介介紹

          HibernateShard
              多數據庫水平分區解決方案。

          1. 簡介
               Hibernate 的一個擴展,用于處理多數據庫水平分區架構。
               由google工程師 2007年 捐獻給 Hibernate社區。 
               http://www.hibernate.org/414.html
               目前版本:   3.0.0 beta2, 未發GA版。
               條件:Hibernate Core 3.2, JDK 5.0

          2. 水平分區原理
               一個庫表如 Order 存在于多個數據庫實例上。按特定的分區邏輯,將該庫表的數據存儲在這些實例中,一條記錄的主鍵 PK,在所有實例中不得重復。
             
              水平分區在大型網站,大型企業應用中經常采用。 像www.sina.com.cn ,www.163.com  www.bt285.cn www.guihua.org
              目的出于海量數據分散存儲,分散操作,分散查詢以便提高數據處理量和整體數據處理性能。
            
              使用:
                google工程師的設計還是非常好的,完全兼容 Hibernate本身的主要接口。
          Java代碼 復制代碼
          1. org.hibernate.Session   
          2. org.hibernate.SessionFactory    
          3.  org.hibernate.Criteria    
          4.  org.hibernate.Query   

               因此程序員開發變化不大,甚至不需要關心后臺使用了分區數據庫。程序遷移問題不大。而且配置上比較簡明。

          3. 三種策略:
             1) ShardAccessStrategy, 查詢操作時,到那個分區執行。
                默認提供兩個實現:
                順序策略:SequentialShardAccessStrategy, 每個query按順序在所有分區上執行。
                平行策略:ParallelShardAccessStrategy, 每個query以多線程方式并發平行的在所有分區上執行。 此策略下,需要使用線程池機制滿足特定的性能需要,java.util.concurrent.ThreadPoolExecutor。

             2) ShardSelectionStrategy, 新增對象時,存儲到哪個分區。
                   框架默認提供了一個輪詢選擇策略 RoundRobinShardSelectionStrategy, 但一般不這樣使用。
                  通常采用“attribute-based sharding”機制,基于屬性分區。一般是用戶根據表自己實現一個基于屬性分區的策略類ShardSelectionStrategy ,例如,以下WeatherReport基于continent屬性選擇分區:
          Java代碼 復制代碼
          1.   public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy {   
          2. public ShardId selectShardIdForNewObject(Object obj) {   
          3.     if(obj instanceof WeatherReport) {   
          4.         return ((WeatherReport)obj).getContinent().getShardId();   
          5.     }   
          6.     throw new IllegalArgumentException();   
          7. }   

           

             3) ShardResolutionStrategy, 該策略用于查找單個對象時,判斷它在哪個或哪幾個分區上。
                默認使用 AllShardsShardResolutionStrategy ,可以自定義例如:
          Java代碼 復制代碼
          1. public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy {   
          2.     public WeatherReportShardResolutionStrategy(List<ShardId> shardIds) {   
          3.         super(shardIds);   
          4.     }   
          5.   
          6.     public List<ShardId> selectShardIdsFromShardResolutionStrategyData(   
          7.             ShardResolutionStrategyData srsd) {   
          8.         if(srsd.getEntityName().equals(WeatherReport.class.getName())) {   
          9.             return Continent.getContinentByReportId(srsd.getId()).getShardId();   
          10.         }   
          11.         return super.selectShardIdsFromShardResolutionStrategyData(srsd);   
          12.     }   
          13. }  



          4. 水平分區下的查詢

             對于簡單查詢 HibernateShard 可以滿足。

             水平分區下多庫查詢是一個挑戰。主要存在于以下三種操作:
             1) distinct
                   因為需要遍歷所有shard分區,并進行合并判斷重復記錄。
             2) order by
                   類似 1)
             3) aggregation
                   count,sim,avg等聚合操作先分散到分區執行,再進行匯總。
                   是不是有點類似于 MapReduce ? 呵呵。
            
             目前 HibernateShard 不支持 1), 2), 對 3) 部分支持

              HibernateShard 目前通過 Criteria 接口的實現對 聚合提供了較好的支持, 因為 Criteria 以API接口指定了 Projection 操作,邏輯相對簡單。

              而HQL,原生 SQL 還不支持此類操作。

              
          5. 再分區和虛擬分區
                當數據庫規模增大,需要調整分區邏輯和數據存儲時, 需要再分區。
                兩種方式: 1)數據庫數據遷移其他分區; 2) 改變記錄和分區映射關系。這兩種方式都比較麻煩。尤其“改變記錄和分區映射關系”,需要調整 ShardResolutionStrategy。

               HibernateShard 提供了一種虛擬分區層。當需要調整分區策略時,只需要調整虛擬分區和物理分區映射關系即可。以下是使用虛擬分區時的配置創建過程:

          Java代碼 復制代碼
          1.     
          2.   Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>();   
          3. virtualShardMap.put(00);   
          4. virtualShardMap.put(10);   
          5. virtualShardMap.put(21);   
          6. virtualShardMap.put(31);   
          7. ShardedConfiguration shardedConfig =   
          8.     new ShardedConfiguration(   
          9.         prototypeConfiguration,   
          10.         configurations,   
          11.         strategyFactory,   
          12.         virtualShardMap);   
          13. return shardedConfig.buildShardedSessionFactory();  


          6.  局限:
              1)HibernateShard 不支持垂直分區, 垂直+水平混合分區。

              2) 水平分區下 查詢功能受到一定限制,有些功能不支持。實踐中,需要在應用層面對水平分區算法進行更多的考慮。
              3) 不支持跨分區的 關系 操作。例如:刪除A分區上的 s 表,B分區上的關聯子表 t的記錄無法進行參照完整性約束檢查。 (其實這個相對 跨分區查詢的挑戰應該說小的多,也許google工程師下個版本會支持,呵呵)

              4) 解析策略接口似乎和對象ID全局唯一性有些自相矛盾,
          AllShardsShardResolutionStrategy 的接口返回的是給定對象ID所在的 shard ID集合,按理應該是明確的一個 shard ID.

          參考資料:HibernateShard 參考指南。

          posted @ 2009-04-01 18:49 王| 編輯 收藏

          GPS經緯度可以用來Java解析

          現在正開發的定位模塊用到的定位設置是塞格車圣導航設備,發送指令返回的經緯度需要轉換成十進制,再到GIS系統獲取地理信息描述。以后需要要經常用到這方面的知識,隨筆寫下。

           

          將經緯度轉換成十進制

           公式:
              Decimal Degrees = Degrees + minutes/60 + seconds/3600
            例:57°55'56.6" =57+55/60+56.6/3600=57.9323888888888
           
          如把經緯度  (longitude,latitude) (205.395583333332,57.9323888888888)轉換據成坐標(Degrees,minutes,seconds)(205°23'44.1",57°55'56.6")。
          步驟如下:

          1、 直接讀取"度":205

          2、(205.395583333332-205)*60=23.734999999920 得到"分":23

          3、(23.734999999920-23)*60=44.099999995200 得到"秒":44.1

           

          發送定位指令,終端返回的經緯度信息如下:

          (ONE072457A3641.2220N11706.2569E000.000240309C0000400)

          按照協議解析

           

          獲得信息體的經緯度是主要,其它不要管,直接用String類的substring()方法截掉,獲取的經緯度

          3641.2220N11706.2569E http://www.bt285.cn

          Java代碼 復制代碼
          1. package com.tdt.test;   
          2.   
          3. import com.tdt.api.gis.LocationInfo;   
          4.   
          5. /**  
          6.  * <p>Title:坐標轉換 </p>  
          7.  *   
          8.  * <p>Description:</p>  
          9.  *   
          10.  * <p>Copyright: Copyright (c) 2009</p>  
          11.  *   
          12.  * <p>Company:</p>  
          13.  *   
          14.  * @author sunnylocus  
          15.  * @version 1.0 [2009-03-24]  
          16.  *   
          17.  */  
          18. public class LonlatConversion {   
          19.   
          20.     /**  
          21.      *   
          22.      * @param dms 坐標  
          23.      * @param type 坐標類型  
          24.      * @return String 解析后的經緯度  
          25.      */  
          26.     public static String xypase(String dms, String type) {   
          27.         if (dms == null || dms.equals("")) {   
          28.             return "0.0";   
          29.         }   
          30.         double result = 0.0D;   
          31.         String temp = "";   
          32.            
          33.         if (type.equals("E")) {//經度   
          34.             String e1 = dms.substring(03);//截取3位數字,經度共3位,最多180度   
          35.                                             //經度是一倫敦為點作南北兩極的線為0度,所有往西和往東各180度    
          36.             String e2 = dms.substring(3, dms.length());//需要運算的小數   
          37.   
          38.             result = Double.parseDouble(e1);   
          39.             result += (Double.parseDouble(e2) / 60.0D);   
          40.             temp = String.valueOf(result);   
          41.             if (temp.length() > 9) {   
          42.                 temp = e1 + temp.substring(temp.indexOf("."), 9);   
          43.             }   
          44.         } else if (type.equals("N")) {      //緯度,緯度是以赤道為基準,相當于把地球分兩半,兩個半球面上的點和平面夾角0~90度   
          45.             String n1 = dms.substring(02);//截取2位,緯度共2位,最多90度   
          46.             String n2 = dms.substring(2, dms.length());   
          47.   
          48.             result = Double.parseDouble(n1);   
          49.             result += Double.parseDouble(n2) / 60.0D;   
          50.             temp = String.valueOf(result);   
          51.             if (temp.length() > 8) {   
          52.                 temp = n1 + temp.substring(temp.indexOf("."), 8);   
          53.             }   
          54.         }   
          55.         return temp;   
          56.     }   
          57.     public static void main(String[] args) {   
          58.         String info="(ONE072457A3641.2220N11706.2569E000.000240309C0000400)";           
          59.         info=info.substring(11,info.length()-13);   
          60.         //緯度   
          61.         String N = info.substring(0, info.indexOf("N"));   
          62.         //經度   
          63.         String E = info.substring(info.indexOf("N")+1,info.indexOf("E"));   
          64.         //請求gis,獲取地理信息描述   
          65.         double x = Double.parseDouble(CoordConversion.xypase(E,"E"));   
          66.         double y = Double.parseDouble(CoordConversion.xypase(N,"N"));   
          67.         String result =LocationInfo.getLocationInfo("test", x, y); //System.out.println("徑度:"+x+","+"緯度:"+y);   
          68.         System.out.println(result);   
          69.     }   
          70. }  

          運行結果

          在濟南市,位于輕騎路和八澗堡路附近;在環保科技園國際商務中心和濟南市區賢文莊附近。

          posted @ 2009-03-26 17:08 王| 編輯 收藏

          用Java來顯示圖片生成器

          一、本圖片生成器具有以下功能特性:

               1、可以設置圖片的寬度、高度、外框顏色、背景色;

               2、可以設置圖片字體的大小、名稱、顏色;

               3、可以設置輸出圖片的格式,如JPEG、GIF等;

               4、可以將圖片存儲到一個文件或者存儲到一個輸出流;

               5、可以為圖片增加若干條干擾線(在生成隨機碼圖片時可用此特性);

               6、打印在圖片上的文字支持自動換行;

           

          另外,本圖片生成器還用到了模板方法模式。

           

          二、下面列出相關的源代碼

               1、抽象類AbstractImageCreator的源代碼

           /**本代碼在 http://www.bt285.cn  http://www.5a520.cn 已使用了 */
          1. public abstract class AbstractImageCreator {   
          2.     private static Random rnd = new Random(new Date().getTime());   
          3.        
          4.     //圖片寬度   
          5.     private int width = 200;   
          6.        
          7.     //圖片高度   
          8.     private int height = 80;   
          9.        
          10.     //外框顏色   
          11.     private Color rectColor;   
          12.        
          13.     //背景色   
          14.     private Color bgColor;   
          15.        
          16.     //干擾線數目   
          17.     private int lineNum = 0;   
          18.        
          19.     //圖片格式   
          20.     private String formatName = "JPEG";   
          21.        
          22.     //字體顏色   
          23.     private Color fontColor = new Color(000);   
          24.        
          25.     //字體名稱   
          26.     private String fontName = "宋體";   
          27.        
          28.     //字體大小   
          29.     private int fontSize = 15;   
          30.        
          31.   
          32.     //##### 這里省略成員變臉的get、set方法 #####   
          33.   
          34.   
          35.     /**  
          36.      * 畫干擾線  
          37.      */  
          38.     private void drawRandomLine(Graphics graph){   
          39.         for(int i=0;i<lineNum;i++){   
          40.             //線條的顏色   
          41.             graph.setColor(getRandomColor(100155));   
          42.                
          43.             //線條兩端坐標值   
          44.             int x1 = rnd.nextInt(width);   
          45.             int y1 = rnd.nextInt(height);   
          46.                
          47.             int x2 = rnd.nextInt(width);   
          48.             int y2 = rnd.nextInt(height);   
          49.                
          50.             //畫線條   
          51.             graph.drawLine(x1, y1, x2, y2);   
          52.         }   
          53.     }   
          54.        
          55.     /**  
          56.      * 隨機獲取顏色對象  
          57.      */  
          58.     private Color getRandomColor(int base, int range){   
          59.         if((base + range) > 255) range = 255 - base;   
          60.            
          61.         int red = base + rnd.nextInt(range);   
          62.         int green = base + rnd.nextInt(range);   
          63.         int blue = base + rnd.nextInt(range);   
          64.            
          65.         return new Color(red, green, blue);   
          66.     }   
          67.            
          68.                 //該方法內應用了模板方法模式   
          69.     public void drawImage(String text)throws IOException{   
          70.         BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);   
          71.            
          72.         if(rectColor == null) rectColor = new Color(000);   
          73.         if(bgColor == null) bgColor = new Color(240251200);   
          74.            
          75.         //獲取畫布   
          76.         Graphics graph = image.getGraphics();   
          77.            
          78.         //畫長方形   
          79.         graph.setColor(bgColor);   
          80.         graph.fillRect(00, width, height);   
          81.            
          82.         //外框   
          83.         graph.setColor(rectColor);   
          84.         graph.drawRect(00, width-1, height-1);   
          85.            
          86.         //畫干擾線   
          87.         drawRandomLine(graph);   
          88.            
          89.         //畫字符串   
          90.         drawString(graph, text);   
          91.            
          92.         //執行   
          93.         graph.dispose();   
          94.            
          95.         //輸出圖片結果   
          96.         saveImage(image);   
          97.     }   
          98.        
          99.     protected abstract void drawString(Graphics graph, String text);   
          100.        
          101.     protected abstract void saveImage(BufferedImage image)throws IOException;   
          102.        
          103. }  

           

               2、類DefaultImageCreator的源代碼

                    該類將生成的圖片存儲到一個文件中,需要設置outputFilePath成員變量值,該成員變量值表示圖片的存儲全路徑。

          Java代碼 復制代碼
          1. public class DefaultImageCreator extends AbstractImageCreator {   
          2.     private String outputFilePath;   
          3.        
          4.     public String getOutputFilePath() {   
          5.         return outputFilePath;   
          6.     }   
          7.   
          8.     public void setOutputFilePath(String outputFilePath) {   
          9.         this.outputFilePath = outputFilePath;   
          10.     }   
          11.        
          12.     public DefaultImageCreator(){   
          13.            
          14.     }   
          15.        
          16.     public DefaultImageCreator(String outputFilePath){   
          17.         this.outputFilePath = outputFilePath;   
          18.     }   
          19.   
          20.     @Override  
          21.     protected void drawString(Graphics graph, String text) {   
          22.         graph.setColor(getFontColor());   
          23.         Font font = new Font(getFontName(), Font.PLAIN, getFontSize());   
          24.         graph.setFont(font);   
          25.            
          26.         FontMetrics fm = graph.getFontMetrics(font);   
          27.         int fontHeight = fm.getHeight(); //字符的高度   
          28.            
          29.         int offsetLeft = 0;   
          30.         int rowIndex = 1;   
          31.         for(int i=0;i<text.length();i++){   
          32.             char c = text.charAt(i);   
          33.             int charWidth = fm.charWidth(c); //字符的寬度   
          34.   
          35.             //另起一行   
          36.             if(Character.isISOControl(c) || offsetLeft >= (getWidth()-charWidth)){   
          37.                 rowIndex++;   
          38.                 offsetLeft = 0;   
          39.             }   
          40.                
          41.             graph.drawString(String.valueOf(c), offsetLeft, rowIndex * fontHeight);   
          42.             offsetLeft += charWidth;   
          43.         }   
          44.     }   
          45.        
          46.     @Override  
          47.     protected void saveImage(BufferedImage image)throws IOException{   
          48.         ImageIO.write(image, getFormatName(), new File(outputFilePath));   
          49.     }   
          50.   
          51. }  

           

               3、類OutputStreamImageCreator的源代碼

                   該類將生成的圖片存儲到一個輸出流中,需要設置out成員變量值。

          Java代碼 復制代碼
          1. public class OutputStreamImageCreator extends DefaultImageCreator {   
          2.     private OutputStream out ;   
          3.        
          4.     public OutputStream getOut() {   
          5.         return out;   
          6.     }   
          7.   
          8.     public void setOut(OutputStream out) {   
          9.         this.out = out;   
          10.     }   
          11.        
          12.     public OutputStreamImageCreator(){   
          13.            
          14.     }   
          15.        
          16.     public OutputStreamImageCreator(OutputStream out){   
          17.         this.out = out;   
          18.     }   
          19.   
          20.     @Override  
          21.     public String getOutputFilePath() {   
          22.         return null;   
          23.     }   
          24.   
          25.     @Override  
          26.     public void setOutputFilePath(String outputFilePath) {   
          27.         outputFilePath = null;   
          28.     }   
          29.   
          30.     @Override  
          31.     protected void saveImage(BufferedImage image) throws IOException {   
          32.         if(out!=null) ImageIO.write(image, getFontName(), out);   
          33.     }   
          34.        
          35. }  

           

          三、實例代碼

               1、圖片存儲到文件

          StringBuffer sb = new StringBuffer();   
          1. sb.append("中華人民共和國\n");   
          2. sb.append("中華人民共和國\n");   
          3.   
          4. DefaultImageCreator creator = new DefaultImageCreator("c:\\img.jpeg");   
          5. creator.setWidth(150);   
          6. creator.setHeight(100);   
          7. creator.setLineNum(60);   
          8. creator.setFontSize(20);   
          9. creator.drawImage(sb.toString());  

           

          posted @ 2009-03-23 18:49 王| 編輯 收藏

          加密java源代碼

          Java程序的源代碼很容易被別人偷看,只要有一個反編譯器,任何人都可以分析別人的代碼。本文討論如何在不修改原有程序的情況下,通過加密技術保護源代碼。

            一、為什么要加密?

            對于傳統的C或C++之類的語言來說,要在Web上保護源代碼是很容易的,只要不發布它就可以。遺憾的是,Java程序的源代碼很容易被別人偷看。只要有一個反編譯器,任何人都可以分析別人的代碼。Java的靈活性使得源代碼很容易被竊取,但與此同時,它也使通過加密保護代碼變得相對容易,我們唯一需要了解的就是Java的ClassLoader對象。當然,在加密過程中,有關Java Cryptography Extension(JCE)的知識也是必不可少的。

            有幾種技術可以“模糊”Java類文件,使得反編譯器處理類文件的效果大打折扣。然而,修改反編譯器使之能夠處理這些經過模糊處理的類文件并不是什么難事,所以不能簡單地依賴模糊技術來保證源代碼的安全。

            我們可以用流行的加密工具加密應用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。這時,最終用戶在運行應用之前必須先進行解密。但解密之后,最終用戶就有了一份不加密的類文件,這和事先不進行加密沒有什么差別。

            Java運行時裝入字節碼的機制隱含地意味著可以對字節碼進行修改。JVM每次裝入類文件時都需要一個稱為ClassLoader的對象,這個對象負責把新的類裝入正在運行的JVM。JVM給ClassLoader一個包含了待裝入類(比如java.lang.Object)名字的字符串,然后由ClassLoader負責找到類文件,裝入原始數據,并把它轉換成一個Class對象。

            我們可以通過定制ClassLoader,在類文件執行之前修改它。這種技術的應用非常廣泛??在這里,它的用途是在類文件裝入之時進行解密,因此可以看成是一種即時解密器。由于解密后的字節碼文件永遠不會保存到文件系統,所以竊密者很難得到解密后的代碼。

            由于把原始字節碼轉換成Class對象的過程完全由系統負責,所以創建定制ClassLoader對象其實并不困難,只需先獲得原始數據,接著就可以進行包含解密在內的任何轉換。

            Java 2在一定程度上簡化了定制ClassLoader的構建。在Java 2中,loadClass的缺省實現仍舊負責處理所有必需的步驟,但為了顧及各種定制的類裝入過程,它還調用一個新的findClass方法。

            這為我們編寫定制的ClassLoader提供了一條捷徑,減少了麻煩:只需覆蓋findClass,而不是覆蓋loadClass。這種方法避免了重復所有裝入器必需執行的公共步驟,因為這一切由loadClass負責。

            不過,本文的定制ClassLoader并不使用這種方法。原因很簡單。如果由默認的ClassLoader先尋找經過加密的類文件,它可以找到;但由于類文件已經加密,所以它不會認可這個類文件,裝入過程將失敗。因此,我們必須自己實現loadClass,稍微增加了一些工作量。

          二、定制類裝入器

            每一個運行著的JVM已經擁有一個ClassLoader。這個默認的ClassLoader根據CLASSPATH環境變量的值,在本地文件系統中尋找合適的字節碼文件。

            應用定制ClassLoader要求對這個過程有較為深入的認識。我們首先必須創建一個定制ClassLoader類的實例,然后顯式地要求它裝入另外一個類。這就強制JVM把該類以及所有它所需要的類關聯到定制的ClassLoader。Listing 1顯示了如何用定制ClassLoader裝入類文件。

            【Listing 1:利用定制的ClassLoader裝入類文件】

          以下是引用片段:

            // 首先創建一個ClassLoader對象 如 http://www.bt285.cn
            ClassLoader myClassLoader = new myClassLoader();
            // 利用定制ClassLoader對象裝入類文件
            // 并把它轉換成Class對象
            Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
            // 最后,創建該類的一個實例
            Object newInstance = myClass.newInstance();
            // 注意,MyClass所需要的所有其他類,都將通過
            // 定制的ClassLoader自動裝入 

             如前所述,定制ClassLoader只需先獲取類文件的數據,然后把字節碼傳遞給運行時系統,由后者完成余下的任務。

            ClassLoader有幾個重要的方法。創建定制的ClassLoader時,我們只需覆蓋其中的一個,即loadClass,提供獲取原始類文件數據的代碼。這個方法有兩個參數:類的名字,以及一個表示JVM是否要求解析類名字的標記(即是否同時裝入有依賴關系的類)。如果這個標記是true,我們只需在返回JVM之前調用resolveClass。

            【Listing 2:ClassLoader.loadClass()的一個簡單實現】

          以下是引用片段:

          public Class loadClass( String name, boolean resolve )  如:http://www.5a520.cn
            throws ClassNotFoundException {
            try {
            // 我們要創建的Class對象
            Class clasz = null;
            // 必需的步驟1:如果類已經在系統緩沖之中,
            // 我們不必再次裝入它
            clasz = findLoadedClass( name );
            if (clasz != null)
            return clasz;
            // 下面是定制部分
            byte classData[] = /* 通過某種方法獲取字節碼數據 */;
            if (classData != null) {
            // 成功讀取字節碼數據,現在把它轉換成一個Class對象
            clasz = defineClass( name, classData, 0, classData.length );
            }
            // 必需的步驟2:如果上面沒有成功,
            // 我們嘗試用默認的ClassLoader裝入它
            if (clasz == null)
            clasz = findSystemClass( name );
            // 必需的步驟3:如有必要,則裝入相關的類
            if (resolve && clasz != null)
            resolveClass( clasz );
            // 把類返回給調用者
            return clasz;
            } catch( IOException ie ) {
            throw new ClassNotFoundException( ie.toString() );
            } catch( GeneralSecurityException gse ) {
            throw new ClassNotFoundException( gse.toString() );
            }
            } 

              Listing 2顯示了一個簡單的loadClass實現。代碼中的大部分對所有ClassLoader對象來說都一樣,但有一小部分(已通過注釋標記)是特有的。在處理過程中,ClassLoader對象要用到其他幾個輔助方法:

            findLoadedClass:用來進行檢查,以便確認被請求的類當前還不存在。loadClass方法應該首先調用它。

            defineClass:獲得原始類文件字節碼數據之后,調用defineClass把它轉換成一個Class對象。任何loadClass實現都必須調用這個方法。

            findSystemClass:提供默認ClassLoader的支持。如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法),則可以調用該方法嘗試默認的裝入方式。這是很有用的,特別是從普通的JAR文件裝入標準Java類時。

            resolveClass:當JVM想要裝入的不僅包括指定的類,而且還包括該類引用的所有其他類時,它會把loadClass的resolve參數設置成true。這時,我們必須在返回剛剛裝入的Class對象給調用者之前調用resolveClass。

            三、加密、解密

            Java加密擴展即Java Cryptography Extension,簡稱JCE。它是Sun的加密服務軟件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一種擴展。

            JCE沒有規定具體的加密算法,但提供了一個框架,加密算法的具體實現可以作為服務提供者加入。除了JCE框架之外,JCE軟件包還包含了SunJCE服務提供者,其中包括許多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。

            為簡單計,在本文中我們將用DES算法加密和解密字節碼。下面是用JCE加密和解密數據必須遵循的基本步驟:

            步驟1:生成一個安全密匙。在加密或解密任何數據之前需要有一個密匙。密匙是隨同被加密的應用一起發布的一小段數據,Listing 3顯示了如何生成一個密匙。 【Listing 3:生成一個密匙】

          以下是引用片段:

          // DES算法要求有一個可信任的隨機數源
            SecureRandom sr = new SecureRandom();
            // 為我們選擇的DES算法生成一個KeyGenerator對象
            KeyGenerator kg = KeyGenerator.getInstance( "DES" );
            kg.init( sr );
            // 生成密匙
            SecretKey key = kg.generateKey();
            // 獲取密匙數據
            byte rawKeyData[] = key.getEncoded();
            /* 接下來就可以用密匙進行加密或解密,或者把它保存
            為文件供以后使用 */
            doSomething( rawKeyData );  

          步驟2:加密數據。得到密匙之后,接下來就可以用它加密數據。除了解密的ClassLoader之外,一般還要有一個加密待發布應用的獨立程序(見Listing 4)。 【Listing 4:用密匙加密原始數據】

          以下是引用片段:

          // DES算法要求有一個可信任的隨機數源
            SecureRandom sr = new SecureRandom();
            byte rawKeyData[] = /* 用某種方法獲得密匙數據 */;
            // 從原始密匙數據創建DESKeySpec對象
            DESKeySpec dks = new DESKeySpec( rawKeyData );
            // 創建一個密匙工廠,然后用它把DESKeySpec轉換成
            // 一個SecretKey對象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
            SecretKey key = keyFactory.generateSecret( dks );
            // Cipher對象實際完成加密操作
            Cipher cipher = Cipher.getInstance( "DES" );
            // 用密匙初始化Cipher對象
            cipher.init( Cipher.ENCRYPT_MODE, key, sr );
            // 現在,獲取數據并加密
            byte data[] = /* 用某種方法獲取數據 */
            // 正式執行加密操作
            byte encryptedData[] = cipher.doFinal( data );
            // 進一步處理加密后的數據
            doSomething( encryptedData );  

            步驟3:解密數據。運行經過加密的應用時,ClassLoader分析并解密類文件。操作步驟如Listing 5所示。 【Listing 5:用密匙解密數據】

            // DES算法要求有一個可信任的隨機數源
            SecureRandom sr = new SecureRandom();
            byte rawKeyData[] = /* 用某種方法獲取原始密匙數據 */;
            // 從原始密匙數據創建一個DESKeySpec對象
            DESKeySpec dks = new DESKeySpec( rawKeyData );
            // 創建一個密匙工廠,然后用它把DESKeySpec對象轉換成
            // 一個SecretKey對象
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
            SecretKey key = keyFactory.generateSecret( dks );
            // Cipher對象實際完成解密操作
            Cipher cipher = Cipher.getInstance( "DES" );
            // 用密匙初始化Cipher對象
            cipher.init( Cipher.DECRYPT_MODE, key, sr );
            // 現在,獲取數據并解密
            byte encryptedData[] = /* 獲得經過加密的數據 */
            // 正式執行解密操作
            byte decryptedData[] = cipher.doFinal( encryptedData );
            // 進一步處理解密后的數據
            doSomething( decryptedData );  

           

          四、應用實例

            前面介紹了如何加密和解密數據。要部署一個經過加密的應用,步驟如下:

            步驟1:創建應用。我們的例子包含一個App主類,兩個輔助類(分別稱為Foo和Bar)。這個應用沒有什么實際功用,但只要我們能夠加密這個應用,加密其他應用也就不在話下。

            步驟2:生成一個安全密匙。在命令行,利用GenerateKey工具(參見GenerateKey.java)把密匙寫入一個文件: % java GenerateKey key.data

            步驟3:加密應用。在命令行,利用EncryptClasses工具(參見EncryptClasses.java)加密應用的類: % java EncryptClasses key.data App.class Foo.class Bar.class

            該命令把每一個.class文件替換成它們各自的加密版本。

            步驟4:運行經過加密的應用。用戶通過一個DecryptStart程序運行經過加密的應用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,啟動被加密應用的程序】

          以下是引用片段:

            import java.io.*;
            import java.security.*;
            import java.lang.reflect.*;
            import javax.crypto.*;
            import javax.crypto.spec.*;
            public class DecryptStart extends ClassLoader
            {
            // 這些對象在構造函數中設置,
            // 以后loadClass()方法將利用它們解密類
            private SecretKey key;
            private Cipher cipher;
            // 構造函數:設置解密所需要的對象
            public DecryptStart( SecretKey key ) throws GeneralSecurityException,
            IOException {
            this.key = key;
            String algorithm = "DES";
            SecureRandom sr = new SecureRandom();
            System.err.println( "[DecryptStart: creating cipher]" );
            cipher = Cipher.getInstance( algorithm );
            cipher.init( Cipher.DECRYPT_MODE, key, sr );
            }
            // main過程:我們要在這里讀入密匙,創建DecryptStart的
            // 實例,它就是我們的定制ClassLoader。
            // 設置好ClassLoader以后,我們用它裝入應用實例,
            // 最后,我們通過Java Reflection API調用應用實例的main方法
            static public void main( String args[] ) throws Exception {
            String keyFilename = args[0];
            String appName = args[1];
            // 這些是傳遞給應用本身的參數
            String realArgs[] = new String[args.length-2];
            System.arraycopy( args, 2, realArgs, 0, args.length-2 );
            // 讀取密匙
            System.err.println( "[DecryptStart: reading key]" );
            byte rawKey[] = Util.readFile( keyFilename );
            DESKeySpec dks = new DESKeySpec( rawKey );
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
            SecretKey key = keyFactory.generateSecret( dks );
            // 創建解密的ClassLoader
            DecryptStart dr = new DecryptStart( key );
            // 創建應用主類的一個實例
            // 通過ClassLoader裝入它
            System.err.println( "[DecryptStart: loading "+appName+"]" );
            Class clasz = dr.loadClass( appName );
            // 最后,通過Reflection API調用應用實例
            // 的main()方法
            // 獲取一個對main()的引用
            String proto[] = new String[1];
            Class mainArgs[] = { (new String[1]).getClass() };
            Method main = clasz.getMethod( "main", mainArgs );
            // 創建一個包含main()方法參數的數組
            Object argsArray[] = { realArgs };
            System.err.println( "[DecryptStart: running "+appName+".main()]" );
            // 調用main()
            main.invoke( null, argsArray );
            }
            public Class loadClass( String name, boolean resolve )
            throws ClassNotFoundException {
            try {
            // 我們要創建的Class對象
            Class clasz = null;
            // 必需的步驟1:如果類已經在系統緩沖之中
            // 我們不必再次裝入它
            clasz = findLoadedClass( name );
            if (clasz != null)
            return clasz;
            // 下面是定制部分
            try {
            // 讀取經過加密的類文件
            byte classData[] = Util.readFile( name+".class" );
            if (classData != null) {
            // 解密...
            byte decryptedClassData[] = cipher.doFinal( classData );
            // ... 再把它轉換成一個類
            clasz = defineClass( name, decryptedClassData,
            0, decryptedClassData.length );
            System.err.println( "[DecryptStart: decrypting class "+name+"]" );
            }
            } catch( FileNotFoundException fnfe )
            // 必需的步驟2:如果上面沒有成功
            // 我們嘗試用默認的ClassLoader裝入它
            if (clasz == null)
            clasz = findSystemClass( name );
            // 必需的步驟3:如有必要,則裝入相關的類
            if (resolve && clasz != null)
            resolveClass( clasz );
            // 把類返回給調用者
            return clasz;
            } catch( IOException ie ) {
            throw new ClassNotFoundException( ie.toString()
            );
            } catch( GeneralSecurityException gse ) {
            throw new ClassNotFoundException( gse.toString()
            );
            }
            }
            } 

           對于未經加密的應用,正常執行方式如下: % java App arg0 arg1 arg2

            對于經過加密的應用,則相應的運行方式為: % java DecryptStart key.data App arg0 arg1 arg2

            DecryptStart有兩個目的。一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時,DecryptStart還包含一個main過程,它創建解密器實例并用它裝入和運行應用。示例應用App的代碼包含在App.java、Foo.java和Bar.java內。Util.java是一個文件I/O工具,本文示例多處用到了它。完整的代碼請從本文最后下載。

            五、注意事項

            我們看到,要在不修改源代碼的情況下加密一個Java應用是很容易的。不過,世上沒有完全安全的系統。本文的加密方式提供了一定程度的源代碼保護,但對某些攻擊來說它是脆弱的。

            雖然應用本身經過了加密,但啟動程序DecryptStart沒有加密。攻擊者可以反編譯啟動程序并修改它,把解密后的類文件保存到磁盤。降低這種風險的辦法之一是對啟動程序進行高質量的模糊處理。或者,啟動程序也可以采用直接編譯成機器語言的代碼,使得啟動程序具有傳統執行文件格式的安全性。

            另外還要記住的是,大多數JVM本身并不安全。狡猾的黑客可能會修改JVM,從ClassLoader之外獲取解密后的代碼并保存到磁盤,從而繞過本文的加密技術。Java沒有為此提供真正有效的補救措施。

            不過應該指出的是,所有這些可能的攻擊都有一個前提,這就是攻擊者可以得到密匙。如果沒有密匙,應用的安全性就完全取決于加密算法的安全性。雖然這種保護代碼的方法稱不上十全十美,但它仍不失為一種保護知識產權和敏感用戶數據的有效方案。

          posted @ 2009-03-19 12:51 王| 編輯 收藏

          <2009年3月>
          22232425262728
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          導航

          統計

          常用鏈接

          留言簿(3)

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 博客| 黄浦区| 韶山市| 正阳县| 沧州市| 景宁| 全南县| 岑巩县| 彰武县| 九江县| 太康县| 南华县| 渭南市| 大田县| 进贤县| 鄂州市| 安乡县| 隆昌县| 府谷县| 红原县| 固始县| 甘洛县| 汶上县| 上栗县| 长春市| 旬阳县| 滕州市| 青冈县| 改则县| 吉安市| 合作市| 蚌埠市| 南岸区| 禹城市| 剑河县| 伊宁市| 郑州市| 曲水县| 清涧县| 富平县| 乐都县|