手撸一个类似于Dubbo的远程调用

 

自定义标签

首先自定义标签 。这边自定义在META-INF下文件名称为soa.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.zhuguangedu.com/schema/soa"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.zhuguangedu.com/schema/soa"	
	elementFormDefault="qualified" attributeFormDefault="unqualified">
	<xsd:element name="registry">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="protocol" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="address" type="xsd:string"></xsd:attribute>
		</xsd:complexType>
	</xsd:element>	
	<xsd:element name="reference">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="interface" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="check" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="protocol" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="loadbalance" type="xsd:string"></xsd:attribute>
		</xsd:complexType>
	</xsd:element>	
	<xsd:element name="protocol">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="name" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="port" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="host" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="contextpath" type="xsd:string"></xsd:attribute>
		</xsd:complexType>
	</xsd:element>	
	<xsd:element name="service">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="interface" type="xsd:string"></xsd:attribute>
			<xsd:attribute name="ref" type="xsd:string"></xsd:attribute>
		</xsd:complexType>
	</xsd:element>	
</xsd:schema>

这边element表示标签的意思,比如

<xsd:element name="registry"> 这个表示registry标签

 

<xsd:attribute这里表示放置registry的属性
xmlns="http://www.zhuguangedu.com/schema/soa" 这里表示命名空间

 

targetNamespace="http://www.zhuguangedu.com/schema/soa" 目标命名空间和xmlns一样
xmlns:xsd="http://www.w3.org/2001/XMLSchema"遵循xsd的规则

然后,我们要知道这个命名空间对应的是哪里的文件于是在META-INF下面新建一个spring.schema 文件 内容为

http\://www.zhuguangedu.com/schema/soa.xsd=META-INF/soa.xsd

如果引用了上面的命名空间就会去META-INF/soa.xsd 去找命名规则。现在我们就可以在xml 文件里面引入xsd 的约束, 然后使用我们的自定义标签了。

18年11月27日补-----分割线-------------------------------------------------------------------------------

为我不负责任道歉,没写完就放上来了。

我们写完以上只是自定义了标签而已,最终我们需要将这个标签里面的内容转换成实体类并且注册到spring容器中。那么如何去解析这个标签呢?在META-INF下面放一个spring.handles文件。文件中的内容为:

http\://www.zhuguangedu.com/schema/soa=com.zhuguang.jack.spring.parse.SOANamespaceHandler

上面的意思为:将经过soa命名空间解析出来的东西交给全路径名为com.zhuguang.jack.spring.parse.SOANamespaceHandler的类处理。

解析自定义标签注入spring容器

SOANamespaceHandler此类必须继承NameSpaceHandlerSupport这个类。具体代码如下图

public class SOANamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
//<xsd:element name="registry"> 下面的第一个参数就是上面自定义标签的name 后面的值,在xml中每一个已下面4个单词开头的都会调用一次对应的registryBeanDefinitionParser一次
        this.registerBeanDefinitionParser("registry", new RegistryBeanDefinitionParse(Registry.class));
        this.registerBeanDefinitionParser("reference", new ReferenceBeanDefinitionParse(Reference.class));
        this.registerBeanDefinitionParser("protocol", new ProtocolBeanDefinitionParse(Protocol.class));
        this.registerBeanDefinitionParser("service", new ServiceBeanDefinitionParse(Service.class));
    }
}

我们进入RegistryBeanDefinitionParse这个解析类看看:

public class RegistryBeanDefinitionParse implements BeanDefinitionParser {
    
