Thymeleaf Not Hot Swapping Intellij - java

I am having some issues getting my Thymeleaf templates to hot swap / update using Intellij. At the moment I have to do a full server restart in order to see my changes, which is rather tedious and slows down my work flow.
I am using Gradle, Intellij 14.1, and Tomcat 8. I am running the application in Debug mode.
I have tried setting Thymeleaf to not cacheable.
#Configuration
public class ThymeleafConfig {
#Autowired
Environment environment;
#Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix(environment.getRequiredProperty("thymeleaf.resolver.prefix"));
resolver.setSuffix(environment.getRequiredProperty("thymeleaf.resolver.suffix"));
resolver.setTemplateMode(environment.getRequiredProperty("thymeleaf.resolver.templatemode"));
resolver.setOrder(environment.getRequiredProperty("thymeleaf.resolver.order", Integer.class));
resolver.setCacheable(environment.getRequiredProperty("thymeleaf.resolver.cacheable", Boolean.class));
resolver.setCharacterEncoding(environment.getRequiredProperty("thymeleaf.resolver.character.encoding"));
return resolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
engine.addDialect(new LayoutDialect());
engine.addDialect(new SpringSecurityDialect());
return engine;
}
#Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
}
Property file the above code is reading from.
# Thymeleaf
thymeleaf.resolver.prefix=/WEB-INF/views/
thymeleaf.resolver.suffix=.html
thymeleaf.resolver.templatemode=HTML5
thymeleaf.resolver.order=1
thymeleaf.resolver.cacheable=false
thymeleaf.resolver.character.encoding=UTF-8
I also tried setting it in the ApplicationInitializer.
#Override
public void onStartup(ServletContext container) throws ServletException {
/**
* If no active profile is set via -Dspring.profiles.active then the application
* will default to development mode
*/
container.setInitParameter("spring.profiles.default", "dev");
/**
* Set thymeleaf cache to false if -Dspring.thymeleaf.cache is not passed
*/
container.setInitParameter("spring.thymeleaf.cache", "false");
/**
* create the root Spring application context
*/
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.setDisplayName("app");
rootContext.register(AppConfig.class);
/**
* manage the lifecycle of the root application context
*/
container.addListener(new ContextLoaderListener(rootContext));
/**
* register and map the dispatcher servlet
*/
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
So far none of this has worked.

Select exploded war for the deployment. Then you can simply update resources or classes and resources when you hit CMD + F10( I assume it might be CTRL on Windows/Linux).

Related

How to use TWO SpringResourceTemplateResolver Beans?

Since Thymeleaf 3, Thymeleaf prefers the use of SpringResourceTemplateResolver (https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html). So I decided to go from ClassLoaderTemplateResolver to SpringResourceTemplateResolver:
#Configuration
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.setOrder(1);
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(0);
resolver.setCheckExistence(true);
return resolver;
}
#Bean
public SpringResourceTemplateResolver templateResolver2() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/templates-2/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
resolver.setCheckExistence(true);
return resolver;
}
}
Unfortunately, when implementig like this, I'll get an error:
Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers.
To be honest, I've simple replaced ClassLoaderTemplateResolver with SpringResourceTemplateResolver in the hope, this will work. It doesn't. But searching for a working solution dealing with two template locations, all I find are outdated samples using ClassLoaderTemplateResolvers.
Trying to implement the code snippet provided by Thymeleaf as shown here https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html won't work either when using two template directories, besides the fact, that this code itself uses the deprecated WebMvcConfigurerAdapter.
Is there any example how to configure a Spring Boot application using Thymeleaf having two or more template locations which isn't completely outdated?
So after a while and many many tryouts I've finally got the templates working. Since there isn't any decent answer to my question flying round the internet, I will post my solution for others:
#Configuration
public class ThymeleafConfig implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(#Autowired ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("classpath:/templates-1/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setOrder(1);
templateResolver.setCheckExistence(true); /* FYI: necessary to chain TemplateResolvers */
templateResolver.setCacheable(false); /* FYI: during development -> false, so that we can see changes we make */
return templateResolver;
}
#Bean
public SpringResourceTemplateResolver templateResolver2() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("classpath:/templates-2/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setOrder(2);
templateResolver.setCheckExistence(true); /* FYI: necessary to chain TemplateResolvers */
templateResolver.setCacheable(false); /* FYI: during development -> false, so that we can see changes we make */
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
/* SpringTemplateEngine automatically applies SpringStandardDialect and
enables Spring's own MessageSource message resolution mechanisms. */
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(this.templateResolver());
templateEngine.addTemplateResolver(this.templateResolver2());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(this.templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setCache(false); /* FYI: during development -> false */
viewResolver.setOrder(1);
return viewResolver;
}
}
I hope this will help others to save time and nerves^^.
If you are using Spring Boot, you can add an extra resolver on top of the default one like this:
First, add this to application.properties:
# This ensures that the default HTML template resolver of Thymeleaf has priority over our custom SVG resolver
spring.thymeleaf.template-resolver-order=0
Then add the extra resolver in a #Configuration class:
#Configuration
public class MyApplicationConfiguration {
#Bean
public ITemplateResolver svgTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/svg/");
resolver.setSuffix(".svg");
resolver.setTemplateMode("XML");
return resolver;
}
}
There is no need to manually define SpringTemplateEngine and ViewResolver beans.
You can disable caching during development using spring.thymeleaf.cache=false.

