如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】

2023-11-13

 

如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】

下面这个例子,是我在开源项目CR949中使用到的部分代码,作为讲解,发布到这里。

jar包中的controller,如何对外暴露接口。
这样一个场景:比如,我去gitee上面,下载一个项目,打成jar包。现在呢,我想把这个jar中的一个接口暴露出来,这样我本地项目启动以后,
我就可以直接访问这个接口了。

例如我们的项目启动时,可以从控制台日志看到swagger2的/v2/api-docs接口注入到HandlerMapping的过程。

 

2020-12-02 11:19:04,106 [main] INFO  springfox.documentation.spring.web.PropertySourcedRequestMappingHandlerMapping - [ -  - ] - Mapped URL path [/v2/api-docs] onto method [public org.springframework.http.ResponseEntity<springfox.documentation.spring.web.json.Json> springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(java.lang.String,javax.servlet.http.HttpServletRequest)]

 

如何实现这个功能呢?

目前来看,思路有2个:
1,直接把Controller类配置到spring.factories
2,实现spring webmvc的HandlerMapping接口

 

为了方便讲解,我们这里写一个demo控制器,代码如下:

在这里,有3个点需要特别关注。
第一点:@RestController注解,如何判断一个Controller是否是HandlerMapping呢?在具体实现时,其实是通过@Controller + @RequestMapping
来判断的。
第二点:@PropertySourcedMapping(value = "${cr949.api.docs.interface.path}", propertyKey = "cr949.api.docs.interface.path")注解
这里的配置cr949.api.docs.interface.path是一个自定义配置,如果我们配置了path,就会使用我们配置的这个path。如果我们没有配置,默认会直接读取
@RequestMapping注解来获取path。
第三点:@ResponseBody注解

 

package com.cr949.auto.docs.controller;

import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import springfox.documentation.spring.web.PropertySourcedMapping;

/**
 * @author cr949
 * @Description 使用spring HandlerMapping,对外暴露接口
 */
@Slf4j
@RestController
@RequestMapping(value = "/cr949")
@ApiIgnore
public class Cr949BootstrapDemoController {

    @RequestMapping(value = "/demo", method = RequestMethod.GET)
    @ApiOperation(value = "使用spring HandlerMapping,对外暴露接口", notes = "使用spring HandlerMapping,对外暴露接口")
    @PropertySourcedMapping(value = "${cr949.api.docs.interface.path}", propertyKey = "cr949.api.docs.interface.path")
    @ResponseBody
    public String swagger2ApiDocsDemo() {

        log.info("使用spring HandlerMapping,对外暴露接口");
        return "使用spring HandlerMapping,对外暴露接口";
    }
}

 

OK,现在我们要把/cr949/demo接口暴露给第三方应用,如何实现呢?

如果按照第一种思路来实现的话,简单粗暴,直接把Cr949BootstrapDemoController配置到jar包中的spring.factories中即可,
作为自动配置类,会被本地应用正常解析。

接下来我们重点说一下第二种思路,实现实现spring webmvc的HandlerMapping接口。

第一步:实现HandlerMapper接口

实现类是Cr949RequestMappingHandlerMapping,这个类本质上就是HandlerMapping的实现。
Cr949RequestMappingHandlerMapping的核心是2个方法:initHandlerMethods和lookupHandlerMethod
initHandlerMethods:把接口路径和HandlerMethod存放到spring boot webmvc的集合。
lookupHandlerMethod:提供根据接口路径查找处理类controller的功能。

具体实现不再赘述,代码中注释的比较清楚了,请大家自行查看。

 

package com.cr949.auto.docs.handler;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UriTemplate;
import springfox.documentation.spring.web.PropertySourcedMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author cr949
 * @description 创建cr949 handler mapping
 */
