Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use JSP and Thymeleaf views in Spring Boot app

There are several similar questions on the site and on the web in general but I haven't been able to make them work in my example as much as I've tried.

I'm working with Spring Boot for the first time and I'm stuck trying to include JSP views via an InternalResourceViewResolver. I already got Thymeleaf views to work.

Application.java

@SpringBootApplication
@ComponentScan("controller")
@EnableWebSecurity
@Configuration
public class Application extends WebSecurityConfigurerAdapter {
    public static void main(String args[]) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setCacheable(false);
        resolver.setOrder(1);
        return resolver;
    }

    //intended for the .jsp view
    @Bean
    public InternalResourceViewResolver jspResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("classpath:/templates/jsp/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        resolver.setOrder(2);
        return resolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(new UrlTemplateResolver());
        templateEngine.addTemplateResolver(templateResolver());

        //IKD if/how I should somehow add jspResolver() here

        templateEngine.addDialect(new SpringSecurityDialect());
        templateEngine.addDialect(new LayoutDialect(new GroupingStrategy()));
        templateEngine.addDialect(new Java8TimeDialect());
        return templateEngine;
    }

    @Bean
    @Description("Thymeleaf View Resolver")
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setOrder(0);
        return viewResolver;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/users/**").hasRole("USER")//USER role can access /users/**
                .antMatchers("/admin/**").hasRole("ADMIN")//ADMIN role can access /admin/**
                .antMatchers("/quests/**").permitAll()// anyone can access /quests/**
                .anyRequest().authenticated()//any other request just need authentication
                .and()
                .formLogin();//enable form login
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.inMemoryAuthentication()
                .passwordEncoder(passwordEncoder())
                .withUser("tim").password(passwordEncoder().encode("123")).roles("ADMIN")
                .and()
                .withUser("joe").password(passwordEncoder().encode("234")).roles("USER");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

MainController.java

@Controller
public class MainController {
@GetMapping("/")
ModelAndView index(Principal principal) {
    ModelAndView mv = new ModelAndView("home");
    if (principal != null) {
        mv.addObject("message", principal.getName());
    } else {
        mv.addObject("message", "anon.");
    }

    return mv;
}

@GetMapping("/**")
String request(HttpServletRequest request, Model model) {
    Authentication auth = SecurityContextHolder.getContext()
            .getAuthentication();
    ModelAndView mv = new ModelAndView("home");
    model.addAttribute("uri", request.getRequestURI())
            .addAttribute("user", auth.getName())
            .addAttribute("roles", auth.getAuthorities());

    return "html"; //<-- whenever I change this to return "jsp/jsp"; it breaks
}

html.html (Thymeleaf)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
    <body>
        <p>
            URI: <h3 th:text="${uri}"></h3>
        User: <h3 th:text="${user}"></h3>
        Roles: <h3 th:text="${roles}"></h3>
        <a href="/admin/">/admin/</a><br/>
        <a href="/users/">/users/</a><br/>
        <a href="/others/">/others/</a><br/>
        <a href="/quests/">/quests/</a><br/><br/>
    </p>
    <form th:action="@{/logout}" method="post">
        <input type="hidden"
               name="${_csrf.parameterName}"
               value="${_csrf.token}"/>
        <input type="submit" value="Logout">
    </form>
</body>
</html>

When I try to make this work with a JSP file well, the browswer only outputs

HTTP Status 500 ? Internal Server Error

and in NetBeans Output window, where gradle's task run is, well, running, the log shows this at the very top (the whole log is quite extensive):

2018-10-07 18:09:40.070 ERROR 6024 --- [nio-8080-exec-4] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-4] Exception processing template "jsp/jsp": An error happened during template parsing (template: "class path resource [templates/jsp/jsp.html]")

JSP view I'm trying to include

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html lang="en">
    <body>
        <p>URI: ${uri} <br/>
            User :  ${user} <br/>
            roles:  ${roles} <br/><br/>
            <a href="/admin/">/admin/</a><br/>
            <a href="/users/">/users/</a><br/>
            <a href="/others/">/others/</a><br/>
            <a href="/quests/">/quests/</a><br/><br/>
        </p>
        <form action="/logout" method="post">
            <input type="hidden"
                   name="${_csrf.parameterName}"
                   value="${_csrf.token}"/>
            <input type="submit" value="Logout">
        </form>
    </body>
</html>

Finally, my project tree:

enter image description here

My assumption is that the app does not know about the file jsp.jsp inside folder templates/jsp, which is why I'm aiming the question to view resolvers, but as I said, I could easily be wrong about it.

This is just an example I'm trying to materialize and build on, so feel free to shred it with suggestions, thanx.

like image 662
Scaramouche Avatar asked Oct 07 '18 23:10

Scaramouche


People also ask

Can we use Thymeleaf with JSP in spring boot?

If you are building web front-ends with Spring Boot or Spring MVC, and you're still using JSP (Java Server Pages) then this course is for you. Thymeleaf is a great templating engine which replaces JSP, and you can easily use it in any Spring MVC or Spring Boot application.

Can I use JSP in spring boot?

Undertow will not support JSP if used as an Embedded Servlet Container. If deploying in a web container, our @SpringBootApplication annotated class should extend SpringBootServletInitializer and provide necessary configuration options. We can't override the default /error page with JSP.

Which is better Thymeleaf or JSP?

On the other hand, Thymeleaf is a template engine which takes the HTML file, parses it and then produces web content which is being served. Thymeleaf is more like an HTML-ish view when you compare it with JSP views. If we put all this to an edge, thymeleaf is the slow one here.


1 Answers

Just adding one more view resolver for jsp won't do, we also need to add one more template resolver for jsp and connect it to spring template engine. The code below works in Spring Boot 2.

package org.jwebshop.webshop.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

import javax.annotation.Resource;

@Configuration
@EnableWebMvc
public class ViewConfiguration implements WebMvcConfigurer {

    @Resource
    protected ApplicationContext applicationContext;

    @Resource
    protected SpringTemplateEngine springTemplateEngine;

    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(){
        final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setViewNames(new String[] {"thyme/*"});
        viewResolver.setExcludedViewNames(new String[] {"jsp/*"});
        viewResolver.setTemplateEngine(springTemplateEngine);
        viewResolver.setCharacterEncoding("UTF-8");
        return viewResolver;
    }

    @Bean
    public InternalResourceViewResolver jspViewResolver(){
        final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewNames("jsp/*");
        return viewResolver;
    }

    @Bean
    public SpringResourceTemplateResolver thymeleafTemplateResolver(){
        final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("/WEB-INF/views/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCacheable(false);
        templateResolver.setOrder(0);
        return templateResolver;
    }

    @Bean
    public SpringResourceTemplateResolver jspTemplateResolver(){
        final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("/WEB-INF/views/");
        templateResolver.setSuffix(".jsp");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCacheable(false);
        templateResolver.setOrder(1);
        templateResolver.setCharacterEncoding("UTF-8");
        return templateResolver;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/webjars/**").addResourceLocations("/webjars/");
        registry.addResourceHandler("/images/**").addResourceLocations("/images/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }
}
package org.jwebshop.webshop.controller.web.thymeleaf;

import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class RootController {

    @Secured("ROLE_CUSTOMER")
    @GetMapping({"/", "/index"})
    public String root() {
        return "thyme/index";
    }
}
package org.jwebshop.webshop.controller.web.jsp;

import org.jwebshop.webshop.dto.converter.impl.UserDataConverter;
import org.jwebshop.webshop.dto.data.UserData;
import org.jwebshop.webshop.entity.User;
import org.jwebshop.webshop.service.UserService;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;

@Controller
public class LuckyController {

    @Resource
    protected UserService userService;

    @Resource
    protected UserDataConverter userDataConverter;

    @Secured("ROLE_CUSTOMER")
    @GetMapping("/lucky")
    public String hello(final Model model, final Authentication auth) {
        final User user = userService.findByEmail(auth.getName());
        final UserData userData = userDataConverter.convertFrom(user);
        model.addAttribute("userData", userData);
        return "jsp/lucky";
    }
}

Folder structure: 
 webapp
   |
 WEB-INF
   |
  views
  |   |
jsp thyme

https://imgur.com/qOTgYZW

like image 165
axel4u Avatar answered Sep 20 '22 12:09

axel4u