34

Non-Intrusive Access to ''Orphaned'' Beans in Sp...

 4 years ago
source link: https://www.tuicool.com/articles/AJV3Inm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The issue that I would like to discuss in this article is how to access a Spring Beans that were not injected into another Bean (hence, the title “Orphaned” Beans) without using Spring-specific classes such as BeanFactory ,   AbstractApplicationContext , or their various extensions (i.e. adhering to the Spring non-intrusiveness principal).

Problem Description

One of the main features of Spring is dependency injection (DI). Another name for the same feature is Inversion Of Control (IOC). Spring was one of the first frameworks (if not the very first) that introduced this concept. In most of cases, when this pattern is used, it is often pretty straightforward. In general, you write a class, declare it as Spring Bean (or Component), and from now on, you can inject it as a member in any other class that is declared as Bean. Let’s look at an example use case. Say you are working on a feature that deals with formatting text documents. You have a class (let’s call it TextFormatter ) that has the method:

public String formatText(String text);

This class is declared as Bean and could be injected into any other Bean that needs this functionality. So far so good. Let’s make it a bit more complicated. Say you need different formatting logic for different documents. So, our   TextFormatter   class would become an interface, and we will have several implementations for this interface such as  CustomerLetterTextFormatterInternalDocumentTextFormatter ,   LegalTextFormatter and so on with possibilities to add some other implementations later on. So, in this case, our class that needs   TextFormatter   will have a member of type  TextFormatter , but we won’t inject any Bean now as we will only find out which implementation we need at runtime based on some parameter. So, it is very obvious that we need to create some   TextFormatterFactory   with some other method:

public static TextFormatter getTextFormatterInstance(String type)

Usually, the implementation of this method would involve using Spring-specific classes like BeanFactory or   AbstractApplicationContext   to retrieve the required Bean, which would be a specific implementation of the  TextFormatter interface. This is a perfectly acceptable solution. However, it does violate Spring non-intrusiveness principle, so I would like to suggest a solution that allows us to access such Beans without using Spring-specific classes.

Idea for Solution

Basically, I would like for each implementation of TextFormatter , by some magic, to insert itself into the   TextFormatterFactory , so when the time comes to extract an instance,   TextFormatterFactory won’t need to use Spring-specific classes to access those Beans, as it already has the instances that magically insert themselves into the factory. Here is the trick: The Spring Framework, upon its initialization, instantiates all Beans. In other words, during its initialization, Spring will call a constructor for every class that is defined as Spring Bean. Bingo – this is my shooting star that will make my wish come true and do the magic for us. Here is how to implement it:

First,   TextFormatterFactory   should have a static member of type Map<String,TextFormatter> that will be used to hold and retrieve all implementations of   TextFormatterTextFormatterFactory also should have a method:

public static void addTextFormatter(String name, TextFormatter textFormatter)

This is the method that would be used by our TextFormatter   implementations to insert themselves into the factory. (Of course, this method will add the   TextFormatter   parameter into the map mentioned above)

Now, let's add a new class. Let's create an abstract BaseTextFormatter class that implements the   TextFormatter   interface. Now, all concrete implementations of the  TextFormatter   interface will extend the   BaseTextFormatter class. And here is the trick: In the   BaseTextFormatter class constructor, we will invoke the following:

TextFormatterFactory.addTextFormatter(this.getClass().getSimpleName(), this)

This is done so we can insert each concrete implementation of the TextFormatter   interface into the factory. Remember each concrete implementation is declared as a Bean, and as we said, Spring, upon its initialization, will call a constructor for every class declared as a Bean. And this is the vehicle that we are hitching the ride with. So now, the time Spring is initialized,   TextFormatterFactory   will hold in its map for all concrete implementations. In our class that needs to use  TextFormatter, we can simply call:

TextFormatterFactory.getTextFormatterInstance(LegalTextFormatter.class.getName())

To get an instance of LegalTextFormatter   or any other concrete implementation, the most important part is that the method implementation, seen below:

TextFormatterFactory.getTextFormatterInstance(String name)

will not need to use Spring-specific classes but simply retrieve an instance from its Map. Hence, our goal is now achieved.

The Solution Implementation

Now that we've figured out how to resolve our problem, let's see how it could be implemented. What if we have several interfaces and sets of their implementations in the same project? Should we just apply the same principle and implement all of them with the same solution?

Well, yes, but obviously, there will be some code duplication, which is never a good thing. So, I implemented some infrastructure that implements the idea and allows a user to extend base classes of that infrastructure to receive common functionality.

The package contains just two classes: BaseEntityFactory and  BaseEntity . They contain the logic described above and the user can now add an interface and base class that implements their interface and extends the   BaseEntity and a  Factory that extends   BaseEntityFactory   and gets all the functionality described above without any code duplication.

This infrastructure implementation is available in the open-source library MgntUtils . Here is the link to the article that explains what utilities are available in the library and where to get it:  Open-source MgntUtils library .

In the article, look for the " Lifecycle management (Self-instantiating factories) " paragraph for a short explanation about the infrastructure. However, for a detailed description of this package, please refer to Javadoc API of package com.mgnt.lifecycle.management description. Also, the source code contains package com.mgnt.lifecycle.management.example , which holds well-commented and detailed code examples on how to use this feature. So if you want to skip another article of mine (I understand why you might...), here is the link to the library .

Finally, this library is available on Maven Central. Here are the Maven artifacts. Version 1.5.0.2 is the latest at the time of writing of this article, but it might change in the near future. To check the latest version, search for the artifact "MgntUtils" at http://search.maven.org/ .

Happy coding!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK