RT 实现多个JFinalConfig扫描加载的功能,目前没有使用场景,主要是拿JF新出的Scanner练练手,最近比较忙都没有分享有意思的东西了,回家前撸了一下,感觉还行,贴🐴 吧~
思路:
做一个主服务MainConfig,然后模仿@Path写个@JFConfig扫描加载项目内的其他MyConfig到一个LIst集合中,再MainConfig的每个回调接口中,都循环调用LIst里面的其他Config对应方法即可达到这个效果。社友反馈需求https://jfinal.com/feedback/7980
先建立一个MainConfig.java为JF配置入口
package com.demo.common.config;
import com.jfinal.config.*;
import com.jfinal.server.undertow.UndertowServer;
import com.jfinal.template.Engine;
import java.util.List;
public class MainConfig extends JFinalConfig {
/**
* 启动入口,运行此 main 方法可以启动项目,此 main 方法可以放置在任意的 Class 类定义中,不一定要放于此
*/
public static void main(String[] args) {
UndertowServer.start(MainConfig.class);
}
protected List<JFinalConfig> configs;
public MainConfig(){
this.configs = new JFinalConfigScanner("com.demo.common.config").scan();
System.out.println("装载JFinalConfigs>" + configs);
}
@Override
public void configConstant(Constants me) {
for (JFinalConfig config : configs) {
config.configConstant(me);
}
}
@Override
public void configRoute(Routes me) {
for (JFinalConfig config : configs) {
config.configRoute(me);
}
}
@Override
public void configEngine(Engine me) {
for (JFinalConfig config : configs) {
config.configEngine(me);
}
}
@Override
public void configPlugin(Plugins me) {
for (JFinalConfig config : configs) {
config.configPlugin(me);
}
}
@Override
public void configInterceptor(Interceptors me) {
for (JFinalConfig config : configs) {
config.configInterceptor(me);
}
}
@Override
public void configHandler(Handlers me) {
for (JFinalConfig config : configs) {
config.configHandler(me);
}
}
@Override
public void onStart() {
for (JFinalConfig config : configs) {
config.onStart();
}
}
@Override
public void onStop() {
for (JFinalConfig config : configs) {
config.onStop();
}
}
}JFinalConfigs.java 注解用于配置 JFinalConfig
package com.demo.common.config;
import java.lang.annotation.*;
/**
* JFinalConfigs 注解用于配置 JFinalConfig
* 搭配 JFinalConfigScanner 实现多个JFinalConfig扫描加载功能
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface JFinalConfigs {
int NULL_ORDER = 0;
/**
* 加载顺序
* @return
*/
int order() default NULL_ORDER;
}FinalConfigScanner.java 扫描 @JFinalConfigs 注解,实现多个JFinalConfig扫描加载功能
package com.demo.common.config;
import com.jfinal.config.JFinalConfig;
import com.jfinal.core.PathScanner;
import com.jfinal.kit.ReflectKit;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* JFinalConfigScanner 扫描 @JFinalConfigs 注解,实现多个JFinalConfig扫描加载功能
*/
public class JFinalConfigScanner {
// 存放已被扫描过的 JFinalConfig
private final List<JFinalConfig> scannedJFinalConfig = new ArrayList<>();
// 过滤被扫描的资源
private Predicate<URL> resourceFilter = null;
// 扫描的基础 package,只扫描该包及其子包之下的类
private String basePackage;
// 跳过不需要被扫描的类
private Predicate<String> classSkip;
private ClassLoader classLoader;
public JFinalConfigScanner(String basePackage, Predicate<String> classSkip) {
if (StrKit.isBlank(basePackage)) {
throw new IllegalArgumentException("basePackage can not be blank");
}
String bp = basePackage.replace('.', '/');
bp = bp.endsWith("/") ? bp : bp + '/'; // 添加后缀字符 '/'
bp = bp.startsWith("/") ? bp.substring(1) : bp; // 删除前缀字符 '/'
this.basePackage = bp;
this.classSkip = classSkip;
}
public JFinalConfigScanner(String basePackage) {
this(basePackage, null);
}
/**
* 过滤被扫描的资源,提升安全性
*
* <pre>
* 例子:
* PathScanner.filter(url -> {
* String res = url.toString();
*
* // 如果资源在 jar 包之中,并且 jar 包文件名不包含 "my-project" 则过滤掉
* // 避免第三方 jar 包中的 Controller 被扫描到,提高安全性
* if (res.contains(".jar") && !res.contains("my-project")) {
* return false; // return false 表示过滤掉当前资源
* }
*
* return true; // return true 表示保留当前资源
* });
* </pre>
*/
public void filter(Predicate<URL> resourceFilter) {
this.resourceFilter = resourceFilter;
}
public List<JFinalConfig> scan() {
try {
classLoader = getClassLoader();
List<URL> urlList = getResources();
scanResources(urlList);
order();
return scannedJFinalConfig;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void order(){
Collections.sort(scannedJFinalConfig, new Comparator<JFinalConfig>() {
@Override
public int compare(JFinalConfig j1, JFinalConfig j2) {
return Integer.compare(//
j1.getClass().getAnnotation(JFinalConfigs.class).order(),//
j2.getClass().getAnnotation(JFinalConfigs.class).order());
}
});
}
private ClassLoader getClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
return ret != null ? ret : PathScanner.class.getClassLoader();
}
private List<URL> getResources() throws IOException {
List<URL> ret = new ArrayList<>();
// 用于去除重复
Set<String> urlSet = new HashSet<>();
// ClassLoader.getResources(...) 参数只支持包路径分隔符为 '/',而不支持 '\'
Enumeration<URL> urls = classLoader.getResources(basePackage);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 过滤不需要扫描的资源
if (resourceFilter != null && !resourceFilter.test(url)) {
continue ;
}
String urlStr = url.toString();
if ( ! urlSet.contains(urlStr) ) {
urlSet.add(urlStr);
ret.add(url);
}
}
return ret;
}
private void scanResources(List<URL> urlList) throws IOException {
for (URL url : urlList) {
String protocol = url.getProtocol();
if ("jar".equals(protocol)) {
scanJar(url);
} else if ("file".equals(protocol)) {
scanFile(url);
}
}
}
private void scanJar(URL url) throws IOException {
URLConnection urlConn = url.openConnection();
if (urlConn instanceof JarURLConnection) {
JarURLConnection jarUrlConn = (JarURLConnection)urlConn;
try (JarFile jarFile = jarUrlConn.getJarFile()) {
Enumeration<JarEntry> jarFileEntries = jarFile.entries();
while (jarFileEntries.hasMoreElements()) {
JarEntry jarEntry = jarFileEntries.nextElement();
String en = jarEntry.getName();
// 只扫描 basePackage 之下的类
if (en.endsWith(".class") && en.startsWith(basePackage)) {
// JarEntry.getName() 返回值中的路径分隔符在所有操作系统下都是 '/'
en = en.substring(0, en.length() - 6).replace('/', '.');
scanJFinalConfig(en);
}
}
}
}
}
private void scanFile(URL url) {
String path = url.getPath();
path = decodeUrl(path);
File file = new File(path);
String classPath = getClassPath(file);
scanFile(file, classPath);
}
private void scanFile(File file, String classPath) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File fi : files) {
scanFile(fi, classPath);
}
}
}
else if (file.isFile()) {
String fullName = file.getAbsolutePath();
if (fullName != null && fullName.endsWith(".class")) {
String className = fullName.substring(classPath.length(), fullName.length() - 6).replace(File.separatorChar, '.');
scanJFinalConfig(className);
}
}
}
private String getClassPath(File file) {
String ret = file.getAbsolutePath();
// 添加后缀,以便后续的 indexOf(bp) 可以正确获得下标值,因为 bp 确定有后缀
if ( ! ret.endsWith(File.separator) ) {
ret = ret + File.separator;
}
// 将 basePackage 中的路径分隔字符转换成与 OS 相同,方便处理路径
String bp = basePackage.replace('/', File.separatorChar);
int index = ret.lastIndexOf(bp);
if (index != -1) {
ret = ret.substring(0, index);
}
return ret;
}
@SuppressWarnings("unchecked")
private void scanJFinalConfig(String className) {
// 跳过不需要被扫描的 className
if (classSkip != null && classSkip.test(className)) {
return;
}
Class<?> c = loadClass(className);
if (c == null){
return;
}
if(!JFinalConfig.class.isAssignableFrom(c)){
return;
}
JFinalConfigs configs = c.getAnnotation(JFinalConfigs.class);
if(configs == null){
return;
}
JFinalConfig config = (JFinalConfig)ReflectKit.newInstance(c);
if (config != null && !scannedJFinalConfig.contains(config)) {
scannedJFinalConfig.add(config);
}
}
private Class<?> loadClass(String className) {
try {
return classLoader.loadClass(className);
}
// 此处不能 catch Exception,否则抓不到 NoClassDefFoundError,因为它是 Error 的子类
catch (Throwable t) {
Log.getLog(PathScanner.class).debug("PathScanner can not load the class \"" + className + "\"");
/**
* 由于扫描是一种主动行为,所以 pom.xml 中的 provided 依赖会在此被 loadClass,
* 从而抛出 NoClassDefFoundError、UnsupportedClassVersionError、
* ClassNotFoundException 异常。return null 跳过这些 class 不处理
*
* 如果这些异常并不是 provided 依赖的原因而引发,也会在后续实际用到它们时再次抛出异常,
* 所以 return null 并不会错过这些异常
*/
return null;
}
}
/**
* 支持路径中存在空格百分号等等字符
*/
private String decodeUrl(String url) {
try {
return URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}代码是从JF的 @Path中拷贝出来,稍改改OK,再写俩测试类试试
package com.demo.common.config;
import com.jfinal.config.*;
import com.jfinal.kit.LogKit;
import com.jfinal.template.Engine;
@JFinalConfigs
public class My1Config extends JFinalConfig {
@Override
public void configConstant(Constants me) {
System.out.println("My1Config>configConstant");
}
@Override
public void configRoute(Routes me) {
System.out.println("My1Config>configRoute");
}
@Override
public void configEngine(Engine me) {
System.out.println("My1Config>configEngine");
}
@Override
public void configPlugin(Plugins me) {
System.out.println("My1Config>configPlugin");
}
@Override
public void configInterceptor(Interceptors me) {
System.out.println("My1Config>configInterceptor");
}
@Override
public void configHandler(Handlers me) {
System.out.println("My1Config>configHandler");
}
@Override
public void onStart() {
System.out.println("My1Config>onStart");
}
@Override
public void onStop() {
System.out.println("My1Config>onStop");
}
}package com.demo.common.config;
import com.jfinal.config.*;
import com.jfinal.kit.LogKit;
import com.jfinal.template.Engine;
@JFinalConfigs(order = 1)
public class My2Config extends JFinalConfig {
@Override
public void configConstant(Constants me) {
System.out.println("My2Config>configConstant");
}
@Override
public void configRoute(Routes me) {
System.out.println("My2Config>configRoute");
}
@Override
public void configEngine(Engine me) {
System.out.println("My2Config>configEngine");
}
@Override
public void configPlugin(Plugins me) {
System.out.println("My2Config>configPlugin");
}
@Override
public void configInterceptor(Interceptors me) {
System.out.println("My2Config>configInterceptor");
}
@Override
public void configHandler(Handlers me) {
System.out.println("My2Config>configHandler");
}
@Override
public void onStart() {
System.out.println("My2Config>onStart");
}
@Override
public void onStop() {
System.out.println("My2Config>onStop");
}
}开始测试!
运行MainConfig.java中的main方法
public static void main(String[] args) {
UndertowServer.start(MainConfig.class);
}效果:

Scanner很快啊,我一点,唰的就执行完了。。。
回家~
jfinal 的 Scanner 精心设计过,性能、场景适应性极好