与其他依赖运行时反射和代理的框架不同,Micronaut使用编译时数据来实现依赖注入。
这与Google Dagger等工具采取的方法类似,后者主要是针对Android设计的。另一方面,Micronaut是为构建服务器端微服务而设计的,它提供了许多与其他框架相同的工具和实用程序,但没有使用反射或缓存过多的反射元数据。
Micronaut IoC容器的目标被总结为:
请注意,Micronaut的IoC部分可以完全独立于Micronaut来使用,无论你想构建什么类型的应用程序。
要做到这一点,配置你的构建,包括micronaut-inject-java依赖,作为一个注释处理器。
最简单的方法是使用Micronaut的Gradle或Maven插件来做到这一点。
Micronaut支持以下类型的依赖注入:
Micronaut是如何在不需要反射的情况下执行上述依赖注入的。
关键是一组AST转换(对于Groovy)和注解处理器(对于Java),生成实现BeanDefinition接口的类。
Micronaut使用ASM字节码库来生成类,由于Micronaut提前知道了注入点,所以不需要像Spring等其他框架那样在运行时扫描所有的方法、字段、构造函数等等。
另外,由于在构造Bean时没有使用反射,JVM可以更好地内联和优化代码,从而获得更好的运行时性能和减少内存消耗。这对于非单体作用域来说尤其重要,因为应用性能取决于Bean创建性能。
此外,使用Micronaut,你的应用程序启动时间和内存消耗不会像使用反射的框架那样受到代码库大小的影响。基于反射的IoC框架为你代码中的每一个字段、方法和构造函数加载并缓存反射数据。因此,随着你的代码大小的增加,你的内存需求也在增加,而Micronaut则不是这样。
package com.oracle.micronaut.service;
// 定义了一个通用的 Engine 接口
public interface Engine {
int getCylinders();
String start();
}
package com.oracle.micronaut.service.impl;
import com.oracle.micronaut.service.Engine;
import jakarta.inject.Singleton;
/**
* Bean是一个对象,其生命周期由Micronaut IoC容器管理。
* 该生命周期可能包括创建、执行和销毁。
* Micronaut实现了JSR-330(javax.injection)--Java的依赖注入规范,
* 因此要使用Micronaut,你只需使用javax.injection提供的注释。
*/
// 定义一个 V8Engine 的实现,并被标记为Singleton范围
@Singleton
public class V8Engine implements Engine {
private int cylinders = 8;
@Override
public int getCylinders() {
return cylinders;
}
@Override
public String start() {
return "Starting V8";
}
}
package com.oracle.micronaut.facade;
import com.oracle.micronaut.service.Engine;
import jakarta.inject.Singleton;
@Singleton
public class Vehicle {
private final Engine engine;
// Engine 是通过构造器注入而被注入的
public Vehicle(Engine engine) {
this.engine = engine;
}
public String start() {
return engine.start();
}
}
package com.oracle.micronaut;
import com.oracle.micronaut.facade.Vehicle;
import io.micronaut.context.ApplicationContext;
public class Application {
public static void main(String[] args) {
/**
* IoC的入口是ApplicationContext接口,它包括一个run方法。
* 下面的例子演示了如何使用它。
*/
/**
* 本例使用Java try-with-resources语法来确保ApplicationContext在应用程序退出时被干净地关闭
*/
// 运行ApplicationContext
try (ApplicationContext context = ApplicationContext.run()) {
// 从ApplicationContext中获取一个bean
Vehicle vehicle = context.getBean(Vehicle.class);
System.out.println(vehicle.start());
}
}
}
输出结果,
Starting V8
BeanContext是所有Bean定义的一个容器对象(它也实现了BeanDefinitionRegistry)。它也是Micronaut的初始化点。
然而,一般来说,你不会直接与BeanContext API交互,而可以简单地使用jakarta.inject注释和io.micronaut.context.annotation包中的注释来满足你的依赖注入需求。
package com.oracle.micronaut;
import com.oracle.micronaut.facade.Vehicle;
import io.micronaut.context.BeanContext;
public class Application {
public static void main(String[] args) {
/**
* 为了执行依赖注入,使用run()方法运行BeanContext,并使用getBean(Class)查找一个Bean,
* 如下面的例子。
*/
/**
* Micronaut自动发现classpath上的依赖注入元数据,并根据你定义的注入点将Bean连接起来。
*/
final BeanContext context = BeanContext.run();
Vehicle vehicle = context.getBean(Vehicle.class);
System.out.println(vehicle.start());
}
}
输出结果,
Starting V8
当注入一个Bean的集合时,它们默认是没有顺序的。实现Ordered接口来注入一个有序的集合。如果请求的Bean类型没有实现Ordered,Micronaut会搜索Bean上的@Order注解。
@Order注解对于由工厂创建的bean类型为第三方库中的一个类的bean的排序特别有用。在这个例子中,LowRateLimit和HighRateLimit都实现了RateLimit接口。
import io.micronaut.context.annotation.Factory;
import io.micronaut.core.annotation.Order;
import jakarta.inject.Singleton;
import java.time.Duration;
@Factory
public class RateLimitsFactory {
@Singleton
@Order(20)
LowRateLimit rateLimit2() {
return new LowRateLimit(Duration.ofMinutes(50), 100);
}
@Singleton
@Order(10)
HighRateLimit rateLimit1() {
return new HighRateLimit(Duration.ofMinutes(50), 1000);
}
}
如果你对一个给定的接口有多种可能的实现要注入,你需要使用一个限定符。
Micronaut再次利用JSR-330以及限定符和命名注解来支持这种用例。
要通过名字来限定,请使用Named注解。
public interface Engine { //
int getCylinders();
String start();
}
@Singleton
public class V6Engine implements Engine {
@Override
public String start() {
return "Starting V6";
}
@Override
public int getCylinders() {
return 6;
}
}
@Singleton
public class V8Engine implements Engine {
@Override
public String start() {
return "Starting V8";
}
@Override
public int getCylinders() {
return 8;
}
}
@Singleton
public class Vehicle {
private final Engine engine;
// javax.inject.Named注解表明,V8Engine的实现会被使用。
@Inject
public Vehicle(@Named("v8") Engine engine) {
this.engine = engine;
}
public String start() {
return engine.start();
}
}
除了能够通过名字来限定,你还可以使用Qualifier注解来建立你自己的限定符。
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
public @interface V8 {
}
@Inject Vehicle(@V8 Engine engine) {
this.engine = engine;
}
从Micronaut 3.0开始,注解限定符也可以使用注解成员来解决正确的bean注入。
import io.micronaut.context.annotation.NonBinding;
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
public @interface Cylinders {
int value();
@NonBinding
String description() default "";
}
@Singleton
@Cylinders(value = 6, description = "6-cylinder V6 engine") //
public class V6Engine implements Engine { //
@Override
public int getCylinders() {
return 6;
}
@Override
public String start() {
return "Starting V6";
}
}
@Singleton
@Cylinders(value = 8, description = "8-cylinder V8 engine") //
public class V8Engine implements Engine { //
@Override
public int getCylinders() {
return 8;
}
@Override
public String start() {
return "Starting V8";
}
}
@Inject Vehicle(@Cylinders(8) Engine engine) {
this.engine = engine;
}
从Micronaut 3.0开始,可以根据类或接口的通用类型参数来选择要注入的bean。
public interface CylinderProvider {
int getCylinders();
}
public interface Engine<T extends CylinderProvider> {
default int getCylinders() {
return getCylinderProvider().getCylinders();
}
default String start() {
return "Starting " + getCylinderProvider().getClass().getSimpleName();
}
T getCylinderProvider();
}
public class V6 implements CylinderProvider {
@Override
public int getCylinders() {
return 6;
}
}
@Singleton
public class V6Engine implements Engine<V6> {
@Override
public V6 getCylinderProvider() {
return new V6();
}
}
public class V8 implements CylinderProvider {
@Override
public int getCylinders() {
return 8;
}
}
@Singleton
public class V8Engine implements Engine<V8> {
@Override
public V8 getCylinderProvider() {
return new V8();
}
}
@Inject
public Vehicle(Engine<V8> engine) {
this.engine = engine;
}
Primary是一个限定词,表示在有多个接口实现的情况下,一个Primary bean是要被选择的bean。
public interface ColorPicker {
String color();
}
import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;
@Primary
@Singleton
class Green implements ColorPicker {
@Override
public String color() {
return "green";
}
}
import jakarta.inject.Singleton;
@Singleton
public class Blue implements ColorPicker {
@Override
public String color() {
return "blue";
}
}
@Controller("/testPrimary")
public class TestController {
protected final ColorPicker colorPicker;
public TestController(ColorPicker colorPicker) {
this.colorPicker = colorPicker;
}
@Get
public String index() {
return colorPicker.color();
}
}
如果存在多个可能的候选者,并且没有定义@Primary,那么就会抛出NonUniqueBeanException。
除了@Primary之外,还有一个Secondary注解,它引起了相反的效果,允许取消Bean的优先级
如果你对被注入的Bean没有特别的要求,那么你可以使用@Any限定词,它将注入第一个可用的Bean。@Any限定符通常与BeanProvider接口一起使用,以允许更多的动态用例。
@Inject @Any
Engine engine;
import io.micronaut.context.BeanProvider;
import io.micronaut.context.annotation.Any;
import jakarta.inject.Singleton;
@Singleton
public class Vehicle {
final BeanProvider<Engine> engineProvider;
public Vehicle(@Any BeanProvider<Engine> engineProvider) {
this.engineProvider = engineProvider;
}
void start() {
engineProvider.ifPresent(Engine::start);
}
}
void startAll() {
if (engineProvider.isPresent()) { //
engineProvider.stream().forEach(Engine::start);
}
}
默认情况下,当你用@Singleton这样的范围来注解Bean时,Bean类和它所实现的所有接口以及它所继承的超类都可以通过@Inject来注入。如果这是不可取的,你可以使用@Bean注解的类型化成员来限制暴露的类型。
@Singleton
@Bean(typed = Engine.class)
public class V8Engine implements Engine {
@Override
public String start() {
return "Starting V8";
}
@Override
public int getCylinders() {
return 8;
}
}
@Bean(typed=…)被用来只允许注入接口引擎,而不是具体类型。类必须实现typed所定义的类或接口,否则会发生编译错误。
@MicronautTest
public class EngineSpec {
@Inject
BeanContext beanContext;
@Test
public void testEngine() {
assertThrows(NoSuchBeanException.class, () ->
beanContext.getBean(V8Engine.class)
);
final Engine engine = beanContext.getBean(Engine.class);
assertTrue(engine instanceof V8Engine);
}
}
上面的测试演示了使用编程查询和BeanContext API的typed的行为。
完结!