java通过接口创建代理进行http请求

spring 同时被 2 个专栏收录
26 篇文章 0 订阅
10 篇文章 0 订阅

场景

现在想要做这么一个事情,公司的dubbo服务都是内网的,但是提供了一个对外的出口,通过链接就能请求到对应的dubbo服务。(具体怎么做的应该就是个网关,然后将http请求转为dubbo请求,通过泛化调用去进行调用。代码看不到。)现在为了方便测试,我需要将配置的接口,通过http请求去请求对应的链接。

分析

项目的思想其实跟mybatis-spring整合包的思想差不多,都是生成代理去执行接口方法。
https://yidajava.blog.csdn.net/article/details/108120233
项目是个简单的spring项目就行了,然后项目引入项目的api,然后通过配置对应的服务名称,通过spring生成代理,注入spring容器,然后执行方法就是根据对应的域名+接口全路径+方法名去进行请求,参数是json。为了方便项目使用了hutool工具类,直接使用fastjson去进行序列化。

操作

首先创建工厂bean,就是用来返回代理的FactoryBean


import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Proxy;

/**
 * @Title:相对于BeanFactory这个大工厂,这是一个小工厂,专门用于创建某种类型的bean(默认创建的是单例bean)
 * @Description 创建代理对象
 * @Version
 */
public class HttpProxyFactoryBean<T> implements FactoryBean<T> {

    /**
     *【注意】
     * 这里之所以可以进行自动装配,是因为当前的这个HttpProxyFactoryBean是会被注册到Spring中的
     * 只不过它的注册方式 跟一般的不一样(一般会在类上,加一个如@Component、@Service这样的注解 )
     * 它是通过注册BeanDefinition的方式注册的,可能会注册多个,而其中的每一个HttpProxyFactoryBean实例都会被自动装配同一个HttpProxyInvocationHandler实例
     *
     * 也有等价的做法是:
     * 利用ApplicationContextAware接口的setApplicationContext获取到applicationContext,
     * 然后把applicationContext 作为属性设置到当前类中
     *  再利用applicationContext的getBean方法来获取InvocationHandler的实例
     */
    @Autowired
    private HttpProxyInvocationHandler httpProxyInvocationHandler;

    private Class<T> rpcInterface;

    public HttpProxyFactoryBean(Class<T> rpcInterface){
        this.rpcInterface = rpcInterface;
    }


    @Override
    public T getObject() throws Exception {
        //这里应该放ComputerService接口
        return (T)Proxy.newProxyInstance(rpcInterface.getClassLoader(),new Class[]{rpcInterface} ,httpProxyInvocationHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return rpcInterface;
    }

}

每一个动态代理类都必须要实现InvocationHandler这个接口

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

我们可以直接将实现InvocationHandler的实现类注入spring容器中,然后每一个接口走同一个innvoke方法,当然也可以每一个都new一个,然后可以在构造方法中塞入特定的一些参数。我这边因为对应的每一个代理没啥特殊的就走同一个了:
定义一些参数,请求的urlproxy.serverUrl,和请求添加的项目,proxy.project


import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 把服务方法的调用转换为对远程服务的http请求
 * @Version
 */
@Component
public class HttpProxyInvocationHandler implements InvocationHandler {

    @Value("${proxy.serverUrl}")
    private String serverUrl;

    @Value("${proxy.project}")
    private String serverProject;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> declaringClass = method.getDeclaringClass();
        if (Object.class.equals(declaringClass)) {
            return method.invoke(this, args);
        }

        String methodName = method.getName();
        String name = method.getDeclaringClass().getName();

        //拼接请求地址
        String url = serverUrl + name + "/" + methodName;
//        String url = "http://test:8080/soa/com.rdd.TestService/createActivity";
        HashMap<String, String> paramMap = new HashMap<>();
    
//        String result = HttpRequest.post(url).headerMap(paramMap, true).body("[" + JSONObject.toJSONString(args) + "]").execute().body();
        String result = HttpRequest.post(url).headerMap(paramMap, true).body(JSONObject.toJSONString(args)).execute().body();

        System.out.println(">>>" + url + "的响应结果为:" + result);

        //将响应结果转换为接口方法的返回值类型
        Class<?> returnType = method.getReturnType();
        if (returnType.isPrimitive() || String.class.isAssignableFrom(returnType)) {
            if (returnType == int.class || returnType == Integer.class) {
                return Integer.valueOf(result);
            } else if (returnType == long.class || returnType == Long.class) {
                return Long.valueOf(result);
            }
            return result;
        } else if (Collection.class.isAssignableFrom(returnType)) {
            return JSONArray.parseArray(result, Object.class);
        } else if (Map.class.isAssignableFrom(returnType)) {
            return JSON.parseObject(result, Map.class);
        } else {
            return JSONObject.parseObject(result, returnType);
        }
    }
}

