• 利用spring,实现package下的类扫描


       项目中需要用到包扫描的情况是很多的,一般是在项目初始化的时候,根据一些条件来对某个package下的类进行特殊处理。现在想实现的功能是,在一个filter或interceptor初始化的时候,扫描指定的一些package路径,遍历下面的每个class,找出method上使用了一个特殊注解的所有方法,然后缓存起来,当方法拦截器工作的时候,就不用再挨个判断方法是否需要拦截了

       网上有很多自己编码实现scan package功能的例子,但是如果有工具已经帮你实现了,并且经受了普遍的验证,那么,自己造轮子的必要性就不大了
       spring框架中有扫描包的类ClassPathBeanDefinitionScanner 里面的findCandidateComponents方法是我们进行改造的依据  

    /**
     * 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;
    }

    改造如下:

     方法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文件

    加强版,package父子集合判断

    package utils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.util.SystemPropertyUtils;
    
    import java.io.IOException;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    /**
     * Created by cdliujian1 on 2016/1/13.
     * 包工具,根据package路径,加载class
     */
    public class PackageUtil {
    
        private final static Log log = LogFactory.getLog(PackageUtil.class);
        //扫描  scanPackages 下的文件的匹配符
        protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    
    
        /**
         * 结合spring的类扫描方式
         * 根据需要扫描的包路径及相应的注解,获取最终测method集合
         * 仅返回public方法,如果方法是非public类型的,不会被返回
         * 可以扫描工程下的class文件及jar中的class文件
         *
         * @param scanPackages
         * @param annotation
         * @return
         */
        public static Set<Method> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
            //获取所有的类
            Set<String> clazzSet = findPackageClass(scanPackages);
            Set<Method> methods = new HashSet<Method>();
            //遍历类,查询相应的annotation方法
            for (String clazz : clazzSet) {
                try {
                    Set<Method> ms = findAnnotationMethods(clazz, annotation);
                    if (ms != null) {
                        methods.addAll(ms);
                    }
                } catch (ClassNotFoundException ignore) {
                }
            }
            return methods;
        }
    
        /**
         * 根据扫描包的,查询下面的所有类
         *
         * @param scanPackages 扫描的package路径
         * @return
         */
        public static Set<String> findPackageClass(String scanPackages) {
            if (StringUtils.isBlank(scanPackages)) {
                return Collections.EMPTY_SET;
            }
            //验证及排重包路径,避免父子路径多次扫描
            Set<String> packages = checkPackage(scanPackages);
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            Set<String> clazzSet = new HashSet<String>();
            for (String basePackage : packages) {
                if (StringUtils.isBlank(basePackage)) {
                    continue;
                }
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
                try {
                    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                    for (Resource resource : resources) {
                        //检查resource,这里的resource都是class
                        String clazz = loadClassName(metadataReaderFactory, resource);
                        clazzSet.add(clazz);
                    }
                } catch (Exception e) {
                    log.error("获取包下面的类信息失败,package:" + basePackage, e);
                }
    
            }
            return clazzSet;
        }
    
        /**
         * 排重、检测package父子关系,避免多次扫描
         *
         * @param scanPackages
         * @return 返回检查后有效的路径集合
         */
        private static Set<String> checkPackage(String scanPackages) {
            if (StringUtils.isBlank(scanPackages)) {
                return Collections.EMPTY_SET;
            }
            Set<String> packages = new HashSet<String>();
            //排重路径
            Collections.addAll(packages, scanPackages.split(","));
            for (String pInArr : packages.toArray(new String[packages.size()])) {
                if (StringUtils.isBlank(pInArr) || pInArr.equals(".") || pInArr.startsWith(".")) {
                    continue;
                }
                if (pInArr.endsWith(".")) {
                    pInArr = pInArr.substring(0, pInArr.length() - 1);
                }
                Iterator<String> packageIte = packages.iterator();
                boolean needAdd = true;
                while (packageIte.hasNext()) {
                    String pack = packageIte.next();
                    if (pInArr.startsWith(pack + ".")) {
                        //如果待加入的路径是已经加入的pack的子集,不加入
                        needAdd = false;
                    } else if (pack.startsWith(pInArr + ".")) {
                        //如果待加入的路径是已经加入的pack的父集,删除已加入的pack
                        packageIte.remove();
                    }
                }
                if (needAdd) {
                    packages.add(pInArr);
                }
            }
            return packages;
        }
    
    
        /**
         * 加载资源,根据resource获取className
         *
         * @param metadataReaderFactory spring中用来读取resource为class的工具
         * @param resource              这里的资源就是一个Class
         * @throws IOException
         */
        private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
            try {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    if (metadataReader != null) {
                        return metadataReader.getClassMetadata().getClassName();
                    }
                }
            } catch (Exception e) {
                log.error("根据resource获取类名称失败", e);
            }
            return null;
        }
    
        /**
         * 把action下面的所有method遍历一次,标记他们是否需要进行敏感词验证
         * 如果需要,放入cache中
         *
         * @param fullClassName
         */
        public static Set<Method> findAnnotationMethods(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
            Set<Method> methodSet = new HashSet<Method>();
            Class<?> clz = Class.forName(fullClassName);
            Method[] methods = clz.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getModifiers() != Modifier.PUBLIC) {
                    continue;
                }
                Annotation annotation = method.getAnnotation(anno);
                if (annotation != null) {
                    methodSet.add(method);
                }
            }
            return methodSet;
        }
    
        public static void main(String[] args) {
            String packages = "com.a,com.ab,com.c,com.as.t,com.as,com.as.ta,com.at.ja,com.at.jc,com.at.";
            System.out.println("检测前的package: " + packages);
            System.out.println("检测后的package: " + StringUtils.join(checkPackage(packages), ","));
        }
    
    }
  • 相关阅读:
    jedata日期控件的开始结束日期设置
    springMVC中对HTTP请求form data和request payload两种数据发送块的后台接收方式
    Java Code Examples for org.codehaus.jackson.map.DeserializationConfig 配置
    Struts 2与AJAX(第二部分)
    在Struts 2中实现文件上传
    Strus 2的新表单标志的使用
    在Struts 2中实现CRUD
    Struts 2与AJAX(第三部分)
    Struts 2中的OGNL
    GridView中实现自定义时间货币等字符串格式
  • 原文地址:https://www.cnblogs.com/windliu/p/6377128.html
Copyright © 2020-2023  润新知