    private Class<?> beanClass;
    //这边构造函数传过来的是Registry.class 所以 beanClass 的值确定了
    public RegistryBeanDefinitionParse(Class<?> beanClass) {
        this.beanClass = beanClass;
    }
    //这边开始解析标签
    public BeanDefinition parse(Element element, ParserContext parserContext) {
      //怎么才能够把element里面的属性值传到Reference,并且交给spring实例化
        //如果要涉及到一个类的实例化交给spring去实例,那么我们就可以new 一个 BeanDefinition对象
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
        rootBeanDefinition.setBeanClass(beanClass);
        rootBeanDefinition.setLazyInit(false);
//标签中定义的id,protocol,address 等值解析出来
        String id = element.getAttribute("id");
        String protocol = element.getAttribute("protocol");
        String address = element.getAttribute("address");
//如果spring容器里面没有这个id 就注册进去
        
        if (id != null && !"".equals(id)) {
            if (parserContext.getRegistry().containsBeanDefinition(id)) {
                throw new IllegalStateException("spring has this id");
            }
            rootBeanDefinition.getPropertyValues().add("id", id);
        }
        else {
            //如果id没有配置,那么就是类名的首字母小写作为一个类的唯一标识
            id = beanClass.getName().substring(0, 1).toLowerCase()
                    + beanClass.getName().substring(1);
            rootBeanDefinition.getPropertyValues().add("id", id);
        }
        
        rootBeanDefinition.getPropertyValues().add("protocol", protocol);
        rootBeanDefinition.getPropertyValues().add("address", address);
        
        //这个创建出来的rootBeanDefinition对象必须要交个spring管理
        parserContext.getRegistry().registerBeanDefinition(id, rootBeanDefinition);
        
        return rootBeanDefinition;
    }
}

那么整个就交给spring容器管理了.

动态代理,写入注册中心

我们去看看Reference类的文件

//FactoryBean ,当我们解析xml 的时候里面调用了这个类,那么就会执行getObject方法ApplicationContextAware 是获取上下文,InitializingBean 是当这个类的属性注册完之后调用的
public class Reference implements FactoryBean,ApplicationContextAware,InitializingBean {
    private String id;
    
    private String intf;
    
    private String check;
    
    private String protocol;
    
    private String loadbalance;
    
    private Invoke invoke;
    
    private ApplicationContext application;
    
    private static Map<String,Invoke> invokeMaps = new HashMap<String,Invoke>();
    
    private static Map<String,LoadBalance> loadBalances = new HashMap<String,LoadBalance>();
    
      
    /** 
     * @Fields registryInfo 本地缓存注册中心中的服务列表信息
     */  
        
    private List<String> registryInfo = new ArrayList<String>();
    
    
    static {
        invokeMaps.put("http", new HttpInvoke());
        invokeMaps.put("rmi", new RmiInvoke());
        invokeMaps.put("netty", new NettyInvoke());
        invokeMaps.put("jack", new NettyInvoke());
        
        loadBalances.put("random", new RondomLoadBalance());
        loadBalances.put("roundrob", new RoundRobinLoadBalance());
    }
    
    /* 
     * 返回一个对象,然后被spring容器管理
     * 
     * 这个方法要返回 intf这个接口的代理实例
     */
    public Object getObject() throws Exception {
        
        if(protocol != null && !"".equals(protocol)) {
        //根据protocol;属性获取缓存中对应的调用方法
            invoke = invokeMaps.get(protocol);
        } else {
            Protocol protocol = application.getBean(Protocol.class);
            if(protocol != null) {
                invoke = invokeMaps.get(protocol.getName());
            } else {
            //默认给个协议
                invoke = invokeMaps.get("http");
            }
        } 
        
        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(),
                //对intf 这个全路径的类进行代理
                new Class<?>[] {Class.forName(intf)},
        //代理类为InvokeInvocationHandler
                new InvokeInvocationHandler(invoke,this));
        //总结一个大白话就是,想用调用intf=com.xxx.xxx.xxxImpl,就会去调用InvokeInvocationHandler的invock 方法。
        return proxy;
    }
    
    /* 
     * 返回实例的类型
     */
    public Class getObjectType() {
        try {
            if (intf != null && !"".equals(intf)) {
                return Class.forName(intf);
            }
        }
        catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
    
    /* 
     * 是否单例
     */
    public boolean isSingleton() {
        return true;
    }
    
    public String getId() {
        return id;
    }
    
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.application = applicationContext;
    }

    public void setId(String id) {
        this.id = id;
    }
    
    public String getIntf() {
        return intf;
    }
    
    public void setIntf(String intf) {
        this.intf = intf;
    }
    
    public String getCheck() {
        return check;
    }
    
    public void setCheck(String check) {
        this.check = check;
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public void afterPropertiesSet() throws Exception {
    //获取此id 存在注册中心里面的信息,并且缓存到本地
        registryInfo = BaseRegistryDelegate.getRegistry(id, application);
    }

    public List<String> getRegistryInfo() {
        return registryInfo;
    }

    public void setRegistryInfo(List<String> registryInfo) {
        this.registryInfo = registryInfo;
    }

    public static Map<String, LoadBalance> getLoadBalances() {
        return loadBalances;
    }

    public static void setLoadBalances(Map<String, LoadBalance> loadBalances) {
        Reference.loadBalances = loadBalances;
    }

    public String getLoadbalance() {
        return loadbalance;
    }

    public void setLoadbalance(String loadbalance) {
        this.loadbalance = loadbalance;
    }
}

 

10 点半了,撸铁洗澡睡觉。有机会再写

----------------------here we go-------------------------------------------------

我们先看看这个registryInfo是怎么缓存到本地的

此处运用了委托模式交给了BaseRegistryDelegate处理,我们且看这个类怎么写的:

public class BaseRegistryDelegate {
//当标签是service时 调用此处的registry方法
   public static boolean registry(String ref,ApplicationContext application) {
       
       //我们要获取registry标签对应的Registry实例类,应为这个标签配置了用什么协议
       Registry registry = application.getBean(Registry.class);
       
       //这个就是注册中心的某一个实例类,这个实例类是有我们的配置文件来决定
       BaseRegistry baseRegistry = registry.getRegistrys().get(registry.getProtocol());
       //将ref 注册到注册中心上
       return baseRegistry.registry(ref,application);
   }
// 此处是reference 获取
   
   public static List<String> getRegistry(String id,ApplicationContext application) {

       //我们要获取registry标签对应的Registry实例类
       Registry registry = application.getBean(Registry.class);
       
       //这个就是注册中心的某一个实例类,这个实例类是有我们的配置文件来决定
       BaseRegistry baseRegistry = registry.getRegistrys().get(registry.getProtocol());
       
       return baseRegistry.getRegistry(id, application);
   }
}

这里注册中心我们用到了redis 当注册中心, 其实意思就是将ref 当key ,从本地中将所有是Service类的找出来 。我们自定义标签的时候解析标签的时候注入了spring容器中,Service标签的beanClass 是Service ,忘掉的往上翻。找出所有的Service 后,看看本地有没有类的ref 和这个ref 相同的 有就将ref 当成key ,然后将protocol 中的host和port属性,以及Service类的全路径当成value。如果有相同的key 则看看此ip和端口是否包含在redispool.lrang()方法查出来的值中,没有就直接添加。有的话,看看有没有修改,有修改就将值取出,删除修改,仔添加。

reference则调用getRegistry。通过key 去redis 中,将ip 列表查出来。

我们再看InvokeInvocationHandler,当调用reference暴露出来的服务接口时,就会先调用InvokeInvocationHandler类中的invoke方法:

远程调用

public class InvokeInvocationHandler implements InvocationHandler {
    
    private Invoke invoke;
    
    private Reference reference;
    
    public InvokeInvocationHandler(Invoke invoke,Reference reference) {
        this.invoke = invoke;
        this.reference = reference;
    }
//method 就是reference代理的接口中那个被调用的方法名称
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("============invoke到了InvokeInvocationHandler=============");
        
        //在这个invoke里面做一个远程的rpc调用。
        Invocation invocation = new Invocation();
        invocation.setIntf(reference.getIntf());
        invocation.setMethod(method);
        invocation.setObjs(args);
        invocation.setReference(reference);
//invoke 是reference 那边根据xml 配置的属性,选择那个方式远程调用。这边是http
        return invoke.invoke(invocation);
    }
    
}

于是我们走到了HttpInvoke

public class HttpInvoke implements Invoke {
    
    public String invoke(Invocation invoke) throws Exception {
        //这里需要得到一个服务列表去调用,那么服务列表怎么来?上面已经说过了
        Reference reference = invoke.getReference();
        List<String> registryInfo = reference.getRegistryInfo();
        String loadbalance = reference.getLoadbalance();
        
        //在这里需要选择某一个服务去调用,那么在这里如何选择呢?就是一个负载均衡算法
        //轮询、随机、最小活跃数、权重
        LoadBalance loadbalanceClass = reference.getLoadBalances()
                .get(loadbalance);
        NodeInfo nodeinfo = loadbalanceClass.doSelect(registryInfo);
        
        JSONObject sendParam = new JSONObject();
        sendParam.put("methodName", invoke.getMethod().getName());
        sendParam.put("serviceId", reference.getId());
        sendParam.put("methodParams", invoke.getObjs());
        sendParam.put("paramTypes", invoke.getMethod().getParameterTypes());
        
        String url = "http://" + nodeinfo.getHost() + ":" + nodeinfo.getPort()
                + nodeinfo.getContextpath();
        //拼接成http请求
        String result = HttpRequest.sendPost(url, sendParam.toJSONString());
        return result;
    }
    
}

