目录

Spring总体结构(简述)

spring处理结构

路径匹配规则

Spring处理Http请求流程(代码详述)

1、Filter过滤器

2、SpringMVC首先通过对Dispatcher配置,处理"/"的请求

3、进入DispatcherServlet处理请求

处理流程简述

4、根据HandlerMapping#getHandler()来找到对应的执行链(HandlerExecutionChain),包括Handler和Interceptor[]数组,(Interceptor[]是通过InterceptorRegistry类维护的,并根据HandlerMethod的url返回对应匹配的Interceptor[])

路径匹配规则

5、HandlerExectionChain便利interceptor[]

6、关于切面@Aspect

7、根据Handler获取对应的HandlerAdapter

一些问题

为什么不直接用Handler而是包一层HandlerAdapter(从反射的角度理解)?

Filter,Interceptor和Aspect的区别?

示例:自定义注解实现登录认证(通过Interceptor)

1、首先自定义出注解

2、定义Interceptor

3、将定义的Interceptor注册到InterceptorRegistry 中。InterceptorRegistry 会负责将这些拦截器注册到请求的 HandlerExecutionChain的Interceptor[] 中

4、使用上自定义注解


Spring总体结构(简述)

spring处理结构

  1. Filter(过滤器):属于Servelt规范,最早被触发,用于全局请求预处理,常用跨域处理、日志打印、请求加密解密,权限校验。
  2. Servlet(DispatcherServlet):SpringMVC的所有请求会转到这里,用doDispatch()方法找到适配器等
  3. Interceptor(拦截器):不依赖Servlet容器,处理链的一部分(HandlerExecutionChain),可以在Controller前后进行处理。
    public class LoginInterceptor implements HandlerInterceptor {
        public boolean preHandle(...)   // 请求前(可中断)
        public void postHandle(...)     // Controller 返回后
        public void afterCompletion(...)// 完成后(资源清理)
    }
    
  4. Aspect(切面):基于动态代理,横切增强任何Bean的方法调用,包括Controller、Service等,常用于日志、权限、事务等。
    @Aspect
    @Component
    public class LogAspect {
        @Before("execution(* com.xxx.controller..*(..))")
        public void before(JoinPoint joinPoint) {
            System.out.println("切面:调用前");
        }
    }
    
  5. Controller(控制器):执行业务逻辑的地方,被HandlerAdapter调用。
  6. 特别注意易搞混!:Handler是被@GetMapping等注解标注的一个方法,而不是整个Controller类,HandlerMapping是将对应的方法转为已给HandlerMethod的对象并与url进行映射。但调用对应HandlerMethod的时候是需要拿到对应Controller的实例的。

路径匹配规则

1、精准匹优先(/user/list)

2、路径变量其次(/user/{id})

