zoukankan      html  css  js  c++  java
  • 云原生quarkus框架项目实践

    写在前面,   不知不觉上篇文章已经是好几年前了,  回到博客园倍感亲切.   总想写点什么,  发现博客园里关于quarkus的文章不多,  故把自己在项目过程中的点滴整理如下,  希望对您有所帮助.

    一、quarkus 是什么?为什么要用quarkus

    quarkus是Redhat开源的云原生微服务框架,  相比较成熟的SpringCloud, 为什么要用quarkus?

    主要有以下几点原因:

    1. Spring系列框架臃肿、复杂, 更像是一个全家桶. 而quarkus 简单、高效, 工具先进
    2. 启动速度, quarkus可以在5秒内启动, 而spring对于一个golang开发者来说, 这个速度直接无法忍受.
    3. quarkus可以热编译, 无需手动编译和重启服务, 而Spring的热编译..
    4. 与其他工具集成, Spring集成了大部分的工具, 但你把DI换成guice试试, quarkus可以很方便的集成工具, 虽然框架本身包含的东西不多
    5. quarkus不依赖tomcat或jetty, 可以编译为原生应用, 性能大幅提高
    6. quarkus耦合低, 项目结构干净, 适合使用代码生成器.

    二、创建一个quarkus项目

    您可以使用maven或gradle来快速创建一个quarkus项目, 具体方法见quarkus网站, quarkus 只需要创建一个Resource类, 就可以启动服务.  零配置.  

    另外:quarkus 对Kotlin支持极为友好,  本文将创建一个使用Kotlin+Gradle的项目.  项目的配置文件: build.gradle.kts内容如下:

    plugins{
        java
        kotlin("jvm") version ("1.3.72")
        kotlin("plugin.allopen") version ("1.3.72")
        id("io.quarkus") version("1.4.2.Final")
    }
    allOpen {
        annotation("javax.enterprise.context.ApplicationScoped")
        annotation("javax.enterprise.context.RequestScoped")
    }
    
    repositories {
        maven("http://maven.aliyun.com/nexus/content/groups/public/")
        mavenCentral()
    }
    
    dependencies {
        implementation(kotlin("stdlib"))
        implementation("io.quarkus:quarkus-kotlin:1.4.2.Final")
        implementation("io.quarkus:quarkus-resteasy:1.4.2.Final")
        implementation("io.quarkus:quarkus-resteasy-jsonb:1.4.2.Final")
        testImplementation("io.quarkus:quarkus-junit5:1.4.2.Final")
    }
    tasks.withType<Test> {
        useJUnitPlatform()
    }
    // 代码生成器 tasks.create(
    "generate").doFirst { exec{ workingDir("./tto") commandLine("sh","-c","./tto.sh") } } tasks.withType<JavaCompile>().configureEach { options.encoding="utf-8" options.compilerArgs = listOf("-Xdoclint:none", "-Xlint:none", "-nowarn") }

    三、配置并启动项目

    您可以创建一个类, 并添加注解:@ApplicationScoped , 作为系统启动类,  代码如下:

    @ApplicationScoped
    class Application {
        fun onStart(@Observes event: StartupEvent?) {
            println("app started..")
        }
    }

    这并不是必须的,  因为上文提到了,  可能需要集成其他工具. 接着我们创建一个服务如下:

    import javax.ws.rs.GET
    import javax.ws.rs.Path
    import javax.ws.rs.Produces
    import javax.ws.rs.core.MediaType
    @Path(
    "/hello") class HelloResource { @GET@Path("/{name}") @Produces(MediaType.APPLICATION_JSON) fun hello(@PathParam("name") name:String): String { return "hello ${name}" } }

    运行命令启动服务

    gradle quarkusDev

    访问服务

    curl http://localhost:8080/hello/jarrysix
    > hello jarrysix

     

    三、使用数据源

    通过上面的步骤, 我们已能运行quarkus, 接下来我们通过极为简单的方式来完成数据源的访问.

    首先, 我们需要添加配置:

    quarkus.datasource.db-kind=h2
    quarkus.datasource.username=username-default
    quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:default
    quarkus.datasource.jdbc.min-size=3
    quarkus.datasource.jdbc.max-size=13

    创建实体类

    @Entity
    public class Gift {
        @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="giftSeq")
        private Long id;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        private String name;
        public String getName() {
            return name;
        }
    }

    创建Panachec仓储类

    @ApplicationScoped
    public class PersonRepository implements PanacheRepository<Person> {
       // put your custom logic here as instance methodspublic void deletePerson(name:String){
           delete("name",name);
       }
    }

    在资源类中调用仓储类

    @Path("/person")
    class HelloResource {
        @Inject
        private lateinit var repo:PersonRepository
    
        @DELETE@Path("/{name}")
        fun delete(@PathParam("name") name:String): String {
            this.repo.deletePerson(name);
            return "success"
        }
    }

    当然在实际项目中不建议直接调用仓储,  就这样我们完成人员删除的服务.

    三:使用docker打包镜像

    quarkus可以通过GraalVM打包成原生镜像, 以在生产环境中得到更低的CPU和内存占用.   如果您不想本地打包, 可以使用docker镜像打包为原生应用.

    本文为了简化, 依然使用JVM来运行quarkus, 镜像构建配置文件如下:

    # Quarkus docker image demo
    # Version 1.0
    # Author : jarrysix(homepage: http://fze.net)
    # Date : 2018-04-13 14:40
    
    FROM adoptopenjdk/openjdk14-openj9:alpine-jre
    
    MAINTAINER jarrysix
    
    WORKDIR /data
    WORKDIR /app
    COPY build/*.jar ./
    COPY build/lib ./lib
    
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && 
        apk add tzdata fontconfig ttf-dejavu && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    EXPOSE 8080 ENTRYPOINT ["java","-jar *-runner.jar"]

    四:使用代码生成器

    因为quarkus的项目结构及对框架和工具依赖较低,  甚至仔细观察,  项目代码里大多引用的就是JAVA自带的工具集.  这样对我们使用代码生成器来生成一些格式重复的代码是相当有利的.

    我在生产环境中, 就用生成器来生成quarkus和vue.js的代码. 极大的减少了工作量.   接下来我们一步一步的创建代码模板并生成代码.

    注: 文中使用的是go编写的代码生成器:tto , 项目主页: http://github.com/ixre/tto ; 其他工具也可以达到效果

    1. 数据实体代码模板:  pojo.java

    #!target:java/{{.global.Pkg}}/pojo/{{.table.Title}}Entity.java
    package {{pkg "java" .global.Pkg}}.pojo;
    
    import javax.persistence.Basic;
    import javax.persistence.Id;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Table;
    import javax.persistence.GenerationType;
    import javax.persistence.GeneratedValue;
    
    /** {{.table.Comment}} */
    @Entity
    @Table(name = "{{.table.Name}}", schema = "{{.table.Schema}}")
    public class {{.table.Title}}Entity {
        {{range $i,$c := .columns}}{{$type := type "java" $c.Type}}
    
        {{if $c.IsPk}}
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY){{else}}
        @Basic{{end}}
        @Column(name = "{{$c.Name}}"{{if not $c.NotNull}}, nullable = true{{end}} {{if ne $c.Length 0}},length = {{$c.Length}}{{end}})
        private {{$type}} {{$c.Name}};
    
        /** {{$c.Comment}} */
        public {{$type}} get{{$c.Prop}}() {
            return this.{{$c.Name}};
        }
    
        public void set{{$c.Prop}}({{$type}} {{$c.Name}}){
            this.{{$c.Name}} = {{$c.Name}};
        }
    
        {{end}}
    
        /** 拷贝数据  */
        public {{.table.Title}}Entity copy({{.table.Title}}Entity src){
            {{.table.Title}}Entity dst = new {{.table.Title}}Entity();
            {{range $i,$c := .columns}}
            dst.set{{$c.Prop}}(src.get{{$c.Prop}}());{{end}}
            return dst;
        }
    }

    2. 仓储代码模板:  quarkus_repo.kt

    #!target:kotlin/{{.global.Pkg}}/repo/{{.table.Title}}JpaRepository.kt.gen
    package {{pkg "java" .global.Pkg}}.repo;
    
    import {{pkg "kotlin" .global.Pkg}}.pojo.{{.table.Title}}Entity
    import io.quarkus.hibernate.orm.panache.PanacheRepository
    import javax.enterprise.context.ApplicationScoped
    
    {{$pkType := type "kotlin" .table.PkType}}
    /** {{.table.Comment}}仓储 */
    @ApplicationScoped
    class {{.table.Title}}JpaRepository : PanacheRepository<{{.table.Title}}Entity> {
    
    }

    3. 服务代码模板:quarkus_service.kt

    #!target:kotlin/{{.global.Pkg}}/service/{{.table.Title}}Service.kt.gen
    package {{pkg "java" .global.Pkg}}.service
    
    import {{pkg "java" .global.Pkg}}.pojo.{{.table.Title}}Entity
    import {{pkg "java" .global.Pkg}}.repo.{{.table.Title}}JpaRepository
    import javax.inject.Inject
    import javax.enterprise.inject.Default
    import javax.enterprise.context.ApplicationScoped
    import net.fze.util.catch
    import net.fze.commons.std.Types
    import net.fze.commons.std.TypesConv
    import net.fze.util.value
    import javax.transaction.Transactional
    
    {{$tableTitle := .table.Title}}
    {{$pkName := .table.Pk}}
    {{$pkProp := lower_title .table.PkProp}}
    {{$pkType := type "kotlin" .table.PkType}}
    /** {{.table.Comment}}服务  */
    @ApplicationScoped
    class {{.table.Title}}Service {
        @Inject@field:Default
        private lateinit var repo: {{$tableTitle}}JpaRepository
    
        fun parseId(id:Any):Long{return TypesConv.toLong(id)}
    
        /** 根据ID查找{{.table.Comment}} */
        fun findByIdOrNull(id:{{$pkType}}):{{$tableTitle}}Entity?{
            return this.repo.findByIdOptional(this.parseId(id))
        }
    
        /** 保存{{.table.Comment}} */
        @Transactional
        fun save{{$tableTitle}}(e: {{$tableTitle}}Entity):Error? {
            return catch {
                var dst: {{$tableTitle}}Entity
                if (e.{{$pkProp}} > 0) {
                    dst = this.repo.findById(this.parseId(e.{{$pkProp}}))!!
                } else {
                    dst = {{$tableTitle}}Entity()
                    {{$c := try_get .columns "create_time"}}
                    {{if ne $c nil}}dst.createTime = Types.time.unix().toLong(){{end}}
                }
                {{range $i,$c := exclude .columns $pkName "create_time" "update_time"}}
                dst.{{lower_title $c.Prop}} = e.{{lower_title $c.Prop}}{{end}}
                {{$c := try_get .columns "update_time"}}
                {{if ne $c nil}}dst.updateTime = Types.time.unix().toLong(){{end}}
                this.repo.persistAndFlush(dst)
                null
            }.error()
        }
    
        /** 批量保存{{.table.Comment}} */
        @Transactional
        fun saveAll{{$tableTitle}}(entities:Iterable<{{$tableTitle}}Entity>){
            this.repo.persist(entities)
            this.repo.flush()
        }
    
        /** 删除{{.table.Comment}} */
        @Transactional
        fun deleteById(id:{{$pkType}}):Error? {
            return catch {
                this.repo.deleteById(this.parseId(id))
            }.error()
        }
    
    }

    4. 资源类代码模板:restful_resource.kt

    #!target:kotlin/{{.global.Pkg}}/resources/{{.table.Title}}Resource.kt.gen
    package {{pkg "java" .global.Pkg}}.resources
    
    import {{pkg "java" .global.Pkg}}.pojo.{{.table.Title}}Entity
    import {{pkg "java" .global.Pkg}}.service.{{.table.Title}}Service
    import {{pkg "java" .global.Pkg}}.component.TinyQueryComponent
    import net.fze.commons.std.Result
    import net.fze.component.report.DataResult
    import javax.inject.Inject
    import javax.ws.rs.*
    import javax.ws.rs.core.MediaType
    import javax.enterprise.context.RequestScoped
    import javax.annotation.security.PermitAll
    
    {{$tableTitle := .table.Title}}
    {{$pkType := type "kotlin" .table.PkType}}
    
    /* {{.table.Comment}}资源 */
    @Path("/{{.table.Name}}")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @RequestScoped
    class {{.table.Title}}Resource {
        @Inject private lateinit var service:{{.table.Title}}Service
        @Inject private lateinit var queryComponent: TinyQueryComponent
    
        /** 获取{{.table.Comment}} */
        @GET@Path("/{id}")
        @PermitAll
        fun get(@PathParam("id") id:{{$pkType}}): {{.table.Title}}Entity? {
            return service.findByIdOrNull(id)
        }
    
        /** 创建{{.table.Comment}} */
        @POST
        @PermitAll
        fun create(entity: {{.table.Title}}Entity):Result {
            val err = this.service.save{{.table.Title}}(entity)
            if(err != null)return Result.create(1,err.message)
            return Result.OK
        }
    
        /** 更新{{.table.Comment}} */
        @PUT@Path("/{id}")
        @PermitAll
        fun save(@PathParam("id") id:{{$pkType}},entity: {{.table.Title}}Entity):Result {
            entity.{{lower_title .table.PkProp}} = id
            val err = this.service.save{{.table.Title}}(entity)
            if(err != null)return Result.create(1,err.message)
            return Result.OK
        }
    
    
        /** 删除{{.table.Comment}} */
        @DELETE@Path("/{id}")
        @PermitAll
        fun delete(@PathParam("id") id:{{$pkType}}):Result {
            val err = this.service.deleteById(id)
            if(err != null)return Result.create(1,err.message)
            return Result.OK
        }
    
        /** {{.table.Comment}}列表 */
        @GET
        @PermitAll
        fun list(): List<{{.table.Title}}Entity> {
            return mutableListOf()
        }
    
        /** {{.table.Comment}}分页数据 */
        @GET@Path("/paging")
        @PermitAll
        fun paging(@QueryParam("params") params:String,
                   @QueryParam("page") page:String,
                   @QueryParam("rows") rows:String
        ): DataResult {
            return this.queryComponent.fetchData("default",
                    "{{.table.Title}}List", params, page, rows)
        }
    }

    5. VUE接口文件代码模板:api.ts

    #!lang:ts#!name:API和定义文件
    #!target:ts/feature/{{.table.Prefix}}/{{.table.Name}}/api.ts
    import request from '@/utils/request'
    
    
    // {{.table.Comment}}对象
    export interface I{{.table.Title}} {
        {{range $i,$c := .columns}}// {{$c.Comment}}
        {{lower_title $c.Prop}}:{{type "ts" $c.Type}}
        {{end}}
    }
    
    export const default{{.table.Title}}:()=>I{{.table.Title}}=()=>{
        return {
            {{range $i,$c := .columns}}
            {{lower_title $c.Prop}}:{{default "ts" $c.Type}},{{end}}
        };
    }
    
    export const get{{.table.Title}} = (id: any, params: any = {}) =>
        request({
            url: `/{{.table.Name}}/${id}`,
            method: 'get',
            params:{...params}
        })
    
    export const get{{.table.Title}}List = (params: any = {}) =>
        request({
            url: '/{{.table.Name}}',
            method: 'get',
            params:{...params}
        })
    
    export const create{{.table.Title}} = (data: any) =>
        request({
            url: '/{{.table.Name}}',
            method: 'post',
            data
        })
    
    export const update{{.table.Title}} = (id: any, data: any) =>
        request({
            url: `/{{.table.Name}}/${id}`,
            method: 'put',
            data
        })
    
    export const delete{{.table.Title}} = (id: any) =>
        request({
            url: `/{{.table.Name}}/${id}`,
            method: 'delete'
        });
    
    export const batchDelete{{.table.Title}} = (arr: any[]) =>
        request({
            url: '/{{.table.Name}}',
            method: 'delete',
            data:arr
        });
    
    
    export const getPaging{{.table.Title}} = (page:number,rows:number,params: any) =>
        request({
            url: '/{{.table.Name}}/paging',
            method: 'get',
            params:{page,rows,params}
        })

    运行命令将代码生成到指定位置

    gradle generate

    五:写在最后

    因作者写作水平有限,  文中以最精简的方法介绍了quarkus的应用, 包括生成代码等骚操作. 

    示例代码打包下载地址:quarkus-kotlin-gradle-demo-feature.zip

  • 相关阅读:
    JavaScript脚本的两种放置方式
    对象 属性 事件 方法
    媒体查询
    HTML5布局
    图像
    布局
    列表,表格,表单
    盒子
    vue.js常见面试题及常见命令介绍
    Winform读报工具
  • 原文地址:https://www.cnblogs.com/newmin/p/quarkus-simle-guide.html
Copyright © 2011-2022 走看看