Nacos1.0.0 GA版本終于出來(lái)了预麸,其中的配置中心非常適合來(lái)配置路由信息酪耳,我們希望把所有的路由信息配置到Nacos中粟瞬,并且一旦有變化能夠及時(shí)變更
構(gòu)建一個(gè)服務(wù)提供者
該項(xiàng)目注釋簡(jiǎn)單的提供一個(gè)測(cè)試接口叫榕,并且把自己注冊(cè)到Nacos當(dāng)中嚼松,其中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>
編寫(xiě)一個(gè)配置文件
application.properties
server.port=18080
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=192.168.5.126:80
編寫(xiě)一個(gè)主運(yùn)行類,打上注解即可
@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;
}
}
}
到此為止袜炕,服務(wù)提供者已經(jīng)完成本谜。接下來(lái)開(kāi)始正式的Zuul集成
構(gòu)建Zuul項(xiàng)目
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>
<!--注意: 這里必須要添加偎窘,否則各種依賴有問(wèn)題 -->
<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>
編寫(xiě)配置文件
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
編寫(xiě)主運(yùn)行類
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
EnableZuulProxy注解開(kāi)啟了Zuul網(wǎng)關(guān)服務(wù)
靜態(tài)類
我們的配置存在nacos上乌助,就需要data_id和group_id,為了方便陌知,把這兩個(gè)參數(shù)放到靜態(tài)類的常量中他托,這里使用接口來(lái)
public interface Constant {
public static final String NACOS_DATA_ID="zuul-server";
public static final String NACOS_GROUP_ID="zuul_route";
}
配置類-nacos配置服務(wù)器
@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() {
//可以發(fā)送監(jiān)聽(tīng)消息到某個(gè)MQ
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("Nacos更新了!");
//切忌F推稀I筒巍志笼!不需要自己去刷新
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
});
return configService;
} catch (NacosException e) {
e.printStackTrace();
}
return null;
}
}
配置類-zuul配置
/**
* 配置路由和監(jiān)聽(tīng)器
*/
@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刷新監(jiān)聽(tīng)器
/**
* @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) {
//設(shè)置為臟,下一次匹配到路徑時(shí),如果發(fā)現(xiàn)為臟把篓,則會(huì)去刷新路由信息
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());
// 優(yōu)化一下配置
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;
}
}
實(shí)體類
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配置中建立一個(gè)配置纫溃,data_id 和 group_id 分別是 zuul-server
和 zuul_route
[圖片上傳失敗...(image-8f511f-1558663873510)]
內(nèi)容區(qū)域?yàn)?/p>
[
{
"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
}
]
測(cè)試,可以開(kāi)始測(cè)試韧掩,并且修改nacos的配置再測(cè)試紊浩,可以看到實(shí)現(xiàn)了動(dòng)態(tài)路由
可以開(kāi)啟多個(gè)Provider,來(lái)測(cè)試LB的效果疗锐。