自定义手写Dubbo(Tomcat版)

环境说明

利用的jdk11。创建的maven项目

首先引入Maven依赖

<!-- Tomcat核心包 -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>9.0.12</version>
</dependency>

<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.51</version>
</dependency>

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.4</version>
</dependency>

增加Redis配置文件

redis.properties文件:
redis.host=127.0.0.1
redis.port=6379
redis.password=
redis.timeout=100000
redis.maxIdle=100
redis.maxActive=300
redis.maxWait=1000

接口和工具类

第一步

定义接口类,用于消费者和生产者都可以调用到。

package com.provider.api;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :接口,消费者和生产者都可以调用的到
 */
public interface IndexService {
    public String sayHello(String name);
}

第二步

定义一个传参的实体类,用于消费者给生产者推送参数时使用。

package com.framework.request;

import java.io.Serializable;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :用于生产者和消费者之间传参
 */
public class Invocation implements Serializable {
    //接口名称
    private String interfaceName;
    //方法名
    private String methodName;
    //方法的参数类型
    private Class[] paramTypes;
    //方法的参数
    private Object[] params;


    public Invocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }
}

第三步

定义接口与实现类关系的仓库。用于根据接口名称获取对应的实现类。

package com.framework.register;

import java.util.HashMap;
import java.util.Map;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :本地注册中心,主要用于存储接口名称和实现类对象的关联关系
 */
public class LocalRegister {
    private static Map<String, Class> map = new HashMap<>();

    public static void regist(String interfaceName, Class implClass) {
        map.put(interfaceName, implClass);
    }

    public static Class get(String interfaceName) {
        return map.get(interfaceName);
    }
}

第四步

定义注册中心,这里注册中心采用Redis,用于根据接口获取服务提供者的地址列表。

package com.framework.register;

import com.alibaba.fastjson.JSONArray;
import com.framework.request.URL;
import com.util.RedisUtil;

import java.util.List;

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :远程注册中心
 */
public class RemoteMapRegister {
    //将服务注册到Redis上
    public static void regist(String interfaceName, URL url) {
        RedisUtil.getInstance().setList(interfaceName, url.toString());
    }

    //从Redis中返回路径信息
    public static List<URL> get(String interfaceName) {
        List<String> redisList = RedisUtil.getInstance().getList(interfaceName);
        List<URL> list = JSONArray.parseArray(redisList.toString(), URL.class);
        return list;
    }

}

第五步

增加网络协议接口,用于规范启动的服务接口。
注:此处实现的是Tomcat。

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :网络传输协议接口
 */
public interface Protocol {
    public Protocol start(String hostname, Integer port) throws LifecycleException;
}

第六步

增加Protocol工厂,用于根据协议,统一返回启动的服务端和发送的消费端。

package com.framework.protocol;

import com.framework.protocol.http.MyHttpClient;
import com.framework.protocol.http.TomcatServer;
import com.framework.request.Invocation;

import java.io.IOException;
import java.net.URISyntaxException;

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :协议工厂
 */
public class ProtocolFactory {
    public static Protocol getProxy(String protocol) {
        switch (protocol) {
            case "http":
            case "https":
                return new TomcatServer();

        }
        return null;
    }

    public static String send(String protocol, String hostname, Integer port, Invocation invocation) throws InterruptedException, IOException, URISyntaxException {
        switch (protocol) {
            case "http":
            case "https":
                //发起请求
                MyHttpClient myHttpClient = new MyHttpClient();
                return myHttpClient.send(hostname, port, invocation);

        }
        return null;
    }
}

第七步

增加URL类,用于存储生产者的地址和端口号。

package com.framework.request;

import java.io.Serializable;

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :URL 用于存储服务提供者的路径信息
 */

public class URL implements Serializable {
    private String hostname;
    private Integer port;

    public URL(String hostname, Integer port) {
        this.hostname = hostname;
        this.port = port;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    @Override
    public String toString() {
        return "{" +
                "hostname:'" + hostname + '\'' +
                ", port:" + port +
                '}';
    }
}

第八步

增加一个负载均衡处理类。

package com.framework.request;

import java.util.List;
import java.util.Random;

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :负载均衡请求工具类
 */
public class LoadBalance {

    /**
     * 随机负载均衡算法
     *
     * @param list
     * @return
     */
    public static URL random(List<URL> list) {
        Random random = new Random();
        int index = random.nextInt(list.size());
        return list.get(index);
    }
}

生产者

第一步

首先先对接口做实现,用于消费者调用的实现类。

package com.provider.impl;

import com.provider.api.IndexService;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :
 */
public class IndexServiceImpl implements IndexService {
    @Override
    public String sayHello(String name) {
        return "Hello," + name;
    }
}

第二步

增加一个TomcatServer,和Springboot一样,利用代码启动Tomcat服务。这个服务需要实现下Protocol接口

package com.framework.protocol.http;

import com.framework.protocol.Protocol;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :代码实现Tomcat服务
 */
public class TomcatServer implements Protocol {

