ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot + MyBatis 跨域问题全解析:从原理到CORS配置实战

Spring Boot + MyBatis 跨域问题全解析:从原理到CORS配置实战 跨域这个词几乎所有做前后端分离的Java开发都绕不开。尤其是用了Spring Boot MyBatis这套组合拳的项目前后端联调时十有八九会撞上那个经典的报错CORS policy: No Access-Control-Allow-Origin header is present。我在面试候选人的时候也几乎必问跨域但说实话能把跨域是什么、为什么出问题、Spring Boot里怎么解决这三层逻辑一气呵成讲清楚的人十个人里未必有一个。今天就把我这些年实际项目中踩过的跨域坑、用Spring Boot MyBatis做后端时总结的解法连同面试时的标准答法一次性整理出来。这篇文章适合三类人看马上要面试、被跨域 bug 折磨的 Junior 开发刚把 Spring Boot 和 MyBatis 搭起来、准备写第一个接口的新手以及想知道除了加个注解之外跨域背后到底还有多少细节的老手。我会把原理、代码、配置、坑位全部铺开争取让你看完之后不仅能解决自己的项目问题还能在面试官面前把这道题答出亮点。1. 跨域请求是什么先搞懂浏览器的门禁系统1.1 同源策略浏览器为什么管这么宽要讲跨域必须先讲同源策略Same-Origin Policy。这是浏览器最核心的安全机制它的存在很久远了从 Netscape Navigator 时代就有了目的非常朴素保护你的登录状态和 cookie 不被恶意网站窃取。所谓同源指三个条件同时满足协议相同http 和 https 算不同源域名相同www.a.com和api.a.com算不同源端口相同a.com:8080和a.com:9090算不同源只要三者有任何一个不同浏览器就认为是跨域。我用一个生活化的类比来解释同源策略就像小区物业的门禁系统你住在 3 栋 502你的朋友住在 3 栋 503门禁允许你进出但如果你想去隔壁 4 栋串门门禁系统就会拦下来因为那栋楼的门禁规则不是你这个小区的。浏览器就是你电脑上的物业它替你守着前端的门凡是跨域的请求都要接受更严格的审查。1.2 跨域请求的三个典型场景实际开发中跨域几乎天天见。最常见的三种前后端分离部署前端跑在localhost:8080Vue 或 React 开发服务器后端跑在localhost:9090Spring Boot端口不一样跨域。不同域名调用前端在www.store.com后端接口在api.store.com域名不一样跨域。协议不一致线上环境前端是https://www.store.com后端却还是http://api.store.com协议不一样同样跨域。我的第一个 Spring Boot MyBatis 项目就是典型的场景一前端用 Vue 脚手架启动在 8080 端口后端 Spring Boot 启动在 9090 端口结果前端一调接口就报跨域当时的我还以为是后端接口写错了折腾了半天才发现问题出在浏览器这一层。1.3 跨域请求到底拦截了谁很多人理解反了这是面试中我最喜欢追问的一个点。大部分人的理解是浏览器拦截了跨域请求这个说法不准确。真实情况是跨域请求发出去了服务器也处理了响应也返回了但浏览器把响应拒收了。你可以理解为快递员已经把包裹送到了小区门口但门卫浏览器发现快递单上的寄件地址不是小区认可的直接把包裹退回快递员还会给你报一个拒收的错误。有个特别好的验证方法打开 Chrome 的 DevTools 切到 Network 面板跨域请求会显示红色的失败状态但点击这个请求看 Response 内容往往会发现服务器其实已经返回了数据。这正是因为拦截发生在浏览器这一层服务端日志里甚至能看到这个请求的执行记录。很多新手调试跨域问题时会觉得后端明明没报错前端就是拿不到数据根源就在这里。1.4 简单请求与预检请求CORS 的两种玩法CORSCross-Origin Resource Sharing跨域资源共享机制把请求分成了两类简单请求GET、POST 且 Content-Type 为application/x-www-form-urlencoded、multipart/form-data、text/plain之一没有自定义头。预检请求除此之外的请求比如 PUT、DELETE、带Authorization头、带application/json的 POST浏览器会先发一个OPTIONS请求探路服务器确认允许后才会发真正的请求。这个区别太重要了。很多人在 Spring Boot 里明明配置了跨域但偏偏 PUT 和 DELETE 请求还是报错就是因为预检请求里的OPTIONS没处理好。后面我会详细讲这个坑。2. 跨域问题有哪些解法三套方案背后的取舍2.1 JSONP老牌的曲线救国JSONP 是最早的跨域解决方案之一核心原理是利用script标签不受同源策略限制的特性动态插入一个 script 标签把回调函数名传给服务器服务器返回一段 JS 代码浏览器执行这段代码就拿到数据了。我可以给你看一个典型的 JSONP 接口返回格式// 前端请求http://api.store.com/user?id1callbackhandleUser // 后端返回 handleUser({id: 1, name: 张三});但 JSONP 有两个致命缺点只能支持 GET 请求、无法处理 RESTful 风格的复杂接口。在 Spring Boot MyBatis 这种以 JSON 数据交互为主的项目里JSONP 基本已经被扫进历史垃圾桶了。我提它是为了让你知道这个方案存在但强烈不推荐在实际业务中用除非你在维护那种非常古老的老系统。2.2 CORS现代后端的主流解法CORS 是 W3C 制定的标准机制靠 HTTP 头完成跨域授权。服务端只要在响应头里声明我允许你来跨域访问浏览器就会放行。核心响应头包括Access-Control-Allow-Origin允许的源可以是*也可以是具体的域名Access-Control-Allow-Methods允许的 HTTP 方法Access-Control-Allow-Headers允许的自定义请求头Access-Control-Max-Age预检请求的有效期可以避免每次都发 OPTIONS用中文说就是服务器在快递单上加了一行字这栋楼的人来取件放行。浏览器看到这个放行声明才会把响应数据交给前端代码。这是目前前后端分离项目中最主流、最标准的跨域解决方案也是我接下来要重点讲的 Spring Boot 方案。2.3 反向代理生产环境和本地开发的两张脸还有一种思路是让浏览器完全感知不到跨域通过 Nginx 做反向代理把/api/路径的请求转发到后端服务。比如前端页面在www.store.comNginx 配置里把www.store.com/api/转发到localhost:9090这样浏览器看到的请求是www.store.com/api/...同源了自然不会触发跨域。本地开发时也可以这么干Vue CLI 的 devServer 代理配置就是一个常见的实现方式。但还是那句话这种方式适合不想/不能改后端逻辑的场景生产环境用 Nginx 转发很常见但对于我们 Spring Boot 开发者来说自己后端把 CORS 配好前端直连也没问题代理反而增加了一层复杂度。三种方案对比下来我的结论非常明确现代前后端分离项目后端用 Spring Boot 配置 CORS 是最合理的选择。代码可控、不需要额外部署 Nginx、也符合 RESTful API 的设计规范。3. Spring Boot 如何处理跨域三种方法实测对比Spring Boot 处理跨域非常方便毕竟它主打的就是约定大于配置。我有三种方式可以用分别适合不同的场景。这里我以实际的项目为例后端是 Spring Boot 2.7 MyBatis前端是 Vue 3 开发服务器跑在 8080 端口。3.1 方式一实现 WebMvcConfigurer 接口全局配置这是我最推荐的方式也是大多数 Spring Boot 项目的标准做法。新建一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里有四个关键点需要解释addMapping(/**)匹配所有路径也可以精确到某个 Controller 比如/api/**allowedOriginPatterns(*)允许所有来源。注意这里我用了Patterns而不是allowedOrigins这是因为后面allowCredentials(true)和*是冲突的Spring 5.3 之后会直接报错用allowedOriginPatterns可以绕过这个限制allowCredentials(true)允许携带凭证cookie、Authorization 头maxAge(3600)预检请求的缓存时间单位秒设置后一个小时内不会再重复发 OPTIONS用这种方式的好处是全局生效不管有多少个 Controller、多少个接口统统覆盖不用担心遗漏。3.2 方式二CrossOrigin 注解细粒度控制如果不希望全局启用跨域只想让某一个 Controller 或某个方法允许跨域可以用CrossOrigin注解RestController RequestMapping(/api/user) CrossOrigin(origins http://localhost:8080, maxAge 3600) public class UserController { GetMapping(/{id}) public User getUser(PathVariable Long id) { return userService.getById(id); } }可以加在类上也可以加在方法上会精确控制到每一个接口。这种方式适合那种内部服务之间调用的场景比如你有一个管理后台只允许特定域名的前端访问那就在对应 Controller 上精确配置来源域名。不过实际经验告诉我如果项目里多个 Controller 都需要跨域注解就得重复写很多遍容易漏不如全局配置干净。3.3 方式三CorsFilter 过滤器第三种方案是注册一个CorsFilterBeanConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }CorsFilter是 Spring Web 提供的一个过滤器实现它不依赖 Spring MVC 的WebMvcConfigurer扩展点而是直接在 Servlet 过滤器链的层面处理跨域。这在一些特殊场景下尤其有用比如你的项目里还配置了 Shiro 或 Spring Security过滤器的执行顺序可能会影响跨域头是否能成功加上。用CorsFilter注册时你可以通过FilterRegistrationBean精确控制它的执行顺序。3.4 三种方式的区别面试官最想听的重点这三种方式表面上都是配置 CORS但底层机制不同面试时如果能说出来就很加分CrossOrigin注解和WebMvcConfigurer走的是 Spring MVC 的 Handler 处理链路在HandlerMapping阶段就会校验和写入 CORS 头CorsFilter走的是标准的 Servlet 过滤器链路比 MVC 的 Handler 执行得更早能拦截更多请求类型我实测过的结论是对于绝大多数 Spring Boot MyBatis 项目用方式一就够了代码最少、最容易维护。但如果项目里加了 Spring Security必须用方式三或者显式指定过滤器顺序否则安全框架的过滤器会先拦截跨域请求导致 CORS 响应头加不上接口照样报跨域错误。还有一个坑我必须提醒如果你在启动类上面直接加了CrossOrigin这个注解通常不起作用因为它要用在 Controller 或对应的方法上放在启动类上只有EnableWebMvc这类注解才有意义。4. 实操过程Spring Boot 整合 MyBatis 并配置跨域的完整工程纸上谈兵讲完了下面直接上一套完整的工程。我从零开始搭建了一个 Spring Boot MyBatis 用户管理模块前端从 Vue devServer 跨域请求整个链路都测通了。你可以照着这个步骤从零搭一遍。4.1 工程结构与依赖准备我的项目结构是这样的springboot-cors-demo ├── pom.xml └── src/main/java/com/example/demo ├── DemoApplication.java ├── config/CorsConfig.java ├── controller/UserController.java ├── entity/User.java ├── mapper/UserMapper.java └── service/UserService.javapom.xml里的关键依赖就两个都是 Spring Boot 全家桶风格的 starterdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency很多人问我 MyBatis 的 starter 版本怎么定我的经验是先看 Spring Boot 主版本。如果你的 Spring Boot 是 2.7.x用mybatis-spring-boot-starter2.x 系列就行如果 Spring Boot 升到 3.x 了建议至少用 3.0 以上的 MyBatis starter。版本太高或太低都会出现各种奇奇怪怪的兼容问题这个在 Spring Boot 版本迭代里是家常便饭。4.2 配置 application.yml我的常用配置模板server: port: 9090 spring: datasource: url: jdbc:mysql://localhost:3306/cors_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里有两个细节要讲。第一map-underscore-to-camel-case: true非常关键。MySQL 表字段习惯用下划线命名比如user_nameJava 实体类习惯用驼峰命名userName开启这个配置后 MyBatis 会自动做映射不需要每个字段都写resultMap。第二log-impl: org.apache.ibatis.logging.stdout.StdOutImpl是在控制台打印 MyBatis 执行的 SQL 语句。新手学 MyBatis 时一定要开这个配置不然 SQL 写错了根本看不出问题在哪。排查问题的时候看控制台打印出来的 SQL、参数、返回结果数效率至少翻两倍。生产环境再关掉别把日志刷爆了。4.3 编写实体类与 Mapper 接口实体类很朴素public class User { private Long id; private String userName; private String email; // 省略 getter/setter }Mapper 接口Mapper public interface UserMapper { User selectById(Param(id) Long id); ListUser selectList(); }对应的 XML 文件放在resources/mapper/UserMapper.xml?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN https://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper select idselectById resultTypecom.example.demo.entity.User SELECT id, user_name, email FROM user WHERE id #{id} /select select idselectList resultTypecom.example.demo.entity.User SELECT id, user_name, email FROM user /select /mapperMyBatis 的 SQL 写在 XML 里和 Java 代码分离维护起来很清晰。这里要提醒一个新手常见错误Mapper注解和MapperScan不要重复使用。如果启动类上加了MapperScan(com.example.demo.mapper)那 Mapper 接口上就不需要再写Mapper了两者只用一个就好写了两个也不会报错但很冗余。4.4 Controller 层和跨域配置实际效果UserController 和跨域配置代码我合在一起展示RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; GetMapping(/{id}) public User getUser(PathVariable Long id) { return userService.getById(id); } GetMapping(/list) public ListUser getUserList() { return userService.getList(); } }配合前面写的CorsConfig启动项目后前端 Vue 的axios.get(http://localhost:9090/api/user/list)就能正常拿到数据了。我前端实测的跨域状态可以分享给你请求 URL 是http://localhost:9090/api/user/list浏览器地址栏是http://localhost:8080Network 面板里可以看到响应头中多了Access-Control-Allow-Origin: http://localhost:8080这行就是后端 CORS 配置生效的直接证据。4.5 一个完整的登录态跨域案例Credentials 的坑上面讲的是无凭证的接口实际业务里跨域和登录态一起出现才是真正的挑战。假设前端把登录接口返回的 token 放在Authorization头里后面所有请求都要带这个头。这时候你如果后端配置的是allowedHeaders(*)在 Spring Boot 老版本里可能没问题但某些版本和浏览器组合下自定义头会被浏览器判为非简单请求触发预检。预检请求的OPTIONS请求里浏览器会带上Access-Control-Request-Headers: authorization如果你的后端没有显式允许这个头浏览器就直接把请求拦了前端控制台会报一个非常迷惑的错误。解决办法就是配置里显式允许allowedHeaders(Authorization, Content-Type)或者干脆用allowedHeaders(*)放开所有请求头。我建议在内部项目里直接放开所有头不要在这种地方给自己找麻烦。另一个更深的坑是携带 Cookie 的跨域。如果登录态是依赖 Cookie 的比如 Session 模式那前端的axios必须设置withCredentials: true而后端的allowCredentials(true)也要开启并且allowedOriginPatterns不能是*必须指定具体的域名。这是 CORS 规范的硬性要求Access-Control-Allow-Origin为*的时候不允许Access-Control-Allow-Credentials为true。很多人在这一步踩坑前端明明都配好了后端也配了*加true结果浏览器依然报错就是因为这个规范限制。用 Spring Boot 的allowedOriginPatterns(*)配合allowCredentials(true)可以正常绕过但如果你用 Nginx 转发或者手写响应头就得自己对域名做精确匹配。5. 常见问题与排查技巧跨域和 MyBatis 的那些坑5.1 排查跨域问题的标准步骤我调试跨域问题时有一套固定的排查思路按照这个顺序走基本能在五分钟内定位问题打开 Chrome DevTools 的 Network 面板看请求到底发出去了没有。如果请求显示红色failed点击请求看 Response headers 里有没有Access-Control-Allow-Origin。没有这个响应头说明后端 CORS 没生效检查后端配置类是否被扫描到、路径是否匹配。有这个响应头但前端还是报错再看看是不是Allow-Credentials、Allow-Methods和实际请求不匹配。如果是一个 400 或 401 的预检失败重点检查OPTIONS请求的处理Spring Security 之类安全框架经常拦掉 OPTIONS。这个步骤几乎能覆盖 90% 的跨域问题。5.2 预检请求 OPTIONS 的 504 超时有一个现象我印象很深刻跨域 GET 请求一切正常但 POST 一个application/json的请求就会报504 Gateway Timeout后端日志里还没有任何记录。排查后发现是请求被安全框架拦截了。具体来说Spring Security 默认会拦截所有请求而OPTIONS请求如果被安全管理器处理要么直接返回 401要么卡在认证流程里前端等不到预检通过的响应最终表现就是超时。解决办法有两个在安全配置里放行 OPTIONShttp.authorizeRequests().antMatchers(HttpMethod.OPTIONS, /**).permitAll()把跨域配置改成CorsFilter并确保它的执行顺序在安全过滤器之前我强烈建议采用第二种因为CorsFilter要先于安全过滤器请求在进入认证体系之前就已经带上了 CORS 响应头这是最保险的方案。5.3 MyBatis 缓存引发的跨域假象MyBatis 的缓存机制也经常和跨域问题梦幻联动。之前有个同事排查一个接口报跨域折腾了半天最后发现是因为 MyBatis 二级缓存把旧数据返回了前端拿到的数据里的某个字段格式变了导致前端解析失败报了一个看起来像跨域的错误。MyBatis 查询缓存分两级一级缓存是 SqlSession 级别的默认开启。同一个 SqlSession 里执行两次相同的 SQL第二次会直接走缓存。注意Spring 整合 MyBatis 后每个 Mapper 方法都会创建新的 SqlSession所以一级缓存的作用范围非常有限。二级缓存是 Mapper 级别的需要在 XML 里显式开启多个 SqlSession 共享同一个二级缓存。我当时排查的思路是接口返回的数据不对但数据库里数据是对的这种情况十有八九是缓存搞的鬼。在 XML 的select标签上加useCachefalse或者flushCachetrue就能绕过缓存拿到最新数据。这里我多说一句二级缓存是个双刃剑。单表查询开着没问题多表关联的查询千万别开因为 MyBatis 的二级缓存没有复杂的失效策略表一被更新缓存不会自动清掉很容易查出脏数据来。我见过不少同学为了优化性能把二级缓存开满整个项目结果线上出现诡异的数据不一致问题。真实企业项目里MyBatis 二级缓存的使用率其实很低做查询缓存一般直接上 Redis 了。5.4 Spring Boot 自动装配为什么 MyBatis 和跨域配置能生效这部分是面试加分点也帮你自己调试问题提供底层视角。Spring Boot 的自动装配核心机制是SpringBootApplication里的EnableAutoConfiguration它会扫描META-INF/spring.factories文件Spring Boot 3.x 之后改成了AutoConfiguration.imports把XxxAutoConfiguration类加载进来再配合ConditionalOnClass、ConditionalOnMissingBean这些条件注解决定要不要创建对应的 Bean。MyBatis 的 starter 能自动装配就是因为在mybatis-spring-boot-autoconfigure里定义了MybatisAutoConfiguration它检测到SqlSessionFactory和DataSource这些类存在时自动帮你创建SqlSessionFactory、SqlSessionTemplate还自动扫描Mapper注解的接口。你自定义的CorsConfig能生效是因为Configuration类在启动时被组件扫描到WebMvcConfigurer的实现类会被 Spring MVC 自动注册到配置体系中。理解了这层你就知道为什么跨域配置放错包扫描不到的时候完全不生效了——因为那个类根本没被 Spring 容器发现。5.5 前端 Vue 打包放进 Spring Boot 的场景热词里有一个很常见的问题Vue 项目打包后的静态文件放进 Spring Boot为什么还会跨域理清部署形态就不困惑了如果把前端打包的dist目录塞进 Spring Boot 的src/main/resources/static下前后端都从同一个端口出如果后端的端口是 9090页面和接口都在localhost:9090下浏览器判定是同源自然没有跨域问题。但如果前端构建出的 index.html 已经写死了http://localhost:9090/api这种完整地址而用户访问的是http://localhost:8080那浏览器还是跨域。所以这种部署模式下我建议前端所有接口都用相对路径/api不写死协议、域名、端口。这样不管前端从哪个端口访问页面请求都是发给当前页面的域名和端口再由 Spring Boot 路由到后端接口彻底绕开跨域。类似地本地开发时如果遇到跨域我也可以分享一个偷懒技巧Vue 的.env.development里设置VITE_BASE_URL /api然后在本地起一个 Nginx 或者直接用 Vite 的 proxy 配置把/api代理到后端localhost:9090这样本地开发后端不用配跨域生产环境 Nginx 再统一转发。但这种方案不适合后端调试因为你得额外配代理。6. 面试题例子跨域 Spring Boot MyBatis 高频问答整理6.1 完整回答模板三层递进式作答面试官问跨域请求是什么怎么解决我推荐的作答思路是三层递进每层之间自然衔接第一层讲定义跨域是指浏览器同源策略下前端页面的源协议、域名、端口与后端请求的目标源不一致。同源策略是浏览器安全模型的核心限制设计初衷是防止恶意网站窃取用户数据。第二层讲问题跨域会导致浏览器拒绝接收服务器的响应数据。单纯的请求服务端能够收到但浏览器会拦截而预检机制还会让非简单请求先发一个 OPTIONS 请求这个请求一旦被安全框架拦截就会出现各种诡异超时。第三层讲解决对于 Spring Boot 后端项目主流方案是 CORS 配置。具体有三种实现方式分别是CrossOrigin注解、WebMvcConfigurer全局配置、CorsFilter实际项目通常用全局配置。生产环境还可以用 Nginx 做反向代理来规避跨域。6.2 面试官追问一跨域请求是服务端拦截吗这个问题的标准答案我前面已经讲过了跨域拦截发生在浏览器侧。更准确地说浏览器对响应的拦截机制分为两部分简单请求直接发出浏览器检查响应头是否包含Access-Control-Allow-Origin没有就拦截。预检请求在正式请求之前先发 OPTIONS如果预检不通过正式请求根本不会发出。服务端一般来说不会主动拦截跨域请求除非你写了额外的过滤器。这个点答好了面试官会认为你真的理解底层的网络流程而不是背了八股文。6.3 面试官追问二Spring Boot 配置跨域之后为什么还是不行这个问题考验实际排错经验。我总结下来最常见的三个原因配置类没有被扫描到比如放在启动类包路径之外。服务端没有显式放行 OPTIONS 预检请求特别是 Spring Security 场景。前端发的是自定义头请求后端allowedHeaders没有包含。另外我还遇到过一种很隐蔽的情况浏览器缓存了旧的 CORS 配置。Chrome 对Access-Control-Max-Age有缓存改完后端配置后前端还在用旧的预检缓存导致看起来配置没生效。解决办法是开一个无痕窗口调试或者把maxAge设置得小一点。6.4 面试官追问三Spring Boot 怎么整合 MyBatis这道题在 Spring Boot 面试里几乎是必考的。我的答题思路是引入mybatis-spring-boot-starter依赖它会自动引入 MyBatis 和 Spring 整合所需的类。配置application.yml设置数据源、mapper-locations、驼峰映射、SQL 日志。Mapper 组件注入方面通过MapperScan或Mapper注解让 MyBatis 扫描到 Mapper 接口。Mapper 的 SQL 可以写在注解里也可以用 XML但复杂 SQL 一定要用 XML。这里还有个进阶的加分点可以讲Spring Boot 自动装配 MyBatis 的过程。MybatisAutoConfiguration会创建SqlSessionFactory它读取mapper-locations加载 XML 文件然后创建SqlSessionTemplate最终把SqlSessionTemplate注入到 Mapper 代理对象里。如果你能把 SqlSessionFactory 和 SqlSessionTemplate 这两个类名说出来面试官基本能确认你不是背的。6.5 面试官追问四MyBatis 缓存和跨域有什么关系我遇到过面试官把两个知识点串起来问的。他会问MyBatis 的一二级缓存分别是怎样的会不会影响接口返回的头信息。这个问题其实考的是排查故障的综合能力。缓存影响的是数据内容不会直接影响响应头但缓存如果返回了老数据导致前端拿到的 JSON 结构不对前端可能会报解析错误这个错误看起来和跨域报错很像容易让排错方向跑偏。MyBatis 二级缓存需要注意开启二级缓存后查询结果会存入缓存如果你的查询涉及到了多表关联一张表的数据变化无法通知其他表对应的缓存失效数据就会不一致。这个点其实反映了你对 MyBatis 的核心机制是否理解而不仅仅是会写 mapper。6.6 面试加分项谈一谈 Session 跨域和 Token 跨域如果面试时间还充裕可以主动扩展一个深度话题登录态的跨域。Session 模式下后端通过 JSESSIONID 维持会话前端请求必须携带 Cookie所以后端allowCredentials(true)是必须的而且allowedOrigins不能是*必须是指定域名。Token 模式下前端把 token 放在Authorization头里后端通过拦截器解析 token这种模式天然对跨域更友好只需要在 CORS 配置里放行Authorization头即可不需要动 Cookie 这一层。我在实际项目里强烈推荐 Token 模式它不光做跨域简单对于前后端分离、多端共用、接口鉴权这些场景都是最顺手的选择。7. 实操心得从踩坑到形成肌肉记忆做后端这些年跨域问题就像一个老朋友时不时出来刷个脸。我自己从最初的懵懂踩坑到现在基本看一眼报错就能猜个八九不离十中间积累了不少心得第一始终默认开启 MyBatis 的 SQL 日志输出。我建议在开发环境的application-dev.yml里设置mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl这样写 SQL 时能实时看到入参和查询结果。第二次踩跨域假象这种坑的时候就会下意识想到先看日志不要被前端表象带着跑。第二CORS 配置一定要集中管理。把CorsConfig放在config包里统一维护allowedOriginPatterns和maxAge。不要今天在 Controller 上加个注解明天在启动类上加个配置否则项目大了之后排查起来非常痛苦。第三生产环境的跨域配置要比开发环境严格得多。开发环境可以用allowedOriginPatterns(*)图省事生产环境务必限定具体域名。站在安全角度跨域开得越宽CSRF 攻击的风险就越大能限定就不要放开。说到后续扩展如果你的项目要接入 Redis 缓存那 MyBatis 二级缓存基本可以果断放弃直接在 Service 层做缓存管理如果你要对接网关那 CORS 不一定要在业务服务里配在网关层统一处理也是常见的架构方式。跨域问题没有一劳永逸的银弹关键还是理解原理遇到新场景能冷静分析不被现象带着走。
返回列表