最近要實現一個項目啟動時進行注解掃描的功能,用於實現方法的動態加載.實際實現版本有兩個版本,第一個版本是直接百度的現成工具類,可以基本實現功能,但是實現的效率和安全性都存在未知性,所以改進了第二個版本,通過類庫: 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
- 參考資料:
