背景
前段时间有一需求,需要动态修改xml模版的内容,但是网上能收集的资料多是关于thymeleaf的HTML使用方式;于是,在【科学上网】与自己的研究下,终于成功解决了这个需求。
通过下文,将可以学到Spring Boot2.x+Thymeleaf的XML模式的使用,以及自定义Thymeleaf方言属性两个知识点;水平有限,若有误,欢迎各路英雄指正。
使用IDEA创建spring boot工程,具体步骤参考其他资料。
在pom.xml中,添加thymeleaf相关的依赖,如下所示
<?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 https://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.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.he</groupId> <artifactId>springboot-template</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-template</name> <description>Spring Boot2.x整合模板引擎技术</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
在此简单起见,我全部使用spring boot默认的配置,可以看到该文件内容是为空的
Thymeleaf的XML模式,不像默认的HTML,需要额外提供两个Bean才可以成功运行;
第一个,是[SpringResourceTemplateResolver],模版解析器;
第二个,是[SpringTemplateEngine],Spring的模版引擎。
package com.he.config; import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.thymeleaf.spring5.SpringTemplateEngine;import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;import org.thymeleaf.templatemode.TemplateMode; /*** @date 2019/10/4* @des Thymeleaf配置*/@Configurationpublic class ThymeleafConfig { @Bean SpringResourceTemplateResolver xmlTemplateResolver(ApplicationContext appCtx) { SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setApplicationContext(appCtx); templateResolver.setPrefix("classpath:/templates/");//指定模版前缀,即存放位置,默认是该地址 templateResolver.setSuffix(".xml");//指定模版后缀 templateResolver.setTemplateMode(TemplateMode.XML);//指定使用‘XML’模式 templateResolver.setCharacterEncoding("UTF-8");//指定使用‘UTF-8’编码 templateResolver.setCacheable(true);//开启缓存 return templateResolver; } @Bean SpringTemplateEngine templateEngine(ApplicationContext appCtx) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setEnableSpringELCompiler(true); templateEngine.setTemplateResolver(xmlTemplateResolver(appCtx)); return templateEngine; } }
在[resources-templates]中,新建我们的模版[person-test.xml],注意Thymeleaf语法的正确使用
xml模版内容如下,十分简单,我们模拟动态的修改一个人的姓名国籍信息
<?xml version="1.0" encoding="UTF-8"?><persons> <person> <!--tag内容,使用strings.concat()拼接示例,读取map内容--> <fname th:text="${#strings.concat('firstname=',pinfo['lastname'])}"></fname> <!--tag属性,需设置多个属性时使用','隔开;或直接使用th:value的方式, 但是当属性值为空时将不显示,若要显示则需要自定义属性方言--> <lname th:attr="lastname=${pinfo['firstname']}"></lname> <!--thymeleaf的其他具体语法参考官网--> <country th:text="${pinfo['country']}"></country> </person></persons>
在此,我引入swagger2,方便调试,具体引入参考代码,这里不再描述
新建[TestController.java],编写我们的测试示例,如下所示
package com.he.controller; import com.he.entity.*;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.thymeleaf.context.Context;import org.thymeleaf.spring5.SpringTemplateEngine; import java.util.*; /*** @date 2019/10/4* @des thymeleaf模版*/@Slf4j@RestController@RequestMapping("/test")public class TestController { @Autowired SpringTemplateEngine springTemplateEngine; @ApiOperation(value = "Thymeleaf模版的XML模式", notes = "XML模版") @ApiImplicitParams({ @ApiImplicitParam(name = "lastname", value = "姓氏"), @ApiImplicitParam(name = "firstname", value = "名字"), @ApiImplicitParam(name = "country", value = "国籍") }) @GetMapping(value = "/test1", produces = {MediaType.APPLICATION_XML_VALUE})//produces改为XML public String test1(@RequestParam String lastname, @RequestParam String firstname, @RequestParam String country) { Map<String, String> pinfo = new HashMap<>(); Context context = new Context(); context.setVariable("pinfo", pinfo); pinfo.put("lastname", lastname); pinfo.put("firstname", firstname); pinfo.put("country", country); log.info("---pinfo:{}", pinfo); String content = springTemplateEngine.process("person-test", context); log.info("---xml:\n{}", content); return content; }}
在swagger-ui.html输入测试参数后,日志打印如下
2019-10-12 16:38:23.898 INFO 12504 --- [nio-8080-exec-7] com.he.controller.TestController : ---pinfo:{country=china, firstname=haha, lastname=ho}2019-10-12 16:38:24.063 INFO 12504 --- [nio-8080-exec-7] com.he.controller.TestController : ---xml:<?xml version="1.0" encoding="UTF-8"?><persons> <person> <!--tag内容,使用strings.concat()拼接示例,读取map内容--> <fname>firstname=ho</fname> <!--tag属性,需设置多个属性时使用','隔开;或直接使用th:value的方式, 但是当属性值为空时将不显示,若要显示则需要自定义属性方言--> <lname lastname="haha"></lname> <!--thymeleaf的其他具体语法参考官网--> <country>china</country> </person></persons>
页面返回如下
可见XML的内容被修改了,到此Thymeleaf的XML模式正式实现。
接下来看下Thymeleaf更加高级一点的用法,即自定义标签,自定义方言属性,在此我只介绍[自定义方言属性],并且是基于XML模式下,在HTML文件中引用是一样的,只需把模式切换回HTML即可
假设我们需要通过SQL查询某一个数据库系统,但是这个SQL是通过xml协议实现的。我们需要动态的修改这个SQL,也就是根据具体的查询语句,动态改变XML的内容。但是,我在改变tag的标签属性时,遇到“属性值为空时,该属性将不被显示”的问题,就是当使用[th:attr]或[th:xxx]这种方式设置属性时,若attr-value为空,将不可见,这对于协议是不允许的。所以只能自定义方言实现这一需求。
在这里,我们将定义我们自己的属性[zdy:attr]
原xml协议如下:
<?xml version="1.0" encoding="utf-8"?><condition> <!--查询字段--> <select> <column func=" " name=" c1" nickname=" C1" comments=" 字段1"/> <column func=" " name=" c2" nickname=" C2" comments=" 字段2"/> </select> <!--查询条件--> <where> <cd func="count($VALUE)" column="c1" compare="=" value="" relation=""/> <cd func="" column="c2" compare="=" value="" relation="and"/> </where></condition>
改造后,如下:
<?xml version="1.0" encoding="utf-8"?><condition> <!--查询字段--> <select> <column th:each="column:${condition.select.columns}" zdy:attr="${#strings.concat('func=',column.func)}; ${#strings.concat('name=',column.name)}; ${#strings.concat('nickname=',column.nickname)}; ${#strings.concat('comments=',column.comments)}"/> </select> <!--查询条件--> <where th:if="${condition.where}"> <cd th:each="cd:${condition.where.cds}" zdy:attr="${#strings.concat('func=',cd.func)}; ${#strings.concat('column=',cd.column)}; ${#strings.concat('compare=',cd.compare)}; ${#strings.concat('value=',cd.value)}; ${#strings.concat('relation=',cd.relation)}"/> </where></condition>
实体类与xml的tag分别对应起来,有5个,分别如下
package com.he.entity; import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable; /*** @date 2019/10/4* @des XML模版<condition>标签**/@Data@NoArgsConstructor@AllArgsConstructorpublic class Condition implements Serializable { /** * 查询字段,非空 */ private Select select = new Select(); /** * 查询条件,可为空 */ private Where where;}
package com.he.entity; import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable;import java.util.List; /*** @date 2019/10/4* @des XML模版<condition>里的<select>*/@Data@NoArgsConstructor@AllArgsConstructorpublic class Select implements Serializable{ /** * 表示单个或多个字段,动态添加 */ private List<Column> columns;}
package com.he.entity; import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable; /*** @date 2019/10/4* @des XML模版<condition>里的<select>里的<column>*/@Data@NoArgsConstructor@AllArgsConstructorpublic class Column implements Serializable { //<column func="count($COLUMN)" name=" c3" nickname=" C3" comments=" 字段3"/> private String func = ""; private String name = ""; private String nickname = ""; private String comments = ""; }
package com.he.entity; import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable;import java.util.List; /*** @date 2019/10/4* @des XML模版<condition>里的<where>*/@Data@NoArgsConstructor@AllArgsConstructorpublic class Where implements Serializable{ /** * cd标签:单条件,动态添加 */ private List<Cd> cds;}
package com.he.entity; import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable; /*** @date 2019/10/4* @des XML模版<condition>里的<where>里的cd标签*/@Data@NoArgsConstructor@AllArgsConstructorpublic class Cd implements Serializable { private String func = ""; private String column = ""; private String compare = ""; private String value = ""; private String relation = "";}
自定义属性处理器,需要继承[AbstractAttributeTagProcessor]类,代码如下
package com.he.config; import org.thymeleaf.IEngineConfiguration;import org.thymeleaf.context.ITemplateContext;import org.thymeleaf.engine.AttributeName;import org.thymeleaf.model.IProcessableElementTag;import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;import org.thymeleaf.processor.element.IElementTagStructureHandler;import org.thymeleaf.standard.expression.IStandardExpression;import org.thymeleaf.standard.expression.IStandardExpressionParser;import org.thymeleaf.standard.expression.StandardExpressions;import org.thymeleaf.templatemode.TemplateMode; /*** @date 2019/10/4* @des 自定义thymeleaf属性处理器*/public class CustomAttrProcessor extends AbstractAttributeTagProcessor { private static final String ATTR_NAME = "attr";//自定义属性名,即(:)后面的名称,与前缀组合后是(zdy:attr) private static final int PRECEDENCE = 10000;//优先级 public CustomAttrProcessor(final String dialectPrefix) { super( TemplateMode.XML, // This processor will apply only to XML mode dialectPrefix, // Prefix to be applied to name for matching null, // No tag name: match any tag name false, // No prefix to be applied to tag name ATTR_NAME, // Name of the attribute that will be matched true, // Apply dialect prefix to attribute name PRECEDENCE, // Precedence (inside dialect's precedence) true); // Remove the matched attribute afterwards } @Override protected void doProcess( final ITemplateContext context, final IProcessableElementTag tag, final AttributeName attributeName, final String attributeValue, final IElementTagStructureHandler structureHandler) { /* * In order to evaluate the attribute value as a Thymeleaf Standard Expression, * we first obtain the parser, then use it for parsing the attribute value into * an expression object, and finally execute this expression object. */ final IEngineConfiguration configuration = context.getConfiguration(); /* * Obtain the Thymeleaf Standard Expression parser */ final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration); /** * 根据“;”拆分属性组合(同一属性不允许多次出现在同一element中,所以使用‘;’进行属性组合) */ System.out.println("--自定义thymeleaf属性值: " + attributeValue); String[] attrArray = attributeValue.split(";"); if (attrArray != null && attrArray.length > 0) { //遍历每个属性,设置单个属性值 for (int i = 0; i < attrArray.length; i++) { //解析单个属性 String attr = attrArray[i]; /** * Parse the attribute value as a Thymeleaf Standard Expression */ final IStandardExpression expression = parser.parseExpression(context, attr); /** * Execute the expression just parsed */ final String tagAttr = (String) expression.execute(context); //根据“=”拆分属性name和value String[] tagAttrArray = tagAttr.split("="); String attrName = tagAttrArray[0]; String attrValue = ""; if (tagAttrArray.length > 1) { attrValue = tagAttrArray[1]; } //设置属性 structureHandler.setAttribute(attrName, attrValue); } } }}
在doProcess()方法里,根据自定义的属性,读取属性值,然后进行拆分出一个个单独的属性,使用structureHandler.setAttribute()重新给tag设置属性,从而达到修改属性的目的。
完成了第4步后,还需要自定义属性方言,并添加到模版引擎中。新建CustomAttrDialect.java继承AbstractProcessorDialect
package com.he.config; import org.thymeleaf.dialect.AbstractProcessorDialect;import org.thymeleaf.processor.IProcessor; import java.util.HashSet;import java.util.Set; /*** @date 2019/10/4* @des 自定义thymeleaf属性方言*/public class CustomAttrDialect extends AbstractProcessorDialect { private static final String DIALECT_NAME = "custom"; // 方言名称 private static final String PREFIX = "zdy"; // 方言前缀,zdy(自定义) ,使用格式(zdy:*) public static final int PROCESSOR_PRECEDENCE = 1000; // 方言优先级 public CustomAttrDialect() { super(DIALECT_NAME, PREFIX, PROCESSOR_PRECEDENCE); } /* * 初始化方言处理器 * */ public Set<IProcessor> getProcessors(final String dialectPrefix) { final Set<IProcessor> processors = new HashSet<>(); processors.add(new CustomAttrProcessor(dialectPrefix));//添加自定义的属性处理器 return processors; }}
然后,把它添加到模版引擎中,如下
@BeanSpringTemplateEngine templateEngine(ApplicationContext appCtx) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setEnableSpringELCompiler(true); templateEngine.setTemplateResolver(xmlTemplateResolver(appCtx)); templateEngine.addDialect(new CustomAttrDialect());//自定义方言 return templateEngine;}
简单测试起见,我们把SQL的参数写死,整个类如下
package com.he.controller; import com.he.entity.*;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.thymeleaf.context.Context;import org.thymeleaf.spring5.SpringTemplateEngine; import java.util.*; /*** @date 2019/10/4* @des thymeleaf模版*/@Slf4j@RestController@RequestMapping("/test")public class TestController { @Autowired SpringTemplateEngine springTemplateEngine; @ApiOperation(value = "Thymeleaf模版的XML模式", notes = "XML模版") @ApiImplicitParams({ @ApiImplicitParam(name = "lastname", value = "姓氏"), @ApiImplicitParam(name = "firstname", value = "名字"), @ApiImplicitParam(name = "country", value = "国籍") }) @GetMapping(value = "/test1", produces = {MediaType.APPLICATION_XML_VALUE})//produces改为XML public String test1(@RequestParam String lastname, @RequestParam String firstname, @RequestParam String country) { Map<String, String> pinfo = new HashMap<>(); Context context = new Context(); context.setVariable("pinfo", pinfo); pinfo.put("lastname", lastname); pinfo.put("firstname", firstname); pinfo.put("country", country); log.info("---pinfo:{}", pinfo); String content = springTemplateEngine.process("person-test", context); log.info("---xml:\n{}", content); return content; } @ApiOperation(value = "Thymeleaf动态修改XML", notes = "可指定参数动态修改XML") @GetMapping(value = "/test2", produces = {MediaType.APPLICATION_XML_VALUE})//produces改为XML public String test2() { Context context = new Context(); //<condition>标签 Condition condition = new Condition(); condition = testSql(); context.setVariable("condition", condition); String content = springTemplateEngine.process("xml-protocol", context); log.info("Thymeleaf dynamic---xml:\n{}", content); return content; } /** * ‘select count(c1) as xxx,c2 from tb where ...’ * * @return Condition */ private Condition testSql() { Condition condition = new Condition(); //select字段 List<Column> columns = new ArrayList<>(); Column c1 = new Column(); c1.setFunc("count(id)"); c1.setNickname("id_count"); Column c2 = new Column(); c2.setName("id"); columns.add(c1); columns.add(c2); condition.getSelect().setColumns(columns); //where条件 List<Cd> cds = new ArrayList<>(); Cd cd1 = new Cd(); cd1.setColumn("name"); cd1.setCompare("="); cd1.setValue("hehe"); Cd cd2 = new Cd(); cd2.setColumn("age"); cd2.setCompare("="); cd2.setValue("18"); cd2.setRelation("and"); cds.add(cd1); cds.add(cd2); Where where = new Where(cds); condition.setWhere(where); return condition; }}
在swagger-ui.html调用测试方法后,页面返回内容如下
日志打印如下
--自定义thymeleaf属性值: ${#strings.concat('func=',column.func)}; ${#strings.concat('name=',column.name)}; ${#strings.concat('nickname=',column.nickname)}; ${#strings.concat('comments=',column.comments)}--自定义thymeleaf属性值: ${#strings.concat('func=',column.func)}; ${#strings.concat('name=',column.name)}; ${#strings.concat('nickname=',column.nickname)}; ${#strings.concat('comments=',column.comments)}--自定义thymeleaf属性值: ${#strings.concat('func=',cd.func)}; ${#strings.concat('column=',cd.column)}; ${#strings.concat('compare=',cd.compare)}; ${#strings.concat('value=',cd.value)}; ${#strings.concat('relation=',cd.relation)}--自定义thymeleaf属性值: ${#strings.concat('func=',cd.func)}; ${#strings.concat('column=',cd.column)}; ${#strings.concat('compare=',cd.compare)}; ${#strings.concat('value=',cd.value)}; ${#strings.concat('relation=',cd.relation)}2019-10-12 17:41:39.288 INFO 18976 --- [nio-8080-exec-8] com.he.controller.TestController : Thymeleaf dynamic---xml:<?xml version="1.0" encoding="utf-8"?><condition> <!--查询字段--> <select> <column func="count(id)" name="" nickname="id_count" comments=""/> <column func="" name="id" nickname="" comments=""/> </select> <!--查询条件--> <where> <cd func="" column="name" compare="" value="hehe" relation=""/> <cd func="" column="age" compare="" value="18" relation="and"/> </where></condition>
可见,测试成功!
至此,Thymeleaf的XML模式,自定义方言属性已介绍完毕,后面将介绍另一模版引擎Freemarker的XML模式使用方式。
https://gitee.com/he-running/springboot-template.git
原网址: 访问
创建于: 2021-05-21 12:01:39
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论