springcloud通过zuul来实现路由网关服务的,路由网关是整个springcloud项目的门户所在,他能提供一个统一的入口供客户端访问,他涉及到的的服务模块众多,他在远程服务调用中担当服务消费者的角色,客户端的请求到达网关,网关会代理到别的服务上,由别的服务模块完成相应的功能。
在springcloud入门这个系列项目中,springcloud接收两个请求,一个是保存用户信息,即save(name),他会调用feign服务提供的save(name)方法,另一个请求是获取一个message,即get()方法,他会调用ribbon服务提供的get()方法。
网关项目gateway需要通过feign和ribbon的方式调用远程服务,因此他需要依赖spring-cloud-starter-openfeign和spring-cloud-starter-netflix-ribbon,他还需要实现熔断策略,因此还需要依赖spring-cloud-starter-netflix-hystrix,作为网关他需要依赖spring-cloud-starter-netflix-zuul,他也需要作为eureka client注册到服务注册中心,因此需要依赖spring-cloud-starter-netflix-eureka-client。
gateway项目依赖配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
依赖中加入jquery,是为了在前端页面中使用jquery,这里网关项目提供网关,也提供UI。
为了操作用户,我们定义User实体,和前面feign项目中用到的User实体是一样的。
package com.xxx.gateway.domain;
import java.util.Date;
public class User {
private Integer id;
private String name;
private String mobile;
private Date birth;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public User(){}
public User(String name){
this.name = name;
this.mobile = "";
this.birth = new Date();
}
}
这里只是控制层操作User,因此无需指定表名、主键,也无需生成构造方法,使用默认构造方法。
这里重点是service层的几个类和方法:
首先是RibbonService.java
package com.xxx.gateway.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class RibbonService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallbackGet")
public String get(){
return restTemplate.getForObject("http://ribbon/get",String.class);
}
public String fallbackGet(){
return "hystrix service broker.";
}
}
这里就涉及到了ribbon远程服务调用的方式,我们需要一个RestTemplate实例对象,然后需要指定服务地址。这里get()方法也通过hystrix熔断服务指定了当远程服务调用失败的时候,备用方法是fallbackGet()。这里需要注入的RestTemplate对象,我们通过如下方式配置:
package com.xxx.gateway.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
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 AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
}
另外一个服务类FeignService.java
package com.xxx.gateway.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.xxx.gateway.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class FeignService {
@Autowired
private UserService userService;
@HystrixCommand(fallbackMethod = "fallbackSave")
public List<User> save(String name){
return userService.save(name);
}
public List<User> fallbackSave(String name){
List<User> list = new ArrayList<>();
User user = new User("user service broker.");
list.add(user);
return list;
}
}
它也通过hystrix熔断机制,指定了调用远程方法失败时候的备选方法fallbackSave(String name),这里备选方法也需要带上和主方法一样的参数。另外这里UserService就是我们通过feign远程调用服务的方式来调用feign项目提供的服务的。
package com.xxx.gateway.service;
import com.xxx.gateway.domain.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@FeignClient("feign")
public interface UserService {
@RequestMapping(method= RequestMethod.POST, value = "/save",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<User> save(@RequestBody String name);
}
UserService是一个接口,只需要通过@FeignClient("feign")注解就可以实现远程服务调用,并且调用时需要通过@RequestMapping()来指定请求的方法。
控制层的方法就很简单了,直接调用服务层service里对应的类和方法,是一个很普通的controller。
package com.xxx.gateway.controller;
import com.xxx.gateway.domain.User;
import com.xxx.gateway.service.FeignService;
import com.xxx.gateway.service.RibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class WebController {
@Autowired
private RibbonService ribbonService;
@Autowired
private FeignService feignService;
@RequestMapping(value = "/dispatch")
@ResponseBody
public List<User> sendMessage(@RequestBody String name){
return feignService.save(name);
}
@RequestMapping(value = "/get",produces = {MediaType.TEXT_PLAIN_VALUE})
public String get(){
return ribbonService.get();
}
}
启动类:
package com.xxx.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableFeignClients
@EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
@EnableEurekaClient表示开启eureka client的支持,@EnableCircuitBreaker表示开启熔断机制,@EnableFeignClients表示开启feign远程调用客户端支持,@EnableZuulProxy表示开始网关路由支持。
最后,到了配置文件出场的时候了,也是application.yml
server:
port: 80
和bootstrap.yml
spring:
application:
name: gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
non-secure-port: ${server.port:80}
另外我们在resources路径下建立一个static文件夹,存放静态页面文件,里面就一个index.html,项目启动直接就可以访问到。内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>gateway</title>
<style type="text/css">
.box{border:1px solid #ddd;border-radius:2px;padding:10px;}
</style>
<script type="text/javascript" src="/webjars/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#btn-save").click(function () {
$.ajax({
url:'/dispatch',
type:'POST',
data:$("#name").val(),
dataType:'json',
headers:{'Content-Type':"application/json"},
success:function(data){
console.log(data);
data = data.map(function(item,i){
return item.name;
});
$("#user-list").html(data.join("|"));
}
});
});
$("#btn-get").click(function () {
$.ajax({
url:'/get',
type:'GET',
success:function(data){
$("#messagelist").html(data);
}
});
});
});
</script>
</head>
<body>
<h2>hello,springcloud</h2>
<div id="container">
<div class="box">
<label>username</label>
<input type="text" name="name" id="name"/>
<input type="button" id="btn-save" value="save"/>
<div id="user-list"></div>
</div>
<div class="box">
<input type="button" id="btn-get" value="get message"/>
<div id="messagelist"></div>
</div>
</div>
</body>
</html>
启动gateway项目,没有报错,那么启动成功。可以在服务发现管理页面看到gateway服务也加入了进来:
我们按照服务暴露的访问地址访问index.html页面。
点击get message按钮,第一次可能会显示ribbon service broker,第二次显示了正确的信息:
在第一个输入框中输入beijing,点击save按钮,返回结果显示user service broker。其实已经存入成功,只不过返回了备选方法的结果。
我们再次输入shanghai,点击save按钮,页面上显示了所有用户名:
这个示例很好的展示了springcloud作为微服务框架给我们带来的体验。 gateway只是一个客户端请求统一入口,真正干活的是feign服务和ribbon服务。到这里一个最简单的springcloud示例就展示了,最后还有一个monitor服务,如果看代码不明白,可以看看springcloud入门系列的其他项目: