当前位置: 首页 > 工具软件 > Micronaut > 使用案例 >

micronaut 微服务框架的控制反转(Inversion of Control)

朱昊乾
2023-12-01

micronaut 控制反转概述

与其他依赖运行时反射和代理的框架不同,Micronaut使用编译时数据来实现依赖注入。
这与Google Dagger等工具采取的方法类似,后者主要是针对Android设计的。另一方面,Micronaut是为构建服务器端微服务而设计的,它提供了许多与其他框架相同的工具和实用程序,但没有使用反射或缓存过多的反射元数据。
Micronaut IoC容器的目标被总结为:

  • 将反射作为最后的手段
  • 避免代理
  • 优化启动时间
  • 减少内存占用
  • 提供清晰、易懂的错误处理

请注意,Micronaut的IoC部分可以完全独立于Micronaut来使用,无论你想构建什么类型的应用程序。

要做到这一点,配置你的构建,包括micronaut-inject-java依赖,作为一个注释处理器。

最简单的方法是使用Micronaut的Gradle或Maven插件来做到这一点。

Micronaut支持以下类型的依赖注入:

  • Constructor 注入(必须是一个public constructor或一个用@Inject注解的single constructor)
  • Field 注入
  • JavaBean property 注入
  • Method parameter 注入

Micronaut是如何在不需要反射的情况下执行上述依赖注入的。
关键是一组AST转换(对于Groovy)和注解处理器(对于Java),生成实现BeanDefinition接口的类
Micronaut使用ASM字节码库来生成类,由于Micronaut提前知道了注入点,所以不需要像Spring等其他框架那样在运行时扫描所有的方法、字段、构造函数等等。
另外,由于在构造Bean时没有使用反射,JVM可以更好地内联和优化代码,从而获得更好的运行时性能和减少内存消耗。这对于非单体作用域来说尤其重要,因为应用性能取决于Bean创建性能。
此外,使用Micronaut,你的应用程序启动时间和内存消耗不会像使用反射的框架那样受到代码库大小的影响。基于反射的IoC框架为你代码中的每一个字段、方法和构造函数加载并缓存反射数据。因此,随着你的代码大小的增加,你的内存需求也在增加,而Micronaut则不是这样。

示例代码

Engine.java 接口

package com.oracle.micronaut.service;

// 定义了一个通用的 Engine 接口
public interface Engine {
    int getCylinders();
    String start();
}

V8Engine.java 实现 Engine 接口

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";
    }
}

Vehicle.java 调用 Engine 的方法

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

Application.java 调用 Vehicle 的方法

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

运行 Application.java

输出结果,

Starting V8

BeanContext 说明

BeanContext是所有Bean定义的一个容器对象(它也实现了BeanDefinitionRegistry)。它也是Micronaut的初始化点。
然而,一般来说,你不会直接与BeanContext API交互,而可以简单地使用jakarta.inject注释和io.micronaut.context.annotation包中的注释来满足你的依赖注入需求。

修改 Application.java 使用 BeanContext

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

运行 Application.java

输出结果,

Starting V8

@Order 注解

当注入一个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);
    }
}

@Named 注解

如果你对一个给定的接口有多种可能的实现要注入,你需要使用一个限定符。
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 注解

除了能够通过名字来限定,你还可以使用Qualifier注解来建立你自己的限定符。

V8.java 类示例代码

import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
public @interface V8 {
}

Vehicle.java 类示例代码

@Inject Vehicle(@V8 Engine engine) {
    this.engine = engine;
}

@Qualifier 注解成员限定

从Micronaut 3.0开始,注解限定符也可以使用注解成员来解决正确的bean注入。

Cylinders.java 类示例代码

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 "";
}

V6Engine.java 类示例代码

@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";
    }
}

V8Engine.java 类示例代码

@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";
    }
}

Vehicle.java 类示例代码

@Inject Vehicle(@Cylinders(8) Engine engine) {
    this.engine = engine;
}

通过通用类型参数进行限定

从Micronaut 3.0开始,可以根据类或接口的通用类型参数来选择要注入的bean。

CylinderProvider.java 类示例代码

public interface CylinderProvider {
    int getCylinders();
}

Engine.java 类示例代码

public interface Engine<T extends CylinderProvider> {
    default int getCylinders() {
        return getCylinderProvider().getCylinders();
    }

    default String start() {
        return "Starting " + getCylinderProvider().getClass().getSimpleName();
    }

    T getCylinderProvider();
}

V6.java 类示例代码

public class V6 implements CylinderProvider {
    @Override
    public int getCylinders() {
        return 6;
    }
}

V6Engine 类示例代码

@Singleton
public class V6Engine implements Engine<V6> {
    @Override
    public V6 getCylinderProvider() {
        return new V6();
    }
}

V8.java 类示例代码

public class V8 implements CylinderProvider {
    @Override
    public int getCylinders() {
        return 8;
    }
}

V8Engine 类示例代码

@Singleton
public class V8Engine implements Engine<V8> {
    @Override
    public V8 getCylinderProvider() {
        return new V8();
    }
}

Vehicle.java 类示例代码

@Inject
public Vehicle(Engine<V8> engine) {
    this.engine = engine;
}

@Primary 注解

Primary是一个限定词,表示在有多个接口实现的情况下,一个Primary bean是要被选择的bean。

ColorPicker.java 类示例代码

public interface ColorPicker {
    String color();
}

Green.java 类示例代码

import io.micronaut.context.annotation.Primary;
import jakarta.inject.Singleton;

@Primary
@Singleton
class Green implements ColorPicker {

    @Override
    public String color() {
        return "green";
    }
}

Blue.java 类示例代码

import jakarta.inject.Singleton;

@Singleton
public class Blue implements ColorPicker {

    @Override
    public String color() {
        return "blue";
    }
}

TestController.java 类示例代码

@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的优先级

@Any 注解

如果你对被注入的Bean没有特别的要求,那么你可以使用@Any限定词,它将注入第一个可用的Bean。@Any限定符通常与BeanProvider接口一起使用,以允许更多的动态用例。

@Any 定义的示例代码

@Inject @Any
Engine engine;

Vehicle.java 类示例代码

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注解的类型化成员来限制暴露的类型。

V8Engine.java 类示例代码

@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所定义的类或接口,否则会发生编译错误。

EngineSpec.java 类示例代码

@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的行为。

完结!

 类似资料: