Skip to content

支持访问多个URL路径前缀

更新: 12/31/2025, 6:43:00 AM   字数: 0 字   时长: 0 分钟

​  在SpringBoot中,server.servlet.context-path只能配置固定的访问路径前缀。如果需要同时支持多种访问路径前缀,可以通过过滤器修改请求url访问地址。

参考资料:

自定义Filter过滤器

UrlFilter

​  自定义的Filter必须实现javax.servlet.Filter接口,这个是Servlet的规范。

java
package com.shooter.springboot.common.filter;

import org.springframework.util.StringUtils;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class UrlFilter implements Filter {

    /**
    * 过滤器初始化
    */
    @Override
    public void init(FilterConfig filterConfig) {
    }

    /**
    * 过滤器销毁
    */
    @Override
    public void destroy() {
    }
   
    /**
    * 执行过滤操作
    */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 获取HttpServletRequest
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 通过过滤器链完成请求的执行
        chain.doFilter(rewriteUrl(httpRequest), response);
    }

    /**
     * 重新请求路径,并转发参数
     * */
    private HttpServletRequestWrapper rewriteUrl(HttpServletRequest httpRequest) {
        return new HttpServletRequestWrapper(httpRequest) {

            @Override
            public String getRequestURI() {
                return getRewriteUrl(httpRequest.getRequestURI());
            }

            /**
            * 调用 getRequestURI()方法重新拼接RequestURL
            */
            @Override
            public StringBuffer getRequestURL() {
                return new StringBuffer(getScheme() + "://" + getServerName() + ":" + getServerPort() + getRequestURI());
            }

            @Override
            public String getServletPath() {
                String newPath = getRewriteUrl(httpRequest.getContextPath() + httpRequest.getServletPath());
                if (StringUtils.hasText(newPath)) {
                    return newPath.substring(httpRequest.getContextPath().length());
                }
                return httpRequest.getServletPath();
            }

            /**
             * 请求路径重写逻辑
             * @param  原请求路径
             * @return 重写后的请求路径
             * */
            private String getRewriteUrl(String requestURI) {
                if (requestURI.contains("/api/")) {
                    // 将包含/api的请求地址修改后,在进行转发
                    return requestURI.replace("/api", "");
                }
                return requestURI;
            }
        };
    }
}

注册UrlFilter

(1)通过 @Bean 注解来配置

java
package com.shooter.springboot.common.config;

import com.shooter.springboot.common.filter.UrlFilter;
import lombok.val;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean registerFilter() {
        val registration = new FilterRegistrationBean();
        // 实例化Filter类
        registration.setFilter(new UrlFilter());
        // 指定url的匹配模式
        registration.addUrlPatterns("/*");
        // 过滤器名称
        registration.setName("UrlFilter");
        // 执行顺序
        registration.setOrder(1);
        return registration;
    }
}

(2)通过 @WebFilter 注解来配置

java
@Component
@WebFilter(urlPatterns = "/*", filterName = "UrlFilter")
public class UrlFilter implements Filter {

}

​  注意,@WebFilte注解是Servlet3.0的规范,并不是SpringBoot提供的。所以,还需在配置类中加@ServletComponetScan注解来指定扫描的包。

java
@SpringBootApplication
@ServletComponentScan("com.shooter.springboot.common.filter")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

​  另外,@WebFilter执行顺序是根据Filter类名字母顺序倒序执行,且@WebFilter指定的过滤器优先级都高于FilterRegistrationBean配置的过滤器。

测试用例

​  首先,将server.servlet.context-path的值 清空 或者 设置为 / ,然后,编写UserController控制器。

java
package com.shooter.springboot.module.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @GetMapping("/user")
    public String selectUser(@RequestParam String name){
        return name;
    }
}

浏览器测试

​  启动项目后,即可访问接口进行测试。

json
/* 通过http://127.0.0.1:8080/api/user访问结果 */
{"userName":"小米","age":12}

/* 通过http://127.0.0.1:8080/user访问结果 */
{"userName":"小米","age":12}

SpringBootTest

java
package com.shooter.springboot;

import lombok.val;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

@EnableWebMvc
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
class SpringBootStartApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getUser() throws Exception {
        val resultStr  = mockMvc.perform(MockMvcRequestBuilders.get("/user?name={name}","mi")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk()).andDo(print())
                .andReturn().getResponse().getContentAsString();
        Assertions.assertEquals("mi",resultStr);
    }

    @Test
    public void getUserApi() throws Exception {
        val resultStr  = mockMvc.perform(MockMvcRequestBuilders.get("/api/user?name={name}","mi")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk()).andDo(print())
                .andReturn().getResponse().getContentAsString();
        Assertions.assertEquals("mi",resultStr);
    }

}

附录

UrlFilter中直接修改Url

​  说明,这种方式虽然可以通过浏览器测试成功,但是无法使用MockMvc在单元测试中进行测试,原因参考这里 或者 这里

java
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class UrlFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig)  {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        String path = httpRequest.getRequestURI();
        if(path.contains("/api/")){
            // 将包含/api的请求地址修改后,在进行转发
            String newPath = path.replace("/api","");
            httpRequest.getRequestDispatcher(newPath).forward(request,response);
        } else {
            // 通过过滤器链完成请求的执行
            chain.doFilter(request,response);
        }
    }
}

更新时间:

Released under the MIT License.