跳到主要内容

2.3 服务与控制反转

在上一节中,我们学习了 Micronaut 的 API 入口:控制器(Controller),有了它,我们就可以实现我们的业务代码了。但实际项目中,由于项目要实现的业务很多,代码量会比较大,将所有的代码写在控制器里是很难有效的维护项目的。

我们会使用很多方法对代码实现进行分层,这些分层的思想总结出来就是我们常用的项目架构,如:MVC、MVVM 等。按最简单、最常用的 MVC 架构来对代码分层,控制器(C,Controller)用于对外接受请求、响应数据等,具体的业务实现我们会再增加一层代码目录,一般叫作“服务(service)”用于实现具体的业务操作,而这些业务操作抽离为服务的类之后,可以被不同的控制器组合使用。

而服务的类要被使用,需要先实例化为一个对象。在原生的 Java 代码中,我们直接使用 new XxxService() 来生成实例化的对象,但是在框架中,如 Spring Boot 或 Micronaut 中,框架都会为我们提供控制反转(IoC)来生成实例对象,有了这个,也就实现了依赖注入(DI)。在 Micronaut 框架中,我们通过注解的方式来实现控制反转。

Service

我们首先创建一个服务类,名叫 HelloService,该类中我们提供两个实现,用于实现上一节中 HelloController 两个接口实现的逻辑。代码如下:

package fun.mortnon.demo;

import jakarta.inject.Singleton;

/**
* @author dev2007
* @date 2023/5/26
*/
@Singleton
public class HelloService {
public String hello() {
return "hello world";
}

public String echo(String txt) {
return "response:" + txt;
}
}

在类中,我们看到有一个注解 @Singleton,该注解表明该类需要由框架的 IoC 进行实例化,并且实例化的对象是一个单例的(即,每个使用它的地方都是使用的同一个对象)。

接着,我们修改 HelloController,将原有的逻辑调整为调用 HelloService 的方法,代码如下:

package fun.mortnon.demo;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import jakarta.inject.Inject;

/**
* @author dev2007
* @date 2023/3/24
*/
@Controller
public class HelloController {

@Inject
private HelloService helloService;

@Get("/hello")
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return helloService.hello();
}

@Post("/echo")
@Consumes(MediaType.TEXT_PLAIN)
public String echo(String txt) {
return helloService.echo(txt);
}
}

代码中,我们可以看到声明了一个字段 private HelloService helloService;,同时它带有一个注解 @Inject。在整个 HelloController 中都没有 new HelloService() 的代码,全通过 Micronaut 框识别到注解 @Inject 后,自动为 helloService 生成了实例。

以上代码运行后,效果与上一节中的效果完全一致。

IoC 注解

在以上的代码中,HelloService 使用了一个注解 @Singleton,它代表的是通过控制反转生成的对象是单例的。在 Micronaut 框架中,有多种注解用于声明控制反转,他们各自有自己的适用情况。如下表 1。

表 1

类型描述
@SingletonSingleton 作用域表示只存在 bean 的一个实例
@ContextContext 作用域表示 bean 将与 ApplicationContext 同时创建(急切初始化)
@PrototypePrototype 作用域表示每次注入 bean 时都会创建一个新的 bean 实例
@InfrastructureInfrastructure 作用域表示不能使用 @Replaces 重写或替换的 bean,因为它对系统的运行至关重要。
@ThreadLocal@ThreadLocal 作用域是一个自定义作用域,通过 ThreadLocal 为每个线程关联一个 bean
@Refreshable@Refreshable 作用域是一个自定义作用域,允许通过 /refresh 端点刷新bean的状态。
@RequestScope@RequestScope 作用域是一个自定义作用域,它指示创建了 bean 的新实例并与每个 HTTP 请求相关联

以上这些注解中,我们最常用的就是 @Singleton,其他的注解按具体的需求使用即可。