Phân tích Nguyên lý Hoạt động của SpringMVC

Giới thiệu SpringMVC

SpringMVC là một framework web nhẹ dựa trên Spring, triển khai mô hình Web MVC, hoạt động theo cơ chế điều khiển yêu cầu. Nó áp dụng triết lý của kiến trúc MVC để tách biệt trách nhiệm ở tầng web và quản lý vòng đời của các đối tượng trong ứng dụng, mang lại sự tiện lợi lớn cho việc phát triển hàng ngày.

Các Thành phần Cốt lõi

  • DispatcherServlet: Bộ điều khiển trung tâm, điều phối việc gọi các thành phần khác, là trung tâm điều khiển của toàn bộ quá trình xử lý yêu cầu và phản hồi. Về bản chất, nó là một Servlet.
  • Handler: Bộ xử lý nghiệp vụ, xử lý yêu cầu cụ thể từ client và trả về kết quả xử lý. Thường tồn tại dưới dạng các Controller.
  • HandlerMapping: Bộ ánh xạ bộ xử lý, thiết lập mối quan hệ giữa URL yêu cầu của client và bộ xử lý nghiệp vụ. Dựa trên URL yêu cầu, nó có thể tìm thấy bộ xử lý nghiệp vụ tương ứng.
  • HandlerAdapter: Bộ điều hợp bộ xử lý, chịu trách nhiệm gọi phương thức cụ thể của bộ xử lý nghiệp vụ và trả về đối tượng ModelAndView (lượt xem logic).
  • ViewResolver: Bộ giải quyết lượt xem, chịu trách nhiệm phân tích đối tượng ModelAndView được trả về bởi bộ xử lý nghiệp vụ thành JSP.

Quy trình Hoạt động của SpringMVC

  1. Client gửi yêu cầu, tất cả các yêu cầu đều được bộ điều khiển trung tâm DispatcherServlet xử lý.
  2. DispatcherServlet sử dụng bộ ánh xạ bộ xử lý HandlerMapping để lấy đối tượng Handler (bộ xử lý nghiệp vụ) tương ứng dựa trên URL yêu cầu của client.
  3. DispatcherServlet gọi bộ điều hợp bộ xử lý HandlerAdapter, thông báo cho HandlerAdapter thực thi Handler cụ thể nào.
  4. HandlerAdapter gọi phương thức của Handler (Controller) cụ thể và nhận được kết quả trả về là ModelAndView, sau đó trả kết quả này về cho DispatcherServlet.
  5. DispatcherServlet chuyển ModelAndView cho ViewResolver để phân tích, sau đó trả về lượt xem thực tế.
  6. DispatcherServlet điền dữ liệu mô hình vào lượt xem.
  7. DispatcherServlet phản hồi kết quả cho người dùng.

Phân tích Mã nguồn SpringMVC

Quá trình Khởi động SpringMVC

SpringMVC cần cấu hình DispatcherServlet trong web.xml, ví dụ như sau:

<servlet>
     springmvc
     org.springframework.web.servlet.DispatcherServlet
      1
      
            contextConfigLocation
            classpath*:springmvc.xml
      
</servlet>

     springmvc
     /

Theo kiến thức về servlet, tất cả các yêu cầu đều được chuyển đến DispatcherServlet, và khi dự án khởi động, DispatcherServlet sẽ được tạo và thực thi phương thức khởi tạo init. DispatcherServlet kế thừa FrameworkServlet, FrameworkServlet kế thừa HttpServletBean, HttpServletBean triển khai phương thức init của HttpServlet, thực tế là thực thi phương thức initServletBean, phương thức này được lớp con FrameworkServlet ghi đè. FrameworkServlet ghi đè phương thức initServletBean với logic như sau:

