利用spring,實現package下的類掃描

   項目中須要用到包掃描的狀況是不少的,通常是在項目初始化的時候,根據一些條件來對某個package下的類進行特殊處理。如今想實現的功能是,在一個filter或interceptor初始化的時候,掃描指定的一些package路徑,遍歷下面的每一個class,找出method上使用了一個特殊註解的全部方法,而後緩存起來,當方法攔截器工做的時候,就不用再挨個判斷方法是否須要攔截了java

   網上有不少本身編碼實現scan package功能的例子,可是若是有工具已經幫你實現了,而且經受了廣泛的驗證,那麼,本身造輪子的必要性就不大了
   spring框架中有掃描包的類ClassPathBeanDefinitionScanner 裏面的findCandidateComponents方法是咱們進行改造的依據  web

 

/**
 * Scan the class path for candidate components.
 * @param basePackage the package to check for annotated classes
 * @return a corresponding Set of autodetected bean definitions
 */
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + "/" + this.resourcePattern;
      Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();
      for (Resource resource : resources) {
         if (traceEnabled) {
            logger.trace("Scanning " + resource);
         }
         if (resource.isReadable()) {
            try {
               MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     if (debugEnabled) {
                        logger.debug("Identified candidate component class: " + resource);
                     }
                     candidates.add(sbd);
                  }
                  else {
                     if (debugEnabled) {
                        logger.debug("Ignored because not a concrete top-level class: " + resource);
                     }
                  }
               }
               else {
                  if (traceEnabled) {
                     logger.trace("Ignored because not matching any filter: " + resource);
                  }
               }
            }
            catch (Throwable ex) {
               throw new BeanDefinitionStoreException(
                     "Failed to read candidate component class: " + resource, ex);
            }
         }
         else {
            if (traceEnabled) {
               logger.trace("Ignored because not readable: " + resource);
            }
         }
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}


改造以下:spring

 方法loadCheckClassMethods的入參是逗號分隔的包路徑,如com.xx緩存

利用Spring的 框架

ResourcePatternResolver來尋找包下面的資源Resource,由於咱們的掃描pattern是.class文件,因此這裏的Resource就是class文件

protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
/**
 * 根據掃描包的配置
 * 加載須要檢查的方法
 */
private void loadCheckClassMethods(String scanPackages) {
    String[] scanPackageArr = scanPackages.split(",");
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
    for (String basePackage : scanPackageArr) {
        if (StringUtils.isBlank(basePackage)) {
            continue;
        }
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
        try {
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                //檢查resource,這裏的resource都是class
                loadClassMethod(metadataReaderFactory, resource);
            }
        } catch (Exception e) {
            log.error("初始化SensitiveWordInterceptor失敗", e);
        }

    }
}
/**
 * 加載資源,判斷裏面的方法
 *
 * @param metadataReaderFactory spring中用來讀取resource爲class的工具
 * @param resource              這裏的資源就是一個Class
 * @throws IOException
 */
private void loadClassMethod(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
    try {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (metadataReader != null) {
                String className = metadataReader.getClassMetadata().getClassName();
                try {
                    tryCacheMethod(className);
                } catch (ClassNotFoundException e) {
                    log.error("檢查" + className + "是否含有須要信息失敗", e);
                }
            }
        }
    } catch (Exception e) {
        log.error("判斷類中的方法實現須要檢測xxx失敗", e);
    }
}
/**
 * 把action下面的全部method遍歷一次,標記他們是否須要進行xxx驗證
 * 若是須要,放入cache中
 *
 * @param fullClassName
 */
private void tryCacheMethod(String fullClassName) throws ClassNotFoundException {
    Class<?> clz = Class.forName(fullClassName);
    Method[] methods = clz.getDeclaredMethods();
    for (Method method : methods) {
        if (method.getModifiers() != Modifier.PUBLIC) {
            continue;
        }
        if (CheckXXX.class.isAssignableFrom(CHECK_ANNOTATION)) {
            CheckXXX checkXXX = (CheckXXX) method.getAnnotation(CHECK_ANNOTATION);
            if (checkXXX != null && checkXXX.check()) {
                cache.put(fullClassName + "." + method.getName(), checkXXX);
                log.info("檢測到須要檢查xxx的方法:" + fullClassName + "." + method.getName());
            }
        }
    }
}


tryCacheMethod作的事就是緩存須要處理的public方法工具


經測試,這種方式能夠取到web的class文件和jar包中的class文件測試

相關文章
相關標籤/搜索