【问题标题】:Why Thymeleaf templates are not found?为什么找不到 Thymeleaf 模板?
【发布时间】:2019-11-22 09:38:25
【问题描述】:

我有一个 Spring Boot 2.1.6 应用程序(Spring 5),我想使用 Thymeleaf 作为我的模板引擎。我按照在线教程设置我的项目、视图和控制器,当我想启动它时,我注意到 Thymeleaf 抱怨它无法找到任何模板:

2019-07-12T17:14:25,269 WARN  [main] o.s.b.a.t.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration: Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)

我认为我应该按原样设置项目(至少根据我能找到的教程和论坛):

src/main/
    java/
        a.b.c.MyController
        rest of the classes and packages
    resources/
        static/
            css/
                bootstrap.min.css
                main.css
            js/
                bootstrap.min.js
                jquery-3.4.1.min.js
                login.js
                main.js
        templates/
            login.html
            main.html

我的控制器如下所示:

@ApiOperation(value = "Get login page", nickname = "login", notes = "", tags = { "My App", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "Success") })
@GetMapping(value = { "/", "/login" })
@ResponseStatus(code = HttpStatus.OK)
public String login(Model model, String error, String logout) {
    if (error != null) {
        model.addAttribute("error", "Your username and/or password is invalid.");
    }

    if (logout != null) {
        model.addAttribute("message", "You have been logged out successfully.");
    }

    return "login";
}

login.html 如下所示:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>My App :: Login</title>
        <link rel="stylesheet" type="text/css" href="@{/css/bootstrap.min.css}">
        <link rel="stylesheet" type="text/css" href="@{/css/main.css}">
    </head>
    <body>
        <h1>My App</h1>

        <div class="container">
            <form id="userform" method="post"  action="#" th:action="@{/authenticate}" th:object="${userForm}" class="form-signin">
                <h2 class="form-heading">Log In</h2>

                <span>${message}</span>

                <div class="form-group ${status.error ? 'has-error' : ''}">
                    <input type="text" class="form-control" placeholder="Username" autofocus th:field="*{username}"></input>
                </div>

                <input name="password" id="password" type="password" class="form-control" placeholder="Password" th:field="*{password}"/>

                <input type="hidden" th:name="${ _csrf.parameterName }" th:value="${ _csrf.token }"/>
                <button class="btn btn-lg btn-primary btn-block" type="submit">Log In</button>
            </form>
        </div>

        <script src="@{/js/jquery-3.4.1.min.js}"></script>
        <script src="@{/js/bootstrap.min.js}"></script>
        <script src="@{/js/login.js}"></script>
    </body>
</html>

当我打开登录页面时,我得到一个简单的 HTML 页面,正文中写着“登录”一词。

我觉得这很奇怪,Thymeleaf 正在寻找 'classpath:/templates/' 中的模板,这应该是正确的,因为我在 application.properties 中将 log4j2 XML 配置为 'logging.config=classpath:log4j2-${ spring.profiles.active}.xml',并且这个 XML 位于同一个 src/main/resources 文件夹中。那么那里找不到模板文件夹的原因是什么?

更新:

我忘了说:我尝试从 Eclipse 中将它作为 Spring Boot 应用程序运行,并尝试使用 Maven 作为 mvn spring-boot:run 运行它,结果相同。

另外,我使用的是 Java 12。我的 pom.xml 看起来像这样:

<?xml version="1.0" encoding="UTF-8"?>
<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>my.groupid</groupId>
    <artifactId>my.artifactid</artifactId>
    <packaging>war</packaging>
    <name>MyApp</name>
    <version>${baseversion}.${gitcommitcount}.${buildnumber}</version>
    <description>My App</description>

    <properties>
        <baseversion>1.0.0</baseversion>
        <java.version>12</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <springfox-version>2.9.2</springfox-version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>

        <buildnumber>0</buildnumber>
        <gitcommitcount>0</gitcommitcount>
    </properties>

    <distributionManagement>
        <repository>
            <id>id</id>
            <name>Internal Local Releases</name>
            <url>http://x.x.x.x:xxxx/repository/local_release/</url>
        </repository>
        <snapshotRepository>
            <id>id</id>
            <name>Internal Local Snapshots</name>
            <url>http://x.x.x.x:xxxx/repository/local_snapshot/</url>
        </snapshotRepository>
    </distributionManagement>

    <repositories>
        <repository>
            <id>id</id>
            <url>x.x.x.x:xxxx/repository/local_group/</url>
        </repository>
    </repositories>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <build>
        <finalName>${project.name}</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <!--
                    Will need to be excluded from final WAR
                    <exclude>*.properties</exclude>
                    <exclude>*.xml</exclude>
                    -->
                </excludes>
                <includes>
                    <!-- Include is only for running locally -->
                    <include>*.properties</include>
                    <include>*.xml</include>
                </includes>
            </resource>
        </resources>
        <sourceDirectory>src/main/java</sourceDirectory>

        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <release>12</release>
                    <compilerArgs>
                        <arg>--enable-preview</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <jvmArguments>--enable-preview</jvmArguments>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <archiveClasses>false</archiveClasses>
                    <warSourceDirectory>WebContent</warSourceDirectory>
                    <archive>
                        <manifestEntries>
                            <Built-On>${maven.build.timestamp} UTC</Built-On>
                            <ModuleName>${project.name}</ModuleName>
                            <ModuleVersion>${project.version}</ModuleVersion>
                        </manifestEntries>
                        <manifestSections>
                            <manifestSection>
                                <name>Release section</name>
                                <manifestEntries>
                                    <BaseVersion>${baseversion}</BaseVersion>
                                    <BuildNumber>${buildnumber}</BuildNumber>
                                    <GITRevision>${gitrevision}</GITRevision>
                                </manifestEntries>
                            </manifestSection>
                        </manifestSections>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>rename-wars</id>
                        <phase>install</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>scripts/rename-wars.bat</executable>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Logging -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!--SpringFox dependencies -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox-version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox-version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.joschi.jackson</groupId>
            <artifactId>jackson-datatype-threetenbp</artifactId>
            <version>2.6.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>tomcat-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- Commons HttpClient -->
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <!-- Commons IO -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- Oracle JDBC -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>12.2.0.1.0</version>
        </dependency>
        <!-- CSV parsing -->
        <dependency>
            <groupId>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>4.6</version>
        </dependency>
        <!-- Javax Mail for email validation -->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>
        <!-- Configuration -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Actuator to gather metrics and health -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- JSON -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20180813</version>
        </dependency>
        <!-- Testing dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>com.vaadin.external.google</groupId>
                    <artifactId>android-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

更新 2:application.properties

server.port=9080
spring.profiles.active=dev
spring.jackson.date-format=a.b.c.RFC3339DateFormat
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
logging.config=classpath:log4j2-${spring.profiles.active}.xml

# Setting session timeout
server.servlet.session.timeout=10m

# ThymeLeaf settings
spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# dbcp2 settings
spring.datasource.dbcp2.test-while-idle=true
spring.datasource.dbcp2.test-on-borrow=true
spring.datasource.dbcp2.test-on-return=false
spring.datasource.dbcp2.validation-query=select 1 from dual
spring.datasource.dbcp2.validation-query-timeout=30000
spring.datasource.dbcp2.time-between-eviction-runs-millis=30000
spring.datasource.dbcp2.min-evictable-idle-time-millis=30000
spring.datasource.dbcp2.initial-size=10
spring.datasource.dbcp2.max-total=20
spring.datasource.dbcp2.pool-prepared-statements=true
spring.datasource.dbcp2.log-abandoned=true
spring.datasource.dbcp2.log-expired-connections=true
spring.datasource.dbcp2.max-wait-millis=1000
spring.datasource.dbcp2.remove-abandoned-on-borrow=true
spring.datasource.dbcp2.remove-abandoned-on-maintenance=true
spring.datasource.dbcp2.remove-abandoned-timeout=60
spring.datasource.dbcp2.num-tests-per-eviction-run=3
spring.datasource.dbcp2.default-auto-commit=true

# File upload settings
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=-1
spring.servlet.multipart.max-request-size=-1

# Actuator settings

# Actuator endpoint settings
management.endpoint.shutdown.enabled=true
management.endpoint.health.enabled=true
management.endpoint.health.show-details=always
management.endpoint.metrics.enabled=true
management.endpoint.loggers.enabled=true
management.endpoint.info.enabled=true
management.endpoints.web.exposure.include=health,metrics,loggers,info
management.health.cassandra.enabled=false
management.health.couchbase.enabled=false
management.health.db.enabled=true
management.health.diskspace.enabled=true
management.health.diskspace.path=/
management.health.elasticsearch.enabled=false
management.health.influxdb.enabled=false
management.health.ldap.enabled=false
management.health.mail.enabled=false
management.health.mongo.enabled=false
management.health.neo4j.enabled=false
management.health.rabbit.enabled=false
management.health.redis.enabled=false
management.health.solr.enabled=false

# App info for actuator
info.app.name=My App
info.app.description=My App
info.app.version=1.0.0
info.customer=My App

更新 3:添加了模板和视图解析器,如下所示:

@Configuration
public class TemplateBeans implements WebMvcConfigurer {

    @Autowired
    private ServletContext servletContext;

    @Bean
    @Description("Thymeleaf template resolver serving HTML5")
    public ServletContextTemplateResolver templateResolver() {
        ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver(
                servletContext);

        servletContextTemplateResolver.setPrefix("classpath:/templates/");
        servletContextTemplateResolver.setCacheable(false);
        servletContextTemplateResolver.setSuffix(".html");
        servletContextTemplateResolver.setTemplateMode("HTML5");
        servletContextTemplateResolver.setCharacterEncoding("UTF-8");

        return servletContextTemplateResolver;
    }

