What Spring can teach us about framework design

Sebastian Sprenger
4 min readJan 18, 2021

In this article I claimed that every framework works somewhat like this:

  1. Write a plugin.
  2. Register plugin with the framework.
  3. Transfer control to framework.

I also explained that most frameworks do this by offering an interface which we can implement (writing plugin). Then tell the framework when to use the implementation of the interface (registering plugin), and finally call a function of the framework which might not return (transfer of control). The Spring Framework follows the same (abstract) pattern, but its practical approach is a little different. It yields two interesting insights when thinking about frameworks. Let us have a quick comparison to other frameworks before we look at how Spring is handling plugins.

express.js

A popular web framework in the JavaScript world is express.js. A hello world looks like this:

const express = require('express')
const app = express()
// write a plugin
var greeting = (req, res) => {
res.send('Hello World!')
}
// register plugin with framework
app.get('/hello', greeting)
// transfer control to framework
app.listen(8080)

Once traffic hits :8080/hello the framework invokes our plugin. Perfect.

JavaScript is a dynamic programming language which does not know explicit interface types. However, by calling functions on something you are implicitly defining an interface (duck typing) — and Express calls (read expects) a function with the signature (request, response).

Go standard library

The Go programming language ships with a web framework in its standard library under net/http:

package mainimport (
"fmt"
"net/http"
)
func main() {
// write a plugin
var greeting = func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
// register plugin with framework
http.HandleFunc("/hello", greeting)
// transfer control to framework
http.ListenAndServe(":8080", nil)
}

Go is a statically typed language. The http.HandleFunc() explicitly wants us to provide something according to the specified interface. So, here as well interfaces are at work to accomplish the plugin functionality.

Spring

In the Java world Spring is a popular framework for nearly everything including web. For the hello world we typically write a so called controller:

package com.example.hello;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
class Hello {
@ResponseBody
@GetMapping("/hello")
String greeting() {
return "Hello World";
}
}

And then have another class which includes the main() function:

package com.example.hello;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
class HelloWorld {
public static void main(String[] args) {
SpringApplication.run(HelloWorld.class, args);
}
}

We can clearly see the controller acting as plugin. Considering that Java is statically typed language it is surprising though that we do not have to implement an interface in the Hello class ¯\_(ツ)_/¯.

The @GetMapping in the Hello class maps the path to the function.

First thing we do in our main() function is transferring control to Spring by calling a static function.

Please note that we never created a reference to the plugin and provided it to the framework. How exactly did Spring know about our plugin — or in other words, where exactly did we register our plugin with the framework?

The Spring way

Let us try to follow the code. In the main() function we call a static run() function of Spring and provide the main class HelloWorld as parameter. Spring then analyzes HelloWorld and finds @SpringBootApplication which really is a shortcut for @ComponentScanamongst other things. Because of that annotation the run() function (remember annotations by themselves do not do anything) traverses down all packages starting with HelloWorld‘s package — in our case com.example.hello and looks at each and every class in there. If the looked at classes are annotated with a @Component or one of its pals (@Controller, @Service, @Repository, etc) Spring will take care of it. You might say Spring considers it as a plugin.

For each class that Spring considers a plugin it creates a so called bean definition. It uses reflection to get an idea how the class is structured, how its constructors look like and which parameters they require. This allows Spring to create instances of the classes it is taking care of. Quite noteworthy, since it is using reflection, it does so without ever actually mentioning the name of the class or constructor in its code. Instead of using an interface to know upfront what needs to be called, Spring identifies everything dynamically. You may think of every bean as a plugin to Spring’s dependency injection functionality.

While analyzing the classes Spring also finds the @GetMapping annotation in the Hello class and therefore maps the /hello path to the greeting() function. Since Spring basically resolves to duck typing in Java our function has to have a certain signature for it to work.

Takeaway

There are two things to take away here.

  1. The traversal and scanning allows Spring to register plugins in a declarative way with annotations. This is incredibly generic and simply brilliant looking from an Open-Closed-Principle (OCP) perspective.
  2. Frameworks need to know how to interact with a plugin without having a dependency on the specifics of the plugin (e.g. its name). Interfaces are one solution to the problem and there are other solutions as well, like Spring impressively demonstrates with reflection. Another possible approach is a protocol describing the interaction contract via IPC or network.

Think bigger

Frameworks are not a code-only thing, I like to think of them as a pattern to organize components and responsibilities. With that being said, frameworks can be a fantastic way to make your solution architecture more flexible. If you are interested in a networked example checkout the OpenServiceBroker API which is a framework to bring together 3rd party services with your apps running on Cloud Foundry or Kubernetes — and it interacts with its plugins via REST.

--

--