Non-Intrusive Access to ''Orphaned'' Beans in Sp...
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 CustomerLetterTextFormatter
, InternalDocumentTextFormatter
,
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 TextFormatter
. TextFormatterFactory
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!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK