Dmitry Nikolaev - stock.adobe.co

Avoid boilerplate code with this typable Spring beans tutorial

Java is always criticized for being bloated. But there are ways to minimize all the fill-in code. This Spring beans tutorial shows you how to write more efficient applications.

Have you ever been working in Spring and had to use incoming data to determine the given instance of a bean you'll need to process a request? It's a common yet often poorly executed use. This typable Spring bean tutorial will show you how to address this scenario in an intelligent and extensible way.

Consider the following example. Let's say we have 50 different StateFormParsers for parsing incoming forms from each American state. Each state form has a different set of fields. Furthermore, the format of each form might be text or HTML, so we'll need 100 different parsers (50 states x 2 different formats). We can definitely find places to code share, but we still need to be able to find the correct parser for each form that enters the system.

And now, we want to find a nice elegant way to retrieve the correct implementation from the inversion of control (IoC) container when we receive a form into our application. We just want data that tells us which state and which format we just received. How can we quickly and simply find which Spring bean we need?

Typable Spring bean tutorial

Start by creating a custom annotation that you can put on each implementation to make them all typable Spring beans. I might even use the new annotation in the ComponentScan so that this annotation will be on a class and make each implementation a Spring bean without even having to add @Component type annotations on it.

Using our example, here is the annotation I might create:

package com.serverside.typable.beans,annotation
 
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StateFormParser {
 
   String state();
   String format() default "text";
}

I can use this annotation on each of my parsers and then set the state and format values. Here is an example of a parser for California that supports the HTML format:

@StateFormParser(state="CA", format="html")
public class CaliforniaParser extends StateFormParserBaseClass {
}

In my Spring configuration for Component Scan, I would add it to my includeFilters property of the componentScan configuration. When you use @ComponentScan, it would look like this:

@ComponentScan(basePackages = ["com.serverside],includeFilters = [
  @ComponentScan.Filter(type=FilterType.ANNOTATION, value=StateFormParser.class),
  @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Repository.class),
  @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Service.class),               
  @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class)
  ]
)

When the Spring IoC container creates its ApplicationContext/BeanFactory, the parser beans will be in the context. But how do you retrieve them?

Spring has a method in the ListableBeanFactory that extends BeanFactory, which you can find within the ApplicationContext.

Map<String, Object> getBeansWithAnnotation(
Class<? extends Annotation> annotationType)
throws BeansException;

We could use this method to retrieve all the beans with our annotations, but we can also do an autowiring trick if each implementation operates in the same interface or base class.

@Autowired Map<String, StateFormParserBaseClass> formParserMap;

In the autowired trick, the String of the map is the bean name, and the value is the bean instance.

Since we have the base classes and not a Map/List by the annotation, we will then need to search for the correct bean in the map.

This code will loop through the values of the map, pull the annotation off of each one that we can then compare to the dynamic data and find the one instance that we want to use. You could use Java 8 streams and incorporate a stream().filter() call if you like.

private StateFormParserBaseClass getFormParser(String state, String format) {
  StateFormParserBaseClass beanFormParser = null;
 
  for (StateFormParserBaseClass formParser: formParserMap.values()) {
    StateFormParser stateFormParser = formParser.getClass().getAnnotation(StateFormParser.class);
    if (stateFormParser.state().equals(state) && stateFormParser.format().equals(format)) {
    beanFormParser = formParser;
    }
  }
    return beanFormParser
}

There are many other ways to retrieve the correct parser bean, but with this Spring beans tutorial, you can simplify the problem with annotations and a simple reflection method or a Spring application method call.

Dig Deeper on Front-end, back-end and middle-tier frameworks