依赖注入(inject)
依赖注入作为实现关系解偶和控制反转的一种设计模式已经成为 Java 开发的核心特性
著名的 Spring 框架最早就是以实现依赖注入而闻名, 随着时间的推移 Spring 已经发展为一个庞然大物, 它变得无所不能的同时失去最开始的轻量级特性. 以 Spring Boot 为例, 由于依赖关系的复杂性和不同类库的的版本兼容问题, 每一个新版本的升级对于用户而言都已经成为了难于逾越的鸿沟
对反射技术的过度使用是 Spring 的另一个问题, 它使运行时的性能开销越来越大, 同时过多的程序逻辑和依赖关系隐藏在了运行时当中, 使得调试变得十分困难, 也无法使用编译器和 IDE 的错误检查, 更成为了 Java Native 化(GraalVM)的严重阻碍
Graphoenix 遵循 Jakarta Inject 和 Jakarta CDI 规范, 对依赖注入, 切面, 配置等 Java 企业级特性提供轻量级实现: Nozdormu
Nozdormu 的设计目标:
- 保持轻量级: 只实现必要的企业级 Java 特性, 不做过多的拓展, 保持简洁和低依赖
- 运行时无反射: 把运行时的动态逻辑前移到编译阶段, 通过 Annotation Processing 在编译阶段完成 IOC 和 AOP
使用 Nozdormu 作为 Graphoeix 的 CDI 并非强制, 任何基于Jakarta Inject 和 Jakarta CDI 规范的 CDI 实现都可作为选择
安装
添加依赖
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation 'org.graphoenix:nozdormu-inject:0.1.0'
annotationProcessor 'org.graphoenix:nozdormu-inject:0.1.0'
// ...
}
依赖定义
注解方式
单例
使用 @Singleton
或 @ApplicationScoped
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class Engine {
public String getName(){
return "V8 Engine";
}
}
多例
使用 @Dependent
import jakarta.enterprise.context.Dependent;
import java.util.UUID;
@Dependent
public class Driver {
private final String name;
public Driver() {
name = "Mr." + UUID.randomUUID();
}
public String getName() {
return name;
}
}
特定生命周期
- 请求:
@RequestScoped
- 会话:
@SessionScoped
- 事务:
@TransactionScoped
import jakarta.enterprise.context.RequestScoped;
@RequestScoped
public class Broadcast {
public String getName(){
return "BBC";
}
}
工厂方法(Produces)
在引用第三方类库中的 Bean 时, 无法通过注解方式定义生命周期, 可以使用工厂方法来提供 Bean 实例, 使用 @Produces
标记方法, 同时配合其他注解定义生命周期
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Produces;
@ApplicationScoped
public class AutoParts {
@ApplicationScoped
@Produces
public Brake brake() {
return new Brake();
}
@ApplicationScoped
@Produces
public Wheel wheel(Brake brake) {
return new Wheel(brake);
}
@RequestScoped
@Produces
public Navigation navigation() {
return new Navigation();
}
}
指定名称
使用 @Named
为 Bean 指定名称, 依赖注入时可根 据名称注入对应的 Bean
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
@ApplicationScoped
@Named("v12")
public class V12Engine implements IEngine {
public String getName() {
return "V12 Engine";
}
}
默认实现
使用 @Default
指定 Bean 为默认实现
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
@ApplicationScoped
@Default
public class Engine implements IEngine {
public String getName() {
return "V8 Engine";
}
}
指定顺序
使用 @Priority
指定 Bean 在集合中的顺序, 在集合注入时会按照指定顺序排序
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
@ApplicationScoped
@Default
@Priority(1)
public class Engine implements IEngine {
public String getName() {
return "V8 Engine";
}
}
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
@ApplicationScoped
@Named("v12")
@Priority(2)
public class V12Engine implements IEngine {
public String getName() {
return "V12 Engine";
}
}
依赖注入(Inject)
构造方法注入
推荐使用构造方法进行依赖注入, 在构造方法上使用 @Inject
注解
例: 把发动机(单例)添加到汽车中
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
@Dependent
public class Car {
private final Engine engine;
@Inject
public Car(Engine engine) {
this.engine = engine;
}
public Engine getEngine() {
return engine;
}
}
测试
import io.nozdormu.inject.test.beans.Car;
import io.nozdormu.spi.context.BeanContext;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class InjectTest {
@Test
void testCar() {
Car car1 = BeanContext.get(Car.class);
assertEquals(car1.getEngine().getName(), "V8 Engine");
}
}
Setter 方法注入
使用 Setter 方式注入, 在字段或 Setter 方法上使用 @Inject
注解
例: 把变速箱(单例)和车主(单例)添加到汽车中
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
@Dependent
public class Car {
private Gearbox gearbox;
@Inject
private Owner owner;
@Inject
public void setGearbox(Gearbox gearbox) {
this.gearbox = gearbox;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
}
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class Gearbox {
public String getName() {
return "automatic";
}
}
import jakarta.enterprise.context.ApplicationScoped;
import java.util.UUID;
@ApplicationScoped
public class Owner {
private final String name;
public Owner() {
name = "Mr." + UUID.randomUUID();
}
public String getName() {
return name;
}
}
测试
import io.nozdormu.inject.test.beans.Car;
import io.nozdormu.spi.context.BeanContext;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class InjectTest {
@Test
void testCar() {
Car car1 = BeanContext.get(Car.class);
Car car2 = BeanContext.get(Car.class);
assertEquals(car1.getGearbox().getName(), "automatic");
assertEquals(car1.getOwner().getName(), car2.getOwner().getName());
}
}