org.redkale.boot.ClassFilter Maven / Gradle / Ivy
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkale.boot;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.Modifier;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.logging.*;
import java.util.regex.*;
import org.redkale.util.*;
import org.redkale.util.AnyValue.DefaultAnyValue;
/**
* class过滤器, 符合条件的class会保留下来存入FilterEntry。
*
* 详情见: https://redkale.org
*
* @author zhangjx
* @param 泛型
*/
@SuppressWarnings("unchecked")
public final class ClassFilter {
private static final Logger logger = Logger.getLogger(ClassFilter.class.getName()); //日志对象
private static final boolean finest = logger.isLoggable(Level.FINEST); //日志级别
private final Set> entrys = new HashSet<>(); //符合条件的结果
private final Set> expectEntrys = new HashSet<>(); //准备符合条件的结果
private boolean refused; //是否拒绝所有数据,设置true,则其他规则失效,都是拒绝.
private Class superClass; //符合的父类型。不为空时,扫描结果的class必须是superClass的子类
private Class[] excludeSuperClasses; //不符合的父类型。
private Class annotationClass;//符合的注解。不为空时,扫描结果的class必须包含该注解
private Pattern[] includePatterns; //符合的classname正则表达式
private Pattern[] excludePatterns;//拒绝的classname正则表达式
private Set privilegeIncludes; //特批符合条件的classname
private Set privilegeExcludes;//特批拒绝条件的classname
private List ors; //或关系的其他ClassFilter
private List ands;//与关系的其他ClassFilter
private AnyValue conf; //基本配置信息, 当符合条件时将conf的属性赋值到FilterEntry中去。
private final ClassLoader classLoader;
public ClassFilter(ClassLoader classLoader, Class annotationClass, Class superClass, Class[] excludeSuperClasses) {
this(classLoader, annotationClass, superClass, excludeSuperClasses, null);
}
public ClassFilter(ClassLoader classLoader, Class annotationClass, Class superClass, Class[] excludeSuperClasses, AnyValue conf) {
this.annotationClass = annotationClass;
this.superClass = superClass;
this.excludeSuperClasses = excludeSuperClasses;
this.conf = conf;
this.classLoader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
}
public static ClassFilter create(Class[] excludeSuperClasses, String includeregs, String excluderegs, Set includeValues, Set excludeValues) {
ClassFilter filter = new ClassFilter(null, null, null, excludeSuperClasses);
filter.setIncludePatterns(includeregs == null ? null : includeregs.split(";"));
filter.setExcludePatterns(excluderegs == null ? null : excluderegs.split(";"));
filter.setPrivilegeIncludes(includeValues);
filter.setPrivilegeExcludes(excludeValues);
return filter;
}
public ClassFilter or(ClassFilter filter) {
if (ors == null) ors = new ArrayList<>();
ors.add(filter);
return this;
}
public ClassFilter and(ClassFilter filter) {
if (ands == null) ands = new ArrayList<>();
ands.add(filter);
return this;
}
/**
* 获取符合条件的class集合
*
* @return Set<FilterEntry<T>>
*/
public final Set> getFilterEntrys() {
HashSet> set = new HashSet<>();
set.addAll(entrys);
if (ors != null) ors.forEach(f -> set.addAll(f.getFilterEntrys()));
if (ands != null) ands.forEach(f -> set.addAll(f.getFilterEntrys()));
return set;
}
/**
* 获取预留的class集合
*
* @return Set<FilterEntry<T>>
*/
public final Set> getFilterExpectEntrys() {
HashSet> set = new HashSet<>();
set.addAll(expectEntrys);
if (ors != null) ors.forEach(f -> set.addAll(f.getFilterExpectEntrys()));
if (ands != null) ands.forEach(f -> set.addAll(f.getFilterExpectEntrys()));
return set;
}
/**
* 获取所有的class集合
*
* @return Set<FilterEntry<T>>
*/
public final Set> getAllFilterEntrys() {
HashSet> rs = new HashSet<>();
rs.addAll(getFilterEntrys());
rs.addAll(getFilterExpectEntrys());
return rs;
}
/**
* 自动扫描地过滤指定的class
*
* @param property AnyValue
* @param clazzname String
* @param url URL
*/
@SuppressWarnings("unchecked")
public final void filter(AnyValue property, String clazzname, URL url) {
filter(property, clazzname, true, url);
}
/**
* 过滤指定的class
*
* @param property application.xml中对应class节点下的property属性项
* @param clazzname class名称
* @param autoscan 为true表示自动扫描的, false表示显著调用filter, AutoLoad的注解将被忽略
*/
public final void filter(AnyValue property, String clazzname, boolean autoscan) {
filter(property, clazzname, autoscan, null);
}
/**
* 过滤指定的class
*
* @param property application.xml中对应class节点下的property属性项
* @param clazzname class名称
* @param autoscan 为true表示自动扫描的, false表示显著调用filter, AutoLoad的注解将被忽略
* @param url URL
*/
public final void filter(AnyValue property, String clazzname, boolean autoscan, URL url) {
boolean r = accept0(property, clazzname);
ClassFilter cf = r ? this : null;
if (r && ands != null) {
for (ClassFilter filter : ands) {
if (!filter.accept(property, clazzname)) return;
}
}
if (!r && ors != null) {
for (ClassFilter filter : ors) {
if (filter.accept(property, clazzname)) {
cf = filter;
break;
}
}
}
if (cf == null || clazzname.startsWith("sun.") || clazzname.contains("module-info")) return;
try {
Class clazz = classLoader.loadClass(clazzname);
if (!cf.accept(property, clazz, autoscan)) return;
if (cf.conf != null) {
if (property == null) {
property = cf.conf;
} else if (property instanceof DefaultAnyValue) {
((DefaultAnyValue) property).addAll(cf.conf);
} else {
DefaultAnyValue dav = new DefaultAnyValue();
dav.addAll(property);
dav.addAll(cf.conf);
property = dav;
}
}
AutoLoad auto = (AutoLoad) clazz.getAnnotation(AutoLoad.class);
if (autoscan && auto != null && !auto.value()) { //自动扫描且被标记为@AutoLoad(false)的
expectEntrys.add(new FilterEntry(clazz, autoscan, true, property));
} else {
entrys.add(new FilterEntry(clazz, autoscan, false, property));
}
} catch (Throwable cfe) {
if (finest && !clazzname.startsWith("sun.") && !clazzname.startsWith("javax.")
&& !clazzname.startsWith("com.sun.") && !clazzname.startsWith("jdk.") && !clazzname.startsWith("META-INF")
&& (!(cfe instanceof NoClassDefFoundError) || ((NoClassDefFoundError) cfe).getMessage().startsWith("java.lang.NoClassDefFoundError: java"))) {
logger.log(Level.FINEST, ClassFilter.class.getSimpleName() + " filter error for class: " + clazzname + (url == null ? "" : (" in " + url)), cfe);
}
}
}
/**
* 判断class是否有效
*
* @param classname String
*
* @return boolean
*/
public boolean accept(String classname) {
return accept(null, classname);
}
/**
* 判断class是否有效
*
* @param property AnyValue
* @param classname String
*
* @return boolean
*/
public boolean accept(AnyValue property, String classname) {
boolean r = accept0(property, classname);
if (r && ands != null) {
for (ClassFilter filter : ands) {
if (!filter.accept(property, classname)) return false;
}
}
if (!r && ors != null) {
for (ClassFilter filter : ors) {
if (filter.accept(property, classname)) return true;
}
}
return r;
}
private boolean accept0(AnyValue property, String classname) {
if (this.refused) return false;
if (this.privilegeIncludes != null && this.privilegeIncludes.contains(classname)) return true;
if (this.privilegeExcludes != null && this.privilegeExcludes.contains(classname)) return false;
if (classname.startsWith("java.") || classname.startsWith("javax.")) return false;
if (excludePatterns != null) {
for (Pattern reg : excludePatterns) {
if (reg.matcher(classname).matches()) return false;
}
}
if (includePatterns != null) {
for (Pattern reg : includePatterns) {
if (reg.matcher(classname).matches()) return true;
}
}
return includePatterns == null;
}
/**
* 判断class是否有效
*
* @param property AnyValue
* @param clazz Class
* @param autoscan boolean
*
* @return boolean
*/
@SuppressWarnings("unchecked")
public boolean accept(AnyValue property, Class clazz, boolean autoscan) {
if (this.refused || !Modifier.isPublic(clazz.getModifiers())) return false;
if (annotationClass != null && clazz.getAnnotation(annotationClass) == null) return false;
boolean rs = superClass == null || (clazz != superClass && superClass.isAssignableFrom(clazz));
if (rs && this.excludeSuperClasses != null && this.excludeSuperClasses.length > 0) {
for (Class c : this.excludeSuperClasses) {
if (c != null && (clazz == c || c.isAssignableFrom(clazz))) return false;
}
}
return rs;
}
public static Pattern[] toPattern(String[] regs) {
if (regs == null || regs.length == 0) return null;
int i = 0;
Pattern[] rs = new Pattern[regs.length];
for (String reg : regs) {
if (reg == null || reg.trim().isEmpty()) continue;
rs[i++] = Pattern.compile(reg.trim());
}
if (i == 0) return null;
if (i == rs.length) return rs;
Pattern[] ps = new Pattern[i];
System.arraycopy(rs, 0, ps, 0, i);
return ps;
}
public void setSuperClass(Class superClass) {
this.superClass = superClass;
}
public Class getSuperClass() {
return superClass;
}
public Class[] getExcludeSuperClasses() {
return excludeSuperClasses;
}
public void setExcludeSuperClasses(Class[] excludeSuperClasses) {
this.excludeSuperClasses = excludeSuperClasses;
}
public void setAnnotationClass(Class annotationClass) {
this.annotationClass = annotationClass;
}
public Pattern[] getIncludePatterns() {
return includePatterns;
}
public void setIncludePatterns(String[] includePatterns) {
this.includePatterns = toPattern(includePatterns);
}
public Pattern[] getExcludePatterns() {
return excludePatterns;
}
public void setExcludePatterns(String[] excludePatterns) {
this.excludePatterns = toPattern(excludePatterns);
}
public Class getAnnotationClass() {
return annotationClass;
}
public boolean isRefused() {
return refused;
}
public void setRefused(boolean refused) {
this.refused = refused;
}
public Set getPrivilegeIncludes() {
return privilegeIncludes;
}
public void setPrivilegeIncludes(Set privilegeIncludes) {
this.privilegeIncludes = privilegeIncludes == null || privilegeIncludes.isEmpty() ? null : privilegeIncludes;
}
public Set getPrivilegeExcludes() {
return privilegeExcludes;
}
public void setPrivilegeExcludes(Set privilegeExcludes) {
this.privilegeExcludes = privilegeExcludes == null || privilegeExcludes.isEmpty() ? null : privilegeExcludes;
}
/**
* 存放符合条件的class与class指定的属性项
*
* @param 泛型
*/
public static final class FilterEntry {
private final HashSet groups = new LinkedHashSet<>();
private final String name;
private final Class type;
private final AnyValue property;
private final boolean autoload;
private final boolean expect;
public FilterEntry(Class type, AnyValue property) {
this(type, false, false, property);
}
public FilterEntry(Class type, final boolean autoload, boolean expect, AnyValue property) {
this.type = type;
String str = property == null ? null : property.getValue("groups");
if (str != null) {
str = str.trim();
if (str.endsWith(";")) str = str.substring(0, str.length() - 1);
}
if (str != null) groups.addAll(Arrays.asList(str.split(";")));
this.property = property;
this.autoload = autoload;
this.expect = expect;
this.name = property == null ? "" : property.getValue("name", "");
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "[thread=" + Thread.currentThread().getName()
+ ", type=" + this.type.getSimpleName() + ", name=" + name + ", groups=" + groups + "]";
}
@Override
public int hashCode() {
return this.type.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
return (this.type == ((FilterEntry) obj).type && this.groups.equals(((FilterEntry) obj).groups) && this.name.equals(((FilterEntry) obj).name));
}
public Class getType() {
return type;
}
public String getName() {
return name;
}
public AnyValue getProperty() {
return property;
}
public boolean isEmptyGroups() {
return groups == null || groups.isEmpty();
}
public HashSet getGroups() {
return groups;
}
public boolean isAutoload() {
return autoload;
}
public boolean isExpect() {
return expect;
}
}
/**
* class加载类
*/
public static class Loader {
protected static final Logger logger = Logger.getLogger(Loader.class.getName());
protected static final ConcurrentMap> cache = new ConcurrentHashMap<>();
public static void close() {
cache.clear();
}
/**
* 加载当前线程的classpath扫描所有class进行过滤
*
* @param excludeFile 不需要扫描的文件夹, 可以为null
* @param excludeRegs 包含此关键字的文件将被跳过, 可以为null
* @param filters 过滤器
*
* @throws IOException 异常
*/
public static void load(final File excludeFile, final String[] excludeRegs, final ClassFilter... filters) throws IOException {
RedkaleClassLoader loader = (RedkaleClassLoader) Thread.currentThread().getContextClassLoader();
List urlfiles = new ArrayList<>(2);
List urljares = new ArrayList<>(2);
final URL exurl = excludeFile != null ? excludeFile.toURI().toURL() : null;
final Pattern[] excludePatterns = toPattern(excludeRegs);
for (URL url : loader.getAllURLs()) {
if (exurl != null && exurl.sameFile(url)) continue;
if (excludePatterns != null) {
boolean skip = false;
for (Pattern p : excludePatterns) {
if (p.matcher(url.toString()).matches()) {
skip = false;
break;
}
}
if (skip) continue;
}
if (url.getPath().endsWith(".jar")) {
urljares.add(url);
} else {
urlfiles.add(url);
}
}
List files = new ArrayList<>();
boolean debug = logger.isLoggable(Level.FINEST);
StringBuilder debugstr = new StringBuilder();
for (final URL url : urljares) {
Set classes = cache.get(url);
if (classes == null) {
classes = new LinkedHashSet<>();
try (JarFile jar = new JarFile(URLDecoder.decode(url.getFile(), "UTF-8"))) {
Enumeration it = jar.entries();
while (it.hasMoreElements()) {
String entryname = it.nextElement().getName().replace('/', '.');
if (entryname.endsWith(".class") && entryname.indexOf('$') < 0) {
String classname = entryname.substring(0, entryname.length() - 6);
if (classname.startsWith("javax.") || classname.startsWith("com.sun.")) continue;
//常见的jar跳过
if (classname.startsWith("com.mysql.")) break;
if (classname.startsWith("org.mariadb.")) break;
if (classname.startsWith("oracle.jdbc.")) break;
if (classname.startsWith("org.postgresql.")) break;
if (classname.startsWith("com.microsoft.sqlserver.")) break;
classes.add(classname);
if (debug) debugstr.append(classname).append("\r\n");
for (final ClassFilter filter : filters) {
if (filter != null) filter.filter(null, classname, url);
}
}
}
}
cache.put(url, classes);
} else {
for (String classname : classes) {
for (final ClassFilter filter : filters) {
if (filter != null) filter.filter(null, classname, url);
}
}
}
}
for (final URL url : urlfiles) {
Set classes = cache.get(url);
if (classes == null) {
classes = new LinkedHashSet<>();
files.clear();
File root = new File(url.getFile());
String rootpath = root.getPath();
loadClassFiles(excludeFile, root, files);
for (File f : files) {
String classname = f.getPath().substring(rootpath.length() + 1, f.getPath().length() - 6).replace(File.separatorChar, '.');
if (classname.startsWith("javax.") || classname.startsWith("com.sun.")) continue;
classes.add(classname);
if (debug) debugstr.append(classname).append("\r\n");
for (final ClassFilter filter : filters) {
if (filter != null) filter.filter(null, classname, url);
}
}
cache.put(url, classes);
} else {
for (String classname : classes) {
for (final ClassFilter filter : filters) {
if (filter != null) filter.filter(null, classname, url);
}
}
}
}
//if (debug) logger.log(Level.INFO, "scan classes: \r\n{0}", debugstr);
}
private static void loadClassFiles(File exclude, File root, List files) {
if (root.isFile() && root.getName().endsWith(".class")) {
files.add(root);
} else if (root.isDirectory()) {
if (exclude != null && exclude.equals(root)) return;
File[] lfs = root.listFiles();
if (lfs == null) throw new RuntimeException("File(" + root + ") cannot listFiles()");
for (File f : lfs) {
loadClassFiles(exclude, f, files);
}
}
}
}
}