Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save UnquietCode/6323695 to your computer and use it in GitHub Desktop.
Save UnquietCode/6323695 to your computer and use it in GitHub Desktop.
Custom implementation of Spring's RequestMappingHandlerMapping which automatically registers a redirect for the same url with (or without) a trailing slash. This is good in cases where for SEO purposes you want 'url' and 'url/' to resolve to the same page.
package com.sb.server.web;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.RedirectView;
import java.lang.reflect.Method;
/**
* Custom implementation of annotation-based handler mapping which
* looks for the {@link RedirectTrailingSlash} annotation and registers
* a handler to cover the case of the url with or without the slash.
* The redirect is handled as 301 Redirect
*
* @author Ben Fagin
* @version 2013-08-22
*/
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
RedirectTrailingSlash redirectAnnotation = AnnotationUtils.findAnnotation(method, RedirectTrailingSlash.class);
if (redirectAnnotation == null) { return; }
for (String pattern : mapping.getPatternsCondition().getPatterns()) {
registerRedirectHandler(pattern, redirectAnnotation.code(), mapping);
}
}
private void registerRedirectHandler(String originalURL, int statusCode, RequestMappingInfo info) {
// skip when there's an extension
if (originalURL.contains(".")) { return; }
final String otherURL;
// redirect when not trailing
if (originalURL.endsWith("/")) {
otherURL = originalURL.substring(0, originalURL.length()-1);
}
// redirect when trailing
else {
otherURL = originalURL+"/";
}
RedirectHandler handler = new RedirectHandler(originalURL, statusCode);
registerHandlerMethod(handler, handlerMethod, synthesizeMapping(info, otherURL));
}
private RequestMappingInfo synthesizeMapping(RequestMappingInfo existing, String url) {
String[] patterns = resolveEmbeddedValuesInPatterns(new String[]{url});
PatternsRequestCondition patternsMatcher = new PatternsRequestCondition(
patterns, getUrlPathHelper(), getPathMatcher(),
useSuffixPatternMatch(), false, getFileExtensions()
);
return new RequestMappingInfo(
patternsMatcher,
existing.getMethodsCondition(),
existing.getParamsCondition(),
existing.getHeadersCondition(),
existing.getConsumesCondition(),
existing.getProducesCondition(),
existing.getCustomCondition()
);
}
private static final Method handlerMethod; static {
try {
handlerMethod = RedirectHandler.class.getMethod("handle");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static class RedirectHandler {
private final String redirectURL;
private final int statusCode;
public RedirectHandler(String redirectURL, int statusCode) {
this.redirectURL = redirectURL;
this.statusCode = statusCode;
}
public View handle() {
RedirectView view = new RedirectView(redirectURL);
view.setStatusCode(HttpStatus.valueOf(statusCode));
return view;
}
}
}
package com.sb.server.web;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marker annotation which says that a given method should
* use a redirect when a trailing slash version is encountered.
*
* The method should already be annotated with
* {@link org.springframework.web.bind.annotation.RequestMapping}.
*
* @author Ben Fagin
* @version 2013-08-22
* @see CustomRequestMappingHandlerMapping
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedirectTrailingSlash {
/**
* The status code to use for the redirect.
*/
int code() default 301;
}
package com.sb.server.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* @author Ben Fagin
* @version 2013-04-22
*/
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
@Bean
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
CustomRequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
handlerMapping.setUseTrailingSlashMatch(false);
handlerMapping.setUseSuffixPatternMatch(false);
return handlerMapping;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment