ClassPathResource详解 
 
 
 

   ClassPathReource resource=new ClassPathResource("spring_beans.xml"); 
 
 
 
public 
  class ClassPathResource 
  extends
 
 

         在ClassPathResource中,含参数String path的构造函数: 
 
 
 
public ClassPathResource(String 
   path
 
  
this ( 
   path , (ClassLoader) 
   null); 
  
 
 

       } 
 
 
 

    2:上述构造函数指向了另外一个构造函数: 
 
 
 
public ClassPathResource (String 
   path , ClassLoader 
   classLoader
 
  

            Assert. 
   notNull( 
   path, 
   "Path must not be null"); 
  
 
  
pathToUse = StringUtils. 
   cleanPath( 
   path); 
  
 
  
if ( 
   pathToUse .startsWith( 
   "/")) { 
  
 
  
pathToUse = 
   pathToUse
 
  

            } 
  
 
  
this . 
   path = 
   pathToUse; 
  
 
  
this . 
   classLoader = ( 
   classLoader != 
   null ? 
   classLoader : ClassUtils. 
   getDefaultClassLoader()); 
  
 
 

       } 
 
 
 

    能够看到path由StringUtils的cleanPath方法返回了pathToUse。由此,我们找到StringUtils的cleanPath方法 
 
 
 
public 
  abstract 
  class
 
 

      
 
 
 