public class Cr949RequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    private final Map<String, HandlerMethod> handlerMethods = new LinkedHashMap();
    private final Environment environment;
    private final Object handler;

    /**
     * 初始化Cr949RequestMappingHandlerMapping
     * @param environment spring boot上下文环境参数
     * @param handler 控制器类 需要生成HandlerMapping的controller类的对象
     */
    public Cr949RequestMappingHandlerMapping(Environment environment, Object handler) {
        this.environment = environment;
        this.handler = handler;
    }

    /**
     * 初始化HandlerMapping,把cr949的接口放到handlerMethods集合
     */
    @Override
    protected void initHandlerMethods() {
        this.logger.debug("initialising cr949 handler methods");
        this.setOrder(-2147482647);
        Class<?> clazz = this.handler.getClass();
        if (this.isHandler(clazz)) {
            Method[] var2 = clazz.getMethods();
            int var3 = var2.length;

            for (int var4 = 0; var4 < var3; ++var4) {
                Method method = var2[var4];
                PropertySourcedMapping mapper = AnnotationUtils.getAnnotation(method, PropertySourcedMapping.class);
                if (mapper != null) {
                    RequestMappingInfo mapping = this.getMappingForMethod(method, clazz);
                    HandlerMethod handlerMethod = this.createHandlerMethod(this.handler, method);
                    String mappingPath = this.mappingPath(mapper);
                    if (mappingPath != null) {
                        this.logger.info(String.format("Mapped URL path [%s] onto method [%s]", mappingPath, handlerMethod.toString()));
                        this.handlerMethods.put(mappingPath, handlerMethod);
                    } else {
                        Iterator var10 = mapping.getPatternsCondition().getPatterns().iterator();

                        while (var10.hasNext()) {
                            String path = (String) var10.next();
                            this.logger.info(String.format("Mapped URL path [%s] onto method [%s]", path, handlerMethod.toString()));
                            this.handlerMethods.put(path, handlerMethod);
                        }
                    }
                }
            }
        }

    }

    /**
     * 过滤@RestController + @RequestMapping的所有java文件,得到目标controller
     * @param beanType
     * @return
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return AnnotationUtils.findAnnotation(beanType, RestController.class) != null || AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null;
    }

    private String mappingPath(PropertySourcedMapping mapper) {
        final String key = mapper.propertyKey();
        final String target = mapper.value();
        return Optional.fromNullable(this.environment.getProperty(key)).transform(new Function<String, String>() {
            public String apply(String input) {
                return target.replace(String.format("${%s}", key), input);
            }
        }).orNull();
    }

    /**
     * 提供获取HandlerMethod的能力
     * 根据uri和request获取HandlerMethod
     * 注意:此方法一定要重写,不然request请求过来时,是找不到对应的Controller接口的
     * @param urlPath
     * @param request
     * @return
     */
    @Override
    protected HandlerMethod lookupHandlerMethod(String urlPath, HttpServletRequest request) {

        HandlerMethod handlerMethod = this.handlerMethods.get(urlPath);
        if (handlerMethod != null) {
            this.logger.warn("cr949 request请求的接口不存在:" + urlPath);
            return handlerMethod;
        } else {
            Iterator var4 = this.handlerMethods.keySet().iterator();

            String path;
            UriTemplate template;
            do {
                if (!var4.hasNext()) {
                    return null;
                }

                path = (String)var4.next();
                template = new UriTemplate(path);
            } while(!template.matches(urlPath));

            request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, template.match(urlPath));
            return this.handlerMethods.get(path);
        }
    }

}

 

第二步:提供配置类Cr949ApiDocsConfiguration

装配Cr949RequestMappingHandlerMapping到IOC容器。
这里很好理解,因为是在jar包中对外部第三方应用提供服务,需要先把我们的service注入到本地项目的Spring IOC容器中。

 

package com.cr949.auto.docs.config;

import com.cr949.auto.docs.controller.Cr949BootstrapDemoController;
import com.cr949.auto.docs.handler.Cr949RequestMappingHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.HandlerMapping;

/**
 * @author cr949
 * @description 配置类 把cr949的HandlerMapping注入到spring ioc容器
 * @date 2020-12-02
 */
@Configuration
@ConditionalOnWebApplication
public class Cr949ApiDocsConfiguration {

    public Cr949ApiDocsConfiguration() {
    }

