Avaje 注射簡介
一、簡介
Avaje Inject由 ebean 的創建者打造,是一個基於 JVM 的高級編譯時依賴注入 (DI) 框架。它採用生成可讀源代碼的方式來支持各種DI操作。 Avaje 讀取 JSR-330 帶註釋的 bean 並生成類以從我們的應用程序收集 bean 實例並在適當的時間使用它們。
2. 依賴注入
提醒一下,依賴注入是控制反轉原理的具體應用,其中程序控制自己的流程。
不同的框架以不同的方式實現依賴注入。特別是,這些差異中最顯著的之一是注入是發生在運行時還是編譯時。
運行時 DI 通常基於反射。它使用簡單,但運行速度很慢。運行時 DI 框架的首要示例是 Spring。
另一方面,編譯時 DI 通常基於代碼生成。這意味著所有重量級操作都在編譯階段執行。它通常執行速度更快,因為它消除了運行時密集反射或類路徑掃描的需要。
Avaje 決定完全依賴註釋處理來生成編譯時源代碼。
3.Maven/Gradle配置
要在項目中使用 Avaje Inject,我們需要將avaje-inject 依賴項添加到pom.xml
中:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>9.5</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-test</artifactId>
<version>9.5</version>
<scope>test</scope>
</dependency>
此外,我們還需要包含注入代碼生成器**來讀取帶註釋的類並生成用於注入的代碼。它添加了一個optional
範圍,因為生成器僅在編譯時需要**:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>9.5</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
如果我們使用 Gradle,我們將包含這些依賴項:
implementation('io.avaje:avaje-inject:9.5')
annotationProcessor('io.avaje:avaje-inject-generator:9.5')
testImplementation('io.avaje:avaje-inject-test:9.5')
testAnnotationProcessor('io.avaje:avaje-inject-generator:9.5')
現在我們的項目中已經有了 avaje-inject,讓我們創建一個示例應用程序來看看它是如何工作的。
4. 實施
對於我們的示例,我們將嘗試通過注入騎士的武器作為依賴項來構建騎士。 avaje-inject 生成器將在編譯時讀取所有各種註釋並在target/generated-sources/annotations
中生成 DI 代碼。
4.1. @Singleton
和@Inject
與 Spring 的@Component
類似, Avaje 使用標準 JSR-330 註釋@Singleton
將類標記為 bean 。為了注入依賴項,我們將@Inject
註釋添加到字段或構造函數。生成的 DI 代碼位於同一個包中,因此要使用字段/方法注入,這些元素必須是公共的或包私有的。
讓我們看看我們的Knight
類,它有兩個武器依賴項:
@Singleton
public class Knight {
private Sword sword;
private Shield shield;
@Inject
public Knight(Sword sword, Shield shield) {
this.sword = sword;
this.shield = shield;
}
//standard getters and setters
}
代碼生成器將讀取註釋並生成一個類來收集依賴項並註冊 bean :
@Generated("io.avaje.inject.generator")
public final class Knight$DI {
public static void build(Builder builder) {
if (builder.isAddBeanFor(Knight.class)) {
var bean = new Knight(builder.get(Sword.class,"!sword"), builder.get(Shield.class,"!shield"));
builder.register(bean);
}
}
}
該類檢查現有的Knight
類,然後從當前作用域中檢索依賴項。
4.2. @Factory
和@Bean
與Spring的@Configuration
類一樣,我們可以用@Factory
註解一個類,並用@Bean
註解它的方法,以將該類標記為創建bean的工廠。
在ArmsFactory
類中,我們使用它來向應用程序範圍提供騎士的武器:
@Factory
public class ArmsFactory {
@Bean
public Sword provideSword() {
return new Sword();
}
@Bean
public Brand provideShield() {
return new Shield(25);
}
}
代碼生成器將讀取註釋並生成一個類來調用構造函數和工廠方法:
@Generated("io.avaje.inject.generator")
public final class ArmsFactory$DI {
public static void build(Builder builder) {
if (builder.isAddBeanFor(ArmsFactory.class)) {
var bean = new ArmsFactory();
builder.register(bean);
}
}
public static void build_provideEngine(Builder builder) {
if (builder.isAddBeanFor(Sword.class)) {
var factory = builder.get(ArmsFactory.class);
var bean = factory.provideEngine();
builder.register(bean);
}
}
public static void build_provideBrand(Builder builder) {
if (builder.isAddBeanFor(Shield.class)) {
var factory = builder.get(ArmsFactory.class);
var bean = factory.provideBrand();
builder.register(bean);
}
}
}
4.3. @PostConstruct
和@PreDestroy
Avaje 可以使用生命週期方法將自定義操作附加到 bean 創建和銷毀。 @PostConstruct
方法在BeanScope
完成連接所有 bean 後執行, @Predestroy
在BeanScope
關閉時運行。
鑑於@PostConstruct
方法將在所有 bean 連接後執行,我們可以添加BeanScope
參數以使用已完成的BeanScope
進一步配置。下面的Ninja
類使用@PostConstruct
用應用程序範圍中的 beans 設置其成員:
@Singleton
public class Ninja {
private Sword sword;
@PostConstruct
void equip(BeanScope scope) {
sword = scope.get(Sword.class);
}
@PreDestroy
void dequip() {
sword = null;
}
//getters/setters
}
代碼生成器將讀取註釋並生成一個類來調用構造函數並註冊生命週期方法:
@Generated("io.avaje.inject.generator")
public final class Ninja$DI {
public static void build(Builder builder) {
if (builder.isAddBeanFor(Ninja.class)) {
var bean = new Ninja();
var $bean = builder.register(bean);
builder.addPostConstruct($bean::equip);
builder.addPreDestroy($bean::dequip);
}
}
}
5. 生成Module
在編譯時, avaje-inject-generator
讀取所有 bean 定義並確定所有 bean 的連接。然後它生成一個代表我們的應用程序及其依賴項的Module
類。
對於上述所有類,將生成以下IntroModule
來執行所有註入以添加到應用程序範圍。我們可以看到應用程序中所有bean的定義和連接順序:
@Generated("io.avaje.inject.generator")
@InjectModule()
public final class IntroModule implements Module {
private Builder builder;
@Override
public Class<?>[] classes() {
return new Class<?>[]{
com.baeldung.avaje.intro.ArmsFactory.class,
com.baeldung.avaje.intro.Knight.class,
com.baeldung.avaje.intro.Ninja.class,
com.baeldung.avaje.intro.Shield.class,
com.baeldung.avaje.intro.Sword.class,
};
}
/**
* Creates all the beans in order based on constructor dependencies.
* The beans are registered into the builder along with callbacks for
* field/method injection, and lifecycle support.
*/
@Override
public void build(Builder builder) {
this.builder = builder;
build_intro_ArmsFactory();
build_intro_Ninja();
build_intro_Sword();
build_intro_Shield();
build_intro_Knight();
}
@DependencyMeta(type = "com.baeldung.avaje.intro.ArmsFactory")
private void build_intro_ArmsFactory() {
ArmsFactory$DI.build(builder);
}
@DependencyMeta(type = "com.baeldung.avaje.intro.Ninja")
private void build_intro_Ninja() {
Ninja$DI.build(builder);
}
@DependencyMeta(
type = "com.baeldung.avaje.intro.Sword",
method = "com.baeldung.avaje.intro.ArmsFactory$DI.build_provideSword",
dependsOn = {"com.baeldung.avaje.intro.ArmsFactory"})
private void build_intro_Sword() {
ArmsFactory$DI.build_provideSword(builder);
}
@DependencyMeta(
type = "com.baeldung.avaje.intro.Shield",
method = "com.baeldung.avaje.intro.ArmsFactory$DI.build_provideShield",
dependsOn = {"com.baeldung.avaje.intro.ArmsFactory"})
private void build_intro_Shield() {
ArmsFactory$DI.build_provideShield(builder);
}
@DependencyMeta(
type = "com.baeldung.avaje.intro.Knight",
dependsOn = {
"com.baeldung.avaje.intro.Sword",
"com.baeldung.avaje.intro.Shield"
})
private void build_intro_Knight() {
Knight$DI.build(builder);
}
}
6. 使用BeanScope
檢索 Bean
為了管理依賴關係, Avaje 的BeanScope
加載並執行應用程序及其依賴關係中生成的所有Module
類,並存儲創建的 bean 以供稍後檢索。
讓我們構建BeanScope
並接收完全配備了Sword
和Shield
Knight
類:
final var scope = BeanScope.builder().build();
final var knight = scope.get(Knight.class);
assertNotNull(knight);
assertNotNull(knight.sword());
assertNotNull(knight.shield());
assertEquals(25, knight.shield().defense());
7.**使用**@InjectTest**
**進行組件測試
當我們需要為測試引導 bean 範圍時, @InjectTest
註釋非常有用。該註釋通過創建將要使用的測試BeanScope
來工作。
我們可以使用 Mockito 的@Mock
註釋將模擬對象添加到測試的BeanScope
中。當我們在字段上使用@Mock
註解時,模擬將被注入到該字段中,並在測試範圍中註冊。模擬將替換範圍內相同類型的任何現有 bean。
如果沒有定義相同類型的bean,則會添加一個新的bean。這對於需要模擬特定 bean(如外部服務)的組件測試非常有用。
在這裡,我們使用注入的Shield
模擬來阻止防禦 方法.
然後,我們使用Inject
從測試範圍中獲取knight bean,以驗證它是否包含模擬的盾牌:
@InjectTest
class ExampleInjectTest {
@Mock Shield shield;
@Inject Knight knight;
@Test
void givenMockedShield_whenGetShield_thenShieldShouldHaveMockedValue() {
Mockito.when(shield.defense()).thenReturn(0);
assertNotNull(knight);
assertNotNull(knight.sword());
assertEquals(knight.shield(), shield);
assertEquals(0, knight.shield().defense());
}
}
八、結論
在本文中,我們通過一個基本示例介紹瞭如何設置和使用 Avaje Inject。我們已經了解了它如何使用代碼生成來執行各種 DI 操作以及如何創建測試和使用模擬。
與往常一樣,本文中的所有代碼都可以在 GitHub 上獲取。