protected final void initServletBean() throws ServletException {
    try {
        // 1. Khởi tạo container Spring Web
        this.webApplicationContext = initWebApplicationContext();
        // 2. Khởi tạo Servlet framework, là một phương thức trống, để lớp con mở rộng
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
}

protected WebApplicationContext initWebApplicationContext() {

    // 1. Cố gắng lấy WebApplicationContext
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    // 2. Nếu không có WebApplicationContext hiện tại, hãy khởi tạo và làm mới WebApplicationContext
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            // 3. Sau khi WebApplicationContext được khởi tạo và làm mới, thực thi phương thức onRefresh
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}
Phương thức createWebApplicationContext được sử dụng để tạo container IOC WebApplicationContext và khởi động làm mới container. Sau khi container Spring khởi động, phương thức onRefresh được thực thi để làm mới Servlet. Logic khởi động và làm mới container Spring không được xem xét chi tiết, phương thức onRefresh thực tế được chuyển cho lớp con DispatcherServlet triển khai. Mã nguồn của phương thức onRefresh trong DispatcherServlet như sau:

// Phương thức onRefresh của DispatcherServlet
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    // Khởi tạo bộ ánh xạ bộ xử lý HandlerMapping
    initHandlerMappings(context);
    // Khởi tạo bộ điều hợp bộ xử lý handlerAdapter
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    // Khởi tạo bộ giải quyết lượt xem ViewResolver
    initViewResolvers(context);
    initFlashMapManager(context);
}
Có thể thấy phương thức onRefresh chủ yếu là khởi tạo các thành phần liên quan, chẳng hạn như khởi tạo bộ ánh xạ bộ xử lý nghiệp vụ HandlerMapping, bộ điều hợp bộ xử lý HandlerAdapter, bộ giải quyết lượt xem ViewResolver, v.v. Ở đây, chúng ta tập trung phân tích quá trình khởi tạo HandlerMapping và HandlerAdapter.

Khởi tạo Bộ ánh xạ Bộ xử lý

Đầu tiên, xem quá trình khởi tạo bộ ánh xạ bộ xử lý, phương thức là initHandlerMapping(ApplicationContext context) của DispatcherServlet, mã nguồn như sau:

// Khởi tạo bộ ánh xạ bộ xử lý
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    // 1. Cố gắng lấy tất cả các HandlerMapping từ container Spring
    if (this.detectAllHandlerMappings) {
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    // 2. Nếu không có HandlerMapping trong container Spring, hãy khởi tạo HandlerMapping mặc định
    if (this.handlerMappings == null) {
        // Khởi tạo HandlerMapping mặc định
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

// Lấy chiến lược mặc định
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    // 1. Lấy chiến lược mặc định từ tệp cấu hình
    // Tệp cấu hình là DispatcherServlet.properties
    // key là đường dẫn đầy đủ của lớp chiến lược
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                // 2. Khởi tạo tất cả các instance chiến lược bằng phản xạ
                Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
            }
            catch (LinkageError err) {
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}
Đầu tiên, cố gắng lấy tất cả các bean HandlerMapping từ container Spring, nếu không tồn tại, hãy tải bộ ánh xạ bộ xử lý mặc định. Phương thức getDefaultStrategies lấy cấu hình mặc định từ tệp DispatcherServlet.properties, nội dung tệp như sau:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
Có thể thấy HandlerMapping mặc định là BeanNameUrlHandlerMapping, RequestMappingHandlerMapping và RouterFunctionMapping. Lấy RequestMappingHandlerMapping làm ví dụ, nó triển khai InitializingBean, vì vậy sau khi khởi tạo, phương thức afterPropertiesSet sẽ được thực thi. Phương thức này khởi tạo thuộc tính và gọi phương thức afterPropertiesSet của lớp cha AbstractHandlerMapping, phương thức này lại thực thi phương thức initHandlerMethods. Phương thức initHandlerMethods của AbstractHandlerMapping về cơ bản là khởi tạo các phương thức ánh xạ, logic như sau:

// Khởi tạo ánh xạ giữa đường dẫn và phương thức
protected void initHandlerMethods() {

    // 1. Duyệt qua tất cả các bean trong container Spring
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // 2. Xử lý tất cả các bean tiếp theo
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

protected void processCandidateBean(String beanName) {
    Class beanType = null;
    try {
        // 1. Lấy đối tượng Class của bean
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {
    }
    // 2. Gọi phương thức isHandler để xác định xem bean có phải là bộ xử lý nghiệp vụ không
    if (beanType != null && isHandler(beanType)) {
        // 2. Tìm các phương thức ánh xạ trong bộ xử lý nghiệp vụ
        detectHandlerMethods(beanName);
    }
}

// Xác định xem bean có phải là bộ xử lý nghiệp vụ (được chú thích bởi @Controller hoặc @RequestMapping) không
protected boolean isHandler(Class beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
Duyệt qua tất cả các bean trong container Spring, xác định xem bean có chứa chú thích @Controller hoặc @RequestMapping không, nếu có thì đó là bộ xử lý nghiệp vụ, sau đó thực thi phương thức detectHandlerMethods để xử lý, logic của phương thức này như sau:

protected void detectHandlerMethods(Object handler) {
    // 1. Lấy đối tượng Class của bộ xử lý
    Class handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        Class userType = ClassUtils.getUserClass(handlerType);
        // 2. Tìm tất cả các tập hợp phương thức ánh xạ, lưu vào Map
        // RequestMappingInfo là lớp ánh xạ phương thức
        Map methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        return getMappingForMethod(method, userType);
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                userType.getName() + "]: " + method, ex);
                    }
                });
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            // Đăng ký bộ xử lý, phương thức, ánh xạ, lưu vào Map trong instance MappingRegistry
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}
Đầu tiên, tìm tất cả các phương thức ánh xạ từ bộ xử lý nghiệp vụ, đóng gói chúng thành instance ánh xạ, sau đó đăng ký bộ xử lý, phương thức và instance ánh xạ vào instance MappingRegistry.

