• 我眼中的服务提供和服务消费


    服务提供和消费脑图

    服务提供和消费脑图

    参见: 服务提供者, 服务消费者, 服务注册中心

    服务提供者

    1.服务提供者启动,解析xml文件中配置的服务,这里使用Dom4j解析。

    2.将服务的一些相关信息注册到 服务注册中心。

    注:服务相关信息:服务中心接口url,接口名称,方法名称,参数信息。

    3.提供一个接口,服务消费者通过调用这个接口url来调用相应的服务。

    参见: 服务提供和消费脑图, 服务注册中心 (1.注册服务), 服务消费者 (3.调用服务)

    服务消费者

    1.服务消费者启动,使用dom4j解析xml获取要消费的服务相关接口。

    2.根据接口信息去服务注册中心判断是否有对应的注册信息,如果有则通过jdk动态代理生成相应的代理类并注册到spring中(代理方法中会根据服务中心返回的信息(服务提供者的url)去调用服务提供者对应的服务)。

    参见: 服务提供和消费脑图, 服务注册中心 (2.消费服务), 服务提供者 (3.调用服务)

    服务注册中心

    1.将来自服务提供者信息存储到redis。

    2.将服务信息提供给服务消费者。

    参见: 服务提供者 (1.注册服务), 服务消费者 (2.消费服务), 服务提供和消费脑图

    工程示例

      注:示例中为了简单,采用rest请求方式来代替socket连接

      注册中心

    @RestController
    @RequestMapping("index")
    public class IndexController {
    
        @Autowired
        private RedisCacheTemplate redisCacheTemplate;
       //注册服务提供者信息,将信息放到redis中
        @RequestMapping(value = "register", method = RequestMethod.POST)
        public SimpleResponse register(@RequestBody RegisterMessage registerMessage) {
            try {
                Map<String, Object> map = new HashMap<>();
                for (InterfaceMessage interfaceMessage : registerMessage.getInterfaceMessageList()) {
                    interfaceMessage.setProviderUrl(registerMessage.getProviderUrl());
                    map.put(ToStringBuilder.reflectionToString(interfaceMessage, ToStringStyle.SHORT_PREFIX_STYLE), true);
                }
                redisCacheTemplate.batchPut(map);
                return SimpleResponse.success(map.size());
            } catch (Exception e) {
                e.printStackTrace();
                return SimpleResponse.error(e.getMessage());
            }
        }
    
       //消费者拿到配置的服务信息到注册中心来匹配,验证是否存在这个服务
        @RequestMapping(value = "contains", method = RequestMethod.POST)
        public SimpleResponse contains(@RequestBody InterfaceMessage interfaceMessage) {
            try {
                if(redisCacheTemplate.exist(ToStringBuilder.reflectionToString(interfaceMessage, ToStringStyle.SHORT_PREFIX_STYLE))) {
                    return SimpleResponse.success(true);
                } else {
                    return SimpleResponse.error(null);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return SimpleResponse.error(e.getMessage());
            }
        }
    
        @RequestMapping(value = "test", method = {RequestMethod.GET, RequestMethod.POST})
        public SimpleResponse test(@RequestParam String providerUrl){
            return SimpleResponse.success(providerUrl);
        }
    }

      服务提供者

    <?xml version="1.0" encoding="UTF-8"?>
    <services-provider>
        <service id="testService" interface="com.hjzgg.simulation.api.ITestService"/>
    </services-provider>

      自定义xml,配置将要注册的服务id及对应的接口类。

    # 内置tomcat服务器配置
    server.port=8088
    server.context-path=/provider-server
    
    #打印彩色日志
    spring.output.ansi.enabled=always
    
    # 日志打印级别
    logging.level.root=debug
    
    # service
    service.xml.path=classpath:service-provider.xml 自定义服务提供者配置文件 位置
    service.provider.path=http://localhost:8088/provider-server/index/provider  服务提供者执行相应服务接口
    service.register.path=http://localhost:8090/register-server/index/register  调用注册中心 接口
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.hjzgg.simulation.common.node.InterfaceMessage;
    import com.hjzgg.simulation.common.node.RegisterMessage;
    import com.hjzgg.simulation.common.parsexml.BeanNode;
    import com.hjzgg.simulation.common.parsexml.ParseServiceXML;
    import com.hjzgg.simulation.common.response.ReturnCode;
    import com.hjzgg.simulation.common.utils.RestTemplateUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.http.MediaType;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Registrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
        private static Logger logger = LoggerFactory.getLogger(Registrar.class);
    
        private String servicesXmlPath;
        private String serviceProviderPath;
        private String serviceRegisterPath;
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            List<BeanNode> beanNodes = ParseServiceXML.getProviderServices(servicesXmlPath); 解析自定义服务提供配置文件
            List<InterfaceMessage> list = new ArrayList<>();
            for(BeanNode beanNode : beanNodes) { 根据服务对应id去 寻找实现的 bean
                if(!registry.containsBeanDefinition(beanNode.getBeanName())) {
                    logger.error("接口" + beanNode.getBeanName() + " " + beanNode.getInterfaceCls().getTypeName() + " 没有对应的实现类");
                } else {
                    InterfaceMessage interfaceMessage = new InterfaceMessage();
                    interfaceMessage.setBeanName(beanNode.getBeanName());
                    interfaceMessage.setInterfacType(beanNode.getInterfaceCls().getTypeName());
                    list.add(interfaceMessage);
                }
            }
            if(!CollectionUtils.isEmpty(list)) { 将配置的服务信息发送的注册中心
                RegisterMessage registerMessage = new RegisterMessage();
                registerMessage.setProviderUrl(this.serviceProviderPath);
                registerMessage.setInterfaceMessageList(list);
                try {
                    String result = RestTemplateUtils.post(this.serviceRegisterPath, (JSONObject) JSON.toJSON(registerMessage), MediaType.APPLICATION_JSON_UTF8);
                    JSONObject retJson = JSONObject.parseObject(result);
                    if(retJson.getInteger("code") == ReturnCode.SUCCESS.getValue()) {
                        logger.debug("服务注册成功...");
                    } else {
                        logger.error("服务注册失败..." + retJson.getString("msg"));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.error("服务注册失败..." + e.getMessage());
                }
            }
        }
    
        @Override
        public void setEnvironment(Environment environment) { 获取环境变量
            this.servicesXmlPath = environment.getProperty("service.xml.path");
            this.serviceProviderPath = environment.getProperty("service.provider.path");
            this.serviceRegisterPath = environment.getProperty("service.register.path");
    
            assert(StringUtils.isNotEmpty(this.serviceProviderPath) && StringUtils.isNotEmpty(serviceRegisterPath) &&
            StringUtils.isNotEmpty(this.servicesXmlPath));
        }
    }
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    /**
     * Created by hujunzheng on 2017/7/7.
     */
    
    @Configuration
    @Import(Registrar.class)
    public class Config { 注册服务配置启动
    }
    import com.hjzgg.simulation.common.node.ServiceMessage;
    import com.hjzgg.simulation.common.response.SimpleResponse;
    import com.hjzgg.simulation.common.utils.ContextUtils;
    import com.hjzgg.simulation.common.utils.SerializeUtil;
    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.lang3.builder.ToStringBuilder;
    import org.springframework.util.ReflectionUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by hujunzheng on 2017/7/8.
     */
    @RestController
    @RequestMapping("index")
    public class IndexController {
    
       服务提供者执行相应服务接口 @RequestMapping(value
    = "invoke", method = RequestMethod.POST) public Object invoke(@RequestParam String serviceMessageBody) { try {
           根据消费者传递的服务信息 找到对应的服务bean以及方法,并利用反射执行方法,最后返回结果 ServiceMessage serviceMessage
    = (ServiceMessage) SerializeUtil.unserialize(Hex.decodeHex(serviceMessageBody.toCharArray())); Object bean = null; if((bean = ContextUtils.getBean(serviceMessage.getBeanName(), serviceMessage.getRequireType())) != null) { List<Class<?>> classList = new ArrayList<>(); if(serviceMessage.getArgs() != null) { for (Object obj : serviceMessage.getArgs()) { classList.add(obj.getClass()); } } Method method = ReflectionUtils.findMethod(bean.getClass(), serviceMessage.getMethodName(), classList.toArray(new Class<?>[0])); if(method != null) { return method.invoke(bean, serviceMessage.getArgs()); } else { return SimpleResponse.error("服务" + serviceMessage.getRequireType().getTypeName() + "中没有对应参数" + ToStringBuilder.reflectionToString(classList) + "的" + serviceMessage.getMethodName() + "方法"); } } else { return SimpleResponse.error("没有名称为" + serviceMessage.getBeanName() + "且类型为" + serviceMessage.getRequireType().getTypeName() + "对应的bean"); } } catch (Exception e) { e.printStackTrace(); return SimpleResponse.error(e.getMessage()); } } }

      服务消费者

    <?xml version="1.0" encoding="UTF-8"?>
    <services-consumer>
        <service ref="testService" interface="com.hjzgg.simulation.api.ITestService" url="http://localhost:8088/provider-server/index/provider"/>
    </services-consumer>

      自定义服务消费者配置,服务引用名称,接口类型,调用服务提供者URL

    # 内置tomcat服务器配置
    server.port=8089
    server.context-path=/consumer-server
    
    #打印彩色日志
    spring.output.ansi.enabled=always
    
    # 日志打印级别
    logging.level.root=debug
    
    # service xml
    service.xml.path=classpath:service-consumer.xml 自定义服务消费配置文件位置
    service.contains.url=http://localhost:8090/register-server/index/contains 注册中心服务查询接口
    service.invoke.url=http://localhost:8088/provider-server/index/invoke 服务提供者执行相应服务接口
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.hjzgg.simulation.common.dynamic.JdkDynamicProxy;
    import com.hjzgg.simulation.common.node.InterfaceMessage;
    import com.hjzgg.simulation.common.parsexml.BeanNode;
    import com.hjzgg.simulation.common.parsexml.ParseServiceXML;
    import com.hjzgg.simulation.common.register.SpringBeanRegister;
    import com.hjzgg.simulation.common.response.ReturnCode;
    import com.hjzgg.simulation.common.utils.RestTemplateUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.http.MediaType;
    
    import java.util.Iterator;
    import java.util.List;
    
    public class Registrar implements ImportBeanDefinitionRegistrar, EnvironmentAware{
    
        private Logger logger = LoggerFactory.getLogger(Registrar.class);
    
        private String servicesXmlPath;
        private String serviceContainsPath;
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
         解析自定义服务消费配置文件 List
    <BeanNode> beanNodes = ParseServiceXML.getConsumerServices(servicesXmlPath); 判断注册中心 是否注册了这个服务了 for(Iterator<BeanNode> it = beanNodes.iterator(); it.hasNext(); ) { BeanNode beanNode = it.next(); InterfaceMessage interfaceMessage = new InterfaceMessage(); interfaceMessage.setProviderUrl(beanNode.getUrl()); interfaceMessage.setInterfacType(beanNode.getInterfaceCls().getTypeName()); interfaceMessage.setBeanName(beanNode.getBeanName()); try { String result = RestTemplateUtils.post(this.serviceContainsPath, (JSONObject) JSON.toJSON(interfaceMessage), MediaType.APPLICATION_JSON_UTF8); JSONObject retJson = JSON.parseObject(result); if (retJson.getInteger("code") == ReturnCode.FAILURE.getValue()) { it.remove(); logger.error(interfaceMessage.getBeanName() + "对应类型" + interfaceMessage.getInterfacType() + "的服务在" + interfaceMessage.getProviderUrl() + "上没有注册"); } } catch (Exception e) { e.printStackTrace(); logger.error("服务" + interfaceMessage.getBeanName() + "对应类型" + interfaceMessage.getInterfacType() + "查找失败..." + e.getMessage()); } }
         将与注册中心一直的服务 以 动态代理的方式 注册到spring中 SpringBeanRegister.registerBean(importingClassMetadata, registry, beanNodes); } @Override
    public void setEnvironment(Environment environment) { 设置环境变量 this.servicesXmlPath = environment.getProperty("service.xml.path"); this.serviceContainsPath = environment.getProperty("service.contains.url"); String serviceInvokePath = environment.getProperty("service.invoke.url"); assert(StringUtils.isNotEmpty(serviceContainsPath) && StringUtils.isNotEmpty(this.servicesXmlPath) && StringUtils.isNotEmpty(serviceInvokePath)); JdkDynamicProxy.setServerProviderInvokeUrl(serviceInvokePath); } }
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    /**
     * Created by hujunzheng on 2017/7/7.
     */
    
    @Configuration
    @Import(Registrar.class)
    public class Config {
    }

       测试一下

      api接口

    import com.alibaba.fastjson.JSONObject;
    
    /**
     * Created by hujunzheng on 2017/7/7.
     */
    public interface ITestService {
        JSONObject testService();
    }

      服务提供者对应的实例

    import com.alibaba.fastjson.JSONObject;
    import com.hjzgg.simulation.api.ITestService;
    import org.springframework.stereotype.Service;
    
    /**
     * Created by hujunzheng on 2017/7/8.
     */
    @Service("testService")
    public class TestServiceImpl implements ITestService {
        @Override
        public JSONObject testService() {
            JSONObject result = new JSONObject();
            result.put("name", "hujunzheng");
            result.put("age", 25);
            return result;
        }
    }

      消费者对应的测试

    import com.hjzgg.simulation.api.ITestService;
    import com.hjzgg.simulation.common.response.SimpleResponse;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * Created by hujunzheng on 2017/7/9.
     */
    @RestController
    @RequestMapping("index")
    public class ConsumerController {
    
        @Autowired
        private ITestService testService;
    
    
        @RequestMapping("test")
        public SimpleResponse test() {
            try {
                return SimpleResponse.success(testService.testService());
            } catch (Exception e) {
                e.printStackTrace();
                return SimpleResponse.error(e.getMessage());
            }
        }
    }

      这就是我的实现方式,就说到这里了。最后我想说,思路很重要,掌握的知识很重要,多积累,多思考,任重而道远。最后附上我从中积累的知识和经验。

    知识和经验

      执行顺序及ProxyFactoryBean实现

      
     
      Public class ProxyFactoryBean implements FactoryBean, InitializingBean;
      方法执行顺序 getObjectType->afterPropertiesSet->getObject
      bean 的属性设置的 先于 getObjectType
     

      Springboot 工程自定义jar包中获取上下文工具类

      
      要加上 @Component注解
     

      实体类型(例如下面)网络传输方法,避免字符串编码格式问题

      发送请求
    ServiceMessage serviceMessage = new ServiceMessage();
    。。。。。

    JSONObject params = new JSONObject();
    params.put("serviceMessageBody", Hex.encodeHexString(SerializeUtil.serialize(serviceMessage)));

    Class<?> returnType = method.getReturnType();
    return RestTemplateUtils.post(SERVER_PROVIDER_INVOKE_URL, params, MediaType.APPLICATION_FORM_URLENCODED, returnType);
      接收请求
    @RequestMapping(value = "invoke", method = RequestMethod.POST)
    public Object invoke(@RequestParam String serviceMessageBody) {
        try {
            ServiceMessage serviceMessage = (ServiceMessage) SerializeUtil.unserialize(Hex.decodeHex(serviceMessageBody.toCharArray()));
          。。。。。 
    }
      参考工具类
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
    </dependency>
      Hex实现十六进制字符串和byte[]之间的转换,另附RestTemplateUtils工具链接
     
      完整项目下载,请点这里!
  • 相关阅读:
    Java中OutOfMemoryError(内存溢出)的情况及解决办法
    php strtotime函数服务器和本地不相同
    Object传入String类型和其他
    Java静态变量,常量,成员变量,局部变量
    Vector使用
    Java反射机制
    List和ArrayList,LinkList的区别
    phpstrtotime()对于31日求上个月有问题
    PGsql解决时差24H
    drawable 另外一种形式dimens.xml
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/7131212.html
Copyright © 2020-2023  润新知