欢迎访问我的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官方建议不要使用私有变量(用默认可见性,即相同package内可见),因为GraalVM将应用制作成二进制可执行文件时,编译器名为Substrate VM,操作私有变量需要用到反射,而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:允许便携式扩展与容器的集成(integrate cleanly )

关于CDI的bean

  • CDI的实现(如quarkus),允许对象做这些事情:
  1. 绑定到生命周期上下文

  2. 注入

  3. 与拦截器和装饰器关联

  4. 通过触发和观察事件,以松散耦合的方式交互

  • 上述场景的对象统称为bean,上下文中的 bean 实例称为上下文实例,上下文实例可以通过依赖注入服务注入到其他对象中

  • 关于CDI的背景知识就介绍到这里吧,接下来要写代码了

源码下载

名称 链接 备注
项目主页 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项目中有多个文件夹,本次实战的源码在quarkus-tutorials文件夹下,如下图红框

  • quarkus-tutorials是个父工程,里面有多个module,本篇实战的module是basic-di,如下图红框

创建demo工程

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,如下所示,用Component注解修饰在类上,spring就会实例化MyComponent对象并注册在bean容器中,需要用此bean的时候用Autowired注解就可以注入了
@Component
public class MyComponent {
public void doWork() {}
}
  • quarkus框架下也有类似方式,演示类ClassAnnotationBean.java如下,用注解ApplicationScoped去修饰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官方成为class-based beans
  • 使用bean也很简单,如下,用注解Inject修饰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());
}
}
  • 如何验证上述代码是否有效?运行服务,再用浏览器访问classannotataionbean接口,肉眼判断返回内容是否符合要求,这样虽然可行,但总觉得会被嘲讽低效...
  • 还是写一段单元测试代码吧,如下所示,注意要用QuarkusTest注解修饰测试类(不然服务启动有问题),测试方法中检查了返回码和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()));
}
}
  • 执行命令mvn clean test -U开始测试,控制台输出如下,提示测试通过
[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官方文档上的一段代码,如下所示,用Bean注解修饰myBean方法,spring框架就会执行此方法,将返回值作为bean注册到容器中,spring把这种bean的处理过程称为lite mode
@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实例注册到容器中,Produces通知quarkus做实例化,ApplicationScoped表明了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称为producer method
  • 看过上述代码,相信聪明的您应该明白了用这种方式创建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的getHelloService方法有个入参,如下所示,入参是OtherService对象,那么,这个OtherService对象也必须是个bean实例(这就像你用@Inject注入一个bean的时候,这个bean必须存在一样),如果OtherService不是个bean,那么应用初始化的时候会报错,(其实这个特性SpringBoot中也有,相信经验丰富的您在使用Configuration类的时候应该用到过)
public class MethodAnnonationBean {

    @Produces
@ApplicationScoped
public HelloService getHelloService(OtherService otherService) {
return new HelloServiceImpl();
}
}
  • quarkus还做了个简化:如果有了ApplicationScoped这样的作用域注解,那么Produces可以省略掉,写成下面这样也是正常运行的
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增加两个注解,Produces通知quarkus做实例化,ApplicationScoped表明了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称为producer field

  • 上述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官方称之为synthetic bean(合成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之后还有更精彩的内容为您奉上,敬请期待

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

quarkus依赖注入之一:创建bean的更多相关文章

  1. [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...

  2. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  3. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  4. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  5. Spring的依赖注入和管理Bean

    采用Spring管理Bean和依赖注入 1.实例化spring容器 和 从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] Ap ...

  6. .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...

  7. spring不依赖注入得到实体bean

    如题,我们一般用spring的ioc,通过配置注入接口得到这个实现类,现在通过研究公司平台框架发现还有一种方法得到spring文件配置的bean方法,举个例子(注:这个ApplicationConte ...

  8. Spring的自动装配与依赖注入

    Spring的自动装配与依赖注入 装配 = 创建Bean + 注入Bean 创建Bean 自动发现 显式注册Bean 注入Bean 基于配置的注入 自动注入 Spring的装配分为显式装配和隐式装配, ...

  9. Java Web系列:Spring依赖注入基础

    一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...

  10. Spring源码解析三:IOC容器的依赖注入

    一般情况下,依赖注入的过程是发生在用户第一次向容器索要Bean是触发的,而触发依赖注入的地方就是BeanFactory的getBean方法. 这里以DefaultListableBeanFactory ...

随机推荐

  1. C# 反射 判断类型是否是列表

    1 /// <summary> 2 /// 判断类型是否为可操作的列表类型 3 /// </summary> 4 /// <param name="type&q ...

  2. 【解决办法】配置banner信息时卡死/无反应,以及正确配置

    环境: 工具:锐捷EVE模拟器 远程工具:SecureCRT 系统版本:Windows 10 问题描述 描述:在配置登录 banner 提示警告信息时,将 "^" 符号放到了警告信 ...

  3. 【Ubuntu】4.挂载/连接VM共享文件夹

    第一步 首先需要在虚拟机设置中开启共享文件夹 第二步 修改fstab文件自动挂载 如果您想要自动挂载共享文件夹,可以编辑/etc/fstab文件并添加以下内容:(二选一即可,推荐) sudo gedi ...

  4. 2022-04-12:给定一个字符串形式的数,比如“3421“或者“-8731“, 如果这个数不在-32768~32767范围上,那么返回“NODATA“, 如果这个数在-32768~32767范围上

    2022-04-12:给定一个字符串形式的数,比如"3421"或者"-8731", 如果这个数不在-32768~32767范围上,那么返回"NODAT ...

  5. 2021-09-04:加油站。在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost

    2021-09-04:加油站.在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升.你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost ...

  6. JDBC-Utils层的简单运用

    项目中JDBC的Utils层运行需要以下六个步骤 //1.定义属性为空 private static String driver = null; private static String url = ...

  7. 【GiraKoo】常用编码的对比(ASCII,GB2312,GBK,GB18030,UCS,Unicode)

    常用编码的对比(ASCII,GB2312,GBK,GB18030,UCS,Unicode) 在程序开发中,文字编码一直扮演着人畜无害,却背后捅一刀的角色. 可能在源代码文件中,注释莫名其妙地变成了乱码 ...

  8. 【Java】Eclipse常用快捷键整理

    前言 还是最近在上Java课,由于疫情原因,看的网课,那里的老师比较实战派,很多时候不知道按了什么快捷键就立马出现了很骚的操作.网上查询后发现了一些快捷键对于我这个eclipse小白还是挺常用的,整理 ...

  9. Charles一文全明白

    自从用上了Mac本,抓包工具也从Fiddler换成了Charles,用了这么长时间,也是该对Charles做一个总结了,避免自己下次配置的时候又忘记怎么操作 1.Charles是什么? Charles ...

  10. .NET周报 【5月第4期 2023-05-27】

    国内文章 C#使用词嵌入向量与向量数据库为大语言模型(LLM)赋能长期记忆实现私域问答机器人落地之openai接口平替 https://www.cnblogs.com/gmmy/p/17430613. ...