10 Tips For Working With Core Spring Framework

Michael Bazos
priceline labs
Published in
10 min readAug 12, 2019

The Spring Framework was created by Rod Johnson and has been around since 2002 when he released the framework along side his book Expert One-on-One J2EE Design and Development. Initially Spring contained an inversion of control container which allowed users to perform dependency injection within their applications. Since 2002 the Spring framework has gone through revisions and changes to the framework to help it grow, stay up to date and provide integrations with a lot of other libraries in the Java ecosystem. It can be overwhelming the first time you look at the Spring framework, but good frameworks in general will give you abstractions at just the right level and allow you to solve business problems and stick to the coding practices you already know and love. Even with Spring branching out into various other areas, at its core it’s still just an inversion of control framework, and below are some tips I have found useful over the years working with Spring specifically, and inversion of control frameworks in general.

1. Don’t use XML configuration

In 2009 Spring Framework released support for Java Configuration as an alternative to XML configuration. Ten years later I see no compelling reason to start a new project using XML configuration anymore. If you work on a current Spring project using XML, and if it’s reasonable to do so, I would convert it to Java Configuration. Here are some reasons why Java Configuration is better:

  • No need to for a special IDE or plugins to help with bean management and references. Since it’s just Java Code you can look up beans and references very easily.
  • No need to learn a new XML format as Java Configuration is just code
  • Wiring complex beans together can be very verbose in XML while in Java Configuration this could just be a couple methods
  • Most of the inversion of control frameworks express their configuration in Java code, so if you were to ever switch to a different framework you would feel more at home

2. Avoid The ApplicationContext

For those of you who are not aware, the ApplicationContext is an interface with access to all the beans and additional advanced functionality of Spring Framework. If you see code that has applicationContext.getBean(…) be cautious. I say this because Spring Framework is a dependency injection (DI) framework and by having your code call applicationContext.getBean(…) not only ties your code to the Spring Framework, but also is more in-line with a Service Locator Pattern. If you are looking for a way to do a proper service locator in Spring there are patterns for this, and it shouldn’t require direct access to the ApplicationContext. There are however times using the ApplicationContext cannot be avoided. Here are some cases I have seen:

  • Integrating with other libraries that might not be directly supported by the Spring Framework
  • Integrating with legacy code — Sometimes re-working code to be DI friendly isn’t always an option
  • Using in an integration test
  • Using advanced functionality of Spring or needing direct access to the BeanFactory

3. Prefer constructor arguments when building components

Spring has supported constructor injection for a long time, but back before there was Spring Java Configuration configuring constructor injection in XML was actually more verbose than setter injection. I believe this led a lot of us developing bad habits. Outlined below are three different ways to construct components to set them up for different types of injection:

  • Constructor Injection
  • Setter Injection
  • Direct Field Injection

Constructor Injection

Client Code:

Setter Injection

Client Code:

Direct Field Injection (Avoid This!)

Client Code:

Key Points:

  • Constructors allow a very natural way of communicating to the user what is required to create a valid object. Setters & Direct Field injection hide this.
  • If someone were to instantiate the PetService outside of the Spring Framework it would be very clear to the client what they need to do to make it valid. Also this allows your PetService to be loosely coupled with the Spring Framework.
  • Setting up components like this make it easier to write good unit tests, without necessarily relying on Spring Framework to run a unit test.
  • Setter injection can be used for optional dependencies.
  • Avoid direct field injection. Component may only work in a DI framework, making your code harder to unit test, and unclear in terms of what your code requires.

Also keep in mind when using Java Config you don’t actually need the annotations @Inject as in the examples above except for direct field injection, but please avoid this 🙂

4. Prefer JSR 303 annotations

Did you know that you don’t need to use Spring Framework annotations and can use something more standardized from the javax.inject package? The JSR 303 annotations should be preferred over Spring’s as they are universal across many DI frameworks beyond Spring. There are of course limitations to the JSR 303 annotations, where Spring’s annotations add in additional features. In my experience I have found in most cases you won’t need the additional functionality, or can work around it in a pure Java way. In addition, if you decide to venture out to use another DI framework besides Spring you will feel right at home knowing the JSR 303 annotations will still work.

Here is a listing of the Spring annotations versus the javax.inject annotations

A note on JSR 303 Provider Interface & Prototype Beans

One thing I see developers run into when they start using the JSR 303 annotations and creating beans is they say “How do I make this bean a prototype?” The JSR 303 answer to this is the Provider interface. The Provider interface is a simple interface with a get() method. Basically, with this interface anytime the get() method is called it asks the Spring container for a new instance of that object. The biggest downside I see to this is you need to wrap your code in a Provider interface, but even with that I found it to be clear about what’s going on. So let’s take a look at an example:

Since in the PetServiceConfiguration the bean is using the scope prototype, every time the get() is called a new instance of the PetDao will be created. If you were to remove the @Scope annotation this would make it a singleton and even though you are using the Provider interface Spring will not create a new instance of the PetDao.

5. Avoid auto-wiring for large projects or shared components

If you are working on any type of project of reasonable size, I suggest avoiding auto-wiring, or at least limiting its usage. Yes, it can be done and it can work, but auto-wiring is a magical feature of Spring which can be difficult for new developers working with Spring for the first time to understand how the system is wired together. The easiest way to avoid this is to favor explicit configuration over auto-wiring. At first this might seem annoying or boiler plate code, but when a project grows over time or evolves, and you need to break down applications into smaller pieces, explicit configuration makes it clear what the boundaries are of each component and how everything fits together. In addition, if you are building shared components that will be used in many applications, absolutely avoid auto-wiring as this will make your shared components more rigid. Finally when choosing auto-wiring consider how you will test the code as if you are relying on auto-wiring heavily it could negatively effect the test run time performance.

