1. Spring MVC进阶与SSM整合实战概述
在Java企业级开发领域,Spring MVC作为Spring框架的核心模块,已经成为构建Web应用的事实标准。而SSM(Spring+Spring MVC+MyBatis)组合更是国内Java开发者最常用的技术栈之一。这套组合拳既能发挥Spring的IoC和AOP优势,又能通过MyBatis实现灵活的SQL管理,同时借助Spring MVC提供清晰的MVC分层架构。
我经历过多个从零搭建到上线的SSM项目,发现很多开发者在框架整合阶段会遇到各种"坑":配置文件冲突、事务管理失效、依赖版本不兼容等问题层出不穷。本文将结合实战经验,带你深入Spring MVC的高级特性,并手把手完成SSM环境的无缝整合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Spring MVC核心机制深度解析
2.1 请求处理流程的底层原理
Spring MVC的核心是DispatcherServlet,它本质上是一个前端控制器。但很多人并不清楚请求在到达Controller之前经历了哪些关键步骤:
- MultipartResolver:首先检查是否为文件上传请求
- LocaleResolver:处理国际化相关的Locale解析
- ThemeResolver:解决主题资源加载
- HandlerMapping:这才是真正决定哪个Controller处理请求的关键
java复制// 自定义HandlerMapping示例
public class CustomHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String uri = request.getRequestURI();
if (uri.startsWith("/api/v2")) {
request.setAttribute("API_VERSION", "v2");
}
return super.getHandlerInternal(request);
}
}
2.2 参数绑定的高级技巧
Spring MVC提供了强大的参数绑定机制,但实际开发中我们经常需要自定义:
java复制@GetMapping("/search")
public String searchProducts(
@RequestParam(value = "page", defaultValue = "1") int page,
@MatrixVariable(pathVar = "category", required = false) String categoryFilter,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime) {
// 业务逻辑
}
注意:使用@MatrixVariable需要显式开启配置:
xml复制<mvc:annotation-driven enable-matrix-variables="true"/>
2.3 视图解析的进阶配置
Thymeleaf与JSP的混合使用是个常见需求:
java复制@Configuration
public class ViewConfig implements WebMvcConfigurer {
@Bean
public ViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine);
resolver.setOrder(1);
resolver.setViewNames(new String[]{"thymeleaf/*"});
return resolver;
}
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(2);
return resolver;
}
}
3. SSM整合实战全流程
3.1 环境搭建与版本控制
SSM整合最大的坑就是版本兼容性问题。这是我验证过的稳定组合:
| 组件 | 推荐版本 | 关键依赖 |
|---|---|---|
| Spring | 5.3.18 | spring-webmvc, spring-jdbc |
| MyBatis | 3.5.9 | mybatis-spring |
| MySQL | 8.0.28 | mysql-connector-java |
| Druid | 1.2.8 | druid-spring-boot-starter |
在pom.xml中需要特别注意排除冲突的依赖:
xml复制<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 MyBatis与Spring的深度整合
3.2.1 数据源配置技巧
Druid是目前最常用的连接池,配置时需要特别注意:
java复制@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid")
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setConnectionInitSqls(Arrays.asList("SET NAMES utf8mb4"));
return ds;
}
@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> reg = new ServletRegistrationBean<>();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("loginUsername", "admin");
reg.addInitParameter("loginPassword", "admin123");
return reg;
}
}
3.2.2 事务管理的正确姿势
声明式事务看似简单,但有很多隐藏细节:
java复制@Configuration
@EnableTransactionManagement
public class TxConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource);
tm.setDefaultTimeout(30); // 单位秒
tm.setNestedTransactionAllowed(true);
return tm;
}
@Bean
public TransactionInterceptor txAdvice(PlatformTransactionManager tm) {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setRollbackFor(Exception.class);
source.addTransactionalMethod("get*", readOnlyTx);
source.addTransactionalMethod("query*", readOnlyTx);
source.addTransactionalMethod("list*", readOnlyTx);
source.addTransactionalMethod("*", requiredTx);
return new TransactionInterceptor(tm, source);
}
}
3.3 整合过程中的典型问题解决
3.3.1 循环依赖问题
在SSM项目中,Service之间相互调用很常见,容易导致循环依赖。推荐解决方案:
- 使用@Lazy延迟加载
- 通过setter方法注入而非构造器注入
- 重构代码,提取公共逻辑到新Service
java复制@Service
public class OrderService {
@Lazy
@Autowired
private UserService userService;
// 替代方案:使用setter注入
public void setUserService(UserService userService) {
this.userService = userService;
}
}
3.3.2 MyBatis映射文件热加载
开发阶段频繁修改Mapper.xml需要重启很麻烦,可以配置自动重载:
java复制@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml"));
// 开启热加载
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
config.setCacheEnabled(true);
config.setLazyLoadingEnabled(true);
config.setAggressiveLazyLoading(false);
config.setMapUnderscoreToCamelCase(true);
config.setLogImpl(StdOutImpl.class);
config.addInterceptor(new RefreshMapperInterceptor()); // 自定义拦截器
factory.setConfiguration(config);
return factory.getObject();
}
4. 性能优化实战技巧
4.1 MyBatis二级缓存优化
二级缓存使用不当会导致脏读问题,正确的配置方式:
xml复制<!-- mapper.xml中 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="7200"/>
<property name="maxEntriesLocalHeap" value="1000"/>
</cache>
对应的Spring配置:
java复制@Bean(destroyMethod = "shutdown")
public net.sf.ehcache.CacheManager ehCacheManager() {
Configuration config = new Configuration();
config.setDynamicConfig(false);
return net.sf.ehcache.CacheManager.newInstance(config);
}
4.2 批量操作性能提升
MyBatis批量插入的三种方式对比:
| 方式 | 10万条耗时 | 内存占用 | 适用场景 |
|---|---|---|---|
| 循环单条插入 | 120s | 低 | 小批量数据 |
| BatchExecutor | 15s | 中 | 通用场景 |
| 批量SQL拼接 | 5s | 高 | 大数据量一次性提交 |
推荐实现:
java复制public void batchInsert(List<User> users) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
if (i % 1000 == 0) {
session.flushStatements();
}
}
session.commit();
} finally {
session.close();
}
}
5. 安全防护最佳实践
5.1 SQL注入防护
虽然MyBatis的#{}已经能防止大部分SQL注入,但动态SQL仍需注意:
xml复制<!-- 不安全的写法 -->
<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE name LIKE '%${name}%'
</select>
<!-- 安全写法 -->
<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE name LIKE CONCAT('%', #{name}, '%')
</select>
5.2 XSS防御方案
Spring MVC提供几种XSS防护方式:
- 使用HtmlUtils.htmlEscape()手动转义
- 自定义WebDataBinder
- 过滤器方案(推荐)
java复制public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
return cleanXSS(super.getParameter(name));
}
private String cleanXSS(String value) {
if (value == null) return null;
return Jsoup.clean(value, Whitelist.basic());
}
}
6. 监控与诊断方案
6.1 慢SQL监控
结合Druid的内置功能:
java复制@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
bean.addUrlPatterns("/*");
bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.css,/druid/*");
bean.addInitParameter("profileEnable", "true");
bean.addInitParameter("slowSqlMillis", "500");
bean.addInitParameter("logSlowSql", "true");
return bean;
}
6.2 接口性能统计
使用Spring拦截器实现:
java复制public class PerformanceInterceptor extends HandlerInterceptorAdapter {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
startTime.set(System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long cost = System.currentTimeMillis() - startTime.get();
if (cost > 500) {
String uri = request.getRequestURI();
logger.warn("Slow API: {} cost {}ms", uri, cost);
}
startTime.remove();
}
}
7. 测试策略与技巧
7.1 单元测试配置
Spring测试环境的正确搭建方式:
java复制@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:spring/applicationContext.xml",
"classpath:spring/spring-mvc.xml"
})
@WebAppConfiguration
@Transactional
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
@Rollback(true)
public void testCreateUser() {
User user = new User();
user.setUsername("test");
int result = userService.createUser(user);
assertEquals(1, result);
}
}
7.2 接口测试方案
使用MockMvc进行Controller层测试:
java复制@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring/spring-mvc.xml")
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testGetUser() throws Exception {
mockMvc.perform(get("/user/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("admin"));
}
}
8. 项目部署与运维
8.1 多环境配置方案
使用Profile实现环境隔离:
xml复制<!-- applicationContext.xml -->
<beans profile="dev">
<context:property-placeholder location="classpath:config-dev.properties"/>
</beans>
<beans profile="prod">
<context:property-placeholder location="classpath:config-prod.properties"/>
</beans>
启动时激活Profile:
bash复制# Tomcat启动参数
-Dspring.profiles.active=prod
8.2 日志切割策略
Logback的详细配置:
xml复制<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
在SSM项目实战中,我发现很多问题都是由于对框架底层原理理解不深导致的。比如事务失效经常是因为没有正确理解代理机制,MyBatis缓存问题多是对缓存作用域认识不清。建议在掌握基本使用后,多花时间研究框架源码,这能帮助你在遇到问题时快速定位根源。
