Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4535

Injecting Spring Beans into Unmanaged Objects

$
0
0

1. Driving Forces

In a Spring application, injecting one bean into another bean is very common. However, sometimes it's desirable to inject a bean into an ordinary object. For instance, we may want to obtain references to services from within an entity object.

Fortunately, achieving that isn't as hard as it might look. The following sections will present how to do so using the @Configurable annotation and an AspectJ weaver.

2. The @Configurable Annotation

This annotation allows instances of the decorated class to hold references to Spring beans.

2.1. Defining and Registering a Spring Bean

Before covering the @Configurable annotation, let's set up a Spring bean definition:

@Service
public class IdService {
    private static int count;

    int generateId() {
        return ++count;
    }
}

This class is decorated with the @Service annotation; hence it can be registered with a Spring context via component scanning.

Here's a simple configuration class enabling that mechanism:

@ComponentScan
public class AspectJConfig {
}

2.2. Using @Configurable

In its simplest form, we can use @Configurable without any element:

@Configurable
public class PersonObject {
    private int id;
    private String name;

    public PersonObject(String name) {
        this.name = name;
    }

    // getters and other code shown in the next subsection
}

The @Configurable annotation, in this case, marks the PersonObject class as being eligible for Spring-driven configuration.

2.3. Injecting a Spring Bean into an Unmanaged Object

We can inject IdService into PersonObject, just as we would in any Spring bean:

@Configurable
public class PersonObject {
    @Autowired
    private IdService idService;

    // fields, constructor and getters - shown in the previous subsection

    void generateId() {
        this.id = idService.generateId();
    }
}

However, an annotation is only useful if recognized and processed by a handler. This is where AspectJ weaver comes into play. Specifically, the AnnotationBeanConfigurerAspect will act on the presence of @Configurable and does necessary processing.

3. Enabling AspectJ Weaving

3.1. Plugin Declaration

To enable AspectJ weaving, we need the AspectJ Maven plugin first:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.11</version>
    <!-- configuration and executions -->
</plugin>

And it requires some additional configuration:

<configuration>
    <complianceLevel>1.8</complianceLevel>
    <Xlint>ignore</Xlint>
    <aspectLibraries>
        <aspectLibrary>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </aspectLibrary>
    </aspectLibraries>
</configuration>

The first required element is complianceLevel. A value of 1.8 sets both the source and target JDK versions to 1.8. If not set explicitly, the source version would be 1.3 and the target would be 1.1. These values are obviously outdated and not enough for a modern Java application.

To inject a bean into an unmanaged object, we must rely on the AnnotationBeanConfigurerAspect class provided in the spring-aspects.jar. Since this is a pre-compiled aspect, we would need to add the containing artifact to the plugin configuration.

Note that such a referenced artifact must exist as a dependency in the project:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

We can find the latest version of spring-aspects on Maven Central.

3.2. Plugin Execution

To instruct the plugin to weave all relevant classes, we need this executions configuration:

<executions>
    <execution>
        <goals>
            <goal>compile</goal>
        </goals>
    </execution>
</executions>

Notice the plugin's compile goal binds to the compile lifecycle phase by default.

3.2. Bean Configuration

The last step to enable AspectJ weaving is to add @EnableSpringConfigured to the configuration class:

@ComponentScan
@EnableSpringConfigured
public class AspectJConfig {
}

The extra annotation configures AnnotationBeanConfigurerAspect, which in turn registers PersonObject instances with a Spring IoC container.

4. Testing

Now, let's verify that the IdService bean has been successfully injected into a PersonObject:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AspectJConfig.class)
public class PersonUnitTest {
    @Test
    public void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
        PersonObject personObject = new PersonObject("Baeldung");
        personObject.generateId();
        assertEquals(1, personObject.getId());
        assertEquals("Baeldung", personObject.getName());
    }
}

5. Injecting a Bean Into a JPA Entity

From the Spring container's point of view, an entity is nothing but an ordinary object. As such, there's nothing special about injecting a Spring bean into a JPA entity.

However, since injecting into JPA entities is a typical use case, let's cover it in more detail.

5.1. Entity Class

Let's start with the entity class's skeleton:

@Entity
@Configurable(preConstruction = true)
public class PersonEntity {
    @Id
    private int id;
    private String name;

    public PersonEntity() {
    }

    // other code - shown in the next subsection
}

Notice the preConstruction element in the @Configurable annotation: it enables us to inject a dependency into the object before it's fully constructed.

5.2. Service Injection

Now we can inject IdService into PersonEntity, similar to what we did with PersonObject:

// annotations
public class PersonEntity {
    @Autowired
    @Transient
    private IdService idService;

    // fields and no-arg constructor

    public PersonEntity(String name) {
        id = idService.generateId();
        this.name = name;
    }

    // getters
}

The @Transient annotation is used to tell JPA that idService is a field not to be persisted.

5.3. Test Method Update

Finally, we can update the test method to indicate that the service can be injected into the entity:

@Test
public void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
    // existing statements

    PersonEntity personEntity = new PersonEntity("Baeldung");
    assertEquals(2, personEntity.getId());
    assertEquals("Baeldung", personEntity.getName());
}

6. Caveats

Although it's convenient to access Spring components from an unmanaged object, it's often not a good practice to do so.

The problem is that unmanaged objects, including entities, are usually part of the domain model. These objects should carry data only to be reusable across different services.

Injecting beans into such objects could tie components and objects together, making it harder to maintain and enhance the application.

7. Conclusion

This tutorial has walked through the process of injecting a Spring bean into an unmanaged object. It also mentioned a design issue associated with dependency injection into objects.

The implementation code can be found over on GitHub.


Viewing all articles
Browse latest Browse all 4535

Trending Articles