6. Prefer Annotations Over Strings for Bean Identifiers

In Spring if you have multiple beans that return the same interface or class, Spring’s container needs to make a decision about which bean to use in another component. Spring does have a facility for bean overriding, but in recent versions of Spring Boot they actually have this explicitly disabled by default. Anyway there are times when you want different implementations of the same interface/class instantiated and you want to differentiate those two beans. There are a few ways to accomplish this in Spring, and the one I see most often used is with bean identifiers, by providing a String in the @Bean annotation and then using the same string in the @Qualifier to specify which bean you want. This works and is fine, but if you are going to do this I highly suggest using String constants to define these bean identifiers. This will allow you to easily look up references, as opposed to just a String values that would need to be copied potentially in multiple places. One feature I see less used, but I think is a great way to manage this, is with annotations. By creating a custom annotation you can actually use that annotation as the @Qualifier. Please see below to compare the two strategies (Please note JSR 303 and Spring annotations @Qualifier annotations exists in both projects, but only the Spring @Qualifier takes a String as a bean identifier):

Custom Annotations For Bean Identifiers

String Bean Identifiers

7. Use Import instead of Class extension for Configuration classes

Often times I see people create common Java Spring configuration classes and then using class extension to take advantage of common Spring configuration. This actually should be discouraged and avoided, as Spring provides an @Import annotation just for this purpose. The reasons for this is with class extension you can actually only extend one configuration class, but the @Import will give you more flexibility to break up Spring configuration classes into multiple files. The @Import works like composition for Spring configuration files. Finally the @Import reminds me of the advice Jashua Bloch’s gave in his book Effective Java “Favor composition over inheritance

Here is an example:

Using Spring’s @Import

Using Class Extension (Avoid This!)

8. AOP is Amazing When Used Carefully

Aspect oriented programming is amazing, but it is also can seem very magical the first time you use it. My recommendation is when using AOP be selective about how you use it. When using AOP you should think about applying it for cross cutting concerns. The best use cases I have seen have been for:

  • Logging
  • Security
  • Global exception handling
  • Caching

One thing I always like to see is code written so that it could work with or without AOP. If you code only works with AOP, be careful as this could be a code smell and means AOP is doing something for you that otherwise your code wouldn’t be able to handle. A good example is logging. With AOP you can add logging between method calls without having to write log statements in your code as you can represent this as an aspect. This is extremely powerful and your code will work with or without a logging aspect. One important thing to note is when using aspects there are different flavors:

  • Spring AOP
  • AspectJ

Each have some different features, and I suggest you read about them here as AOP is a core component in the Spring library, and knowing how and when to use it properly can help you out with cross cutting concerns.

9. Checkout Spring’s CacheManager and Cache Abstraction

Spring’s cache abstraction is really well done and I haven’t come across another set of interfaces that integrates with as many caches as Spring has. Specifically I am talking about Spring’s CacheManager & Cache. Here is a short list of the Cache implementations they have out of the box:

  • Ehcache
  • ConcurrentHashMap
  • JCache
  • CaffeineCache

Additional Spring projects contain other implementations, for example there is a Spring Data Redis project that contains the Cache implementation. Even if there isn’t a Cache that exists, implementing the Cache interface is really straight forward. Using the caches is also very simple: on any public method you can add the @Cachable annotation and a proxy around the underlying class will be created which will flow through the cache before actually calling the underlying public method. This is super powerful, as it pushes the act of cache to be a cross cutting concern and you no longer need to write code to interact with the cache directly. In addition, because of the nice interfaces, if you decide to swap the underlying cache for a different implementation your code doesn’t change at all, only the configuration of the cache. Finally when adding caching it’s always very important to think about how data will be evicted from your cache so read-only data or data that doesn’t change frequently are usually the best candidates to start with.

10. Limit the scope and usage of Spring Framework

Yes, that’s right. When using the Spring Framework and specifically the inversion of control system and dependency injection, avoiding Spring’s features is actually the most natural way to use the core framework. When building new systems it’s best to focus on components, how they interact with each other and their responsibilities and not the Spring framework itself. Of course there are always exceptions, and now Spring has branched out into many different areas that it is sometimes it’s unavoidable, but focusing on good design principles will set up any application to be well organized, maintainable and should be able to fit in any inversion of control system nicely. When you have components set up like that, the inversion of control framework it helps tie everything together in a standard way.

Spring & Beyond

Spring was the first broadly used framework to offer inversion of control and dependency injection, but since then the framework itself has grown and it can be intimidating to new users. The Spring team has gone leaps and bounds with what they have done with spring-boot and spring-initializer to make things easier to get going. Spring was the first dependency injection framework in Java, but now the ecosystem has grown to other frameworks. If you are familiar with Spring and haven’t looked at any other frameworks I suggest you take a look:

Dagger — https://dagger.dev/

Google Guice — https://github.com/google/guice

Feather — https://github.com/zsoltherpai/feather

Apache Delta Spike — https://deltaspike.apache.org/

HK2 — https://javaee.github.io/hk2/

JayWire — https://github.com/vanillasource/jaywire

The tips provided are some of my favorite suggestions I have shared with co-workers while working in this type of environment, but it’s by no means complete. If you have any suggestions I would love to hear them.

--

--