欢迎来到 安阳市某某运输服务培训中心
全国咨询热线:020-123456789
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

新闻中心
最佳实践之接口规范性检查插件
  来源:安阳市某某运输服务培训中心  更新时间:2024-05-09 00:22:21

最佳实践之接口规范性检查插件

前言

本文是最佳之接针对微服务架构下各服务之间交互接口契约定义与声明规范性在实际生产中的一个最佳实践。在经历无数次组件间和项目间的实践扯皮、修改再修改的口规过程中 ,总结的范性一套契约规范 ,并辅之以有效的检查自动化检测手段即本文提及的检查插件,通过上述规范和工具统一各项目服务间的插件接口的理解,降低出错返工概率,最佳之接最终提高开发效率,实践节省开发成本 。口规

一、范性Maven插件是检查什么

Maven本质上是一个插件框架 ,它的插件核心并不执行任何具体的构建任务,所有这些任务都交给插件来完成 ,最佳之接例如编译源代码是实践由maven- compiler-plugin完成的  。进一步说,口规每个任务对应了一个插件目标(goal),每个插件会有一个或者多个目标,例如maven- compiler-plugin的compile目标用来编译位于src/main/java/目录下的主源码 ,testCompile目标用来编译位于src/test/java/目录下的测试源码。

用户可以通过两种方式调用Maven插件目标。第一种方式是将插件目标与生命周期阶段(lifecycle phase)绑定 ,这样用户在命令行只是输入生命周期阶段而已 ,例如Maven默认将maven-compiler-plugin的compile目标与 compile生命周期阶段绑定 ,因此命令mvn compile实际上是先定位到compile这一生命周期阶段 ,然后再根据绑定关系调用maven-compiler-plugin的compile目标。第二种方式是直接在命令行指定要执行的插件目标 ,例如mvn archetype:generate 就表示调用maven-archetype-plugin的generate目标,这种带冒号的调用方式与生命周期无关 。
目前已经积累了大量有用的插件 ,参考:

  1. http://maven.apache.org/plugins/index.html
  2. http://mojo.codehaus.org/plugins.html

二 、接口定义(Yaml)

为避免从代码生成yaml再到yaml编出的代码 ,两者间产生不一致和歧义,如下某Rest接口返回的对象模型:

@Gettern@Settern@NoArgsConstructorn@ToString(callSuper = true)n@EqualsAndHashCode(callSuper = true)n@ApiModel(description = "com.xxxx.node.IpL2Node")npublic class IpL2Node extends IpNode { nn @JsonProperty("admin-ip")n @ApiModelProperty(name = "admin-ip", value = "管理地址")n private String adminIp;

团队约定在编写接口代码里  ,要求遵守下列约定  :

模型实体类上必须使用注解@ApiModel  ,并且description设置为该实体类的全路径名

模型实体类上尽量使用注解@JsonIgnoreProperties(ignoreUnknown = true)

模型实体类上必须使用注解@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class),同时不允许使用@JsonProperty特殊指定实体类中某字段的序列化字符串

模型实体类中每个字段必须使用注解@ApiModelProperty,并且保证name定义与序列化出的JSON字符串完全一致

Rest接口不允许返回Set集合

接口代码中不允许使用注解@JsonInclud(JsonInclud.Include.NON_NULL)

Rest接口中 ,不要使用JAVA层面的方法重载 ,会导致生成yaml编译代码后 ,相同方法后会自动加_0参数用于区分,影响接口准确性

通过上述约定 ,基本上保证了代码输出的yaml与再次编出的代码含义一致,但不能寄希望于开发人员主观遵守 ,所以需要一种自动化的手段 ,能够在出错或不满足上述约定时,在开发阶段即通知开发人员,因此我们通过开发maven插件方式完成上述功能。

三、插件实现

具体插件的实现原理也十分简单 ,即配置该插件后,在代码的编译阶段,通过扫描classPath下所有类文件,通过注解或包括Json相关过滤出潜在模型类,然后按上述规则进行校验,并给出报错明细信息 。具体代码开发逻辑记录如下 :

该插件的工程目录 :

最佳实践之接口规范性检查插件

POM依赖 :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"n xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">n <modelVersion>4.0.0</modelVersion>n <groupId>com.zte.sdn.oscp.topology</groupId>n <artifactId>oscp-restapi-check</artifactId>n <packaging>maven-plugin</packaging>n <version>1.0-SNAPSHOT</version>n <name>oscp-restapi-check Maven Mojo</name>n <url>http://maven.apache.org</url>n <dependencies>n <dependency>n <groupId>org.apache.maven</groupId>n <artifactId>maven-plugin-api</artifactId>n <version>2.0</version>n </dependency>n <dependency>n <groupId>junit</groupId>n <artifactId>junit</artifactId>n <version>3.8.1</version>n </dependency>n <dependency>n <groupId>org.apache.maven</groupId>n <artifactId>maven-core</artifactId>n <version>3.3.9</version>n </dependency>n <dependency>n <groupId>org.apache.maven.plugin-tools</groupId>n <artifactId>maven-plugin-annotations</artifactId>n <version>3.4</version>n </dependency>n <dependency>n <groupId>io.swagger</groupId>n <artifactId>swagger-annotations</artifactId>n <version>1.5.3</version>n </dependency>n <dependency>n <groupId>com.fasterxml.jackson.core</groupId>n <artifactId>jackson-core</artifactId>n <version>2.9.6</version>n </dependency>n <dependency>n <groupId>com.fasterxml.jackson.core</groupId>n <artifactId>jackson-databind</artifactId>n <version>2.9.6</version>n </dependency>n </dependencies>n <build>n <plugins>n <plugin>n <groupId>org.apache.maven.plugins</groupId>n <artifactId>maven-compiler-plugin</artifactId>n <version>3.8.0</version>n <configuration>n <source>1.8</source>n <target>1.8</target>n <encoding>UTF-8</encoding>n </configuration>n </plugin>n <plugin>n <groupId>org.apache.maven.plugins</groupId>n <artifactId>maven-plugin-plugin</artifactId>n <version>3.5.2</version>n </plugin>n </plugins>n </build>n</project>

入口类RestFormatCheckMojo.java ,设置该插件的运行在PROCESS_CLASSES生命周期 ,保证编译完源码后能够进行模型的较验。