    @Bean
    @Description("Thymeleaf template engine with Spring integration")
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();

        springTemplateEngine.setTemplateResolver(templateResolver());

        return springTemplateEngine;
    }

    @Bean
    @Description("Thymeleaf view resolver")
    public ViewResolver viewResolver() {
        ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();

        thymeleafViewResolver.setTemplateEngine(templateEngine());
        thymeleafViewResolver.setCharacterEncoding("UTF-8");

        return thymeleafViewResolver;
    }
}

有了这个,我得到了例外:

2019-07-15T14:43:21,382 DEBUG [http-nio-9080-exec-3] o.s.w.s.FrameworkServlet: Failed to complete request: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/classpath:/templates/login.html")
2019-07-15T14:43:21,389 ERROR [http-nio-9080-exec-3] o.a.j.l.DirectJDKLog: Servlet.service() for servlet [dispatcherServlet] in context with path [/AICGDPR] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "/classpath:/templates/login.html")] with root cause
java.io.FileNotFoundException: ServletContext resource "/classpath:/templates/login.html" does not exist

我也尝试过使用 ClassLoaderTemplateResolver 而不是 ServletContextTemplateResolver,但遇到了一些不同的异常:

2019-07-15T14:48:54,208 DEBUG [http-nio-9080-exec-1] o.s.w.s.FrameworkServlet: Failed to complete request: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "classpath:/templates/login.html")
2019-07-15T14:48:54,217 ERROR [http-nio-9080-exec-1] o.a.j.l.DirectJDKLog: Servlet.service() for servlet [dispatcherServlet] in context with path [/AICGDPR] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "classpath:/templates/login.html")] with root cause
java.io.FileNotFoundException: ClassLoader resource "classpath:/templates/login.html" could not be resolved

【问题讨论】:

  • 您可以添加您的应用程序属性/yaml 文件吗?
  • 您是说一切正常,但您看到的是警告消息?
  • @want2learn 不,模板没有打开,我只在 HTML 正文中看到字符串“login”,而不是“login.html”模板。但我认为这与 Thymeleaf 记录未找到模板的警告的原因相同。
  • 只是想知道您是否正在使用 restcontroller 注释。确保您使用的是控制器注解。
  • @want2learn 我使用了RestController注解,很好的调用,改成Controller后,我收到一个错误,说模板无法解析。所以切换注解后,至少是在尝试解析模板。

标签: spring-boot spring-mvc thymeleaf


【解决方案1】:

将登录页面的位置更改为静态而不是模板。 应该是这样的,

resources/static/login.html 而不是resources/templates/login.html

别忘了在你的控制器中指定扩展,return "login.html";

(希望您已经配置了视图解析器,因为如果没有,您的端点将返回字符串“login.html”)

【讨论】:

  • 不,还是一样。我将 html 文件移动到静态文件夹,并尝试使用“login”和“login.html”,仍然相同。另外,当一切都在默认位置时,我不确定模板解析器的意义何在?据我所知,在模板解析器中,您只能配置我添加到 applicaiton.properties 中的相同内容(模板位置、前缀、后缀等)。
  • 是的,我的错误,由于某种原因,我阅读了 Template Resolver 而不是 View Resolver :) 我现在添加了它们,请查看更新后的问题
  • 您的 login.html 在哪里。它仍然在资源/模板中还是您将其移动资源/静态
  • 它在资源/模板中。但我也尝试过资源/静态(复制,而不是移动)。
  • 另外,我想知道,所有教程都说,对于 Spring Boot,我只需要包含 thymeleaf-starter 依赖项,并将我的资源放在模板文件夹中,全部完成,不需要客户任何类型的解析器。我需要所有这些自定义代码才能使它工作,我会错过什么?
【解决方案2】:

最终改用 JSP。第一次尝试完美无瑕。

【讨论】:

    【解决方案3】:

    嘿,我遇到了同样类型的问题。很长一段时间后,我发现我缺少类级别的映射。例如,我正在为我的案例编写此代码:

    @Controller
    public class ProviderController {
        @GetMapping(value = "providers")
        public String getAllProviders(Model model){
            model.addAttribute("roomReservations", null);
            return "provider";
        }
    

    而 thyleaf 没有检测到我的视线。我通过使用以下代码解决了这个问题:

    @RequestMapping(value = "providers")
    @Controller
    public class ProviderController {
    @GetMapping
        public String getAllProviders(Model model){
            model.addAttribute("roomReservations", null);
            return "provider";
        }
    

    我希望它也能解决您的问题。

    【讨论】:

    • 感谢您的想法,不幸的是,我可能无法再验证这一点,也许有一个新的测试项目。有时间我会试试的。
    猜你喜欢
    • 2023-01-14
    • 2019-04-05
    • 1970-01-01
    • 2020-08-27
    • 2017-06-21
    • 2023-03-04
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多