Khởi tạo Bộ điều hợp Bộ xử lý

Quá trình khởi tạo bộ điều hợp bộ xử lý HandlerAdapter tương tự như quá trình khởi tạo bộ ánh xạ bộ xử lý HandlerMapping, cuối cùng dẫn đến việc khởi tạo bộ điều hợp mặc định RequestMappingHandlerAdapter, cuối cùng là ghi đè phương thức afterPropertiesSet.

Quy trình Hoạt động của SpringMVC

Quy trình làm việc của Servlet thực tế là thực thi phương thức service sau khi nhận được yêu cầu từ client, vì vậy điểm vào xử lý yêu cầu của SpringMVC là phương thức service của DispatcherServlet. Phương thức service của DispatcherServlet thực thi phương thức service của lớp cha FrameworkServlet. Phương thức này lại thực thi phương thức processRequest của FrameworkServlet, cuối cùng gọi phương thức doService của lớp con. Phương thức doService của DispatcherServlet là phương thức cốt lõi xử lý logic nghiệp vụ, mã nguồn như sau:

// Phương thức xử lý nghiệp vụ của DispatcherServlet
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 1. Chụp nhanh tham số yêu cầu, lưu tạm các tham số yêu cầu
    Map attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // 2. Thêm cấu hình vào tham số yêu cầu
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    try {
        // 3. Thực thi phân phối xử lý yêu cầu
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}
Đầu tiên, xử lý tham số, sau đó gọi phương thức doDispatch để phân phối, logic như sau:

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 {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // 1. Dựa trên yêu cầu, truy vấn bộ xử lý nghiệp vụ cụ thể từ HandlerMapping
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 2. Dựa trên bộ xử lý nghiệp vụ, truy vấn bộ điều hợp bộ xử lý nghiệp vụ tương ứng
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 3. Gọi phương thức handle của bộ điều hợp bộ xử lý để xử lý logic nghiệp vụ cụ thể
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            //...
        }
        // 4. Xử lý kết quả thực thi yêu cầu
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
        //...
    } finally {
        //...
    }
}
Logic cốt lõi khá rõ ràng: trước tiên, truy vấn bộ xử lý nghiệp vụ tương ứng từ bộ ánh xạ bộ xử lý, sau đó tìm bộ điều hợp bộ xử lý tương ứng dựa trên bộ xử lý nghiệp vụ, sau đó gọi phương thức handle của bộ điều hợp để xử lý nghiệp vụ, cuối cùng thực thi phương thức processDispatchResult để xử lý kết quả xử lý yêu cầu. getHandler là logic tìm bộ xử lý phù hợp từ tập hợp handlerMappings; getHandlerAdapter là logic tìm bộ điều hợp tương ứng từ tập hợp handlerAdapters; handle là logic thực thi phương thức của bộ xử lý tương ứng bằng cơ chế phản xạ; processDispatchResult là logic đóng gói kết quả thực thi thành đối tượng ModelAndView.

Thẻ: SpringMVC DispatcherServlet HandlerMapping HandlerAdapter MVC

Đăng vào ngày 18 tháng 5 lúc 09:12