首页 / 资讯中心 / 文章详情

Spring Boot中用addViewController实现无业务逻辑页面跳转

Spring Boot中用addViewController实现无业务逻辑页面跳转 ★ FEATURED ARTICLE
做一个后台管理系统的时候我经常遇到这类需求用户注册成功要跳到一个成功提示页、首页需要临时挂一个改版占位页、某个协议条款要能通过固定链接访问。这些页面的共同特点是跳转本身没有任何业务逻辑不查库、不算数、不做权限判断就是一个URL对应一个模板。最开始我处理得很笨老老实实写Controller一个类里堆了七八个空方法每个方法就一行return xxx。时间一长自己看着都别扭。直到某次代码评审一位同事提了一句这种跳转你们为什么不用addViewController后来我去查了Spring Boot的配置机制发现确实有更干净的解法。这篇文章就围绕我在Spring Boot中使用addViewController实现无业务逻辑跳转的完整过程展开适合正在做中小型Web项目、想精简Controller层的Java开发者。就算你是刚接触Spring Boot的新手按文中的步骤走一遍也能在现有项目里直接落地。1. 先搞清楚addViewController到底在做什么1.1 没有业务逻辑的跳转真的不用写Controller很多刚入行的开发者会把跳转逻辑全部塞进Controller理由是“这样代码集中在一个地方好找”。但这里有个问题当一个URL只是简单地把请求转发给某个视图时你写的所谓Controller方法本质上只是一个“转发跳板”。传统写法是这样的Controller public class PageController { GetMapping(/about) public String about() { return about; } GetMapping(/terms) public String terms() { return terms; } GetMapping(/login) public String login() { return login; } }这个类没有任何业务逻辑也没有请求参数更没有模型数据。可它占据了一个类的空间、需要被组件扫描、需要在方法表里维护映射关系同事阅读代码时还得花几秒钟确认这个Controller到底干了什么。Spring Boot早就考虑到了这种情况提供了ViewControllerRegistry和addViewController方法。通过它们你可以直接在配置层声明一个URL和视图的对应关系完全不需要写Controller。Configuration public class ViewConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/about).setViewName(about); registry.addViewController(/terms).setViewName(terms); registry.addViewController(/login).setViewName(login); } }这段配置和上面三行方法的Controller做的事情一模一样当请求/about时交给视图解析器解析名为about的逻辑视图。区别在于这里没有任何多余的Java方法没有任何可被误调用的入口配置即映射。1.2 它与普通Controller的本质差异有人可能会问反正都是跳转用Controller写也没什么影响啊区别的确不在性能上而在于设计的表达力。普通Controller是“有业务能力的处理器”。它可以通过形参接收请求参数、调用Service、组装Model、甚至通过RedirectView做复杂重定向。它被设计用来承载逻辑而不仅仅是路由跳转。addViewController注册的是一种轻量化视图控制器。在Spring Boot内部它通过ParameterizableViewController实现专门处理“无业务逻辑的视图跳转”。它不做参数绑定不经过你的Service层也不关心Model数据只完成一个动作把请求映射到视图。用生活里的例子类比普通Controller像一个正式的营业窗口什么业务都能办但每次办理都要经过叫号、填表、留档案的流程而addViewController就是一个“只发单子的自助取号机”不涉及任何后室流程路径对上了就直接出结果。把这两种职责分开后代码阅读者一看就知道页面跳转配置在ViewConfig里业务接口在Controller里团队协作时不会产生误解。1.3 适用场景与使用边界作为一个负责任的技术选型者你得知道什么情况适合用它什么情况不适合。我用一张表把判断条件列出来场景是否适合addViewController原因注册成功页、支付成功页适合纯粹的静态视图跳转首页/、登录页入口适合无前置逻辑URL固定协议条款、隐私政策页适合内容固定不需要动态数据重定向到另一个URL适合有专门的方法支持固定的HTTP状态码响应适合有addStatusController需要从URL取参数的跳转不适合需要Controller方法入参需要权限判断的页面不适合应由安全框架或拦截器控制需要动态刷新数据的页面不适合视图控制器不带Model组装能力未来大概率会加业务逻辑的页面不适合到时候还得迁移回Controller我的个人判断标准很简单这个跳转在未来两个迭代内会不会产生“需要从URL里取值、需要查库、需要根据登录状态切换”的需求如果有就直接写Controller如果确定是纯跳板放心用addViewController。2. 核心细节addViewController的使用姿势与边界2.1 三个API方法setViewName、addRedirectViewController、addStatusController分别怎么用ViewControllerRegistry里最常用的方法其实有三个很多文章只讲了第一个。第一个是addViewController(String urlPath)返回一个ViewControllerRegistration对象通过它的setViewName()指定逻辑视图名。它用于服务器内部转发浏览器URL不会发生变化。registry.addViewController(/about).setViewName(about);访问/about服务端把请求转发给视图名为about的模板整个过程对浏览器来说是无感知的地址栏里的URL不会变成别的。第二个是addRedirectViewController(String urlPath, String redirectUrl)它用于重定向。底层实现基于RedirectView默认返回302状态码浏览器地址栏会变为重定向后的目标地址。registry.addRedirectViewController(/go-about, /about);访问/go-about浏览器会收到302响应然后自动跳到/about。这个场景很适合老链接迁移或者短链接入口。第三个是addStatusController(String urlPath, HttpStatus statusCode)用于固定返回某个HTTP状态码。比如你想让某个老路径直接返回410 Gone告诉客户端这个资源已经永久废弃就可以这么配registry.addStatusController(/old-api, HttpStatus.GONE);三个方法可以连续注册配置类看起来就像一份路由表Configuration public class ViewConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/login).setViewName(login); registry.addViewController(/register-success).setViewName(register-success); registry.addRedirectViewController(/home, /index); registry.addStatusController(/deprecated, HttpStatus.GONE); } }2.2 逻辑视图名和模板引擎的映射关系setViewName()里的名字不是文件的真实路径而是逻辑视图名真正的路径映射由ViewResolver负责。如果你用的是Thymeleaf并且没有手动改过配置Spring Boot默认的规则是视图名拼上classpath:/templates/前缀和.html后缀。也就是说setViewName(about)最终解析的是src/main/resources/templates/about.html。典型的项目资源结构如下src/main/resources/ ├── static/ ├── templates/ │ ├── about.html │ ├── login.html │ └── register-success.html └── application.properties这里有两个容易踩的坑。第一个把模板放错目录比如放到了static/about.html。这种情况下setViewName(about)不会生效因为视图解析器去templates目录下找不到目标文件。第二个在视图名里多写了后缀写成setViewName(about.html)。某些转换配置不严格的情况下解析器会把视图名当成about.html去找拼上后缀后变成about.html.html然后妥妥地404。还有一个反直觉的细节如果你的页面是纯静态HTML且放在了static目录下你其实根本不需要配置任何跳转。Spring Boot默认把/**映射到静态资源目录直接访问/about.html就能打开文件。addViewController在这种场景下的真正价值是为了配合模板引擎让你能用“无后缀的干净路径”访问页面。2.3 路径匹配斜杠、通配符和多级目录addViewController支持的路径写法比很多开发者想象中灵活但也需要克制使用。最基础的是单级路径registry.addViewController(/about).setViewName(about);多级路径直接写全路径registry.addViewController(/user/agreement).setViewName(user-agreement);路径里也可以带通配符。在Spring Boot 3.x默认的PathPattern规则下*匹配一层路径**匹配多层路径registry.addViewController(/docs/*).setViewName(doc);但我在实际项目中不建议轻易在视图控制器里用通配符。因为如果多个路径都匹配同一个视图名你很难在配置层面判断具体哪个URL生效了。而且一旦某个路径未来要加业务逻辑通配符会让排查变得麻烦。更稳妥的做法是固定URL又不差那几行配置。另外路径里不要写协议头、域名或查询参数。这里配置的是服务端路由不是完整网址。2.4 优先级和映射冲突问题Spring Boot内部注册了多个HandlerMapping不同来源的请求映射有不同的优先级。默认情况下基于RequestMapping注解产生的映射优先级高于addViewController注册的视图映射。这意味着如果你的某个Controller里恰好有一个方法和视图控制器注册了相同的URLController里的方法会优先生效。这个设计本身没问题但在团队协作里容易造成困惑。比较典型的场景是你在ViewConfig里配了/user/agreement另一个没有仔细看代码的同事在Controller里也写了一个/user/agreement方法你配置的页面永远不会被展示而且没有报错。我在项目中的处理原则是Controller类负责所有带业务逻辑的路由addViewControllers只负责纯视图路由两者必须严格不重复。在代码评审时如果有新加的URL映射我会提醒对方先查一遍是否已经在视图配置里存在否则不要让控制器覆盖它。3. 实操记录从空Controller到addViewController的完整改造3.1 最小Demo从0到1跑通一个页面跳转为了让你能完整复现我写一个最小可运行的项目。首先创建一个Spring Boot项目引入两个起步依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency创建一个配置类ViewConfig.javaimport org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class ViewConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/about).setViewName(about); registry.addRedirectViewController(/go-about, /about); registry.addViewController(/welcome).setViewName(welcome); } }然后在src/main/resources/templates/下创建about.html!DOCTYPE html html langzh head meta charsetUTF-8 title关于我们/title /head body h1关于我们/h1 p这是通过addViewController直接映射的静态页面。/p /body /html启动项目访问http://localhost:8080/about你会看到这个页面。再访问http://localhost:8080/go-about浏览器地址栏会变成/about页面正常展示。这个Demo虽然简单但它验证了三条链路视图控制器的注册是否生效、逻辑视图名是否正确解析、重定向是否按预期工作。如果你在项目里碰到了404优先检查这三条链路中的哪一环出了问题。3.2 真实项目里的复合配置登录页、首页、协议页一起配实际项目里不会只有一个跳转而是成组出现。我这里列一个日常系统中很典型的组合配置Configuration public class ViewConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/).setViewName(index); registry.addViewController(/login).setViewName(login); registry.addViewController(/register-success).setViewName(register-success); registry.addViewController(/privacy).setViewName(privacy); registry.addViewController(/terms).setViewName(terms); registry.addRedirectViewController(/old-privacy, /privacy); } }你可以看到无业务逻辑的页面被集中配置到一个类里整个系统的路由入口一目了然。这里有个小建议把这类配置类命名为ViewConfig或者WebViewConfig不要塞进业务配置类里因为业务配置类通常还会处理拦截器、资源映射等混在一起后“路由表”存在感会大幅降低。在这个配置里首页入口/被映射到逻辑视图名index前提是你必须存在templates/index.html。如果你的项目里是通过WebMvcConfigurer的addViewControllers配置了根路径尤其要注意不要和Controller里的GetMapping(/)方法重复。3.3 一次真实的“空Controller清理”演练我之前维护过某个旧系统里面有一个PageController名字取得非常随便一眼望去全是空跳转Controller public class PageController { GetMapping(/about) public String about() { return about; } GetMapping(/help) public String help() { return help; } GetMapping(/faq) public String faq() { return faq; } GetMapping(/rules) public String rules() { return rules; } }这个Controller有4个方法每个方法都是模板化的返回语句。清理时我直接在配置类里追加了对应映射registry.addViewController(/about).setViewName(about); registry.addViewController(/help).setViewName(help); registry.addViewController(/faq).setViewName(faq); registry.addViewController(/rules).setViewName(rules);然后删掉整个PageController类重启项目所有页面依旧正常。消息量少的接口变少测试也没受影响。后续同事再遇到这类“纯跳板”需求第一反应已经不是写Controller而是先找ViewConfig看有没有现成入口。3.4 配置类的两种写法实现接口和注册Bean除了在配置类上直接实现WebMvcConfigurer接口Spring Boot还支持在配置类里声明一个WebMvcConfigurer类型的BeanConfiguration public class ViewConfig { Bean public WebMvcConfigurer viewControllerConfigurer() { return new WebMvcConfigurer() { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/about).setViewName(about); } }; } }两种写法都能生效。区别在于当项目里已经有其他实现了WebMvcConfigurer的配置类时多个配置类会被合并所有注册都会保留。我推荐直接在配置类上实现接口因为这样阅读者打开类文件就能看到所有相关配置不需要再跳转去查Bean定义。还有两个和自动配置相关的注意事项第一不要在配置类上加EnableWebMvc。这个注解会让Spring Boot的WebMvc自动配置大部分失效包括默认的视图解析器、静态资源处理等机制很可能导致你其他配置突然不工作。第二不要通过继承WebMvcConfigurationSupport来扩展功能。这会触发同样的自动配置失效问题。扩展Spring MVC配置时优先使用WebMvcConfigurer接口。4. 常见问题与排查技巧实录4.1 问题速查表实际使用中我总结出下面这些高频问题和对应的排查方向现象可能原因处理方式访问配置的URL直接404配置类没有加Configuration检查ViewConfig上是否有注解404但配置类有注解模板文件放在static下而不是templates下把文件迁移到templates目录404文件位置也正确项目里配置了EnableWebMvc自动配置失效移除该注解视图名解析不出页面视图名多写了.html后缀改为不带后缀的逻辑名浏览器地址栏变了而页面不对重定向目标地址填错检查addRedirectViewController第二个参数跳转没生效但Controller有同路径注解映射优先级更高删除Controller中重复的方法样式全丢页面模板里使用了相对路径的CSS/JS改用绝对路径或补上项目前缀重定向无限循环/a重定向到/b/b又重定向到/a检查重定向目标是否为最终视图页面被安全框架拦截视图控制器不经过业务层但仍会经过过滤器链在安全配置中放行对应路径4.2 日志和调试技巧如果你配置了但访问还是404最有效的做法是打开Spring MVC的调试日志在application.properties里加一行logging.level.org.springframework.webDEBUG启动项目后日志里会输出当前注册的HandlerMapping信息。你可以看到像SimpleUrlHandlerMapping的映射表确认自己配置的路径是否真的注册进去了。如果是重定向场景想确认返回状态码和跳转目标可以不用打开浏览器直接验证curl -I http://localhost:8080/go-about返回结果里会包含Location: http://localhost:8080/about这样的响应头。这个方法在接口测试和自动化验证里非常实用。4.3 两个隐藏比较深的坑第一个坑是视图控制器的路径和默认静态资源映射发生干扰。Spring Boot默认把/**路径下的请求交给静态资源处理器。如果你给templates目录下不存在的路径做视图映射同时static目录下恰好也没有同名文件最终就会返回404。反过来如果你给static目录下已有的文件路径设置了addViewController则视图控制器和静态资源处理器都可能处理这个路径产生不可预期的结果。我的规避方案很明确templates里有的页面才用视图控制器static里的纯静态文件直接用原始路径访问不额外配置映射。第二个坑是拦截器仍在视图控制器之前执行。很多开发者以为addViewController是“直通”路径可以绕过拦截器。这是误解。它只是绕过了Controller和Service层但整个过滤器链和拦截器链照样会执行。如果你的项目里配了需要登录才能访问的拦截器那么即使注册了视图控制器请求依然会被拦截器拦下。正确处理方式是在拦截器注册处放行这些路径Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /about, /privacy, /terms); }每次在视图控制器里新增一个不需要登录的页面记得同步更新拦截器或安全框架的放行名单。我个人在实际操作中的体会是addViewController最大的价值不仅是少写几个空方法而是把“页面路由”和“业务接口”两个概念在工程上真正分开。踩过几次坑之后我的习惯是先按未来两个迭代的需求判断页面会不会长业务逻辑再做选型配置时固定URL不用通配符所有纯跳转集中放在一个配置类里同时保持与拦截器放行路径同步。如果你能把这几点坚持下来这种简单的配置也能在长期迭代中保持稳定、不出幺蛾子。
阅读完成 · 觉得有帮助?
咨询建站