自定义标签
首先自定义标签 。这边自定义在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中拿到对应的实体类,通过传过来的方法名称、数据类型以及数量找到与之相匹配的)。最后通过反射调用方法