why JSP pages not showing when using multiple view resolver in Spring boot?

I have added multiple view resolver for PDF, Excel and InternalResourceViewResolver for JSP files. PDF and Excel resolver works fine but when I call /test that is calling my test (test.jsp) I see a blank page. But when I remove other resolvers or disable my webconfig then test.jsp (/test) loads fine.
Following is the error I see when calling /test (binded to test controller and test.jsp):
2018-08-11 23:43:06.191 ERROR 12136 --- [nio-9090-exec-1] o.s.boot.web.support.ErrorPageFilter : Cannot forward to error page for request [/test] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
Also, following is my webconfig responsible for adding multiple resolvers (pdf, excel and jsp pages):
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.favorPathExtension(true);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/*
* 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(internalResourceViewResolver());
resolvers.add(pdfViewResolver());
resolvers.add(excelViewResolver());
resolver.setViewResolvers(resolvers);
return resolver;
}
#Bean
public ViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp");
resolver.setSuffix(".jsp");
return resolver;
}
/*
* Configure View resolver to provide XLS output using Apache POI library to
* generate XLS output for an object content
*/
#Bean
public ViewResolver excelViewResolver() {
return new ExcelViewResolver();
}
/*
* Configure View resolver to provide Pdf output using iText library to
* generate pdf output for an object content
*/
#Bean
public ViewResolver pdfViewResolver() {
return new PdfViewResolver();
}
}

Spring Boot content-negotiation configuration

I have difficulties configuring content-negotiation with spring-boot.
I would like to keep most of the default spring-boot configuration.
I followed the following https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc/
not so recent tutorial. At the moment when I send a request for application/json or txt/html the view doesn't seem to get resolved, but when I turn on #EnableWebMvc it does seem to get resolved.
Below is my current configuration.
#Configuration // according to the spring-boot docs this should be enough with spring-boot
//#EnableWebMvc If I enable this content-negotiation seems to work without any configuration, but I loose the default spring-boot configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean(name = "jsonViewResolver")
public ViewResolver getJsonViewResolver() {
return new JsonViewResolver();
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// Simple strategy: only path extension is taken into account
configurer.favorPathExtension(true)
.defaultContentType(MediaType.TEXT_HTML)
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
#Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = newContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
return resolver;
}
}
You are not registering your resolvers in your content negotiation manager.
please try with the following modification:
#Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager){
ContentNegotiatingViewResolver resolver = newContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
List<ViewResolver> resolvers = new ArrayList<>();
ViewResolver aViewResolver = getJsonViewResolver();
resolvers.add(aViewResolver);
resolver.setViewResolvers(resolvers);
return resolver;
}

Spring Boot + Thymeleaf, connect new formatter

I am buidling application on top of the Spring Boot. At this point I've decided to use Thymeleaf to render some of the messages in this application. The application uses Thymeleaf configuration to render template provided as String's.
This is the Thymeleaf configuration:
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setDialect(templateDialect());
engine.setTemplateResolver(templateResolver());
engine.setEnableSpringELCompiler(true);
return engine;
}
#Bean
public StringTemplateResolver templateResolver() {
final StringTemplateResolver templateResolver = new StringTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.TEXT);
return templateResolver;
}
#Bean
public SpringStandardDialect templateDialect() {
final SpringStandardDialect dialect = new SpringStandardDialect();
dialect.setEnableSpringELCompiler(true);
return dialect;
}
This is code that I use for rendering:
final Context context = new Context();
context.setVariables(/*provide map with "variable" in it*/);
return engine.process("[[${variable}]]", context);
The problem occurs when i try to process a list object. I want to add Formatter for the list type so it can be rendered in a right way. I've tried to do it using WebMvcConfigurer class by overriding method: AddFormatters with no success so far.

Spring context loading twice with both xml and annotation configuration

