如何在JSTL中动态修改Map键值对——Spring MVC+JSP场景
嘿,这个需求很常见,尤其是做XSS防护的时候!针对你用Spring MVC + JSP构建的应用,要在遍历Map时修改mapItem的键和值,这里有几个实用的方案,适配不同的场景:
1. 用JSTL的fn:escapeXml直接转义输出(最推荐)
如果你的核心需求是XSS防护,最规范的做法是在输出时对内容做HTML转义,而不是修改原Map的数据。JSTL的fn标签库自带的escapeXml函数,可以自动把HTML特殊字符(比如<转成<)转义成纯文本,避免恶意脚本被浏览器执行。
步骤很简单:
- 先引入
fn标签库 - 输出key和value时用
fn:escapeXml包裹
修改后的JSP代码:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <%@ page import="java.util.ArrayList"%> <%@ page import="java.util.HashMap"%> <% ArrayList<HashMap<String, String>> listOfMap = new ArrayList<HashMap<String, String>>(); HashMap<String, String> map; for (int i = 0; i < 3; i++) { map = new HashMap<String, String>(); map.put("key1", "value1" + i); map.put("key2", "<script>alert('xss')</script>" + i); // 模拟恶意内容 listOfMap.add(map); } request.setAttribute("listOfMap", listOfMap); %> <html> <body> <c:forEach items="${listOfMap}" var="maps"> <c:forEach items="${maps}" var="mapItem"> ${fn:escapeXml(mapItem.key)} ${fn:escapeXml(mapItem.value)} <br /> </c:forEach> </c:forEach> </body> </html>
这样输出的内容会被安全转义,恶意代码只会显示成普通文本,完美解决XSS问题。
2. 在循环内用<c:set>自定义处理键值
如果你除了XSS防护,还需要对key或value做其他自定义修改(比如加前缀、截断长度),可以用<c:set>标签在循环里创建处理后的变量,再输出:
<c:forEach items="${listOfMap}" var="maps"> <c:forEach items="${maps}" var="mapItem"> <%-- 处理key:转义后加个前缀 --%> <c:set var="processedKey" value="${fn:escapeXml(mapItem.key)}_processed" /> <%-- 处理value:转义后只取前10个字符 --%> <c:set var="processedValue" value="${fn:escapeXml(fn:substring(mapItem.value, 0, 10))}" /> ${processedKey} ${processedValue} <br /> </c:forEach> </c:forEach>
这种方式灵活度很高,你可以根据需求随便调整处理逻辑。
3. 后端提前处理数据(符合Spring MVC最佳实践)
Spring MVC的最佳实践是尽量少在JSP里写业务逻辑,所以更推荐你在Controller层就把Map的键值对处理好,再传到JSP页面渲染:
示例Controller代码:
@Controller public class DemoController { @GetMapping("/demo") public String demo(Model model) { List<Map<String, String>> listOfMap = new ArrayList<>(); for (int i = 0; i < 3; i++) { Map<String, String> map = new HashMap<>(); // 用Spring自带的HtmlUtils做HTML转义 String processedKey = HtmlUtils.htmlEscape("key1" + i); String processedValue = HtmlUtils.htmlEscape("<script>alert('xss')</script>" + i); map.put(processedKey, processedValue); listOfMap.add(map); } model.addAttribute("listOfMap", listOfMap); return "demo"; // 对应你的JSP视图名 } }
然后JSP里直接输出处理好的内容就行,不用再写任何处理逻辑:
<c:forEach items="${listOfMap}" var="maps"> <c:forEach items="${maps}" var="mapItem"> ${mapItem.key} ${mapItem.value} <br /> </c:forEach> </c:forEach>
这种方式让代码职责更清晰,Controller管数据处理,JSP只管渲染,后期维护也更方便。
4. 自定义EL函数(复杂逻辑复用场景)
如果你的处理逻辑很复杂,而且要在多个JSP里复用,可以自定义EL函数:
- 先写一个Java工具类,封装处理逻辑:
public class XssUtils { public static String escape(String input) { // 这里可以实现自定义的转义逻辑,比如除了HTML转义还处理其他特殊字符 return HtmlUtils.htmlEscape(input); } }
- 在
WEB-INF下创建一个tld文件(比如custom-functions.tld),注册你的EL函数:
<?xml version="1.0" encoding="UTF-8"?> <taglib xmlns="http://java.sun.com/xml/ns/javaee" version="2.1"> <tlib-version>1.0</tlib-version> <short-name>custom</short-name> <uri>http://your-domain.com/custom-functions</uri> <function> <name>escapeXss</name> <function-class>com.yourpackage.XssUtils</function-class> <function-signature>java.lang.String escape(java.lang.String)</function-signature> </function> </taglib>
- 在JSP里引入标签库,直接调用自定义函数:
<%@ taglib uri="http://your-domain.com/custom-functions" prefix="custom"%> <c:forEach items="${listOfMap}" var="maps"> <c:forEach items="${maps}" var="mapItem"> ${custom:escapeXss(mapItem.key)} ${custom:escapeXss(mapItem.value)} <br /> </c:forEach> </c:forEach>
这种方式适合需要复用复杂处理逻辑的场景,不用在每个JSP里重复写代码。
总结一下:如果只是做XSS防护,优先用fn:escapeXml或者后端提前处理;如果需要自定义修改逻辑,用<c:set>或者自定义EL函数就好。
内容的提问来源于stack exchange,提问作者MiniSu

