看完zuul 核心源码之后 我们自己实现一个zuul网关
-
思路梳理
1, 基本上就是 多个 模糊匹配的url(核心的就是mvc中的AbstractUrlHandlerMapping) -> 对应一个 controller 在controller中 写自己的过滤器实现2,我们需要做哪些事情:
第一个 我们如何将自己的 路由信息 注册到mvc中 zuul中是在 请求过来 lookup()方法中
调用registerHandlers()方法注册的路由信息,类似于懒加载 但是这个方法弊端就是 第一次请求会很慢 因为要记载路由信息,可以参考SimpleUrlHandlerMapping中initApplicationContext() 方法 在初始化applicationContext容器中 加在自己的路由信息
第二个 请求过来之后 因为tomcat是开了多线程调用 线程安全如何保证 zuu中的RequestContext 继承了 ConcurrentHashMap 里面有个TheardLocal做存储,相当于每一个线程对应一个 ConcurrentHashMap对象
第三个 自定义自己的过滤器组件,可以使用springboot自动装配,这一篇只讲springmvc继承模块,下一片会讲过滤器组件封装
3,组件相关:
第一个组件:zuul 核心ZuulHandlerMapping 需要继承springmvc的AbstractUrlHandlerMapping 重写里面 lookupHandler方法 方法里面需要完成对路由的加载
第二个组件:zuul的 Controller 可以继承springmvc的ServletWrappingController但是引入ZuulServlet 或者直接实现AbstractController 重写里面的handleRequestInternal 方法
第三个组件:Properties 定义路由信息
第四个组件:RestTemplate实现http调用并设置LoadBalancerInterceptor拦截器 完成负载均衡( @LoadBalanced 原理)
第五个组件:自定义 starter 实现过滤器
项目结构
-
代码实现
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<groupId>org.example</groupId>
<artifactId>MyZuul</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<discovery.version>6.0.7</discovery.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.3.RELEASE-SR2</version>
</dependency>
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-common</artifactId>
<version>${discovery.version}</version>
</dependency>
</dependencies>
</project>
spring application.yml配置文件
my:
gateway:
map:
test:
path: /api/mytes01/**
service-id: mytest01
login:
path: /api/test02/**
service-id: mytest02
spring:
cloud:
nacos:
server-addr: nacos地址
application:
name: my-test
server:
port: 8085
主启动类:
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SpringMain {
public static void main(String[] args) {
SpringApplication.run(SpringMain.class);
}
}
配置类:
package org.example.config;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
@Configuration
public class MyConfig {
@Bean(name = "myRestTemplate")
public RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(loadBalancerInterceptor));
return restTemplate;
}
}
contoller
package org.example.contorller;
import org.example.filter.MyZuulServlet;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.ServletWrappingController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyZuulController extends ServletWrappingController {
public MyZuulController() {
setServletClass(MyZuulServlet.class);
setServletName("my");
setSupportedMethods((String[]) null);
}
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
return super.handleRequestInternal(request, response);
}
}
servlet 此类中可以自己实现业务逻辑 这里只使用 RestTemplate和loadBalancerInterceptor 结合nacos 完成了简单的get请求负载均衡调用
package org.example.filter;
import lombok.extern.slf4j.Slf4j;
import org.example.helper.ApplicationHelper;
import org.example.properties.RoutesProperties;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
@Slf4j
public class MyZuulServlet extends HttpServlet {
private static final long serialVersionUID = -3374242278843351500L;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
//实现http调用
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
RestTemplate restTemplate = ApplicationHelper.getBeanName("myRestTemplate", RestTemplate.class);
RoutesProperties zuulProperties = ApplicationHelper.getBean(RoutesProperties.class);
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String reqUrl = httpServletRequest.getRequestURI();
Map<String, RoutesProperties.MyRoute> map = zuulProperties.getMap();
Collection<RoutesProperties.MyRoute> values = map.values();
for (RoutesProperties.MyRoute value : values) {
String path = value.getPath();
if (antPathMatcher.match(path, reqUrl)) {
String url = value.getUrl();
String serviceId = value.getServiceId();
if (StringUtils.hasText(url)) {
String forObject = restTemplate.getForObject(url, String.class);
httpServletResponse.getOutputStream().write(forObject.getBytes(StandardCharsets.UTF_8));
} else if (StringUtils.hasText(serviceId)) {
String replace = path.replace("/**", "");
String s = reqUrl.replaceAll(replace, "");
String invokeUrl = "http://" + serviceId + s;
StringBuilder stringBuilder = new StringBuilder(invokeUrl);
Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
while (parameterNames.hasMoreElements()) {
String s1 = parameterNames.nextElement();
String v = httpServletRequest.getParameter(s1);
if (stringBuilder.indexOf("?") <= 0) {
stringBuilder.append("?").append(s1).append("=").append(v);
} else {
stringBuilder.append("&").append(s1).append("=").append(v);
}
}
String forObject = restTemplate.getForObject(stringBuilder.toString(), String.class);
if (StringUtils.hasText(forObject)) {
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.getOutputStream().write(forObject.getBytes(StandardCharsets.UTF_8));
}
}
}
}
}
}
ApplicationHelper 工具类 获取bean
package org.example.helper;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationHelper implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getBean(Class<T> tarClass) {
return applicationContext.getBean(tarClass);
}
public static <T> T getBeanName(String bn, Class<T> tarClass) {
return applicationContext.getBean(bn, tarClass);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationHelper.applicationContext = applicationContext;
}
}
properties 路由配置
package org.example.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Data
@Component
@ConfigurationProperties("my.gateway")
public class RoutesProperties {
private Map<String, MyRoute> map;
@Data
public static class MyRoute {
private String path;
private String url;
private String serviceId;
}
}
最新代码已经放到 gitee上面 了 https://gitee.com/wang_1009654487/my-zuul