公司动态

SSM框架整合实战:从零构建新闻系统,掌握Java Web开发核心

📅 2026/8/31 18:32:04
SSM框架整合实战:从零构建新闻系统,掌握Java Web开发核心
简介这是一套基于SpringSpringMVCMyBatisSSM框架开发的简易新闻系统完整源码面向Java Web初学者与中级开发者用于快速掌握企业级Web应用的分层架构设计与主流框架整合实践。资源包含227个文件涵盖36个核心Java业务类、54个编译后Class字节码、24个XML配置与Mapper映射文件、23个JSP视图页面及23个JS交互脚本辅以31个运行依赖Jar包、6个图片资源及SQL建库脚本等整体压缩包仅12.21MB结构清晰、开箱即用。已有83人下载学习可直接导入IDE运行完整呈现新闻后台管理增删改查、分类维护与前台展示列表浏览、详情查看、评论互动全流程。代码严格遵循MVC分层规范含Service事务控制、MyBatis动态SQL实现、SpringMVC请求路由与数据绑定、以及预编译的Example条件构造器如NewsExample$Criteria等是理解SSM协同机制与实战编码规范的优质入门范例。1. 项目概述与核心价值最近在整理硬盘翻出来一个几年前做的老项目一个基于SSMSpringSpringMVCMyBatis框架的简易新闻系统。虽然现在Spring Boot大行其道微服务架构满天飞但这个经典的SSM组合对于理解Java Web开发的底层脉络、掌握MVC分层思想以及数据库交互的细节依然有着不可替代的价值。这个系统麻雀虽小五脏俱全包含了新闻的前台展示和后台管理两大核心模块代码结构清晰非常适合刚学完Java基础、想踏入企业级开发门槛的朋友作为第一个“像样”的练手项目也适合有经验的开发者用来回顾和巩固Web开发的基础设施。这个系统解决的核心问题是如何将一个常见的业务需求新闻的发布与管理通过一套标准、可维护的企业级技术栈来实现。它不像那些简单的“学生管理系统”只停留在CRUD层面而是涉及了用户权限、富文本编辑、文件上传、分页查询、数据校验等Web开发中必然会遇到的真实场景。通过拆解这个项目你能清晰地看到请求是如何从浏览器出发经过SpringMVC的控制器Controller分发调用Spring管理的业务层Service逻辑再通过MyBatis与数据库交互最后将数据填充到JSP视图层返回给用户的完整闭环。理解了这个闭环你就掌握了传统Java Web应用开发的“任督二脉”。2. 技术栈选型与架构设计思路2.1 为什么是SSM而不是Spring Boot现在很多新手可能会直接上手Spring Boot因为它开箱即用配置简单。但回过头来学习SSM恰恰是为了理解Spring Boot帮你自动配置了哪些东西。SSM框架的整合过程本身就是一次对Spring IoC容器、AOP、事务管理、MVC模式以及ORM映射原理的深度实践。Spring在这里扮演了“大管家”的角色。它通过IoC控制反转容器管理着项目中所有的Bean比如Service、DAOData Access Object、事务管理器等。它的AOP面向切面编程能力让我们可以优雅地处理一些横切关注点比如在这个新闻系统中我们可能会用AOP来统一记录日志或者管理事务。虽然在这个简易系统中可能没有显式配置复杂的AOP但理解Spring的这部分能力是设计可扩展系统的关键。SpringMVC是处理Web请求的核心。它清晰地划分了控制器、模型和视图的职责。当一个用户请求新闻列表时这个请求会被SpringMVC的DispatcherServlet拦截然后根据配置的映射关系找到对应的NewsController中的方法。控制器方法调用业务服务获取数据模型然后决定渲染哪个JSP页面视图并将模型数据传递过去。这种清晰的分离使得代码结构非常易于理解和维护。MyBatis是一个半自动化的ORM框架它比Hibernate更轻量SQL可控性更强。对于新闻系统这类业务逻辑相对简单但对查询灵活性有一定要求的项目MyBatis非常合适。我们可以编写直观的SQL语句在XML映射文件中并通过动态SQL标签如if,foreach来灵活构建查询条件例如根据标题、分类、时间范围来筛选新闻。同时MyBatis的#{}和${}参数占位符的区别也是面试和实际开发中必须搞清楚的一个点这直接关系到SQL注入的安全问题。选择这三者组合是一个经典、稳定且学习资源丰富的方案。它要求开发者手动配置web.xml、Spring的applicationContext.xml、SpringMVC的spring-mvc.xml和MyBatis的mybatis-config.xml以及数据库连接池如Druid。这个过程虽然繁琐但能让你深刻理解一个Web应用从启动到运行的每一个环节。2.2 系统核心模块与数据流设计这个简易新闻系统通常包含以下核心模块其数据流设计体现了典型的分层架构思想前台展示模块新闻首页展示新闻分类导航和最新的新闻列表通常需要分页。新闻列表页按分类展示新闻支持分页。新闻详情页展示单篇新闻的完整内容包括标题、作者、发布时间、正文等。简单搜索根据关键词在新闻标题或内容中进行模糊查询。后台管理模块管理员登录基于Session或Token的认证。新闻分类管理对新闻分类进行增删改查CRUD。新闻内容管理新闻的发布、编辑、删除、上下线操作。这里是核心涉及富文本编辑器如UEditor、KindEditor的集成和图片上传功能。用户管理简易版可能包含后台管理员账号的管理。数据流设计遵循典型的MVC模式用户请求浏览器发起一个请求例如/news/list?categoryId1page2。前端控制器DispatcherServlet在web.xml中配置拦截所有请求。处理器映射SpringMVC根据配置注解驱动如RequestMapping找到对应的NewsController.listNews()方法。控制器处理Controller方法接收参数categoryId,page调用NewsService.getNewsByCategory()业务方法。业务逻辑层Service层处理业务逻辑例如组装查询条件、处理业务规则如审核状态然后调用NewsMapper接口。数据访问层Mapper接口对应的MyBatis XML映射文件执行具体的SQL语句查询数据库。结果返回查询结果ListNews沿调用链原路返回最终被封装到Model中。视图渲染Controller方法返回一个视图名如news/list视图解析器定位到/WEB-INF/views/news/list.jspJSP页面使用JSTL/EL表达式渲染模型数据生成HTML。响应输出生成的HTML通过DispatcherServlet返回给浏览器。这个清晰的数据流是系统可维护性的基石。3. 核心细节解析与实操要点3.1 关键配置文件详解与避坑指南SSM整合的“重头戏”和“坑点”大多集中在配置文件上。下面以典型配置为例解析关键点web.xml- 应用入口!-- 1. 配置Spring上下文监听器加载applicationContext.xml -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext-*.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 2. 配置SpringMVC的前端控制器DispatcherServlet -- servlet servlet-namespringDispatcherServlet/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringDispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping !-- 3. 字符编码过滤器解决中文乱码 -- filter filter-namecharacterEncodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-namecharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping注意ContextLoaderListener加载的Spring根上下文主要包含Service、DAO、事务等Bean和DispatcherServlet加载的SpringMVC子上下文主要包含Controller、视图解析器等Bean是父子关系。子上下文可以访问父上下文的Bean反之则不行。务必确保Bean扫描路径不重叠否则可能导致Bean被重复创建或注入失败。通常做法是根上下文扫描service,dao包MVC上下文扫描controller包。spring-mvc.xml- MVC配置核心!-- 开启注解驱动这是使用Controller, RequestMapping等注解的基础 -- mvc:annotation-driven/ !-- 静态资源处理否则CSS/JS/图片会被DispatcherServlet拦截导致404 -- mvc:resources mapping/static/** location/static// !-- 配置视图解析器将逻辑视图名解析为具体JSP路径 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean !-- 配置文件上传解析器 -- bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namedefaultEncoding valueUTF-8/ property namemaxUploadSize value10485760/ !-- 10MB -- /bean实操心得mvc:resources配置至关重要新手常在这里踩坑页面样式全无。InternalResourceViewResolver的prefix和suffix设置后控制器方法返回news/list就会自动指向/WEB-INF/views/news/list.jsp。将JSP放在WEB-INF下是一种安全实践可以防止用户直接通过URL访问JSP文件。applicationContext.xml- 业务与数据整合核心!-- 1. 配置数据源这里以阿里Druid为例 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property namedriverClassName value${jdbc.driver}/ !-- 连接池其他参数配置 -- /bean !-- 2. 配置SqlSessionFactoryBean MyBatis与Spring整合的关键 -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ !-- 指定MyBatis全局配置文件位置 -- property nameconfigLocation valueclasspath:mybatis/mybatis-config.xml/ !-- 指定Mapper XML文件的位置可以使用通配符 -- property namemapperLocations valueclasspath:mybatis/mapper/*.xml/ !-- 配置实体类别名包这样在XML中可以直接写类名 -- property nametypeAliasesPackage valuecom.example.news.entity/ /bean !-- 3. 配置Mapper扫描器自动为Mapper接口创建代理对象 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.news.dao/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean !-- 4. 配置事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean !-- 开启注解事务 -- tx:annotation-driven transaction-managertransactionManager/避坑指南SqlSessionFactoryBean的mapperLocations路径一定要写对确保能扫描到所有的*Mapper.xml文件。MapperScannerConfigurer的basePackage指定的是Mapper接口所在的包它会自动将这些接口注册为Spring Bean。事务管理器的配置是保证数据库操作原子性的关键在Service层方法上使用Transactional注解即可声明事务。3.2 MyBatis映射文件与动态SQL实战新闻系统的查询往往带有动态条件MyBatis的动态SQL功能在这里大显身手。以新闻分页多条件查询为例NewsMapper.xml:?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.news.dao.NewsMapper resultMap idBaseResultMap typeNews id columnid propertyid/ result columntitle propertytitle/ result columncategory_id propertycategoryId/ result columncontent propertycontent/ result columnauthor propertyauthor/ result columnpublish_time propertypublishTime/ result columnstatus propertystatus/ !-- 其他字段映射 -- /resultMap sql idBase_Column_List id, title, category_id, content, author, publish_time, status /sql !-- 动态查询新闻列表 -- select idselectNewsByCondition resultMapBaseResultMap parameterTypemap SELECT include refidBase_Column_List/ FROM t_news WHERE status 1 !-- 只查询已发布的新闻 -- if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR content LIKE CONCAT(%, #{keyword}, %)) /if if teststartTime ! null AND publish_time #{startTime} /if if testendTime ! null AND publish_time lt; #{endTime} /if ORDER BY publish_time DESC !-- 使用PageHelper等分页插件时此处不需要写LIMIT -- /select !-- 插入新闻获取自增主键 -- insert idinsertNews parameterTypeNews useGeneratedKeystrue keyPropertyid INSERT INTO t_news (title, category_id, content, author, publish_time, status) VALUES (#{title}, #{categoryId}, #{content}, #{author}, #{publishTime}, #{status}) /insert /mapper关键点解析resultMap定义了数据库列与Java实体类属性之间的映射关系。id标签用于主键能提高性能。如果数据库字段名如publish_time和Java属性名publishTime遵循下划线转驼峰的规则可以通过全局设置mapUnderscoreToCamelCase为true来简化无需逐一配置result。sql与include用于提取可重用的SQL片段保持代码整洁。动态SQL标签if标签根据传入参数Map或JavaBean中的属性值动态拼接WHERE条件。这是MyBatis最常用的功能之一。#{}与${}的区别**务必使用#{}**进行参数占位。#{keyword}会被预处理为?能有效防止SQL注入。而${}是字符串直接替换存在极高的安全风险除非在极少数需要动态指定表名或列名的场景且参数可控否则严禁使用。useGeneratedKeys在插入操作后自动将数据库生成的主键值回填到传入的News对象的id属性中非常方便。4. 前后台核心功能实现与代码剖析4.1 后台新闻发布与富文本编辑器集成后台新闻发布是系统的核心功能难点在于富文本编辑器如UEditor的集成和图片上传。1. 集成UEditor将UEditor的ueditor文件夹包含dialogs,lang,themes,third-party,ueditor.all.js等复制到项目的/static/plugins/目录下。在发布新闻的JSP页面中引入UEditor的JS文件并通过一个script标签初始化一个文本区域。关键在于配置UEditor的服务器端上传接口。UEditor需要向后端发送一个特定的请求来上传图片。我们需要在SpringMVC中编写一个控制器来处理这个请求。2. 图片上传控制器Controller RequestMapping(/ueditor) public class UEditorController { Autowired private FileStorageService fileStorageService; // 自定义的文件存储服务 RequestMapping(value /config) ResponseBody public String config() { // 读取UEditor前端需要的config.json配置可以直接返回一个JSON字符串 // 这里为了简化可以硬编码或从配置文件读取 return {\imageActionName\: \uploadimage\, \imageFieldName\: \upfile\, ...}; } RequestMapping(value /uploadimage, method RequestMethod.POST) ResponseBody public MapString, Object uploadImage(RequestParam(upfile) MultipartFile file, HttpServletRequest request) { MapString, Object result new HashMap(); try { // 1. 校验文件类型、大小 if (file.isEmpty() || !file.getContentType().startsWith(image/)) { result.put(state, 文件类型不正确); return result; } // 2. 调用服务保存文件获取访问URL String fileUrl fileStorageService.store(file); // 3. 按照UEditor要求的格式返回JSON result.put(state, SUCCESS); result.put(url, fileUrl); result.put(title, file.getOriginalFilename()); result.put(original, file.getOriginalFilename()); } catch (Exception e) { result.put(state, 上传失败); } return result; } }注意事项文件存储服务FileStorageService需要自己实现核心逻辑是将MultipartFile写入到服务器磁盘的某个目录如/upload/images/并生成一个唯一的文件名使用UUID防止重名最后返回能通过Web服务器如Nginx访问的URL路径例如/upload/images/2023/10/27/abc123.jpg。同时需要在SpringMVC配置中放行这个上传目录的静态资源访问。3. 新闻发布Service层Service Transactional // 声明事务 public class NewsServiceImpl implements NewsService { Autowired private NewsMapper newsMapper; Override public void publishNews(News news) { // 业务逻辑校验 if (news.getTitle() null || news.getTitle().trim().isEmpty()) { throw new IllegalArgumentException(新闻标题不能为空); } // 设置默认值 news.setPublishTime(new Date()); news.setStatus(NewsStatus.PUBLISHED.getCode()); // 枚举定义状态 // 调用DAO层 int affectedRows newsMapper.insertNews(news); if (affectedRows ! 1) { throw new RuntimeException(发布新闻失败); } // 这里可以添加其他业务如发布后通知、更新缓存等 } }4.2 前台新闻列表分页与详情展示前台展示的核心是高效的分页查询和防止详情页的慢查询。1. 分页查询实现 在SSM项目中使用PageHelper插件是实现分页最优雅的方式。引入依赖在pom.xml中添加PageHelper的starter依赖。配置拦截器在mybatis-config.xml或Spring配置中配置PageHelper插件。Service层使用Override public PageInfoNews getNewsByPage(Integer categoryId, String keyword, Integer pageNum, Integer pageSize) { // 紧跟在查询语句前调用页码和每页数量 PageHelper.startPage(pageNum, pageSize); // 构建查询条件Map MapString, Object params new HashMap(); params.put(categoryId, categoryId); params.put(keyword, keyword); // 这个查询方法就是上面Mapper里定义的selectNewsByCondition ListNews newsList newsMapper.selectNewsByCondition(params); // 用PageInfo包装结果PageInfo包含了非常丰富的分页信息 return new PageInfo(newsList); }PageInfo对象包含了当前页数据列表、总记录数、总页数、当前页码、是否有上一页/下一页等所有分页信息直接传递给前端即可。2. 新闻详情页与性能考量 新闻详情页通常根据新闻ID查询单条记录。这里要注意两个问题SQL注入确保在Mapper中使用#{id}并且ID参数来自可信路径如经过校验的请求参数。大字段查询新闻内容content字段可能很大存储了富文本HTML。在列表页查询时绝对不要在SQL中SELECT *或包含content字段。列表页的查询应该只取id,title,author,publish_time,summary摘要等必要字段。只有在进入详情页时才根据ID查询包含content的完整新闻信息。这能极大提升列表页的查询速度和减轻数据库压力。3. 控制器与视图交互Controller RequestMapping(/news) public class NewsController { Autowired private NewsService newsService; Autowired private CategoryService categoryService; GetMapping(/list) public String list(RequestParam(value cat, required false) Integer categoryId, RequestParam(value kw, required false) String keyword, RequestParam(value page, defaultValue 1) Integer pageNum, Model model) { // 获取分页数据 PageInfoNews pageInfo newsService.getNewsByPage(categoryId, keyword, pageNum, 10); // 获取所有分类用于导航栏 ListCategory categories categoryService.getAllCategories(); // 将数据放入Model供视图使用 model.addAttribute(pageInfo, pageInfo); model.addAttribute(categories, categories); model.addAttribute(currentCat, categoryId); model.addAttribute(keyword, keyword); return news/list; // 对应 /WEB-INF/views/news/list.jsp } GetMapping(/detail/{id}) public String detail(PathVariable(id) Long id, Model model) { News news newsService.getNewsById(id); if (news null) { // 可以跳转到404页面 return error/404; } model.addAttribute(news, news); return news/detail; } }在JSP中可以使用JSTL和EL表达式轻松渲染数据例如使用c:forEach遍历pageInfo.list展示新闻列表使用${news.title}显示新闻标题。5. 开发部署常见问题与排查实录在实际开发和部署这个SSM新闻系统的过程中你几乎一定会遇到下面这些问题。我把它们和解决方案整理出来希望能帮你节省大量排查时间。5.1 启动与配置类问题问题1Tomcat启动时报ClassNotFoundException或NoClassDefFoundError涉及Spring、MyBatis等jar包。排查思路这是典型的依赖缺失或冲突。首先检查项目的pom.xmlMaven或lib目录传统Web项目是否包含了所有必要的jar包。解决方案Maven项目在IDE中查看Maven Dependencies库确认所有依赖已成功下载。运行mvn clean compile或mvn dependency:tree检查依赖树看是否有版本冲突。确保scopeprovided/scope的依赖如servlet-api在部署时Tomcat本身会提供。传统项目手动将所需jar包复制到WEB-INF/lib下。务必注意jar包版本兼容性例如Spring 5.x需要Tomcat 8.5和JDK 8。检查web.xml中配置的类名是否正确特别是过滤器和监听器的全限定类名。问题2启动成功但访问任何页面都报404或者静态资源CSS/JS加载不到。排查思路URL映射错误或静态资源被拦截。解决方案检查DispatcherServlet的url-pattern。配置为/会拦截所有请求必须配合mvc:resources来放行静态资源。确认静态资源目录如/static/的mapping和location配置正确。检查控制器RequestMapping的路径和访问的URL是否匹配。注意上下文路径Context Path。在浏览器开发者工具的“网络”Network选项卡中查看具体是哪个请求返回了404是JSP页面还是静态资源。问题3页面中文显示乱码。排查思路字符编码过滤器未生效或生效顺序不对数据库连接字符集问题JSP页面编码问题。解决方案确保CharacterEncodingFilter配置在web.xml中最前面或至少在其他过滤器之前并且forceEncoding设置为true。检查数据库、表、字段的字符集是否为utf8mb4推荐支持emoji或utf8。在JDBC连接URL中显式指定字符集如jdbc:mysql://localhost:3306/news_db?useUnicodetruecharacterEncodingUTF-8。确保JSP页面开头有% page contentTypetext/html;charsetUTF-8 languagejava %。5.2 运行时与数据库交互问题问题4Service方法中抛出异常但数据库数据仍然被修改了事务未回滚。排查思路Spring声明式事务未生效。解决方案检查applicationContext.xml中是否配置了DataSourceTransactionManager并开启了tx:annotation-driven。检查Transactional注解是否添加在Service实现类的方法或类上而不是接口上虽然加在接口上也行但使用CGLIB代理时可能有问题。确保注解是来自org.springframework.transaction.annotation.Transactional。默认情况下Spring事务只在遇到运行时异常RuntimeException和Error时回滚。如果抛出的是受检异常Exception事务不会回滚。如果需要回滚受检异常需要在注解中指定Transactional(rollbackFor Exception.class)。确保调用事务方法是通过Spring代理对象调用的。在同一个Service类中方法A调用方法BB有Transactional由于是this.B()调用不经过代理B的事务会失效。这是非常常见的坑。问题5MyBatis查询结果为空但SQL在数据库客户端执行有数据。排查思路映射关系错误或参数传递问题。解决方案开启MyBatis的SQL日志。在mybatis-config.xml中配置settingssetting namelogImpl valueSTDOUT_LOGGING//settings或者使用Logback等日志框架配合log4jdbc查看完整SQL和参数。核对resultMap中的column和property映射是否正确特别是数据库下划线命名和Java驼峰命名的对应关系。可以尝试在mybatis-config.xml中设置setting namemapUnderscoreToCamelCase valuetrue/来自动转换。检查Mapper接口方法名和XML中的id是否一致参数类型是否匹配。检查传入的参数是否为null动态SQL中的if test条件判断是否因参数为null或空字符串而将整个条件过滤掉了。问题6使用PageHelper分页后获取到的PageInfo中total总记录数为0或者分页无效。排查思路PageHelper插件配置或使用方式错误。解决方案最重要PageHelper.startPage(pageNum, pageSize)必须紧跟在要分页的查询方法调用之前中间不能有其它数据库查询操作。检查PageHelper依赖版本是否与MyBatis、Spring版本兼容。确保分页查询的SQL语句是简单的SELECT语句PageHelper是通过拦截器在SQL后自动添加LIMIT子句实现的。如果SQL很复杂如包含UNION可能需要使用PageHelper提供的其他API。5.3 文件上传与集成交互问题问题7上传文件时报org.springframework.web.multipart.MultipartException: Current request is not a multipart request。排查思路前端表单格式不正确或Spring配置缺失。解决方案确保前端上传文件的表单form设置了enctypemultipart/form-data并且提交方法是POST。检查SpringMVC配置文件中是否正确声明了CommonsMultipartResolver的Bean且其ID必须为multipartResolver默认名称。检查是否引入了commons-fileupload和commons-io的jar包。问题8UEditor编辑器显示正常但点击上传图片没反应浏览器控制台报JS错误或404/500。排查思路UEditor前后端配置不一致或上传接口未正确实现。解决方案打开浏览器开发者工具“网络”选项卡查看点击上传时浏览器向哪个URL发送了请求请求方法、参数是什么。与后端UEditorController中配置的RequestMapping路径进行比对。确保后端/config接口返回的JSON配置中的imageActionName如uploadimage与处理上传的控制器方法映射路径一致。后端上传接口需要严格按照UEditor要求的JSON格式返回结果包括state、url、title等字段。一个字段不对都可能导致前端解析失败。检查文件保存的目录是否有写权限以及返回的url是否能被公开访问到例如是否在SpringMVC的mvc:resources中配置了映射。这个基于SSM的新闻系统项目虽然技术栈不是最新的但它所涵盖的知识点——从MVC分层、ORM使用、事务管理到文件上传、富文本编辑、分页查询——构成了Java Web开发的坚实基石。把这些流程和坑点都走一遍、解决一遍你对一个Web应用如何运作的理解会深刻得多。之后再学习Spring Boot你会发现它无非是用约定大于配置的方式帮你把这些繁琐的XML配置和整合工作自动化了底层的核心原理依然相通。代码最终是要服务于业务的清晰的结构、合理的分层和扎实的基础永远比追逐最新的框架更重要。本文还有配套的精品资源点击获取