Spring 소스 코드 학습 - 마스크 경로 분석 포함 (상)

50370 단어 어댑터경로정칙
질문
경로 에 어댑터 (?, *, * *) 가 포함 되 어 있 으 면 spring 은 어떻게 처리 합 니까?classpath * 로 시작 하면 어 떨 까요?
우선 어댑터 (?) 를 포함 하 는 것 을 테스트 분석 합 니 다.

  
  
  
  
  1. /**  
  2.  *  :*,?  
  3.  * <p>D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\ap?-context.xml</p>  
  4.  *  , Spring \\  
  5.  *   
  6.  * @author lihzh  
  7.  * @date 2012-5-5  10:53:53  
  8.  */ 
  9. @Test 
  10. public void testAntStylePathFail() {  
  11.     String pathOne = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\ap?-context.xml";  
  12.     ApplicationContext appContext = new FileSystemXmlApplicationContext(pathOne);  
  13.     assertNotNull(appContext);  
  14.     VeryCommonBean bean = null;  
  15.     try {  
  16.         bean = appContext.getBean(VeryCommonBean.class);  
  17.         fail("Should not find the [VeryCommonBean].");  
  18.     } catch (NoSuchBeanDefinitionException e) {  
  19.     }  
  20.     assertNull(bean);  

테스트 사례 에서 보 듯 이 이 빈 을 찾 을 수 없다.이 건 또 왜?Spring 은 어댑터 를 지원 하지 않 습 니까?FileSystemXmlApplication Context 의 주석 에서 도 마스크 의 상황 이 언급 되 었 습 니 다.

  
  
  
  
  1. <p>The config location defaults can be overridden via {@link #getConfigLocations},  
  2. * Config locations can either denote concrete files like "/myfiles/context.xml"  
  3. * or Ant-style patterns like "/myfiles/*-context.xml" (see the  
  4. * {@link org.springframework.util.AntPathMatcher} javadoc for pattern details). 

 코드 에서 답 을 찾다.지난번 else 분기 로 돌아 갑 니 다. 어댑터 가 포함 되 어 있 기 때문에 첫 번 째 하위 분기 에 들 어 갑 니 다.

  
  
  
  
  1. /**  
  2.      * Find all resources that match the given location pattern via the  
  3.      * Ant-style PathMatcher. Supports resources in jar files and zip files  
  4.      * and in the file system.  
  5.      * @param locationPattern the location pattern to match  
  6.      * @return the result as Resource array  
  7.      * @throws IOException in case of I/O errors  
  8.      * @see #doFindPathMatchingJarResources  
  9.      * @see #doFindPathMatchingFileResources  
  10.      * @see org.springframework.util.PathMatcher  
  11.      */ 
  12.     protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {  
  13.         String rootDirPath = determineRootDir(locationPattern);  
  14.         String subPattern = locationPattern.substring(rootDirPath.length());  
  15.         Resource[] rootDirResources = getResources(rootDirPath);  
  16.         Set<Resource> result = new LinkedHashSet<Resource>(16);  
  17.         for (Resource rootDirResource : rootDirResources) {  
  18.             rootDirResource = resolveRootDirResource(rootDirResource);  
  19.             if (isJarResource(rootDirResource)) {  
  20.                 result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));  
  21.             }  
  22.             else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {  
  23.                 result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));  
  24.             }  
  25.             else {  
  26.                 result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));  
  27.             }  
  28.         }  
  29.         if (logger.isDebugEnabled()) {  
  30.             logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);  
  31.         }  
  32.         return result.toArray(new Resource[result.size()]);  
  33.     } 

 이 방법 이 들 어 오 는 완전한 처리 되 지 않 은 경 로 는 첫 줄 부터 들 어 오 는 경 로 를 단계별 로 처리 합 니 다. 먼저 '루트' 경 로 를 결정 합 니 다. determineRootDir (locationPattern) 

  
  
  
  
  1. /**  
  2.      * Determine the root directory for the given location.  
  3.      * <p>Used for determining the starting point for file matching,  
  4.      * resolving the root directory location to a <code>java.io.File</code>  
  5.      * and passing it into <code>retrieveMatchingFiles</code>, with the  
  6.      * remainder of the location as pattern.  
  7.      * <p>Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",  
  8.      * for example.  
  9.      * @param location the location to check  
  10.      * @return the part of the location that denotes the root directory  
  11.      * @see #retrieveMatchingFiles  
  12.      */ 
  13.     protected String determineRootDir(String location) {  
  14.         int prefixEnd = location.indexOf(":") + 1;  
  15.         int rootDirEnd = location.length();  
  16.         while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {  
  17.             rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;  
  18.         }  
  19.         if (rootDirEnd == 0) {  
  20.             rootDirEnd = prefixEnd;  
  21.         }  
  22.         return location.substring(0, rootDirEnd);  
  23.     } 

