看完zuul 核心源码之后 我们自己实现一个zuul网关
- 思路梳理
第一个组件:zuul 核心ZuulHandlerMapping 需要继承springmvc的AbstractUrlHandlerMapping 重写里面 lookupHandler方法 方法里面需要完成对路由的加载
第二个组件:zuul的 Controller 可以继承springmvc的ServletWrappingController但是引入ZuulServlet 或者直接实现AbstractController 重写里面的handleRequestInternal 方法
第三个组件:Properties 定义路由信息
第四个组件:RestTemplate实现http调用并设置LoadBalancerInterceptor拦截器 完成负载均衡( @LoadBalanced 原理)
项目结构
-
代码实现
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; } }