关于后端:quarkus依赖注入之一创建bean

6次阅读

共计 10277 个字符,预计需要花费 26 分钟才能阅读完成。

欢送拜访我的 GitHub

这里分类和汇总了欣宸的全副原创 (含配套源码):https://github.com/zq2599/blog_demos

对于依赖注入

  • 对一名 java 程序员来说,依赖注入应该是个相熟的概念,简略的说就是:我要用 XXX,但我不负责 XXX 的生产
  • 以下代码来自 spring 官网,serve 办法要应用 MyComponent 类的 doWork 办法,然而不负责 MyComponent 对象的实例化,只有用注解 Autowired 润饰成员变量 myComponent,spring 环境会负责为 myComponent 赋值一个实例
@Service
public class MyService {
    
    @Autowired
    MyComponent myComponent;
    
    public String serve() {myComponent.doWork();
        return "success";
    }
}
  • 对于依赖注入,网上有很多优良文章,这里就不开展了,咱们要关注的是 quarkus 框架的依赖注入

对于《quarkus 依赖注入》系列

  • 《quarkus 依赖注入》共六篇文章,整体规划上隶属于《quarkus 实战》系列,但专一于依赖注入的知识点和实战
  • 如果您相熟 spring 的依赖注入,那么浏览本系列时会发现 quarkus 与 spring 之间有太多相似之处,很多中央一看就懂

本篇概览

  • 作为《quarkus 依赖注入》的开篇,本文先介绍 CDI,再学习如何创立 bean 实例,全文内容如下
graph LR

L1(本篇内容) --> L2-1(官网揭示)
L1 --> L2-2(CDI)
L1 --> L2-3(创立 bean)

L2-2 --> L3-1(对于 CDI)
L2-2 --> L3-2(对于 bean)

L2-3 --> L3-3(注解润饰在类上)
L2-3 --> L3-4(注解润饰在办法上)
L2-3 --> L3-5(注解润饰在成员变量上)
L2-3 --> L3-6(扩大组件中的 synthetic bean)
  • 学习 quarkus 的依赖注入之前,来自官网的揭示十分重要

官网揭示

  • 在应用依赖注入的时候,quankus 官网倡议 <font color=”red”> 不要应用公有变量 </font>(用默认可见性,即雷同 package 内可见),因为 GraalVM 将利用制作成二进制可执行文件时,编译器名为 <font color=”blue”>Substrate VM</font>,操作公有变量须要用到反射,而 GraalVM 应用反射的限度,导致动态编译的文件体积增大
Quarkus is designed with Substrate VM in mind. For this reason, we encourage you to use *package-private* scope instead of *private*.

对于 CDI

  • 《Contexts and Dependency Injection for Java 2.0》,简称 CDI,该标准是对 JSR-346 的更新,quarkus 对依赖注入的反对就是基于此标准实现的
  • 从 2.0 版开始,CDI 面向 Java SE 和 Jakarta EE 平台,Java SE 中的 CDI 和 Jakarta EE 容器中的 CDI 共享 core CDI 中定义的个性。
  • 简略看下 CDI 标准的内容(请原谅欣宸的英语水平):
  1. 该标准定义了一组弱小的补充服务,有助于改良利用程序代码的构造
  2. 给有状态对象定义了生命周期,这些对象会绑定到上下文,上下文是可扩大的
  3. 简单的、平安的依赖注入机制,还有开发和部署阶段抉择依赖的能力
  4. 与 Expression Language (EL) 集成
  5. 装璜注入对象的能力(集体想到了 AOP,你拿到的对象其实是个代理)
  6. 拦截器与对象关联的能力
  7. 事件告诉模型
  8. web 会话上下文
  9. 一个 SPI:容许 <font color=”blue”> 便携式扩大 </font> 与 <font color=”blue”> 容器 </font> 的集成(integrate cleanly)

对于 CDI 的 bean

  • CDI 的实现(如 quarkus),容许对象做这些事件:
  1. 绑定到生命周期上下文
  2. 注入
  3. 与拦截器和装璜器关联
  4. 通过触发和察看事件,以涣散耦合的形式交互
  • 上述场景的对象统称为 <font color=”red”>bean</font>,上下文中的 bean 实例称为 <font color=”blue”> 上下文实例 </font>,上下文实例能够通过依赖注入服务注入到其余对象中
  • 对于 CDI 的背景常识就介绍到这里吧,接下来要写代码了

