为什么找不到Thymeleaf模板?

时间:2019-07-12 15:31:58

标签: spring-boot spring-mvc thymeleaf

我有一个Spring Boot 2.1.6应用程序(春季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页面,在正文中写有“ login”一词。

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

更新:

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

此外,我正在使用Java12。我的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

3 个答案:

答案 0 :(得分:0)

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

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

别忘了在控制器return "login.html";

中指定扩展名

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

答案 1 :(得分:0)

最终改为使用JSP。首次尝试即可完美运行。

答案 2 :(得分:-1)

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

@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";
    }

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