请求发送了, 在接受请求的时候我们给这个信息截取到就行了:

接受远程请求调用本地方法

package com.zhuguang.jack.servlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.context.ApplicationContext;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zhuguang.jack.spring.configBean.Service;

public class DispatcherServlet extends HttpServlet {
    
      
    /** 
     * @Fields serialVersionUID TODO 
     */  
        
    private static final long serialVersionUID = 2341676214124313L;

    /* 
     * 这里就会接受到消费端的请求
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        JSONObject httpProcess = httpProcess(req, resp);
//取参数
        String serviceId = httpProcess.getString("serviceId");
        String methodName = httpProcess.getString("methodName");
        JSONArray paramTypes = httpProcess.getJSONArray("paramTypes");
        JSONArray methodParam = httpProcess.getJSONArray("methodParams");
        
        Object[] objs = null;
        if (methodParam != null) {
            objs = new Object[methodParam.size()];
            int i = 0;
            for (Object o : methodParam) {
                objs[i++] = o;
            }
        }
        
        //从spring容器中拿到serviceid对应的bean的实例吧,然后调用methodName
        ApplicationContext application = Service.getApplication();
        Object bean = application.getBean(serviceId);
        
        //反射调用方法 方法见下
        Method method = getMethod(bean, methodName, paramTypes);
        try {
            if (method != null) {
                //反射调用
                Object result = method.invoke(bean, objs);
                PrintWriter pw = resp.getWriter();
                pw.write(result.toString());
            } else {
                PrintWriter pw = resp.getWriter();
                pw.write("==============no such method====================" + methodName);
            }
        }
        catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    private Method getMethod(Object bean, String methodName,
            JSONArray paramTypes) {
//拿出此类中所有的方法
        Method[] methods = bean.getClass().getMethods();
        List<Method> retMethod = new ArrayList<Method>();
        
        for (Method method : methods) {
//找出方法名相等的放到集合中
            if (methodName.equals(method.getName())) {
                retMethod.add(method);
            }
        }
//集合只有一个,那么就是他了,继续判断参数个数和类型是不是一样
        
        if (retMethod.size() == 1) {
            return retMethod.get(0);
        }
        
        boolean isSameSize = false;
        boolean isSameType = false;
        jack: for (Method method : retMethod) {
            Class<?>[] types = method.getParameterTypes();
            //判断参数个数
            if (types.length == paramTypes.size()) {
                isSameSize = true;
            }
            if (!isSameSize) {
                continue;
            }
            判断类型是否相等
            for (int i = 0; i < types.length; i++) {
                if (types[i].toString().contains(paramTypes.getString(i))) {
                    isSameType = true;
                }
                else {
                    isSameType = false;
                }
                
                if (!isSameType) {
                    continue jack;
                }
            }
            
            if (isSameType) {
                return method;
            }
        }
        
        return null;
    }
//解析传过来的数据
    
    private JSONObject httpProcess(HttpServletRequest req,
            HttpServletResponse resp) {
        StringBuffer sb = new StringBuffer();
        try {
            ServletInputStream in = req.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in,
                    "utf-8"));
            String s = "";
            while ((s = br.readLine()) != null) {
                sb.append(s);
            }
            
            if (sb.toString().length() <= 0) {
                return null;
            }
            else {
                return JSONObject.parseObject(sb.toString());
            }
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

全剧终。

总结

将这几个标签初始化, 并且放入spring容器中, 交给spring容器管理。而且比如我想远程调用某个Service的方法。那么应该在调用此方法的时候,拦截到,并且远程调用此方法。 这里呢实际上是对service所有方法的动态代理,所以当调用此方法的时候我们可以在invcationHandle 中进行远程调用。 那么远程调用的信息在哪里呢?我们可以在启动的时候给服务注册到zk或者redis里面。然后选择传输协议(http、redis、rmi)。 将方法名称,参数,beanid 传过去,生产者这边接到之后进行一些判断(根据beanid 从applicationcontext中拿到对应的实体类,通过传过来的方法名称、数据类型以及数量找到与之相匹配的)。最后通过反射调用方法

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值