DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Fixing Your Microservices Architecture Using Graph Analysis
  • What Java DAO Layer Is Best for Your Project
  • Secure Communication with Token-based RSocket
  • Incorporating Fault-Tolerance Into Your Microservice Architecture

Trending

  • Using Python Libraries in Java
  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  • Enforcing Architecture With ArchUnit in Java
  • How To Build Resilient Microservices Using Circuit Breakers and Retries: A Developer’s Guide To Surviving
  1. DZone
  2. Coding
  3. Frameworks
  4. Common Code in Spring MVC: Where to Put It?

Common Code in Spring MVC: Where to Put It?

When answering common questions, it's good to have as many options as possible. In this post, we do just that in search of what to do with common code in Spring MVC.

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Apr. 12, 17 · Tutorial
Likes (9)
Comment
Save
Tweet
Share
19.3K Views

Join the DZone community and get the full member experience.

Join For Free

During my journey coding an actuator for a non-Spring Boot application, I came upon an interesting problem regarding where to actually put a snippet of common code. This post tries to list all available options, and their respective pros and cons, in a specific context.

As a concrete example, let’s use the REST endpoint returning the map of all JVM properties accessible through the /jvmprops sub-context. Furthermore, I wanted to offer the option to search not only for a single property e.g. /jvmprops/java.vm.vendor but also to allow for filtering for a subset of properties e.g. /jvmprops/java.vm.*.

The Current Situation

The code is designed around nothing but boring guidelines for a Spring application. The upper layer consists of controllers. They are annotated with @RestController and provide REST endpoints made available as @RequestMapping-annotated methods. In turn, those methods call the second layer implemented as services.

As seen above, the filter pattern itself is the last path segment. It’s mapped to a method parameter via the @PathVariable annotation.

@RestController class JvmPropsController(private val service: JvmPropsService) {
    @RequestMapping(path = arrayOf("/jvmprops/{filter}"), method = arrayOf(GET))
    fun readJvmProps(@PathVariable filter: String): Map<String, *> = service.getJvmProps()
}


To effectively implement filtering, the path segment allows star characters. In Java, however, string matching is achieved via regular expression. It’s then mandatory to “translate” the simple calling pattern to a full-fledge regexp. Regarding the above example, not only the dot character needs to be escaped - from . but to \\., but the star character needs to be translated accordingly - from * to .*:

val regex = filter.replace(".", "\\.").replace("*", ".*")


Then, the associated service returns the filtered map, which is in turn returned by the controller. Spring Boot and Jackson take care of JSON serialization.

Straightforward Alternatives

This is all fine and nice, until additional map-returning endpoints are required (for example, to get environment variables), and the above snippet ends up being copied-pasted in each of them.

There surely must be a better solution, so where factor this code?

In a Controller Parent Class

The easiest hack is to create a parent class for all controllers, put the code there and call it explicitly.

abstract class ArtificialController() {
    fun toRegex(filter: String) = filter.replace(".", "\\.").replace("*", ".*")
}

@RestController class JvmProps(private val service: JvmPropsService): ArtificialController() {
    @RequestMapping(path = arrayOf("/jvmprops/{filter}"), method = arrayOf(GET))
    fun readJvmProps(@PathVariable filter: String): Map<String, *> {
        val regex = toRegex(filter)
        return service.getJvmProps(regex)
    }
}


This approach has three main disadvantages:

  1. It creates an artificial parent class just for the sake of sharing common code.
  2. It’s necessary for other controllers to inherit from this parent class.
  3. It requires an explicit call, putting the responsibility of the transformation in the client code. Chances are high that no developer but the one who created the method will ever use it.

In a Service Parent Class

Instead of setting the code in a shared method of the controller layer, it can be set in the service layer.

The same disadvantages as above apply.

In a Third-Party Dependency

Instead of an artificial class hierarchy, let’s introduce an unrelated dependency class. This translates into the following code.

class Regexer {
    fun toRegex(filter: String) = filter.replace(".", "\\.").replace("*", ".*")
}

@RestController class JvmProps(private val service: JvmPropsService,
    private val regexer: Regexer) {
    @RequestMapping(path = arrayOf("/jvmprops/{filter}"), method = arrayOf(GET))
    fun readJvmProps(@PathVariable filter: String): Map < String, * > {
        val regex = regexer.toRegex(filter)
        return service.getJvmProps(regex)
    }
}


While favoring composition over inheritance, this approach still leaves out a big loophole: the client code is required to call the shared one.

In a Kotlin Extension Function

If one is allowed to use alternate languages on the JVM, it’s possible to benefit for Kotlin’s extension functions:

interface ArtificialController

fun ArtificialController.toRegex(filter: String) = filter.replace(".", "\\.").replace("*", ".*")

@RestController class JvmProps(private val service: JvmPropsService): ArtificialController {
  @RequestMapping(path = arrayOf("/jvmprops/{filter}"), method = arrayOf(GET))
  fun readJvmProps(@PathVariable filter: String): Map<String, *> {
    val regex = toRegex(filter)
    return service.getJvmProps(regex)
  }
}


