服务之间的通信
上文中已经讲述了基本环境搭建,本文基于上文环境。
spring-cloud中微服务之间通信主要有俩种形式:
- resttemplate方式
- feign方式
resttempalte方式请求url硬编码在客户端,当有注册中心有多个服务时,注册中心无法知道服务由谁提供。
feign方式由于是以接口的形式进行通信,更适合这种架构。
先来说resttemplate方式,构建一个名为custom-common的maven-project。
pom.xml:
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <parent> <groupid>com.custom.mg</groupid> <artifactid>custom.root</artifactid> <version>0.0.1-snapshot</version> </parent> <artifactid>custom.common</artifactid> <dependencies> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-eureka-server</artifactid> </dependency> </dependencies> </project>
application.yml:
server: port: 8001 spring: application: name: custom-common #注册中心指向start eureka: instance: instance-id: custom-common appname: ${spring.application.name} client: service-url: #url前面增加注册中心账号以及密码 defaultzone: http://admin:123@127.0.0.1:8080/eureka/ #注册中心指向end
编写controller
package custom.common.controller; import org.springframework.beans.factory.annotation.value; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; /** * * * @title saycontroller.java * @packge custom.common.controller * @description todo(用一句话描述该类的作用) * @author pandong * @date 2019年2月14日 */ @restcontroller @requestmapping(value="/common") public class saycontroller { @value("${server.port}") private string port; @getmapping(value="/hello") public string say() { return "hello!!i'm server :"+port; } }
编写入口类启动注册到注册中心。
再构建一个名为custom-role的maven-project
pom.xml:
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <parent> <groupid>com.custom.mg</groupid> <artifactid>custom.root</artifactid> <version>0.0.1-snapshot</version> </parent> <artifactid>custom.role</artifactid> <dependencies> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-eureka-server</artifactid> </dependency> </dependencies> </project>
application.yml:
server: port: 8002 spring: application: name: custom-role #注册中心指向start eureka: instance: instance-id: custom-role appname: ${spring.application.name} client: service-url: #url前面增加注册中心账号以及密码 defaultzone: http://admin:123@127.0.0.1:8888/eureka/ #注册中心指向end
注意:
这里使用了defaultzone: http://admin:123@127.0.0.1:8888/eureka/。@前面的是对应注册中心设置的账号、密码。
上篇章讲述到访问注册中心会出现红字提醒,是因为没有为注册中心配置密码,任何服务都能注册进来,这里将密码配置进来,修改注册中心的相关文件
——————————————————————————————————————————————————————————————————————————————————————
pom.xml中增加依赖:
<!-- 密码配置依赖 --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency>
application.yml完整配置:
server: port: 8080 spring: security: user: name: admin # 配置登录的账号是admin password: 123 # 配置登录的密码是123 eureka: instance: hostname: localhost client: # 是否要注册到其他eureka server实例 register-with-eureka: false # 是否要从其他eureka server实例获取数据 fetch-registry: false service-url: defaultzone: http://{spring.security.user.name}:{spring.security.user.password}@{hostname}:{server.port}/eureka/ server: # 关闭注册中心对服务的保护措施(如果服务挂掉了,又没进行该配置,那么服务会以up状态一直存在于注册中心。反之会在3分钟内容在注册中心下线) enableselfpreservation: false
增加安全策略配置:
package com.server.config; import org.springframework.security.config.annotation.web.builders.httpsecurity; import org.springframework.security.config.annotation.web.configuration.enablewebsecurity; import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter; /** * spring cloud finchley及更高版本,必须添加如下代码,部分关闭掉spring security的csrf保护功能,否则应用无法正常注册! * {@link http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_securing_the_eureka_server} * * @title securityconfig.java * @package com.server.config * @description todo(一句话描述该类作用) * @author pandong * @date 2019年2月18日 */ @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { @override protected void configure(httpsecurity http) throws exception { http.csrf().ignoringantmatchers("/eureka/**"); super.configure(http); } }
启动注册中心,访问localhost:8080,会出现登录页面,输入配置的账号、密码即可访问,进去后会发现之前的红字提醒不见了,但是,又出现新的红字警示,这是因为application.yml中增加了enableselfpreservation: false该项配置所引起,可以不用理会。
——————————————————————————————————————————————————————————————————————————————————————
编写新建工程入口类:
package com.user; import org.springframework.boot.springapplication; import org.springframework.boot.autoconfigure.springbootapplication; import org.springframework.boot.web.servlet.servletcomponentscan; import org.springframework.cloud.netflix.eureka.enableeurekaclient; import org.springframework.cloud.openfeign.enablefeignclients; @springbootapplication @enableeurekaclient public class roleapplication { public static void main(string[] args) { springapplication.run(userapplication.class, args); } }
编写controller:
package custom.role.controller; import org.springframework.beans.factory.annotation.autowired; import org.springframework.beans.factory.annotation.value; import org.springframework.cloud.client.serviceinstance; import org.springframework.cloud.client.loadbalancer.loadbalancerclient; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; import org.springframework.web.client.resttemplate; /** * * * @title sayapicontroller.java * @packge custom.role.controller * @description todo(用一句话描述该类的作用) * @author pandong * @date 2019年2月14日 */ @restcontroller @requestmapping(value="/api") public class sayapicontroller { @value("${server.port}") private string port; @autowired private resttemplate resttemplate; @autowired private loadbalancerclient loadbalancerclient; /** * 第一种方式,直接使用resttemplate,url写死。 * 因为服务端的 api 被硬编码在客户端,因此有两个缺点: * – 当注册中心有很多服务时,我们可能不知道我们需要的服务由谁提供、api是多少,因此就可能无法调用到服务。 * –当某个服务部署了多个,例如 api 是: “localhost:8080/hello,localhost:8081/hello “,那么此时就需要负载均衡,这种硬编码显然不符合场景。 * @return */ @getmapping(value="/say") public string say() { resttemplate template = new resttemplate(); return template.getforobject("http://127.0.0.1:8001/common/hello", string.class); } /** * 第二种方式:客户端通过 loadbalancerclient 来获取应用名,进而获取地址和端口,在格式化拼接地址,从而调用 product服务。 * 缺点是每次调用服务都要这样写,编码很麻烦。 */ @getmapping("/say2") public string say2() { resttemplate resttemplate = new resttemplate(); serviceinstance serviceinstance = loadbalancerclient.choose("custom-common"); // serviceid 为提供服务的应用名 string url = string.format("http://%s:%s",serviceinstance.gethost(),serviceinstance.getport() + "/common/hello"); string response = resttemplate.getforobject( url, string.class); return response; } /** * 第三种方式:通过 @loadbalanced,可在resttemplate 直接使用应用名字。 * 这种方式需要编写配置类,{@link custom.role.config.configbeans} */ @getmapping("/say3") public string say3() { return resttemplate.getforobject("http://custom-common/common/hello", string.class); } }
resttemplate配置类
package custom.role.config; import org.springframework.cloud.client.loadbalancer.loadbalanced; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.client.resttemplate; @configuration public class configbeans { @bean @loadbalanced public resttemplate resttemplate(){ return new resttemplate(); } }
启动服务,一次访问,你会发现最后都会调用到custom-comon的服务中,这种硬编码在代码中的方式,显然是不合适的。
下面来说使用feign方式进行通信。
在custom-role中添加依赖:
<!-- feign组件依赖 --> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-openfeign</artifactid> </dependency>
入口类上面添加注解@enablefeignclients。
然后编写feign通信接口:
package custom.role.service; import org.springframework.cloud.netflix.feign.feignclient; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; /** * 定义feign接口 * @title serverservice.java * @packge custom.role.service * @description todo(用一句话描述该类的作用) * @author pandong * @date 2019年2月14日 */ @feignclient(name = "custom-common") // 调用服务名 public interface serverservice { @requestmapping(method = requestmethod.get,value = "/common/hello") // 访问路径,要与服务中提供的一致 string hello(); }
@feignclient(name = "custom-common"),custom-common对应的是服务提供者的服务名
编写feigncontroller控制器:
package custom.role.controller; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.bind.annotation.restcontroller; import custom.role.service.serverservice; /** * feign通信控制器 * @title feignclientcontroller.java * @packge custom.role.controller * @description 以feign方式进行服务之间的通信 * @author pandong * @date 2019年2月14日 */ @restcontroller @requestmapping(value="/feign") public class feignclientcontroller { @autowired private serverservice service; @requestmapping(method = requestmethod.get,value = "/say") public string say() { return service.hello(); } }
方法上只能使用requestmapping,不能使用getmapping之类的注解。
启动服务后访问/feign/say,你会发现同样会调用的custom-common中的服务。
相信到这里大家对于选择哪种方式就不用多说了。
最后说一句,由于在写学习日记的时候是另外一个版本,后面有重新搭建了一个更新的版本,文中都是从本地写好的日记中拷贝的,有些地方对应不上基础篇的地方就自行修改一下。
上一篇: 浅谈SpringMVC执行过程
下一篇: STL-vector