基于Spring IOC的依赖查找实现类restful 接口

不知道大家都没有在web开发过程中有遇到过这种情况,就是前端需要一个接口,我们都要写对应的Controller接口配置对应的url,那有没有一种方式我们只需要写一个主的Controller,是否可以通过Spring IOC的依赖查找来实现接口的调用呢?

Spring IOC

大家可能都知道Spring的两大核心 一个是IOC(控制反转),一个AOP(面向切面)
IOC是一种设计思想,在java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部进行控制,
(1)基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。
(2)实现IOC的技术手段:DI(依赖注入)DL(依赖查找)Spring中的核心机制就是DI(依赖注入)。通俗来说就是ServiceImpl类中,有Dao对象,那就是ServiceImpl依赖了Dao

依赖注入(Depedency Injection)

意思是自身对象中的内置对象是通过注入的方式进行创建。依赖注入有两种实现方式:Setter方式(传值方式)和构造器方式(引用方式)。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造子传递给需要的对象。相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。

依赖查找(Dependency Lookup)

依赖查找一共有两种类型:依赖拖拽(DP)上下文依赖查找(CDL)
我们实现用注解的方式来编写类restful接口用到的就是上下文依赖查找方式:
上下文依赖查找的实现方式:
(1)使用beanFactory.getBean("")来获取。
(2)使用applicationContext.getBeansWithAnnotation(class)

自定义实现Restful接口

实现类Restful接口我们可以从这几步开始:

  • 自定义注解
  • 编写公共接口
  • 使用上下文查找来实现接口查找

自定义注解

自定义两个注解一个作用在类上,一个作用在方法上:

  • ApiRestService
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiRestService {
}
  • ApiRestMethod
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiRestMethod {

    String apiCode() default "";
}

编写web统一调用接口

@RestController
@RequestMapping("/rest/service")
@Slf4j
public class RestApiServiceController {
    /**
     * 服务入口
     * CrossOrigin实现跨域访问
     * @param apiQuaryParam
     * @return AjaxResult
     *
     */
    @CrossOrigin(origins = "*", maxAge = 3600)
    @PostMapping(value = "/api")
    @ResponseBody
    public AjaxResult api(@RequestBody(required = false) RestApiQuaryParam apiQuaryParam) {
        return ApiUtil.getApi(apiQuaryParam);
    }

    //请求参数返回示例
   /**
    * {
    *     "data": {
    *         "storeNo": "050166",
    *         "userName": "admin",
    *         "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdG9yZU5vIjoiMDUwMTY2IiwiZXhwIjoxNjM5NDY5Mzg3LCJ1dWlkIjoiMTIzMTI0MTI0MTMiLCJ1c2VybmFtZSI6ImFkbWluIn0.qbkEcfcq1mZdtH6Cu4UBNaddAVP-1hOHcKJiuqeQVjA"
    *     },
    *     "message": "操作成功",
    *     "status": 200
    * }
    * */
}

注意:前端传递需要传递给后端服务接口apiCode

Spring IOC过滤查找到对应后端服务

编写Spring IOC工具类

@Slf4j
@Component
public class SpringIocHelper implements ApplicationContextAware, InitializingBean {

    /**
     * 实现ApplicationContextAware接口,通过setApplicationContext回调注入applicationContext
     */
    private static ApplicationContext applicationContext;

    /**
     * api method缓存
     * 实现InitializingBean,通过afterPropertiesSet方法初始化缓存
     */
    private static Map<String,Map<Object, Method>> apiMap = new HashMap(256);


    public static AjaxResult invoke(String apiCode, RestApiQuaryParam apiQuaryParam) {
        //获取服务接口
        Map<Object, Method> methodMap = match(apiCode);
        if (CollectionUtils.isEmpty(methodMap)) {
            return AjaxResult.error(1010, String.format("请检查apiCode【%s】是否正确", apiCode));
        }
        for (Object service : methodMap.keySet()) {
            Method method = methodMap.get(service);
            try {
                Class<?>[] paramClass = method.getParameterTypes();
                if (paramClass.length > 0) {
                    if (paramClass[0].getName().equals(UhiApiQuaryParam.class.getName())) {
                        return AjaxResult.success(method.invoke(service, apiQuaryParam));
                    }
                }
                return AjaxResult.success(method.invoke(service));
            } catch (RestApiBaseException | InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
                log.error("service paramJson:{}", apiQuaryParam != null ? JSONObject.toJSONString(apiQuaryParam) : "", e);
                if (e instanceof InvocationTargetException) {
                    return AjaxResult.error(1004, ((InvocationTargetException) e).getTargetException().getMessage());
                }
            }
        }
        return AjaxResult.error(1000, "服务异常,请联系服务人员");
    }

    /**
     * 寻址查到apiCOde对应的类以及方法名称
     *
     * @param apiCode 接口编码
     * @return Map
     */
    private static Map<Object, Method> match(String apiCode) {
        return apiMap.get(apiCode);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringIocHelper.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        //会触发getBean 从而保证所有ApiService对应的bean比SpringHelper先初始化完成
        Map<String, Object> map = applicationContext.getBeansWithAnnotation(ApiService.class);
        for (Object service : map.values()) {
            //获取本类中的所有方法
            Method[] methods = service.getClass().getDeclaredMethods();

            for (Method method : methods) {
                //获取该方法的上所有的注解
                Annotation[] annotations = method.getDeclaredAnnotations();
                for (Annotation annotation : annotations) {
                    //判断是否存在该注解
                    if (!annotation.toString().contains(ApiRestMethod.class.getSimpleName())) {
                        continue;
                    }
                    ApiRestMethod apiMethod = ApiRestMethod.class.cast(annotation);
                    //判断是否存在数据
                    String apiCode = apiMethod.apiCode();
                    if (apiMap.containsKey(apiCode)) {
                        throw new RuntimeException("find too many match method [" + apiCode + "]");
                    } else {
                        Map<Object, Method> methodMap = new HashMap<>();
                        methodMap.put(service, method);
                        apiMap.put(apiCode, methodMap);
                    }
                }
            }
        }
    }
}

注意:需在启动类后加入获取上下文:

 //复制上下文
 SpringHelper.setApplicationContext(applicationContext);

服务端类注解使用

如下图所示:

@Service
@ApiRestService
public class LoginApiService {

    /**
     * api登录调用接口获取token
     *
     * @param apiQuaryParam 请求参数
     */
    @ApiRestMethod(apiCode = "REST_LOGIN")
    public Map<String, Object> login(RestApiQuaryParam apiQuaryParam) throws RestApiBaseException {

        Map<String, Object> objectMap = new HashMap<>();
        try {
            objectMap.put("token", TokenUtils.token(apiQuaryParam.getUserName(), apiQuaryParam.getUuid(), apiQuaryParam.getStoreNo()));
            objectMap.put("userName", apiQuaryParam.getUserName());
            objectMap.put("storeNo", apiQuaryParam.getStoreNo());
            return objectMap;
        } catch (Exception e) {
            throw new RestApiBaseException(e.getMessage());
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容