Compared to putting the code in a parent controller, at least the code is localized to the file. But the same disadvantages still apply, so the gain is only marginal.

More Refined Alternatives

Refactorings described above work in every possible context. The following options apply specifically for (Spring Boot) web applications.

They all follow the same approach: instead of explicitly calling the shared code, let’s somehow wrap controllers in a single component where it will be executed.

In a Servlet Filter

In a web application, code that needs to be executed before/after different controllers are bound to take place in a servlet filter.

With Spring MVC, this is achieved through a filter registration bean:

@Bean
fun filterBean() = FilterRegistrationBean().apply {
    urlPatterns = arrayListOf("/jvmProps/*")
    filter = object: Filter {
        override fun destroy() {}
        override fun init(config: FilterConfig) {}
        override fun doFilter(req: ServletRequest, resp: ServletResponse, chain: FilterChain) {
            chain.doFilter(httpServletReq, resp)
            val httpServletReq = req as HttpServletRequest
            val paths = request.pathInfo.split("/")
            if (paths.size > 2) {
                val subpaths = paths.subList(2, paths.size)
                val filter = subpaths.joinToString("")
                val regex = filter.replace(".", "\\.")
                    .replace("*", ".*")
                    // Change the JSON here...
            }
        }
    }
}


The good point about the above code is it doesn’t require controllers to call the shared code explicitly. There’s a not-so-slight problem however: at this point, the map has already been serialized into JSON, and been processed into the response. It’s mandatory to wrap the initial response in a response wrapper before proceeding with the filter chain and process the JSON instead of an in-memory data structure.

Not only is this way quite fragile, it has a huge impact on performance.

In a Spring MVC Interceptor

Moving the above code from a filter in a Spring MVC interceptor unfortunately doesn’t improve anything.

In an Aspect

The need of translating the string parameter and to filter the map are typical cross-cutting concerns. This is a typical use-case fore Aspect-Oriented Programming. Here’s what the code looks like:

@Aspect class FilterAspect {
  @Around("execution(Map ch.frankel.actuator.controller.*.*(..))")
  fun filter(joinPoint: ProceedingJoinPoint): Map<String, *> {
    val map = joinPoint.proceed() as Map<String, *>
    val filter = joinPoint.args[0] as String
    val regex = filter.replace(".", "\\.").replace("*", ".*")
    return map.filter { it.key.matches(regex.toRegex()) }
  }
}


Choosing this option works in the intended way. Plus, the aspect will be applied automatically to all methods of all classes in the configured package that return a map.

In a Spring MVC Advice

There’s a nice gem hidden in Spring MVC: a specialized advice being executed just after the controller returns but before the returned value is serialized in JSON format (thanks to @Dr4K4n for the hint).

The class just needs to:

  1. Implement the ResponseBodyAdvice interface
  2. Be annotated with @ControllerAdvice to be scanned by Spring, and to control which package it will be applied to


@ControllerAdvice("ch.frankel.actuator.controller")
class TransformBodyAdvice(): ResponseBodyAdvice<Map<String, Any?>> {

  override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>) =
  returnType.method.returnType == Map::class.java

  override fun beforeBodyWrite(map: Map<String, Any?>, methodParameter: MethodParameter,
            mediaType: MediaType, clazz: Class<out HttpMessageConverter<*>>,
            serverHttpRequest: ServerHttpRequest, serverHttpResponse: ServerHttpResponse): Map<String, Any?>  {
    val request = (serverHttpRequest as ServletServerHttpRequest).servletRequest
    val filterPredicate = getFilterPredicate(request)
    return map.filter(filterPredicate)
  }

  private fun getFilterPredicate(request: HttpServletRequest): (Map.Entry<String, Any?>) -> Boolean {
    val paths = request.pathInfo.split("/")
    if (paths.size > 2) {
      val subpaths = paths.subList(2, paths.size)
      val filter = subpaths.joinToString("")
      val regex = filter.replace(".", "\\.")
                        .replace("*", ".*")
                        .toRegex()
      return { it.key.matches(regex) }
    }
    return { true }
  }
}


This code doesn’t require to be called explicitly, it will be applied to all controllers in the configured package. It also will only be applied if the return type of the method is of type Map (no generics check due to type erasure though).

Even better, it paves the way for future development involving further processing (ordering, paging, etc.).

Conclusion

There are several ways to share common code in a Spring MVC app, each having different pros and cons. In this post, for this specific use-case, the ResponseBodyAdvice has the most benefits.

The main takeaway here is that the more tools one has around one’s toolbelt, the better the final choice. Go explore some tools you don’t know already about. What about reading some documentation today?

Spring Framework IT

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Fixing Your Microservices Architecture Using Graph Analysis
  • What Java DAO Layer Is Best for Your Project
  • Secure Communication with Token-based RSocket
  • Incorporating Fault-Tolerance Into Your Microservice Architecture

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!