☰
SSM完整项目拆解:从登录链路到二次开发实战
2026/10/17 3:19:06 网站建设 项目流程

简介:基于SSM框架的完整Java Web项目,整合了Spring、Spring MVC与MyBatis三大主流框架,遵循模型-视图-控制器设计模式,适合Java初学者学习框架整合流程,也便于开发者在此基础上进行二次开发,或用作课程设计与毕业设计参考。压缩包共226个文件、约56.11MB,包含32个Java源码、26个依赖Jar包、20个JSP页面、20个XML配置、32个class文件以及JS、CSS等前端资源,另含少量APK文件,整体结构完整,覆盖了SSM项目常见的代码、配置与页面层内容。目前已有2386人浏览学习。项目中展示了Spring依赖注入、Spring MVC请求分发、MyBatis持久层操作等核心用法,还提供典型的Mapper接口、Service业务层、Controller控制器以及视图解析器配置,可帮助读者快速理解SSM三层架构的协作方式,熟悉从数据库访问到页面响应的完整链路。资源目录清晰,将源码、配置和Web资源分离存放,同时包含project、classpath等工程描述文件,适合直接导入开发工具进行调试,并可作为后续功能扩展和学习研究的起点。

1. 一个SSM完整项目到底“完整”在哪:先从一次正常登录说起

第一次拆这种 java ssm 框架的完整项目,我做了件蠢事:把几十个 XML 从头翻到尾,差点没睡着。后来才明白,这类项目真正值钱的地方不是文件多,而是 Spring、SpringMVC、MyBatis 三套框架在同一套代码里各管一段、互不越权。所谓“完整”,指的是有一条从浏览器请求到数据库再返回的完整链路——能登录、能增删改查、能带权限管理,而不是一堆散落的类和配置。

这套东西适合两类人:一是要做 SSM 课设或毕设的学生,二是公司里被安排维护老后台系统的开发。前三个月你会咒骂 XML 配置,三年后你会庆幸年轻时把它看明白了。下面按我实际拆项目的顺序来:先看骨架,再走一遍请求链路,然后跑起来,最后照着加一个新模块。

2. 项目骨架与技术选型:Spring管容器、SpringMVC管请求、MyBatis管SQL

SSM 项目的第一道门槛不是代码,是认清谁在管谁。很多人直接点开 controller,发现看不懂在调什么,是因为跳过了分层。这一章把目录、配置、数据库脚本三块先立起来,后面走链路才有地图。

2.1 目录结构里藏着整套分层思想

拿到压缩包先不要急着导入 IDE,先在文件管理器里过一遍顶层结构。合格的完整项目长这样:

ssm-project ├── pom.xml ├── sql │ └── init.sql └── src ├── main │ ├── java/com/xxx │ │ ├── controller │ │ ├── service │ │ ├── dao │ │ └── entity │ ├── resources │ │ ├── spring │ │ ├── mybatis │ │ ├── mapper │ │ └── jdbc.properties │ └── webapp │ ├── WEB-INF │ ├── static │ └── index.jsp └── test

这套目录本身就是分层的说明书。controller 只管收参数和决定返回什么视图或 JSON;service 是业务逻辑和事务的边界;dao 是数据访问接口,配合 resources/mapper 下的 XML 写 SQL;entity 是表结构的 Java 映射。为什么这套东西能活十几年?因为每层只干一件事:改 SQL 不用动 controller,换前端页面不用动 service,各层的替换成本都压到了最低。

有两点新手最容易看走眼。第一,resources 和 webapp 的职责完全不同:resources 下的 xml 和 properties 是 JVM 启动时被 Spring 容器加载的,webapp 下是 Tomcat 管的 Web 资源。把 mapper XML 放进 java 目录而不是 resources,编译后 target/classes 里没有它,启动不报错,一查询就报绑定异常。第二,层与层之间是单向依赖:controller 依赖 service,service 依赖 dao,dao 依赖 mapper XML,谁反向依赖谁,后期改需求时就等着哭。用一张表把这四层的定位钉死:

