Mathias Hauser

My opinions and thoughts on various things and experiences of software development

Spring RestController specific basePath

I recently started taking a look into Angular 2 and though about a proper project setup in combination with Spring RestControllers. During development, the front-end Angular 2 application will run via Webpack and the Spring Boot application with an embedded Tomcat. All RestController should use a common base path, since I plan to run the Angular 2 application together with the Spring application in a production environment. The test scenario is a simple index.html file that should be hosted in the URL ‘/index.html’ and RestController hosted in ‘/api/hello’

Expected URL structure

/index.html – angular app placeholder
/api/hello – Endpoint for a rest controller

I recommend downloading the source of this example before you start. Link to the repository blog_rest-controller-base-path direct link to the source zip source-zip

Content of this post

  • Spring configuration for the base path of all RestControllers
  • Custom annotation that combines @RestController and @RequestMapping

Spring configuration

The starting point is a Spring Boot project generated with start.spring.io with just the web dependency.

The only necessary configuration is a bean of type WebMvcRegistrationsAdapter that was introduced with Spring Boot 1.4.0. This class allows to define a custom implementation of the RequestMappingHandlerMapping via overriding ‘getRequestMappingHanlderMapping’. As usual with Spring Boot we can declare such a bean in any class that is annotation with @Configuration.

In this implementation we check if the declaring annotation is annotated with @RestController. It is a good approach to use the AnnotationUtils class for this check since it traverses interfaces, annotations, and superclasses.

In this implementation we define a new pattern that starts with ‘api’ and is followed by the existing patter. If the @RestController annotation is present.

@Configuration
public class WebConfig {

    @Bean
    public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrationsAdapter() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {
                    private final static String API_BASE_PATH = "api";

                    @Override
                    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
                        Class<?> beanType = method.getDeclaringClass();
                        if (AnnotationUtils.findAnnotation(beanType, RestController.class) != null) {
                            PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
                                    .combine(mapping.getPatternsCondition());

                            mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
                                    mapping.getMethodsCondition(), mapping.getParamsCondition(),
                                    mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                                    mapping.getProducesCondition(), mapping.getCustomCondition());
                        }

                        super.registerHandlerMethod(handler, method, mapping);
                    }
                };
            }
        };
    }

}

The following controller is used to test if the our controllers are now hosted relative to the ‘api’ path.

@RestController
@RequestMapping("hello")
public class HelloController {

    @RequestMapping
    public Hello all() {
        Hello hello = new Hello();
        hello.setText("Hello api");
        return hello;
    }
}

We expect that static content will still be available in the root path ‘/’

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello page</title>
</head>
<body>
Hello world!
</body>
</html>

If we run this application as expect the URL ‘/index.html’ displays ‘Hello World’ and the URL ‘/api/hello’ displays ‘{“text”:”Hello api”}’

Custom annotation combining @RestController and @RequestMapping

Once you write multiple controllers, it is necessary to annotated all of them with @RestController and @RequestMapping(“subpath”). This can combined into a custom annotation. I named it @RestApiController that expects a parameter for the subpath similar to @RequestMapping.

The @AliasFor annotation introduced with Spring 4.2 allows to pass the parameter to another annotation. For our case we pass the value from @RestApiController to @RequestMapping

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RestController
@RequestMapping
public @interface RestApiController {

 @AliasFor(annotation = RequestMapping.class, attribute = "value")
 String[] value();

}

The modified controller using this annotation looks like the following.

@RestApiController("hello")
public class HelloController {

    @RequestMapping
    public Hello all() {
        Hello hello = new Hello();
        hello.setText("Hello api");
        return hello;
    }
}

Questions

Feel free to ask any questions about this post and I will try to answer them in the comments or via mail -> dev[at]mathias-hauser.at.
You can also send me a mail if you have a suggestion for another post.

Advertisements

One response to “Spring RestController specific basePath

  1. Muhammad Zafar April 4, 2017 at 12:53 pm

    First I would like to thank you for a wonderful post. Thanks. One suggestion though, if you would add default value in @RestApiController then the value will not be mandatory when decorating the class with annotation and it will still work with basePath prepended to all rest controllers. For example.

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @RestController
    @RequestMapping
    public @interface RestApiController {
     
     @AliasFor(annotation = RequestMapping.class, attribute = "value")
     String[] value() default "";
    }
    
    @RestApiController
    public class HelloController {
    	@RequestMapping
    	public Hello all() {
    		Hello hello = new Hello();
    		hello.setText("Hello api");
    		return hello;
    	}
    }
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: