spring boot使用jsp的一个大坑

现象

最近有个spring boot的应用总感觉卡卡的,前前后后花了三四天的时间才定位到问题所在。现象是程序刚跑起来的时候,做任何数据库查询都是正常的毫秒内完成,一旦涉及到更新的事务提交就会慢到3秒以上,而且只有第一次事务提交这个现象是百分百重现,之后的事务提交则是间歇性出现。花了很长时间,把cpu、内存、网络、数据库、连接池的问题全都排除了,还是找不到问题的原因。只好使出最无奈的一招,把全局日志级别调到TRACE,每一行代码之间打日志看具体哪一行慢,慢的过程中有什么特别的TRACE日志。上天眷顾,果真出现了端倪:

2019-11-14 00:20:37.251 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :     --> Returning ClassNotFoundException
2019-11-14 00:20:37.251 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :     --> Passing on ClassNotFoundException
2019-11-14 00:20:37.252 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :     findClass(com.renaissance.core.entity.BaseEntityCustomizer)
2019-11-14 00:20:37.253 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :       findClassInternal(com.renaissance.core.entity.BaseEntityCustomizer)
2019-11-14 00:20:39.695 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :     --> Returning ClassNotFoundException
2019-11-14 00:20:39.695 DEBUG 7035 --- [http-nio-8081-exec-1] o.a.c.loader.WebappClassLoaderBase       :     --> Passing on ClassNotFoundException

这种时候,花2秒的时间加做类加载?而且还没加载到!!!WTF?
经过了一顿的google操作,终于找到了问题的根源。

为什么慢?

https://github.com/spring-projects/spring-boot/issues/16471
根据github里面的讨论,这种情况只会出现在spring boot使用jsp的时候,因为使用jsp要求你将项目打成war包,通过java -jar xxx.war的方式运行的时候,类加载需要经历两层压缩嵌套,war包和war包内WEB-INF/lib内的jar包,tomcat的WebappClassLoaderBase没有对这种加载做过优化,所以会异常慢。

为什么会时不时地就加载类?

https://stackoverflow.com/questions/39234159/springboot-embedded-tomcat-classloader-slowness
根据stackoverflow上的讨论,因为embeded tomcat默认会开启reloadable热加载,默认每超过15秒都会尝试重新加载。这也大概解释了为什么日志显示加载不到还是能正常跑,估计首次启动的时候是通过spring boot的类加载成功了,后面通过tomcat的热加载不支持的两层嵌套就会一直失败,不过这也纯粹是我的猜测,但因为懒惰,我也没有深入去验证这个猜测!

解决方案

既然是war包导致的,换到jar包就好了。spring boot大部分人应该本来就打的是jar包,所以不会遇到这个问题,这大概也是spring boot那些作者认为这个bug是个low priority的原因吧(这些人也太不负责任了)。
但是如果你的项目里面用了jsp,按照官方文档,只有打war包才能让jsp生效!幸好有个阿里大神给出了让jar包也能支持jsp的代码方案。http://hengyunabc.github.io/spring-boot-fat-jar-jsp-sample/
按照大神的说法,servlet 3规范里的应用jar包的META-INF/resources就是一个ResourceSet,所以只要在项目的resources目录里新建META-INF/resources,把jsp放在里面就ok了。但是后来spring boot 1.4调整了fat jar里面的结构,将项目代码都放在fat jar的BOOT-INF/classes目录里面,导致tomcat扫不到META-INF/resources了,所以需要手动把fatjar根目录下的META-INF/ classes加入resourceSet。
对于大部分人来说这个大神的这个解决方案是可行的,然而,放到我的项目里面却还是不行!!!又是几个小时的时间,我发现,尽管是相同的spring boot版本,用maven打出来的jar包和用gradle打出来的jar包是不一样的。maven出来的jar包确实如大神所说jsp会被放在在fatjar的META-INF/resources目录里面,但是gradle出来的jar包jsp却是在BOOT-INF/classes/META-INF/resources里面,我的天!看来我只好修改一下大神的代码了。
修改点包括:

  1. 增加applicationClass参数,确保StaticResourceConfigurer这个类放在其他jar中获取的路径也不会出错。
  2. // when run as exploded directory那一段几乎很少用的上,所以我注释掉了。
  3. 把/BOOT-INF/classes/META-INF/resources也加入resourceSet。
public class StaticResourceConfigurer implements LifecycleListener {

    private final Context context;

    private final Class applicationClass;

    public StaticResourceConfigurer(Context context, Class applicationClass) {
        this.context = context;
        this.applicationClass = applicationClass;
    }

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            URL location = applicationClass.getProtectionDomain().getCodeSource().getLocation();

//          if (ResourceUtils.isFileURL(location)) {
//              // when run as exploded directory
//              String rootFile = location.getFile();
//              if (rootFile.endsWith("/BOOT-INF/classes/")) {
//                  rootFile = rootFile.substring(0, rootFile.length() - "/BOOT-INF/classes/".length() + 1);
//              }
//              if (!new File(rootFile, "META-INF" + File.separator + "resources").isDirectory()) {
//                  return;
//              }
//
//              try {
//                  location = new File(rootFile).toURI().toURL();
//              } catch (MalformedURLException e) {
//                  throw new IllegalStateException("Can not add tomcat resources", e);
//              }
//          }

            String locationStr = location.toString();
            // when run as fat jar
            if (locationStr.endsWith("/BOOT-INF/classes!/")) {
                locationStr = locationStr.substring(0, locationStr.length() - "/BOOT-INF/classes!/".length() + 1);
                try {
                    location = new URL(locationStr);
                } catch (MalformedURLException e) {
                    throw new IllegalStateException("Can not add tomcat resources", e);
                }
                // maven jar
                this.context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/", location, "/META-INF/resources");
                // gradle jar
                this.context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/", location, "/BOOT-INF/classes/META-INF/resources");
            }

        }
    }
}

也附上我的demo地址,使用的是gradle+spring boot 2.1.0。
https://github.com/shougaoshougao/spring-boot-fat-jar-jsp-sample

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容