0

I am new to spring 5 and spring boot. I am trying to create a spring 5/spring boot application with thymeleaf. I want to creates a war rather than use the embedded webserver with spring boot.

When I deploy my war, my application starts and I can access test html pages in src/main/resources/static/ with javascript in them which call my controllers. I can perform round trips on these pages to my controller and db.

However, I get a 404 when I try to open a thymeleaf page located at src/main/resources/templates/testtemplate.html

Relevant maven:

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.3.5.RELEASE</version>
        </dependency>

Application:

@SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class, DataSourceAutoConfiguration.class})
public class WarApplication extends SpringBootServletInitializer
    {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
        {
        return application.sources(WarApplication.class);
        }

    public static void main(String[] args) throws Exception
        {
        SpringApplication.run(WarApplication.class, args);
        }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException
        {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(DataServiceConfig.class);

        servletContext.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebConfig.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
        }
    }

WebConfig:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.myproject")
public class WebConfig implements WebMvcConfigurer
    {

    @Autowired
    ApplicationContext ctx;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)
        {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        }

    @Bean
    @Description("Thymeleaf Template Resolver")
    public SpringResourceTemplateResolver  templateResolver()
        {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver ();
        templateResolver.setPrefix("/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
        }


    @Bean
    @Description("Thymeleaf Template Engine")
    public SpringTemplateEngine templateEngine()
        {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.setEnableSpringELCompiler(true);
        templateEngine.setTemplateEngineMessageSource(messageSource());
        return templateEngine;
        }
    
    @Bean
    @Description("Thymeleaf View Resolver")
    public ThymeleafViewResolver viewResolver()
        {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setOrder(1);
        return viewResolver;
        }


    @Bean
    @Description("Spring Message Resolver")
    public ResourceBundleMessageSource messageSource()
        {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        return messageSource;
        }

In the web config, if I remove the addResourceHandlers method it changes nothing. I can still find my static pages at localhost:8080/. I tried adding this line to it: registry.addResourceHandler("/**").addResourceLocations("classpath:/templates/");

When I do I can then access the thymeleaf template at localhost:8080/mytemplate.html. However, it appears as a static page. The fragments are not translated. The "th" tags appear to be ignored.

I also tried removing the templateResolver, viewResolver and templateEngine beans from my webconfig as I thought maybe I was overwriting some automatic configuration. This didn't have any effect.

I believe my directory structure is fairly standard:

src/main/
       /java/com/myproject/[code here]
       /resources/static[web pages here]
       /resources/templates[thymeleaf pages here]

What am I missing?
I am a total newbie with modern spring. So I might be doing somethign boneheaded. All this autoconfiguration in springboot stuff gets really frustrating as I can't figure out how to debug what it is doing.

springcorn
  • 611
  • 2
  • 15
  • 28
  • I noticed you are using Spring Boot but you are defining your own dependency versions and Thymeleaf config. Adding `spring-boot-starter-thymeleaf` would be enough. https://github.com/gtiwari333/spring-boot-blog-app please take a ref to this – gtiwari333 Nov 10 '20 at 18:58
  • Thymeleaf templates needs to be compiled. But it looks you are trying to load them as static HTML files. – gtiwari333 Nov 10 '20 at 19:01
  • @gitiwari333 Thanks for responses. I tried removing the other dependencies and that made no difference. I don't understand what you mean by "Thymeleaf templates needs to be compiled" ? – springcorn Nov 10 '20 at 20:56
  • I commented about the dependency because the way you defined dependency+versions is not the recommended way. – gtiwari333 Nov 10 '20 at 23:20
  • `When I do I can then access the thymeleaf template at localhost:8080/mytemplate.html. However, it appears as a static page. The fragments are not translated. The "th" tags appear to be ignored.` --> here you are getting the thymeleaf page as static (fragment and th tags not rendered)... That's because Thymeleaf didn't process(compile) those – gtiwari333 Nov 10 '20 at 23:22
  • These thymeleaf pages are served using MVC pattern by the @Controller classes. – gtiwari333 Nov 10 '20 at 23:25
  • I understand your comment, the thymeleaf needs to be processed. So there is now way to access a thymeleaf page directly? Like what about a basic index.html kind of thymeleaf page? Is there a way to to tell if the resolver is picking them up? – springcorn Nov 11 '20 at 01:46
  • My question is why are you using thymeleaf? how are you sending dynamic data to thymeleaf to render? OR you are using thymeleaf just to render some fragments etc? – gtiwari333 Nov 11 '20 at 02:57
  • 1
    1) If you plan on using truely a dynamic rendering(with dynamic data) I believe you must use Spring MVC. 2) If you are using thymeleaf just to generate html(without dynamic data), the solution would be to pre-render the html and serve as static content. – gtiwari333 Nov 11 '20 at 02:59
  • 1
    3) even if you don't want to render dynamic data but serve the thymeleaf page.. I believe you should use `@Controller` to return the thymeleaf html file name as response. – gtiwari333 Nov 11 '20 at 03:01
  • I have created a webapp in a very long time. I created a spring mvc back end. First time doing spring mvc so everything new. Now I am trying to mock up experiment with some different front ends to play with until I pick a direction. The two I picked out are thymeleaf and vue.js. Thymeleaf is the first try so. You are very helpful. Thank you much... – springcorn Nov 11 '20 at 03:19
  • Two problems apparently. 1)I thought you could access the templates directly. Don't really understand why, but apparently not. I created a / mapping and can now go from there. 2)@Controller looks like the biggest issue. @RestController doesn't seem to work for thymeleaf. Is there a way to make RestController work with thymeleaf? – springcorn Nov 11 '20 at 03:21
  • `@RestController` is to return JSON. You need `@Controller` – gtiwari333 Nov 11 '20 at 03:26
  • You can use thymeleaf and vue/angular etc together. See my example. The app.js can contain any js code – gtiwari333 Nov 11 '20 at 03:27

1 Answers1

1

Here's how you can serve thymeleaf html with static js/css resources with a simple spring @Controller

HTML - with thymeleaf

homePage.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Hello App</title>
    <!-- /resources/static/ folder is automatically mapped for static files -->
    <script th:src="@{/js/app.js}"></script>

</head>
<body>

<h2 th:text="${msg}"></h2>


</body>
</html>

app.js:

alert("Hello Alert!")

Java with Controller

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@SpringBootApplication
public class WebApp {
    public static void main(String[] args) {
        SpringApplication.run(WebApp.class, args);
    }
}

@Controller
class CtrlA {

    @GetMapping({"", "/"})
    String home(Model m) {
        m.addAttribute("msg", "Hello World");
        return "homePage";
    }
}

Directory structure:

├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── demo
│       │       ├── DemoApplication.java
│       │       └── WebApp.java
│       └── resources
│           ├── application.properties
│           ├── static
│           │   └── js
│           │       └── app.js
│           └── templates
│               └── homePage.html

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>gt</groupId>
    <artifactId>web</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

gtiwari333
  • 24,554
  • 15
  • 75
  • 102