最后后将对应的工厂bean封装成bean定义,注入到spring容器中

我们的接口一般都是jar形式的,我就简单的写在一个proxy.txt文件中,然后去读取对应的接口全路径,注入到spring容器中,当然也可以通过扫描某个包,自定义注解等等方式实现。

import cn.hutool.core.io.file.FileReader;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Title:bean工厂的后置处理器,用于动态注册bean
 * @Date 2021/3/23 10:13
 * @Description
 * @Version
 */
@Component
@PropertySource("classpath:application.properties")
public class HttpProxyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    /**
     * 该方法用来注册更多的bean到spring容器中
     *
     * @param beanDefinitionRegistry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        //默认UTF-8编码,可以在构造中传入第二个参数做为编码
        FileReader fileReader = new FileReader("proxy.txt");
        List<String> classStrList = fileReader.readLines();
        Set<Class<?>> proxyClazzSet = new HashSet<>();
        for (String s : classStrList) {
            if (StringUtils.isBlank(s)) {
                continue;
            }
            try {
                Class<?> aClass = Class.forName(s);
                proxyClazzSet.add(aClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        for (Class<?> targetClazz : proxyClazzSet) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(targetClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinitionBuilder.getRawBeanDefinition();
            //设置构造方法的参数  对于Class<?>,既可以设置为Class,也可以传Class的完全类名
            //definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz);
            definition.getConstructorArgumentValues().addGenericArgumentValue(targetClazz.getName());

            //Bean的类型,指定为某个代理接口的类型
            definition.setBeanClass(HttpProxyFactoryBean.class);
            //表示 根据代理接口的类型来自动装配
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinitionRegistry.registerBeanDefinition(targetClazz.getName(),definition);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}
  • 0
    点赞
  • 2
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

JAVA开发人员必备是HTML格式的 JavaTM 2 Platform Standard Edition 6 API 规范 本文档是 Java 2 Platform Standard Edition 6.0 的 API 规范。 请参见: 描述 Java 2 Platform 软件包 java.applet 提供创建 applet 所必需的类和 applet 用来与其 applet 上下文通信的类。 java.awt 包含用于创建用户界面和绘制图形图像的所有类。 java.awt.color 提供用于颜色空间的类。 java.awt.datatransfer 提供在应用程序之间和在应用程序内部传输数据的接口和类。 java.awt.dnd Drag 和 Drop 是一种直接操作动作,在许多图形用户界面系统中都会遇到它,它提供了一种机制,能够在两个与 GUI 中显示元素逻辑相关的实体之间传输信息。 java.awt.event 提供处理由 AWT 组件所激发的各类事件的接口和类。 java.awt.font 提供与字体相关的类和接口java.awt.geom 提供用于在与二维几何形状相关的对象上定义和执行操作的 Java 2D 类。 java.awt.im 提供输入方法框架所需的类和接口java.awt.im.spi 提供启用可以与 Java 运行时环境一起使用的输入方法开发的接口java.awt.image 提供创建和修改图像的各种类。 java.awt.image.renderable 提供用于生成与呈现无关的图像的类和接口java.awt.print 为通用的打印 API 提供类和接口java.beans 包含与开发 beans 有关的类,即基于 JavaBeansTM 架构的组件。 java.beans.beancontext 提供与 bean 上下文有关的类和接口java.io 通过数据流、序列化和文件系统提供系统输入和输出。 java.lang 提供利用 Java 编程语言进行程序设计的基础类。 java.lang.annotation 为 Java 编程语言注释设施提供库支持。 java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供了引用对象类,支持在某种程度上与垃圾回收器之间的交互。 java.lang.reflect 提供类和接口,以获得关于类和对象的反射信息。 java.math 提供用于执行任意精度整数算法 (BigInteger) 和任意精度小数算法 (BigDecimal) 的类。 java.net 为实现网络应用程序提供类。 java.nio 定义作为数据容器的缓冲区,并提供其他 NIO 包的概述。 java.nio.channels 定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的选择器。 java.nio.channels.spi 用于 java.nio.channels 包的服务提供者类。 java.nio.charset 定义用来在字节和 Unicode 字符之间转换的 charset、解码器和编码器。 java.nio.charset.spi java.nio.charset 包的服务提供者类。 java.rmi 提供 RMI 包。 java.rmi.activation 为 RMI 对象激活提供支持。 java.rmi.dgc 为 RMI 分布式垃圾回收提供了类和接口java.rmi.registry 提供 RMI 注册表的一个类和两个接口java.rmi.server 提供支持服务器端 RMI 的类和接口java.security 为安全框架提供类和接口java.security.acl 此包中的类和接口已经被 java.security 包中的类取代。 java.security.cert 提供用于解析和管理证书、证书撤消列表 (CRL) 和证书路径的类和接口java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-
相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值