import static org.apache.maven.plugins.annotations.LifecyclePhase.PROCESS_CLASSES;nnimport java.io.File;nimport java.lang.reflect.Field;nimport java.lang.reflect.Modifier;nimport java.util.ArrayList;nimport java.util.Arrays;nimport java.util.List;nimport java.util.Map;nimport java.util.Set;nimport java.util.stream.Collectors;nnimport io.swagger.annotations.ApiModel;nimport io.swagger.annotations.ApiModelProperty;nimport junit.framework.Assert;nimport org.apache.maven.plugin.AbstractMojo;nimport org.apache.maven.plugin.MojoExecutionException;nimport org.apache.maven.plugins.annotations.Mojo;nimport org.apache.maven.plugins.annotations.Parameter;nimport org.apache.maven.plugins.annotations.ResolutionScope;nimport org.apache.maven.project.MavenProject;nnimport com.fasterxml.jackson.annotation.JsonIgnore;nimport com.fasterxml.jackson.annotation.JsonProperty;nimport com.fasterxml.jackson.databind.ObjectMapper;nimport com.fasterxml.jackson.databind.annotation.JsonNaming;nimport com.google.common.collect.Lists;nimport com.zte.sdn.oscp.topology.annotation.IgnoreFormatCheck;nimport com.zte.sdn.oscp.topology.util.JavaClassScanner;nn@Mojo(name = "yang2bean", defaultPhase = PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.COMPILE)npublic class RestFormatCheckMojo extends AbstractMojo { nn private List<String> notCheckPkg = Lists.newArrayList();nn @Parameter(property = "skipCheckPkg")n private String skipCheckPkg;nn @Parameter(property = "outDirectory", defaultValue = "${ project.build.outputDirectory}")n private File outputDirectory;nn @Parameter(defaultValue = "${ project.basedir}")n private File baseDir;nnn @Parameter(defaultValue = "${ project}", readonly = true)n private MavenProject project;nn @Parameter(defaultValue = "${ project.build.sourceDirectory}")n private File srcDir;nn @Parameter(defaultValue = "${ project.build.outputDirectory}")n private File outDir;n @Parameter(defaultValue = "${ project.compileClasspathElements}", readonly = true, required = true)n private List<String> compilePath;nn @Overriden public void execute() throws MojoExecutionException { n if (skipCheckPkg != null && !"".equals(skipCheckPkg.trim())) { n String[] split = skipCheckPkg.split(",");n notCheckPkg = Arrays.asList(split);n }n JavaClassScanner scanner = new JavaClassScanner(project);n scanner.scan(outDir);n List<Class> clsList = scanner.getClsList();n List<Class> models = filterByCondition(clsList);n try { n check(models);n } catch (Exception e) { n e.printStackTrace();n }n }nn private void check(List<Class> models) throws Exception { n ObjectMapper objectMapper = new ObjectMapper();n for (Class<?> aClass : models) { n Assert.assertTrue("[" + aClass.getName() + "] must annotated with AipModel", aClass.isAnnotationPresent(ApiModel.class));n Assert.assertEquals(aClass.getName().replace("$", "."), ((ApiModel) aClass.getAnnotation(ApiModel.class)).description());n String serialize = objectMapper.writeValueAsString(aClass.newInstance());n if ("[]".equals(serialize)) { n continue;n }n Map jsonPropertyMap = objectMapper.readValue(serialize, new MapTypeReference());n jsonPropertyMap.remove("class");nn List<String> apiModelProperties = new ArrayList<>();n List<Field> allFields = getAllFields(aClass);n for (Field field : allFields) { n if (field.isAnnotationPresent(JsonIgnore.class)) { n continue;n }n if (Modifier.isStatic(field.getModifiers())) { n continue;n }n //严格要求,必须定义ApiModelPropertyn Assert.assertTrue("[" + field.getName() + "] in [" + aClass.getName() + "] must annotated with ApiModelProperty", field.isAnnotationPresent(ApiModelProperty.class));n String name = field.getAnnotation(ApiModelProperty.class).name();n if (name == null || "".equals(name)) { n apiModelProperties.add(field.getName());n } else { n apiModelProperties.add(name);n }n }n Assert.assertEquals(jsonPropertyMap.size(), apiModelProperties.size());n Set set = jsonPropertyMap.keySet();n Assert.assertTrue("[" + aClass.getName() + "] JSON and ApiModelPorperty not same", set.containsAll(apiModelProperties));n Assert.assertTrue("[" + aClass.getName() + "] JSON and ApiModelPorperty not same", apiModelProperties.containsAll(set));n }n }nn private List<Class> filterByCondition(List<Class> clsList) { n List<Class> result = clsList.stream().filter(p -> { n if (p.isInterface()) { n return false;n }n for (String pkg : notCheckPkg) { n if (p.getName().startsWith(pkg)) { n return false;n }n }n if (p.isAnnotationPresent(IgnoreFormatCheck.class)) { n return false;n }n if (p.isAnnotationPresent(ApiModel.class)) { n return true;n }n if (p.isAnnotationPresent(JsonNaming.class)) { n return true;n }n for (Field declaredField : p.getDeclaredFields()) { n if (declaredField.isAnnotationPresent(JsonProperty.class)) { n return true;n }n }n return false;n }).collect(Collectors.toList());n return result;n }nnn private List<Field> getAllFields(Class cls) { n List<Field> fields = new ArrayList<>();n Class current = cls;n while (current != Object.class) { n fields.addAll(Arrays.asList(current.getDeclaredFields()));n current = current.getSuperclass();n }n return fields;n }nnn}

在插件开发中 ,无法直接使用Class.forName进行类加载 ,需要通过动态读取目标项目所依赖的classpath并根据这些classpath生成相应的url数组 ,以这个url数组作为参数得到的类加载器可以实现在maven插件中动态加载目标项目类及第三方引用包的目的,具体可以参考:https://www.cnblogs.com/coder-chi/p/11305498.html

Please note that the plugin classloader does neither contain the dependencies of the current project nor its build output. Instead, plugins can query the project’s compile, runtime and test class path from the MavenProject in combination with the mojo annotation requiresDependencyResolution from the Mojo API Specification. For instance, flagging a mojo with @requiresDependencyResolution runtime enables it to query the runtime class path of the current project from which it could create further classloaders.

四 、运行效果

如图所示:相关不满足规范的定义已经正确拦截

最佳实践之接口规范性检查插件最佳实践之接口规范性检查插件最佳实践之接口规范性检查插件

友情链接现在比较火的苹果手机游戏2022十大游戏排名原神:一个可莉三火队的科普性攻略现在比较火的苹果手机游戏2022十大游戏排名DNF阿修罗95刷图加点 95级阿修罗技能加点推荐王者荣耀原初进化机制介绍魔兽7.0武僧神器少昊之杖任务同人:回归迷踪岛变成猫的我太受欢迎了金铲铲之战S10七法超级璐璐怎么玩 七法超级璐璐阵容玩法攻略魔兽WLK:前夕骑术打折,符文布价格会上涨?“地精”急着出货了大脚插件的副本掉落查询功能怎么开启?魔兽世界副本掉落插件阴阳师正式服1月20日更新维护公告 新SSR鬼童丸上线恶魔城X历代记全救人攻略自动秒发正版 仙剑5前传DCL剧情激活 梦华幻斗DLC 激活码序列号光遇 国际服英语常用单词词汇或翻译阴阳师现世召唤阵大全,阴阳师现世召唤阵怎么召唤SSR新SSR鬼切召唤图案王者荣耀变声器在哪里,幻音变声器怎么用到王者荣耀psp时空之轮2攻略格式,psp王国之心梦中降生 AQUA 2段跳技能DNF剑豪TB刷图加点推荐 剑豪国服521改版技能改动一览赛尔号入坑指南(3)超详细教你刷巅峰周任务*内含对抗「假自爆」阵容教程*魔兽WLK:前夕骑术打折,符文布价格会上涨?“地精”急着出货了风暴航路的地图编号_DNF:提前来看100级版本主要地图有多神奇传奇2023新春手游sf服下载元气骑士前传风暴刺客怎么加点英雄联盟(LOL)哪个英雄操作最难 盘点LOL中最难操作的英雄江湖风云录采茶怎么做 临安隐藏支线采茶任务解析博人传 火影忍者新时代 第18集简介英雄联盟新排位,lol新排位等级怎么区分两件17段无级别 群雄逐鹿亚军装备展示《龙之谷手游》剑皇PK技能加点推荐 剑皇玩法技能属性详解天技宠技能升级所需帮贡与金钱详表-------常备常用英雄联盟人在塔在小游戏兑换码大全 最新礼包CDK兑换码分享[多图]成都祛疤痕医院排行榜|华西医院、省人民医院、成医附二院上榜!《星之海洋:第二个故事 R》v1.0三十九项修改器风灵月影版dnf剑魂光恶魔打字攻略 剑魂光恶魔怎么打字Dnf:韩服更新:剑神、剑皇、念帝、魔枪士等职业伤害调整管理出效益——“打怪升级”的王者荣耀之路lol卡莎皮肤手感排名虐杀原形1技能作弊码是什么魔兽世界5.0装备掉落等级等级简要介绍阴阳师地域鬼王如何快速排名 阴阳师高分鬼王攻略推荐
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

0.2637

Copyright © 2024 Powered by 安阳市某某运输服务培训中心   sitemap