    /**
     * 为Cr949BootstrapDemoController生成HandlerMapping
     * @param environment 通过@ConditionalOnWebApplication注解获取environment对象
     * @return
     */
    @Bean
    public HandlerMapping cr949BootstrapDemoControllerMapping(Environment environment) {
        return new Cr949RequestMappingHandlerMapping(environment, new Cr949BootstrapDemoController());
    }

}

 

第三步:提供SPI给第三方应用


spring.factories中增加以下配置,关于spring.factories的实现原理,可以参考我的后续博客。
解析spring.factories的类是SpringFactoriesLoader.class,这里不再赘述。

 

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cr949.auto.docs.config.Cr949ApiDocsConfiguration

来看一下效果图,这里我是把自己的开源项目以jar包的形式引入了本地应用,启动本地应用后,访问/cr949/demo接口,效果如下:

 

 

这里扩展一下,Spring的spring.factories开放了4种接口的自定义实现。
org.springframework.context.ApplicationContextInitializer
org.springframework.context.ApplicationListener
org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer

OK,简单三步,就可以实现把/cr949/demo接口暴露给第三方应用的功能了。

目前这个项目已经贡献给了中国的开源社区,gitee,大家直接搜索CR949,就可以找到我的开源项目。
欢迎大家关注交流,一起技术进步。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】 的相关文章

  • 社区团购的运营模式是什么?

    社区团购是一种近年来兴起的新型电商模式 它通过社区的力量 以线上线下联动的方式将消费者聚集起来 以优惠的价格和更好的商品为社区居民提供服务 这种模式能够更好地满足社区居民的需求 并且可以有效地提高社区居民的生活质量 运营模式主要分为以下几个
  • 不懂23种设计模式?别灰心,这份核心笔记来帮你,你想知道的都在这里!

    设计模式是软件工程中各种常见问题的经典解决方案 设计模式不只是代码 而是组织代码的方式 假设一行行的代码是砖 设计模式就是蓝图 什么是设计模式 设计模式是解决问题的一种思想 和语言无关 在面向对象软件设计的工程中 针对特定的问题简洁优雅的一