    @Override
    public Protocol start(String hostname, Integer port) throws LifecycleException {
        Tomcat tomcat = new Tomcat();
        tomcat.setHostname(hostname);
        Connector conn = new Connector();
        conn.setPort(port);
        tomcat.getService().addConnector(conn);
        //注册Servlet
        Context ctx = tomcat.addContext("/", null);
        Wrapper wrapper = Tomcat.addServlet(ctx, "dispatcherServlet", "com.framework.protocol.http.DispatcherServlet");
        wrapper.addMapping("/*");
        //启动
        tomcat.start();
        System.out.println("Tomcat启动成功,端口号:" + port);
        //4.阻塞当前线程
        tomcat.getServer().await();
        return this;
    }
}

第三步

实现一个Servlet,用于接收请求

package com.framework.protocol.http;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :接收Servlet
 */
public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            new TomcatHandler().handler(req, resp);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

第四步

增加一个Handler,用于处理Servlet接收的请求。

package com.framework.protocol.http;

import com.alibaba.fastjson.JSONObject;
import com.framework.register.LocalRegister;
import com.framework.request.Invocation;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :处理发过来的请求
 */
public class TomcatHandler {
    //处理请求
    public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        //从请求中,转换对象
        Invocation invocation = JSONObject.parseObject(req.getInputStream(), Invocation.class);
        //本地仓库中,获取接口对应的代理对象
        Class clazz = LocalRegister.get(invocation.getInterfaceName());
        //反射对象
        Object o = clazz.getDeclaredConstructor().newInstance();
        //根据对象,获取方法
        Method method = clazz.getMethod(invocation.getMethodName(), invocation.getParamTypes());
        //调用方法
        String result = (String) method.invoke(o, invocation.getParams());
        //这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().print(result);
    }
}

第五步

编写启动类。对外提供服务。对外提供前,需要往本地注册中心注册。

package com.provider;

import com.framework.protocol.Protocol;
import com.framework.protocol.ProtocolFactory;
import com.framework.register.RemoteMapRegister;
import com.framework.request.URL;
import com.provider.api.IndexService;
import com.provider.impl.IndexServiceImpl;
import com.framework.register.LocalRegister;
import org.apache.catalina.LifecycleException;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :服务提供者
 */
public class ProviderApplication {
    public static void main(String[] args) throws LifecycleException {
        //接口名称
        String interfaceName = IndexService.class.getName();
        //当前服务的域名和端口号
        URL url = new URL("localhost", 8999);

        //注册接口服务
        LocalRegister.regist(interfaceName, IndexServiceImpl.class);
        //注册远程地址服务
        RemoteMapRegister.regist(interfaceName, url);

        //定义协议,发布服务
        Protocol protocol = ProtocolFactory.getProxy("http");
        protocol.start(url.getHostname(), url.getPort());
    }
}

消费者

第一步

增加动态代理类,主要用于实现消费者可以直接调用,不关心内部细节。内部细节由代理实现。

package com.framework.proxy;

import com.framework.protocol.ProtocolFactory;
import com.framework.protocol.http.MyHttpClient;
import com.framework.register.RemoteMapRegister;
import com.framework.request.Invocation;
import com.framework.request.LoadBalance;
import com.framework.request.URL;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * @author :Mall
 * @date :Created in 2021-07-01
 * @description :动态代理工厂,用于对接口产生代理对象
 */
public class ProxyFactory<T> {

    /**
     * 根据传入的接口类,返回代理对象
     *
     * @param interfaceClazz
     * @return
     */
    public static <T> T getProxy(Class interfaceClazz) {
        /**
         * 此处采用JDK的动态代理工具。对接口代理
         * 对传入的接口生成代理对象并处理代理逻辑
         */
        return (T) Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz}, new InvocationHandler() {
            /**
             * 调用方法都会走这个方法。
             * @param proxy 代理的对象
             * @param method 调用的方法
             * @param args 方法参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //封装请求的参数
                Invocation invocation = new Invocation(interfaceClazz.getName(), method.getName(), method.getParameterTypes(), args);
                try {
                    //根据接口名称,获取请求地址
                    List<URL> urlList = RemoteMapRegister.get(interfaceClazz.getName());
                    //负载均衡,随机获取路径
                    URL url = LoadBalance.random(urlList);
                    
                    //从启动配置参数上获取协议
                    String protocol = System.getProperty("protocol");
                    //发起请求
                    return  ProtocolFactory.send(protocol,url.getHostname(), url.getPort(), invocation);
                } catch (Exception e) {
                    return "接口请求异常";
                }
            }
        });
    }
}

第二步

增加一个HttpClient请求发起的类。

package com.framework.proxy;

import com.alibaba.fastjson.JSONObject;
import com.framework.Invocation;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :发送请求
 */
public class MyHttpClient {


    //利用JDK自带的HttpClient实现
    public String send(String hostname, Integer port, Invocation invocation) throws URISyntaxException, IOException, InterruptedException {
        URI uri = new URI("http", null, hostname, port, "/", null, null);
        HttpRequest request = HttpRequest.newBuilder(uri).POST(HttpRequest.BodyPublishers.ofString(JSONObject.toJSONString(invocation))).build();
        HttpResponse<String> send = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString(Charset.forName("UTF-8")));

        return send.body();
    }
}

第三步

消费者启动应用,调用生产者。

package com.consumer;

import com.framework.proxy.ProxyFactory;
import com.provider.api.IndexService;

import java.io.IOException;
import java.net.URISyntaxException;

/**
 * @author :Mall
 * @date :Created in 2021-06-30
 * @description :
 */
public class ConsumerApplication {
    public static void main(String[] args) throws InterruptedException, IOException, URISyntaxException {
        //根据接口类,生成代理对象
        IndexService indexService = ProxyFactory.getProxy(IndexService.class);
        //调用接口
        String result = indexService.sayHello("马罗乐");
        System.out.println(result);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

铁蛋的铁,铁蛋的蛋

留一杯咖啡钱给作者吧

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

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

打赏作者

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

抵扣说明:

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

余额充值