項目中須要用到包掃描的狀況是不少的,通常是在項目初始化的時候,根據一些條件來對某個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文件測試