Below is my class in which i had to use both @Configuration
and @Controller
as there should be only one instance of Thymeleaf
in the e
To refactor them, it's easy and straight forward to separate @Bean
methods to a separate @Configuration
class:
@Configuration
// @Controller is redundant as we have @Configuration
// @EnableWebMvc is also redundant since you already annotate it in other class
// @ApplicationScope is also redundant since you do not need to create bean of MyThymeleafConfig anymore
public class MyThymeleafConfig {
/*
configuration for thymeleaf and template processing
*/
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:");
templateResolver.setSuffix(".html");
templateResolver.setCacheable(false);
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
}
@Configuration
class return the same bean instance, regardless how many times you call the bean methods.
Now in your controller:
@Controller
public class MyThymeleafConfig {
@Autowired
private SpringTemplateEngine templateEngine;
@GetMapping("/view-template")
@ResponseBody
public void viewTemplates() {
Context context = new Context();
context.setVariable("mydata", "this is it");
String html = templateEngine.process("templates/view-to-process.html", context);
System.out.println(html);
}
}
But honestly, I don't know why you have to manually interact with TemplateEngine / SpringTemplateEngine, since Spring-thymeleaf will automatically process the template with given variable for you. (Like @sedooe example)
These two annotations are for different things, thus it's better to not use them on the same class. Cause it's against Separation of Concerns principal.
Take look at Spring Boot documentation typical layout
Also this article SOLID Programming Principles
And look at Spring Boot guide Spring Boot Thymeleaf (you don't need your @Bean configurations)
In two words you should separate
1. MyThymeleafConfig
configuration
2. TemplateController
with viewTemplates()
and another endpoints
Assuming you're using Spring Boot, since you have it in tags, you do not need any configuration to use Thymeleaf.
By just having this dependency, you can:
@GetMapping("/view-template")
public String viewTemplates(Model model) {
model.addAttribute("mydata", "this is it")
return "view-to-process";
}
And it should work.
By the way, yes, having @Configuration
and @Controller
in the same class is something you should never need.
If you see the source codes of the annotations (Spring 5) you have:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this Configuration class. If left unspecified (the common case),
* a bean name will be automatically generated.
* <p>The custom name applies only if the Configuration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the Configuration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
* @return the suggested component name, if any (or empty String otherwise)
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
you notice that they are the same (they both include the more generic @Component
annotation). So it doesn't make sense to use them both by seeing this fact. Another thing, more important, is that spring is trying to give a sort of tags meaning of these annotations that should describe the use.
The Configuration
is used to wire in necessary parts to the application to function properly, at startup phase.
The Controller
is used to define a class which is serving as an interface to the outside world, i.e: how can other actors use your application.
As you can see, it makes very little sense to use those 2 together.
Don't put request mappings inside configuration classes, it violates the principal of separation of concerns. You can go for a approach like below.
All the application wide beans are setup in Application
class which is present in the root of the classpath. Application
class in the best place to have your thymeleaf and static resource configurations too, since the Application
class have application-scope.
@SpringBootApplication
@EnableWebMvc
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
resolver.setCache(false);
return resolver;
}
@Bean
public TemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setEnableSpringELCompiler(true);
templateEngine.addDialect(new LayoutDialect());
templateEngine.addDialect(new Java8TimeDialect());
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new
SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("classpath:/templates/");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
}
If you put the static resources inside a folder named static or public in the classpath, springboot identify that as the location for static resources. Then you don't need to override addResourceHandlers
method. If you really want to do it, you can do it inside the Application class extending WebMvcConfigurerAdapter
. You don't need separate class to configure just static resource paths.
Don't put request mappings inside configuration classes, put them in separate controller classes like:
@Controller
public class MyController {
@GetMapping("/view-template")
@ResponseBody
public void viewTemplates() {
Context context = new Context();
context.setVariable("mydata", "this is it");
String html = templateEngine().process("templates/view-to-process.html", context);
System.out.println(html);
}
}
Of cause, springboot allows you to do it the way you like, but you'd better stick to a general approach.