實(shí)戰(zhàn):穩(wěn)定交付與工程落地指南)
簡(jiǎn)介這是一套基于SSM框架開發(fā)的電影在線觀看系統(tǒng)完整源碼面向Java Web初學(xué)者與中級(jí)開發(fā)者適用于課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或Web全棧技能實(shí)戰(zhàn)訓(xùn)練。系統(tǒng)采用JSP前端頁(yè)面配合Layui UI組件后端整合Spring、SpringMVC與MyBatis支持用戶注冊(cè)登錄、影片瀏覽、搜索、播放記錄等功能具備典型MVC分層結(jié)構(gòu)與可擴(kuò)展性。資源包共322個(gè)文件涵蓋35個(gè)Java業(yè)務(wù)類、31個(gè)XML映射配置、29個(gè)JSP視圖頁(yè)、30個(gè)JS交互腳本及23個(gè)JPG/PNG素材輔以SQL建表語(yǔ)句與Maven依賴配置總大小4.32MB結(jié)構(gòu)清晰、模塊職責(zé)分明。已有551人學(xué)習(xí)下載讀者可直接導(dǎo)入Eclipse或IDEA運(yùn)行調(diào)試快速掌握SSM整合開發(fā)流程、Layui前端集成技巧及MySQL數(shù)據(jù)庫(kù)設(shè)計(jì)實(shí)踐。1. 為什么一個(gè)“電影在線觀看系統(tǒng)”要用 SSM JSP Layui 組合它不是過(guò)時(shí)了而是穩(wěn)在刀刃上很多人看到“Java SSM JSP Layui Maven MySQL”這個(gè)技術(shù)棧第一反應(yīng)是這不就是十年前的老項(xiàng)目模板嗎但真實(shí)情況恰恰相反——在中小型企業(yè)內(nèi)部系統(tǒng)、教育類實(shí)訓(xùn)項(xiàng)目、政務(wù)輕量后臺(tái)、以及大量需要快速交付長(zhǎng)期可維護(hù)的業(yè)務(wù)型 Web 應(yīng)用中這套組合仍是落地成本最低、團(tuán)隊(duì)協(xié)作最順、運(yùn)維壓力最小的黃金閉環(huán)。它不追求前端炫技或微服務(wù)拆分而是把“用戶能點(diǎn)開視頻、管理員能增刪影片、數(shù)據(jù)能查得準(zhǔn)、上線不崩”作為硬性指標(biāo)來(lái)兌現(xiàn)。JSP 負(fù)責(zé)服務(wù)端模板渲染與視頻資源路徑拼接Layui 提供開箱即用的表格、彈窗、播放器容器和權(quán)限控制 UI 框架SSMSpring SpringMVC MyBatis承擔(dān)請(qǐng)求路由、事務(wù)管理與 SQL 映射Maven 管理所有 jar 包版本與構(gòu)建生命周期MySQL 存儲(chǔ)影片元數(shù)據(jù)、用戶行為日志與播放記錄。這套組合對(duì) Java 初學(xué)者友好對(duì)三年以上開發(fā)人員也足夠清晰可控——沒(méi)有隱藏的異步陷阱沒(méi)有跨域調(diào)試黑洞沒(méi)有 npm install 后的 node_modules 失效問(wèn)題。它解決的不是“能不能做”而是“能不能今天下午就讓客戶在測(cè)試服務(wù)器上看到首頁(yè)輪播圖和最新上映列表”。2. 搭建 SSM 骨架從 pom.xml 到 DispatcherServlet 的最小可行路徑2.1 Maven 依賴選型邏輯與關(guān)鍵版本鎖定Maven 不是簡(jiǎn)單羅列dependency而是要建立三層依賴契約基礎(chǔ)運(yùn)行時(shí)契約JDK 兼容性、框架協(xié)同契約Spring 與 MyBatis 版本匹配、前端橋接契約JSP 標(biāo)簽庫(kù)與 Servlet 規(guī)范對(duì)齊。本項(xiàng)目采用 JDK 8兼容絕大多數(shù)企業(yè)環(huán)境因此 Spring 必須選用 5.3.x 系列Spring 6 要求 JDK 17MyBatis 用 3.4.6適配 Spring 5.3 的 SqlSessionFactoryBean 初始化方式JSP 相關(guān)依賴必須顯式聲明javax.servlet.jsp-api2.3.3避免 Tomcat 9 自帶的 jsp-api 沖突。以下是pom.xml中不可省略的核心段落properties spring.version5.3.30/spring.version mybatis.version3.4.6/mybatis.version mysql.connector.version8.0.33/mysql.connector.version jstl.version1.2/jstl.version /properties dependencies !-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- JSP 支持 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version${jstl.version}/version /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency /dependencies提示scopeprovided/scope表示該 jar 由 Tomcat 容器提供打包時(shí)不會(huì)打入 WAR避免與服務(wù)器內(nèi)置 API 沖突。若使用 Tomcat 10需改用 Jakarta EE 命名空間jakarta.servlet.jsp此時(shí)必須同步升級(jí) Spring 到 5.3.29 并啟用spring.web.resources.static-path-pattern/static/**配置。2.2 web.xml 中 DispatcherServlet 的注冊(cè)與初始化參數(shù)SSM 項(xiàng)目啟動(dòng)入口是web.xml中的DispatcherServlet它不是“配置完就完事”而是決定了整個(gè)請(qǐng)求鏈路的起點(diǎn)與上下文加載方式。必須顯式指定contextConfigLocation參數(shù)指向 Spring 配置文件并設(shè)置load-on-startup為 1 保證優(yōu)先加載servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping同時(shí)spring-mvc.xml中必須開啟注解驅(qū)動(dòng)與靜態(tài)資源放行mvc:annotation-driven / mvc:default-servlet-handler / !-- 視圖解析器JSP 文件默認(rèn)放在 /WEB-INF/jsp/ 下 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean注意mvc:default-servlet-handler /是關(guān)鍵——它讓 DispatcherServlet 在找不到對(duì)應(yīng) Controller 時(shí)將/static/css/app.css這類請(qǐng)求交還給 Tomcat 默認(rèn) Servlet 處理否則所有靜態(tài)資源都會(huì) 404。這是 JSP 項(xiàng)目區(qū)別于前后端分離項(xiàng)目的底層機(jī)制。2.3 MyBatis 數(shù)據(jù)源與 SqlSessionFactoryBean 的手動(dòng)裝配SSM 中 MyBatis 不通過(guò)MapperScan自動(dòng)掃描那是 Spring Boot 方式而是通過(guò) XML 手動(dòng)注入SqlSessionFactoryBean。其核心在于dataSource和configLocation的綁定順序必須先定義數(shù)據(jù)源再將其注入到SqlSessionFactoryBean最后由MapperScannerConfigurer掃描接口。典型配置如下!-- 數(shù)據(jù)源使用 Druid 替代 BasicDataSource支持連接池監(jiān)控 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl valuejdbc:mysql://localhost:3306/movie_db?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8 / property nameusername valueroot / property namepassword value123456 / property nameinitialSize value5 / property namemaxActive value20 / /bean !-- SqlSessionFactoryBean -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property nameconfigLocation valueclasspath:mybatis-config.xml / property namemapperLocations valueclasspath:mapper/*.xml / /bean !-- Mapper 接口掃描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.movie.dao / /bean關(guān)鍵點(diǎn)說(shuō)明mapperLocations使用classpath:mapper/*.xml表示所有src/main/resources/mapper/下的 XML 映射文件會(huì)被加載basePackage必須與 DAO 接口所在包路徑完全一致且接口名如MovieDao需與 XML 文件名MovieDao.xml嚴(yán)格匹配否則Invalid bound statement異常必然出現(xiàn)。3. JSP 頁(yè)面與 Layui 集成視頻播放、分頁(yè)與權(quán)限控制的三重落地3.1 JSP 中嵌入 MP4 視頻播放器的兼容寫法JSP 不是靜態(tài) HTML它需要?jiǎng)討B(tài)拼接視頻路徑并處理瀏覽器兼容性。不能直接寫video src${movie.url}因?yàn)?{movie.url}可能是相對(duì)路徑如/upload/avengers.mp4而 Tomcat 默認(rèn)不開放/upload目錄訪問(wèn)。正確做法是在 Controller 中將視頻 URL 轉(zhuǎn)為絕對(duì)路徑或配置 Tomcat 的Context元素映射上傳目錄。更穩(wěn)妥的方式是在web.xml中添加資源映射servlet-mapping servlet-namedefault/servlet-name url-pattern/upload/*/url-pattern /servlet-mapping然后在 JSP 中使用標(biāo)準(zhǔn) HTML5video標(biāo)簽并通過(guò) Layui 的layer彈窗封裝播放邏輯c:forEach items${movieList} varmovie div classlayui-card div classlayui-card-header${movie.title}/div div classlayui-card-body img src${pageContext.request.contextPath}${movie.coverPath} alt${movie.title} width120 height180 / button classlayui-btn layui-btn-sm onclickplayVideo(${movie.videoUrl})播放/button /div /div /c:forEach script function playVideo(videoUrl) { layer.open({ type: 1, title: 在線播放, content: video controls width100% height400source src videoUrl typevideo/mp4/video, area: [800px, 500px] }); } /script注意videoUrl必須是能被瀏覽器直接訪問(wèn)的 HTTP 地址如/upload/avengers.mp4不能是D:/upload/avengers.mp4這類本地路徑。Controller 返回前需調(diào)用String url request.getContextPath() /upload/ filename;構(gòu)造合法 URL。3.2 Layui Table 分頁(yè)與后端數(shù)據(jù)聯(lián)動(dòng)的完整鏈路Layui 的table.render()默認(rèn)走 Ajax但 SSM 項(xiàng)目中 Controller 必須返回 JSON 格式且符合 Layui 的data字段規(guī)范。常見(jiàn)錯(cuò)誤是返回ListMovie而非{ code: 0, msg: , count: 100, data: [...] }結(jié)構(gòu)。因此需定義統(tǒng)一響應(yīng)體public class LayuiTableResultT { private int code; // 0 成功1 失敗 private String msg; private long count; // 總條數(shù) private ListT data; // getter/setter 省略 }Controller 方法示例RequestMapping(/movie/list) ResponseBody public LayuiTableResultMovie list(RequestParam Integer page, RequestParam Integer limit) { PageHelper.startPage(page, limit); ListMovie movies movieService.selectAll(); PageInfoMovie pageInfo new PageInfo(movies); return new LayuiTableResult(0, , pageInfo.getTotal(), movies); }JSP 中的 Layui table 初始化table idmovieTable lay-filtermovieTable/table script layui.use([table, layer], function(){ var table layui.table; table.render({ elem: #movieTable, url: ${pageContext.request.contextPath}/movie/list, page: true, cols: [[ {field:id, title:ID, width:80}, {field:title, title:片名, width:200}, {field:director, title:導(dǎo)演, width:150}, {field:duration, title:時(shí)長(zhǎng)(分鐘), width:120}, {fixed: right, title:操作, toolbar: #movieBar, width:180} ]] }); // 工具欄事件 table.on(tool(movieTable), function(obj){ if(obj.event play){ playVideo(obj.data.videoUrl); } }); }); /script提示page: true表示開啟 Layui 自帶分頁(yè)url必須是完整路徑含 contextPath否則跨上下文請(qǐng)求失敗toolbar: #movieBar對(duì)應(yīng)頁(yè)面底部的script typetext/html idmovieBar.../script模板用于定義“播放”“編輯”等按鈕。3.3 基于 Session 的簡(jiǎn)易權(quán)限控制管理員與普通用戶視圖隔離SSM 項(xiàng)目不引入 Shiro 或 Spring Security而是用最樸素的HttpSession實(shí)現(xiàn)角色分流。登錄成功后將用戶角色存入 session// LoginController.java RequestMapping(/login) public String login(String username, String password, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(user, user); session.setAttribute(role, user.getRole()); // role admin or user return redirect:/index.jsp; } return login; }在 JSP 中用 JSTL 判斷角色并渲染不同內(nèi)容% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:if test${sessionScope.role admin} a classlayui-btn layui-btn-sm href${pageContext.request.contextPath}/movie/add新增影片/a /c:if c:if test${sessionScope.role user} span classlayui-badge普通用戶/span /c:if注意sessionScope是 JSP EL 的內(nèi)置對(duì)象無(wú)需額外導(dǎo)入c:if標(biāo)簽必須在頁(yè)面頂部聲明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %否則會(huì)報(bào)錯(cuò)According to TLD or attribute directive in tag file, attr is not a valid attribute。4. MySQL 影片表設(shè)計(jì)與視頻元數(shù)據(jù)存儲(chǔ)策略4.1 核心表結(jié)構(gòu)兼顧查詢效率與擴(kuò)展性電影在線系統(tǒng)不是博客它的數(shù)據(jù)模型必須支撐“按類型篩選”“按年份排序”“模糊搜索片名”“統(tǒng)計(jì)播放次數(shù)”四大高頻操作。因此movie表不能只存標(biāo)題和 URL還需預(yù)計(jì)算字段與索引策略CREATE TABLE movie ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 片名, director VARCHAR(50) DEFAULT NULL COMMENT 導(dǎo)演, actors TEXT COMMENT 主演逗號(hào)分隔, type VARCHAR(20) NOT NULL COMMENT 類型動(dòng)作/喜劇/科幻, year SMALLINT NOT NULL COMMENT 上映年份, duration INT NOT NULL COMMENT 時(shí)長(zhǎng)分鐘, cover_path VARCHAR(255) NOT NULL COMMENT 封面圖路徑, video_url VARCHAR(255) NOT NULL COMMENT 視頻文件路徑, description TEXT COMMENT 簡(jiǎn)介, play_count INT DEFAULT 0 COMMENT 播放次數(shù), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_type_year (type, year), INDEX idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT影片主表;關(guān)鍵設(shè)計(jì)說(shuō)明cover_path和video_url存儲(chǔ)的是相對(duì)路徑如/upload/cover/avengers.jpg由 Web 服務(wù)器映射到物理目錄idx_type_year是聯(lián)合索引支撐WHERE type動(dòng)作 AND year 2020類查詢idx_title是單列索引配合LIKE %復(fù)仇%實(shí)現(xiàn)模糊搜索雖不如全文索引高效但滿足中小數(shù)據(jù)量play_count字段避免每次播放都查表更新直接UPDATE movie SET play_count play_count 1 WHERE id ?即可。4.2 視頻文件存儲(chǔ)路徑規(guī)范與安全性約束MySQL 不存儲(chǔ)視頻二進(jìn)制數(shù)據(jù)BLOB而是存路徑引用。物理文件必須放在 Web 應(yīng)用可訪問(wèn)的目錄下且需規(guī)避路徑遍歷風(fēng)險(xiǎn)。推薦方案視頻文件統(tǒng)一存放在webapp/upload/video/目錄封面圖存放在webapp/upload/cover/目錄Controller 中校驗(yàn)文件名合法性禁止../、%2e%2e等繞過(guò)private boolean isValidFilename(String filename) { return filename ! null !filename.contains(..) !filename.startsWith(/) filename.matches(^[a-zA-Z0-9_\\-\\.]$); }上傳后生成唯一文件名如UUID.randomUUID().toString().replace(-, ) .mp4再保存到數(shù)據(jù)庫(kù)。這樣既防止覆蓋又杜絕惡意文件名執(zhí)行。4.3 查詢優(yōu)化實(shí)戰(zhàn)如何讓“最新上映”列表 10ms 內(nèi)返回當(dāng)影片數(shù)量超過(guò) 1 萬(wàn)條時(shí)ORDER BY create_time DESC LIMIT 10會(huì)變慢。解決方案不是加索引而是冗余字段 覆蓋索引ALTER TABLE movie ADD COLUMN is_latest TINYINT DEFAULT 0 COMMENT 是否最新上映1是; -- 每次新增影片時(shí)將前 10 條設(shè)為 1其余設(shè)為 0 UPDATE movie SET is_latest 0; UPDATE movie SET is_latest 1 ORDER BY create_time DESC LIMIT 10; -- 創(chuàng)建覆蓋索引 CREATE INDEX idx_latest ON movie(is_latest, create_time, id, title, cover_path);查詢語(yǔ)句改為SELECT id, title, cover_path, create_time FROM movie WHERE is_latest 1 ORDER BY create_time DESC;效果驗(yàn)證執(zhí)行EXPLAIN可見(jiàn)typeref、ExtraUsing index表示走覆蓋索引無(wú)需回表。實(shí)測(cè) 5 萬(wàn)條數(shù)據(jù)下查詢耗時(shí)穩(wěn)定在 3~8ms。5. Maven 構(gòu)建與部署排錯(cuò)從 war 包缺失 class 到 JSP 修改不生效的根因定位5.1 WAR 包中缺少 com.movie.controller 包的三大原因與修復(fù)Maven 打包后發(fā)現(xiàn)WEB-INF/classes/com/movie/controller/目錄為空常見(jiàn)于以下場(chǎng)景現(xiàn)象根因修復(fù)方式mvn clean package后 target/classes 下無(wú) controller 類src/main/java目錄未被 Maven 正確識(shí)別為源碼目錄在 IDE 中右鍵項(xiàng)目 → Maven → Update Project勾選Force Update或檢查.project文件中buildCommandnameorg.eclipse.m2e.core.maven2Builder/name/buildCommand是否存在target/movie.war解壓后WEB-INF/classes/下有 dao、service 但無(wú) controllerpom.xml中maven-compiler-plugin編譯級(jí)別低于 JDK 實(shí)際版本顯式聲明插件并鎖定 source/targetxmlbrpluginbr groupIdorg.apache.maven.plugins/groupIdbr artifactIdmaven-compiler-plugin/artifactIdbr version3.8.1/versionbr configurationbr source1.8/sourcebr target1.8/targetbr /configurationbr/pluginbrController 類存在但DispatcherServlet找不到映射Controller注解類未被 Spring 掃描到檢查spring-mvc.xml中context:component-scan base-packagecom.movie /的base-package是否包含 controller 所在包且不能漏掉子包如com.movie.controller5.2 JSP 修改后頁(yè)面不刷新不是緩存是 Tomcat 的 JSP 編譯機(jī)制JSP 不是純靜態(tài)文件它會(huì)被 Tomcat 編譯為 Servlet存于work/Catalina/localhost/[app]/org/apache/jsp/。修改 JSP 后不生效90% 情況是Tomcat 未檢測(cè)到文件變更在conf/context.xml中添加reloadabletrue屬性JSP 編譯緩存未清除刪除work/Catalina/localhost/[app]/下全部?jī)?nèi)容IDE 自動(dòng)部署未觸發(fā)Eclipse 中需勾選Project → Properties → MyEclipse → Web → Deploy → Auto Deploy on SaveIntelliJ IDEA 中需在Build → Build Artifacts → Rebuild后點(diǎn)擊Redeploy。最徹底的驗(yàn)證方式打開work目錄下的.java文件如index_jsp.java搜索out.print確認(rèn)輸出內(nèi)容是否與最新 JSP 一致。5.3 MySQL 8.0 連接失敗Public Key Retrieval is not allowed的本質(zhì)與解法使用 MySQL Connector/J 8.0 連接時(shí)拋出SQLException: Public Key Retrieval is not allowed根本原因是 MySQL 8.0 默認(rèn)啟用caching_sha2_password認(rèn)證插件而舊版驅(qū)動(dòng)要求顯式授權(quán)公鑰獲取。兩種解法方案一推薦修改用戶認(rèn)證方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;方案二臨時(shí)URL 中添加參數(shù)jdbc:mysql://localhost:3306/movie_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse注意allowPublicKeyRetrievaltrue僅用于開發(fā)環(huán)境生產(chǎn)環(huán)境必須用方案一否則存在中間人攻擊風(fēng)險(xiǎn)。6. Layui 日期控件與 JSP 動(dòng)態(tài)賦值的精準(zhǔn)協(xié)同技巧6.1 Layui date 組件設(shè)置最大日期為當(dāng)前日期的兩種寫法Layui 的laydate.render()默認(rèn)不限制日期范圍但電影系統(tǒng)中“上映年份”字段需限制為 ≤ 當(dāng)前年份。不能寫死m(xù)ax: 2024-01-01而應(yīng)動(dòng)態(tài)生成方法一JSP 中生成 JS 變量script var today new Date(); var maxDate (today.getFullYear()) - (today.getMonth() 1).toString().padStart(2, 0) - today.getDate().toString().padStart(2, 0); /script然后在laydate.render()中引用laydate.render({ elem: #releaseYear, type: year, max: maxDate.split(-)[0] // 只取年份 });方法二后端傳參更安全// Controller model.addAttribute(currentYear, Calendar.getInstance().get(Calendar.YEAR));JSP 中直接使用input typetext idreleaseYear lay-keyyear / script laydate.render({ elem: #releaseYear, type: year, max: ${currentYear} }); /script優(yōu)勢(shì)對(duì)比方法二避免 JS 日期格式化誤差且不受客戶端時(shí)區(qū)影響方法一適合純前端場(chǎng)景但需注意new Date()獲取的是瀏覽器本地時(shí)間。6.2 Layui table 單列綁定點(diǎn)擊事件的 DOM 代理寫法Layui table 的event機(jī)制本質(zhì)是事件委托但初學(xué)者常誤以為tool事件只能用于工具欄按鈕。其實(shí)可通過(guò)cell事件監(jiān)聽任意列table.on(cell(movieTable), function(obj){ if(obj.field title){ // 點(diǎn)擊片名列 layer.msg(你點(diǎn)擊了 obj.value); // 此處可跳轉(zhuǎn)詳情頁(yè)或彈窗 window.location.href ${pageContext.request.contextPath}/movie/detail?id obj.data.id; } });關(guān)鍵點(diǎn)obj.field是列的field屬性值如titleobj.data是整行數(shù)據(jù)對(duì)象obj.value是當(dāng)前單元格值。此寫法比給每行加a onclick...更輕量且支持虛擬滾動(dòng)。6.3 JSP 中 JSONArray 的正確導(dǎo)入與遍歷避免import javax.json.*錯(cuò)誤網(wǎng)絡(luò)熱詞中頻繁出現(xiàn)jsp jsonarray import但 JSP 本身不支持javax.jsonJava EE 7 特性Tomcat 8.5 才內(nèi)置。正確做法是后端組裝 JSON 字符串用com.alibaba.fastjson.JSON或org.json.JSONObject生成字符串傳入 requestJSP 中用 JSTL 遍歷若數(shù)據(jù)已是ListMapString, Object直接用c:forEach// Controller ListMapString, Object tags new ArrayList(); MapString, Object tag new HashMap(); tag.put(name, 科幻); tag.put(count, 12); tags.add(tag); request.setAttribute(tags, tags);c:forEach items${tags} vartag span classlayui-badge${tag.name}(${tag.count})/span /c:forEach嚴(yán)禁寫法% page importjavax.json.JsonArray %—— Tomcat 未提供該類編譯即報(bào)錯(cuò)。JSON 處理必須在后端完成JSP 只負(fù)責(zé)展示。本文還有配套的精品資源點(diǎn)擊獲取