简介:一套基于SSM框架与JSP技术的宠物医院信息管理系统源码,内含MySQL数据库脚本,并附带毕业论文与答辩PPT。项目面向计算机、数学、电子信息等专业学生,适合作为课程设计、期末大作业或毕业设计参考资料,也适合需要快速搭建完整Web系统的开发者学习借鉴。资源包共1347个文件、约21.45MB,主体为Java后端源码、JSP页面、CSS/JS前端资源、SQL数据库脚本与项目配置文档;系统基于经典SSM三层架构,业务逻辑与数据访问分层清晰,代码经过严格调试,无明显bug,下载后可直接部署运行。覆盖患者信息管理、预约挂号、药品库存、财务报表等典型模块,可帮助理解框架整合、MVC分层结构与MySQL数据交互。目前已有103人学习浏览,附带的论文与PPT也可帮助从业务功能到技术实现形成完整认知;在掌握现有代码后,还可扩展在线支付、健康咨询等更多功能。
1. 一套 SSM+JSP 宠物医院管理系统,毕设选题为什么绕不开它
又到毕业设计启动季,Java 方向的学生打开选题列表,大概率会撞见“基于 SSM 的某某信息管理系统”这个句式。宠物医院信息管理系统就是这类题目的典型变体:业务边界清晰(预约、接诊、药品、收费)、角色分明(前台、医生、管理员)、数据表数量适中,既不会简单到撑不起论文篇幅,也不会复杂到一个人做不完。它被选中的频率高,不是因为技术新,而是因为 SSM+JSP 这条技术栈在高校课程里覆盖面最广,Spring 管对象、Spring MVC 管请求、MyBatis 管数据库、JSP 管页面渲染,每一层都能在论文里写出独立的“关键技术”章节。
但对已经工作几年的人来说,这套组合并不陌生——大量中小型企业的存量 JavaWeb 系统至今仍跑在 SSM+JSP 上。理解它怎么组织、怎么调试,等于拿到了接手旧系统的基础能力。这篇文章不假设你手上已经有了某个具体源码包,而是顺着“SSM+JSP 宠物医院管理系统”这个标题,把一套可落地的实现路径讲清楚:从工程结构、数据库设计、核心查询到底层排错,顺便说清楚毕业论文和答辩 PPT 里哪些素材可以直接从代码里提炼。
2. SSM+JSP 项目的地基:三层架构与工程初始化
2.1 Spring、Spring MVC、MyBatis 各管哪一段
SSM 不是三个框架的简单堆叠,而是各管一段:Spring 容器负责 Service 层和 DAO 层的 Bean 管理、事务控制;Spring MVC 负责前端请求的接收与分发,Controller 里写接口逻辑;MyBatis 负责 SQL 与 Java 对象的映射,把数据库操作收敛到 Mapper 接口和 XML 文件里。JSP 在这里扮演的是视图层角色,直接放在 WEB-INF 目录下,由 InternalResourceViewResolver 转发渲染。
我见过不少项目把业务逻辑写在 Controller 里、SQL 拼在 Service 里,短期内能跑,但论文的“系统设计”章节会很难写——因为分层不清晰,ER 图、流程图、架构图都对应不上代码。正确做法是严格按 Controller → Service → Mapper 三层调用链写,每个角色对应一个业务包,后面无论是写论文还是给系统加功能,路径都会清晰很多。
2.2 工程目录结构与 Maven 依赖的合理配置
一个标准的 SSM 单体项目,无论是不是宠物医院系统,包结构都长这样:
src/main/java ├── com.pethospital.controller # 前后台 Controller ├── com.pethospital.service # 业务接口 ├── com.pethospital.service.impl # 业务实现,加 @Service ├── com.pethospital.dao # MyBatis Mapper 接口 ├── com.pethospital.entity # 实体类,对应数据库表 └── com.pethospital.common # 分页、常量、工具类 src/main/resources ├── jdbc.properties # 数据库连接配置 ├── spring-mvc.xml # 控制器与视图解析器配置 ├── spring-mybatis.xml # 数据源与事务配置 └── mybatis-config.xml # MyBatis 全局配置 src/main/webapp/WEB-INF ├── web.xml └── views # JSP 全部放这里,不能直接 URL 访问pom.xml 里的关键依赖,按照 SSM 项目最常见的组合写是这样:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.4</version> </dependency> </dependencies>版本选择上,Spring 5.3.x 配合 Tomcat 8.5/9.0 是最稳的搭配,MyBatis 3.5.x 支持 JDK 8 到 17。MySQL 驱动用 5.1.49 是因为市面毕设大多连 MySQL 5.7,如果你本机是 MySQL 8.x,需要换成com.mysql.cj.jdbc.Driver驱动和8.0.x版本,否则启动时会报驱动类找不到。JSTL 依赖必须有,不然 JSP 页面里的<c:forEach>标签全部失效。
2.3 web.xml 与 spring-mvc.xml:请求是怎么走到 JSP 的
Spring MVC 的入口是 DispatcherServlet,它需要在 web.xml 里注册,并配置拦截路径和编码过滤器:
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>注意<url-pattern>/</url-pattern>表示除 JSP 外的所有请求都交给 Spring MVC 处理,JSP 文件的请求仍然由 Tomcat 默认的 JspServlet 处理。这里最容易踩的坑是把 url-pattern 配成/*,会导致 JSP 页面也被 DispatcherServlet 拦截,结果所有页面都变成 404 或者输出乱码。
spring-mvc.xml 里需要开启注解驱动和视图解析器:
<context:component-scan base-package="com.pethospital.controller"/> <mvc:annotation-driven/> <mvc:default-servlet-handler/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5242880"/> <property name="defaultEncoding" value="UTF-8"/> </bean>组件扫描只扫 controller 包,Service 和 DAO 的 Bean 交给 spring-mybatis.xml 管理,这是 SSM 项目两个 Spring 配置文件分开的常见原因。视图解析器把 Controller 返回的字符串"pet/list"拼成/WEB-INF/views/pet/list.jsp,JSP 放在 WEB-INF 下的好处是用户无法通过浏览器地址栏直接访问页面文件,必须经过 Controller 转发,这样权限控制才有意义。
2.4 环境版本选择:JDK、Tomcat、MySQL 怎么配最稳
下表是我在多个类似项目里验证过、也建议毕设同学直接照抄的版本组合:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 学校机器和云服务器都兼容,避免 JDK 17 的模块化报错 |
| Tomcat | 8.5.x 或 9.0.x | 与 Servlet 3.x/4.x 兼容,Spring 5 官方支持的容器 |
| MySQL | 5.7.x | 5.1.49 驱动直接可用,不用处理时区问题 |
| Maven | 3.6.3 以上 | 太低版本拉不全依赖,太高版本可能和旧插件冲突 |
提示:JDK 版本和 Tomcat 版本必须匹配。Tomcat 9 要求 JDK 8+,Tomcat 10 把
javax.servlet改成了jakarta.servlet,如果你的 SSM 项目代码里全是javax.servlet.http.HttpServletRequest,就不要用 Tomcat 10。
这套环境配置好之后,项目启动起来就能看到宠物医院系统的登录页。但环境和工程只是地基,真正的业务逻辑——宠物档案、挂号接诊、药品库存——要靠数据库设计和 MyBatis 的 SQL 来支撑。这也恰好是毕业论文最看重的部分。
3. 宠物医院的核心业务:从表设计到多条件查询
3.1 功能模块划分:别把系统做成单纯的 CRUD
宠物医院信息管理系统常见模块有六个:系统管理(员工账号与角色)、宠物档案管理、预约挂号管理、接诊与病历管理、药品与库存管理、收费统计管理。毕设版本的“信息管理系统”容易被做成每个模块一个增删改查页面,这在答辩时会被老师追问“你系统解决了什么实际问题”。正确做法是让业务闭环——前台登记宠物档案,顾客预约挂号,医生接诊并填写病历,诊断后开药,收费时关联病历和药品。
我一般建议把“挂号—接诊—病历—收费”这条链作为核心流程来设计表结构,宠物档案作为基础数据,员工表区分管理员、前台、医生三种角色。这样一来,ER 图、数据流图、时序图都能从这套业务里画出来,论文的第三章和第四章自然就有内容可写。
3.2 核心表结构:pet_info 与 medical_record
宠物档案表是系统的基础表,设计要覆盖宠物基本信息与主人联系方式:
CREATE TABLE pet_info ( pet_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '宠物ID', pet_name VARCHAR(30) NOT NULL COMMENT '宠物昵称', pet_type VARCHAR(20) NOT NULL COMMENT '宠物种类:狗/猫/鸟等', breed VARCHAR(30) DEFAULT NULL COMMENT '品种', pet_age INT DEFAULT NULL COMMENT '年龄(月)', owner_name VARCHAR(30) NOT NULL COMMENT '主人姓名', owner_phone VARCHAR(20) NOT NULL COMMENT '联系电话', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建档时间', INDEX idx_owner_phone (owner_phone), INDEX idx_pet_type (pet_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物档案表';病历表是整个系统里最重要的业务表,关联宠物与接诊医生,核心是一次接诊一条记录:
CREATE TABLE medical_record ( record_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '病历ID', pet_id INT NOT NULL COMMENT '宠物ID,关联pet_info', vet_id INT NOT NULL COMMENT '接诊医生ID,关联employee', visit_date DATETIME NOT NULL COMMENT '接诊时间', symptom_desc VARCHAR(500) DEFAULT NULL COMMENT '症状描述', diagnosis VARCHAR(500) DEFAULT NULL COMMENT '诊断结果', treatment VARCHAR(500) DEFAULT NULL COMMENT '治疗方案', prescription VARCHAR(500) DEFAULT NULL COMMENT '医嘱/处方', status TINYINT DEFAULT 0 COMMENT '状态:0就诊中/1已完成', CONSTRAINT fk_record_pet FOREIGN KEY (pet_id) REFERENCES pet_info(pet_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接诊病历表';字段类型上有两个细节。所有VARCHAR都显式指定长度,MyBatis 自动映射时对字符串类型没有歧义;手机号字段加普通索引而不是唯一索引,因为同一个主人可能带多只宠物来看病,重复手机号是正常业务。主键统一用INT AUTO_INCREMENT,毕设规模用不到分布式 ID,保持简单反而让 SQL 更好写。
3.3 MyBatis 动态 SQL:宠物档案多条件查询怎么写才不卡壳
列表页最常见的需求是组合查询:输入宠物名模糊匹配、选择宠物类型下拉、输入主人手机号精确匹配。如果每个条件都用if在 Service 层拼 SQL,代码会脏得没法看。MyBatis 的<where>动态标签能解决这个问题:
<select id="queryPetList" parameterType="map" resultType="com.pethospital.entity.PetInfo"> SELECT pet_id, pet_name, pet_type, breed, pet_age, owner_name, owner_phone, create_time FROM pet_info <where> <if test="petName != null and petName != ''"> AND pet_name LIKE CONCAT('%', #{petName}, '%') </if> <if test="petType != null and petType != ''"> AND pet_type = #{petType} </if> <if test="ownerPhone != null and ownerPhone != ''"> AND owner_phone = #{ownerPhone} </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select><where>标签会自动处理首个子句前的AND,条件都不传时生成不带 WHERE 的查询语句。LIMIT #{offset}, #{pageSize}是实现分页的常见方式,offset在 Service 层计算为(currentPage - 1) * pageSize。这里有个容易忽略的坑:LIKE拼接用CONCAT('%', #{petName}, '%')而不是直接在 SQL 里写'%${petName}%',后者存在 SQL 注入风险,论文里如果写了“防止 SQL 注入”而你代码用的是${},答辩时会被一眼看穿。
MyBatis 的全局配置里,我建议打开下划线转驼峰映射:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>这样数据库字段owner_name会自动映射到实体类的ownerName属性,不用在 resultMap 里逐个写映射关系。这行配置放在 mybatis-config.xml 的<settings>节点里,也是论文“查询优化”小节能写的一个点。
3.4 Service 事务边界:为什么挂号接诊必须加 @Transactional
实体字段和查询都准备好了,接下来看业务层。药品出库、接诊写病历这类操作涉及多张表的状态变化,必须保证原子性,否则处方写进数据库而库存没有扣减,账面就乱了。
@Override @Transactional(rollbackFor = Exception.class) public boolean finishConsult(MedicalRecord record, Integer drugId, Integer quantity) { // 1. 写入病历记录 int recordRows = medicalRecordDao.insert(record); // 2. 扣减药品库存 int stockRows = drugDao.deductStock(drugId, quantity); // 3. 更新接诊状态 int updateRows = appointmentDao.updateStatus(record.getAppointmentId(), 1); return recordRows > 0 && stockRows > 0 && updateRows > 0; }@Transactional(rollbackFor = Exception.class)指明任何 Exception 及其子类都触发回滚,这是和只用默认配置的 Spring 声明式事务的关键差异——默认配置下只会回滚 RuntimeException,而 SQL 写入失败抛出的常常是受检异常。事务生效需要<tx:annotation-driven transaction-manager="transactionManager"/>配置在 spring-mybatis.xml 里,很多项目漏掉这行导致事务注解不生效,全部操作变成自动提交,但项目不报错,只有对账时才发现数据不一致。
4. JSP 页面联调:分页渲染、路径冲突与文件上传参数
4.1 JSTL+EL 渲染分页列表,JSP 里的三层循环
Controller 处理完查询后,把分页对象放进 ModelAndView,JSP 通过 EL 表达式和 JSTL 标签遍历输出。以一个“宠物档案列表”页面为例:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <table class="table table-bordered"> <thead> <tr> <th>宠物编号</th> <th>宠物昵称</th> <th>种类</th> <th>主人</th> <th>联系电话</th> <th>建档时间</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${page.list}" var="pet"> <tr> <td>${pet.petId}</td> <td>${pet.petName}</td> <td>${pet.petType}</td> <td>${pet.ownerName}</td> <td>${pet.ownerPhone}</td> <td><fmt:formatDate value="${pet.createTime}" pattern="yyyy-MM-dd HH:mm"/></td> <td> <a href="${pageContext.request.contextPath}/pet/detail?id=${pet.petId}">查看</a> <a href="${pageContext.request.contextPath}/pet/edit?id=${pet.petId}">编辑</a> </td> </tr> </c:forEach> </tbody> </table>${page.list}里的page是分页对象,包含list、pageNum、pageSize、total、pages属性,这部分由 Service 层封装。JSP 里必须用${pageContext.request.contextPath}拼接项目根路径,否则部署到带 context-path 的环境时路径全部 404。时间格式化用<fmt:formatDate>而不是在实体类里拼字符串,这样数据格式和页面展示解耦。
分页导航的常见写法是生成首页、上一页、页码列表、下一页、末页:
<div class="pagination"> <a href="${pageContext.request.contextPath}/pet/list?pageNum=1">首页</a> <c:if test="${page.pageNum > 1}"> <a href="${pageContext.request.contextPath}/pet/list?pageNum=${page.pageNum - 1}">上一页</a> </c:if> <c:forEach begin="1" end="${page.pages}" var="i"> <c:choose> <c:when test="${i == page.pageNum}"> <span class="current">${i}</span> </c:when> <c:otherwise> <a href="${pageContext.request.contextPath}/pet/list?pageNum=${i}">${i}</a> </c:otherwise> </c:choose> </c:forEach> <c:if test="${page.pageNum < page.pages}"> <a href="${pageContext.request.contextPath}/pet/list?pageNum=${page.pageNum + 1}">下一页</a> </c:if> <a href="${pageContext.request.contextPath}/pet/list?pageNum=${page.pages}">末页</a> </div>注意页码循环用begin="1" end="${page.pages}",如果数据只有 1 页,这段代码不会报错,但如果pages是 0(空列表时某些分页组件的默认值),c:forEach会直接跳过循环体。要是列表页空数据显示异常,先看 Service 层是否把pages至少置为 1。
4.2 basePath 标签:彻底根治 JSP 相对路径 404
JSP 页面里资源引用(CSS、JS、图片)如果写相对路径,在多层 URL 下容易出错,比如当前 URL 是/pet/list而页面里有css/style.css,浏览器会请求/pet/css/style.css,直接 404。常见做法是在每个 JSP 头部统一声明 basePath:
<% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <base href="<%=basePath%>"><base>标签设置后,页面里所有相对路径都会基于它解析。request.getScheme()返回http,getServerName()返回主机名,getServerPort()返回端口号,加上getContextPath()拼出完整的根路径。这段脚本是 JSP 页面里少有的合法 Java 代码片段,在毕设项目里几乎每页都有。
4.3 文件上传的三个必调参数与常见坑
宠物医院系统里上传宠物照片、化验单图片是常见功能,Spring MVC 的 multipart 解析器参数要和表单字段对应好:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5242880"/> <property name="maxInMemorySize" value="10240"/> <property name="defaultEncoding" value="UTF-8"/> </bean>| 参数 | 设置值 | 作用 |
|---|---|---|
| maxUploadSize | 5242880 | 单次请求上传总大小上限,单位字节,5MB 对照片足够 |
| maxInMemorySize | 10240 | 文件在内存中的阈值,超过则落临时文件 |
| defaultEncoding | UTF-8 | 上传文件名编码,不设中文文件名会乱码 |
Controller 接收文件时用MultipartFile:
@RequestMapping("/upload") public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { String realPath = request.getServletContext().getRealPath("/upload"); File dir = new File(realPath); if (!dir.exists()) { dir.mkdirs(); } String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename(); file.transferTo(new File(dir, fileName)); return "redirect:/pet/edit/" + fileName; }getRealPath("/upload")返回的是 Tomcat 部署目录下的物理路径,文件写上去了但下一次redeploy会被清空,这是毕设项目里常见的坑。如果文件不想因重新部署丢失,把路径改成写死的磁盘目录(如D:/pethospital/upload),然后在 Nginx 或 Tomcat 里做虚拟目录映射。表单项里enctype必须设成multipart/form-data,否则文件数据到不了后端,这个错误前端页面不报语法问题,只是 Controller 里MultipartFile为空。
4.4 局部刷新:JSP 页面里嵌 JSON 接口的合理姿势
JSP 项目不需要全站前后端分离,但“查询医生排班”“挂号时校验手机号是否已建档”这类操作值得用局部刷新实现。Controller 里返回 JSON 数据而不是完整页面:
@RequestMapping("/doctor/listByDept") @ResponseBody public Map<String, Object> listByDept(Integer deptId) { List<Doctor> doctors = doctorService.findByDept(deptId); Map<String, Object> result = new HashMap<>(); result.put("code", 0); result.put("data", doctors); return result; }@ResponseBody配合 jackson-databind 依赖,能把 Controller 返回值直接序列化成 JSON 字符串。前端用 jQuery 的$.ajax或原生fetch调用:
$.ajax({ url: contextPath + "/doctor/listByDept", data: {deptId: $("#deptId").val()}, dataType: "json", success: function (res) { if (res.code === 0) { var options = ""; $.each(res.data, function (i, doctor) { options += "<option value='" + doctor.id + "'>" + doctor.doctorName + "</option>"; }); $("#doctorSelect").html(options); } } });注意这个接口返回的是 JSON 而不是 JSP 页面,所以在 web.xml 的 DispatcherServlet 配置中,<url-pattern>/</url-pattern>刚好能匹配这类/doctor/listByDept请求。如果页面里的下拉框不刷新数据,打开浏览器开发者工具看 Network 面板,确认请求是否被拦截、返回的是 HTML 还是 JSON。
5. 从源码到毕业论文:素材提炼与答辩前的验证方法
5.1 论文结构如何对应当前代码模块
拿到源码后,论文各章节可以从具体代码里直接提炼素材。常见的对应方式是把“系统设计”章节的 E-R 图换成 pet_info、medical_record 等实体关系,“系统实现”章节按 SSM 三层逐一展示 Controller、Service、Mapper 的关键代码片段,同时用表格整理核心接口与测试用例。
一张速查表能加快这个整理过程:
| 论文章节 | 从源码中提取的素材 |
|---|---|
| 系统需求分析 | 用例图、角色权限(管理员/前台/医生) |
| 数据库设计 | pet_info、medical_record、drug_info 表结构 |
| 系统设计 | SSM 三层架构图、流程图、时序图 |
| 系统实现 | 登录拦截器、分页查询、病历 CRUD |
| 系统测试 | 边界输入(空条件查询、超长文本)、权限越权访问 |
5.2 答辩前按清单验证,避免现场演示翻车
演示环节建议按固定的业务顺序走,抓主要流程,不演示侥幸功能:
| 检查项 | 操作 | 预期结果 |
|---|---|---|
| 数据库初始化 | 运行 init.sql | 5 张核心表创建成功并有测试数据 |
| 登录 | 管理员账号登录 | 跳转到系统首页而非停留在登录页 |
| 宠物档案管理 | 新增宠物、列表查询 | 数据插入正确,多条件查询返回匹配行 |
| 挂号接诊 | 新建挂号,医生接诊 | 病历生成,关联宠物 ID 正确 |
| 药品库存 | 接诊开药后查库存 | 库存数量减少,事务生效 |
注意提前把项目重新 clean、install 一次,确认环境变量配置正确,因为很多现场演示问题都出在target目录残留旧 class 文件上。
5.3 调试技巧:Tomcat work 目录里查看 JSP 编译后的 Java 类
JSP 页面报错时的提示往往不够直接,比如只有 500 状态码没有具体行号。JSP 本质上会被容器编译成 Servlet:JSP 首次访问时被 Jasper 编译成一个 Java 类并保存到 Tomcat 的 work 目录里。在 Tomcat 的work/Catalina/localhost/项目名/org/apache/jsp下,能找到每个 JSP 页面对应的.java和.class文件。
C:/apache-tomcat-9.0.85/work/Catalina/localhost/pethospital/org/apache/jsp打开login_jsp.java,能看到_jspService(HttpServletRequest request, HttpServletResponse response)方法是页面渲染的入口,JSP 里的 HTML 被写成out.write(...),<c:forEach>被翻译成forEach循环。JSP 写 Java 代码的脚本片段<% %>也会原样出现在这个文件里。因此排查 JSP 假死、个别变量渲染为空这类问题时,查这个编译产物往往比猜标签写法更快。
在演示完功能后,顺带提一句“我在排错时如何利用 Tomcat 的 JSP 编译产物定位问题”,也容易在答辩加一点印象分。说到底,SSM+JSP 这套技术栈能不能在简历上站住脚,不取决于框架新旧,取决于能否把一条业务链路从头跑到尾,并说清楚每一步为什么这么设计。
本文还有配套的精品资源,点击获取