# 1 整合SSM(springMVC+spring+mybatis) ## 1.1 依赖 \~\~\~xml 4.0.0 com.wyc ssm 1.0-SNAPSHOT war ssm 17 17 com.alibaba druid 1.2.23 com.mysql mysql-connector-j 8.0.33 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 3.0.3 org.springframework spring-jdbc 6.2.9 org.springframework spring-test 6.2.6 org.springframework spring-webmvc 6.2.6 com.fasterxml.jackson.core jackson-databind 2.15.2 com.fasterxml.jackson.core jackson-core 2.15.2 com.fasterxml.jackson.core jackson-annotations 2.15.2 javax.annotation javax.annotation-api 1.3.2 org.projectlombok lombok 1.18.30 junit junit 4.12 test jakarta.servlet jakarta.servlet-api 5.0.0 provided org.mybatis.generator mybatis-generator-core 1.4.0 org.apache.maven.plugins maven-war-plugin 3.3.2 \~\~\~ ## 1.2 配置类: ### 1 DispatcherServletInitConfig \~\~\~java package com.woniu.config; import jakarta.servlet.Filter; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer; //web容器配置类 代替 web.xml //tomcat启动容器时会自动执行该类的方法来实现容器初始化 public class DispatcherServletInitConfig extends AbstractDispatcherServletInitializer { @Override protected Filter\[\] getServletFilters() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return new Filter\[\]{filter}; } //加载springmvc配置类(管理controller) @Override protected WebApplicationContext createServletApplicationContext() { //初始化基于注解的WebApplicationContext对象 AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); //加载指定配置类--起始配置类 ctx.register(SpringmvcConfig.class); return ctx; } /\* 设置由springmvc控制器处理的请求映射路径 /表示DispatcherServlet将处理所有请求路径 /api/\*表示DispatcherServlet将处理/api开头的请求路径 \*/ @Override protected String\[\] getServletMappings() { return new String\[\]{"/"}; } //返回null代表不使用根应用上下文 //这里用于管理其他层的配置,比如管理Service和DAO @Override protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringConfig.class); return ctx; } //通过servlet的filter过滤器来处理post乱码,到达DispatcherServlet之前触发 //使用Spring内置的CharacterEncodingFilter: } \~\~\~ ### 2 SpringmvcConfig \~\~\~java package com.woniu.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan("com.woniu.controller") //开启json数据类型自动转换 @EnableWebMvc public class SpringmvcConfig { /\* @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/"); resolver.setSuffix(".jsp"); return resolver; }\*/ } \~\~\~ ### 3 SpringConfig \~\~\~java package com.woniu.config; import org.springframework.context.annotation.\*; @Configuration @ComponentScan({"com.woniu.service","com.woniu.dao"}) @Import({MybatisConfig.class,JdbcConfig.class})//@Import 注解会将指定的类注册到IOC中 public class SpringConfig { } \~\~\~ ### 4 MybatisConfig \~\~\~java package com.woniu.config; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.Configuration; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; //@Configuration 该配置类到时候是启动配置类通过@Import导入,因此会自动注册为bean public class MybatisConfig { //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象 //@Bean修饰的方法的参数中可以拿取IOC中已存在的bean对象 @Bean public SqlSessionFactoryBean sqlSessionFactorybean(DruidDataSource druidDataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置别名扫包 sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.domain"); //配置MyBatis设置 开启驼峰命名转换 Configuration configuration = new Configuration(); configuration.setMapUnderscoreToCamelCase(true); sqlSessionFactoryBean.setConfiguration(configuration); //设置数据源 sqlSessionFactoryBean.setDataSource(druidDataSource); return sqlSessionFactoryBean; } //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); //设置Mapper扫包 mapperScannerConfigurer.setBasePackage("com.woniu.dao"); return mapperScannerConfigurer; } } \~\~\~ ### 5 JdbcConfig \~\~\~java package com.woniu.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; //@Configuration @PropertySource("classpath:jdbc.properties") public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("dataSource")//dataSource类似于bean的id,如果不指明,默认bean的id为该方法名 public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setInitialSize(5); druidDataSource.setMinIdle(5); druidDataSource.setMaxActive(20); druidDataSource.setMaxWait(60000); return druidDataSource; } } \~\~\~ ## 1.3 UserController \~\~\~java @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/selectAll") @ResponseBody public List selectAll(){ List users = userService.selectAll(); System.out.println(users); return users; } \~\~\~ ## 1.4 service UserService \~\~\~ public interface UserService { public List selectAll(); } \~\~\~ UserServiceImpl \~\~\~java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public List selectAll() { return userDao.selectAll(); } \~\~\~ ## 1.5 dao UserDao \~\~\~java public interface UserDao { public List selectAll(); } \~\~\~ UserDao.xml (src/main/resources/com/woniu/dao/UserDao.xml) \~\~\~xml select \* from tb_user \~\~\~ ## 1.6 domain \~\~\~java @Data public class User { private Long id; private String username; private String password; private Integer age; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date birth; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createTime; } \~\~\~ ## 1.7 jdbc.properties \~\~\~properties jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/woniu122 jdbc.username=root jdbc.password=123456 \~\~\~ ## 1.8 junit UserServiceTest \~\~\~java package com.woniu; import com.woniu.config.SpringConfig; import com.woniu.domain.User; import com.woniu.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class}) public class UserServiceTest { @Autowired private UserService userService; @Test public void test1(){ List users = userService.selectAll(); System.out.println("users = " + users); } } \~\~\~ # 2 统一结果封装 ## 2.1 用于最终controller层返回 随着业务的增长,我们需要返回的数据类型会越来越多。对于前端开发人员在解析数据的时候就比较凌乱了,所以对于前端来说,如果后台能够返回一个统一的数据结果,前端在解析的时候就可以按照一种方式进行解析。开发就会变得更加简单。 所以我们就想能不能将返回结果的数据进行统一,具体如何来做,大体的思路为: \* data ---- 返回结果 \* code --- 状态码 \* msg --- 消息 Result: \~\~\~java package com.woniu.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Result { private Integer code; private String msg; private T data; //快速返回操作成功响应结果(带响应数据) public static Result success(T data) { return new Result(0, "操作成功", data); } //快速返回操作成功响应结果 public static Result success() { return new Result(0, "操作成功", null); } //快速返回操作失败响应结果 public static Result error(String message) { return new Result(1, message, null); } } \~\~\~ UserController: \`\`\`java @RequestMapping("/selectAll") @ResponseBody public Result\> selectAll(){ List users = userService.selectAll(); return Result.success(users); } \`\`\` ## 2.2 用于分页 PageResult: \~\~\~java @Data @AllArgsConstructor @NoArgsConstructor public class PageResult { private Long total;//总条数 private List list;//分页集合 } \~\~\~ # 3 @RestController 标记为controller层的bean + 所有方法都将把对象转为json @RestController = @Controller + @ResponseBody # 4 统一异常处理 ## 4.0为什么需要全局异常处理: 1 统一异常处理 避免在每个业务方法中重复编写try-catch代码 集中处理所有异常,保持代码整洁 2 提升用户体验 将系统异常转换为用户友好的提示信息 避免直接暴露系统堆栈信息给前端 3 便于维护 异常处理逻辑集中在一个地方 修改异常处理策略时只需修改一处 4 完整的异常分类处理 区分业务异常(ServiceException)和系统异常 不同类型异常可以有不同的处理策略 ## 4.1 新建业务异常类 !\[image-20250925210428534\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250925210428.png) ServiceException: \~\~\~java @Data @AllArgsConstructor @NoArgsConstructor public class ServiceException extends RuntimeException{ private int code; private String msg; } \~\~\~ ErrCode枚举:用来自定义异常code和异常msg \~\~\~java package com.woniu.exception; import lombok.Getter; @Getter public enum ErrCode { //自定义业务异常 SAVE_ERR(5001,"保存失败"), UPDATE_ERR(5002,"更新失败"), NO_EXISTS_USER(5003,"用户不存在"); private final int code; private final String msg; ErrCode(int code, String msg) { this.code = code; this.msg = msg; } } \~\~\~ ## 4.2 全局异常处理类 GlobalExceptionHandler: !\[image-20250925210551594\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250925210551.png) \~\~\~java package com.woniu.handler; import com.woniu.domain.Result; import com.woniu.exception.ServiceException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; //@RestControllerAdvice =@ControllerAdvice +@ResponseBody //使该类成为全局异常处理器 @RestControllerAdvice public class GlobalExceptionHandler { //捕获业务异常 @ExceptionHandler(ServiceException.class) public Result handleException(ServiceException e){ e.printStackTrace(); return new Result(e.getCode(),e.getMsg(),null); } //兜底 @ExceptionHandler(Exception.class) public Result handleException(Exception e){ e.printStackTrace(); return Result.error(e.getMessage()); } } \~\~\~ 核心注解说明:@RestControllerAdvice + @ExceptionHandler @RestControllerAdvice 1 异常捕获发生在整个请求处理流程中 2 从Controller → Service → DAO的调用链中抛出的异常都会向上传播 3 @RestControllerAdvice 会捕获整个调用链中未处理的异常(如果自己try-catch了则不会捕获) @ExceptionHandler 1 Spring会识别该注解并将相应异常路由到标记的方法中 2 进入到指定异常方法后,springmvc会直接响应该方法的返回结果 ## 4.3 异常处理类需要被包扫描到 !\[image-20250925211720155\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250925211720.png) ## 4.4 捕获异常的方法 \~\~\~java package com.woniu.exception; import com.woniu.entity.Result; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /\*\* \* 全局异常处理 \*/ @RestControllerAdvice //=@ControllerAdvice +@ResponseBody public class GlobalExceptionHandler { // @ExceptionHandler(ArithmeticException.class) // public Result handleMyException(ArithmeticException ex){ // System.out.println(ex.getMessage()); // Result res = new Result(); // res.setCode(ErrCode.ZERO_ERR.getCode()); // res.setMsg(ErrCode.ZERO_ERR.getMsg()); // return res; // } /\*\* \* 业务异常 \* @param ex \* @return \*/ @ExceptionHandler(ServiceException.class) public Result handleException(ServiceException ex){ System.out.println(ex.getMessage()); Result res = new Result(); res.setCode(ex.getCode()); res.setMsg(ex.getMsg()); return res; } /\*\* \* 兜底的系统异常 \* @param ex \* @return \*/ @ExceptionHandler(Exception.class) public Result handleException(Exception ex){ System.out.println(ex.getMessage()); Result res = new Result(); res.setCode(ErrCode.SYSTEM_ERR.getCode()); res.setMsg(ErrCode.SYSTEM_ERR.getMsg()); return res; } } \~\~\~ ## 4.5 模拟异常 !\[image-20250925211748416\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250925211748.png) # 5 Spring 事务自动提交机制 Spring 在没有显式配置事务的情况下会自动提交事务,无需手动调用 commit() 方法 如果在一个业务中,需要保证多组sql操作的原子性,需要添加 @Transactional @Transactional可以作用于方法和类上,如果是类,则代表该类下所有方法都生效 推荐在具体的方法上添加@Transactional ## 5.1PlatformTransactionManager spring中管理事务的核心组件:PlatformTransactionManager:Spring事务管理的核心接口,负责管理事务的生命周期,包括开启、提交和回滚事务等操作。 事务一般用于service层 \~\~\~ \~\~\~ ## 5.2 开启事务管理 通过@EnableTransactionManagement + 把PlatformTransactionManager放入IOC来实现 SpringConfig: \~\~\~java @EnableTransactionManagement//启用了事务管理的功能 public class SpringConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { //DataSourceTransactionManager是PlatformTransactionManager的一个实现类 //它专门用于管理基于DataSource的数据源事务,通过构造函数注入DataSource,与具体的数据库连接关联 return new DataSourceTransactionManager(dataSource); } } \~\~\~ ## 5.3 事务分类 1 声明式事务 在service实现类中某个方法上写@Transactional ,则该方法会自动用到事务 在service实现类上写@Transactional ,则该类下所有方法都会自动用到事务 2 编程式事务 通过TransactionTemplate在代码中手动管理事务----细粒度更精确,但比声明式麻烦 ## 5.4 声明式事务(需要开启事务管理) @Transactional 注解标记了方法需要事务管理 Spring框架会从IOC容器中获取之前注册的 PlatformTransactionManager 实例 当方法执行时,PlatformTransactionManager 负责开启事务、提交或回滚事务 如果方法执行过程中出现异常,事务管理器会自动回滚事务,保证数据一致性 提供UserDao.java+UserDao.xml \~\~\~java public interface UserDao { void save(User user); } \~\~\~ \~\~\~xml insert into tb_user (username, password, age, birth, create_time) values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=TIMESTAMP}, now()) \~\~\~ 提供BookDao.java+BookDao.xml \~\~\~java public interface BookDao { void save(Book book); } \~\~\~ \~\~\~xml insert into tb_book (book_name, book_price, book_desc, type_id, img, create_time) values (#{bookName,jdbcType=VARCHAR}, #{bookPrice,jdbcType=DECIMAL}, #{bookDesc,jdbcType=VARCHAR}, #{typeId,jdbcType=INTEGER}, #{img,jdbcType=VARCHAR}, now()) \~\~\~ UserServiceImpl: 当没有@Transactional时,用户添加成功,书籍添加失败 当有@Transactional时,用户添和书籍都会添加失败,证明@Transactional事务具有原子性,要么都成功,要么都失败 \~\~\~ @Transactional @Override public void testTr() { //添加用户 User u=new User();u.setUsername("www2"); userDao.save(u); //模拟异常 int a=1/0; //添加书籍 Book b=new Book();b.setBookName("www2"); bookDao.save(b); } \~\~\~ ## 5.5 编程式事务(需要开启事务管理) 1 提供更精细的控制能力,通过代码显式控制事务边界 2 可以在事务中根据业务逻辑决定是否回滚:status.setRollbackOnly() 3 需要手动把TransactionTemplate放入IOC中 SpringConfig: \~\~\~java @Configuration @ComponentScan({"com.woniu.service","com.woniu.mapper"}) @Import({MybatisConfig.class,JdbcConfig.class})//@Import 注解会将指定的类注册到IOC中 @EnableTransactionManagement//启用了事务管理的功能 public class SpringConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { //DataSourceTransactionManager是PlatformTransactionManager的一个实现类 //它专门用于管理基于DataSource的数据源事务,通过构造函数注入DataSource,与具体的数据库连接关联 return new DataSourceTransactionManager(dataSource); } @Bean public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { return new TransactionTemplate(transactionManager); } } \~\~\~ \~\~\~java @Autowired private TransactionTemplate transactionTemplate; @Override public void testTr() { //TransactionCallbackWithoutResult:代表无返回值 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // 执行业务逻辑 //添加用户 User u=new User();u.setUsername("woniu6"); userDao.save(u); //模拟异常 int a=1/0; //添加书籍 Book b=new Book();b.setBookName("woniu6"); bookDao.save(b); // 无需手动调用提交,正常执行完自动提交, // 无需手动调用回滚,如果异常会自动回滚 // 如果需要手动回滚,可以调用: status.setRollbackOnly(); } }); \~\~\~ # 6 引入slf4j+logback日志 ## 6.1依赖: \~\~\~xml org.slf4j slf4j-api 1.7.20 ch.qos.logback logback-classic 1.2.3 ch.qos.logback logback-core 1.2.3 \~\~\~ ## 6.2 resources下添加logback.xml 需要更改的代码: \~\~\~xml \~\~\~ \~\~\~xml %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n \~\~\~ # 7 mybatis分页插件-pageHelper ## 7.1原理 通过 MyBatis 插件机制拦截 SQL,动态改写 SQL 实现分页 具体的有两步: 1 拦截目标sql,追加limit命令 2 生成count查询总条数的sql ## 7.2 添加依赖 \~\~\~xml com.github.pagehelper pagehelper 5.3.2 \~\~\~ ## 7.3 MybatisConfig配置 \~\~\~java @Bean public SqlSessionFactoryBean sqlSessionFactorybean(DruidDataSource druidDataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置别名扫包 sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.domain"); //配置MyBatis设置 开启驼峰命名转换 Configuration configuration = new Configuration(); configuration.setMapUnderscoreToCamelCase(true); sqlSessionFactoryBean.setConfiguration(configuration); //设置数据源 sqlSessionFactoryBean.setDataSource(druidDataSource); //配置PageHelper分页插件 PageInterceptor pageInterceptor = new PageInterceptor(); Properties properties = new Properties(); //数据库方言,根据你使用的数据库选择 properties.setProperty("helperDialect", "mysql"); //是否启用分页合理化,如 pageNum \< 1 时自动转为 1,如果pageNum\>pages则会查询最后一页 properties.setProperty("reasonable", "true"); pageInterceptor.setProperties(properties); sqlSessionFactoryBean.setPlugins(pageInterceptor); return sqlSessionFactoryBean; } \~\~\~ ## 7.4 userDto \~\~\~java @Data public class UserDto { private int pageNum; private int pageSize; \~\~\~ ## 7.5 controller \~\~\~java @RequestMapping("/page") public Result\> page(@RequestBody UserDto userDto){ PageResult pageResult = userService.page(userDto); return Result.success(pageResult); } \~\~\~ ## 7.6 service 注意顺序不能变:启用分页插件--\>查询结果集---\>加载到pageInfo \~\~\~java @Override public PageResult page(UserDto userDto) { //启用分页插件 PageHelper.startPage(userDto.getPageNum(),userDto.getPageSize()); //查询结果集(sql中无需写分页相关命令) List list = userDao.selectAll(); //把结果集list加载到PageInfo中 PageInfo pageInfo = new PageInfo\<\>(list); //封装自己的PageResult return new PageResult(pageInfo.getList(),pageInfo.getTotal()); } \~\~\~ ## 7.7 dao \`\`\` public List selectAll(); \`\`\` \`\`\`xml select \* from tb_user \`\`\` # 8 拦截器 ## 8.1 介绍 !\[image-20250926102144302\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926102144.png) (1)浏览器发送一个请求会先到Tomcat的web服务器 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源 (3)如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问 (4)如果是动态资源,就需要交给项目的后台代码进行处理 (5)在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行 (6)然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截 (7)如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果 (8)如果不满足规则,则不进行处理 拦截器作用: 如果我们需要在每个Controller方法执行的前后添加业务 \* 在指定的controller方法调用前后执行预先设定的代码(功能增强) \* 阻止原始方法的执行 ## 8.2 拦截器和过滤器区别 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强 先后顺序不同:先进入Filter,再进入Interceptor !\[image-20250926102502781\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926102502.png) ## 8.3 实现自定义拦截器 位置: !\[image-20250926102705443\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926102705.png) 让类实现HandlerInterceptor接口,重写接口中的三个方法。 MyInterceptor: \~\~\~java package com.woniu.interceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; @Component//必须注册为bean容器 public class MyInterceptor implements HandlerInterceptor { //原始方法调用前执行的内容 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //返回true代表放行,false中断执行(正常响应前端会200,但是响应体为空) return true; } //原始方法调用后执行的内容,controller的return响应前执行-----用得少,比如要修改数据等 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } //原始方法调用完成后执行的内容,一般用释放资源 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } \~\~\~ 注意: 1 顺序: 访问请求接口---\>拦截器的preHandle---\>目标controller的return之前---\>拦截器的postHandle---\>拦截器的afterCompletion 2 拦截器类要被SpringMVC容器扫描到。 ## 8.4 SpringmvcConfig扫描拦截器包 !\[image-20250926103354130\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926103354.png) ## 8.5 注册自定义拦截器,使之生效 SpringmvcConfig: \~\~\~java package com.woniu.config; import com.woniu.interceptor.MyInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan({"com.woniu.controller","com.woniu.handler","com.woniu.interceptor"}) //开启json数据类型自动转换 @EnableWebMvc public class SpringmvcConfig implements WebMvcConfigurer { @Autowired private MyInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //注册拦截器 registry.addInterceptor(interceptor) .addPathPatterns("/\*\*") //所有请求都被拦截 .excludePathPatterns("/user/login","/user/register");//不被拦截的请求 } } \~\~\~ 可以注册多个拦截器: !\[image-20250926104513416\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926104513.png) 扩展: WebMvcConfigurer 是 Spring MVC 的配置接口 我们看到 addInterceptors 只是 WebMvcConfigurer 其中用来注册拦截器的配置,他还有很多其他配置,比如配置跨域、静态资源处理、视图控制器、 格式化器配置、参数解析器等, 但是一般我们只需要重新需要的配置,而其他的配置默认即可 ## 拦截器使用场景 1 数据预处理(跨域) 2 用户登录状态认证 3 多租户环境下的租户识别 。。。。 # 9 跨域-基于拦截器 跨域(Cross-Origin Resource Sharing,简称 CORS)是一种安全策略,用于限制一个域的网页如何与另一个域的资源进行交互。这是浏览器实现的同源策略(Same-Origin Policy)的一部分,旨在防止恶意网站通过一个域的网页访问另一个域的敏感数据。 由于浏览器实施的同源策略(Same Origin Policy),这是一种基本的安全协议,它确保了浏览器的稳定运行。没有同源策略,浏览器的许多功能可能无法正常工作。整个Web体系建立在同源策略之上,浏览器是这一策略的具体实现。该策略禁止来自不同域的JavaScript脚本与另一个域的资源进行交互。所谓同源,指的是两个页面必须具有相同的协议(protocol)、域名(host)和端口号(port)。 !\[\](https://developer.qcloudimg.com/http-save/yehe-8411071/37a270d50ebd678f280778b3f9084b15.png) 跨域的各种解决方案 https://cloud.tencent.com/developer/article/2425759 SpringmvcConfig: \`\`\`java //跨域设置 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/\*\*")//代表过滤器要拦截的请求,等价于之前的@WebFilter("/\*") .allowedOrigins("\*")//允许所有域能够访问该资源 .allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")//允许以下提交方式可以访问 .allowedHeaders("\*");//允许所有请求头 } \`\`\` SpringmvcConfig: \`\`\`java //跨域设置 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/\*\*")//代表过滤器要拦截的请求,等价于之前的@WebFilter("/\*") .allowedOrigins("http://localhost:8080")//这里要写前端具体的域名 .allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")//允许以下提交方式可以访问 .allowedHeaders("Content-Type","Authorization")//允许所有请求头(不能用\*) .allowCredentials(true);//允许前端携带凭证 } \`\`\` ### 说明 1 Spring MVC如果检测到是 OPTIONS 跨域预检请求,框架自动处理并返回响应请求处理结束,不会进入业务 Controller,因此以前配置的预检特殊处理,在springmvc框架中不存在这个问题了: !\[image-20250926110548976\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926110549.png) 2 springMVC中 /\*:单级路径匹配 只匹配当前目录下的一级路径 例如:匹配 /user、/api,但不匹配 /user/list 或 /api/v1/users /\*\*:多级路径匹配 匹配所有层级的路径 例如:匹配 /user、/user/list、/api/v1/users 等所有路径 servlet的filter中 /\* 等价于 springMVC中/\*\* # 10 springMVC-文件上传 ## 10.1 DispatcherServletInitConfig配置 重写onStartup方法 \`\`\`java @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); // 调用父类方法完成标准初始化 // 添加multipart配置 ServletRegistration.Dynamic registration = (ServletRegistration.Dynamic) servletContext.getServletRegistration("dispatcher"); if (registration != null) { // 参数顺序 // null - 临时文件存储位置,null表示使用默认位置 // 限制单个上传文件的大小 1024 \* 1024 \* 10, 10MB // 限制该次请求的所有文件最大 1024 \* 1024 \* 50, 50MB // 超过此大小的文件将被写入磁盘 1024 \* 1024 \* 2 2MB MultipartConfigElement multipartConfig = new MultipartConfigElement (null, 1024 \* 1024 \* 10, 1024 \* 1024 \* 50, 1024 \* 1024 \* 2); registration.setMultipartConfig(multipartConfig); } } \`\`\` 说明:关于字符串dispatcher代表servlet容器名,该名子来自于父类 ServletRegistration.Dynamic registration = (ServletRegistration.Dynamic) servletContext.getServletRegistration("dispatcher"); \~\~\~ 1 已知 DispatcherServletInitConfig extends AbstractDispatcherServletInitializer 2 查看父类 AbstractDispatcherServletInitializer 3 可以看到第一行: public static final String DEFAULT_SERVLET_NAME = "dispatcher"; \~\~\~ ## 10.2 UploadController \~\~\~java package com.woniu.controller; import com.woniu.domain.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @RestController @RequestMapping("/upload") public class UploadController { //定义要保存的磁盘目录 private static final String UPLOAD_DIR = "C:\\\\imgs\\\\"; @RequestMapping("/book") public Result uploadBook(@RequestParam("file") MultipartFile file){ if (!file.isEmpty()) { try { String fileName = file.getOriginalFilename(); String newFileName= UUID.randomUUID().toString().replaceAll("-","")+fileName; file.transferTo(new File(UPLOAD_DIR + newFileName)); return Result.success(); } catch (IOException e) { return Result.error("上传失败"); } } return Result.error("上传失败"); } } \~\~\~ ## 10.3 postman测试 ## !\[image-20250927234021196\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250927234021.png)