相关文章推荐
逆袭的蘑菇  ·  【java框架】SpringMVC(4) ...·  1 周前    · 
淡定的地瓜  ·  iOS 通信通知 ...·  1 周前    · 
爱听歌的猴子  ·  Mysql ...·  14 小时前    · 
逃跑的高山  ·  ASP.NET - 使用 ASP.NET ...·  9 月前    · 
坚韧的包子  ·  qt - QTextBrowser ...·  10 月前    · 
紧张的大蒜  ·  pyqt qthread stop ...·  1 年前    · 


如何把项目打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接口,效果如下:

jar包服务 java jar包如何提供接口服务_jar包

这里扩展一下,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接口暴露给第三方应用的功能了。


gitlab修改受保护权限分支

前段时间完成了一个核心代码保护的功能,目标是在关键代码被修改及时同步给其他人,避免没经过 review 就上线导致问题,提示的效果图如下:在实现的过程中,用到一些平时使用不多的 Git 技巧,这篇文章来总结一下。如何获取当前提交用户信息这个比较简单,通过 git config user.name 即可:04318deMacBook-Pro % git config user.name zhangs

info查看latest_fork_usec

基于Linux的操作系统是一种自由和开放源代码的类UNIX操作系统。Linux的几大特点决定了它的不可代替和无法超越性:(1)免费的/开源的;(2)支持多线程/多用户; (3)安全性好; (4)对内存和文件管理有自己优越的方法。Linux常用的命令###路径的表示方法###cd :change directory的缩写,用于改变当前路径;语法:cd [相对路径或绝对路径]绝对路径:c