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
purpose 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.
WebConfig.java
@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);
}
};
}
};
}
}
here it is used to test if the our controllers are now hosted relative to the ‘api’ path.
HelloController.java
@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 ‘/’
resources/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello page</title>
</head>
<body>
Hello world!
</body>
</html>
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
RestApiController.java
@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;
}
}
RESOURCES: