?
服務提供和消費腦圖
服務提供和消費腦圖

?
參見: 服務提供者, 服務消費者, 服務注冊中心
服務提供者
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 {@Autowiredprivate 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;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {List<BeanNode> beanNodes = ParseServiceXML.getProviderServices(servicesXmlPath); 解析自定義服務提供配置文件List<InterfaceMessage> list = new ArrayList<>();for(BeanNode beanNode : beanNodes) { 根據服務對應id去 尋找實現的 beanif(!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());}}}@Overridepublic 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;@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate ITestService testService;@RequestMapping("test")public SimpleResponse test() {try {return SimpleResponse.success(testService.testService());} catch (Exception e) {e.printStackTrace();return SimpleResponse.error(e.getMessage());}} }
這就是我的實現方式,就說到這里了。最后我想說,思路很重要,掌握的知識很重要,多積累,多思考,任重而道遠。最后附上我從中積累的知識和經驗。
知識和經驗
執行順序及ProxyFactoryBean實現

Springboot 工程自定義jar包中獲取上下文工具類

實體類型(例如下面)網絡傳輸方法,避免字符串編碼格式問題
。。。。。
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);
??? <groupId>commons-codec</groupId>
??? <artifactId>commons-codec</artifactId>
</dependency>