SpringMVC 优雅实现 301 跳转 时间: 2019-03-25 01:45 分类: JAVA Web,Spring,修仙日记 ###前言 对于做站的人来说,SEO 是一件挺重要的事情,所以当我们的网页请求规则发生改变时,为了减少丢失搜索引擎的权重,我们就很有必要对以前老的 URL 做 301 跳转了。 在 HTTP 协议中,重定向分为两种:一种是 301 永久重定向,另外一种是 302 临时跳转。 ###分析与实现 需求已定,下面就是分析如何实现了,最容易想到的就是,在`Controller`的方法中加入如下代码: ```java response.setStatus(301); response.setHeader("Location", "xxx"); ``` 这样做代码显得非常糟糕,首先,每个方法中都必须添加`HttpResponse`参数,其次,代码显得有些重复。 这个时候你应该想到`SpringMVC`自带的重定向功能了,比如下面用法: ```java return "redirect:/xxx"; ``` 是的,或许你开始想到的就是上面的做法,但不幸的是,**`它默认是 302 临时跳转`**。 既然`SpringMVC`自带的跳转不行,那么我们为何不去看看它的实现呢? `视图解析器`是`SpringMVC`中一个重要的组件,这里我们不对它做过多的讲解,直接看看它的源码,通常我们在配置文件中配置的视图解析器是: > org.springframework.web.servlet.view.InternalResourceViewResolver 它的源码我们不看,因为它继承了`UrlBasedViewResolver`,我们直接看这个类的源码: ```java public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered { /** * Prefix for special view names that specify a redirect URL (usually * to a controller after a form has been submitted and processed). * Such view names will not be resolved in the configured default * way but rather be treated as special shortcut. */ public static final String REDIRECT_URL_PREFIX = "redirect:"; /** * Prefix for special view names that specify a forward URL (usually * to a controller after a form has been submitted and processed). * Such view names will not be resolved in the configured default * way but rather be treated as special shortcut. */ public static final String FORWARD_URL_PREFIX = "forward:"; /** * Overridden to implement check for "redirect:" prefix. * Not possible in {@code loadView}, since overridden * {@code loadView} versions in subclasses might rely on the * superclass always creating instances of the required view class. * @see #loadView * @see #requiredViewClass */ @Override protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); return applyLifecycleMethods(viewName, view); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); } ``` 上面我只贴了关键部分的代码,它的`转发(forard)`与`重定向(redirect)`实现都在`createView`方法中。 前面说了`SpringMVC`默认的重定向是 302 跳转,看上面源码可知,重定向视图就是`RedirectView`这个类,我们查看它的源码可以知道它默认就是 302 跳转: ```java protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { String encodedRedirectURL = response.encodeRedirectURL(targetUrl); if (http10Compatible) { HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE); if (this.statusCode != null) { response.setStatus(this.statusCode.value()); response.setHeader("Location", encodedRedirectURL); } else if (attributeStatusCode != null) { response.setStatus(attributeStatusCode.value()); response.setHeader("Location", encodedRedirectURL); } else { //看这里的注释 // Send status code 302 by default. response.sendRedirect(encodedRedirectURL); } } else { HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl); response.setStatus(statusCode.value()); response.setHeader("Location", encodedRedirectURL); } } ``` 好了,到了这里我们要做的就是自定义视图解析器了,需要注意的是,如果你是直接继承`UrlBasedViewResolver`类,启动是会报错的,如下: ```java Caused by: java.lang.IllegalArgumentException: Property 'viewClass' is required at org.springframework.web.servlet.view.UrlBasedViewResolver.initApplicationContext(UrlBasedViewResolver.java:405) at org.springframework.context.support.ApplicationObjectSupport.initApplicationContext(ApplicationObjectSupport.java:120) at org.springframework.web.context.support.WebApplicationObjectSupport.initApplicationContext(WebApplicationObjectSupport.java:76) at org.springframework.context.support.ApplicationObjectSupport.setApplicationContext(ApplicationObjectSupport.java:74) at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:119) ``` 就是说`viewClass`属性是必须的,我们参见`InternalResourceViewResolver`类源码: ```java /** * Sets the default {@link #setViewClass view class} to {@link #requiredViewClass}: * by default {@link InternalResourceView}, or {@link JstlView} if the JSTL API * is present. */ public InternalResourceViewResolver() { Class> viewClass = requiredViewClass(); if (InternalResourceView.class == viewClass && jstlPresent) { viewClass = JstlView.class; } setViewClass(viewClass); } ``` 它在构造方法中调用了`setViewClass`来设置`viewClass`,如果你选择继承`UrlBasedViewResolver`类的话,可以参考`InternalResourceViewResolver`类的做法,但是既然我们一般都是用的`InternalResourceViewResolver`视图解析器,为何我们不直接继承它呢? 所以我的做法是选择继承`InternalResourceViewResolver`类,这样一来只需要将`springmvc.xml`中的视图解析器类改成我们自定义的即可: ```xml ``` 自定义视图解析器: ```java public class CustomViewResolver extends InternalResourceViewResolver { private static final String REDIRECT_301_URL_PREFIX = "redirect 301:"; @Override protected View createView(String viewName, Locale locale) throws Exception { if (!canHandle(viewName, locale)) { return null; } if (viewName.startsWith(REDIRECT_301_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_301_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible(), false); //设置 301 状态码 view.setStatusCode(HttpStatus.MOVED_PERMANENTLY); return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName); } return super.createView(viewName, locale); } } ``` 最后在我们的`Controller`中语法如下: ```java @RequestMapping("/{id}.htm") public String film(@PathVariable("id") Integer id) { return "redirect 301:/xzj/{id}.htm"; } ``` 大功告成! 标签: 无