I have an already existing client module with apache tiles and thymeleaf, what works well. I wanted to convert it to spring-boot and wanted to do it step by step, but I am r
Okay guys,
I got it and I hope it helps for other developers with similar problems.
In POM I removed all Spring-Dependencies and I use only Spring-Starter-Dependencies like following snippet:
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Apache Tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-template</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>
<!-- Thymeleaf ... -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-tiles2-spring4</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
....
In my Starter-Class I actived Thymeleaf Autoconfiguration again, my full class looks like follows. Be attended, I use two different servlets, one for REST, one for Thymeleaf.
@Configuration
@Import({ ServiceConfig.class, RestMvcConfig.class, WebMvcConfig.class })
@EnableAutoConfiguration
public class ApplicationClientMvc {
public static void main(final String[] args) {
SpringApplication.run(ApplicationClientMvc.class, args);
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public ServletRegistrationBean applicationDispatcherRegistration(final DispatcherServlet dispatcherServlet) {
final ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
registration.setName("applicationServlet");
registration.addUrlMappings("/app/*");
registration.setAsyncSupported(true);
registration.setLoadOnStartup(1);
registration.addInitParameter("contextConfigLocation", "a.c.b.c.m.w.WebMvcConfig");
registration.setOrder(1);
return registration;
}
@Bean
public ServletRegistrationBean restDispatcherRegistration(final DispatcherServlet dispatcherServlet) {
final ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
registration.setName("restServlet");
registration.addUrlMappings("/rest/*");
registration.setAsyncSupported(true);
registration.setLoadOnStartup(0);
registration.addInitParameter("contextConfigLocation", "a.c.b.c.m.r.RestMvcConfig");
registration.setOrder(0);
return registration;
}
}
====
My WebMVCConfig Class looks like follows, is in package ....mvc.rest
:
@Configuration
@ComponentScan
public class WebMvcConfig extends WebMvcAutoConfigurationAdapter {
@Autowired public SpringTemplateEngine templateEngine;
@Bean
public ThymeleafTilesConfigurer tilesConfigurer() {
final ThymeleafTilesConfigurer configurer = new ThymeleafTilesConfigurer();
configurer.setDefinitions(ThymeleafAutoConfiguration.DEFAULT_PREFIX + "**/views.xml");
return configurer;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
final ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setViewClass(ThymeleafTilesView.class);
resolver.setTemplateEngine(templateEngine);
resolver.setCharacterEncoding(UTF_8);
return resolver;
}
@Bean
public TilesDialect tilesDialect() {
return new TilesDialect();
}
//
@Value("${server.session-timeout}") private Long sessionTimeOut;
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(sessionTimeOut * 1000L);
configurer.registerCallableInterceptors(timeoutInterceptor());
}
@Bean
public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
return new TimeoutCallableProcessingInterceptor();
}
}
===
My RestConfig Class is pretty simple
@Configuration
@ComponentScan
public class RestMvcConfig {}
===
I moved all my html-files or thymeleaf-templates to template-folder under src/main/resources. Also in same folder you can put all client resources like images, js and css under the folder static. The folder static and templates are keywords and will be found by Spring-Boot automatically. There are no special configurations in application.properties what would be needed for thymeleaf and apache tiles.
====
That your application is able to find the resources, you can configure the path like follows:
<link rel="stylesheet" type="text/css" media="screen, projection"
href="/static/css/bootstrap.css"
th:href="@{/css/bootstrap.css}" />
<link rel="stylesheet" type="text/css" media="screen, projection"
href="/static/css/bootstrap-responsive.css"
th:href="@{/css/bootstrap-responsive.css}" />
===
Folder java/src/webapp
is no more needed!!!
====
If this helps for you, dont hesitate to vote ;-). If you need more information, just comment, I will try to answer within few days.
======
SECOND STEP
Okay, until now it only works under Eclipse. When I do executable jar, I get still an error. Maybe someone wants to help ;-)
Stacktrace is :
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/layout/login", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:245)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1060)
at org.thymeleaf.extras.tiles2.renderer.ThymeleafAttributeRenderer.write(ThymeleafAttributeRenderer.java:155)
at org.apache.tiles.renderer.impl.AbstractBaseAttributeRenderer.render(AbstractBaseAttributeRenderer.java:106)
at org.thymeleaf.extras.tiles2.renderer.MetadataCleaningAttributeRendererWrapper.render(MetadataCleaningAttributeRendererWrapper.java:111)
at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:670)
at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:690)
at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:644)
at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:627)
at org.apache.tiles.impl.BasicTilesContainer.render(BasicTilesContainer.java:321)
at org.thymeleaf.extras.tiles2.spring4.web.view.ThymeleafTilesView.render(ThymeleafTilesView.java:125)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1221)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1005)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:952)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:280)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:421)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1736)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1695)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
The Structure of my JAR is like:
======
SOLUTION:
I don't know exactly why, but additional I have to fix the url-paths for my templates, what works like it is in Eclipse, but not as executable jar. To get it run in both environments, I had to fix all template urls like follows, have a special look on attribute value:
BEFORE:
<definition name="home" extends="standard1ColLayout">
<put-attribute name="title" value="/home/title :: content" type="thymeleaf" />
<put-attribute name="body" value="/home/body :: content" type="thymeleaf" />
</definition>
AFTER:
<definition name="home" extends="standard1ColLayout">
<put-attribute name="title" value="./home/title :: content" type="thymeleaf" />
<put-attribute name="body" value="./home/body :: content" type="thymeleaf" />
</definition>
Finally it works under Eclipse and as executable jars. Converting from normal Spring-Project to Spring-Boot Project was not really easy running, I hope it will help to other developers and I hope that post is worth to vote. ;-)
This example demonstrate how to configure Thymeleaf with Apache tiles, but it is letting spring boot to autoconfigure Thymeleaf. This way you can still use all spring-boot predefined environment variables like spring.thymeleaf.* in your application.properties
@Configuration
@AutoConfigureAfter(ThymeleafAutoConfiguration.class)//tweak autoconfiguration for apache tiles after spring boot
@EnableConfigurationProperties(ThymeleafProperties.class)
public class ThymeleafTilesConfig {
@Autowired//this bean is autoconfigured with spring-boot-thymeleaf
private ThymeleafViewResolver thymeleafViewResolver;
@Autowired
private ThymeleafProperties properties;
@PostConstruct
public void setThymeleafTilesViewClass() {
//just set view class for thymeleaf-tiles
thymeleafViewResolver.setViewClass(ThymeleafTilesView.class);
}
@Bean
TilesDialect tilesDialect() {
// This bean will be auto picked-up by spring-boot and will be autoconfigured :)
return new TilesDialect();
}
@Bean//create tiles configurer for your needs
ThymeleafTilesConfigurer tilesConfigurer() {
final ThymeleafTilesConfigurer configurer = new ThymeleafTilesConfigurer();
configurer.setDefinitions("classpath:/templates/**/views.xml");
configurer.setCheckRefresh(!properties.isCache());
return configurer;
}
}
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java#L116