欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

服务之间的通信

程序员文章站 2022-04-28 10:46:33
上文中已经讲述了基本环境搭建,本文基于上文环境https://www.cnblogs.com/xxpandong/p/10485172.html。 spring-cloud中微服务之间通信主要有俩种形式: RestTempalte方式请求url硬编码在客户端,当有注册中心有多个服务时,注册中心无法知 ......

 

 

 

 上文中已经讲述了基本环境搭建,本文基于上文环境。

 

  spring-cloud中微服务之间通信主要有俩种形式:

  1. resttemplate方式
  2. 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>
view code

 

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 
view code

 

编写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;
    }
    
    
}
 
view code

 

 

编写入口类启动注册到注册中心。

 

再构建一个名为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>
view code

 

 

  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 
view code

 注意:

    这里使用了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
view code

 

 增加安全策略配置:

服务之间的通信
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);
    }
}
view code

 

启动注册中心,访问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);
    }
    
    
}
view code

 

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();
    }

}
view code

 

 

启动服务,一次访问,你会发现最后都会调用到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();
    
}
view code

@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();
    }
    
}
view code

 

方法上只能使用requestmapping,不能使用getmapping之类的注解。

启动服务后访问/feign/say,你会发现同样会调用的custom-common中的服务。

 

相信到这里大家对于选择哪种方式就不用多说了。

 

最后说一句,由于在写学习日记的时候是另外一个版本,后面有重新搭建了一个更新的版本,文中都是从本地写好的日记中拷贝的,有些地方对应不上基础篇的地方就自行修改一下。