我有一个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
答案 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";
}
我希望它也能解决您的问题。