SpringMVC接口的动态创建和删除

一.业务背景

        医院有很多的业务系统,像LIS、PACS、HIS、面向患者的小程序等等,这些系统都不是孤立存在的,系统间都有很多接口调用,数据交互。目前线上有一套集成引擎,不过这套集成引擎每次对接都要开人人员定制化开发,然后从中做转发,为了减少成本,对集成引擎进行了升级,使用配置化方式完成对接,这样就不需要开发人员接入,实施人员便可以完成这项工作,但是新集成引擎面临一个问题,就是如何替换线上已有的接口,同时还不需要调用方和被调用改造,实施人员又能通过配置实现替换,因此想到了使用Groovy动态加载代码实现创建接口。

二.实现思路

 1.创建脚本

        界面提供输入框,用于录入controller中的方法,该方法的url就是要替换的线上接口的url,参数可以是json对象或者json数组

 2.加载脚本

        录入脚本后,后台提供加载功能,将该脚本方法所表示的类动态创建并添加到Spring容器中

三.具体实现

1.引入Groovy

 脚本加载使用Groovy实现

pom.xml引入Groovy

<dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy</artifactId>
      <version>${groovy.version}</version>
</dependency>

2.动态创建对象

public void load(String javaText) {
        Class<?> javaClazz = groovyLoader.parseClass(javaText);

        //使用类名作为bean名称
        String beanName = javaClazz.getSimpleName();
        //创建bean信息.
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(javaClazz);

        //动态注册bean.
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
        //  Object bean = defaultListableBeanFactory.getBean(beanName);
        try {
            /**
             *启动注册Controller,使用反射调用原生内部方法,虽然spring提供了开放接口,但是接口参数比较复杂,直接反射省事
             */
            Method method=RequestMappingHandlerMapping.class.getSuperclass().getSuperclass().getDeclaredMethod("detectHandlerMethods",Object.class);

            //将private改为可使用
            method.setAccessible(true);
            method.invoke(requestMappingHandlerMapping,beanName);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        groovyLoader.clearCache();
    }

说明:

1.需要将代码段以类的方式注册到Spring中,beanDefinition是Spring生命周期的一环,必须有

2.需要了解SpringMVC注册接口的过程,这里使用了反射的方法来注册,其实SpringMVC有提供api来注册接口,但是入参构造比较麻烦,果断放弃了,还是反射来的方便

 3.文中只有方法有界面录入,这个方法所在的类在本业务中是后台自动生成的,这个类会在load方法入参时拼接进去

3.从Spring容器中移除接口

       动态创建的接口难免有需要修改的时候,修改还是比较复杂的,需要将原来的类和方法全部卸载掉,再重新加载。不卸载不行吗?不卸载有可能造成再次加载报错。

 当这个方法url和请求方式(post之类的)没变但是方法名变了这里就会报错,提示该接口已经mapped

所以为了安全,每次修改需要将原方法卸载,整个类从Spring容器中移除,再重新加载。

卸载代码:

public void unload(String javaText){
        Class<?> javaClazz = groovyLoader.parseClass(javaText);
        String beanName = javaClazz.getSimpleName();
        if(defaultListableBeanFactory.containsBean(beanName)){
            defaultListableBeanFactory.removeBeanDefinition(beanName);
            defaultListableBeanFactory.destroySingleton(beanName);
        }
        /**
         * 1.类上的注解获取url
         */
        RequestMapping requestMapping = javaClazz.getAnnotation(RequestMapping.class);
        String prefix = "";
        if(requestMapping != null){
            String[] value = requestMapping.value();
            prefix = value[0];
        }
        List<RequestMappingInfo> requestMappingInfoList = new ArrayList<>();
        //查询所有方法
        Method[] declaredMethods = javaClazz.getDeclaredMethods();
        /**
         * 2.方法上的注解获取url
         * 最终拼接到一快,防止出现//,最后统一替换一次
         */
        for(Method method:declaredMethods){
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            if(postMapping != null){
                String[] value = postMapping.value();
                String url = ((prefix+value[0]).replaceAll("//","/"));
                RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(url).methods(RequestMethod.POST).build();
                requestMappingInfoList.add(requestMappingInfo);
                continue;
            }
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            if(getMapping != null){
                String[] value = getMapping.value();
                String url = ((prefix+value[0]).replaceAll("//","/"));
                RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(url).methods(RequestMethod.GET).build();
                requestMappingInfoList.add(requestMappingInfo);
                continue;
            }
            PutMapping putMapping = method.getAnnotation(PutMapping.class);
            if(putMapping != null){
                String[] value = putMapping.value();
                String url = ((prefix+value[0]).replaceAll("//","/"));
                RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(url).methods(RequestMethod.PUT).build();
                requestMappingInfoList.add(requestMappingInfo);
                continue;
            }
            DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
            if(deleteMapping != null){
                String[] value = deleteMapping.value();
                String url = ((prefix+value[0]).replaceAll("//","/"));
                RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(url).methods(RequestMethod.DELETE).build();
                requestMappingInfoList.add(requestMappingInfo);
            }
        }
        for(RequestMappingInfo requestMappingInfo:requestMappingInfoList){
            requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
        }
        groovyLoader.clearCache();
    }

说明:

1.需要先移除Spring容器的beanDefinition和实例对象

2.构造RequestMappingInfo,一个RequestMappingInfo代表一个接口描述

3.使用SpringMVC提供的api来移除注册的接口(这个api入参比较好构造,就不反射了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hamilton_Huan

原创不易,结合业务原创更不易

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值