I have a web application on Tomcat 7.0.34, Spring 3.2.3, Spring Security 3.2.0.RC1 and Spring Social 1.1.
For some reason the Spring context is being loaded twice. The second load is happening immediately after the first load has finished. The log below shows the Context Loader loading the Root WebApplicationContext. Everything progresses normally and all the RequstMappingHandlers are registering correctly. Then immediately the context is refreshed again.
I've read several solutions on SO about ensuring you don't load the configuration as part of the Context Loader and the DispatcherServlet at the same time and have tested various combinations of this but that doesn't seem to have fixed it and I'm becoming code blind as well.
All my testing on this has pushed me to an annotation only configuration both for the container and Spring components but the config classes are pretty much copy and paste from the Spring Social examples on github. Although I've included the SocialConfig.java details below, this problem has been happening before I implemented Spring Social but I can't move on without fixing it.
Also, the problem was present with a hybrid xml (web.xml, security-app-context.xml) and annotation configuration.
I'm implementing WebApplicationInitializer rather than having a web.xml
public class WebClientInitialiser implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
// Manage the lifecycle of the root application context
appContext.setConfigLocation("com.mycompany.webclient.config");
appContext.setServletContext(container);
container.addListener(new ContextLoaderListener(appContext));
container.addListener(new MyCompanyContextListener());
container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
// Register and map the dispatcher servlet
Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
My MainConfig.java
/**
* Main configuration class for the application.
* Turns on #Component scanning, loads externalized application properties
* and imports legacy security configuration
*/
#Configuration
#ComponentScan(basePackages = "com.mycompany.webclient", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:wc.properties")
#ImportResource("/WEB-INF/spring/appServlet/security-app-context.xml")
public class MainConfig {
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
My WebMvcConfig.java
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/WEB-INF/messages/messages");
return messageSource;
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
My SocialConfig.java
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
private SocialUserDAO socialUserDao;
//
// SocialConfigurer implementation methods
//
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
String clientId="XXXXXX";
String clientSecret="XXXXX";
cfConfig.addConnectionFactory(new FacebookConnectionFactory(clientId, clientSecret));
}
#Override
public UserIdSource getUserIdSource() {
return new UserIdSource() {
#Override
public String getUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return authentication.getName();
}
};
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new HibernateUsersConnectionRepository(socialUserDao, connectionFactoryLocator, Encryptors.noOpText());
}
//
// API Binding Beans
//
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Twitter twitter(ConnectionRepository repository) {
Connection<Twitter> connection = repository.findPrimaryConnection(Twitter.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public LinkedIn linkedin(ConnectionRepository repository) {
Connection<LinkedIn> connection = repository.findPrimaryConnection(LinkedIn.class);
return connection != null ? connection.getApi() : null;
}
//
// Web Controller and Filter Beans
//
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
ConnectController connectController = new ConnectController(connectionFactoryLocator, connectionRepository);
connectController.addInterceptor(new PostToWallAfterConnectInterceptor());
connectController.addInterceptor(new TweetAfterConnectInterceptor());
return connectController;
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SimpleSignInAdapter(new HttpSessionRequestCache()));
}
#Bean
public DisconnectController disconnectController(UsersConnectionRepository usersConnectionRepository, Environment env) {
return new DisconnectController(usersConnectionRepository, env.getProperty("facebook.clientSecret"));
}
#Bean
public ReconnectFilter apiExceptionHandler(UsersConnectionRepository usersConnectionRepository, UserIdSource userIdSource) {
return new ReconnectFilter(usersConnectionRepository, userIdSource);
}
}
Any help, comments, pointers is greatly appreciated.
This log output is repeated twice at the start of each context refresh:
org.springframework.web.context.ContextLoader- Root WebApplicationContext: initialization started
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Refreshing Root WebApplicationContext: startup date [Mon Nov 25 22:43:39 GMT 2013]; root of context hierarchy
org.springframework.context.annotation.ClassPathBeanDefinitionScanner- JSR-330 'javax.inject.Named' annotation found and supported for component scanning
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Registering annotated classes: [class com.mycompany.webclient.config.WebMvcConfig,class com.mycompany.webclient.config.SocialConfig]
You are passing the same context to both the ContextLoaderListener and DispatcherServlet and hence this will trigger loading the configuration twice.
You should have 2 seperate AnnotationConfigWebApplicationContext instances one for the ContextLoaderListener loading all your generic beans (services etc.) and one for the DispatcherServlet loading the web related things.
public class WebClientInitialiser implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(MainConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
container.addListener(new MyCompanyContextListener());
container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(WebMvcConfig.class, SocialConfig.class);
// Register and map the dispatcher servlet
Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
Something like this. Also you don't need to set the contextConfigLocation simply register the #Configuration annotated classes. Also setting the ServletContext is already done by Spring so no need for that to.
A note on your configuration, the PropertySourcesPlaceHolderConfigurer is enabled by default so no need to register that again in in MainConfig class.
Another thing to take into account is that now probably your application fails (i.e. your #Controllers don't work anymore). This is due to the fact that everything is inside the root application context whereas #Controllers should be loaded by the DispatcherServlet. To fix this you need to exclude #Controller scanning in your MainConfig and enable #Controller scanning on the WebMvcConfig class.

Categories

Resources