最近要实现一个项目启动时进行注解扫描的功能,用于实现方法的动态加载.实际实现版本有两个版本,第一个版本是直接百度的现成工具类,可以基本实现功能,但是实现的效率和安全性都存在未知性,所以改进了第二个版本,通过类库: classgraph 来实现.
package a.custom.utils; import a.custom.annotation.BizPermission; 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.stereotype.Component; 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; /** * @author 123 * @Description * @create 2021/11/3 11:12 */ @Component public class PackageUtils { private final static Log log = LogFactory.getLog(PackageUtils.class); //扫描 scanPackages 下的文件的匹配符 protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; /** * 查询指定注解信息 * @param scanPackages * @param annotation * @return * @throws ClassNotFoundException */ public static Set<Annotation> findClassAnnotations(String scanPackages, Class<? extends Annotation> annotation) throws ClassNotFoundException { //获取所有的类 Set<String> clazzSet = findPackageClass(scanPackages); Set<Annotation> methods = new HashSet<>(); //遍历类,查询相应的annotation方法 for (String clazz : clazzSet) { Set<Annotation> ms = findAnnotations(clazz, annotation); if (ms != null) { methods.addAll(ms); } } return methods; } /** * 结合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<>(); //遍历类,查询相应的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.isEmptyOrNull(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.isEmptyOrNull(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.isEmptyOrNull(scanPackages)) { return Collections.EMPTY_SET; } Set<String> packages = new HashSet<>(); //排重路径 Collections.addAll(packages, scanPackages.split(",")); String[] strings = packages.toArray(new String[packages.size()]); for (String pInArr : strings) { if (StringUtils.isEmptyOrNull(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) { 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<>(); 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; } /** * 查询指定注解信息 * @param fullClassName * @param anno * @return * @throws ClassNotFoundException */ public static Set<Annotation> findAnnotations(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException { Set<Annotation> methodSet = new HashSet<>(); 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) { if(methodSet.contains(annotation)){ log.error("注解不存在"); } methodSet.add(annotation); } } return methodSet; } public static void main(String[] args) throws ClassNotFoundException { String packages = "scan.package"; Set<Annotation> classAnnotationMethods = findClassAnnotations(packages, BizPermission.class); classAnnotationMethods.forEach(set->{ BizPermission annotation = (BizPermission)set; System.out.println(annotation.code()+" "+annotation.name()); }); } }
该版本功能上只提供了方法注解的查询,类注解的需要自己再完善;优点是原生实现,不需要额外的包依赖
需要引入classgraph maven依赖
<dependency> <groupId>io.github.classgraph</groupId> <artifactId>classgraph</artifactId> <version>4.8.132</version> </dependency>
查询方法注解
package a.custom.utils; import io.github.classgraph.AnnotationParameterValueList; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfoList; import io.github.classgraph.ScanResult; import java.lang.annotation.Annotation; import java.util.List; import java.util.stream.Collectors; /** * @author 123 * @Description 类工具 * @create 2021/11/18 9:56 */ public class ClassUtils { /** * 扫描指定方法注解 * @param pkg 扫描包 * @param annotation 获取的注解类型 * @return 返回注解参数 [{name:name,value:value}] */ public static List<AnnotationParameterValueList> methodAnnotionScan(String pkg, Annotation annotation) { try (ScanResult scanResult = // Assign scanResult in try-with-resources new ClassGraph() // Create a new ClassGraph instance .enableAllInfo() // Scan classes, methods, fields, annotations .acceptPackages(pkg) // Scan com.xyz and subpackages .scan()) { // Perform the scan and return a ScanResult // 获取类里指定方法注解 ClassInfoList ciList = scanResult.getClassesWithMethodAnnotation(annotation.getClass()); // 指定方法注解内容提取,提取流程: ClassInfoList -> ClassInfo -> MethodInfo -> AnnotationInfo -> ParameterValues -> AnnotationParameterValue return ciList.stream().flatMap(ci->ci.getMethodInfo().stream().filter(me->me.getAnnotationInfo(annotation.getClass())!=null) .map(me->me.getAnnotationInfo(annotation.getClass()).getParameterValues())).collect(Collectors.toList()); } } }
classgraph是一个基于jvm语言进行类路径和包扫描的开源工具包.基于jvm语言,它拥有基于分析或响应其他代码属性而编写代码的能力.拥有了更灵活的扩展性.
根据类的层级关系,它的数据提取层级如下:
ClassInfoList -> ClassInfo -> MethodInfo -> AnnotationInfo -> ParameterValues -> AnnotationParameterValue
Reflections Corn Classpath Scanner annotation-detector Scannotation Sclasner Annovention ClassIndex (compiletime annotation scanner/processor) Jandex (Java annotation indexer, part of Wildfly) Spring has built-in classpath scanning Hibernate has the class org.hibernate.ejb.packaging.Scanner. extcos -- the Extended Component Scanner Javassist ObjectWeb ASM QDox, a fast Java source parser and indexer bndtools, which is able to "crawl"/parse the bytecode of class files to find all imports/dependencies, among other things. coffea, a command line tool and Python library for analyzing static dependences in Java bytecode org.clapper.classutil.ClassFinder com.google.common.reflect.ClassPath jdependency Burningwave Core
classgraph