随机推荐

  • Java实现五子棋小游戏(附思路讲解,全部代码,游戏截图)

    本文章是如何实现一个单机版双人五子棋小游戏 通过Swing技术进行可视操作 个人简介 个人主页 码云不秃头 本人是一名大三学生 马上就要变成考研狗啦 通过一学期对Java学习 经过老师的教学 实现单机版的双人五子棋小游戏 大家互相学习 也同
  • 一个web app有多主题,多环境

    在一个web app应用中 需要有多个运行环境 并且每个运行环境主题也是不一样 本项目解决方案 import http es6 ruanyifeng com docs module import ES6 import 可以动态加载 可以利用
  • Extjs入门

    1 什么是Extjs Ext JS 是一个强大的JavaScript类库 提供了丰富且美观的UI组件 和easyUI类似 但更强大 因而使用了Ext JS 您需要写的代码基本上是JavaScript 不需要写HTML 它主要用于创建前端用户
  • LLM推理部署(一):LLM七种推理服务框架总结

    自从ChatGPT发布以来 国内外的开源大模型如雨后春笋般成长 但是对于很多企业和个人从头训练预训练模型不太现实 即使微调开源大模型也捉襟见肘 那么直接部署这些开源大模型服务于企业业务将会有很大的前景 本文将介绍七中主流的LLM推理和服务开
  • 数字电路和模拟电路-10时序逻辑电路的分析和设计

    前言 学习同步时序逻辑电路的分析 设计 一 同步时序逻辑电路的分析 1 时序逻辑电路的分析步骤 步骤一 逻辑图 同步or异步 计数器or状态机 一条总线同步 多条总线是异步 计数器无输入 状态机有输入 状态机还分摩尔型和米里型 步骤二 驱动
  • zotero配置

    1 下载安装 2 配置坚果云同步 编辑 首选项 同步 输入zotero账户密码进行数据同步 文件同步选择坚果云同步 3 配置茉莉花插件 安装pdftk
  • C++-函数模板特化如何避免重复定义

    本文转自 https www cnblogs com dracohan p 3401660 html 转来收藏以便查阅 感谢原作者 另一篇相关博文 https blog csdn net shixin 0125 article detail
  • 【Tensorflow 2.12 电影推荐系统之排序模型】

    Tensorflow 2 12 电影推荐系统之排序模型 学习笔记 导入相关模块 准备数据 加载数据 数据预处理 获取词汇表 构建模型 定义评分排序模型 定义损失函数以及模型评估指标 定义完整的评分排序模型 训练和评估 创建排序模型实例 缓存
  • 2022年大厂Android高级面试题分享,安卓Apk安装过程

    现在的IT行业竞争压力越来越大 尤其是Android开发行业 而很多Android程序员却每天都在重复CRUD 原地徘徊 今年年初 你就想改变现状 于是在网上刷了大量面试题 强行记下之后 开始参加面试 但是你发现 现在的面试 却越来越难了
  • 2017.03 JAVA 面试题 中高级

    2017年3月份 从北京跳槽来到深圳 各种面试 面试的大部分公司都发了offer 现整理出面试的问答题目 如下 一 基础知识 1 集合类 List和Set比较 各自的子类比较 ArrayList Vector LinkedList Hash
  • angular 跨平台&dom操作&组件嵌套&投影

    angular 跨平台 angular 是跨平台的 不仅仅可以再pc端运行 anulgar 为跨平台做的工作 为了能够支持跨平台 Angular 通过抽象层封装了不同平台的差异 比如定义了抽象类 Renderer2 抽象类 RootRend
  • 小程序base64 图片for循环多个展示不了_微信小程序基础之一

    1 微信小程序在wxss中不能直接引用图片 微信小程序在wxss中使用背景图片会报错 渲染层网络层错误 pages demo demo wxss 中的本地资源图片无法通过 WXSS 获取 可以使用网络图片 或者 base64 或者使用
  • Anaconada 几个系统基本命令

    1 python 命令加入系统路径 找出 anaconada 安装路径 打开系统变量并写入该路径即可在系统内运行 python 命令 2 pip 命令写入系统路径 pip 的写入路途则是如下 方法相同 3 conda 的运行 conda c
  • 028.PowerDesigner16:导入SQL脚本、显示中文注释

    导入SQL脚本 生成物理模型 1 击File gt Reverse Engineer gt Database 2 弹出弹窗对模型进行命名 同时在DBMS下拉选择框中需要选择自己对应的数据库类型 点击确定 3 新的弹窗 选中Using scr
  • 西米支付:如何选择自己需求的接口(传奇游戏支付接口)

    传奇游戏是中国网游无法绕过的一座碑 也是千万初代网游玩家的游戏启蒙 2001年一款游戏横空出世 靠着超爽的打击感 和多人同屏战斗迅速在网游火了起来 它就是传奇 随着 传奇 盛大的成长 兴盛与衰弱 一路走来 已经在14年 游戏的充值模式也由以
  • 达梦8常用性能优化相关SQL

    一 内存性能相关 1 1 查看数据库当前运行内存大小 select select sum n pages page size 1024 1024 from v bufferpool MB as BUFFER SIZE select sum
  • 计算shell脚本执行的时间

    我们在使用shell脚本进行一些批量活动的时候 在有的场景下会需要知道脚本执行用了多长的时间 一谈到这个话题 我们一般的想法就是记录时间再开始阶段 执行完成后再记录时间 然后求时间差 这样是可以的 但是要进行格式的转换 比较麻烦 今天我们使
  • Mysql 问题集锦

    一 Host is not allowed to connect to this MySQL server解决方法 1 在安装Mysql数据库的主机上登录root用户 mysql u root p use mysql select host
  • 巧用闭包拷贝对象

    我们知道对象的赋值实际上是赋值它的应用 并没有产生对象的副本 如 var p1 x 1 y 2 var p2 p1 p2 x alert p1 x 得出的结果是2 改变p2 x的值 p1 x的值随之改变 当然可以重新new一个对象 但是这样
  • 如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】

    如何把项目打jar包 然后暴露接口给第三方应用提供服务 实战讲解 下面这个例子 是我在开源项目CR949中使用到的部分代码 作为讲解 发布到这里 jar包中的controller 如何对外暴露接口 这样一个场景 比如 我去gitee上面 下