源码下载

  • 本篇实战的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示 (https://github.com/zq2599/blog_demos)
名称 链接 备注
我的项目主页 https://github.com/zq2599/blog_demos 该我的项目在 GitHub 上的主页
git 仓库地址 (https) https://github.com/zq2599/blog_demos.git 该我的项目源码的仓库地址,https 协定
git 仓库地址 (ssh) git@github.com:zq2599/blog_demos.git 该我的项目源码的仓库地址,ssh 协定
  • 这个 git 我的项目中有多个文件夹,本次实战的源码在 <font color=”blue”>quarkus-tutorials</font> 文件夹下,如下图红框
    <img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/image-20220312091203116.png” alt=”image-20220312091203116″ style=”zoom: 80%;” />
  • <font color=”blue”>quarkus-tutorials</font> 是个父工程,外面有多个 module,本篇实战的 module 是 <font color=”red”>basic-di</font>,如下图红框
    <img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/image-20220312091404031.png” alt=”image-20220312091404031″ style=”zoom:80%;” />

创立 demo 工程

  • 您能够参考《quarkus 实战之二:利用的创立、构建、部署》,创立个最简略的 web 工程,默认生成一个 web 服务类 HobbyResource.java,代码如下,前面的演示代码都写在这个工程中
package com.bolingcavalry;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/actions")
public class HobbyResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {return "Hello RESTEasy," + LocalDateTime.now();
    }
}
  • 接下来,从最根底的创立 bean 实例创立开始

创立 bean 实例:注解润饰在类上

  • 先来看看 spring 是如何创立 bean 实例的,回顾文章刚开始的那段代码,myComponent 对象来自哪里?
  • 持续看 spring 官网的 demo,如下所示,用 <font color=”blue”>Component</font> 注解润饰在类上,spring 就会实例化 MyComponent 对象并注册在 bean 容器中,须要用此 bean 的时候用 Autowired 注解就能够注入了
@Component
public class MyComponent {public void doWork() {}}
  • quarkus 框架下也有相似形式,演示类 ClassAnnotationBean.java 如下,用注解 <font color=”blue”>ApplicationScoped</font> 去润饰 ClassAnnotationBean. 类,如此 quarkus 就会实例化此类并放入容器中
package com.bolingcavalry.service.impl;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ClassAnnotationBean {public String hello() {return "from" + this.getClass().getSimpleName();}
}
  • 这种注解润饰在类上的 bean,被 quarkus 官网成为 <font color=”blue”>class-based beans</font>
  • 应用 bean 也很简略,如下,用注解 <font color=”blue”>Inject</font> 润饰 ClassAnnotationBean 类型的成员变量即可
package com.bolingcavalry;

import com.bolingcavalry.service.impl.ClassAnnotationBean;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/classannotataionbean")
public class ClassAnnotationController {

    @Inject
    ClassAnnotationBean classAnnotationBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                classAnnotationBean.hello());
    }
}
  • 如何验证上述代码是否无效?运行服务,再用浏览器拜访 <font color=”blue”>classannotataionbean</font> 接口,肉眼判断返回内容是否符合要求,这样尽管可行,但总感觉会被讥嘲低效 …
  • 还是写一段单元测试代码吧,如下所示,留神要用 <font color=”blue”>QuarkusTest</font> 注解润饰测试类(不然服务启动有问题),测试方法中查看了返回码和 body,如果后面的依赖注入没问题,则上面的测试应该能通过才对
package com.bolingcavalry;

import com.bolingcavalry.service.impl.ClassAnnotationBean;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
class ClassAnnotationControllerTest {

    @Test
    public void testGetEndpoint() {given()
                .when().get("/classannotataionbean")
                .then()
                .statusCode(200)
                // 查看 body 内容,是否含有 ClassAnnotationBean.hello 办法返回的字符串
                .body(containsString("from" + ClassAnnotationBean.class.getSimpleName()));
    }
}
  • 执行命令 <font color=”blue”>mvn clean test -U</font> 开始测试,控制台输入如下,提醒测试通过
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.702 s
[INFO] Finished at: 2022-03-12T15:48:45+08:00
[INFO] ------------------------------------------------------------------------
  • 如果您的开发工具是 IDEA,也能够用它的图形化工具执行测试,如下图,能失去更丰盛的测试信息
  • 把握了最根底的实例化形式,接着看下一种形式:润饰在办法上

创立 bean 实例:注解润饰在办法上

  • 下一种创立 bean 的形式,咱们还是先看 spring 是怎么做的,有了它作比照,对 quarkus 的做法就好了解了
  • 来看 spring 官网文档上的一段代码,如下所示,用 <font color=”blue”>Bean</font> 注解润饰 <font color=”red”>myBean</font> 办法,spring 框架就会执行此办法,将返回值作为 bean 注册到容器中,spring 把这种 bean 的处理过程称为 <font color=”blue”>lite mode</font>
@Component
 public class Calculator {public int sum(int a, int b) {return a+b;}

     @Bean
     public MyBean myBean() {return new MyBean();
     }
 }
  • kuarkus 框架下,也能用注解润饰办法来创立 bean,为了演示,先定义个一般接口
package com.bolingcavalry.service;

public interface HelloService {String hello();
}
  • 以及 HelloService 接口的实现类
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;

public class HelloServiceImpl implements HelloService {
    @Override
    public String hello() {return "from" + this.getClass().getSimpleName();}
}
  • 留神,HelloService.java 和 HelloServiceImpl.java 都是一般的 java 接口和类,与 quarkus 没有任何关系
  • 上面的代码演示了用注解润饰办法,使得 quarkus 调用此办法,将返回值作为 bean 实例注册到容器中,<font color=”blue”>Produces</font> 告诉 quarkus 做实例化,<font color=”red”>ApplicationScoped</font> 表明了 bean 的作用域是整个利用
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

