SSM框架整合项目实战:乡村小学校园官网从设计到部署全解析
1. 项目思路拆解从“课程设计交差”到“能拿得出手的SSM整合案例”期末课程设计选题那会儿宿舍里四个人三个都选了商城系统、后台管理这种烂大街的题目。我当时想着既然要花两周时间做一个Java项目不如选一个自己真正有表达欲的场景。乡村小学校园官网这个题目有几个天然优势第一功能边界清晰不需要像电商系统那样处理订单、支付、库存这些复杂业务流第二信息展示类网站非常适合SSM框架来练手Controller-Service-Mapper三层各司其职逻辑不绕第三做出来之后是真的能部署给乡镇学校用的不是那种只躺在课程设计报告里的“哑项目”。Ssm项目源码的典型问题在于很多同学拿到的源码要么是Eclipse老版本要么依赖包冲突要么数据库脚本缺字段导进Idea里直接红一片。我今天分享的这个项目案例是严格按照IdeaMavenJDK1.8Tomcat8.5MySQL5.7这套主流环境整理的SSM版本用的是Spring 5.1.x SpringMVC 5.1.x MyBatis 3.5.x算是当前课程设计和毕业设计里兼容性最好的一套组合。先说这个项目整体分两大块前台展示和后台管理。前台面向访客展示学校概况、校园新闻、教师队伍、教学资源、校园风采图片、联系方式后台面向管理员用于登录验证、新闻发布、图片上传、教师信息维护、公告管理。整套东西跑起来之后就是一个完整的“信息发布型中小企业官网”的mini版你把这个项目吃透以后做企业官网、政府门户、学校CMS全都能复用同一套思路。项目源码的核心价值不在于代码本身多高级而在于它能帮你把SSM框架的知识点串成一条线。很多初学者背了一堆Spring的IOC、AOP概念但是真正打开一个项目后连web.xml里的监听器配置都看不懂。这个项目里每一处配置我都做了注释同时这篇文章里我会把核心的配置逻辑、踩坑点、面试官爱问的问题全部讲透建议你边读边打开源码对照着看。2. 技术栈选型解析为什么是这个组合换掉哪一环会踩坑2.1 SSM框架组合的内在逻辑Spring、SpringMVC、MyBatis这三个框架的组合本质上是把Java Web开发分成了三个清晰的层次。Spring管对象负责把所有Service、Mapper这些bean的创建和依赖关系管理起来你不需要到处new对象SpringMVC管请求浏览器发来的URL请求由DispatcherServlet统一分发转发给对应的Controller方法MyBatis管数据库它把你写的SQL语句和Java方法对应起来查询结果自动映射成List、Map或者实体对象。我自己带过的课程设计小组里很多同学卡在“为什么要用三层”这个问题上。其实回答特别简单为了不让你改需求时候想砸电脑。比如官网的“学校简介”这个功能你可能今天想在页面上加一个校长致辞明天想改简介排版。如果前台页面直接连数据库那每次改动都要去翻JSP里的脚本代码改一处崩三处而SSM把“取数据的逻辑”和“页面显示的逻辑”拆开了Controller只负责把数据塞给页面页面只负责显示数据改前端不会动后台改后台也不会影响页面。2.2 JDK版本与依赖版本的对齐问题这个项目里最容易被忽略的就是JDK版本和编译级别不一致。很多人从网上下载的SSM源码打开后报“无效的源发行版”错误本质上就是Idea里Project Structure配置的JDK版本和pom.xml里maven.compiler.source/target指定版本对不上。这里我给出一个在实际项目里验证过多次的稳定配法JDK用1.8因为在JDK8下Spring 5.x、Tomcat 8.5、MyBatis 3.5.x兼容性最好JDK11以上Spring 5.x也能跑但是Tomcat要升级到9或10否则servlet-api会对不上JDK17则需要Spring 6和对应框架那就不是SSM课程设计的常规范畴了。所以如果你只是想老老实实把课程设计跑通JDK8是最省心的没有之一。pom.xml中几个核心依赖的版本号直接抄作业可以参考下面这个组合。properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.1.9.RELEASE/spring.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.4/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.4/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.1.11/version /dependency /dependencies2.3 开发工具选择Idea社区版还是旗舰版顺带说一句IDE的问题。这个项目是基于IntelliJ IDEA导入运行的社区版也能完全胜任SSM项目的开发、运行和调试。网上很多人纠结社区版没有Tomcat集成启动按钮其实这个问题根本不存在——你只需要在Run Configuration里配置一个Tomcat Server本地端口社区版照样能跑Web项目。如果你还在用Eclipse我的建议是有时间尽量迁到Idea上来。Idea对Maven工程的支持、对XML配置的自动提示补全、断点调试的体验比Eclipse好太多了。尤其这个项目里涉及多份Spring配置文件的切换查看Idea的Structure视图能让你一眼定位到某个bean的定义位置排错效率高很多。不过这里我要提醒一个刚换Idea的细节默认情况下Idea不会自动把resources目录下的XML文件编译到target/classes里。如果你打开target目录发现里面只有.class文件而没有mapperXML文件一定记得检查Project Structure里的Resources标签是否把src/main/resources标记成了资源根目录。3. 数据库设计五张表撑起一个校园官网3.1 表结构设计的“最小可用”原则这个项目在数据库层面并没有搞复杂的存储过程、触发器就是老老实实的五张标准表管理员表、学校新闻表、教师信息表、校园图片表和网站公告表。为什么这么设计一句话信息展示类网站的表结构永远比业务系统简单但表之间的关联关系必须有否则就失去了练习的意义。我在设计时遵循了“最小可用”原则先想清楚页面要展示什么字段再反推数据库需要什么字段。比如前台首页要展示“最新三条新闻标题”那我就给新闻表设置id、title、content、author、publish_time、view_count、top_flag这几个字段后台要支持新闻置顶所以设计了一个top_flag字段通过ORDER BY top_flag DESC, publish_time DESC来控制排序。3.2 建表SQL中的关键细节以新闻表和图片表为例建表语句有几个容易踩坑的地方需要重点说明。CREATE TABLE t_school_news ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 新闻ID, title VARCHAR(200) NOT NULL COMMENT 新闻标题, content TEXT COMMENT 新闻正文, author VARCHAR(50) DEFAULT admin COMMENT 发布人, cover_image VARCHAR(255) COMMENT 封面图URL, publish_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 发布时间, view_count INT DEFAULT 0 COMMENT 浏览量, top_flag TINYINT DEFAULT 0 COMMENT 是否置顶0否1是, category VARCHAR(20) DEFAULT 校园新闻 COMMENT 新闻分类 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT校园新闻表; CREATE TABLE t_campus_photo ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 图片ID, photo_url VARCHAR(255) NOT NULL COMMENT 图片存储路径, photo_title VARCHAR(100) COMMENT 图片标题, photo_desc VARCHAR(255) COMMENT 图片描述, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, album_id INT COMMENT 所属相册ID ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT校园风采图片表;第一字符集一定要用utf8mb4而不是utf8。utf8mb4是utf8的超集能存emoji表情和一些生僻字乡村学校官网可能会发布包含少数民族语言文字的内容如果用utf8会导致某些字符写入报错。第二发布时间的默认值设置成CURRENT_TIMESTAMP这样后台添加新闻时少写一个参数代码也更简洁。第三内容字段用TEXT而不是VARCHAR因为新闻正文往往超过255个字符VARCHAR会截断。3.3 数据库初始化脚本的使用方式源码包里通常附带一个sql文件我的习惯是命名成db_village_school.sql。打开Navicat或者命令行新建数据库school_website然后运行脚本即可。这里有个细节如果你是用命令行source导入一定要先执行use school_website否则表会建到默认库里。数据库连接信息在jdbc.properties里修改这个文件是SSM项目数据库层配置的总入口。jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/school_website?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456特别注意url中的characterEncodingutf-8这个参数必须带上否则页面和数据库之间传中文会出现乱码。另外useSSLfalse也很重要MySQL5.7版本默认开启了SSL握手如果你本地连数据库报SSL相关的warning加这个参数直接跳过。至于serverTimezoneMySQL8.0以下版本不写也能跑但MySQL8.0必须指定这个参数否则会报时区错误。我见过太多人卡在这个时区问题上建议直接把这个配置当成标配写上。4. 项目结构和核心配置逐文件拆解4.1 Config目录下的“三件套”配置思路SSM项目在Java源码的config包下通常有三个配置文件分别负责不同的层spring-context.xml管Service和Mapperspring-mvc.xml只管Controller和视图解析mybatis-config.xml管MyBatis的全局属性。这种拆分的核心逻辑是让每个配置文件职责单一避免像早期SSH项目那样一个applicationContext.xml写到上千行。为什么要单独拆出来一个spring-mvc.xml因为SpringMVC容器和Spring容器是父子容器关系。子容器里的Controller可以使用父容器里的Service但父容器不能反向引用子容器的东西。如果你把Controller和Service全部放进spring-context.xml在部分高级场景下会引发事务切面不生效的问题。所以正规的SSM项目一定是spring-mvc.xml扫描controller包spring-context.xml扫描service和mapper包。这里有同学会问那Idea怎么区分两个配置文件的加载顺序答案是通过web.xml。你必须在web.xml的context-param里指定spring-context.xml用来初始化Spring根容器再在DispatcherServlet的init-param里指定spring-mvc.xml用来初始化Web层的子容器。这样Tomcat一启动两个容器各自加载自己该管的bean。4.2 web.xml配置的必备理解web.xml是Java Web应用的部署描述文件。很多初学者直接复制别人的web.xml连里面每个标签的含义都不清楚导致出现问题根本不会排查。这个项目里的web.xml有四个核心节点必须懂CharacterEncodingFilter是字符编码过滤器强制所有请求和响应都使用UTF-8这一层只拦截Servlet请求不拦截静态资源DispatcherServlet是核心前端控制器它接管所有符合url-pattern的请求这个项目里配置成斜杠意思是所有请求都先进入SpringMVC由Controller决定谁处理ContextLoaderListener是Spring根容器的启动监听器Tomcat启动时候它读取context-param指定的spring-context.xml此外还需要一个显式的Servlet和Filter的映射声明这里因为目前我们使用了Servlet 3.0注解扫描的特性也可以省略一些配置但为了兼容性我还是会写全。很多网上下载的SSM源码直接访问JSP页面没问题但是一访问Controller路径就404大概率就是DispatcherServlet的url-pattern配置问题。这个项目统一配置成‘/’配合spring-mvc.xml里的静态资源放行配置既能走Controller又能加载CSS、JS、图片。这个方案也是目前SSM项目里最标准的写法。4.3 SpringMVC的静态资源处理既然上面提到静态资源放行这里详细说一下配置方式。当DispatcherServlet拦截所有请求后如果不做特殊处理浏览器请求css/style.css也会被当成Controller路径去找结果当然找不到所以页面样式全丢。解决办法是在spring-mvc.xml中加上这一段配置。mvc:resources mapping/static/** location/static// mvc:annotation-driven/mapping标签指的是URL访问路径location指的是文件在项目中的实际存放目录。也就是说凡是浏览器路径里带/static/的请求直接去项目的webapp/static目录下找对应资源不过Controller。mvc:annotation-driven必须带上它负责注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter没有它Controller上的RequestMapping注解不生效页面会全部404。5. 核心功能模块的实现细节5.1 新闻模块列表分页与详情页跳转的传参技巧新闻展示是SSM项目中最典型的列表详情模式理解了它就等于理解了市面上80%的CRUD操作。前台列表页需要展示新闻的标题、发布时间、摘要、浏览量并且支持分页点击新闻标题后跳转到详情页展示完整正文。分页实现我选择的是PageHelper插件。这个插件的使用有个经典三连第一步在pom.xml引入依赖第二步在mybatis-config.xml里配置拦截器第三步在Service层调用PageHelper.startPage(pageNum, pageSize)开启分页。三步缺一不可而且startPage方法必须写在紧接着的Mapper查询方法前一行中间不能插入其他逻辑否则分页不生效。// NewsService.java public PageInfoSchoolNews getNewsList(int pageNum, int pageSize) { // 这一行必须在mapper查询之前 PageHelper.startPage(pageNum, pageSize); ListSchoolNews newsList newsMapper.selectNewsList(); return new PageInfo(newsList); }核心逻辑在于PageHelper底层会通过拦截器改写你接下来要执行的这条SQL自动拼接limit关键字并把查询出来的结果封装成一个Page对象PageInfo则是进一步帮我们封装了总条数、总页数、当前页等分页所需信息。Controller拿到PageInfo后把当前页的新闻列表、总页数、当前页码传回页面前台用jstl的c:forEach循环遍历展示再配合c:if做上一页下一页的按钮控制一个标准的分页新闻列表就完成了。详情页涉及一个传参的细节URL怎么写才能既美观又不容易乱码。这里我建议使用REST风格路径Controller用PathVariable接收。RequestMapping(/news/detail/{id}) public String newsDetail(PathVariable(id) Integer id, Model model) { SchoolNews news newsService.getNewsById(id); newsService.incrementViewCount(id); // 浏览量加1 model.addAttribute(news, news); return news-detail; }5.2 图片上传CommonsMultipartResolver的配置与文件命名陷阱校园风采模块需要支持后台管理员上传图片这个功能涉及SpringMVC的文件上传解析器。在spring-mvc.xml中配置一个CommonsMultipartResolver的bean就激活了上传功能。bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namedefaultEncoding valueUTF-8/ property namemaxUploadSize value5242880/ property namemaxUploadSizePerFile value2097152/ /beanmaxUploadSize是全部文件的总大小上限maxUploadSizePerFile是单个文件大小上限。单位是字节所以5242880是5MB2097152是2MB。上传接口中前端表单的enctype一定要设为multipart/form-dataController接收参数时用MultipartFile来接收file域的name。这里有一个特别容易踩的坑文件名冲突。如果直接用用户上传时的原文件名保存那两个人传一个叫“学校大门.png”的图片后传的就会把先传的覆盖掉。我的做法是生成UUID作为新文件名拼接上原始文件的后缀。String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; file.transferTo(new File(uploadDir newFileName));另外还需要在spring-mvc.xml里放行上传目录的映射否则上传成功后浏览器也无法通过URL访问图片外观上就等于是上传失败了。你也需要配置虚拟路径映射把磁盘上的某个绝对路径映射成一个URL路径。mvc:resources mapping/upload/** locationfile:D:/school_upload//5.3 后台管理登录拦截器与Session设计后台管理模块不是简单地把增删改查页面拼起来而是要做到真正的安全控制。我设计了一个简单的登录验证流程管理员提交用户名和密码Service层通过adminMapper查询匹配匹配成功则把管理员对象放入session并记录登录时间匹配失败则返回错误提示页面弹出“用户名或密码错误”的提示。但是这个流程有个漏洞——管理员一旦登录成功之后浏览器访问任何后台URL后台是否都要重新验证登录状态答案是要但不能靠每个Controller方法自己去判断否则代码重复写死。标准做法是使用SpringMVC的拦截器写一个LoginInterceptor在preHandle方法里统一检查session中的管理员标识符。如果没有该标识直接重定向到登录页不放行如果有则继续访问目标路径。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object admin request.getSession().getAttribute(loginAdmin); if (admin null) { response.sendRedirect(request.getContextPath() /admin/login); return false; } return true; } }然后在spring-mvc.xml里注册这个拦截器并指定拦截路径。mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ mvc:exclude-mapping path/admin/doLogin/ bean classcom.school.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors注意exclude-mapping一定不能漏否则登录页面本身也会被拦截形成“绕圈”的访问死循环。6. 前台页面渲染JSP EL JSTL的经典组合6.1 公共页面片段的抽取方式前台页面如果用纯JSP写最头疼的是每个页面都有重复的头部导航和底部版权信息。如果每个页面都复制粘贴一份改一行导航菜单地址就要改十几个文件这是维护灾难。JSP里有两种抽取公共片段的方式一种是静态包含把公共页面整体嵌入目标页面效率高但无法传递参数另一种是动态包含在运行时动态引入页面片段可以传参。官网站点的导航栏是固定的推荐使用动态包含的方式。在head.jsp中放一个导航栏变量比如当前处于哪个栏目需要高亮对应的菜单各页面里用request.setAttribute设置这个变量后include实现了接近模板引擎的渲染效果。这个技巧很有意思很多教材都不讲但是在真实项目中非常实用。6.2 前端资源版本的坑CSS改了却不生效如果你按照我的方法配置了静态资源放行页面样式能正常加载了但改CSS后发现浏览器没变化刷新也没有反应。这个九成是浏览器缓存。CSS、JS这种静态资源在第一次加载后会被浏览器缓存后续请求可能直接读缓存里的旧文件。解决办法有两个。第一个是在开发者工具里勾选Disable cache选项只对当前调试页面生效第二个是给静态资源URL加上版本号参数。link relstylesheet href${pageContext.request.contextPath}/static/css/style.css?v20240101发布新版本时手动调整这个参数缓存自然刷新。这个方法在企业项目中也很常见只是我们不会用一个硬编码的数字每次更新发布版本这里只需要在开发阶段这样处理就够了。官方网站发布更新频率不高每次更新改一下版本号足够。6.3 页面表格循环里怎么区分图片和文字校园风采页面通常是一个图片墙布局每个图片下面配图片标题。如果用表格布局展示需要控制每行显示三张图片这里推荐JSTL的forEach配合varStatus状态变量来实现。c:forEach items${photoList} varphoto varStatusstatus c:if test${status.index % 3 0} div classrow /c:if div classcol-4 img src${photo.photoUrl} alt${photo.photoTitle} p${photo.photoTitle}/p /div c:if test${status.index % 3 2 || status.last} /div /c:if /c:forEach这段代码的核心逻辑是每循环三次就用div.row包一层形成一行三列的视觉效果。判断条件里status.last是为了处理图片总数不是3的倍数时最后一行也能正确闭合标签。7. 常见问题与排查技巧实录7.1 项目启动报错速查表我在调试这个项目的过程中整理了下面这个排错速查表基本覆盖了SSM项目最常见的那几个问题。报错现象根本原因解决方案启动时ClassNotFoundException: DispatcherServlet缺少spring-webmvc依赖包检查pom.xml是否引入spring-webmvc执行Maven Reimport连接数据库报Access denied for user用户名或密码错误检查jdbc.properties里的jdbc.username和jdbc.password访问页面报Whitelabel Error PageController路径没有映射确认Controller注解存在、RequestMapping路径是否与访问地址一致中文乱码请求或响应的编码未统一为UTF-8检查web.xml的CharacterEncodingFilter、数据库连接url是否带characterEncodingutf-8MapperBindingException: Invalid bound statementMapper接口方法没有对应的XML查询语句检查mapper XML的namespace是否指向接口全限定名id是否与方法名一致BeanCreationException: Error creating bean某个bean依赖的类没有被扫描到检查注解扫描路径范围是否正确Service是否漏了Service注解启动时端口被占用8080被其他进程占用在Run Configuration里换一个端口或在cmd里使用netstat -ano命令查占用进程7.2 MyBatis中实体类属性和表字段映射不一致我在这个项目里遇到过一个比较隐蔽的问题数据库字段是publish_timeJava实体类的属性是publishTime两者默认情况下无法自动映射。因为MyBatis默认开启了下划线转驼峰映射但是如果配置项没打开执行查询后publishTime字段就是null。解决办法就是在mybatis-config.xml中开启驼峰映射。settings setting namemapUnderscoreToCamelCase valuetrue/ /settings这个配置项属于MyBatis的全局设置打开后publish_time字段会自动映射到publishTime属性上。相当于MyBatis官方帮你做了字段名的下划线转驼峰处理省去了为每个实体类写resultMap的麻烦。7.3 用Debug模式快速定位404和500SSM项目最常见的两个状态码404通常是路由映射有问题500则是代码运行时报错。排查技巧很直接在Idea里以Debug模式启动Tomcat在Controller方法第一行打断点然后浏览器访问对应路径。如果断点没进去说明请求根本没到达Controller问题出在拦截器配置、web.xml映射或者路径写错如果断点进去了但执行到某一步报500那么异常堆栈就是定位问题的第一手线索。7.4 面试常问的SSM相关题目如何在这个项目里找答案“Java面试题”“java面经八股文”里关于SSM框架的题目这个项目里基本都能找到对应落点。比如面试官问Spring的IOC和DI你可以说在这个项目中Service层和Mapper层的关系全部交给Spring容器管理在Spring配置文件里定义数据源在Service实现类上标Service注解Controller通过Autowired注入Service接口这就是典型的依赖注入。比如面试官问MyBatis中#和$的区别你可以说项目中的MapperXML里所有占位符都用#{}它经过预编译可以防止SQL注入而${}是字符串拼接方式如果必须要用也需要做好白名单校验。回答这类问题时能结合一个真实做过的项目去说明比背概念的话术有说服力得多这也是做这个官网实战项目带给你的额外价值。8. 部署上线与后续扩展建议8.1 用Maven打war包部署到Tomcat课程设计答辩通过后这个项目还能不能继续走下去能。前台的展示逻辑和后台的管理逻辑都属于能直接部署到生产环境的东西。在Terminal中执行mvn clean package项目会在target目录生成war包把这个war包扔到Tomcat的webapps目录下启动Tomcat后按war包名访问路径整个学校官网就上线了。这里有一个坑如果你本地开发环境的Context路径是斜杠但部署后变成了war包名那么页面里的绝对路径带有项目名时能访问如果代码里没有加项目名前缀则会导致找不到样式和图片。所以你的JSP页面开头尽量用${pageContext.request.contextPath}统一拼接项目根路径。8.2 功能扩展从课程设计到真实可用的官网如果你想让这个官网更贴近真实使用场景可以把静态的“教学资源”换成文件下载功能给JSP页面的下载链接绑定一个Controller用response.setContentType和response.getOutputStream实现文件流下载也可以为“教师队伍”模块添加职称分类查询页面通过下拉框选择职称Ajax请求后台返回对应的教师列表。这些扩展本质上都没有跳出SSM这套框架的“请求-处理-响应”模型只是业务字段丰富了一些而已。8.3 本地访问之外的发布思路在校园局域网内部署是最稳妥的显示方式让用手机访问同一WiFi下其他设备也能看到这个官网。如果采购了云服务器把war包部署上去在服务器安全组里把80或8080端口放开一个“乡村小学校园官网”就能让远在外地的学生家长访问到这些具体部署方式每个云厂商的文档都有对应说明这里就不展开了。我个人在实际操作中的体会是课程设计项目最大的价值不是代码里用了多高深的框架而是你亲手把一套SSM流程完整跑通之后脑子里会对Java Web开发的全链路形成一个整体认知——从浏览器请求发出到DispatcherServlet分发到Controller接收到Service处理业务到Mapper操作数据库最后再逐层返回渲染到页面中间的每一个环节都在这个项目里得到了具象化。之后再学Spring Boot你会发现一切是那么熟悉只是框架把很多配置默认帮你做掉了而已。最后再分享一个小技巧保存源码时一定保留好数据库初始化脚本和README文档写清楚JDK版本、Tomcat版本、数据库账号密码和启动步骤。我见过太多人答辩前一周发现源码在自己电脑上跑不起来结果重装环境、导数据库折腾得焦头烂额最后才发现是少了sql脚本里的一条初始化数据。环境说明这些内容平时不起眼关键时刻能救你一次。