Use JSP and Thymeleaf views in Spring Boot app - spring-mvc

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>
/admin/<br/>
/users/<br/>
/others/<br/>
/quests/<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/>
/admin/<br/>
/users/<br/>
/others/<br/>
/quests/<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:
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.

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. JSP template resolver order must be the last, because it throws exception (smth like 'page not found') when can not resolve. 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

I haven't actually tried it yet, as I used jsp and thymeleaf on totally different project. And also converted jsp into thymeleaf, but not use it together. I just want to help, as I bump into this question and I find it interesting.
I assume you already checked this thread? Using both Thymeleaf and JSP
According to this blog, you can leave as is the default configuration of thymeleaf. You just need to add InternalResourceViewResolverfor the jsp configuration.
Full example here:
Add below dependencies to your pom.xml file
<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>
In your application.properties set thymeleaf view names and JSP
configuration for internal view resolution
spring.view.prefix:/WEB-INF/
spring.view.suffix:.jsp
spring.view.view-names:jsp/*
spring.thymeleaf.view-names:thymeleaf/*
create a configuration class for view resolution for JSP pages
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;
}
}

Related

spring mvc view resolution configuration fail to display jsp page

I am new to spring mvc.
When I run this url:http://localhost:8080/spring-mvc-foundation/main/
I get "hello". I was expecting to execute the hello.jsp page and get output:Hello World TEST123
DispatcherInitializer.java:
package com.oreilly.mvc;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class DispatcherInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
RootConfig.java:
package com.oreilly.mvc;
import org.springframework.context.annotation.ComponentScan;
#ComponentScan("com.oreilly.mvc.services")
public class RootConfig {
}
WebConfig.java:
package com.oreilly.mvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#EnableWebMvc
#ComponentScan("com.oreilly.mvc.controllers")
public class WebConfig {
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolverBean = new InternalResourceViewResolver();
viewResolverBean.setPrefix("/WEB-INF/views/");
viewResolverBean.setSuffix(".jsp");
return viewResolverBean;
}
}
MainController.java:
package com.oreilly.mvc.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.oreilly.mvc.services.GreetingService;
#Controller
#RequestMapping("/main")
public class MainController {
#Autowired
public GreetingService greetingService;
#ResponseBody
#RequestMapping("/")
public String message(Model model) {
model.addAttribute("message",this.greetingService.greet());
return "hello";
}
}
hello.jsp:
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Hello World Application</title>
</head>
<body>
<h1>${message} TEST123</h1>
</body>
</html>
location of jsp page hello.jsp:
browser output:
Remove #ResponseBody annotation.
#ResponseBody is a Spring annotation which binds a method return value to the web response body. It is not interpreted as a view name. It uses HTTP Message converters to convert the return value to HTTP response body, based on the content-type in the request HTTP header.

Very simple Spring MVC sample with thymeleaf not working

I am very disappointed, I followed a tutorial on how to add Thymeleaf to a Spring MVC project but it doesn't even work with a 3-classes sample project. Thymeleaf cannot find my views and I don't know what I am missing.
Stacktrace :
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "ServletContext resource [/WEB-INF/views/test.html]")
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
SpringMVCConfiguration.class
#Configuration
#ComponentScan({ "main.java" })
#EnableWebMvc
public class SpringMVCConfiguration implements WebMvcConfigurer {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
registry.viewResolver(resolver);
}
}
DispatcherServletConfiguration.class
public class DispatcherServletConfiguration extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { SpringMVCConfiguration.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
TestController.class
#Controller
public class TestController {
#GetMapping("/test")
public String test(Model model) {
System.out.println("controller test");
return "test";
}
}
In Eclipse, the HTML file is located here :
-- Project
-- WebContent
-- WEB-INF
-- views
- test.html
test.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>TEST</h1>
</body>
</html>
To get off the ground running faster, I would suggest using spring-boot with its thyme-leaf starter. It handles most of the frustrating wiring code to avoid these issues.
https://www.mkyong.com/spring-boot/spring-boot-hello-world-example-thymeleaf/
Per your code, I'm guessing your component scan is wrong. Can you switch it from main.java to the root of your package hierarchy... whatever that is? Its probably the package name that the main.java file is in.
Also, maybe try moving your WEB-INF to src/main/web-app/WEB-INF?

How to resolve view in thymleaf + Springboot?

Currently, i have function, which is to convert the data from MYSQL to CSV. The CSV function contain the webconfig where use the viewResolver. The problem is, when i used below function, the page cannot view but the CSV file can be download and vice versa. Is there anything that i need to configure ?
-Configure ContentNegotiatingViewResolver
#Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
// Define all possible view resolvers
List<ViewResolver> resolvers = new ArrayList<>();
resolvers.add(csvViewResolver());
resolver.setViewResolvers(resolvers);
return resolver;
}
WebConfig- full code
package com.portal.dmtt.csvDownload.config;
import com.portal.dmtt.csvDownload.viewResolver.CsvViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import java.util.ArrayList;
import java.util.List;
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.favorPathExtension(true);
}
/*
* Configure ContentNegotiatingViewResolver
*/
#Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
// Define all possible view resolvers
List<ViewResolver> resolvers = new ArrayList<>();
resolvers.add(csvViewResolver());
resolver.setViewResolvers(resolvers);
return resolver;
}
/*
* Configure View resolver to provide Csv output using Super Csv library to
* generate Csv output for an object content
*/
#Bean
public ViewResolver csvViewResolver() {
return new CsvViewResolver();
}
}
Export Controller
package com.portal.dmtt.csvDownload.controller;
import com.portal.dmtt.repo.dmttDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class ExportController {
#Autowired
private dmttDAO dmttDAO;
/**
* Handle request to download an Excel document
*/
#GetMapping("/dl")
public String download(Model model) {
model.addAttribute("results", dmttDAO.getAllResultSet());
return "";
}
}
Abstract View
package com.portal.dmtt.csvDownload.view;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public abstract class AbstractCsvView extends AbstractView {
private static final String CONTENT_TYPE = "text/csv";
public AbstractCsvView() {
setContentType(CONTENT_TYPE);
}
#Override
protected boolean generatesDownloadContent() {
return true;
}
#Override
protected final void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType(getContentType());
buildCsvDocument(model, request, response);
}
protected abstract void buildCsvDocument(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
CSV View
package com.portal.dmtt.csvDownload.view;
import com.portal.dmtt.model.exceptionMonitoring.FN_Result_Set;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
public class CsvView extends AbstractCsvView {
#Override
protected void buildCsvDocument(Map<String, Object> model, HttpServletRequest request, HttpServletResponse
response) throws Exception {
response.setHeader("Content-Disposition", "attachment; filename=\"my-csv-file.csv\"");
List<FN_Result_Set> fnResultSetList = (List<FN_Result_Set>) model.get("results");
String[] header = {"SP_ID", "SP_ID", "XFER_XMIT_STATUS", "XFER_FILE_NAME", "UPDATE_TS", "YYMM", "REMARKS"};
try {
ICsvBeanWriter csvWriter = new CsvBeanWriter(response.getWriter(),
CsvPreference.STANDARD_PREFERENCE);
csvWriter.writeHeader(header);
for (FN_Result_Set user : fnResultSetList) {
csvWriter.write(user, header);
}
csvWriter.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
View Resolver
package com.portal.dmtt.csvDownload.viewResolver;
import com.portal.dmtt.csvDownload.view.CsvView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import java.util.Locale;
public class CsvViewResolver implements ViewResolver {
#Override
public View resolveViewName(String s, Locale locale) throws Exception {
return new CsvView();
}
}
One of the problems is that your CSVViewResolver is resolving a view for any view name. You may want to return null from CSVViewResolver.resolveViewName() if s, the view name, is not empty .
Another issue is that the browser (at least my Chrome) doesn't send text/csv as Accept header, but text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Set the media type explicitly in configureContentNegotiation for CSV:
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.favorPathExtension(true)
.mediaType("csv", MediaType.parseMediaType("text/csv"));
}
Remove the Bean contentNegotiatingViewResolver
You shouldn't create a contentNegotiatingViewResolver as one is provided by Spring Boot. If you provide one, you will have two of this type, and yours will not have the Thymeleaf ViewResolver. As your CSVViewResolver will return a view for any view name, the view will be resolved in the ContentNegotiatingViewResolver provided by you, not in the one provided by Spring.
Details:
The csvViewResolver bean will be picked up by the Spring Boot's ContentNegotiatingViewResolver along others like BeanNameViewResolver, ThymeleafViewResolver, ViewResolverComposite, InternalResourceViewResolver.
To debug this set a breakpoint on DispatcherServlet.resolveViewName:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}

Spring MVC Java Config issues

I am trying to do an example to change the xml config to Java config for Spring MVC. But my simple example is not working. On running this project on server, I can't see any beans initializing or the dispatcher servlet name on console.
and I get 404 error on running the http://localhost:8080/Servlet3Example/
I have created a maven project and following is my code:
package com.project.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyDispatcherServlet extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
System.out.println("get root config");
//return new Class[]{RootConfig.class};
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
System.out.println("get web config");
return new Class[]{WebConfig.class};
}
#Override
protected String[] getServletMappings() {
System.out.println("in dispatcher servlet");
return new String[] {"/"};
}
}
And the WebConfig is:
package com.project.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages={"com.project.controllers"})
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setSuffix(".jsp");
viewResolver.setPrefix("/WEB-INF/views/");
return viewResolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
And controller:
package com.project.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
public class HomeController {
#RequestMapping(value="/",method=RequestMethod.GET)
public String home()
{
return "home";
}
}
Can you please change #ComponentScan to #ComponentScan(basePackages={"com.project.*"})

