服务之间的依赖:
其实根据上图我们发现会员管理服务其实是依赖于我们图书的这个服务的,那么为什么要依赖于图书这个服务呢,因为会员服务想要进行借阅图书的时候,必须要对图书模块的图书的库存等做校验才可以,所以member服务要调用book服务的API了
首先我们要对book这个服务进行改写,首先book服务的service层的代码:
package com.laosan.book.service;
import com.laosan.book.entity.Book; import com.laosan.book.exception.BookNotFoundException; import com.laosan.book.repository.BookRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
import java.util.List; import java.util.Optional;
@Service public class BookService { @Autowired private BookRepository repository;
public List<Book> findAll() { List<Book> bookList = repository.findAll(); return bookList; }
/** * 根据图书Id查询某个图书信息 * @return */ public Book getInfo(Long bid) { Optional<Book> optional = repository.findById(bid); Book book = null; //第一个判断这个书是否存在 if(optional.isPresent()) { book = optional.get(); //获得对应的对象 } else { //如果不存在抛出一个对应的异常信息 throw new BookNotFoundException("图书"+bid+"未找到"); } return book; } } 自定义异常类: package com.laosan.book.exception; /** * book自定义异常信息 */ public class BookNotFoundException extends RuntimeException{ public BookNotFoundException(String message) { super(message); } } |
然后book项目的Controller中添加一个方法:
package com.laosan.book.controller; @RestController @CrossOrigin //进行该Controller的跨域操作 public class BookController { @Autowired private BookService bookService; /** * book模块返回所有的操作 * @return */ @GetMapping("/list") public Map<String, Object> list() { Map<String, Object> map = new HashMap<>(); try { List<Book> list = bookService.findAll(); map.put("code", "0"); map.put("message", "success"); map.put("data", list); } catch (Exception e) { e.printStackTrace(); map.put("code", e.getClass().getSimpleName()); map.put("message", e.getMessage()); } return map; }
@GetMapping("/info") public Map<String, Object> info(Long bid) { Map<String, Object> map = new HashMap<>(); try { Book book = bookService.getInfo(bid); map.put("code", "0"); map.put("message", "success"); map.put("data", book); } catch (Exception e) { e.printStackTrace(); map.put("code", e.getClass().getSimpleName()); map.put("message", e.getMessage()); } return map; } } |
重启所有服务:
首先对book进行单体应用测试,输入地址进行测试:
现在的问题是我们刚刚查询出来的根据bid查询的数据并不是给book服务来用的而是给member服务来用的,那么现在我们就要开始应用远程调用的操作了
我们先在member项目的Controller里面进行相应的测试操作:
package com.laosan.member.controller; @Controller @CrossOrigin //跨域问题的解决 public class MemberController { @Autowired private MemberService memberService; @GetMapping("/check") @ResponseBody public Map<String, Object> checkMobile(String mobile, HttpServletResponse response) { //response.setHeader("Access-Control-Allow-Origin", "*"); Map<String, Object> map = new HashMap<String, Object>(); try{ Member member = memberService.checkByMobile(mobile); map.put("code", "0"); map.put("message", "success"); map.put("data", member); }catch (Exception e) { e.printStackTrace(); map.put("code", e.getClass().getSimpleName()); map.put("message", e.getMessage()); } return map; } /** * 远程调用我们需要的book服务里面对应的操作 * @param bid * @return */ @GetMapping("/test") @ResponseBody public String test(Long bid) { //在这里我们使用SpringCloud内置的RestTemplate对象进行远程调用 RestTemplate restTemplate = new RestTemplate(); //这里先new一个RestTemplate对象 String json = restTemplate.getForObject("http://localhost:8100/info?bid=" + bid, String.class); System.out.println(json); return json; } } |
把所有服务开启,在浏览器对member项目做单体应用测试:输入地址:http://localhost:8088/test?bid=1,具体的返回如下图所示:
其实我们发现我们访问的本来是member服务里面的请求但是调用的却是book服务里面的数据
但是有个问题我们要说下就是我们根据RestTemplate获得的数据地址时被写死了,所以不推荐使用这种方式,那么我们就要换一种方式进行操作
RestTemplate restTemplate = new RestTemplate(); //这里先new一个RestTemplate对象
String json = restTemplate.getForObject("http://localhost:8100/info?bid=" + bid, String.class);
我们在这里采用Ribben进行客户端负载均衡,那么我们要改写上面的代码:
//注入负载均衡客户端,这里Ribbon的核心组件 @Autowired private LoadBalancerClient loadBalancerClient; /** * 远程调用我们需要的book服务里面对应的操作 * @param bid * @return */ @GetMapping("/test") @ResponseBody public String test(Long bid) { //在这里我们使用SpringCloud内置的RestTemplate对象进行远程调用 //RestTemplate restTemplate = new RestTemplate(); //这里先new一个RestTemplate对象 //String json = restTemplate.getForObject("http://localhost:8100/info?bid=" + bid, String.class); //System.out.println(json);
//不推荐使用RestTemplate因为被写死了远程调用的地址 //推荐使用Ribbon进行负载均衡远程调用 RestTemplate restTemplate = new RestTemplate(); //获得服务列表 ServiceInstance instance = loadBalancerClient.choose("book");//在Erueak服务中获得book服务的名字并找到该服务 String host = instance.getHost(); //获得主机端口号 int port = instance.getPort(); //获得端口号 //通过RestTemplate做远程调用 String json = restTemplate.getForObject("http://" + host + ":" + port + "/info?bid=" + bid, String.class); return json; } |
然后输入地址进行测试:http://localhost:8088/test?bid=1
我们来看下我们使用Ribbon也可以完成对服务的请求的操作,而且我们只需要拿到某个服务的服务名称可端口就可以直接访问那个服务了
客户端负载均衡Ribbon执行流程:
我们知道Ribbon是负载均衡的客户端,那么他也是具备负载均特性的,所以我们做个测试,先把book项目按照8100端口进行启动,然后回到我们的git里面进行对配置中心的设置把配置中心的端口从8100改成8101再次进行做一个8101服务启动看看我们Ribbon是否能够执行负载均衡的操作,我呢可以debug进行测试。答案是肯定的,这就是Ribbon的作用它会根据我们在Eureka中提供的服务器列表进行轮询操作,那么这个就是Ribbon的核心技术负载均衡。
但是我们看到上面虽然实现了负载均衡的操作,对于代码的角度来说还是比较难看的,那么我么要改写代码,让他的操作更为简洁,如下所示
我们要在member项目上进行改写:
首先我们打开member项目的主启动类
package com.laosan.member;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;
@SpringBootApplication public class MemberApplication { public static void main(String[] args) { SpringApplication.run(MemberApplication.class, args); }
/** * 注入一个RestTemplate * @return */ @Bean @LoadBalanced //对RestTemplate对象进行负载均衡操作 public RestTemplate getRestTemplate() { RestTemplate restTemplate = new RestTemplate(); return restTemplate; } } |
然后简化member项目中的Controller方法的操作
//在这里注入我们RestTemplate @Autowired private RestTemplate restTemplate;
/** * 远程调用我们需要的book服务里面对应的操作 * @param bid * @return */ @GetMapping("/test") @ResponseBody public String test(Long bid) { //在这里我们使用SpringCloud内置的RestTemplate对象进行远程调用 //RestTemplate restTemplate = new RestTemplate(); //这里先new一个RestTemplate对象 //String json = restTemplate.getForObject("http://localhost:8100/info?bid=" + bid, String.class); //System.out.println(json);
//不推荐使用RestTemplate因为被写死了远程调用的地址 //推荐使用Ribbon进行负载均衡远程调用 //RestTemplate restTemplate = new RestTemplate(); //获得服务列表 //ServiceInstance instance = loadBalancerClient.choose("book");//在Erueak中获得book服务的名字并找到该服务 //String host = instance.getHost(); //获得主机端口号 //int port = instance.getPort(); //获得端口号 //通过RestTemplate做远程调用 //String json = restTemplate.getForObject("http://" + host + ":" + port + "/info?bid=" + bid, String.class);
//这里直接通过RestTemplate进行操作即可 String json = restTemplate.getForObject("http://book/info?bid="+bid, String.class); return json; } 其实我们来看代码变的极为简单 |
输入地址进行测试即可:
主启动类上:
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test")
public String test(Long bid){
/*//创建一个对象
RestTemplate restTemplate=new RestTemplate(); //Spring对httpclient 进行了封装
String str = restTemplate.getForObject("http://localhost:8006/info?bid=" + bid, String.class);
return str;*/
//不推荐使用RestTemplate因为被写死了远程调用的地址
//推荐使用Ribbon进行负载均衡远程调用
/* RestTemplate restTemplate = new RestTemplate();
//获得服务列表
ServiceInstance instance = loadBalancerClient.choose("book");//在Erueak服务中获得book服务的名字并找到该服务
String host = instance.getHost(); //获得主机端口号
int port = instance.getPort(); //获得端口号
//通过RestTemplate做远程调用
String json = restTemplate.getForObject("http://" + host + ":" + port + "/info?bid=" + bid, String.class);
return json;*/
String str = restTemplate.getForObject("http://book/info?bid=" + bid, String.class);
return str;
} |
服务间的通信Feign :
下面让我们来改造member项目,第一步引入Feign的依赖:
<!--feign的相关依赖--> <!--feign的相关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> |
然后在member项目的启动类中添加一个注解用来启动feign
package com.laosan.member;
@SpringBootApplication @EnableFeignClients //开启feign客户端 public class MemberApplication { public static void main(String[] args) { SpringApplication.run(MemberApplication.class, args); } @Bean @LoadBalanced //对RestTemplate对象进行负载均衡操作 public RestTemplate getRestTemplate() { RestTemplate restTemplate = new RestTemplate(); return restTemplate; } } |
我们下面开始操作fegin,首先在member项目中我们创建一个client包在该包下我们来进行操作:
package com.laosan.member.client;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="book") //这里book是Eureka中book服务的名字,这里说明我们要调用book微服的客户端 public interface BookClient { @GetMapping("/info") //请求,这里的请求是和book服务里面的请求保持一致的 //@RequestParam("bid") 是绑定传递过去参数的,get请求使用@RequestParam public String getInfo(@RequestParam("bid") Long bid); } |
我们在member项目的Controller中根据fegin的操作从新定义一个方法
@Autowired
private BookFeignClient bookFeignClient;
@RequestMapping("/test1")
public String test1(Long bid){
String str= bookFeignClient.info(bid);
return str;
}
输入地址进行单体member项目的测试:http://localhost:8088/test1?bid=1
这里对于feign操作请求的时候如果是post这里有个细节我么要知道,如下所示:
//这里如果是post请求我们在绑定参数的时候必须使用@RequestBody @PostMapping("/aa") public String aa(@RequestBody String aa); |
格式化book服务中返回的数据,来看下图
根据上面的通过feign的方法我们知道我们返回的数据是一个字符串,但是我们知道我们返回的数据其实是有对象的数据在里面的如我们data的数据,那么我们要对整体返回的数据进行格式化,如下面操作
首先在Member项目中我们创建一个vo包,并创建一个类:
package com.laosan.member.vo;
import lombok.Data;
import java.io.Serializable;
/** * 格式化数据的封装类 */ @Data public class ResultVo<T> implements Serializable { private String code; private String message; private T data; } |
在member项目中创建dto包并创建一个类用于封装从服务端传递过来的对象:
这个对象里面的数据要和json字符串里面的data数据保持一致
package com.laosan.member.dto;
import lombok.Data;
import java.io.Serializable;
/** * 封装book的类 */ @Data public class BookDTO implements Serializable { private Long bid; private String sn; private String name; private String author; private String publishing; private Float bprice; private Float sprice; private String btype; private Integer stock; } |
对feign的操作接口进行修改,如下所示:
@FeignClient(name="book") //这里book是Eureka中book服务的名字,这里说明我们要调用book微服的客户端 public interface BookClient { @GetMapping("/info") //请求,这里的请求是和book服务里面的请求保持一致的 //@RequestParam("bid") 是绑定传递过去参数的,get请求使用@RequestParam public ResultVo<BookDTO> getInfo(@RequestParam("bid") Long bid); } |
然后对member项目中的controller对应的方法进行修改:
@Autowired private BookClient bookClient;
@GetMapping("/test1") @ResponseBody public String test1(Long bid) { ResultVo<BookDTO> resultVo = bookClient.getInfo(bid); return resultVo.getData().getName(); } |
最后进行测试即可:
最后强调个东西Feign默认就是支持Ribbon的负载均衡策略