3、单通配符(/user/*)

4、双通配符(/user/**)

Spring处理Http请求流程(代码详述)

1、Filter过滤器

由Servlet容器执行,例如Tomcat。例如下述自定义过滤器。

@WebFilter("/*")
public class MyFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("👉 Filter before DispatcherServlet");
        chain.doFilter(request, response);
        System.out.println("👈 Filter after DispatcherServlet");
    }
}

2、SpringMVC首先通过对Dispatcher配置,处理"/"的请求

spring.mvc.servlet.path=/

之后SpringBoot的配置类 DispatcherServletAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@Conditional({DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
    protected DispatcherServletRegistrationConfiguration() {
    }

    @Bean(
        name = {"dispatcherServletRegistration"}
    )
    @ConditionalOnBean(
        value = {DispatcherServlet.class},
        name = {"dispatcherServlet"}
    )
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
        registration.setName("dispatcherServlet");
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        Objects.requireNonNull(registration);
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
}

@Configuration(
    proxyBeanMethods = false
)
@Conditional({DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
protected static class DispatcherServletConfiguration {
    protected DispatcherServletConfiguration() {
    }

    @Bean(
        name = {"dispatcherServlet"}
    )
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        this.configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet);
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    private void configureThrowExceptionIfNoHandlerFound(WebMvcProperties webMvcProperties, DispatcherServlet dispatcherServlet) {
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
    }

    @Bean
    @ConditionalOnBean({MultipartResolver.class})
    @ConditionalOnMissingBean(
        name = {"multipartResolver"}
    )
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        return resolver;
    }
    }

上述语句使得所有的请求都打到DispatcherServlet上:org.springframework.web.servlet.DispatcherServlet

3、进入DispatcherServlet处理请求

DispatcherServlet#doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 检查是否是Multipart请求
            processedRequest = this.checkMultipart(request);
            multipartRequestParsed = processedRequest != request;  // 标记是否为multipart请求

            // 获取请求的处理器
            mappedHandler = this.getHandler(processedRequest);
            if (mappedHandler == null) {
                this.noHandlerFound(processedRequest, response);
                return;
            }

            // 获取处理器适配器
            HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

            // 处理GET或HEAD请求的缓存
            String method = request.getMethod();
            if (HttpMethod.GET.matches(method) || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && HttpMethod.GET.matches(method)) {
                    return;  // 如果缓存未修改,直接返回
                }
            }

            // 执行前置处理
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 执行请求处理并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // 检查是否为异步请求
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // 设置视图名称并执行后置处理
            this.applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);

        } catch (Exception ex) {
            dispatchException = ex;
        } catch (Throwable err) {
            dispatchException = new ServletException("Handler dispatch failed: " + err, err);
        }

        // 处理请求结果
        this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    } finally {
        // 异步请求的后续操作
        if (asyncManager.isConcurrentHandlingStarted()) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            asyncManager.setMultipartRequestParsed(multipartRequestParsed);
        } else if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
            this.cleanupMultipart(processedRequest);
        }
    }
}
处理流程简述
  1. 判断是否multipart请求:multipart请求通常包含文件数据 需要较大的内存和处理能力 尤其是当文件很大时。因此,提前检查是否是multipart请求可以避免不必要的解析操作,节省系统资源。
  2. 获取处理器Handler,从HandlerMapping中获取执行链,包括1、Handler对象 2、拦截器数组Interceptor[]
  3. 获取适配器HandlerAdapter
  4. 处理GETHEAD请求。因为这两类请求通常用于获取数据而不做什么改动,因此对这两种请求进行缓存,有效提高请求效率。
  5. 利用处理器适配器HandlerAdapter来执行Handler方法并返回ModelAndView给前端

4、根据HandlerMapping#getHandler()来找到对应的执行链(HandlerExecutionChain),包括Handler和Interceptor[]数组,(Interceptor[]是通过InterceptorRegistry类维护的,并根据HandlerMethod的url返回对应匹配的Interceptor[])

首先Spring启动时会扫描所有@Controller,将所有带有@RequestMapping注解的方法扫描出来建立一张URL->HandlerMethod的映射表。

注意,每个HandlerMethod对应的HandlerExecutionChain下的Interceptor[]是不一样的。可以参考下面的自定义注解实现登录认证的实例。因为每个Interceptor都可以被指定需要拦截的路径和不需要拦截的路径。

InterceptorRegistry会根据当前HandlerMethod对应的url去match出对应的Interceptor[]。

特别注意:Handler(HandlerMethod)是被@GetMapping等注解标注的一个方法,而不是整个Controller类,HandlerMapping是将对应的方法转为已给HandlerMethod的对象并与url进行映射。但调用对应HandlerMethod的时候是需要拿到对应Controller的实例的。

路径匹配规则

1、精准匹优先(/user/list)

2、路径变量其次(/user/{id})

3、单通配符(/user/*)

4、双通配符(/user/**)

注意单通配符只能匹配单层路径,比如(/user/*)能匹配(/user/abc)但不能匹配(/user/a/b),双通配符可以匹配任意层请求。

5、HandlerExectionChain便利interceptor[]

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
    return;
}

this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);

首先mappedHandler.applyPreHandle(processedRequest, response)这个方法,调用所有拦截器的preHandle(),运行所有拦截器的方法,只要其中一个返回了false,Controller就不会执行。例如,登录拦截器发现未登录。

public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < interceptors.length; i++) {
        HandlerInterceptor interceptor = interceptors[i];
        if (!interceptor.preHandle(request, response, handler)) {
            triggerAfterCompletion(request, response, null); // 执行 afterCompletion()
            return false;
        }
    }
    return true;
}

接着ha.handle(processedRequest, response, mappedHandler.getHandler())执行Handle的业务方法。

最后mappedHandler.applyPostHandle(processedRequest, response, mv),调用所有interceptor的postHandle()方法。

6、关于切面@Aspect

与interceptor和HandlerAdapter不同,切面基于动态代理实现,实在方法层面实现的拦截。例如:

@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Before method");
        Object result = pjp.proceed(); // 执行目标方法
        System.out.println("After method");
        return result;
    }
}

Spring 会为目标类生成一个代理类(JDK 动态代理或 CGLIB 子类),在调用该方法时会先进入代理类然后在执行方法。

7、根据Handler获取对应的HandlerAdapter

public class RequestMappingHandlerAdapter implements HandlerAdapter {
    //用来判断能否处理某个请求
    @Override
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }
    //用来处理请求
    @Override
    public ModelAndView handle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) throws Exception {
        // 强转为 HandlerMethod
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // 通过反射调用 controller 方法
        Object result = handlerMethod.getMethod().invoke(handlerMethod.getBean());

        // 封装为 ModelAndView 返回
        return new ModelAndView("viewName", "data", result);
    }
}

HandlerAdapter主要实现#supports和#handler方法。

利用support方法的InstanceOf来找对应的HandlerAdapter,然后调用方法返回ModelAndView

一些问题

为什么不直接用Handler而是包一层HandlerAdapter(从反射的角度理解)?

首先利用HanlerMapping根据路径获取到了Handler,然后用instanceOf来找对应的HandlerAdapter。

下面为简单的代码逻辑。

public class RequestMappingHandlerAdapter implements HandlerAdapter {
    //用来判断能否处理某个请求
    @Override
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;
    }
    //用来处理请求
    @Override
    public ModelAndView handle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) throws Exception {
        // 强转为 HandlerMethod
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // 通过反射调用 controller 方法
        Object result = handlerMethod.getMethod().invoke(handlerMethod.getBean());

        // 封装为 ModelAndView 返回
        return new ModelAndView("viewName", "data", result);
    }
}

上述为一个HandlerAdapter的简单代码。现在假设我们有两个HandlerAdapter,分别是MyControllerHandlerAdapter和MyHttpHandlerAdapter,他们两个都被注册到DispatcherSimulator方法的HandlerAdapters数组中。

public class DispatcherSimulator {

    // 模拟注册的适配器(像 Spring 一样有多个适配器)
    private List<HandlerAdapter> handlerAdapters = new ArrayList<>();

    public DispatcherSimulator() {
        handlerAdapters.add(new MyControllerHandlerAdapter());
        handlerAdapters.add(new MyHttpHandlerAdapter());
    }

    // 核心调度逻辑
    public void dispatch(Object handler) {
        for (HandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                adapter.handle(handler);
                return;
            }
        }
        throw new RuntimeException("❌ 没有找到合适的 HandlerAdapter");
    }

    public static void main(String[] args) {
        DispatcherSimulator dispatcher = new DispatcherSimulator();

        // 模拟两种请求类型
        Object controllerHandler = new MyControllerHandler();
        Object httpHandler = new MyHttpHandler();

        dispatcher.dispatch(controllerHandler); // 👉 MyControllerHandler: 处理请求
        dispatcher.dispatch(httpHandler);       // 👉 MyHttpHandler: 执行 service()
    }
}

从上面的代码可以看到,在有新请求到达时,会依次调用HandlerAdapters数组中每个HandlerAdapter的support方法来判断是否能处理该请求(),如果能处理则调用对应HandlerAdapter的handle方法。

Filter,Interceptor和Aspect的区别?

机制

执行层面

拦截方式

作用域

常见用途

Filter

Servlet容器(Tomcat)

doFilter()+chain.doFilter()

所有请求

权限、跨域、编码、日志

Interceptor

SpringMVC(DispatcherServlet)

HandlerExectionChain内数组遍历

Handler请求,如Controller

登录校验、权限控制、计时

Aspect

Spring Bean

生成目标方法和类的代理类

代理的方法

日志,事务,统一异常处理

示例:自定义注解实现登录认证(通过Interceptor)

1、首先自定义出注解

@Target(ElementType.METHOD)             
@Retention(RetentionPolicy.RUNTIME)     
@Documented                                                    
public @interface LoginRequired {
    boolean required() default false;
}

@Target表示该注解的作用范围,此处指作用于方法上

@Retention表示该注解在什么时候对程序可见,此处是运行时

@Document表示该注解会出现在JavaDoc中

2、定义Interceptor

public class LoginInterceptor implements HandlerInterceptor{
    private static final Logger LOGGER= LoggerFactory.getLogger(LoginInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.判断是否Handler类方法
        if(!(handler instanceof HandlerMethod)){
            LOGGER.info("不是HandlerMethod类型,则无需检查");
            return true;
        }
        //2.强制转型
        HandlerMethod method = (HandlerMethod)handler;
        //3.判断该Handler的方法上是否存在注解
        boolean hasLoginAnnotation=method.getMethod().isAnnotationPresent(LoginRequired.class);
        if(!hasLoginAnnotation){
            //不存在LoginRequired注解,则直接通过
            LOGGER.info("不存在LoginRequired注解,则直接通过");
            return true;
        }
        LoginRequired loginRequired=method.getMethod().getAnnotation(LoginRequired.class);
        //2.required=false,则无需检查是否登录
        if(!loginRequired.required()){
            LOGGER.info("required=false,则无需检查是否登录");
            return true;
        }
        //3.登录状态检查,使用response返回指定信息
        LOGGER.info("required=true,需检查是否登录");
        response.getWriter().append("you need login!");
        return false;
    }
}
  1. 首先,判断当前传入的handler是否是HandlerMethod(即是否是Controller类中被指定了url的方法,用@GetMapping等注解了的方法会被HandlerMapping封装为一个HandlerMethod对象)。(例如,存在静态资源,Spring处理器等,这些的Handler也可能为其他类型,比如 ResourceHttpRequestHandler,则会直接跳出 )。
  2. 判断handler为HandlerMethod类型后,将传入的Object类型的handler转为HandlerMethod类型。
  3. 利用反射机制,method.getMethod()返回一个Java.lang.reflect.Method的对象,用于获取方法的元信息(包括注解、参数、返回值类型等),之后判断我们写的注解是否在方法上出现。
  4. 若出现了我们的注解,则走一遍登录认证的逻辑。

3、将定义的Interceptor注册到InterceptorRegistry 中。InterceptorRegistry 会负责将这些拦截器注册到请求的 HandlerExecutionChain的Interceptor[] 中

@Configuration
public class LoginConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
         .addPathPatterns("/**") // 拦截所有路径
        .excludePathPatterns("/login", "/register"); // 排除登录/注册接口
    }
}

4、使用上自定义注解

@RestController
public class LoginAction {
    private static final Logger LOGGER= LoggerFactory.getLogger(LoginAction.class);

    /**
     * 首页数据接口,无需登录
     * @return
     */
    @RequestMapping("index")
    public String index(){
        return "index";
    }

    /**
     * 签到接口(需要在登录状态下)
     * @param request
     * @return
     */
    @LoginRequired(required = true)
    @RequestMapping("signIn")
    public String signIn(HttpServletRequest request){
        //进行签到逻辑
        return "success";
    }
}

参考:自定义注解(一)登录判断:自定义登录注解 + 拦截器实现_java 注解判断用户登录-CSDN博客
 

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