Configuring Multiple View Resolvers (Thymeleaf and Json) Spring

I am trying to configure a Spring based app, where I want to configure two view resolvers. From my controller, if I return just the string name like "login", then it should be handled by the Thymeleaf resolver, whereas if the controller's method returns an object, then appropriate json view should be used. When I try to run my application as configured below, I get the following error
"Could not resolve view with name 'login' in servlet with name
'dispatcher'"
Requesting you guys to look at the Java classes below. The first is the configuration class, the second is the Controller I am trying to use.
package com.gojha.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
#Configuration
#EnableWebMvc
#ComponentScan("com.gojha.web")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public ViewResolver viewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
cnvr.setContentNegotiationManager(cnm);
return cnvr;
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
#Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
#Bean
public TemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine((SpringTemplateEngine) templateEngine());
return viewResolver;
}
}
Controller
package com.gojha.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
#Controller
#RequestMapping("/")
public class LoginController {
private RestTemplate restTemplate;
private class Test {
private String a;
public Test() {
super();
}
public Test(String a) {
super();
this.a = a;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
}
#Autowired
public LoginController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#RequestMapping(method=GET)
public String testing(){
return "login";
}
#RequestMapping(method=GET, produces="application/json")
public Test testing2(){
return new Test("wow");
}
}
I hope the code is self-explanatory.
I got it working by changing the configuration file and allocating orders to view resolvers. From what I understand, it looks like first it tries to resolve the view using ContentNegotiation, and if it fails, falls back to Thymeleaf resolver. I am marking this as the answer, if someone has a better approach, or a suggested correction, let me know.
package com.gojha.web;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
#Configuration
#EnableWebMvc
#ComponentScan("com.gojha.web")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
#Bean
public ViewResolver viewResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
viewResolver.setOrder(2);
return viewResolver;
}
#Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
cnvr.setContentNegotiationManager(cnm);
cnvr.setOrder(1);
List<View> views = new ArrayList<View>();
views.add(jsonView());
cnvr.setDefaultViews(views);
return cnvr;
}
#Bean
public View jsonView() {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setPrettyPrint(true);
return view;
}
}

Resources