层典型文件职责常见错误
Controllercontroller/*.java参数接收、路由、响应写业务、直接调 dao
Serviceservice 接口+impl业务编排、事务边界漏标 @Transactional
Daodao/*.java声明数据访问方法与 XML 的 id 对不上
Mapper XMLresources/mapper/*.xml真正执行的 SQLnamespace 写错

2.2 Maven依赖与三个关键XML配置

老 SSM 项目没有 Spring Boot 的自动配置,所有关联都要手写在 pom.xml 和 XML 里。pom 里真正核心的依赖不到十个,先把这六个看住:

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.x</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.x</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.x</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.x</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.x</version> </dependency> </dependencies>

版本号不用背,以本地 Maven 仓库能拉到稳定版本为准,但版本之间的连带关系必须懂:mysql 驱动版本、Druid 版本、MyBatis 版本三者最容易打架,后面第 4 章的翻车现场有一半从这堆依赖里来。spring-webmvc 负责 SpringMVC;mybatis-spring 是 MyBatis 接入 Spring 的桥,没有它,SqlSession 拿不到 Spring 管理的数据源;jackson 把 controller 返回的对象序列化成 JSON;druid 是连接池,顺带解决频繁建连的性能问题。

依赖之外,三个 XML 构成 Spring 的“黑匣子”。老项目的常见命名是 spring-dao.xml、spring-service.xml、spring-web.xml,对应三层容器装配。以 spring-dao.xml 为例,它把数据源、SqlSessionFactory、Mapper 扫描三件事接起来:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.xxx.dao"/> </bean>

dataSource 的三个 property 引用了 jdbc.properties 里的占位符,所以运行前第一件事是改这个属性文件里的数据库地址、账号、密码。sqlSessionFactory 的 configLocation 指向 MyBatis 自己的配置,mapperLocations 告诉 Spring 去哪找 SQL 映射 XML,路径对不上就是启动正常、查询必挂。MapperScannerConfigurer 把 com.xxx.dao 包下的接口自动代理成 MyBatis 的 Mapper,这就是为什么你没写过任何 dao 实现类,却能直接 @Autowired。

spring-service.xml 简单得多:开启注解扫描指向 service 包,注册一个 DataSourceTransactionManager,让 @Transactional 真正生效。spring-web.xml 负责扫描 controller 包、开启 mvc 注解驱动、配视图解析器。这三个文件的加载顺序在 web.xml 里通过 context-param 和 DispatcherServlet 的 init-param 指定,顺序错了容器直接起不来。

2.3 数据库脚本:建表语句里看出业务边界

完整项目的 sql 目录通常只有一个 init.sql,里面按顺序建库、建表、插初始数据。SSM 教学型项目最典型的表是用户表加权限表,再配两三张业务表。用户表长这样:

CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) COMMENT '真实姓名', status TINYINT DEFAULT 1 COMMENT '1正常 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

这里有几个老项目很常见的细节:status 用 TINYINT 而不是 VARCHAR,方便做状态过滤;create_time 交给数据库默认值,省得业务代码里手动 set;密码字段存的是摘要而不是明文——如果 init.sql 里能看到明文初始密码,那是为了测试方便,正式改造第一件事就是换加密逻辑。再看权限相关的两张表:

CREATE TABLE t_role ( id INT PRIMARY KEY AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL ); CREATE TABLE t_user_role ( user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id) );

这种 user-role 中间表的存在,说明项目带了 RBAC 权限模型。拆项目时先读一遍 init.sql,就能预判 controller 里哪些接口要过权限校验、哪些是公开的登录注册接口。建表语句里没写外键也是常态:业务量上来之后,外键约束在不少公司是被主动去掉的,一致性交给 service 层控制。这份资源里的建表脚本一般能直接执行,我习惯先建一个空库再 source 进去,而不是整库执行,避免脚本里写死的库名跟本地其它库撞车。

3. 核心模块拆解:一次登录请求从URL到数据库的完整链路

这一章用项目里最典型的功能——登录——把三层串起来。SSM 的所有业务接口都是同一条链路:DispatcherServlet 接住请求,HandlerMapping 找到 controller 方法,controller 调 service,service 调 dao,dao 的代理对象执行 mapper XML 里的 SQL,结果再原路返回。能看懂登录,就能看懂项目里九成接口。

3.1 Controller层:路由分发与参数校验

老项目的 controller 往往不长,但它身上集中了注解路由、参数绑定、响应格式三个最容易踩坑的点,先看登录接口这一段:

@Controller @RequestMapping("/user") public class LoginController { @Autowired private UserService userService; @PostMapping("/login") @ResponseBody public Result login(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) { User user = userService.login(username, password); if (user == null) { return Result.error("用户名或密码错误"); } session.setAttribute("loginUser", user); return Result.success(user); } }

几个点值得停下来看。@RequestMapping("/user") 定类级前缀,@PostMapping("/login") 是 @RequestMapping(method = POST) 的简写,完整路径是 POST /user/login。@RequestParam 把表单参数绑到方法参数上,前端传的字段名和这里不一致会直接抛 400,这是联调时最常见的翻车点。@ResponseBody 把返回值交给 jackson 转 JSON 写回响应体;去掉它,Spring 会把返回值当视图名去拼 JSP 路径,然后还你一顿 404。

Result 是项目里的统一返回体,通常长这样:

public class Result { private int code; private String message; private Object data; public static Result success(Object data) { Result r = new Result(); r.code = 200; r.data = data; return r; } public static Result error(String message) { Result r = new Result(); r.code = 500; r.message = message; return r; } // getter / setter 省略 }

统一返回体的价值在后期特别明显:前端不管调哪个接口,先看 code 再走分支,不需要为每个接口单独处理异常结构。老项目里 controller 直接返回 String 拼 JSP 的写法也存在,但近年改造方向几乎都是 JSON 接口加页面前后端分离。

controller 里还有一个容易被忽视的点:参数校验。简单场景直接在 controller 判空——用户名或密码为空就不用进 service。这类判断放 controller 而不是 service,是因为 controller 关心协议层合不合法;一旦进了 service,就默认参数已经合法。别在这一层写业务规则,否则 service 脱离 Web 场景就没法复用。

3.2 Service层:事务边界与业务编排

Service 层是 SSM 项目里最值得抄代码的地方,因为它是唯一允许出现业务规则的位置。接口和实现分开写是老项目的习惯,先看接口和实现:

public interface UserService { User login(String username, String password); void register(User user); } @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Override @Transactional(readOnly = true) public User login(String username, String password) { User user = userDao.selectByUsername(username); if (user != null && user.getPassword().equals(password)) { return user; } return null; } @Override @Transactional(rollbackFor = Exception.class) public void register(User user) { userDao.insert(user); userDao.insertUserRole(user.getId(), 2L); } }

@Transactional 的位置值得强调:它放在 service 实现类的方法上,而不是 controller 上。事务粒度应该和业务用例一致——注册这个用例,插入用户和绑定默认角色是同一个原子动作,必须同生共死;把事务标到 controller,事务边界就会随着前端调用组合不同而失去一致性。这也是面试里“事务放在哪一层”的标准答案来源。

readOnly = true 在登录这种只读场景是有意义的:MyBatis 会跳过某些缓存回写,MySQL 对只读事务也有优化,同时它给后来人一条信息——这个方法不该产生写操作。rollbackFor = Exception.class 是把默认的“只回滚 RuntimeException”扩大为“回滚所有异常”,因为你检查到账号已存在时抛的是自定义的 ServiceException,它是 Exception 的子类,不设 rollbackFor 就会留一条垃圾数据在库里。这是我见过最多的线上脏数据来源。

再提一句接口和实现分离:接口定义行为,实现类写逻辑,写单元测试时可以用 Mock 替换实现。Spring 的声明式事务本质是给实现类生成代理对象,所以 @Transactional 放实现类上更稳,避免某些代理模式下注解不生效的玄学问题。

3.3 Dao层与MyBatis映射:XML里写SQL的关键细节

Dao 层由两样东西组成:一个 Java 接口和一个同名 XML。接口只声明方法:

public interface UserDao { User selectByUsername(@Param("username") String username); int insert(User user); }

对应的 UserMapper.xml:

<mapper namespace="com.xxx.dao.UserDao"> <select id="selectByUsername" resultType="com.xxx.entity.User"> SELECT id, username, password, real_name AS realName, status, create_time FROM t_user WHERE username = #{username} </select> <insert id="insert" parameterType="com.xxx.entity.User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_user (username, password, real_name, status) VALUES (#{username}, #{password}, #{realName}, 1) </insert> </mapper>

namespace 必须等于接口的完整限定名,id 必须等于方法名,否则运行时报 Invalid bound statement。resultType 默认做自动映射:它按列名匹配实体类的属性名,t_user 的 create_time 是下划线命名,实体里是 createTime 驼峰命名,不配 mybatis-config 里的 mapUnderscoreToCamelCase 就会拿到 null。老项目里到处是 real_name AS realName 这种别名写法,其实就是为了绕开映射问题,开了驼峰配置后这些别名可以删掉。

#{} 和 ${} 的区别是 SSM 的面试热点,也是踩坑热点。#{} 是预处理占位符,MyBatis 会编译成 ? 再 setString,能防 SQL 注入;${} 是字符串拼接,直接替换进 SQL,一般只用在表名、排序字段这类无法参数化的地方。模糊查询别写成 LIKE '%${keyword}%',会有注入风险,常见做法是:

<select id="list" resultMap="userMap"> SELECT id, username, real_name, status, create_time FROM t_user <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> </where> ORDER BY id DESC </select>

这段代码解决的是“可选查询条件”问题:username 传了才拼这个条件,没传就不拼。 标签会自动处理掉多余的 AND,避免拼出 WHERE AND username = ? 这种语法错误。CONCAT('%', #{username}, '%') 把 % 也当作参数传入,而不是拼进 SQL 字符串,这是防注入的关键。order by 后面不要用 ${} 接用户传的列名,要接就做一个白名单校验。

配套的 mybatis-config.xml 里有两个设置值得一开:

<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> </configuration>

mapUnderscoreToCamelCase 打开后,create_time 自动映射到 createTime,XML 里不用再写别名;logImpl 设为 STDOUT_LOGGING,控制台会打印每次执行的 SQL 和参数,排查“代码看着对但数据不对”的问题时效率翻倍。这两行配置,是我拿到任何 SSM 项目都会先确认的。

4. 运行与避坑:把项目跑起来要过的五个坎

写代码最痛苦的不是写,是跑不起来。SSM 项目配置分散,启动失败的原因五花八门,而且报错信息经常词不达意。这一章先给一套能稳定跑起来的环境组合,再把这几年拆项目遇到的高频翻车现场逐个拆开。

4.1 环境准备与版本搭配

老 SSM 项目对环境的偏好很固定:JDK 1.8、Tomcat 8.5、Maven 3.6、MySQL 5.7。不是说新版不行,而是这套组合是项目当初开发时验证过的,换新版本容易踩驱动和框架的连带坑。版本匹配参考如下:

组件推荐版本说明
JDK1.8老项目基本按 1.8 编译,高版本反而可能报模块化错误
Tomcat8.5支持 Servlet 3.1,部署 war 最稳
MySQL5.7与 mysql-connector-java 5.x 匹配
Maven3.6.x拉依赖够用,新版对旧插件更挑剔

按这个组合准备,再把 jdbc.properties 改成你本地库的配置:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_db?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

如果你本地是 MySQL 8,driver 和 url 要跟着变,原因看 4.3 的第五个坑。执行 init.sql 时注意先建库再导表;脚本里如果写死了 DROP DATABASE,就只选内容导入,别整库执行,防止把本地其它库误删——这是我吃过亏的地方。

4.2 打包部署与启动验证

SSM 项目没有内置容器,必须打成 war 丢给 Tomcat,或者用 IDE 配一个 Tomcat Runtime 直接跑。命令行选手走这一条:

mvn clean package -DskipTests

clean 清掉 target 旧产物,package 执行打包并生成 war,-DskipTests 跳过测试减少等待。产物在 target/ 下,文件名一般是 项目名.war。把它复制到 Tomcat 的 webapps 目录,再启动 bin/startup.sh(Windows 是 startup.bat)。不改名直接放,访问路径就是 http://localhost:8080/war包名/;想用根路径访问就改名 ROOT.war 覆盖原来的。

启动验证看三步。第一步看 Tomcat 日志有没有容器初始化错误,比如 NoClassDefFoundError、BeanCreationException;第二步看浏览器能不能打开登录页,这验证了 web.xml 和 DispatcherServlet 没问题;第三步故意用错密码登录一次,如果返回业务错误而不是 500,说明数据库和 Mapper 全通了。三步都过,这套骨架就算立住了。

注意 IDE 里跑 war 和命令行有区别:Artifacts 里没把依赖 jar 加进 lib 的话,编译能过、启动就报类找不到,下一个坑细说。

4.3 高频翻车现场与排查方法

以下五个坑按“现象 → 原因 → 解决”的顺序写,遇到问题直接对照查。

坑一:启动正常,访问路径却 404

遇到过太多次:Tomcat 起来了,日志也安静,浏览器访问 http://localhost:8080/项目名/ 却是白屏加 404,后台一句报错都没有。这种“看起来正常其实没活”的故障最耗时间。

原因八成出在 web.xml 里 DispatcherServlet 的 url-pattern 配成了 /*。/ 会把 JSP 和静态资源全部拦截进 SpringMVC,而配置里并没有为它们准备 handler,结果自然是 404;配成 / 的话,JSP 和静态资源交给容器默认的 DefaultServlet 处理。

解决:把 url-pattern 从 /* 改成 /,同时确认 index.jsp 在 webapp 根目录。改完重新打包部署,刷新页面就好。

坑二:启动报 NoClassDefFoundError: DispatcherServlet

现象是 Tomcat 启动到一半抛 java.lang.NoClassDefFoundError: org/springframework/web/servlet/DispatcherServlet,或者后台出现 ClassNotFoundException。编译能过,运行找不到,这是典型的“打包时依赖没跟上”。

原因是 war 包里的 WEB-INF/lib 是空的或不完整。命令行 mvn package 一般不出这个问题,IDEA 自己配置 Artifacts 时最容易漏掉将 Maven 依赖加入 lib 目录。

解决:改用 Maven 打包,或者检查 IDEA 里 Artifacts 的 Output Layout,确认依赖 jar 已添加进 lib。改完重新 build 再启动,报错消失。

坑三:一调接口就报 Invalid bound statement

现象:项目启动正常,登录页也出来了,一提交表单后台抛 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。这类报错通常出现在 dao 层和 XML 没绑上。

原因基本三种:XML 里 namespace 和接口完整类名不一致;方法 id 和接口方法名不一致;XML 文件没被 mapperLocations 的 classpath:mapper/*.xml 扫到,最常见的是把 XML 放进了 java 目录而不是 resources 目录。

解决:逐个核对 namespace、id、目录位置,然后重新 clean 编译,确认 target/classes 下能看到对应的 XML 文件。看不到就是放错目录,搬过去再打包。

坑四:数据库里全是 ??? 或者接口返回乱码

现象:插入的数据在 MySQL 里显示为 ???,或者页面返回的 JSON 中文乱码。乱码问题要按两条路分别查:一条是数据到达数据库之前,一条是数据返回浏览器之前。

原因三层都可能出问题:数据库连接 URL 没加 characterEncoding=utf8;Tomcat 的 POST 请求没经过 UTF-8 编码过滤器;表或库的字符集不是 utf8mb4。

解决:jdbc.url 加 useUnicode=true&characterEncoding=utf8;web.xml 里配 Spring 的 CharacterEncodingFilter,forceEncoding 设为 true,让它同时管请求和响应;最后把库表字符集统一成 utf8mb4。三条都做了,乱码基本绝迹。

坑五:连不上数据库,Communications link failure

现象:启动时数据源初始化报 Communications link failure,或者第一次查询时 Connection refused。这个报错是最容易误判的,有人改半天代码,结果只是 MySQL 没启动。

原因两类最常见:一是 MySQL 服务没起或者端口不是 3306;二是 MySQL 8 换了认证和驱动,老项目的 com.mysql.jdbc.Driver 连 MySQL 8 会缺 serverTimezone 参数。

解决:先用命令行确认 MySQL 能连上;再用 mysql-connector-java 8.x 并改 driverClassName 为 com.mysql.cj.jdbc.Driver,url 加上 serverTimezone=Asia/Shanghai&useSSL=false。

提示:不同 Tomcat 和 MySQL 版本下报错措辞会有差异,排查时抓住关键词——NoClassDefFoundError、BindingException、Communications link failure——比背全文有用。

5. 二次开发与改造:给这套骨架加一个新业务模块

最后落到改造。拿到完整项目不是为了只跑通,是要在上面长出新功能。新手最容易犯的错是一上来就写 controller,结果没有 service、没有 mapper,代码写哪都不对。正确的顺序是从数据表往上长。

5.1 新增业务模块的最小改动清单

假设要给这套系统加一个“公告管理”模块,最小改动是这张表:

步骤改动文件内容
1sql/init.sql新建 t_notice 表
2entity/Notice.java实体类,字段对齐表
3dao/NoticeDao.java + resources/mapper/NoticeDao.xml接口和 SQL 映射
4service/NoticeService(Impl)增删改查业务
5controller/NoticeController.javaREST 接口
6webapp 静态页或对接前端页面联调

每一步都顺着依赖方向往下走:表定义实体,实体支撑 mapper,mapper 支撑 service,service 支撑 controller。倒着写也能跑,但排查问题时的路径会乱掉。我一般把第 3、4 步一起做,因为 mapper 的 SQL 写法和 service 的调用参数是强关联,分开写容易两边对不上。

5.2 给接口加上登录拦截器

业务模块加多之后,不能每个 controller 都手写 session 判断,要在 spring-web.xml 里统一挂拦截器。先写拦截器类:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getSession().getAttribute("loginUser") == null) { response.sendRedirect(request.getContextPath() + "/user/login"); return false; } return true; } }

再在 spring-web.xml 里注册:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/user/login"/> <mvc:exclude-mapping path="/static/**"/> <bean class="com.xxx.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

mapping 管拦截路径,exclude-mapping 管放行路径。登录接口和静态资源必须放行,否则用户连登录页都进不去,JS、CSS 全被拦下来,页面样式全丢。这是给老项目加权限时最容易翻车的配置,没有之一。

5.3 改完怎么验证与回归

每次改造完,我都强制自己走一遍固定验证流程:先启动看日志有没有 Bean 创建异常;再走一遍老的登录流程,确认基础链路没被改坏;然后用新模块的接口跑新增、查询、修改、删除四条主流程;最后开 MyBatis 的 SQL 日志,核对每次请求实际执行的 SQL 和参数。

SQL 日志在 mybatis-config.xml 里加一行就能开,前面 3.3 已经给过配置。开启后控制台会打印每个 mapper 方法的执行 SQL 和占位符参数,排查“代码看着对但数据不对”最管用。从那以后我每次拆新的 SSM 项目,第一件事永远不变:先跑通登录,再动手改造。登录链路是整套骨架的体检报告——它通了,说明配置、事务、映射全都没脱节。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询