如何把项目打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;
                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,就可以找到我的开源项目。
欢迎大家关注交流,一起技术进步。

如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】下面这个例子,是我在开源项目CR949中使用到的部分代码,作为讲解,发布到这里。jar包中的controller,如何对外暴露接口。这样一个场景:比如,我去gitee上面,下载一个项目,打成jar包。现在呢,我想把这个jar中的一个接口暴露出来,这样我本地项目启动以后,我就可以直接访问这个接口了。例如我们的项目启动时,可以从控制台日志看到swagger2的/v2/api-docs接口注入到HandlerMapping的过...
springboot如何暴露接口 1.再对应的service层编写对应的方法2.在web层创建个文件夹对外暴露接口(controller层是对前端暴露接口) 3.再指定的地方 提供对应的接口给别人使用(映射的url是在web层对应的url) Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
Doe 发布 [V1.0.0] 前段时间排查某问题的时候,想要快速知道某些dubbo接口(三无)的响应结果,但不想启动项目(因为这些项目不是你负责的,不会部署而且超级笨重),也不想新建一个dubbo客户端项目(占地方),也不想开telnet客户端连接口(麻烦而且有限制)。所以扣了dubbo的netty模块源码,封装了个收发客户端集成一个工具,可以快速调试dubbo接口。源码地址:https://github.com/VIPJoey/doe 极简模式 普通模式 mmc-dubbo-api 接口项目,主要用于测试。 mmc-dubbo-provider dubbo提供项目,主要用于测试。 mmc-dubbo-doe 主项目,实现dubbo接口调试。 deploy 部署文档 极简模式:通过dubbo提供的telnet协议收发数据。 普通模式:通过封装netty客户端收发数据。 用例模式:通过缓存数据,方便下一次操作,依赖普通模式。 增加依赖:通过调用maven命令,下载jar包和热加载到系统,主要用来分析接口方法参数,主要作用在普通模式。 依赖列表:通过分析pom文件,展示已经加载的jar包springboot 整合 redis,支持spring el 表达式。 springboot 整合 thymeleaf。 springboot 整合 logback。 netty rpc 实现原理。 开发环境 jdk 1.8 maven 3.5.3 dubbo 2.6.1 lombok 1.16.20 idea 2018 windows 7 安装jdk 安装maven,并设置好环境变量,仓库目录。 进入mmc-dubbo-api目录,执行mvn clean install命令,省api的jar包。 进入mmc-dubbo-doe目录,执行mvn clean install 命令,在target目录生成dubbo-doe-1.0.0-RELEASE.jar 在F盘(可以任意盘)创建目录F:\app\doe 把dubbo-doe-1.0.0-RELEASE.jar拷贝到F:\app\doe 把deploy目录中的所有文件拷贝到F:\app\doe 如果您电脑安装了git bash,可以在bash窗口运行 ./deploy.sh start,否则如果没有安装git bash,只能打开cmd切换到F:\app\doe目录,然后执行java -jar dubbo-doe-1.0.0-RELEASE.jar --spring.profiles.active=prd 打开浏览器,访问地址:http://localhost:9876/doe/home/index 引入这个jar 安装到本地仓库 直接指定jar包的位置由于我们写项目的时候,有的时候并不是一个独立项目,而是作为一个第三方类库来提供服务的存在,用来给别的项目引入作为某个功能的封装。打包成jar解决方案主要是从pom.xml来入手,因为是maven项目,所以在pom里面加入build的参数,然后就可以了。 <build> <plugins>
  当我们想要利用SpringBoot封装一套组件并发布给第三方使用时,我们就不得不考虑我们的组件能否被使用者正确引入使用,此处描述的时打包成 jar 包后 Spring 配置类不为扫描、未注册Bean的问题。   此处提供三种解决方案,友好型依次提升。 ...
什么是jar包? JAR(Java ARchive)翻译过来即Java归档文件,我们可以将多个文件合成一个jar文件,这就是归档(常称封装)。jar文件其实就是zip文件,但它与zip文件的区别是,它在被生成的时候会自动创建一个MANIFEST.MF文件,该文件主要描述所在jar包的部署信息。 什么时候可以将自己写的接口、类等封装成jar包?有何作用?(以下为个人见解) 当我们做一个项目的时候,...
公司做的很多项目都是属于客户端—服务器形式,在客户端部分运行了属于面向客户的项目jar包,客户端机器就不想服务器那样,经常会避免重启机器的情况,所有让我们的项目能自启动就是个必须的工作。 1、用.bat脚本,并将脚本文件生成快捷方式,放置在电脑的启动目录下 打开目录快捷键:win+R 输入 shell:startup 2、做成服务(推荐使用这个),电脑重启 服务也会重启 具体方法: 1、.bat脚本文件启动jar包 新建一个文本,编辑内容示例如下,修改文本文件保存格式为.bat,
调用第三方jar包里的接口,需要先将该jar包导入到你的项目中。然后,你需要在代码中引入该jar包中的类或接口。最后,你可以根据该接口的文档或者源代码,使用相应的方法来调用该接口。 以下是一个示例代码,假设你已经将名为example.jar的jar包导入了项目中,并且该jar包中有一个名为ExampleInterface的接口,其中有一个名为exampleMethod的方法: import com.example.ExampleInterface; import com.example.ExampleInterfaceImpl; public class Example { public static void main(String[] args) { ExampleInterface exampleInterface = new ExampleInterfaceImpl(); String result = exampleInterface.exampleMethod("input"); System.out.println(result); 在上述示例代码中,首先我们导入了ExampleInterface和ExampleInterfaceImpl类。然后,在main方法中,我们创建了一个ExampleInterfaceImpl对象,并调用了该对象的exampleMethod方法,传入了一个字符串参数"input"。最后,我们将返回的结果打印到控制台上。 请注意,以上示例仅供参考,具体的调用方式可能会因为不同的jar包而有所不同。因此,在调用第三方jar包接口时,一定要仔细查看其文档或源代码,确保使用正确的方法。