[JAVA] I created and set my own Dialect with Thymeleaf and tried using it

Due to the circumstances of the projects we participated in, there were many behaviors that could not be realized with the standard standard dialect alone, so we decided to implement it ourselves. In the first place, Thymeleaf itself didn't have much opportunity to touch from the training when I was a newcomer, but rather I was more familiar with JSP, so I started studying from the beginning.

As I implemented it through trial and error and was able to reproduce the intended operation, it became more and more fun, so I will leave it as a memorandum.

What is Dialect in the first place?

The typical ones that are basically used are classified as follows.

name Description
IProcessorDialect Dialect that provides elements and attributes
IExpressionObjectDialect A dialect that provides an object that can be used in an EL expression in a tag attribute value

IProcessorDialect registers a Processor and implements tags, and can be used with a description method such as th: text. IExpressionObjectDialect can add utilities that can be used in EL expressions, such as # strings.

How to use and create your own Dialect

IProcessorDialect

A Dialect that can be described like an element or attribute. Since IProcessorDialect has an abstract class called AbstractProcessorDialect, we usually inherit and implement it. Create a Processor that implements the actual processing separately, and register it in the implementation class of AbstractProcessorDialect so that it works.

For example, if you create a date to format it as yyyy / mm / dd, you can use it as follows.

■ Description on the template

<span thex:formatdate="${date}"></span>

■ Elements that are actually output

<span>2020/01/01</span>

Also, when I wrote the Dialect I created earlier inline in the script tag, I was addicted to it because the Dialect was not recognized.

<script th:inline="javascript">
	var test =  [# thex:formatdate="${date}" /] ;
	console.log(test);
</script>

It seems that it will not be recognized on the template unless the description is set to be recognized inline in the implementation of AbstractProcessorDialect. Therefore, it is necessary to set the template mode according to the case where JavaScript or CSS is used.

@Override
public Set<IProcessor> getProcessors(String dialectPrefix) {
  Set<IProcessor> proccessors = new HashSet<>();

  proccessors.add(new SampleProcesssor(TemplateMode.HTML, dialectPrefix));
  proccessors.add(new SampleProcesssor(TemplateMode.JAVASCRIPT, dialectPrefix));
  proccessors.add(new SampleProcesssor(TemplateMode.CSS, dialectPrefix));

  return proccessors;
}

IExpressionObjectDialect

As a trial, we will implement ʻIExpressionObjectDialectand create a utility that can be used in EL expressions as# sample`. Be careful not to duplicate the name you add as a utility with the one provided as standard.

First, we will implement Dialect to register Expression Object. Here, we will generate the name of the utility when using it in the EL expression and the class that actually performs the processing.

public class SampleDialect extends AbstractDialect implements IExpressionObjectDialect {

	//Name constants when used in EL expressions
	private static final String SAMPLE_DIALECT_NAME = "sample";
	
	private static final Set<String> EXPRESSION_OBJECT_NAMES = Collections.singleton(SAMPLE_DIALECT_NAME);


	public SampleDialect() {
		super(SAMPLE_DIALECT_NAME);
	}
	
	@Override
	public IExpressionObjectFactory getExpressionObjectFactory() {
		return new IExpressionObjectFactory() {

			@Override
			public Set<String> getAllExpressionObjectNames() {
				return EXPRESSION_OBJECT_NAMES;
			}

			@Override
			public Object buildObject(IExpressionContext context, String expressionObjectName) {
				if (SAMPLE_DIALECT_NAME.equals(expressionObjectName)) {
					//Instantiation of a class that processes as a utility
					return new Sample();
				}
				return null;
			}

			@Override
			public boolean isCacheable(String expressionObjectName) {
				return true;
			}

		};
	}

}

Next, we will create a class that actually transfers the processing from the template side. This time, we will implement it with a simple process of passing a character string, formatting it, and displaying it on the screen.

public class Sample {
	
	public String getSampleString(final String str) {
		return "Sample string "" + str + "」";
	}

}

You can actually use it in the EL expression as follows on HTML.

■ Description on the template

<span th:text="${#sample.getSampleString('sample')}"

■ Elements that are actually output

<span>Sample string "sample"</span>

Configuration implementation on Spring MVC

We will set ViewResolver in the setting class so that it can operate on MVC. (For information on how to implement Java Config, click here](https://qiita.com/HiroyaEnd/items/17175e947911d84c1b3b#webmvcconfig)) Since you can register your own Dialect here, you can also register it and make it work on the template. (The setting method is basically the same for both IProcessorDialect and IExpressionObjectDialect)

//Constants used for encoding settings
public static final String CHARACTER_ENCODING = "UTF-8";

//A constant in the base directory path where the template is located
private static final String VIEW_LOCATION = "/WEB-INF/views/";


//Bean definition ThymeleafViewResolver
@Bean
public ThymeleafViewResolver viewResolver() {
	final ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();
	thymeleafViewResolver.setTemplateEngine(templateEngine());
	thymeleafViewResolver.setCharacterEncoding(CHARACTER_ENCODING); //Set the response encoding
	
	return thymeleafViewResolver;
}

//Define SpringTemplateEngine Bean
@Bean
public SpringTemplateEngine templateEngine() {
	final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
	templateEngine.setTemplateResolver(templateResolver());
	templateEngine.addDialect(new SampleDialect()); //Register your own Dialect

	return templateEngine;
}

//Define TemplateResolver Bean
@Bean
public AbstractConfigurableTemplateResolver templateResolver() {
	final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
	templateResolver.setPrefix(VIEW_LOCATION); //Specifies the base directory where Thymeleaf templates are stored
	templateResolver.setSuffix(".html"); //Set the Thymeleaf template extension
	templateResolver.setTemplateMode(TemplateMode.HTML); //Set the template mode to interpret(The default value is HTML mode, but it is explicitly set)
	templateResolver.setCharacterEncoding(CHARACTER_ENCODING); //Set the template file encoding
	
	return templateResolver;
}

After the setting implementation is completed, you can operate it by creating a template file and transitioning it. The above settings are just samples, so I think you need to change them according to your environment.

Summary

When I actually used it, there were many points that I thought were easier to use than JSP. I found it attractive that it is highly extensible because you can easily add your own Dialect, and that it can be flexibly implemented by combining it with Standard Dialect. Personally, it was easier to extend than JSP's original tag.

Since Spring recommends Thymeleaf, there are likely to be many opportunities to come into contact with it in the future, so I would like to further increase my knowledge. (Preferably freemaker ...)

reference

https://qiita.com/yoshikawaa/items/aba090f291f69185e6a5

https://macchinetta.github.io/server-guideline-thymeleaf/1.5.1.RELEASE/ja/ArchitectureInDetail/WebApplicationDetail/Thymeleaf.html#id21

Recommended Posts

I created and set my own Dialect with Thymeleaf and tried using it
I tried to make my own transfer guide using OpenTripPlanner and GTFS
I tried using JOOQ with Gradle
I tried using Realm with Swift UI
I tried using Scalar DL with Docker
I tried using OnlineConverter with SpringBoot + JODConverter
I tried using OpenCV with Java + Tomcat
[Android] I quit SQLite and tried using Realm
I also tried WebAssembly with Nim and C
I made blackjack with Ruby (I tried using minitest)
I called YouTube video from DB with haml and tried to embed and display it
I tried to investigate the mechanism of Emscripten by using it with the Sudoku solver
I tried to read and output CSV with Outsystems
I started MySQL 5.7 with docker-compose and tried to connect
I tried using YOLO v4 on Ubuntu and ROS
roman numerals (I tried to simplify it with hash)
I tried using Gson
I tried using TestNG
I tried using Galasa
I was trapped when I generated my own class equals and hashCode in Java using IDE
I tried unit testing Rails app using RSpec and FactoryBot
I updated my own blackjack made with Ruby for my portfolio
[Rails] I tried to implement "Like function" using rails and js
I tried to get started with Swagger using Spring Boot
I tried to summarize object orientation in my own way.
I tried using the CameraX library with Android Java Fragment
[Spring Boot] I want to add my own property file and get the value with env.getProperty ().
When I tried to run my own service, it failed, so I screwed it into the task scheduler
I tried DI with Ruby
I tried using azure cloud-init
I tried using Apache Wicket
I tried using Java REPL
I tried UPSERT with PostgreSQL.
I tried BIND with Docker
I tried printing a form with Spring MVC and JasperReports 3/3 (Spring MVC control)
I tried to integrate Docker and Maven / Netbean nicely using Jib
I tried connecting to MySQL using JDBC Template with Spring MVC
I tried to make it an arbitrary URL using routing nesting
Image Spring Boot app using jib-maven-plugin and start it with Docker
I want to download a file on the Internet using Ruby and save it locally (with caution)