Nacos1.0.0 GA版本终于出来了,其中的配置中心非常适合来配置路由信息,我们希望把所有的路由信息配置到Nacos中,并且一旦有变化能够及时变更
构建一个服务提供者
该项目注释简单的提供一个测试接口,并且把自己注册到Nacos当中,其中Pom文件如下
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
编写一个配置文件
application.properties
server.port=18080
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=192.168.5.126:80
编写一个主运行类,打上注解即可
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@RestController
class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
return "receive " + string;
}
}
}
到此为止,服务提供者已经完成。接下来开始正式的Zuul集成
构建Zuul项目
Pom文件
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<!-- 利用传递依赖,公共部分 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>0.2.3-RC1</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--注意: 这里必须要添加,否则各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
编写配置文件
bootstrap.properties
nacos.address=192.168.5.126:80
spring.cloud.nacos.discovery.server-addr=${nacos.address}
spring.cloud.nacos.config.server-addr=${nacos.address}
spring.application.name: zuul-server
server.port: 8888
编写主运行类
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
EnableZuulProxy注解开启了Zuul网关服务
静态类
我们的配置存在nacos上,就需要data_id和group_id,为了方便,把这两个参数放到静态类的常量中,这里使用接口来
public interface Constant {
public static final String NACOS_DATA_ID="zuul-server";
public static final String NACOS_GROUP_ID="zuul_route";
}
配置类-nacos配置服务器
@Configuration
public class NacosServerConfig {
@Value("${spring.cloud.nacos.config.server-addr:127.0.0.1:8848}")
private String serverAddr;
@Autowired
NewZuulRouteLocator routeLocator;
@Autowired
ApplicationEventPublisher publisher;
@Bean
public ConfigService configService(){
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
try {
ConfigService configService = NacosFactory.createConfigService(properties);
configService.addListener(NACOS_DATA_ID, NACOS_GROUP_ID, new Listener() {
@Override
public Executor getExecutor() {
//可以发送监听消息到某个MQ
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("Nacos更新了!");
//切忌!!!不需要自己去刷新
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
});
return configService;
} catch (NacosException e) {
e.printStackTrace();
}
return null;
}
}
配置类-zuul配置
/**
* 配置路由和监听器
*/
@Configuration
public class NewZuulConfig {
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private ServerProperties serverProperties;
/**
*
* 功能描述:
*
* @param:
* @return:
* @auther: tangshun
* @date: 2019/4/17 20:18
*/
@Bean
public NewZuulRouteLocator routeLocator() {
NewZuulRouteLocator routeLocator = new NewZuulRouteLocator(
this.serverProperties.getServlet().getServletPrefix(), this.zuulProperties);
return routeLocator;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
}
Zuul刷新监听器
/**
* @ClassName ZuulRefreshListener
* @Author Tangshun
* @Description //TODO
* @Date 10:42 2019/4/18
* @Version 1.0.0
**/
public class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent) {
//设置为脏,下一次匹配到路径时,如果发现为脏,则会去刷新路由信息
this.zuulHandlerMapping.setDirty(true);
}else if (event instanceof HeartbeatEvent) {
if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
this.zuulHandlerMapping.setDirty(true);
}
}
}
}
核心类
public class NewZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
@Autowired
private ZuulProperties properties;
@Autowired
private PropertiesAssemble propertiesAssemble;
public NewZuulRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.properties = properties;
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
// 从application.properties中加载路由信息
routesMap.putAll(super.locateRoutes());
// 从Nacos中加载路由信息
routesMap.putAll(propertiesAssemble.getProperties());
// 优化一下配置
LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
// Prepend with slash if not already present.
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
values.put(path, entry.getValue());
}
return values;
}
}
实体类
public class ZuulRouteEntity {
/**
* The ID of the route (the same as its map key by default).
*/
private String id;
/**
* The path (pattern) for the route, e.g. /foo/**.
*/
private String path;
/**
* The service ID (if any) to map to this route. You can specify a
* physical URL or a service, but not both.
*/
private String serviceId;
/**
* A full physical URL to map to the route. An alternative is to use a
* service ID and service discovery to find the physical address.
*/
private String url;
/**
* Flag to determine whether the prefix for this route (the path, minus
* pattern patcher) should be stripped before forwarding.
*/
private boolean stripPrefix = true;
/**
* Flag to indicate that this route should be retryable (if supported).
* Generally retry requires a service ID and ribbon.
*/
private Boolean retryable;
private String apiName;
private Boolean enabled;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isStripPrefix() {
return stripPrefix;
}
public void setStripPrefix(boolean stripPrefix) {
this.stripPrefix = stripPrefix;
}
public Boolean getRetryable() {
return retryable;
}
public void setRetryable(Boolean retryable) {
this.retryable = retryable;
}
public String getApiName() {
return apiName;
}
public void setApiName(String apiName) {
this.apiName = apiName;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
组装类
@Component
public class PropertiesAssemble{
@Autowired
private ConfigService configService;
public Map<String, ZuulRoute> getProperties() {
Map<String, ZuulRoute> routes = new LinkedHashMap<>();
List<ZuulRouteEntity> results = listenerNacos(NACOS_DATA_ID,NACOS_GROUP_ID);
for (ZuulRouteEntity result : results) {
if (StringUtils.isBlank(result.getPath())
/*|| org.apache.commons.lang3.StringUtils.isBlank(result.getUrl())*/) {
continue;
}
ZuulRoute zuulRoute = new ZuulRoute();
try {
BeanUtils.copyProperties(result, zuulRoute);
} catch (Exception e) {
}
routes.put(zuulRoute.getPath(), zuulRoute);
}
return routes;
}
private List<ZuulRouteEntity> listenerNacos (String dataId, String group) {
try {
String content = configService.getConfig(dataId, group, 5000);
System.out.println("从Nacos返回的配置:" + content);
return JSONObject.parseArray(content, ZuulRouteEntity.class);
} catch (NacosException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
}
nacos配置
在nacos配置中建立一个配置,data_id 和 group_id 分别是 zuul-server
和 zuul_route
[图片上传失败...(image-8f511f-1558663873510)]
内容区域为
[
{
"enable":true,
"id":"baidu",
"path":"/baidu/**",
"retryable":false,
"stripPrefix":true,
"url":"http://www.baidu.com"
}, {
"enable":true,
"id":"163",
"path":"/163/**",
"retryable":false,
"stripPrefix":true,
"url":"https://www.163.com"
},
{
"enable":true,
"id":"service-provider",
"serviceId":"service-provider",
"path":"/provider/**",
"retryable":false,
"stripPrefix":true
}
]
测试,可以开始测试,并且修改nacos的配置再测试,可以看到实现了动态路由
可以开启多个Provider,来测试LB的效果。