Spring MVC & custom routing conditionsI have recently been building a Spring MVC app (well, actually using the Spring Boot project - which is quite nice in parts, and crazy frustrating in others - but the underlying mechanics are the same). The application is actually a re-build of another application, so it has involved a lot of playing and exploring Spring source code to try and replicate the apps functionality like for like.
One of the first things I found, was that Spring doesn't really cater for the concept of a single app running on different sub-domains. I assume the thinking is that you would build separate applications for different sub-domains, but in this case, we just have a single app.
There are two main speed-bumps I have come across so far:
- Controller routing based on subdomain
- Security considerations based on subdomain
Basically, as you can probably imagine, once you have subdomains on a single web application, the URL path is no longer unique (e.g. http://automateddeveloper.blogspot.com/ is clearly not the same as http://blogspot.com/ )
The rest of this post, I will look at the routing element of it. I will do another post later in the week about the Spring security stuff (I haven't solved all of that yet - but got far enough to make it work).
Understanding the subdomainThe first thing you need to sort out is a common and consistent way to determine the subdomain of any given request. There are a variety of ways to do this, for example, you could parse it from the request in apache and set a header, so your app doesn't have to worry about it, or you could just parse it from the request object server name. I will assume you have some bean/service/helper class to do this everywhere (although for now we only need it in one place).
An annotationFirst up, we need a new annotation, that we can easily apply to a Controller, just like we would use the @RequestMapping - at the moment, the nice built in Spring RequestMapping handling allows you to define a URL path (plus other bits and pieces) to map a request to a URL to a given controller & method - what we want is to also specify the subdomain element of the requested URL
Defining an annotation is simple:
For the sake of simplicity of this example, I will only allow it to be used at class level (so no method based subdomain routing - but that will be pretty easy to do once you have understood the rest of the post). You will also note that the value is defined as a String array, this will allow us to define mappings to multiple subdomains if needed.
The mapping conditionSo, that was easy. Obviously, at this point the annotation doesn't actually do anything - you can add it to all the controllers you like, but it won't actually make any difference to your request routing.
To get our new annotation involved, we can implement something called a RequestCondition. This is exactly what it sounds like, Spring lets you implement additional request conditions that must be satisfied for a request mapping.
The condition could be based on any logic you like, but in our case we just simply need to check for an annotation and then examine the value provided. If the annotation value matches our incoming request then the condition is met, easy! Returning the condition indicates to Spring that the condition has been met, returning the null value indicates that the condition is not met.
Adding the condition to the mapping handlerUsually, we would use the standard Spring RequestMappingHandlerMapping to handle all the routing of requests based on the URLs, but now we need to also ask Spring to consider our new custom condition from above.
This is a simple case of extending the normal RequestMappingHandlerMapping class and adding our new condition as a custom condition. Luckily, this is really easy:
Allwe are doing is checking to see if the handler class (our controller) has our new @Subdomain annotation and if it does, we register our new custom condition for consideration.
Basically..That's really all there is to it - we can then decorate our controller class with @Subdomain("subdomain") and have them handle the routing of requests.
As you may have noticed, this is a pretty nice pattern for any kind of custom routing you might want to use - the same template could be used for routing by any request info/header or any user info (e.g. routing requests to different controllers based on their logged in role etc)