【问题标题】:Use thymeleaf template of another project使用另一个项目的百里香模板
【发布时间】:2019-11-13 05:32:33
【问题描述】:

如果在多个项目中使用百里香似乎会有问题。

假设我在项目 2 中有一个 spring 应用程序,在项目 1 中有一个控制器。通过正确定义 @ComponentScan,应用程序能够在我的模板文件夹中显示视图/html。但是,一旦我添加了 thymeleaf-layout-dialect maven 依赖项,它就会失败并显示“出现意外错误(类型=未找到,状态=404)。”错误。

我的项目:

prj1 pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>macrohard.org</groupId>
  <artifactId>prj1</artifactId>
  <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> 
    </parent>

 <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>nz.net.ultraq.thymeleaf</groupId>
        <artifactId>thymeleaf-layout-dialect</artifactId>
    </dependency>

</dependencies>
</project>

prj2 pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>macrohard.org</groupId>
  <artifactId>prj2</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <dependencies>
    <dependency>
        <groupId>macrohard.org</groupId>
        <artifactId>prj1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

hello-world.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>HW</title>
</head>
<body>
    Hello World!!
</body>
</html>

prj2中的App2:

package prj2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = { "prj1"} )
public class App2 {
    public static void main(String[] args) {
        SpringApplication.run(App2.class, args);
    }
}

prj1中的Controller1

package prj1.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
@RequestMapping(value="/api")
public class Controller1 {

    // this can be run by App2 & App1
    @GetMapping(value="/hello")
    public String hello() {
        return "hello world";
    }

    // this can be run by App1 only
    @GetMapping(value="/hello2")
    public ModelAndView hello2() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("hello-world");
        return mav;
    }

}

和Controller1一样,“api/hello”可以请求成功,因为它不涉及模板。但是对于“api/hello2”请求,它无法渲染 hello-world.html。以下是错误堆栈:

2019-07-02 22:21:28.758  INFO 19680 --- [           main] prj2.App2                                : Started App2 in 1.89 seconds (JVM running for 2.454)
2019-07-02 22:21:51.807  INFO 19680 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-07-02 22:21:51.807  INFO 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-07-02 22:21:51.807 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.multipart.support.StandardServletMultipartResolver@2ded92
2019-07-02 22:21:51.810 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No LocaleResolver 'localeResolver': using default [AcceptHeaderLocaleResolver]
2019-07-02 22:21:51.811 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No ThemeResolver 'themeResolver': using default [FixedThemeResolver]
2019-07-02 22:21:51.814 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No RequestToViewNameTranslator 'viewNameTranslator': using default [DefaultRequestToViewNameTranslator]
2019-07-02 22:21:51.817 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No FlashMapManager 'flashMapManager': using default [SessionFlashMapManager]
2019-07-02 22:21:51.817 DEBUG 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2019-07-02 22:21:51.817  INFO 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 10 ms
2019-07-02 22:21:51.829 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/api/hello2", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2019-07-02 22:21:51.834 TRACE 19680 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.web.servlet.ModelAndView prj1.web.Controller1.hello2()
2019-07-02 22:21:51.843 TRACE 19680 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: []
2019-07-02 22:21:51.851 DEBUG 19680 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8]
2019-07-02 22:21:51.851 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Rendering view [org.springframework.web.servlet.view.InternalResourceView: name 'hello-world'; URL [hello-world]] 
2019-07-02 22:21:51.851 DEBUG 19680 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView  : View name 'hello-world', model {}
2019-07-02 22:21:51.853 DEBUG 19680 --- [nio-8080-exec-1] o.s.w.servlet.view.InternalResourceView  : Forwarding to [hello-world]
2019-07-02 22:21:51.857 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : "FORWARD" dispatch for GET "/api/hello-world", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2019-07-02 22:21:51.860 TRACE 19680 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to HandlerExecutionChain with [ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]] and 3 interceptors
2019-07-02 22:21:51.862 DEBUG 19680 --- [nio-8080-exec-1] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2019-07-02 22:21:51.862 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No view rendering, null ModelAndView returned.
2019-07-02 22:21:51.862 DEBUG 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Exiting from "FORWARD" dispatch, status 404, headers={}
2019-07-02 22:21:51.864 DEBUG 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND, headers={}
2019-07-02 22:21:51.865 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2019-07-02 22:21:51.865 TRACE 19680 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ /error, produces [text/html]}, { /error}]
2019-07-02 22:21:51.866 TRACE 19680 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2019-07-02 22:21:51.875 TRACE 19680 --- [nio-8080-exec-1] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [org.apache.catalina.core.ApplicationHttpRequest@6fdc83, org.apache.catalina.connector.ResponseFacade@119a78b]
2019-07-02 22:21:51.885 DEBUG 19680 --- [nio-8080-exec-1] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
2019-07-02 22:21:51.886 TRACE 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Rendering view [org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$StaticView@216e30] 
2019-07-02 22:21:51.908 DEBUG 19680 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404, headers={}

