PlayFramework1.2.7介绍及优化打包发布[三]

排除模板,编译分离

我们知道,项目真正需要编译的是我们的.java文件,成百上千个java文件的编译是需要耗费一定的时间的,但是一些静态文件,如图片,网页这些资源,其实是不需要编译的.对于一个java项目来说,编译的过程可以说就是对.java文件的处理,那么结合我们的play项目,我们实际上除了类文件外的其他所有资源都可以排除掉,不让他参与编译和输出,既我们只需要让其输出precompiled中的java文件夹即可.

尝试着排除所有文件打包,看看项目是否如我们所想

play war youapplication -o outpath --exclude app:conf:lib:test:public

上边我们将所有文件夹都排除,观察输出的内容

微信截图20191118101838.png微信截图20191118101902.png

结果仅留下了precompiled目录,且速度上又快了几十秒.

但是,我们将这个包替换原先的项目后放入tomcat中运行,项目会提示错误!报错内容会提示templates not find,缺少了模板文件?我们观察对比一下,看看究竟少了啥

微信截图20191118103404.png
微信截图20191118103355.png

第一张是报错的打包内容,第二张是正常的内容,我们发现少了app和conf 2个文件夹,app中包含了views文件夹,里面是所有模板的缓存文件,conf中有一个routes文件,是路由的缓存文件.这几个文件在前面介绍过,也是属于框架运行时会去查找的目录,原来我们把目录排出后,连缓存文件也不生成了,play在运行时为了加速模板文件的读取和执行,就会尝试去寻找缓存的模板和路由文件,找不到就报错了.

缓存的逻辑思路其实很简单,先去判断是否存在缓存,不存在则去读取原文件,若存在,则我们读取缓存.大都的缓存逻辑应该是这样的,play的缓存机制也应该如此.我们可以尝试将这些缓存文件中的乱码清空,仅保留文件的结构,相当于创建了一个空的模板缓存,发现项目可以正常运行.

通过上面的实验,我们知道不能将框架需要的模板文件也排除,且play框架在运行时会去找寻这些模板缓存文件.
这让我们的优化思路遇到了点阻碍,使用play自带的命令无法生成缓存文件.但是仔细想,我们仅仅只是生成一个和原文件相同的目录结构和文件结构,实际上不是什么太大的难题,并且既然缓存可以清除,那么我们实际上只要让java文件全部编译打包就可以了,其余的这些文件我们都可以看成是静态资源处理.

有了上面的思路,我们接下来要解决的问题就变成了:如何只做java文件的编译?并且根据上面排除指令的效果和缓存文件的存在,我们可以肯定的知道,play在打包时还会对模板文件进行编译缓存的操作,这一步也是我们需要分离出来的.查了半天帮助命令也没有找到相关分离打包的操作,遇到了框架层面的问题,我们唯一且快速有效的方式即是查看源码,从源码上找寻到底是如何编译这些文件.带着问题查看源码,可以加速我们的查询时间.

查询源码,找到编译类的方法

我们找到play1.2.7的play包,找到Play.java查看源码,我们可以搜索关键字,例如我们在输入打包命令时控制台会输出一句日志Precompiling ...,那么我们就可以搜索这个关键,搜索后我们在427行找到了打包的代码:

微信截图20191118110759.png

//play打包  
static boolean preCompile() {  
 if (usePrecompiled) {  
 if (getFile("precompiled").exists()) {  
 classloader.getAllClasses();  
 Logger.info("Application is precompiled", new Object[0]);  
 return true;  
 } else {  
 Logger.error("Precompiled classes are missing!!", new Object[0]);  
 fatalServerErrorOccurred();  
 return false;  
 }  
 } else {  
 try {  
 Logger.info("Precompiling ...", new Object[0]);  
 Thread.currentThread().setContextClassLoader(classloader);  
 long start = System.currentTimeMillis();  
 classloader.getAllClasses();  
 if (Logger.isTraceEnabled()) {  
 Logger.trace("%sms to precompile the Java stuff", new Object[]{System.currentTimeMillis() - start});  
 }  
  
 if (!lazyLoadTemplates) {  
 start = System.currentTimeMillis();  
 TemplateLoader.getAllTemplate();  
 if (Logger.isTraceEnabled()) {  
 Logger.trace("%sms to precompile the templates", new Object[]{System.currentTimeMillis() - start});  
 }  
 }  
  
 return true;  
 } catch (Throwable var2) {  
 Logger.error(var2, "Cannot start in PROD mode with errors", new Object[0]);  
 fatalServerErrorOccurred();  
 return false;  
 }  
 }  
 }  

这段源码逻辑很简单,注意观察到440行至454行,里面是打包的过程classloader.getAllClasses();
TemplateLoader.getAllTemplate();这2句明显是编译类和编译模板文件的操作,我们可以看到,play在编译时的确同时编译了java和模板文件,并且,在编译时是没有忽略需要排除的目录的,排除指令(–exclude)是在输出的时候发生的作用,这样就可以解释了,为什么排除所有的文件后,编译的速度并没有加速特别多,因为实际上play任然是对整个项目中的文件进行了遍历.

我们只需要他编译类文件即可,既只需要play执行打包命令中的

