基于springmvc实现自己的网关

看完zuul 核心源码之后 我们自己实现一个zuul网关

  1. 思路梳理
    第一个组件:zuul 核心ZuulHandlerMapping 需要继承springmvc的AbstractUrlHandlerMapping 重写里面 lookupHandler方法 方法里面需要完成对路由的加载
    第二个组件:zuul的 Controller 可以继承springmvc的ServletWrappingController但是引入ZuulServlet 或者直接实现AbstractController 重写里面的handleRequestInternal 方法
    第三个组件:Properties 定义路由信息
    第四个组件:RestTemplate实现http调用并设置LoadBalancerInterceptor拦截器 完成负载均衡( @LoadBalanced 原理)

项目结构
在这里插入图片描述

  1. 代码实现

    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;
    }
}