public class MethodAnnonationBean {

    @Produces
    @ApplicationScoped
    public HelloService getHelloService() {return new HelloServiceImpl();
    }
}
  • 这种用于创立 bean 的办法,被 quarkus 称为 <font color=”blue”>producer method</font>
  • 看过上述代码,置信聪慧的您应该明确了用这种形式创立 bean 的长处:在创立 HelloService 接口的实例时,能够管制所有细节(构造方法的参数、或者从多个 HelloService 实现类中抉择一个),没错,在 SpringBoot 的 Configuration 类中咱们也是这样做的
  • 后面的 getHelloService 办法的返回值,能够间接在业务代码中依赖注入,如下所示
package com.bolingcavalry;

import com.bolingcavalry.service.HelloService;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/methodannotataionbean")
public class MethodAnnotationController {

    @Inject
    HelloService helloService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                helloService.hello());
    }
}
  • 单元测试代码如下
package com.bolingcavalry;

import com.bolingcavalry.service.impl.HelloServiceImpl;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
class MethodAnnotationControllerTest {

    @Test
    public void testGetEndpoint() {given()
                .when().get("/methodannotataionbean")
                .then()
                .statusCode(200)
                // 查看 body 内容,HelloServiceImpl.hello 办法返回的字符串
                .body(containsString("from" + HelloServiceImpl.class.getSimpleName()));
    }
}
  • 测试通过
  • producer method 有个个性须要重点关注:如果方才生产 bean 的 <font color=”blue”>getHelloService</font> 办法有个入参,如下所示,入参是 OtherService 对象,那么,<font color=”red”> 这个 OtherService 对象也必须是个 bean 实例 </font>(这就像你用 @Inject 注入一个 bean 的时候,这个 bean 必须存在一样),如果 OtherService 不是个 bean,那么利用初始化的时候会报错,(其实这个个性 SpringBoot 中也有,置信经验丰富的您在应用 Configuration 类的时候应该用到过)
public class MethodAnnonationBean {

    @Produces
    @ApplicationScoped
    public HelloService getHelloService(OtherService otherService) {return new HelloServiceImpl();
    }
}
  • quarkus 还做了个简化:如果有了 <font color=”blue”>ApplicationScoped</font> 这样的作用域注解,那么 <font color=”red”>Produces</font> 能够省略掉,写成上面这样也是失常运行的
public class MethodAnnonationBean {

    @ApplicationScoped
    public HelloService getHelloService() {return new HelloServiceImpl();
    }
}

创立 bean 实例:注解润饰在成员变量上

  • 再来看看最初一种形式,注解在成员变量上,这个成员变量就成了 bean
  • 先写个一般类用于稍后测试
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;

public class OtherServiceImpl {public String hello() {return "from" + this.getClass().getSimpleName();}
}
  • 通过成员变量创立 bean 的形式如下所示,给 otherServiceImpl 减少两个注解,<font color=”blue”>Produces</font> 告诉 quarkus 做实例化,<font color=”red”>ApplicationScoped</font> 表明了 bean 的作用域是整个利用,最终 OtherServiceImpl 实例会被创立后注册到 bean 容器中
package com.bolingcavalry.service.impl;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

public class FieldAnnonationBean {

    @Produces
    @ApplicationScoped
    OtherServiceImpl otherServiceImpl = new OtherServiceImpl();}
  • 这种用于创立 bean 的成员变量(如下面的 otherServiceImpl),被 quarkus 称为 <font color=”blue”>producer field</font>
  • 上述 bean 的应用办法如下,可见与后面的应用并无区别,都是从 quarkus 的依赖注入
@Path("/fieldannotataionbean")
public class FieldAnnotationController {

    @Inject
    OtherServiceImpl otherServiceImpl;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                otherServiceImpl.hello());
    }
}
  • 测试代码与后面相似就不赘述了,请您自行实现编写和测试

对于 synthetic bean

  • 还有一种 bean,quarkus 官网称之为 <font color=”blue”>synthetic bean</font>(合成 bean),这种 bean 只会在扩大组件中用到,而咱们日常的利用开发不会波及,synthetic bean 的特点是其属性值并不来自它的类、办法、成员变量的解决,而是由扩大组件指定的,在注册 syntheitc bean 到 quarkus 容器时,罕用 SyntheticBeanBuildItem 类去做相干操作,来看一段实例化 synthetic bean 的代码
@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .runtimeValue(recorder.createFoo("parameters are recorder in the bytecode")) 
                .done();}
  • 至此,《quarkus 依赖注入》的开篇曾经实现,创立 bean 之后还有更精彩的内容为您奉上,敬请期待

欢送关注思否:程序员欣宸

学习路上,你不孤独,欣宸原创一路相伴 …

正文完
 0