自实现网关-基于springmvc实现自己的网关

本文详细介绍了如何基于Zuul的核心源码,自定义一个SpringBoot的网关,包括路由注册、控制器设计、过滤器组件、负载均衡和配置管理,以及相关的代码实现和依赖管理。
摘要由CSDN通过智能技术生成

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

  1. 思路梳理
    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 实现过滤器

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

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

最新代码已经放到 gitee上面 了 https://gitee.com/wang_1009654487/my-zuul

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值