springboot使用 enjoy模板引擎(利用spring-boot-devtools支持热加载)

声明:本文主要介绍在用springboot集成enjoy模板引擎后,修改java代码或者前台代码时项目热加载重启之后出现异常(com.jfinal.template.TemplateException: object is not an instance of declaring class)的解决方案以及原因;

简单介绍spring-boot-devtools:

      当我们用springboot构建项目的时候,如果需要其支持热加载(即修改代码保存后项目自动重新启动,节省开发时间)则需要引入spring-boot-devtools依赖,maven坐标如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

        如果我们打算集成enjoy模板引擎,则还需要引入enjoy的maven依赖,坐标如下:

<!-- enjoy模板引擎 begin -->
<dependency>
    <groupId>com.jfinal</groupId>
    <artifactId>enjoy</artifactId>
    <version>3.2</version>
</dependency>
<!-- enjoy模板引擎 end -->

        具体的配置这里不再赘述,请参考jfinal官方文档,其中有章节对springboot整合enjoy做出了详细介绍。做到这一步我们大多数人一般会觉得万事大吉可以愉快的在springboot项目中使用enjoy,而不用继续使用thymeleaf,freemarker诸如此类的模板引擎了(至少我是屁颠屁颠的准备愉快的干活了~),然而“惊喜”(异常)马上就要出现了!接下来贴测试代码,首先是实体类和controller代码:

/**
 * Description: 用户实体类
 */
public class User {
    private String name;
    private int age;
    
    set get 方法忽略......
}

@Controller
public class TestController {
    @RequestMapping("/test")
    public String test(HttpServletRequest request, 
                HttpServletResponse response, Model model) {
        User user = new User();
        user.setName("张三");
        user.setAge(20);
        model.addAttribute("user", user);
        return "test";
    }
}

接着是html代码:

<!DOCTYPE html>
<html>
<head>
    <title>test</title>
</head>
<body>
    用户姓名:<p>#(user.name)</p>
    用户年龄:<p>#(user.age)</p>
</body>
</html>

接着不用说,启动项目,访问http://localhost:8080/test,页面如下图所示,如您所见,一切正常:

image.png

重点来了:此时你随便修改下代码,比如在页面上多加一行如下所示:

<!DOCTYPE html>
<html>
<head>
    <title>test</title>
</head>
<body>
    用户姓名:<p>#(user.name)</p>
    用户年龄:<p>#(user.age)</p>
    用户年龄:<p>#(user.age)</p> <!-- 新加的一行 -->
</body>
</html>

此时项目会进行热加载,之后刷新页面,这时你一定会觉得一切照常,只不过页面上多了一行 用户年龄:20 罢了,但实际是:控制台会报如下异常:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.jfinal.template.TemplateException: object is not an instance of declaring class

Template: "/view/test.html". Line: 7

局部截图如下:

image.png

image.png

而此时如果你把服务关了,重新启动项目,在没有修改任何代码的情况下,刷新页面,呈现给你的将是你之前所期待的那样,页面上只不过多显示了一句 用户年龄:20,究竟是什么原因导致的呢?下面几句话简单分析下(引用百度来的内容,期待大神在评论中具体分析下~):

    spring-boot-devtools帮助springboot项目实现热加载的原理是:其使用了两
个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包,即我们通过
maven引进来的jar包),另一个ClassLoader加载会更改的类(即我们自己写的),称为
restartClassLoader,这样在有代码更改的时候,原来的restartClassLoader 被丢弃,
重新创建一个restartClassLoader,但是以jar形式引入的class文件(即编译后的文件)
会使用基础的ClassLoader(也就是AppClassLoader)加载,不是restartClassLoader,
也就是说我们修改了java代码或者html启动热加载后,第三方的jar和自己写的文件被加载
的classLoader并不是同一个。

    说到这里,解决办法其实也就出来了,我们只需要让加载第三方jar的classLoader和自己写的文件的classLoader是同一个就OK,对于enjoy来说,只需要让enjoy-3.2.jar中的文件和我们自己写的文件是同一个classLoader加载即可,方法就是 在src/main/resources目录下面创建META-INF文件夹,然后创建spring-devtools.properties文件,文件里面的内容如下即可:

restart.include.thirdparty=/enjoy-3.2.jar

综上所述:在你用springboot项目打算集成enjoy模板引擎的时候,如果打算使用spring-boot-devtools来支持springboot项目实现热加载的话(方便开发的时候提高调试效率),在按照Jfinal官方文档中关于配置enjoy的步骤做完之后,只需要在src/main/resources目录下面创建META-INF文件夹,然后创建spring-devtools.properties文件,其中加上

restart.include.thirdparty=/enjoy-3.2.jar

即可。

------下面的不重要,看到这里即可

补充说明一个问题:如果你只想修改java文件的时候热加载,修改html css js时不热加载,只需要在application.properties文件中加上

spring.devtools.restart.exclude=static/**,view/**

即可,我的静态资源 css js在src/main/resources/static文件夹下放着,html在src/main/resources/view的子目录下放着

评论区

walking_

2017-10-31 21:25

欢迎大神前来具体分析深层原因~

JFinal

2017-11-01 15:24

这个分享太有用了,而且解决方案比我预想的要好,通过简单配置就可以搞定问题

我预先想到的方案是用 jetty maven plugin 来代替 spring-boot-devtools

阿帕奇

2018-03-19 14:46

楼主你好,麻烦你能对springboot整合enjoy做出了详细介绍,提供一下整合的文章地址吗?

walking_

2018-04-09 16:01

@阿帕奇 参考jfinal官方文档

阿帕奇

2018-04-26 13:35

@walking_ 非常感谢你,不过我遇到了新的问题,就是在保存session的时候,在页面怎么也取不到;对于jfinal 的 me.add(new SessionInViewInterceptor()); 这句代码应该怎么使用呢?

walking_

2018-04-30 15:00

@阿帕奇 不好意思 您遇到的具体问题我并没有研究过,建议您在俱乐部请教其它人~

阿帕奇

2018-05-02 10:01

@walking_ 谢谢,问题已经解决了,参考的这篇文章http://www.jfinal.com/share/531 只需要在EnjoyConfig 中设置session就OK了,比如:jfr.setSessionInView(true);//是否支持以 #(session.value) 的方式访问 session

lwcompany

2018-07-09 20:02

添加本地包,jdk8,jetty-server-8.1.8 jfinal-3.4-bin-with-src 然后在pom.xml中设置一个依赖包试下 org.springframework.boot
spring-boot-starter-web
2.0.2.RELEASE
,项目会自动下载其它spring boot依赖包,

lwcompany

2018-07-09 20:04

亲测kcgljfinal-----------------spring boot 好用 http://localhost:8093/hello Hello JFinal World

JFinal

2020-07-10 15:24

配置改成下面的更好:
restart.include.thirdparty=/enjoy-[\\w.-]+.jar

不用关心版本升级