이 "뿌리" 는 어댑터 가 포함 되 지 않 은 가장 긴 부분 입 니 다. 우리 의 경 로 를 예 로 들 면 이 "뿌리" 는 원래 D: \ \ \ \ \ \ workspace - home \ \ \ spring - custom \ \ \ src \ \ \ main \ \ \ resources \ \ \ \ spring \ \ \ \ ,그러나 실제로 determineRootDir 의 실현 을 보면 다음 과 같다. 
우선, 짝 퉁: 색인 위치, prefixEnd 에 할당 。
 
그 다음 에 콜론 부터 마지막 문자열 까지 마스크 가 포함 되 어 있 는 지 순환 적 으로 판단 합 니 다. 포함 되 어 있 으 면 마지막 으로 '/' 로 분 단 된 부분 을 차단 합 니 다. 예 를 들 어 우리 경로 에서 마지막 ap 입 니까? -context. xml 이 단락 입 니 다.나머지 경로 에 마스크 가 포함 되 지 않 을 때 까지 나머지 부분 을 반복 해서 판단 합 니 다.
검색 이 완료 되면 rootDirEnd = 0 이 되면 이전에 할당 한 prefixEnd 의 값 을 rootDirEnd, 즉 ":" 가 있 는 색인 위치 에 부여 합 니 다.
 
마지막 으로 루트 DirEnd 를 처음부터 문자열 을 끊 습 니 다.
 
우리 의 문 제 는 중요 한 두 번 째 단계 입 니 다. Spring 은 문자열 에서 만 "/" 를 찾 습 니 다. "\ \ \" 와 같은 경로 분할 방식 을 지원 하지 않 기 때문에 "\ \", rootDirEnd = - 1 + 1 = 0 을 찾 을 수 없습니다.그래서 순환 한 후에 단계 에서 나 오 는 경 로 는 D 입 니 다.
 ,자연 Spring 에서 설정 파일 을 찾 을 수 없습니다. 용 기 를 초기 화 할 수 없습니다.
 
상기 분석 을 바탕 으로 우 리 는 경 로 를 D: / workspace - home / spring - custom / src / main / resources / spring / ap? -context.xml,
테스트 통과.
 
방금 분 석 했 을 뿐 입 니 다. 우리 가 이전에 경 로 를 분석 한 문제 가 있 습 니 다. 그리고 제 생각 에 도 여러분 의 관심 이 있 는 것 같 습 니 다. 바로 마스크 가 어떻게 일치 하 는 지 하 는 것 입 니 다.그러면 우 리 는 소스 코드 를 계속 분석 하고 findPathMatchingResources 로 돌아 갑 니 다. 방법
 
