quarkus依赖注入之一:创建bean
欢迎访问我的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实例,全文内容如下
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规范的内容(请原谅欣宸的英语水平):
- 该规范定义了一组强大的补充服务,有助于改进应用程序代码的结构
- 给有状态对象定义了生命周期,这些对象会绑定到上下文,上下文是可扩展的
- 复杂的、安全的依赖注入机制,还有开发和部署阶段选择依赖的能力
- 与Expression Language (EL)集成
- 装饰注入对象的能力(个人想到了AOP,你拿到的对象其实是个代理)
- 拦截器与对象关联的能力
- 事件通知模型
- web会话上下文
- 一个SPI:允许便携式扩展与容器的集成(integrate cleanly )
关于CDI的bean
- CDI的实现(如quarkus),允许对象做这些事情:
绑定到生命周期上下文
注入
与拦截器和装饰器关联
通过触发和观察事件,以松散耦合的方式交互
上述场景的对象统称为bean,上下文中的 bean 实例称为上下文实例,上下文实例可以通过依赖注入服务注入到其他对象中
关于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项目中有多个文件夹,本次实战的源码在quarkus-tutorials文件夹下,如下图红框
- quarkus-tutorials是个父工程,里面有多个module,本篇实战的module是basic-di,如下图红框
创建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,如下所示,用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的更多相关文章
- [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...
- 依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...
- 依赖注入[4]: 创建一个简易版的DI框架[上篇]
本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...
- .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]
原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...
- Spring的依赖注入和管理Bean
采用Spring管理Bean和依赖注入 1.实例化spring容器 和 从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] Ap ...
- .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...
- spring不依赖注入得到实体bean
如题,我们一般用spring的ioc,通过配置注入接口得到这个实现类,现在通过研究公司平台框架发现还有一种方法得到spring文件配置的bean方法,举个例子(注:这个ApplicationConte ...
- Spring的自动装配与依赖注入
Spring的自动装配与依赖注入 装配 = 创建Bean + 注入Bean 创建Bean 自动发现 显式注册Bean 注入Bean 基于配置的注入 自动注入 Spring的装配分为显式装配和隐式装配, ...
- Java Web系列:Spring依赖注入基础
一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...
- Spring源码解析三:IOC容器的依赖注入
一般情况下,依赖注入的过程是发生在用户第一次向容器索要Bean是触发的,而触发依赖注入的地方就是BeanFactory的getBean方法. 这里以DefaultListableBeanFactory ...
随机推荐
- 虚拟机中Docker下部署gitlab
一.安装Gitlab 1.拉取镜像并启动 由于服务器的80端口可能被占用,所以这里我们改成了其他端口来启动 docker run -d -p 2443:443 -p 5678:80 -p 2222:2 ...
- 2022-06-01:给定一个数组arr,可能有正、有负、有0,无序。 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小。 返回可能的最小的值。
2022-06-01:给定一个数组arr,可能有正.有负.有0,无序. 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小. 返回可能的最小的值. 答案2022-06-01: 排序,双指针. 代码 ...
- 2021-04-21:手写代码:Dijkstra算法。
2021-04-21:手写代码:Dijkstra算法. 福大大 答案2021-04-21: Dijkstra算法是一种基于贪心策略的算法.每次新扩展一个路程最短的点,更新与其相邻的点的路程.时间紧,未 ...
- 2022-02-04:组合总和 Ⅳ。 给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保证
2022-02-04:组合总和 Ⅳ. 给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target .请你从 nums 中找出并返回总和为 target 的元素组合的个数. 题目数据保证 ...
- Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.
问题描述 新建表或者修改表varchar字段长度的时候,出现这个错误 Row size too large. The maximum row size for the used table type, ...
- 「P3」试下1个半月能不能水出个毕设
前言 虽然说标题上写的时间是1个半月,但是实际上,真正开始行动的时间应该1个月都没有.之前都是在公司上班没活的时候干的.现在请了个长假,专门来做毕业设计:预计5月前能做完整个毕业设计,然后劳动节放假期 ...
- java解决中文乱码的几种写法
工作中总会遇到中文乱码问题,以导出文件,文件名称是中文的话,下载下来的文件名称会乱码问题,总结了几种解决文件名乱码的写法,仅供参考. 首先定义一个汉语字符串 String zhName = " ...
- INFINI Labs 产品更新 | Console 新增数据比对、新增数据看板表格组件及支持下钻功能等
INFINI Labs 产品更新啦~,本次产品版本更新包括 Gateway v1.14.0.Console v1.2.0.Easysearch v1.1.1 等,其中 Console 在上一版基础上做 ...
- Java实现猜拳小游戏
Java实现猜拳游戏的核心在于电脑随机数的生成,Java中的随机数生成方法是:首先引入包 import java.util.*; 然后 int r=new Random().nextInt( ...
- 【RS】多光谱波段和全色波段的区别
<p><strong>1.全色波段(Panchromatic Band)</strong></p> 全色图像是单通道的(即单波段灰色影像),其中全色是指 ...