Injecting Spring Beans into Unmanaged Objects – 将Spring Bean注入到非托管对象中

最后修改: 2020年 6月 18日

中文/混合/英文(键盘快捷键:t)

1. Driving Forces

1.驱动力

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.

在Spring应用程序中,将一个Bean注入另一个Bean是非常常见的。然而,有时将一个Bean注入到一个普通的对象中是可取的。例如,我们可能想从一个实体对象中获得对服务的引用。

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.

幸运的是,实现这一目标并不像看起来那么难。下面几节将介绍如何使用@Configurable注解和AspectJ织机来实现这一点。

2. The @Configurable Annotation

2.@Configurable注释

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

这个注解允许被装饰类的实例持有对Spring Bean的引用。

2.1. Defining and Registering a Spring Bean

2.1.定义和注册一个Spring Bean

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

在介绍@Configurable注解之前,让我们先建立一个Spring Bean定义。

@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.

这个类用@Service注解进行了装饰;因此它可以通过组件扫描在Spring上下文中注册。

Here’s a simple configuration class enabling that mechanism:

这里有一个简单的配置类,可以实现这一机制。

@ComponentScan
public class AspectJConfig {
}

2.2. Using @Configurable

2.2.使用@Configurable

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

在其最简单的形式中,我们可以使用@Configurable而不使用任何元素:

@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.

在这种情况下,@Configurable注解PersonObject类标记为符合Spring驱动的配置。

2.3. Injecting a Spring Bean into an Unmanaged Object

2.3.将Spring Bean注入到非托管对象中

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

我们可以将IdService注入PersonObject,就像我们在任何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.

然而,注解只有在被处理程序识别和处理时才有用。这就是AspectJ weaver发挥作用的地方。具体来说,AnnotationBeanConfigurerAspect将对@Configurable的存在采取行动并进行必要的处理。

3. Enabling AspectJ Weaving

3.启用AspectJ织网

3.1. Plugin Declaration

3.1.插件声明

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

要启用AspectJ编织,我们首先需要AspectJ Maven插件

<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.

第一个必要元素是complianceLevel1.8的值将源JDK和目标JDK版本都设置为1.8。如果不明确设置,源版本将是1.3,目标版本将是1.1。这些值显然是过时的,对于现代的Java应用来说是不够的。

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.

要将 Bean 注入非托管对象,我们必须依靠 AnnotationBeanConfigurerAspect 类,该类提供于 spring-aspects.jar。由于这是一个预编译的方面,我们需要将包含工件添加到插件配置中。

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.

我们可以在Maven中心上找到最新版本的spring-aspects

3.2. Plugin Execution

3.2.插件的执行

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

为了指示该插件编织所有相关的类,我们需要这个executions配置。

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

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

注意该插件的compile目标默认与编译生命周期阶段绑定。

3.2. Bean Configuration

3.2.Bean配置

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

启用AspectJ编织的最后一步是在配置类中添加@EnableSpringConfigured

@ComponentScan
@EnableSpringConfigured
public class AspectJConfig {
}

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

额外的注解配置了AnnotationBeanConfigurerAspect,它又将PersonObject实例与Spring IoC容器注册。

4. Testing

4.测试

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

现在,让我们验证一下,IdService Bean已经成功注入到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

5.在JPA实体中注入一个Bean

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.

从Spring容器的角度来看,实体只不过是一个普通的对象。因此,将Spring Bean注入到JPA实体中并没有什么特别。

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

然而,由于注入JPA实体是一个典型的用例,让我们更详细地介绍一下它。

5.1. Entity Class

5.1.实体类

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.

请注意 @Configurable 注解中的 preConstruction 元素。它使我们能够在对象完全构建之前将一个依赖关系注入其中。

5.2. Service Injection

5.2.服务注入

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

现在我们可以将IdService注入PersonEntity,类似于我们对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.

@Transient注解被用来告诉JPA,idService是一个不被持久化的字段。

5.3. Test Method Update

5.3.测试方法更新

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

6.注意事项

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

虽然从非管理对象中访问Spring组件很方便,但这样做往往不是一个好的做法。

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.

将Bean注入此类对象可能会将组件和对象捆绑在一起,从而使维护和增强应用程序变得更加困难。

7. Conclusion

7.结语

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.

本教程介绍了将Spring Bean注入到非托管对象的过程。它还提到了一个与向对象注入依赖关系相关的设计问题。

The implementation code can be found over on GitHub.

实施代码可以在GitHub上找到over