classloader.getAllClasses();

一种简单粗暴的方法是直接改源码,在源码中删除编译模板文件的逻辑.但秉承开闭原则,我们肯定是不应该去修改源码的,那么我们只能提取我们需要的逻辑自己组合成一个新的打包方法.

我们继续使用搜索关键字的形式查找具体的编译方法.因为最后的calss文件都输出到了precompiled/java文件夹中,我们再次搜索"precompiled/java/"关键字,可以在play.classloading.ApplicationClasses.java中找到一个方法:

//将字节码写入到.class文件中
public byte[] enhance() {  
 this.enhancedByteCode = this.javaByteCode;  
 if (this.isClass()) {  
 boolean shouldEnhance = true;  
  
 try {  
 CtClass ctClass = enhanceChecker_classPool.makeClass(new ByteArrayInputStream(this.enhancedByteCode));  
 if (ctClass.subclassOf(ctPlayPluginClass)) {  
 shouldEnhance = false;  
 }  
 } catch (Exception var4) {  
 }  
  
 if (shouldEnhance) {  
 Play.pluginCollection.enhance(this);  
 }  
 }  
  
 if (System.getProperty("precompile") != null) {  
 try {  
 File f = Play.getFile("precompiled/java/" + this.name.replace(".", "/") + ".class");  
 f.getParentFile().mkdirs();  
 FileOutputStream fos = new FileOutputStream(f);  
 fos.write(this.enhancedByteCode);  
 fos.close();  
 } catch (Exception var3) {  
 var3.printStackTrace();  
 }  
 }  
  
 return this.enhancedByteCode;  
 }  

这个方法既是将编译好的字节码输出到.calss文件的方法,我们再搜索一下哪些地方调用到了这个方法,查看一下官方的调用逻辑.

play.classloading.ApplicationClassloader.java中的158行我们找到了打包时调用该方法的代码

微信截图20191118114254.png

OK,基本方法我们已经找齐了,我们可以精简和参考源码的逻辑,写出一个test,来执行我们的优化打包逻辑:

import org.junit.Test;  
import play.Play;  
import play.classloading.ApplicationClasses.ApplicationClass;  
import play.test.FunctionalTest;  
  
import java.io.File;  
  
/**  
 * 单个文件打包编译  
 *  
 * @author 子牙  
 */  
public class SinglePrecompild extends FunctionalTest {  
 @Test  
 public void test() {  
 File precompiledFile = Play.getFile("/precompiled/");  
 if (precompiledFile.exists()) {  
 deleteDir(precompiledFile);// 删除打包文件  
 }  
 System.setProperty("precompile", "true");// 进入编译状态  
 //需要打包的类名  
 String[] path = {"jobs.EveryDayJob"};  
 compileAndenhance(path);  
 }  
  
 /**  
 * 编译并输出  
 *  
 * @param fileArray  
 */  
 private void compileAndenhance(String[] fileArray) {  
 for (ApplicationClass applicationClass : Play.classes.all()) {  
 for (String name : fileArray) {  
 if (name.contains(applicationClass.name)) {  
 applicationClass.compile();// 编译  
 applicationClass.enhance();// 输出  
 break;  
 }  
 }  
 }  
 }  
  
 /**  
 * 递归删除目录下的所有文件及子目录下所有文件  
 *  
 * @param dir 将要删除的文件目录  
 * @return boolean Returns "true" if all deletions were successful. If a  
 * deletion fails, the method stops attempting to delete and returns  
 * "false".  
 */  
 private boolean deleteDir(File dir) {  
 if (dir.isDirectory()) {  
 String[] children = dir.list();  
 // 递归删除目录中的子目录下  
 for (int i = 0; i < children.length; i++) {  
 boolean success = deleteDir(new File(dir, children[i]));  
 if (!success) {  
 return false;  
 }  
 }  
 }  
 // 目录此时为空,可以删除  
 return dir.delete();  
 }  
}  
  

稍微说明一下几个关键点:

  1. 若需要调用框架中的方法,我们毕需要运行框架的单元测试(目前没有找到单独运行的方式)

  2. 根据打包的代码逻辑,我们需要设置系统参数System.setProperty("precompile", "true")使其进入编译的状态

3.play启动时,其实compile()编译这一步操作就已经完成了,这一步是可以注释掉的

这边的关键代码在compileAndenhance方法中,先编译(这一步其实可以省略),然后将编译好的字节码写入文件中,并且可以实现打个文件的编译打包操作.若需要全部文件打包,我们只需要去除if判断,让所有的文件都编译即可.执行一下上方的test,编译单个文件,时间仅需5s!感觉速度还是很快的!

微信截图20191118115731.png

我们再试一下全部文件的打包,看看需要花多久

微信截图20191118120133.png

2m23s!!我们的项目可是有上千个文件的,对于这个时间,优化的效果已经显而易见了,从原先的4~5分钟,缩短到了2分半内!

至此我们已经用源码中的方法,取代了原先的打包命令,成功编译出了.class文件,已经可以应付所有的代码变动的更新需求,并且可以实现单个文件的编译了,我们完全的把编译分离了出来. 那么接下去我们就需要将重新组合我们的输出,使其在结构上和原先的打包输出一致了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值