最近要实现一个项目启动时进行注解扫描的功能,用于实现方法的动态加载.实际实现版本有两个版本,第一个版本是直接百度的现成工具类,可以基本实现功能,但是实现的效率和安全性都存在未知性,所以改进了第二个版本,通过类库: classgraph 来实现.
- 版本1 自定义工具类
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());
});
}
}
该版本功能上只提供了方法注解的查询,类注解的需要自己再完善;优点是原生实现,不需要额外的包依赖
- 版本2 classgraph
需要引入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
- 参考资料: