Main Contents: This chapter records the learning process of Thymeleaf template engine technology, basic concepts, attributes, and expression grammar combined with actual code page functionality development.
1. Know the Thymeleaf Template Engine.
2. Springboot integrates Thymeleaf.
3. Thymeleaf template syntax and past and present generations.
4. Thymeleaf template tag properties.
5. Notes on using Thymeleaf template engine.
1-Understanding the Thymeleaf Template Engine:
(1),Thymeleaf
This should be the most popular template engine technology yet, and Springboot officially recommends using it instead of jsp technology in Java web development, mainly because its "prototype is page" concept fits well with the fast development advocated by Springboot.At the same time, the Thymeleaf template engine does have advantages that other technologies do not have.
Core features of (2), Thymeleaf 3
Thymeleaf and May 2016 officially released the Thymeleaf-3.0.0 RELEASE version, which is currently used in most project development processes, so this record is also 3.0.
Complete HTML5 tag support, new parsers.
Multiple template modes are available and can only be extended to support other template formats.
It also works well in both web and non-Web environments (offline).
Support for Spring web development is excellent.
Independent of the Servlet API.
Other features:
Thymeleaf 3.0 introduces a new expression as part of the general Thymeleaf standard expression system: fragment expression; and
Another new feature of the Thymeleaf standard expression in Thymeleaf 3.0 is the NO-OP (no operation) token, which is represented by an underscore symbol ().
Thymeleaf 3.0 allows full (and optional) decoupling of template logic from the template itself in both template and template modes to achieve 100%-Thymeleaf-free logic templates.
Thymeleaf 3.0 uses a new dialect system.
Thymeleaf 3.0 completed the refactoring of the core API.
Thymeleaf is a template engine for advanced languages. The syntax is simpler and more powerful. The next step will be the integration of Thymeleaf with Spring Boot and the grammar records of the Thymeleaf template engine.
2-Springboot integration Thymeleaf:
2.1 - Introducing Thymeleaf dependencies:
Because Springboot officially provides Thymeleaf's scene launcher, spring-boot-start-thymeleaf, you can add it directly to the pom.xml file with the following code:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.yangmufa.SpringBlog</groupId> <artifactId>springboot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringBlog-Demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Thymeleaf Template Engine Dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.2 - Create a template file:
Create a new template file thymeleaf.html in the resources/templates directory.
Thymeleaf template engine default suffix name is html, after adding a file, import Thymeleaf's namespace in the <html>tag of the template file:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
This namespace was imported primarily for the use of Thymeleaf's syntax hints and Thymeleaf tags, then added to the template the same display as in the previous JSP, and the final template file was used as follows:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <title>Thymeleaf demo</title> </head> <body> <p>description Field values are:</p> <p th:text="${description}">What's shown here is description Field Content</p> </body> </html>
2.3-Edit the Controller code:
Add a new ThymeleafController.java file under the controller package, assign values to the description field required by the template file and forward the value template file, encoding as follows;
package com.lou.springboot.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest; @Controller public class ThymeleafController { @GetMapping("/thymeleaf") public String hello(HttpServletRequest request, @RequestParam(value = "description", required = false, defaultValue = "springboot-thymeleaf") String description) { request.setAttribute("description", description); return "thymeleaf"; } }
The final code directory structure is as follows:
2.4 - Start and access:
After encoding is complete, you can start. Since the default is in the SpringBlog directory, the command line is not in the current directory, so you can switch back first, and then start the Springboot project through the Maven plug-in with the command mvn spring-boot:run.
After the project has started successfully, you can type http//localhost:9090/yangmufa/thymaleaf in your browser and you can see that the contents of the source static <p>tag have been replaced with the "SpringBlog-Thymeleaf" string instead of the default content.
3-Thymeleaf template syntax and past and present generations:
3.1-th:* Attribute:
Th:text corresponds to the text attribute in HTML5. In addition to the th:text attribute, Thymeleaf also provides other tag attributes to replace the original HTML5 native attribute values, with the following excerpts:
Th:backgroundcorresponds to the background attribute in HTML5.
th:class corresponds to the class attribute in HTML5.
th:href corresponds to the connection address attribute in HTML5.
th:id and th:name correspond to the ID and name attributes in HTML5...
Thymeleaf is a unique Thymeleaf block-level element provided by Thymeleaf, especially because the Thymeleaf template engine deletes itself when processing <th:block>.Save its empty content instead.
You can view Thymeleaf-attributes for complete attribute content.
3.1-Modify Attribute Value Practice:
Next, understand this by having a complete coding practice and focus on attributes to switch workspaces to SpringBlog.
The complete structure of the project is as follows:
SpringBlog └── src ├── main │ ├── java │ │ └── com │ │ └── lou │ │ └── springboot │ │ ├── Application.java │ │ └── controller │ │ └── ThymeleafController.java │ └── resources │ ├── application.properties │ ├── static │ └── templates │ ├── attributes.html │ ├── simple.html │ ├── test.html │ └── thymeleaf.html └── test └── java └── com └── lou └── springboot
The attributes.html code is as follows:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <title>Thymeleaf setting-value-to-specific-attributes</title> <meta charset="UTF-8"> </head> <!-- background Label--> <body th:background="${th_background}" background="#D0D0D0"> <!-- text Label--> <h1 th:text="${title}">html Label demonstration</h1> <div> <h5>id,name,value Label:</h5> <!-- id,name,value Label--> <input id="input1" name="input1" value="1" th:id="${th_id}" th:name="${th_name}" th:value="${th_value}"/> </div> <br/> <div class="div1" th:class="${th_class}"> <h5>class,href Label:</h5> <!-- class,href Label--> <a th:href="${th_href}" href="##/">Link Address</a> </div> </body> </html>
It contains HTML tags such as background, id, name, class, etc., sets default values, and adds corresponding th tags to each tag to read dynamic data. Choose open with ->Preview or Mini Browser directly to view the page as follows:
It is important to note that if you open an html file directly without passing through a web server, the page is default although it can be composed to access its content and element properties; you can then add a corresponding Controller method under the Controller package and forward the request to the template page, coded as follows:
// ThymeleafController2.java package com.yangmufa.blog.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; @Controller public class ThymeleafConeroller2 { @GetMapping("/attributes") public String attributes(ModelMap map) { // Change h1 content map.put("title", "Thymeleaf Label demonstration"); // Change id, name, value map.put("th_id", "thymeleaf-input"); map.put("th_name", "thymeleaf-input"); map.put("th_value", "13"); // Change class, href map.put("th_class", "thymeleaf-class"); map.put("th_href", "http://13blog.site"); return "attributes"; } }
Or:
// ThymeleafController.java package com.yangmufa.blog.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest; @Controller public class ThymeleafController { @GetMapping("/thymeleaf") public String hello( HttpServletRequest request, @RequestParam(value = "description", required = false, defaultValue = "springboot-thymeleaf") String description) { request.setAttribute("description", description); return "thymeleaf"; } @GetMapping("/attributes") public String attributes(ModelMap map) { // Change h1 content map.put("title", "Thymeleaf Label demonstration"); // Change id, name, value map.put("th_id", "thymeleaf-input"); map.put("th_name", "thymeleaf-input"); map.put("th_value", "Poplar hair"); // Change class, href map.put("th_class", "thymeleaf-class"); map.put("th_href", "http://www.yangmufa.com"); return "attributes"; } }
Start the project after encoding is complete, switch to the Spring Blog directory and start the Spring Boot project through the Maven plug-in with the command mvn spring-boot:run, then you can wait for the project to start.
After the project has started successfully, you can view the site effects in browser input display.
Due to the presence of th tags, the content and element attributes of a page rendered with Thymeleaf have been dynamically switched from static page to static page, which can be understood in conjunction with other resources and try several other common tags.
4-Thymeleaf Template Label Properties:
4.1-Thymeleaf Set Property Value:
First, take a look at Thymeleaf's official summary of the characteristics of standard expressions:
Expression syntax:
Variable expression: ${...}
Select variable expression: *{...}
Info expression: #{...}
Link URL expression: @{...}
Section expression: ~{...}
Literal quantity:
String:'one text','Another one!'...
Numbers: 0, 34, 3.0, 12.3...
Boolean values: true, false
Null value: null
Literal quantities: one, sometext, main...
Text operations:
String Stitching: +
Literal replacement: |The name is ${name}|
Arithmetic operations:
Binary operators: +, -, *, /,%
Negative sign (unary operator): -
Boolean operations:
Binary operators: and, or
Boolean not (unary operator):!, not
Comparisons:
Compare: >, <, >=, <= (gt, lt, ge, le)
Equality operator: ==,!= (eq, ne)
The comparison operator can also use escape characters, such as the greater than sign, the Thymeleaf syntax GT or the escape character >
Conditional operators:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special grammar:
No action: _
4.2-Simple syntax:
The next step is to practice these knowledge points by encoding them and concatenating them to get closer to actual development rather than demo.
Create a new simple.html template page. This case mainly records literal quantities, which are simple operations, including common literal quantities such as strings, numbers, Booleans, and common operators and splicing operations. The code is as follows:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <title>Thymeleaf simple syntax</title> <meta charset="UTF-8"> </head> <body> <h1>Thymeleaf Simple Grammar</h1> <div> <h5>Basic type operation(Character string):</h5> <p>A simple string:<span th:text="'thymeleaf text'">default text</span>.</p> <p>String connection:<span th:text="'thymeleaf text concat,'+${thymeleafText}">default text</span>.</p> <p>String connection:<span th:text="|thymeleaf text concat,${thymeleafText}|">default text</span>.</p> </div> <div> <h5>Basic type operation(number):</h5> <p>A simple magical number:<span th:text="2019">1000</span>.</p> <p>Arithmetic operations: 2019+1=<span th:text="${number1}+1">0</span>.</p> <p>Arithmetic operations: 667-1=<span th:text="2021-1">0</span>.</p> <p>Arithmetic operations: 673 * 3=<span th:text="673*${number2}">0</span>.</p> <p>Arithmetic operations: 9 ÷ 3=<span th:text="9/3">0</span>.</p> </div> <div> <h5>Basic type operation(Boolean Value):</h5> <p>A simple comparison of numbers: 2020 > 2019=<span th:text="2020>2019"> </span>.</p> <p>String comparison: thymeleafText == 'yangmufa.com',The result is<span th:text="${thymeleafText} == 'yangmufa.com'">0</span>.</p> <p>Number comparison: 13 == 39/3 The results are: <span th:text="13 == 39/3">0</span>.</p> </div> </body> </html>
There are also some variables that set values for the background, which are combined with the literal quantity to demonstrate the practice, with the new Controller method:
// ThymeleafController.java @GetMapping("/simple") public String simple(ModelMap map) { map.put("thymeleafText", "www.yangmufa.com"); map.put("number1", 2020); map.put("number2", 3); return "simple"; }
Then restart to view the effect in the browser input:
The left side is a static html result and the right side is a result rendered by the Thymeleaf template engine. You can see the display and operation results of the literal quantity.These are variables in Thymeleaf syntax and simple operations.
4.3-Expression syntax:
Expressions include variable expressions ${...}, select variable expressions *{...}, information expressions #{...}, link URL expressions: @{...}, piecewise expressions: ~{...}, these expressions can only be written in the th tag of a Thymeleaf template file, otherwise they will not work. The main function of expression syntax is to get variable values, get variable values of bound objectsInternationalized variable values, URL splicing and generation, Thymeleaf template layout.
Variable expression:
Variable expressions, OGNL expressions or Spring EL expressions, are used to get the value of the object bound to the back-end return data in the template, written in ${...}. This is the most common expression and can be used in value assignment, logical judgment, loop statements, for example:
<!-- Read parameters --> <p>Arithmetic operations: 2019+1=<span th:text="${number1}+1">0</span>.</p> <!-- Read Parameters and Operate --> <div th:class="${path}=='links'?'nav-link active':'nav-link'"></div> <!-- Reading attributes in objects --> <p>read blog In object title Field:<span th:text="${blog.blogTitle}">default text</span>.</p> <!-- Loop traversal --> <li th:each="blog : ${blogs}">
Variable expressions can also use built-in basic objects:
- ctx : the context object.
- vars : the context variables.
- locale : the context locale.
- Request: The HttpServletRequest object in the web environment.
- Response: The HTTP ServletResponse object in the web environment.
- Session: a HttpSession object in a web environment.
- ServletContext: A ServletContext object in a web environment.
Examples are as follows:
<p>Read the contents of the request in the built-in object: <span:text="${#request.getAttribute ('requestObject')}">default text</span>. </p> <p>Read sessions from built-in objects: <span th:text="${#session.getAttribute ('sessionObject')}">default text</span>. </p>
Thymeleaf also provides a series of Utility tool objects (built into the Context) that can be accessed directly from #, with the following tool classes:
- dates: Functional method class for java.util.Date.
- calendars: like #dates, for java.util.Calendar
- Number: Tool method class for formatting numbers
- strings: Tool method class for string objects, contains,startWiths,prepending/appending, and so on.
- bools: Tool method for evaluating Boolean values.
- Arrays: Tool method for arrays.
- lists: Tools for java.util.List
- sets: Tools for java.util.Set
- maps: Tools for java.util.Map
You can think of these methods as tool classes, which make Thymeleaf easier to manipulate variables.
Select (asterisk) expression:
A selection expression is similar to a variable expression, but it executes with a pre-selected object instead of a context variable container (map) in the following syntax: *{blog.blogId}, the specified object is defined by the th:object tag property, and the title field of the blog object read earlier can be replaced by:
<p th:object="${blog}">read blog In object title Field:<span th:text="*{blogTitle}">text</span>.</p>
Without regard to context, there is no difference between the two. What you read with ${...} can be completely replaced with *{...} for reading. The only difference is that you can pre-define and manipulate an object in the parent tag with th:object before using *{...}.
<p>read blog In object title Field:<span th:text="*{blog.blogTitle}">default text</span></p> <p>read text Field:<span th:text="*{text}">default text</span>.</p>
URL expression:
Th:href corresponds to the href tag in html, which calculates and replaces the link URL address in the href tag. The link URL address in th:href can be set directly to a static address or can be stitched dynamically using variable values read from the expression syntax.
For example, a detail page URL address: http://localhost:9090/blog/2, which can be written as follows when using a URL expression:
<a th:href="@{'http://Localhost:9090/blog/2'} ">Details page</a>
It can also be replaced by an id value written as:
<a th:href="@{'/blog/'+${blog.blogId}}">Detail Page</a>
Or it could be written like this:
<a th:href="@{/blog/{blogId}(blogId=${blog.blogId})">Detail Page</a>
All three of the above expression writings result in the same URL. Developers can assemble URLs themselves using string splicing (the second way), or using the syntax provided by the URL expression (the third way).If you have multiple parameters that can assemble strings themselves or use commas to separate them, write as follows:
<a th:href="@{/blog/{blogId}(blogId=${blog.blogId},title=${blog.blogTitle},tag='java')}">Detail Page</a>
The resulting URL is http://localhost:9090/blog/2? Title=lou-springboot&tag=java. In addition, the path in the URL that starts with'/'(e.g. /blog/2), the default generated URL adds the current address of the project to form the full URL.
4.4 - Complex syntax:
Combining some of the knowledge points in the previous section for more complex grammar practices, mainly the methods that will appear in the subsequent tutorials, such as judgment statements, loop statements, tool class use, and so on.
The ThymeleafComplex grammarTest.html template file is as follows:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="${title}">Grammar Test</title> </head> <body> <h3>#strings tool class test </h3> <div th:if="${not #strings.isEmpty(testString)}" > <p>testString initial value : <span th:text="${testString}"/></p> <p>toUpperCase : <span th:text="${#strings.toUpperCase(testString)}"/></p> <p>toLowerCase : <span th:text="${#strings.toLowerCase(testString)}"/></p> <p>equalsIgnoreCase : <span th:text="${#strings.equalsIgnoreCase(testString, '13')}"/></p> <p>indexOf : <span th:text="${#strings.indexOf(testString, 'r')}"/></p> <p>substring : <span th:text="${#strings.substring(testString, 5, 9)}"/></p> <p>startsWith : <span th:text="${#strings.startsWith(testString, 'Spring')}"/></p> <p>contains : <span th:text="${#strings.contains(testString, 'Boot')}"/></p> </div> <h3>#bools tool class test </h3> <!-- If bool The value of false If so, the div Will not be displayed--> <div th:if="${#bools.isTrue(bool)}"> <p th:text="${bool}"></p> </div> <h3>#arrays tool class test </h3> <div th:if="${not #arrays.isEmpty(testArray)}"> <p>length : <span th:text="${#arrays.length(testArray)}"/></p> <p>contains : <span th:text="${#arrays.contains(testArray, 5)}"/></p> <p>containsAll : <span th:text="${#arrays.containsAll(testArray, testArray)}"/></p> <p>Loop Read : <span th:each="i:${testArray}" th:text="${i+' '}"/></p> </div> <h3>#lists Tool Class Test </h3> <div th:unless="${#lists.isEmpty(testList)}"> <p>size : <span th:text="${#lists.size(testList)}"/></p> <p>contains : <span th:text="${#lists.contains(testList, 0)}"/></p> <p>sort : <span th:text="${#lists.sort(testList)}"/></p> <p>Loop Read : <span th:each="i:${testList}" th:text="${i+' '}"/></p> </div> <h3>#maps tool class test </h3> <div th:if="${not #maps.isEmpty(testMap)}"> <p>size : <span th:text="${#maps.size(testMap)}"/></p> <p>containsKey : <span th:text="${#maps.containsKey(testMap, 'platform')}"/></p> <p>containsValue : <span th:text="${#maps.containsValue(testMap, '13')}"/></p> <p>read map The middle key is title Value of : <span th:if="${#maps.containsKey(testMap,'title')}" th:text="${testMap.get('title')}"/></p> </div> <h3>#dates Tool Class Test </h3> <div> <p>year : <span th:text="${#dates.year(testDate)}"/></p> <p>month : <span th:text="${#dates.month(testDate)}"/></p> <p>day : <span th:text="${#dates.day(testDate)}"/></p> <p>hour : <span th:text="${#dates.hour(testDate)}"/></p> <p>minute : <span th:text="${#dates.minute(testDate)}"/></p> <p>second : <span th:text="${#dates.second(testDate)}"/></p> <p>Format: <span th:text="${#dates.format(testDate)}"/></p> <p>yyyy-MM-dd HH:mm:ss Format: <span th:text="${#dates.format(testDate, 'yyyy-MM-dd HH:mm:ss')}"/></p> </div> </body> </html>
Add Controller as follows:
//ThymeleafController.java @GetMapping("/test") public String ThymeleafCompleGrammarTest(ModelMap map) { map.put("title", "Thymeleaf Grammar Test"); map.put("testString", "Get along well with Spring Boot"); map.put("bool", true); map.put("testArray", new Integer[]{2018,2019,2020,2021}); map.put("testList", Arrays.asList("Spring", "Spring Boot", "Thymeleaf", "MyBatis", "Java")); Map testMap = new HashMap(); testMap.put("platform", "yangmufa.com"); testMap.put("title", "Get along well with Spring Boot"); testMap.put("author", "Green Source Network www.lvyvan.cn"); map.put("testMap", testMap); map.put("testDate", new Date()); return "test"; }
Then restart the project and access/test as follows:
In the strings tool class test, we first use the th:if tag for logical judgment, th:if="${not #strings.isEmpty(testString)}" is a judgment statement, and a Boolean result is returned in the ${...} expression. If true, the contents of the div will continue to be displayed, otherwise the main tag of th:if will not be displayed.
#strings.isEmpty serves as a string null, returns true if the testString is empty, and nots before the expression represent logical nonoperations, that is, continues to show the contents of the div if the testString is not empty.
Similar to th:if, the judgment tag is th:unless, which is the opposite of th:if. When the result returned in the expression is false, the content in the tag will continue to be displayed. We used th:unless in the #lists tool class test, and you can compare the differences when debugging code.
The syntax for looping statements in the Thymeleaf template engine is th:each="i:${testList}", similar to the c:foreach expression in JSP, mainly the logic for looping, which is used in many page logic generations.
Another way to read Map objects is ${testMap.get('title')}, which is similar to the Java language.Logical judgment and circular statement are two common and important points in system development. I hope you can combine code practice and master them firmly.
Notes for using the 5-Thymeleaf template engine:
5.1 - Namespaces must be introduced:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
Static resource access and template dynamic access will not fail even if the above namespaces are not introduced, so this may sometimes be ignored.However, it is recommended that this namespace be introduced during development because it will be followed by syntax hints for Thymeleaf code, which can improve development efficiency and reduce human-induced low-level errors.
5.2 - Disable template caching:
Thymeleaf's default cache settings are determined by the configuration file's spring.thymeleaf.cache property, which is found to be true by the configuration property class ThymeleafProperties of the Thymeleaf template above, so Thymeleaf defaults to using template caching, which helps improve application performance because templates only need to be compiled once, butIt is recommended that you set this property to false and modify it in the configuration file as follows:
spring.thymeleaf.cache=false
5.2-IDEA Pop-red Waveline Problem when reading variables through Thymeleaf syntax:
As shown in the figure below, you may encounter this problem when you first start developing with Thymeleaf, where a red wavy line, or a flag of error, appears under the name of a variable when it is read through the Thymeleaf syntax in a template file.
Unfamiliar friends may have problems with their own template files, but they are not that serious. Just because expression parameter validation is turned on by default in IDEA, even if the variable is added to the back-end model data, it is not aware of the front-end file, so this error will be reported. You can turn off validation or prompt level by default in IDEAYou can also modify it.
Summary:
Thymeleaf template engine technology is documented in detail, as well as the integration of Spring Boot and Thymeleaf.Finally, I have sorted out a few points that need attention when using the Thymeleaf template engine. If you are new to Thymeleaf, you need not be confused with similar problems. These pits have been trampled by someone for us. In order to learn more smoothly in the future, you should practice the basic grammar a little more.
/*-------------------------- Anti-Web Crawler Declaration o(*///////*) Sizzling----------------------------------------------------------------------------------------------------------------------------
Author: Poplar hair
Copyright Statement:
This is a blogger's favorite original article. Please attach a link to the source article for the whole story!
If you feel you have gained something from this article, please comment in favor of it
Reasonable and high quality forwarding will also be the motivation to encourage me to continue creating.
More exciting can Baidu search for Yang Mufa or:
Personal website: www.yangmufa.com ,
Open Source China: https://my.oschina.net/yangmufa ,
Gitee: https://gitee.com/yangmufa ,
GitHub: https://github.com/yangmufa .
Persist in creating and be good at summarizing the high-quality progress of open source sharing.
---------------------- Anti-Web Crawler Declaration o(*///////*) Sizzling--------------------------*/