경 로 를 어댑터 와 포함 되 지 않 는 두 부분 으로 나 누 면 Spring 은 루트 경 로 를 하나의 리 소스 로 만 들 고 getResources 방법 을 사용 합 니 다.그리고 루트 경로 의 종 류 를 확인 합 니 다. Jar 경로 입 니까?VFS 경로 입 니까?우리 의 이런 일반적인 경로 에 대해 서 는 자 연 스 럽 게 마지막 지점 까지 간다.

    
    
    
    
  1. /**  
  2.      * Find all resources in the file system that match the given location pattern  
  3.      * via the Ant-style PathMatcher.  
  4.      * @param rootDirResource the root directory as Resource  
  5.      * @param subPattern the sub pattern to match (below the root directory)  
  6.      * @return the Set of matching Resource instances  
  7.      * @throws IOException in case of I/O errors  
  8.      * @see #retrieveMatchingFiles  
  9.      * @see org.springframework.util.PathMatcher  
  10.      */ 
  11.     protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)  
  12.             throws IOException {  
  13.  
  14.         File rootDir;  
  15.         try {  
  16.             rootDir = rootDirResource.getFile().getAbsoluteFile();  
  17.         }  
  18.         catch (IOException ex) {  
  19.             if (logger.isWarnEnabled()) {  
  20.                 logger.warn("Cannot search for matching files underneath " + rootDirResource +  
  21.                         " because it does not correspond to a directory in the file system", ex);  
  22.             }  
  23.             return Collections.emptySet();  
  24.         }  
  25.         return doFindMatchingFileSystemResources(rootDir, subPattern);  
  26.     }  

  
  
  
  
  1. /**  
  2.      * Retrieve files that match the given path pattern,  
  3.      * checking the given directory and its subdirectories.  
  4.      * @param rootDir the directory to start from  
  5.      * @param pattern the pattern to match against,  
  6.      * relative to the root directory  
  7.      * @return the Set of matching File instances  
  8.      * @throws IOException if directory contents could not be retrieved  
  9.      */ 
  10.     protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {  
  11.         if (!rootDir.exists()) {  
  12.             // Silently skip non-existing directories.  
  13.             if (logger.isDebugEnabled()) {  
  14.                 logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");  
  15.             }  
  16.             return Collections.emptySet();  
  17.         }  
  18.         if (!rootDir.isDirectory()) {  
  19.             // Complain louder if it exists but is no directory.  
  20.             if (logger.isWarnEnabled()) {  
  21.                 logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");  
  22.             }  
  23.             return Collections.emptySet();  
  24.         }  
  25.         if (!rootDir.canRead()) {  
  26.             if (logger.isWarnEnabled()) {  
  27.                 logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +  
  28.                         "] because the application is not allowed to read the directory");  
  29.             }  
  30.             return Collections.emptySet();  
  31.         }  
  32.         String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");  
  33.         if (!pattern.startsWith("/")) {  
  34.             fullPattern += "/";  
  35.         }  
  36.         fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");  
  37.         Set<File> result = new LinkedHashSet<File>(8);  
  38.         doRetrieveMatchingFiles(fullPattern, rootDir, result);  
  39.         return result;  
  40.     } 

  
  
  
  
  1. /**  
  2.      * Recursively retrieve files that match the given pattern,  
  3.      * adding them to the given result list.  
  4.      * @param fullPattern the pattern to match against,  
  5.      * with prepended root directory path  
  6.      * @param dir the current directory  
  7.      * @param result the Set of matching File instances to add to  
  8.      * @throws IOException if directory contents could not be retrieved  
  9.      */ 
  10.     protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {  
  11.         if (logger.isDebugEnabled()) {  
  12.             logger.debug("Searching directory [" + dir.getAbsolutePath() +  
  13.                     "] for files matching pattern [" + fullPattern + "]");  
  14.         }  
  15.         File[] dirContents = dir.listFiles();  
  16.         if (dirContents == null) {  
  17.             if (logger.isWarnEnabled()) {  
  18.                 logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");  
  19.             }  
  20.             return;  
  21.         }  
  22.         for (File content : dirContents) {  
  23.             String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");  
  24.             if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {  
  25.                 if (!content.canRead()) {  
  26.                     if (logger.isDebugEnabled()) {  
  27.                         logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +  
  28.                                 "] because the application is not allowed to read the directory");  
  29.                     }  
  30.                 }  
  31.                 else {  
  32.                     doRetrieveMatchingFiles(fullPattern, content, result);  
  33.                 }  
  34.             }  
  35.             if (getPathMatcher().match(fullPattern, currPath)) {  
  36.                 result.add(content);  
  37.             }  
  38.         }  
  39.     } 

주요 일치 하 는 작업 은 doRetrieveMatchingFiles 에서 방법 은 시작 했다.앞의 것 은 모두 간단 한 패 키 징 과도 입 니 다. retrieveMatchingFiles 에서 다음 경로 가 존재 하 는 지, 폴 더 인지, 읽 을 수 있 는 지 여 부 를 판단 합 니 다.그렇지 않 으 면 모두 빈 집합 으로 돌아간다.모두 만족 한 후에 야 들 어 왔 습 니 다. 방법이 방법 에서
 
우선 이 폴 더 의 모든 파일 을 보 여 줍 니 다.
그리고 모든 파일 을 옮 겨 다 니 며 폴 더 라면 doRetrieveMatchingFiles 방법 을 재 귀적 으로 호출 합 니 다.그렇지 않 으 면 getPathMatcher (). match (fullPattern, currPath) 를 호출 하여 파일 이름 의 마지막 매 칭 을 하고 조건 을 만족 시 켜 결과 집합 에 넣 습 니 다.
 
