在 Spring Boot 应用程序中使用 JSP 和 Thymeleaf 视图

Posted

技术标签:

【中文标题】在 Spring Boot 应用程序中使用 JSP 和 Thymeleaf 视图【英文标题】:Use JSP and Thymeleaf views in Spring Boot app 【发布时间】:2019-03-12 14:53:41 【问题描述】:

网站和网络上通常有几个类似的问题,但我无法像我尝试过的那样让它们在我的示例中发挥作用。

我是第一次使用 Spring Boot,但我一直在尝试通过 InternalResourceViewResolver 包含 JSP 视图。我已经让 Thymeleaf 视图工作了。

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(百里香叶)

<!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>

当我尝试用 JSP 文件很好地完成这项工作时,浏览器只输出

HTTP 状态 500 ?内部服务器错误

在 NetBeans Output 窗口中,gradle 的任务正在运行,日志显示在最顶部(整个日志非常广泛):

2018-10-07 18:09:40.070 错误 6024 --- [nio-8080-exec-4] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-4]异常处理模板“jsp/jsp”:模板解析时出错(模板:“类路径资源[templates/jsp/jsp.html]”)

我正在尝试包含的 JSP 视图

<%@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>

最后,我的项目树:

我的假设是应用程序不知道文件夹 templates/jsp 中的文件 jsp.jsp,这就是为什么我将问题的目标是查看解析器,但正如我所说,我很容易就错了。

这只是我试图具体化和构建的一个示例,因此请随时提出建议,谢谢。

【问题讨论】:

如果您正在开始新项目,为什么要使用 jsp.和百里香一起去。 @want2learn;我以前听说过,是的,我会选择百里香,但我只是 want2learn Spring boot 不鼓励使用带有嵌入式 tomcat 的 jsp。但是如果你想要2learn检查github.com/spring-projects/spring-boot/tree/v2.0.5.RELEASE/… 我会的,谢谢链接,但是,真的没有办法使用 JSP没有 webapp/WEB-INF 结构,但只能使用资源/模板之一? @want2learn 由于我没有将jsp与spring boot一起使用,我不能从我的经验中说,但是spring boot docs说将你的jsps放在webapp / WEB-INF结构上 【参考方案1】:

仅仅为 jsp 添加一个视图解析器是不行的, 我们还需要为 jsp 添加一个模板解析器并将其连接到 spring 模板引擎。 下面的代码适用于 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

【讨论】:

【参考方案2】:

我还没有真正尝试过,因为我在完全不同的项目中使用了 jspthymeleaf。并且还将jsp 转换为thymeleaf,但不能一起使用。我只是想帮忙,因为我碰到了这个问题,我觉得它很有趣。


我假设你已经检查过这个线程? Using both Thymeleaf and JSP


根据this blog,可以保留thymeleaf的默认配置。您只需要为jsp配置添加InternalResourceViewResolver即可。

此处为完整示例:

将以下依赖项添加到您的 pom.xml 文件中

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
 </dependency>
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
 </dependency>

在你的 application.properties 中设置 thymeleaf 视图名称和 JSP 内部视图分辨率配置

spring.view.prefix:/WEB-INF/
spring.view.suffix:.jsp
spring.view.view-names:jsp/*
spring.thymeleaf.view-names:thymeleaf/*

为 JSP 页面的视图解析创建配置类

package com.example.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
public class JspConfig 
    @Value("$spring.view.prefix")
    private String prefix;

    @Value("$spring.view.suffix")
    private String suffix;

    @Value("$spring.view.view-names")
    private String viewNames;

    @Bean
    InternalResourceViewResolver jspViewResolver() 
        final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix(prefix);
        viewResolver.setSuffix(suffix);
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setViewNames(viewNames);
        return viewResolver;
    

【讨论】:

您好,感谢您的帮助。我认为,在我的问题中,//intended for the .jsp view 下的@Bean 与您的JspConfig 类完全一样,只是带有硬编码的前缀和后缀,对吧?另外,我真的很想保持一个 webapp/WEB-INF 结构并坚持使用classpath:templates,我正在尝试构建一个 .jar 而不是.war

以上是关于在 Spring Boot 应用程序中使用 JSP 和 Thymeleaf 视图的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Thymeleaf 或 JSP 在 Spring Boot 中启用目录列表

我应该将jsp文件放在spring-boot项目中的位置

Spring Boot JSP应用实例

JSP 文件未在 Spring Boot Web 应用程序中呈现

如何使用 Spring MVC & Security、Jasig CAS 和 JSP 视图在 Spring Boot 2 中配置 UTF-8?

无法使用 Spring Boot 和 MVC 找到/渲染 JSP 文件