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 精心设计过,性能、场景适应性极好