ALL is Well!

          敏捷是一條很長(zhǎng)的路,摸索著前進(jìn)著

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            30 隨筆 :: 23 文章 :: 71 評(píng)論 :: 0 Trackbacks
          當(dāng)我們?cè)谑褂肧pring進(jìn)行開(kāi)發(fā)時(shí),我們經(jīng)常使用占位符引用屬性文件的屬性值來(lái)簡(jiǎn)化我們的配置及使我們的配置具有更高的靈活性和通用性。
          使用這種方式的好處這里就不贅述了,這里要講的是怎樣對(duì)此外部屬性文件的屬性值進(jìn)行加密、解密。
          以下是我們熟悉的配置:jdbc.properties
          driver=oracle.jdbc.OracleDriver
          dburl
          =jdbc:oracle:thin:@127.0.0.1:1521:root
          username
          =myusr
          password
          =mypassword

          applicationContext.xml
              <bean id="propertyConfigurer"
                  class
          ="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
                  
          <property name="locations">
                      
          <list>      
                          
          <value>classpath:jdbc.properties</value>
                      
          </list>
                  
          </property>
                  
          <property name="fileEncoding" value="utf-8"/>
              
          </bean> 
              
          <bean id="proxoolDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
                  
          <property name="driverClassName" value="${driver}" />
                  
          <property name="url" value="${dburl}" />
                  
          <property name="username" value="${username}" /> 
                  
          <property name="password" value="${password}" />
              
          </bean>


          但當(dāng)我們配置一個(gè)應(yīng)用的時(shí)候,假設(shè)打成myapp.jar文件,并不希望將一些配置文件一同打在jar里。
          原因是:
          有的配置文件要經(jīng)常改動(dòng),例如:由于環(huán)境的不同,數(shù)據(jù)庫(kù)的連接信息要經(jīng)常變動(dòng),出于安全方面的考慮,密碼要經(jīng)常變換。
          一旦配置文件有了修改,就要停止myapp.jar程序,重新打包,再啟動(dòng)myapp.jar,這樣無(wú)疑對(duì)于維護(hù)人員來(lái)說(shuō)是很杯具的。

          如果我們將配置文件放在myapp.jar外面,那么每次修改配置文件后,只要重啟myapp.jar即可。

          在配置一些敏感屬性的時(shí)候(例如密碼等),需要對(duì)其進(jìn)行加密。
          我們期望看到的jdbc.properties的內(nèi)容是這樣的:

          jdbc.properties
          driver
          =oracle.jdbc.OracleDriver
          dburl
          =jdbc:oracle:thin:@127.0.0.1:1521:root
          username
          =myusr
          password
          ={3DES}VwHsU01hJOqskgCppbmTXg==
          對(duì)password屬性值進(jìn)行3DES加密(這里提供了對(duì)加密方式的配置),其他的屬性值不變。
          既達(dá)到了安全的效果,又讓配置清晰明了。

          好了,讓我們開(kāi)始來(lái)實(shí)現(xiàn)我們的需求吧。
          我們從org.springframework.beans.factory.config.PropertyPlaceholderConfigurer這個(gè)類入手。
          因?yàn)橹拔覀兌际怯眠@個(gè)類來(lái)完成對(duì)外部屬性文件的引用的。
          讀了一下這個(gè)類的代碼,沒(méi)發(fā)現(xiàn)能入手的地方,繼續(xù)找它的父類。
          最終,PropertiesLoaderSupport.java 這個(gè)抽象類被我們發(fā)現(xiàn)了。其中的loadProperties方法便是我們的入口。
          看此方法的注釋大意是:加載屬性到已給出的實(shí)例(翻譯的很白癡,汗)。
          原來(lái)Spring先是生成一個(gè)Properties的實(shí)例,然后通過(guò)這個(gè)loadProperties方法,將屬性的鍵值對(duì)設(shè)置到該實(shí)例中。該實(shí)例相當(dāng)于一個(gè)籃子,進(jìn)入方法時(shí),是一個(gè)空籃子,待方法返回時(shí),將籃子裝滿。
          以下請(qǐng)看代碼,對(duì)該段代碼進(jìn)行簡(jiǎn)單理解:

              /**
               * Load properties into the given instance.
               * 
          @param props the Properties instance to load into
               * 
          @throws java.io.IOException in case of I/O errors
               * 
          @see #setLocations
               
          */

              
          protected void loadProperties(Properties props) throws IOException {
                  
          if (this.locations != null{
                      
          for (int i = 0; i < this.locations.length; i++// 遍歷屬性文件列表
                          Resource location = this.locations[i]; // 取得一個(gè)屬性文件句柄
                          if (logger.isInfoEnabled()) {
                              logger.info(
          "Loading properties file from " + location);
                          }

                          InputStream is 
          = null;
                          
          try {
                              is 
          = location.getInputStream();
                              
          if (location.getFilename().endsWith(XML_FILE_EXTENSION)) // 判斷該屬性文件是否為.xml文件
                                  this.propertiesPersister.loadFromXml(props, is);   // 此處略過(guò),我們只考慮.properties文件
                              }

                              
          else {
                                  
          if (this.fileEncoding != null// 加載屬性文件 入口1
                                      this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding));
                                  }

                                  
          else {  // 加載屬性文件 入口2
                                      this.propertiesPersister.load(props, is);
                                  }

                              }

                          }

                          
          catch (IOException ex) {
                              
          if (this.ignoreResourceNotFound) {
                                  
          if (logger.isWarnEnabled()) {
                                      logger.warn(
          "Could not load properties from " + location + "" + ex.getMessage());
                                  }

                              }

                              
          else {
                                  
          throw ex;
                              }

                          }

                          
          finally {
                              
          if (is != null{
                                  is.close();
                              }

                          }

                      }

                  }

              }

          在入口1、入口2處實(shí)現(xiàn)load接口的是org.springframework.util.DefaultPropertiesPersister.java。
          分別看一下這兩個(gè)方法:
          1.void load(Properties props, Reader reader) // 入口1分支
          2.load(Properties props, InputStream is)     // 入口2分支

          先看入口2的方法吧,因?yàn)樗鼘⑹潜晃覀兲蕴姆椒āT蚴撬贿m合我們改造
          以下是入口load方法的實(shí)現(xiàn):
              public void load(Properties props, InputStream is) throws IOException {
                  props.load(is); 
          // props為java.util.Properties對(duì)象。
              }

          props為java.util.Properties對(duì)象,所以想要在這里做文章會(huì)比較麻煩。

          所以我選擇入口1.

          public void load(Properties props, Reader reader) throws IOException {
                  
          if (loadFromReaderAvailable) {
                      
          // On JDK 1.6+
                      props.load(reader); // 入口3
                  }

                  
          else {
                      
          // Fall back to manual parsing.
                      doLoad(props, reader); // 入口4
                  }

              }

          入口3也被放棄,理由同放棄入口2。
          讓我們看看入口4的具體實(shí)現(xiàn):
              protected void doLoad(Properties props, Reader reader) throws IOException {
                  BufferedReader in 
          = new BufferedReader(reader);
                  
          while (true{
                      String line 
          = in.readLine();
                      
          if (line == null{
                          
          return;
                      }

                      line 
          = StringUtils.trimLeadingWhitespace(line);
                      
          if (line.length() > 0{
                          
          char firstChar = line.charAt(0);
                          
          if (firstChar != '#' && firstChar != '!'{
                              
          while (endsWithContinuationMarker(line)) {
                                  String nextLine 
          = in.readLine();
                                  line 
          = line.substring(0, line.length() - 1);
                                  
          if (nextLine != null{
                                      line 
          += StringUtils.trimLeadingWhitespace(nextLine);
                                  }

                              }

                              
          int separatorIndex = line.indexOf("=");
                              
          if (separatorIndex == -1{
                                  separatorIndex 
          = line.indexOf(":");
                              }

                              
          // 從這里開(kāi)始便是我們要改造的地方了。
                              
          // 得到value后,我們將value進(jìn)行解密,然后再裝到props這個(gè)籃子里。
                              String key = (separatorIndex != -1 ? line.substring(0, separatorIndex) : line);
                              String value 
          = (separatorIndex != -1? line.substring(separatorIndex + 1) : "";
                              key 
          = StringUtils.trimTrailingWhitespace(key);
                              value 
          = StringUtils.trimLeadingWhitespace(value);
                              props.put(unescape(key), unescape(value));
                          }

                      }

                  }

              }


          知道了要改造的地方,那么我們寫(xiě)代碼吧。
          新建類:DecryptPropertyPlaceholderConfigurer.java繼承 PropertyPlaceholderConfigurer.java
          新建類:DecryptPropertiesPersister.java繼承DefaultPropertiesPersister.java

          由于
          locations、propertiesPersister、fileEncoding、ignoreResourceNotFound 這些變量在抽象類PropertiesLoaderSupport.java中并沒(méi)有提供set方法。
          所以我們?cè)贒ecryptPropertyPlaceholderConfigurer.java聲明這些成員變量并將父類的覆蓋。

          其中propertiesPersister變量用我們寫(xiě)的DefaultPropertiesPersister類來(lái)實(shí)現(xiàn)。
          具體代碼:
          DecryptPropertyPlaceholderConfigurer.java

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

          import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
          import org.springframework.core.io.Resource;

          public class DecryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
              
          private Resource[]                 locations;
              
          private DecryptPropertiesPersister propertiesPersister    = new DecryptPropertiesPersister();
              
          private String                     fileEncoding = "utf-8";
              
          private boolean                    ignoreResourceNotFound = false;

              @Override
              
          public void setLocations(Resource[] locations) {
                  
          this.locations = locations;
              }


              @Override
              
          public void setFileEncoding(String encoding) {
                  
          this.fileEncoding = encoding;
              }


              @Override
              
          public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
                  
          this.ignoreResourceNotFound = ignoreResourceNotFound;
              }


              @Override
              
          public void loadProperties(Properties props) throws IOException {
                  
          if (this.locations != null{
                      
          for (int i = 0; i < this.locations.length; i++{
                          Resource location 
          = this.locations[i];
                          InputStream is 
          = null;
                          
          try {
                              is 
          = location.getInputStream();
                              
          if (location.getFilename().endsWith(XML_FILE_EXTENSION)) {
                                  
          this.propertiesPersister.loadFromXml(props, is);
                              }
           else {
                                  
          this.propertiesPersister.doLoad(props, new InputStreamReader(is,
                                      
          this.fileEncoding));
                              }

                          }
           catch (IOException ex) {
                              
          if (this.ignoreResourceNotFound) {
                                  
          if (logger.isWarnEnabled()) {
                                      logger.warn(
          "Could not load properties from " + location + ""
                                          
          + ex.getMessage());
                                  }

                              }
           else {
                                  
          throw ex;
                              }

                          }
           finally {
                              
          if (is != null{
                                  is.close();
                              }

                          }

                      }

                  }

              }

          }

          DecryptPropertiesPersister.java
          import java.io.BufferedReader;
          import java.io.IOException;
          import java.io.Reader;
          import java.util.Properties;

          import org.springframework.util.DefaultPropertiesPersister;
          import org.springframework.util.StringUtils;

          public class DecryptPropertiesPersister extends DefaultPropertiesPersister {
              @Override
              
          protected void doLoad(Properties props, Reader reader) throws IOException {
                  BufferedReader in 
          = new BufferedReader(reader);
                  
          while (true{
                      String line 
          = in.readLine();
                      
          if (line == null{
                          
          return;
                      }

                      line 
          = StringUtils.trimLeadingWhitespace(line);
                      
          if (line.length() == 0{
                          
          continue;
                      }

                      
          char firstChar = line.charAt(0);
                      
          if (firstChar != '#' && firstChar != '!'{
                          
          while (endsWithContinuationMarker(line)) {
                              String nextLine 
          = in.readLine();
                              line 
          = line.substring(0, line.length() - 1);
                              
          if (nextLine != null{
                                  line 
          += StringUtils.trimLeadingWhitespace(nextLine);
                              }

                          }

                          
          int separatorIndex = line.indexOf("=");
                          
          if (separatorIndex == -1{
                              separatorIndex 
          = line.indexOf(":");
                          }

                          String key 
          = (separatorIndex != -1 ? line.substring(0, separatorIndex) : line);
                          String value 
          = (separatorIndex != -1? line.substring(separatorIndex + 1) : "";
                          key 
          = StringUtils.trimTrailingWhitespace(key);

                  
          // 從這里開(kāi)始,我們要關(guān)注了。
                          value = StringUtils.trimLeadingWhitespace(value);
                          
          // 對(duì)加密的屬性進(jìn)行3DES解密
                          value = decrypt("key", value);// 解密方法略 密鑰配置的方法略
                          props.put(unescape(key), unescape(value));
                      }

                  }

              }

              
              
          private String decrypt(String key, String str) {
                  
          if(org.apache.commons.lang.StringUtils.isEmpty(str)) {
                      
          return "";
                  }

                  
          // 解密方法
                  if(key.startsWith("{3DES}")) {
                      
          // 解密 略
                  }
           
                  
          return str;
              }

          }

          修改applicationContext.xml文件如下:
              <bean id="propertyConfigurer"
                  
          class="com.jn.spring.DecryptPropertyPlaceholderConfigurer">
                  
          <property name="locations">
                      
          <list>      
                          
          <value>classpath:jdbc.properties</value>
                      
          </list>
                  
          </property>
                  
          <property name="fileEncoding" value="utf-8"/>
              
          </bean> 
              
          <bean id="proxoolDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
                  
          <property name="driverClassName" value="${driver}" />
                  
          <property name="url" value="${dburl}" />
                  
          <property name="username" value="${username}" /> 
                  
          <property name="password" value="${password}" />
              
          </bean>

          這樣就完成了。在Spring進(jìn)行加載的時(shí)候,debug看看是否解析對(duì)了就OK了。

          注:由于這里主要講解的是如果通過(guò)擴(kuò)展Spring而實(shí)現(xiàn)對(duì)外部屬性文件的屬性值進(jìn)行加密,而不是介紹加密解密方法,所以關(guān)于加密解密方法略。
          google一下到處都是。

          本文為原創(chuàng),歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處BlogJava

          posted on 2010-09-30 14:34 李 明 閱讀(2745) 評(píng)論(0)  編輯  收藏 所屬分類: Spring
          主站蜘蛛池模板: 昭平县| 青神县| 南安市| 阜城县| 习水县| 杂多县| 陇西县| 盐池县| 金昌市| 米脂县| 冷水江市| 合作市| 贡山| 定安县| 罗江县| 双城市| 馆陶县| 祁阳县| 长泰县| 乃东县| 潜山县| 兴海县| 青浦区| 开平市| 凤山市| 镶黄旗| 哈尔滨市| 庆城县| 页游| 阳原县| 滁州市| 新民市| 正阳县| 阳新县| 都昌县| 鄂尔多斯市| 徐水县| 姚安县| 修文县| 仙游县| 定襄县|