public 
  static String cleanPath (String 
  path
 
 
if ( 
  path == 
  null) { 
 
 
 
return 
  null
 
 

           } 
 
 
 
pathToUse = 
  replace( 
  path , 
  WINDOWS_FOLDER_SEPARATOR , 
  FOLDER_SEPARATOR); 
 
 
 
int 
  prefixIndex = 
  pathToUse .indexOf( 
  ":"
 
 
prefix = 
  ""
 
 
if ( 
  prefixIndex
 
 
prefix = 
  pathToUse .substring(0, 
  prefixIndex
 
 
pathToUse = 
  pathToUse .substring( 
  prefixIndex
 
 

           } 
 
 
 
if ( 
  pathToUse .startsWith( 
  FOLDER_SEPARATOR)) { 
 
 
 
prefix = 
  prefix + 
  FOLDER_SEPARATOR; 
 
 
 
pathToUse = 
  pathToUse
 
 

           } 
 
 
 

     
 
 
 
pathArray = 
  delimitedListToStringArray( 
  pathToUse, 
  FOLDER_SEPARATOR
 
 
pathElements = 
  new
 
 
int 
  tops
 
 

     
 
 
 
for ( 
  int 
  i = 
  pathArray. 
  length - 1; 
  i >= 0; 
  i
 
 
element = 
  pathArray [ 
  i
 
 
if ( 
  CURRENT_PATH .equals( 
  element)) { 
 
 
 
// Points to current directory - drop it.
 
 

               } 
 
 
 
else 
  if ( 
  TOP_PATH.equals( 
  element)) { 
 
 
 
// Registering top path found.
 
 
tops
 
 

               } 
 
 
 
else
 
 
if ( 
  tops
 
 
// Merging path element with element corresponding to top path.
 
 
tops
 
 

                   } 
 
 
 
else
 
 
// Normal path element found.
 
 
pathElements .add(0, 
  element
 
 

                   } 
 
 
 

               } 
 
 
 

           } 
 
 
 

     
 
 
 
// Remaining top paths need to be retained.
 
 
for ( 
  int 
  i = 0; 
  i < 
  tops; 
  i++) { 
 
 
 
pathElements .add(0, 
  TOP_PATH); 
 
 
 

           } 
 
 
 

     
 
 
 
return 
  prefix + 
  collectionToDelimitedString( 
  pathElements, 
  FOLDER_SEPARATOR
 
 

       } 
 
 
 

        4:StringUtils类中 replace方法 
 
 
 
public 
  static String replace (String 
  inString , String 
  oldPattern , String 
  newPattern
 
 
if (! 
  hasLength( 
  inString ) || ! 
  hasLength( 
  oldPattern) || 
  newPattern == 
  null
 
 
return 
  inString
 
 

           } 
 
 
 
sb = 
  new
 
 
int 
  pos = 0; 
  // our position in the old string
 
 
int 
  index = 
  inString .indexOf( 
  oldPattern
 
 
// the index of an occurrence we've found, or -1
 
 
int 
  patLen = 
  oldPattern.length(); 
 
 
 
while ( 
  index
 
 
sb.append( 
  inString .substring( 
  pos , 
  index
 
 
sb.append( 
  newPattern
 
 
pos = 
  index + 
  patLen; 
 
 
 
index = 
  inString .indexOf( 
  oldPattern, 
  pos
 
 

           } 
 
 
 
sb.append( 
  inString .substring( 
  pos
 
 
// remember to append any characters to the right of a match
 
 
return 
  sb
 
 

       } 
 
 
 

        5:StringUtils类类中的hasLength方法。由此可以看出,同样的方法名,不同的方法签名,然后在其中一个方法中引用另外一个方法。好多类都是这么用的,就像开始的时候的构造函数那样,虽然不知道好处 是什么,但先记下来。 
 
 
 
     
  public 
  static 
  boolean 
  hasLength (String 
  str 
  ) {
 
 
         
  return 
  hasLength((CharSequence) 
  str 
  );
 
 
    }
 
 
     
   public 
   static 
   boolean 
   hasLength (CharSequence 
   str 
   ) {
 
  
         
   return 
   ( 
   str 
   != 
   null 
   && 
   str 
   .length() > 0);
 
 
    }
 
 

        跟踪到这里,可以知道hasLength方法的目的就是str不为空且str的长度大于0。突然发现CharSequence这个类没接触过,来看一下它的源码 
 
 
 
public 
  interface 
   CharSequence{}
 
 
  额,源码没看懂 就不粘贴过来了。
 
 

       7:回到StringUtils的replace方法 
 
 
 

         首先判断传入的三个参数,如果为空后者长度小于0,直接返回inString;那么我看一下这三个参数都是什么: 
 
 
 

        inString :path 这个就是我们传入的文件名 
 
 
 
private 
  static 
  final 
  String 
  WINDOWS_FOLDER_SEPARATOR 
  = 
  "\\" 
  ;
 
 
private 
  static 
  final 
  String 
  FOLDER_SEPARATOR 
  = 
  "/" 
  ;这两个是文件分隔符
 
 

        然后给局部变量index赋值,通过查阅API:       
 
 
 

                       public int  
  indexOf(int ch) 
 
 
 

                  返回指定字符在此字符串中第一次出现处的索引。 
 
 
 

        意思就是在path中查找"\\",例如我写文件的绝对路径是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM,我就需要循环的读取“\\”,接下来while循环中出现了substring方法,继续查阅API: 
 
 
 
String  
   substring(int beginIndex) 
  
 
 

   返回一个新的字符串,它是此字符串的一个子字符串。该子字符串从指定索引处的字符开始,直到此字符串末尾。 
 
 
 
public String substring(int beginIndex,
                        int endIndex)
 
 

   返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 
  beginIndex 处开始,直到索引 
  endIndex - 1 处的字符。因此,该子字符串的长度为 
  endIndex-beginIndex 
 
 
 

        故此 ,第一次循环会把path路径中从0索引开始,直到第一个"\\"之间的内容添加到StringBuffer中,然后再在StringBuffer中添加“/”,接下来pos和index都需要改变,要往后挪。因为循环需要往后走,我们要找到第二个“\\”,觉得这个有点算法的意思。返回sb.toString()。 
 
 
 

   总结一下replace方法,本意是根据传入的路径path,如果是D:\\文件\\API\\JDK_API_1_6_zh_CN.CHM这种格式的,给转换成D:/文件/API/JDK_API_1_6_zh_CN.CHM这种格式。 
 
 
 

        8:StringUtils的cleanPath方法: 
 
 
 

             通过replace的返回值,我们得到了可以用的路径pathToUse,然后我们要把这个路径下“:”给找出来,正如代码 
 
 
 
          int 
  prefixIndex 
  = 
  pathToUse 
  .indexOf( 
  ":" 
  );
 
 

        那样,需要知道,indexOf方法只要没找到相应的字符,就会返回-1,所以在下面的判断中才会以perfixIndex是否为-1来进行判断。如果路            径中有“:”,接着以D:/文件/API/JDK_API_1_6_zh_CN.CHM举例,prefix="D:"  pathToUse="/文件/API/JDK_API_1_6_zh_CN.CHM ",这个很有意思,因为程序不知道我们输入的是绝对路径 带D:的这种 ,还是/开头的这种,或者说相对路径,程序直接全给你判断了。接下来会判断pathToUse是否以“/"开头,是的话prefix会加上“/”,现在的prefix有两种情况,可能是"D:/"这种,也可能是"/"这种,而pathToUse肯定是“文件/API/JDK_API_1_6_zh_CN.CHM ”这种了。 
 
 
 
     String[] 
  pathArray 
  = delimitedListToStringArray( 
  pathToUse 
  , 
  FOLDER_SEPARATOR 
  );
 
 

              看到这句代码,我估计是把pathToUse给拆成字符串数组里,就像是这样,文件 API ***的这种。接下来看看具体的代码是不是这样: 
 
 
 

     9:StringUtils的delimitedListToStringArray方法 
 
 
 
     
   public 
   static 
   String[] delimitedListToStringArray(String 
   str 
   , String 
   delimiter 
   ) {
 
 
         
  return 
  delimitedListToStringArray( 
  str 
  , 
  delimiter 
  , 
  null 
  );
 
 
    }
 
 

         
 
 
 
public 
  static 
  String[] delimitedListToStringArray(String 
  str 
  , String 
  delimiter 
  , String 
  charsToDelete 
  ) {
 
 
         
  if 
  ( 
  str 
  == 
  null 
  ) {
 
 
             
  return 
  new 
  String[0];
 
 
        }
 
 
         
  if 
  ( 
  delimiter 
  == 
  null 
  ) {
 
 
             
  return 
  new 
  String[] { 
  str 
  };
 
 
        }
 
 
        List<String> 
  result 
  = 
  new 
  ArrayList<String>();
 
 
         
  if 
  ( 
  "" 
  .equals( 
  delimiter 
  )) {
 
 
             
  for 
  ( 
  int 
  i 
  = 0; 
  i 
  < 
  str 
  .length(); 
  i 
  ++) {
 
 
                 
  result 
  .add(deleteAny( 
  str 
  .substring( 
  i 
  , 
  i 
  + 1), 
  charsToDelete 
  ));
 
 
            }
 
 
        }
 
 
         
  else 
  {
 
 
             
  int 
  pos 
  = 0;
 
 
             
  int 
  delPos 
  ;
 
 
             
  while 
  (( 
  delPos 
  = 
  str 
  .indexOf( 
  delimiter 
  , 
  pos 
  )) != -1) {
 
 
                 
  result 
  .add(deleteAny( 
  str 
  .substring( 
  pos 
  , 
  delPos 
  ), 
  charsToDelete 
  ));
 
 
                 
  pos 
  = 
  delPos 
  + 
  delimiter 
  .length();
 
 
            }
 
 
             
  if 
  ( 
  str 
  .length() > 0 && 
  pos 
  <= 
  str 
  .length()) {
 
 
                 
  // Add rest of String, but not in case of empty input.
 
 
                 
  result 
  .add(deleteAny( 
  str 
  .substring( 
  pos 
  ), 
  charsToDelete 
  ));
 
 
            }
 
 
        }
 
 
         
  return 
  toStringArray( 
  result 
  );
 
 
    }
 
 

         先看看传入的参数: 
 
 
 

             str:pathToUse,就是文件/API/JDK_API_1_6_zh_CN.CHM 
 
 
 

                       delimiter:"/" 
 
 
 

                       charsToDelete:null 
 
 
 
     
   public 
   static 
   String deleteAny(String 
   inString 
   , String 
   charsToDelete 
   ) {
 
  
         
   if 
   (!hasLength( 
   inString 
   ) || !hasLength( 
   charsToDelete 
   )) {
 
  
             
   return 
   inString 
   ;
 
  
        }
 
  
        StringBuilder 
   sb 
   = 
   new 
   StringBuilder();
 
  
         
   for 
   ( 
   int 
   i 
   = 0; 
   i 
   < 
   inString 
   .length(); 
   i 
   ++) {
 
  
             
   char 
   c 
   = 
   inString 
   .charAt( 
   i 
   );
 
  
             
   if 
   ( 
   charsToDelete 
   .indexOf( 
   c 
   ) == -1) {
 
  
                 
   sb 
   .append( 
   c 
   );
 
  
            }
 
  
        }
 
  
         
   return 
   sb 
   .toString();
 
 
    }
 
 

        如果说我们传入的"/"等于""的话,显然是不可能,我们所假如的话,会把pathTOUse倒着循环,每个字符都摘出来,然后当成字符串用,传入deleteAny中,然后又是循环,对每个字符而言,如果charsToDelete中没有这个字符,就在StringBuilder中添加这个字符。返回值是String。当然了,这个还没用到。我们用到的是那个很复杂的else 
 
 
 
delimitedListToStringArray方法之后,接着往后循环,最终的结果就是实现了把pathToUse给切割成若干个String的形式。
 
 

        10:回到StringUtils的cleanPath方法 
 
 
 

        我们遇到了一个倒着的循环,如果说我们这个是特别正常的路径,就相当于复制了,如果是以.或者..结尾的这些内容,我们就把它给忽略了。 
 
 
 

        我得承认上面这些过程真的好复杂,其实就是做了一件事,对输入的路径进行了处理,只不过考虑的情况多了一点。所以我决定还是用debug来走一遍看看。 
 
 
 

        这时我用的是绝对路径 
 
 
 

      终于熬到了这个方法的结束。 
 
 
 

     11:回到ClassPathResource的构造函数 
 
 
 

         
 
 
 
this 
  . 
  classLoader 
  = ( 
  classLoader 
  != 
  null 
  ? 
  classLoader 
  : ClassUtils.getDefaultClassLoader());
 
 

            如果传入的classLoaser有值,就返回这个值,如果没有,就获取一个。 
 
 
 
public 
   static 
   ClassLoader getDefaultClassLoader() {
 
  
        ClassLoader 
   cl 
   = 
   null 
   ;
 
  
         
   try 
   {
 
  
             
   cl 
   = Thread.currentThread().getContextClassLoader();
 
  
        }
 
  
         
   catch 
   (Throwable 
   ex 
   ) {
 
  
             
   // Cannot access thread context ClassLoader - falling back...
 
  
        }
 
  
         
   if 
   ( 
   cl 
   == 
   null 
   ) {
 
  
             
   // No thread context class loader -> use class loader of this class.
 
  
             
   cl 
   = ClassUtils. 
   class 
   .getClassLoader();
 
  
             
   if 
   ( 
   cl 
   == 
   null 
   ) {
 
  
                 
   // getClassLoader() returning null indicates the bootstrap ClassLoader
 
  
                 
   try 
   {
 
  
                     
   cl 
   = ClassLoader.getSystemClassLoader();
 
  
                }
 
  
                 
   catch 
   (Throwable 
   ex 
   ) {
 
  
                     
   // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
 
  
                }
 
  
            }
 
  
        }
 
  
         
   return 
   cl 
   ;
 
 
    }
 
 

        写到这,我们的ClassPathResouce resouce实例就有了path 和 classLoader这两个关键属性。 
 
 
 

        如果我们想获取输入流 
 
 
 
     
   @Override
 
  
     
   public 
   InputStream getInputStream() 
   throws 
   IOException {
 
  
        InputStream 
   is 
   ;
 
  
         
   if 
   ( 
   this 
   . 
   clazz 
   != 
   null 
   ) {
 
  
             
   is 
   = 
   this 
   . 
   clazz 
   .getResourceAsStream( 
   this 
   . 
   path 
   );
 
  
        }
 
  
         
   else 
   if 
   ( 
   this 
   . 
   classLoader 
   != 
   null 
   ) {
 
  
             
   is 
   = 
   this 
   . 
   classLoader 
   .getResourceAsStream( 
   this 
   . 
   path 
   );
 
  
        }
 
  
         
   else 
   {
 
  
             
   is 
   = ClassLoader.getSystemResourceAsStream( 
   this 
   . 
   path 
   );
 
  
        }
 
  
         
   if 
   ( 
   is 
   == 
   null 
   ) {
 
  
             
   throw 
   new 
   FileNotFoundException(getDescription() + 
   " cannot be opened because it does not exist" 
   );
 
  
        }
 
  
         
   return 
   is 
   ;
 
 
    }
 
 

        会判断clazz 有没有值,classLoader有没有值,然后再获取输入流。前两天整理了java.lang.Class这个类的意思,现在就能用一点了,先看看定义的一些属性 
 
 
 
     
   private 
   final 
   String 
   path 
   ;
 
  
 
 
  
     
   private 
   ClassLoader 
   classLoader 
   ;
 
  
 
 
 
     
  private 
  Class<?> 
  clazz 
  ;
 
 

 


       ClassPathResource有好几个构造函数,有的构造函数会传入classLoader,有的会传入clazz,这个clazz就是相应的类在JVM上的实例,显然上面的例子中并没有这个东西,而classLoader是有的,所以通过classLoader获取输入流。我觉得对ClassPathResource理解的更透彻了,虽然大部分时间都是在对path进行处理。近期还要看看ClassLoader,还不是很清楚它的工作机制。