将Spring Bean注入非托管对象

2023/05/13

1. 概述

在Spring应用程序中,将一个bean注入另一个bean是很常见的。但是,有时需要将bean注入到普通对象中。 例如,我们可能希望从实体对象中获取对Service对象的引用。

幸运的是,实现这一目标并不像看上去那么难。以下部分将介绍如何使用@Configurable注解和AspectJ weaver来实现这一点。

2. @Configurable注解

这个注解允许被修饰类的实例保存对Spring bean的引用。

2.1 定义和注册Spring bean

在介绍@Configurable注解之前,让我们配置一个Spring bean定义:


@Service
public class IdService {
    private static int count;

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

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

下面是一个启用该机制的简单配置类:


@ComponentScan
public class AspectJConfig {

}

2.2 使用@Configurable

在最简单的形式中,我们可以使用不带任何属性的@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
}

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

2.3 将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();
    }
}

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

3. 开启AspectJ Weaving

3.1 插件声明

要启用AspectJ Weaving,我们首先需要AspectJ Maven插件:


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
</plugin>

它需要一些额外的配置:


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

第一个必需元素是complianceLevel,1.8表示将源JDK版本和目标JDK版本都设置为1.8。 如果未明确设置,则源版本为1.3,目标版本为1.1。这些值显然已经过时,对于现代Java应用程序来说远远不够。

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

请注意,这样的引用工件必须作为依赖项存在于项目中:


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

3.2 插件Execution

为了指示插件织入所有相关的类,我们需要以下executions配置:


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

注意,默认情况下,插件的compile goal绑定到compile生命周期阶段

3.2 Bean配置

启用AspectJ weaving的最后一步是将@EnableSpringConfigured添加到配置类中:


@ComponentScan
@EnableSpringConfigured
public class AspectJConfig {

}

额外的注解配置了AnnotationBeanConfigurerAspect,而AnnotationBeanConfigurerAspect向Spring IoC容器注册PersonObject实例。

4. 测试

现在,让我们测试IdService bean是否已成功注入PersonObject:


@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AspectJConfig.class)
class PersonUnitTest {

    @Test
    void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
        PersonObject personObject = new PersonObject("tuyucheng");
        personObject.generateId();

        assertEquals(1, personObject.getId());
        assertEquals("tuyucheng", personObject.getName());
    }
}

5. 将Bean注入JPA实体

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

然而,由于注入JPA实体是一个典型的用例,在这里详细的说明几点。

5.1 实体类


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

    public PersonEntity() {
    }

    // other code - shown in the next subsection
}

注意@Configurable注解中的preConstruction属性:它使我们能够在对象完全构造之前将依赖项注入到对象中

5.2 注入Service

现在我们可以将IdService注入到PersonEntity中,类似于PersonObject一样:


@Entity
@Configurable(preConstruction = true)
public class PersonEntity {

    @Autowired
    @Transient
    private IdService idService;

    // fields and no-arg constructor

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

    // getters
}

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

5.3 修改测试

最后,我们可以修改测试方法来验证IdService对象正确注入到PersonEntity中:

class PersonUnitTest {

    @Test
    void givenUnmanagedObjects_whenInjectingIdService_thenIdValueIsCorrectlySet() {
        // existing statements

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

6. 注意事项

尽管从非Spring托管的对象访问Spring组件很方便,但这样做通常不是一个好的做法。

问题是非托管对象(包括实体)通常是域模型的一部分,这些对象应该只携带可跨不同服务重用的数据。

将bean注入到此类对象中可能会将组件和对象联系在一起,从而使维护和增强应用程序变得更加困难

7. 总结

本教程介绍了将Spring bean注入到非托管对象的问题。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章