jfinal-webjars实现

问题:   目前正在看jfinal,考虑使用webjars来管理静态资源,发现没有现成的实现方式,自己就抛砖引玉


解决方案:


需要添加如下依赖

<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>jquery</artifactId>
			<version>3.4.1</version>
		</dependency>
		<dependency>
			<groupId>org.webjars.npm</groupId>
			<artifactId>bootstrap</artifactId>
			<version>4.4.1</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.6</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>


工具类:

package com.demo.common;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.IOUtils;

public class WebjarsUtil {

	public static void main(String[] args) throws Exception {
		/* 资源文件路径,不能以'/'字符字符开头 */
		String resourcePath = "META-INF/resources/webjars/bootstrap/4.4.1/dist/css/bootstrap.min.css";
		/* 获取ClassPath下的所有jar路径 */
		String[] cps = System.getProperty("java.class.path").split(";");
		for (String cp : cps) {
			if (!cp.endsWith(".jar")) {
				continue;
			}
			if (cp.indexOf("bootstrap")<0) {
				continue;
			}
			InputStream in = loadResourceFromJarFile(cp, resourcePath);
			if (in != null) {
				IOUtils.copy(in, new FileOutputStream(new File("aaa.txt")));
				System.err.println(IOUtils.toString(in,Charset.forName("UTF-8")));
				in.close();
			}
		}

		/* 读取本地Jar文件 */
		for (String cp : cps) {
			if (!cp.endsWith(".jar")) {
				continue;
			}
			InputStream in = loadResourceFromJarURL(cp, resourcePath);
			if (in != null) {
				//System.err.println(IOUtils.toString(in,Charset.forName("UTF-8")));
				in.close();
			}
		}

		/* 读取网络Jar文件 */
//		InputStream in = loadResourceFromJarURL(
//				"http://localhost:8080/SpringStruts2Integration/struts2-spring-plugin-2.3.4.1.jar", resourcePath);
//		if (in != null) {
//			System.err.println(IOUtils.toString(in));
//			in.close();
//		}
	}

	/**
	 * 读取Jar文件中的资源
	 * 
	 * @param jarPath 本地jar文件路径
	 * @param resPath 资源文件所在jar中的路径(不能以'/'字符开头)
	 * @return 如果资源加载失败,返回null
	 */
	public static InputStream loadResourceFromJarFile(String jarPath, String resPath) {
		if (!jarPath.endsWith(".jar")) {
			return null;
		}
		try {
			JarFile jarFile = new JarFile(jarPath);
			JarEntry jarEntry = jarFile.getJarEntry(resPath);
			if (jarEntry == null) {
				return null;
			}
			System.out.println(jarPath);
			return jarFile.getInputStream(jarEntry);
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 读取Jar文件中的资源
	 * 
	 * @param jarUrl  本地jar文件或网络上(ttp://host:port/subpath/jarfile.jar)jar文件路径
	 * @param resPath 资源文件所在jar中的路径(不能以'/'字符开头)
	 * @return 如果资源加载失败,返回null
	 */
	public static InputStream loadResourceFromJarURL(String jarUrl, String resPath) {
		if (!jarUrl.endsWith(".jar")) {
			return null;
		}
		URL url = null;
		if (jarUrl.startsWith("http://")) {
			try {
				url = new URL("jar:" + jarUrl + "!/");
			} catch (MalformedURLException e) {
				e.printStackTrace();
				return null;
			}
		} else {
			try {
				url = new URL("jar:file:/" + jarUrl + "!/");
			} catch (MalformedURLException e) {
				e.printStackTrace();
				return null;
			}
		}
		try {
			JarURLConnection jarURLConnection;
			jarURLConnection = (JarURLConnection) url.openConnection();
			JarFile jarFile = jarURLConnection.getJarFile();
			JarEntry jarEntry = jarFile.getJarEntry(resPath);
			if (jarEntry == null) {
				return null;
			}
			return jarFile.getInputStream(jarEntry);
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
}


    Handler实现类

package com.demo.common;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import com.jfinal.handler.Handler;

public class MyHandler extends Handler{

	@Override
	public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
		if (target.contains("/webjars/")) {
			String resourcePath = StringUtils.substringAfter(target, "/webjars/");
			resourcePath = "META-INF/resources/webjars/"+ resourcePath;
			
			/* 获取ClassPath下的所有jar路径 */
			String[] cps = System.getProperty("java.class.path").split(";");
			boolean findFlag = false;
			for (String cp : cps) {
				if (!cp.endsWith(".jar")) {
					continue;
				}
				try(
						InputStream in = WebjarsUtil.loadResourceFromJarFile(cp, resourcePath);
				){
					if(in!=null) {
						IOUtils.copy(in, response.getOutputStream());
						findFlag = true;
						break;
					}
				} catch (IOException e) {
					System.out.println("cant get static resource : " + resourcePath + " from jar");
				}
			}
			if(!findFlag) {
				//TODO 没有找到静态资源
				response.setStatus(404);
			}			 
			isHandled[0] = true;
		}else {
			this.next.handle(target, request, response, isHandled);
		
		}
		
		
	

	}

}


启动类:

package com.demo.common;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;

import com.demo.blog.BlogController;
import com.demo.common.model._MappingKit;
import com.demo.index.IndexController;
import com.jfinal.config.Constants;
import com.jfinal.config.Handlers;
import com.jfinal.config.Interceptors;
import com.jfinal.config.JFinalConfig;
import com.jfinal.config.Plugins;
import com.jfinal.config.Routes;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.server.undertow.UndertowServer;
import com.jfinal.template.Engine;

/**
 * 本 demo 仅表达最为粗浅的 jfinal 用法,更为有价值的实用的企业级用法
 * 详见 JFinal 俱乐部: http://jfinal.com/club
 * 
 * API 引导式配置
 */
public class DemoConfig extends JFinalConfig {
	
	static Prop p;
	
	/**
	 * 启动入口,运行此 main 方法可以启动项目,此 main 方法可以放置在任意的 Class 类定义中,不一定要放于此
	 * @throws IOException 
	 * @throws URISyntaxException 
	 */
	public static void main(String[] args) throws IOException, URISyntaxException {
		
		String path = PathKit.class.getResource("/").toURI().getPath();
		String file = new File(path).getParentFile().getParentFile().getCanonicalPath();
		System.out.println(file);
		
		
		
		path = getClassLoader().getResource("").toURI().getPath();
		file = new File(path).getAbsolutePath();
		System.out.println(file);
		
		UndertowServer.start(DemoConfig.class);
	}
	
	private static ClassLoader getClassLoader() {
		ClassLoader ret = Thread.currentThread().getContextClassLoader();
		return ret != null ? ret : PathKit.class.getClassLoader();
	}
	
	/**
	 * PropKit.useFirstFound(...) 使用参数中从左到右最先被找到的配置文件
	 * 从左到右依次去找配置,找到则立即加载并立即返回,后续配置将被忽略
	 */
	static void loadConfig() {
		if (p == null) {
			p = PropKit.useFirstFound("demo-config-pro.txt", "demo-config-dev.txt");
		}
	}
	
	/**
	 * 配置常量
	 */
	public void configConstant(Constants me) {
		loadConfig();
		
		me.setDevMode(p.getBoolean("devMode", false));
		
		/**
		 * 支持 Controller、Interceptor、Validator 之中使用 @Inject 注入业务层,并且自动实现 AOP
		 * 注入动作支持任意深度并自动处理循环注入
		 */
		me.setInjectDependency(true);
		
		// 配置对超类中的属性进行注入
		me.setInjectSuperClass(true);
	}
	
	/**
	 * 配置路由
	 */
	public void configRoute(Routes me) {
		me.add("/", IndexController.class, "/index");	// 第三个参数为该Controller的视图存放路径
		me.add("/blog", BlogController.class);			// 第三个参数省略时默认与第一个参数值相同,在此即为 "/blog"
	}
	
	public void configEngine(Engine me) {
		me.addSharedFunction("/common/_layout.html");
		me.addSharedFunction("/common/_paginate.html");
	}
	
	/**
	 * 配置插件
	 */
	public void configPlugin(Plugins me) {
		// 配置 druid 数据库连接池插件
		DruidPlugin druidPlugin = new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim());
		me.add(druidPlugin);
		
		// 配置ActiveRecord插件
		ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
		// 所有映射在 MappingKit 中自动化搞定
		_MappingKit.mapping(arp);
		me.add(arp);
	}
	
	public static DruidPlugin createDruidPlugin() {
		loadConfig();
		
		return new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim());
	}
	
	/**
	 * 配置全局拦截器
	 */
	public void configInterceptor(Interceptors me) {
		
	}
	
	/**
	 * 配置处理器
	 */
	public void configHandler(Handlers me) {
		me.add(new MyHandler());
	}
}


layouthtml文件

#define layout()
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="zh-CN" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<link href="/assets/css/manage.css" media="screen" rel="stylesheet" type="text/css" />
<script src="/assets/js/jquery-1.4.4.min.js" type="text/javascript" ></script>
<link href="/webjars/bootstrap/4.4.1/dist/css/bootstrap.min.css" media="screen" rel="stylesheet" type="text/css"/>
<link href="/webjars/bootstrap/4.4.1/dist/js/bootstrap.min.js" media="screen" rel="stylesheet" type="text/css"/>
<link href="/webjars/bootstrap/4.4.1/dist/css/bootstrap.min.js" media="screen" rel="stylesheet" type="text/css"/>

</head>
<body>
<div>
<div>
<div>
<a href="http://www.jfinal.com" target="_blank">JFinal web framework</a>
</div>
<div id="nav">
<ul>
<li><a href="/"><b>首页</b></a></li>
<li><a href="/blog"><b>Blog管理</b></a></li>
</ul>
</div>
</div>
<div>
#@main()
</div>
</div>
</body>
</html>
#end



效果


image.png



项目源码

评论区

JFinal

2019-12-12 12:32

webjars 曾经有几个人问过,博主是第一个给出完整方案的,核心在于用 Handler 接管静态请求导流到 webjars

思路清晰,代码简洁,谢谢你的分享

山东小木

2019-12-12 12:48

这样做的好处是?

残风vs誓梦

2019-12-13 09:58

@山东小木 优化静态资源依赖问题,如果静态资源升级,通过pom依赖的方式快速升级,回滚版本。存在即合理

热门分享

扫码入社