이 match 방법 은 실제 AntPathMatcher 의 doMatch 방법 을 호출 하 였 습 니 다.

  
  
  
  
  1. /**  
  2.      * Actually match the given <code>path</code> against the given <code>pattern</code>.  
  3.      * @param pattern the pattern to match against  
  4.      * @param path the path String to test  
  5.      * @param fullMatch whether a full pattern match is required (else a pattern match  
  6.      * as far as the given base path goes is sufficient)  
  7.      * @return <code>true</code> if the supplied <code>path</code> matched, <code>false</code> if it didn't  
  8.      */ 
  9.     protected boolean doMatch(String pattern, String path, boolean fullMatch,  
  10.             Map<String, String> uriTemplateVariables) {  
  11.  
  12.         if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {  
  13.             return false;  
  14.         }  
  15.  
  16.         String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);  
  17.         String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);  
  18.  
  19.         int pattIdxStart = 0;  
  20.         int pattIdxEnd = pattDirs.length - 1;  
  21.         int pathIdxStart = 0;  
  22.         int pathIdxEnd = pathDirs.length - 1;  
  23.  
  24.         // Match all elements up to the first **  
  25.         while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {  
  26.             String patDir = pattDirs[pattIdxStart];  
  27.             if ("**".equals(patDir)) {  
  28.                 break;  
  29.             }  
  30.             if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {  
  31.                 return false;  
  32.             }  
  33.             pattIdxStart++;  
  34.             pathIdxStart++;  
  35.         }  
  36.  
  37.         if (pathIdxStart > pathIdxEnd) {  
  38.             // Path is exhausted, only match if rest of pattern is * or **'s  
  39.             if (pattIdxStart > pattIdxEnd) {  
  40.                 return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :  
  41.                         !path.endsWith(this.pathSeparator));  
  42.             }  
  43.             if (!fullMatch) {  
  44.                 return true;  
  45.             }  
  46.             if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {  
  47.                 return true;  
  48.             }  
  49.             for (int i = pattIdxStart; i <= pattIdxEnd; i++) {  
  50.                 if (!pattDirs[i].equals("**")) {  
  51.                     return false;  
  52.                 }  
  53.             }  
  54.             return true;  
  55.         }  
  56.         else if (pattIdxStart > pattIdxEnd) {  
  57.             // String not exhausted, but pattern is. Failure.  
  58.             return false;  
  59.         }  
  60.         else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {  
  61.             // Path start definitely matches due to "**" part in pattern.  
  62.             return true;  
  63.         }  
  64.  
  65.         // up to last '**'  
  66.         while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {  
  67.             String patDir = pattDirs[pattIdxEnd];  
  68.             if (patDir.equals("**")) {  
  69.                 break;  
  70.             }  
  71.             if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {  
  72.                 return false;  
  73.             }  
  74.             pattIdxEnd--;  
  75.             pathIdxEnd--;  
  76.         }  
  77.         if (pathIdxStart > pathIdxEnd) {  
  78.             // String is exhausted  
  79.             for (int i = pattIdxStart; i <= pattIdxEnd; i++) {  
  80.                 if (!pattDirs[i].equals("**")) {  
  81.                     return false;  
  82.                 }  
  83.             }  
  84.             return true;  
  85.         }  
  86.  
  87.         while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {  
  88.             int patIdxTmp = -1;  
  89.             for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {  
  90.                 if (pattDirs[i].equals("**")) {  
  91.                     patIdxTmp = i;  
  92.                     break;  
  93.                 }  
  94.             }  
  95.             if (patIdxTmp == pattIdxStart + 1) {  
  96.                 // '**/**' situation, so skip one  
  97.                 pattIdxStart++;  
  98.                 continue;  
  99.             }  
  100.             // Find the pattern between padIdxStart & padIdxTmp in str between  
  101.             // strIdxStart & strIdxEnd  
  102.             int patLength = (patIdxTmp - pattIdxStart - 1);  
  103.             int strLength = (pathIdxEnd - pathIdxStart + 1);  
  104.             int foundIdx = -1;  
  105.  
  106.             strLoop:  
  107.             for (int i = 0; i <= strLength - patLength; i++) {  
  108.                 for (int j = 0; j < patLength; j++) {  
  109.                     String subPat = pattDirs[pattIdxStart + j + 1];  
  110.                     String subStr = pathDirs[pathIdxStart + i + j];  
  111.                     if (!matchStrings(subPat, subStr, uriTemplateVariables)) {  
  112.                         continue strLoop;  
  113.                     }  
  114.                 }  
  115.                 foundIdx = pathIdxStart + i;  
  116.                 break;  
  117.             }  
  118.  
  119.             if (foundIdx == -1) {  
  120.                 return false;  
  121.             }  
  122.  
  123.             pattIdxStart = patIdxTmp;  
  124.             pathIdxStart = foundIdx + patLength;  
  125.         }  
  126.  
  127.         for (int i = pattIdxStart; i <= pattIdxEnd; i++) {  
  128.             if (!pattDirs[i].equals("**")) {  
  129.                 return false;  
  130.             }  
  131.         }  
  132.  
  133.         return true;  
  134.     }  

비교 방법 은 다음 과 같다.
 