我确信这是由于 thymeleaf-layout-dialect,因为如果我从 pom.xml 中删除它,一切正常。 问题是我需要它来布局 thymeleaf 页面。如何在另一个与spring应用不同的项目中显示模板?

【问题讨论】:

  • 呃 - 为什么不直接将模板复制到第二个项目中?
  • @paulsm4 我不是想在项目/控制器之间共享模板。在现实世界的场景中,prj1 和 prj2 会有自己不同的模板。只有与 prj1 相关的模板才会在 prj1 中,对于 prj2 也是如此。每个项目中也会有Controller。现在,我只有一个 Spring App,我应该把它放在 prj2 中,因为 prj2 依赖于 prj1。那么App尝试使用Controller1就会出现上述问题。

标签: maven spring-boot spring-mvc thymeleaf


【解决方案1】:

由于您发布了一个示例项目,我无法关注该问题:/

您确定您不只是将来自控制器的“hello world”消息与来自模板的消息混淆了吗?

当我修改时:\prj1\src\main\resources\templates\hello-world.html

<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>HW</title>
</head>
<body>
    Hello World!! (from Template in prj1)!
</body>
</html>

由于一些奇怪的类路径问题,我不得不将其添加到 prj1 的 pom.xml 中: (您使用的 thymeleaf-layout-dialect 库似乎带来了一个额外的 thymeleaf jar - 没有对此进行调查)

 <dependency>
     <groupId>org.thymeleaf</groupId>
     <artifactId>thymeleaf</artifactId>
     <version>3.0.11.RELEASE</version>
 </dependency>

当我运行 prj2 应用程序并调用:http://localhost:8080/api/hello3http://localhost:8080/api/hello2 我明白了:

Hello World!! (from Template in prj1)!

我们想要什么?

当我打电话时:http://localhost:8080/api/hello 由于一些未找到的模板(“hello world”当然不是模板文件),我收到一个错误。

所以我不确定为什么它不适合你:/

【讨论】:

  • 非常感谢@wemu 原因:将 thymeleaf-layout-dialect 添加到 maven 依赖项时,maven 将使用旧版本的 thymeleaf-3.0.00.RELEASE.jar,它不能正常工作。解决方案:需要像你一样添加thymeleaf依赖,然后maven会使用thymeleaf-3.0.11.RELEASE.jar没有问题。我应该责怪 maven 还是 thymeleaf?
  • 不是他们。 maven 是一个依赖管理工具,它会做一些自动的事情来匹配最常见的结果,但只是作为一个假设。依赖管理本身仍然是开发人员的责任。因此,您的项目中最终会出现哪个版本是您告诉 maven 的工作。 Maven 使用“最短路径”算法来确定使用哪个版本的工件。在这种情况下,我假设错误的版本更接近正确的版本。因此,也可以从布局依赖项中排除 thymeleaf 依赖项,以回退到 spring 提供的版本。所以开发者选择了。
【解决方案2】:

我不确定在没有这种依赖关系的情况下它是如何工作的。使用模板的控制器可以直接返回模板名称。而且您正在实施休息控制器,这些控制器建议的默认值与使用模板所需的默认值不同(如内容类型)。

所以调用模板的控制器看起来像(这是我的做法,不能声称我知道它是唯一的):

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MyController {

    @Autowired
    public MyController() {
    }

    @RequestMapping("/hello-world")
    public String hello(Model model) {

        model.addAttribute("someKey", "someValue");
        return "hello-world"; // this is the template name in src/main/resources
    }

}

通过这种方式,还可以将控制器/模板捆绑在一个 jar 中,并在不同的 Spring Boot 应用程序中使用它们作为依赖项。因为它都是基于类路径的(there is a property to configure this: spring.thymeleaf.prefix),所以这对我有用。

我认为原因确实是你的休息控制器 - 这可能会让事情有点混乱。

【讨论】:

  • 我试过你的方法,但它不起作用。它仍然给我“无视图渲染,返回 null ModelAndView。退出“FORWARD”调度,状态 404,headers={}。错误。你自己测试过吗?
  • 是的,我有一个像这样工作的应用程序。我没有尝试你的例子。您是否尝试将@RestController 更改为@Controller?
  • 是的,这是我换成Controller后得到的。对于 RestController,您的示例将改为显示“hello-world”字符串。
  • 看起来百里香叶罐不见了。我认为 prj1 可以只是一个不需要成为 Spring Boot 应用程序本身的依赖项。 prj2 可能需要spring boot parent 或spring boot maven 插件来创建一个propper spring boot uber jar。你有一个 github repo,里面有你的例子吗?
猜你喜欢
  • 2015-11-29
  • 2015-06-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-19
  • 2013-02-03
  • 1970-01-01
  • 2020-02-05
  • 2021-01-09
相关资源
最近更新 更多