📜 ⬆️ ⬇️

Guide: Thymeleaf + Spring. Part 1

Original documentation

The second part of
The third part

Table of contents:
')
1. Integrating Thymeleaf with Spring
2. SpringStandard Dialect
3. Views and View Resolvers
3.1 Views and View Resolvers in Spring MVC
3.2 Views and View Resolvers in Thymeleaf
4. Spring Thyme Seed Starter Manager
4.1 Concept
4.2 Business layer
4.3 Spring MVC configuration
4.4 Controller
4.5 Configuring Conversion Service
5 Seed Starter Data Display
6 Creating Forms
6.1 Processing Command Object
6.2 Inputs
6.3 Checkbox fields
6.4 Radio Button fields
6.5 Dropdown / List selectors
6.6 Dynamic fields
7 Check and error messages
7.1 Field errors
7.2 All errors
7.3 Global Errors
7.4 Mapping errors out of form
7.5 Rich Error Objects
8 This is still a prototype!
9 The Conversion Service
9.1 Configuration
9.2 Double bracket syntax
9.3 Forms Use
9.4 #conversions transform object
10 Drawing Fragments of the Template Fragments Template (AJAX etc)
10.1 Definition of fragments in a view bean
10.2 Definition of fragments in the return value of the controller
11 Advanced integration features
11.1 Integration with RequestDataValueProcessor
11.1 Building URIs to Controllers
12 Spring WebFlow Integration
12.2 AJAX Fragments in Spring WebFlow

This guide explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring MVC.

Please note that Thymeleaf has integration for versions 3.x and 4.x of the Spring Framework and above, provided by two separate libraries called thymeleaf-spring3 and thymeleaf-spring4. These libraries are packaged in separate .jar files (thymeleaf-spring3- {version} .jar and thymeleaf-spring4- {version} .jar) and should be added to your class path to use Thymeleaf Spring integrations in your application.

The code examples and sample applications in this guide use Spring 4.x and its corresponding Thymeleaf integrations, but the content of this text also applies to Spring 3.x. If your application uses Spring 3.x, all you need to do is replace the org.thymeleaf.spring4 package with org.thymeleaf.spring3 in the code examples.

1. Integrating Thymeleaf with Spring


Thymeleaf offers a set of Spring integrations that allow it to be used as a full-featured JSP replacement in Spring MVC applications.

These integrations will allow you to:

  1. Make the mapping to the methods in your Spring MVC Controller objects of templates managed by Thymeleaf, just like you do with JSP.
  2. Use Spring Expression Language (Spring EL) instead of OGNL in your templates.
  3. Create forms in your templates that are fully integrated with your components (bean) of support for forms and results bindings, including the use of property editors, conversion services and the processing of validation errors.
  4. Display internationalization messages from Spring-managed message files (through normal MessageSource objects).
  5. Find your templates using your own Spring resource resolution mechanisms.

Please note that in order to fully understand this tutorial, you must first go through the tutorial " Using Thymeleaf ", which explains the standard dialect in detail.

2. SpringStandard Dialect


To achieve easier and better integration, Thymeleaf provides a dialect that specifically implements all the necessary functions to work correctly with Spring.

This particular dialect is based on the standard Thymeleaf dialect and is implemented in the class org.thymeleaf.spring4.dialect.SpringStandardDialect, which actually comes from org.thymeleaf.standard.StandardDialect.

In addition to all the functions already present in the standard dialect and, therefore, inherited, SpringStandard Dialect offers the following specific functions:

  1. Use Spring expression language (Spring EL or SpEL) as a variable expression language, not OGNL. Consequently, all expressions $ {...} and * {...} will be evaluated by the Spring expression language engine. Also note that support for the Spring EL compiler is available (Spring 4.2.4+).
  2. Access any components in the context of your application using the SpringEL syntax: $ {@ myBean.doSomething ()}
  3. New attributes for processing the form: th: field , th: errors and th: errorclass , in addition to the new implementation of th: object , which allows it to be used to select the form command.
  4. The object and expression method # themes.code (...) , which is equivalent to the JSP spring: theme custom tag.
  5. The object and expression method, # mvc.uri (...) , which is equivalent to the JSP spring custom function : mvcUrl (...) (only in Spring 4.1+).

Note that in most cases you should not use this dialect directly in a regular TemplateEngine object as part of its configuration. If you do not have special needs for integrating with Spring, you should instead create an instance of a new template class that automatically performs all the necessary configuration steps: org.thymeleaf.spring4.SpringTemplateEngine .

Bean configuration example:

@Bean public SpringResourceTemplateResolver templateResolver(){ // SpringResourceTemplateResolver automatically integrates with Spring's own // resource resolution infrastructure, which is highly recommended. SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setApplicationContext(this.applicationContext); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); // HTML is the default value, added here for the sake of clarity. templateResolver.setTemplateMode(TemplateMode.HTML); // Template cache is true by default. Set to false if you want // templates to be automatically updated when modified. templateResolver.setCacheable(true); return templateResolver; } @Bean public SpringTemplateEngine templateEngine(){ // SpringTemplateEngine automatically applies SpringStandardDialect and // enables Spring's own MessageSource message resolution mechanisms. SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); // Enabling the SpringEL compiler with Spring 4.2.4 or newer can // speed up execution in most scenarios, but might be incompatible // with specific cases when expressions in one template are reused // across different data types, so this flag is "false" by default // for safer backwards compatibility. templateEngine.setEnableSpringELCompiler(true); return templateEngine; } 

Or, using a Spring-based XML configuration:

 <!-- SpringResourceTemplateResolver automatically integrates with Spring's own --> <!-- resource resolution infrastructure, which is highly recommended. --> <bean id="templateResolver" class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver"> <property name="prefix" value="/WEB-INF/templates/" /> <property name="suffix" value=".html" /> <!-- HTML is the default value, added here for the sake of clarity. --> <property name="templateMode" value="HTML" /> <!-- Template cache is true by default. Set to false if you want --> <!-- templates to be automatically updated when modified. --> <property name="cacheable" value="true" /> </bean> <!-- SpringTemplateEngine automatically applies SpringStandardDialect and --> <!-- enables Spring's own MessageSource message resolution mechanisms. --> <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> <!-- Enabling the SpringEL compiler with Spring 4.2.4 or newer can speed up --> <!-- execution in most scenarios, but might be incompatible with specific --> <!-- cases when expressions in one template are reused across different data --> <!-- ypes, so this flag is "false" by default for safer backwards --> <!-- compatibility. --> <property name="enableSpringELCompiler" value="true" /> </bean> 

3. Views and View Resolvers


3.1 Views and View Resolvers in Spring MVC



There are two interfaces in Spring MVC that correspond to the core of its pattern system:


Views model the pages in our applications and allow you to change and predetermine their behavior, defining them as bean-components. Views are responsible for rendering a real HTML interface, usually for executing some kind of template mechanism, for example, Thymeleaf.

ViewResolvers are objects responsible for retrieving View objects for a particular operation and locale. Usually, controllers ask ViewResolvers to send the view with a specific name (the string returned by the controller method), and then all the view resolution in the application runs in an ordered chain until one of them can resolve the view, in which case the View object returns and control is passed for rendering HTML.

Please note that not all pages in our applications should be defined as views, but only those whose behavior we want us to be non-standard or customized in a special way (for example, by connecting some special components to it). If the ViewResolver requests a view that does not have a corresponding bean (which is a common case), a new View object is created ad hoc and returned.

A typical JSP + JSTL ViewResolver configuration in a Spring MVC application from the past looked like this:

 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsps/" /> <property name="suffix" value=".jsp" /> <property name="order" value="2" /> <property name="viewNames" value="*jsp" /> </bean> 

A quick glance at his properties is enough to find out how he was set up:

  1. viewClass sets the View instance class. This is necessary for the JSP recognizer, but not at all when we work with Thymeleaf.
  2. The prefix and suffix work in the same way as attributes with the same name in Thymeleaf TemplateResolver objects.
  3. order sets the order in which the ViewResolver will be queried in the chain.
  4. viewNames allows you to define (with wildcard characters) the names of the views that will be resolved by this ViewResolver.

3.2 Views and View Resolvers in Thymeleaf


Thymeleaf offers implementations for the two interfaces mentioned above:

  1. org.thymeleaf.spring4.view.ThymeleafView
  2. org.thymeleaf.spring4.view.ThymeleafViewResolver

These two classes will be responsible for processing the Thymeleaf templates as a result of the execution of the controllers.

The Resolver Thymeleaf View configuration is very similar to JSP:

 @Bean public ThymeleafViewResolver viewResolver(){ ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine()); // NOTE 'order' and 'viewNames' are optional viewResolver.setOrder(1); viewResolver.setViewNames(new String[] {".html", ".xhtml"}); return viewResolver; } 

... or in XML:

 <bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <!-- NOTE 'order' and 'viewNames' are optional --> <property name="order" value="1" /> <property name="viewNames" value="*.html,*.xhtml" /> </bean> 

The templateEngine parameter, of course, is the SpringTemplateEngine object that we defined in the previous chapter. The other two ( order and viewNames ) are optional and have the same meaning as in the JSP ViewResolver, which we saw earlier.

Please note that we do not need prefix or suffix parameters, because they are already listed in the Template Resolver (which, in turn, is passed to the Template Engine).

But what if we want to define a View bean and add a few static variables to it ? Easy, just define a prototype for it:

 @Bean @Scope("prototype") public ThymeleafView mainView() { ThymeleafView view = new ThymeleafView("main"); // templateName = 'main' view.setStaticVariables( Collections.singletonMap("footer", "The ACME Fruit Company")); return view; } 

By doing this, you can query this component by selecting it by name (in this case, the mainView).

4. Spring Thyme Seed Starter Manager


The source code for the examples shown in this and subsequent chapters of this guide can be found in the GitHub Spring Seyme Seed Starter Manager repository.

4.1 Concept


Regarding Thymeleaf, we are big fans of thyme, and every spring we prepare our starter kits with good soil and our favorite seeds, plant them under the Spanish sun and wait patiently for our new plants to grow.

But this year we were tired of sticking labels on the starting seed containers to find out what seeds were in each cell of the container, so we decided to prepare the application using Spring MVC and Thymeleaf to help us catalog our starters: The Spring Thyme SeedStarter Manager .



Similar to Good Thymes Virtual Grocery, which we developed in the “Using Thymeleaf” tutorial, STSM will allow us to demonstrate the most important aspects of integrating Thymeleaf as a template engine for Spring MVC.

4.2 Business layer


We will need a very simple business layer for our application. First of all, let's look at our model objects:

image

A pair of very simple service classes will provide the necessary business methods. Like:

 @Service public class SeedStarterService { @Autowired private SeedStarterRepository seedstarterRepository; public List<SeedStarter> findAll() { return this.seedstarterRepository.findAll(); } public void add(final SeedStarter seedStarter) { this.seedstarterRepository.add(seedStarter); } } 

AND:

 @Service public class VarietyService { @Autowired private VarietyRepository varietyRepository; public List<Variety> findAll() { return this.varietyRepository.findAll(); } public Variety findById(final Integer id) { return this.varietyRepository.findById(id); } } 

4.3 Spring MVC configuration


Then we need to configure the Spring MVC configuration for the application, which will include not only the standard Spring MVC artifacts, such as resource processing or annotation scanning, but also the creation of Template Engine instances and View Resolver .

 @Configuration @EnableWebMvc @ComponentScan public class SpringWebConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware { private ApplicationContext applicationContext; public SpringWebConfig() { super(); } public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /* ******************************************************************* */ /* GENERAL CONFIGURATION ARTIFACTS */ /* Static Resources, i18n Messages, Formatters (Conversion Service) */ /* ******************************************************************* */ @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { super.addResourceHandlers(registry); registry.addResourceHandler("/images/**").addResourceLocations("/images/"); registry.addResourceHandler("/css/**").addResourceLocations("/css/"); registry.addResourceHandler("/js/**").addResourceLocations("/js/"); } @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; } @Override public void addFormatters(final FormatterRegistry registry) { super.addFormatters(registry); registry.addFormatter(varietyFormatter()); registry.addFormatter(dateFormatter()); } @Bean public VarietyFormatter varietyFormatter() { return new VarietyFormatter(); } @Bean public DateFormatter dateFormatter() { return new DateFormatter(); } /* **************************************************************** */ /* THYMELEAF-SPECIFIC ARTIFACTS */ /* TemplateResolver <- TemplateEngine <- ViewResolver */ /* **************************************************************** */ @Bean public SpringResourceTemplateResolver templateResolver(){ // SpringResourceTemplateResolver automatically integrates with Spring's own // resource resolution infrastructure, which is highly recommended. SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setApplicationContext(this.applicationContext); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); // HTML is the default value, added here for the sake of clarity. templateResolver.setTemplateMode(TemplateMode.HTML); // Template cache is true by default. Set to false if you want // templates to be automatically updated when modified. templateResolver.setCacheable(true); return templateResolver; } @Bean public SpringTemplateEngine templateEngine(){ // SpringTemplateEngine automatically applies SpringStandardDialect and // enables Spring's own MessageSource message resolution mechanisms. SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); // Enabling the SpringEL compiler with Spring 4.2.4 or newer can // speed up execution in most scenarios, but might be incompatible // with specific cases when expressions in one template are reused // across different data types, so this flag is "false" by default // for safer backwards compatibility. templateEngine.setEnableSpringELCompiler(true); return templateEngine; } @Bean public ThymeleafViewResolver viewResolver(){ ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine()); return viewResolver; } } 

4.4 Controller


Of course, we also need a controller for our application. Since the STSM will contain only one web page with a list of initial values ​​and a form for adding new ones, we will write only one controller class for all server interactions:

 @Controller public class SeedStarterMngController { @Autowired private VarietyService varietyService; @Autowired private SeedStarterService seedStarterService; ... } 

Now let's see what we can add to this controller class.

Model Attributes

First we will add some attributes of the model that we will need on the page:

 @ModelAttribute("allTypes") public List<Type> populateTypes() { return Arrays.asList(Type.ALL); } @ModelAttribute("allFeatures") public List<Feature> populateFeatures() { return Arrays.asList(Feature.ALL); } @ModelAttribute("allVarieties") public List<Variety> populateVarieties() { return this.varietyService.findAll(); } @ModelAttribute("allSeedStarters") public List<SeedStarter> populateSeedStarters() { return this.seedStarterService.findAll(); } 

Mapped methods

And now the most important part of the controller, mapped methods: one to display the form page, and another to handle the addition of new SeedStarter objects.

 @RequestMapping({"/","/seedstartermng"}) public String showSeedstarters(final SeedStarter seedStarter) { seedStarter.setDatePlanted(Calendar.getInstance().getTime()); return "seedstartermng"; } @RequestMapping(value="/seedstartermng", params={"save"}) public String saveSeedstarter( final SeedStarter seedStarter, final BindingResult bindingResult, final ModelMap model) { if (bindingResult.hasErrors()) { return "seedstartermng"; } this.seedStarterService.add(seedStarter); model.clear(); return "redirect:/seedstartermng"; } 

4.5 Configuring Conversion Service


To provide simple formatting of Date objects as well as Variety objects in our presentation layer, we set up our application so that the Spring ConversionService object is created and initialized (extensible WebMvcConfigurerAdapter ) using a couple of formatting objects we need.
Look again:

 @Override public void addFormatters(final FormatterRegistry registry) { super.addFormatters(registry); registry.addFormatter(varietyFormatter()); registry.addFormatter(dateFormatter()); } @Bean public VarietyFormatter varietyFormatter() { return new VarietyFormatter(); } @Bean public DateFormatter dateFormatter() { return new DateFormatter(); } 

Spring formatters are implementations of the org.springframework.format.Formatter interface. For more information on the spring.io .

Let's look at DateFormatter , which formats dates according to the format string present in the date.format message key of our Messages.properties :

 public class DateFormatter implements Formatter<Date> { @Autowired private MessageSource messageSource; public DateFormatter() { super(); } public Date parse(final String text, final Locale locale) throws ParseException { final SimpleDateFormat dateFormat = createDateFormat(locale); return dateFormat.parse(text); } public String print(final Date object, final Locale locale) { final SimpleDateFormat dateFormat = createDateFormat(locale); return dateFormat.format(object); } private SimpleDateFormat createDateFormat(final Locale locale) { final String format = this.messageSource.getMessage("date.format", null, locale); final SimpleDateFormat dateFormat = new SimpleDateFormat(format); dateFormat.setLenient(false); return dateFormat; } } 

VarietyFormatter automatically converts Variety between our objects and the way we want to use them in our forms (mainly according to the values ​​of their id fields):

 public class VarietyFormatter implements Formatter<Variety> { @Autowired private VarietyService varietyService; public VarietyFormatter() { super(); } public Variety parse(final String text, final Locale locale) throws ParseException { final Integer varietyId = Integer.valueOf(text); return this.varietyService.findById(varietyId); } public String print(final Variety object, final Locale locale) { return (object != null ? object.getId().toString() : ""); } } 

We will learn more about how these formatters affect the way our data is displayed in the future.

Source: https://habr.com/ru/post/435062/


All Articles