우선, 입력 경로 와 비교 할 경 로 를 각각 파일 구분자 에 따라 문자열 배열 로 나 눕 니 다.(예: ("D:", "Workspace - home", "spring - custom"... 곶)
그리고 시작 과 끝 자 리 를 설정 한 후 이 두 배열 에 대해 while 순환 (코드 의 첫 번 째 while 순환) 을 진행 합 니 다. 한 단락 이 만족 하지 않 으 면 fasle 로 돌아 갑 니 다.
 
현재 테스트 경로 에 * * 부분 이 포함 되 어 있 지 않 기 때문에 주요 판단 은 기본적으로 첫 번 째 while 에서 이 루어 집 니 다. 이 부분 작업 은 당연히 match Strings 에서 이 루어 집 니 다.
 
생각해 보 세 요: 마스크 경로 에 맞 는 기능 을 완성 시 키 면 어떻게 하 시 겠 습 니까? 정규 가 자 연 스 럽 게 연상 되 었 습 니까? 좋 은 선택 인 것 같 습 니 다. spring 이 어떻게 하 는 지 보 세 요.

   
   
   
   
  1. private boolean matchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) {  
  2.         AntPathStringMatcher matcher = new AntPathStringMatcher(pattern, str, uriTemplateVariables);  
  3.         return matcher.matchStrings();  
  4.     } 

AntPathStringMatcher 인 스 턴 스 를 구성 할 때 spring 은 과연 정규 를 만 들 었 습 니 다.

  
  
  
  
  1. AntPathStringMatcher(String pattern, String str, Map<String, String> uriTemplateVariables) {  
  2.         this.str = str;  
  3.         this.uriTemplateVariables = uriTemplateVariables;  
  4.         this.pattern = createPattern(pattern);  
  5.     }  
  6.  
  7. private Pattern createPattern(String pattern) {  
  8.         StringBuilder patternBuilder = new StringBuilder();  
  9.         Matcher m = GLOB_PATTERN.matcher(pattern);  
  10.         int end = 0;  
  11.         while (m.find()) {  
  12.             patternBuilder.append(quote(pattern, end, m.start()));  
  13.             String match = m.group();  
  14.             if ("?".equals(match)) {  
  15.                 patternBuilder.append('.');  
  16.             }  
  17.             else if ("*".equals(match)) {  
  18.                 patternBuilder.append(".*");  
  19.             }  
  20.             else if (match.startsWith("{") && match.endsWith("}")) {  
  21.                 int colonIdx = match.indexOf(':');  
  22.                 if (colonIdx == -1) {  
  23.                     patternBuilder.append(DEFAULT_VARIABLE_PATTERN);  
  24.                     variableNames.add(m.group(1));  
  25.                 }  
  26.                 else {  
  27.                     String variablePattern = match.substring(colonIdx + 1, match.length() - 1);  
  28.                     patternBuilder.append('(');  
  29.                     patternBuilder.append(variablePattern);  
  30.                     patternBuilder.append(')');  
  31.                     String variableName = match.substring(1, colonIdx);  
  32.                     variableNames.add(variableName);  
  33.                 }  
  34.             }  
  35.             end = m.end();  
  36.         }  
  37.         patternBuilder.append(quote(pattern, end, pattern.length()));  
  38.         return Pattern.compile(patternBuilder.toString());  
  39.     } 

 쉽게 말 하면 spring 이 먼저 정규 를 사용 하 는 것 이다.

  
  
  
  
  1. private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}"); 

경로 에 있 는 "?" 와 "*" 마스크 를 찾 은 다음 자바 정규 임의의 문자 "." 와 ". *" 로 변환 합 니 다. 찾 은 파일 의 경로 와 일치 하 는 다른 정규 표현 식 을 생 성 합 니 다. 일치 하면 true 로 돌아 갑 니 다.
 
이로써 경로 에 포 함 된? 와 * 의 상황 해석 spring 의 해석 방식 에 대해 우 리 는 이미 기본적으로 알 고 있 습 니 다. * * 의 상황 을 함께 소개 하려 고 했 지만 고려 한 편폭 이 너무 길 어서 다음 에 다시 함께 연구 합 시다.
 
마지막 으로 모든 연 구 는 필자 가 일 한 여가 시간 에 소일 하고 잘못된 지적 은 각종 형식 과 내용 에 대한 연 구 를 환영한다 고 지적 했다.
블 로 거들 추천:
Java Coder 기술 교류 고급 군: 91513074
추천 글:
 
가장 일반적인 IT 남 - 코 더 잡담
 
 
 
본 고 는 '코 더 를 핍박 하 는' 블 로그 에서 나 온 것 으로 전 재 를 사절 합 니 다!

좋은 웹페이지 즐겨찾기