controller层-浅谈SpringMVC

我们都知道-Tomcat, 是一个web发布容器, 运行在当前服务器上, 且监听指定端口(也就是IP:Port), 那么一个http请求打进来以后,
是怎样进到服务里并且处理的呢?

1 ContextLoaderListener类

继承了ContextLoader 实现了 ServletContextListener

重点这个监听接口 ServletContextListener

public interface ServletContextListener extends EventListener {
    
    
	// 当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,
	// 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
    default void contextInitialized(ServletContextEvent sce) {
    
    
    }
	// 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
    default void contextDestroyed(ServletContextEvent sce) {
    
    
    }
}

2. Servlet

2.01 Servlet配置

web.xml 中的配置, 这里配置的意思就是实例化DispatcherServlet, 并且它的映射路径是/*, 也就是说http请求打进这个服务之后, 就会进入到这个DispatcherServlet.

<servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

2.02 Servlet源码

public interface Servlet {
	// tomcat通过反射创建servlet之后, 调用init方法 传入ServletConfig(包含了服务名称, 上下文, 参数信息)
    void init(ServletConfig var1) throws ServletException;
    ServletConfig getServletConfig();
	// 核心逻辑 tomcat解析请求 封装相关头以及参数数据
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    String getServletInfo();
	// 销毁前调用的方法
    void destroy();
}

从上述源码, 我们可以看的出来, 当一个http请求打进tomcat, 会先封装web.xml中的init param以及上下文数据到ServletConfig, 然后 封装请求头以及传入参数到ServletRequest以及响应相关数据封装到ServletResponse, 再执行service方法, 我们可以先猜测就是这个service方法走到了我们的controller层.

2.1 GenericServlet

// GenericServlet主要将ServletConfig提成了全局变量
private transient ServletConfig config;

2.2 HttpServlet

// 我们可以看的出来 HttpServlet主要是将service方法重写, 讲常见的get, post, delete等等请求 进入到对应的doGet, doPost等等里
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
    
    
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
    
    
            this.doGet(req, resp);
        } else {
    
    
            long ifModifiedSince;
            try {
    
    
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
    
    
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
    
    
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
    
    
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
    
    
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
    
    
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
    
    
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
    
    
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
    
    
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
    
    
        this.doTrace(req, resp);
    } else {
    
    
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{
    
    method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

2.3 HttpServletBean

实现了 EnvironmentCapable, EnvironmentAware接口 这个类主要是加载参数信息以及封装BeanWrapper

@Override
public final void init() throws ServletException {
    
    

	// 加载web.xml init parameters.
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
    
    
		try {
    
    
			// BeanWrapper设置pvs属性值
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
    
    
			if (logger.isErrorEnabled()) {
    
    
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// 子类扩展逻辑
	initServletBean();
}

2.4 FrameworkServlet

@Override
protected final void initServletBean() throws ServletException {
    
    
	...
	try {
    
    
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	}
	...
}

protected WebApplicationContext initWebApplicationContext() {
    
    
	// 获取root WebApplicationContext 也就是Spring IOC容器
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	// 定义SpringMVC上下文
	WebApplicationContext wac = null;

	// 	非第一次进入
	if (this.webApplicationContext != null) {
    
    
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
    
    
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
    
    
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
    
    
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				// 刷新容器
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
    
    
		// 在ServletContext中寻找是否有springMVC容器,初次运行是没有的
		wac = findWebApplicationContext();
	}
	if (wac == null) {
    
    
		// 如下
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
    
    
		// 如果没有收到刷新监听
		synchronized (this.onRefreshMonitor) {
    
    
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
    
    
		// 缓存起来 方便下次拿上下文
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    
    
	Class<?> contextClass = getContextClass();
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    
    
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
				"': custom WebApplicationContext class [" + contextClass.getName() +
				"] is not of type ConfigurableWebApplicationContext");
	}
	// 实例化一个容器对象
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	// 设置环境信息
	wac.setEnvironment(getEnvironment());
	// 设置父容器 也就是Spring IOC容器
	wac.setParent(parent);
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
    
    
		// SpringMVC的配置信息
		wac.setConfigLocation(configLocation);
	}
	// 刷新容器 完成加载
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    
    
	......
	
	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	// 发布容器刷新事件 onRefresh 最终会调到DispatcherServlet的onRefresh
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
    
    
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}

	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	wac.refresh();
}

2.5 DispatcherServlet源码

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
    
    
	initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
 // 初始化九大核心组件
protected void initStrategies(ApplicationContext context) {
    
    
	initMultipartResolver(context);// 文件解析
	initLocaleResolver(context);// 本地解析
	initThemeResolver(context);// 主题解析
	initHandlerMappings(context);// url映射解析
	initHandlerAdapters(context);// 初始化真正调用的方法
	initHandlerExceptionResolvers(context);// 异常解析
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);// 视图解析
	initFlashMapManager(context);
}

HttpServlet里面的doGet为例, 如果一个http请求打进这个项目, 那么就会走到processRequest里的doService方法.最终会走到DispatcherServlet的doService.

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
	logRequest(request);

	// Keep a snapshot of the request attributes in case of an include,
	// to be able to restore the original attributes after the include.
	Map<String, Object> 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));
			}
		}
	}

	// 设置web上下文
	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 {
    
    
		// 核心方法
		doDispatch(request, response);
	}
	finally {
    
    
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    
    
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) {
    
    
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}
}

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文件 进行封装multipartRequest
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			// 根据请求获取handler拦截链
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
    
    
				noHandlerFound(processedRequest, response);
				return;
			}

			// 根据请求获取适配器
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// Process last-modified header, if supported by the handler.
			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;
			}
			// 执行业务逻辑 返回视图模型
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			if (asyncManager.isConcurrentHandlingStarted()) {
    
    
				return;
			}
			// 设置默认名字
			applyDefaultViewName(processedRequest, mv);
			// 拦截器逻辑
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
    
    
			dispatchException = ex;
		}
		catch (Throwable err) {
    
    
			// As of 4.3, we're processing Errors thrown from handler methods as well,
			// making them available for @ExceptionHandler methods and other scenarios.
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		// 处理结果 包含了异常处理
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
    
    
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
    
    
		triggerAfterCompletion(processedRequest, response, mappedHandler,
				new NestedServletException("Handler processing failed", err));
	}
	finally {
    
    
		if (asyncManager.isConcurrentHandlingStarted()) {
    
    
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
    
    
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
    
    
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
    
    
				cleanupMultipart(processedRequest);
			}
		}
	}
}

3.1 HandlerMapping-RequestMappingHandlerMapping举例

@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
    
    // afterPropertiesSet spring生命周期 初始化中
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(useSuffixPatternMatch());
	this.config.setTrailingSlashMatch(useTrailingSlashMatch());
	this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	super.afterPropertiesSet();
}

super.afterPropertiesSet();—AbstractHandlerMethodMapping

@Override
public void afterPropertiesSet() {
    
    
	initHandlerMethods();
}

initHandlerMethods();

protected void initHandlerMethods() {
    
    
	for (String beanName : getCandidateBeanNames()) {
    
    
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    
    
			// 处理@RequestMapping注解逻辑
			processCandidateBean(beanName);
		}
	}
	handlerMethodsInitialized(getHandlerMethods());
}

// 标有@Controller或者@RequestMapping注解的类 往后执行
processCandidateBean -> detectHandlerMethods

protected void detectHandlerMethods(Object handler) {
    
    
	// 根据name获取bean class
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
    
    
		// 如果是代理类 获取父类
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		// 遍历此类所有方法 T就是RequestMappingInfo
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
    
    
					try {
    
    
						// RequestMappingInfo包含了requestMapping注解url 和 handler 也就是method方法
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
    
    
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
    
    
			logger.trace(formatMappings(userType, methods));
		}
		methods.forEach((method, mapping) -> {
    
    
			// 找出可外部调用的方法
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			// 注册到registry变量 handlerMethod为bean与method的封装类
			// this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

getMappingForMethod(method, userType)

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    
    
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
    
    
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
    
    
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
    
    
			info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
		}
	}
	return info;
}

3.2 HandlerAdapter-RequestMappingHandlerAdapter举例

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

	ModelAndView mav;
	checkRequest(request);

	// synchronizeOnSession 需要session同步处理的操作
	if (this.synchronizeOnSession) {
    
    
		HttpSession session = request.getSession(false);
		if (session != null) {
    
    
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
    
    
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
    
    
			// 没有session
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
	}
	else {
    
    
		// 不需要一个session内同步执行
		mav = invokeHandlerMethod(request, response, handlerMethod);
	}
	// 不包含Cache-Control 则对response进行处理 设置过期时间
	if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    
    
		if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    
    
			applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
		}
		else {
    
    
			prepareResponse(response);
		}
	}

	return mav;
}

invokeHandlerMethod

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
    
    
		// 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中配置的InitBinder,用于进行参数的绑定
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		// 获取容器中全局配置的ModelAttribute和当前HandlerMethod所对应的Controller的ModelAttribute 
		// 保证业务逻辑之前调用
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		// 整体调用链封装成ServletInvocableHandlerMethod
		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		if (this.argumentResolvers != null) {
    
    
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
    
    
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		invocableMethod.setDataBinderFactory(binderFactory);
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		// @ModelAttribute方法调用
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
		// 创建一个一步请求AsyncWebRequest
		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);
		// 线程池 请求 返回以及Interceptors封装到WebAsyncManager
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.setTaskExecutor(this.taskExecutor);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		asyncManager.registerCallableInterceptors(this.callableInterceptors);
		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
		// 判断是否有异步结果
		if (asyncManager.hasConcurrentResult()) {
    
    
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
    
    
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			// 异步结果进行封装
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		// 调用目标HandlerMethod
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
    
    
			return null;
		}
		// 封装成ModelAndView且返回
		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
    
    
		webRequest.requestCompleted();
	}
}

流程总结:

  1. SpringMVC默认加载RequestMappingHandlerMapping(还有其他HandlerMapping类)和RequestMappingHandlerAdapter(还有其他HandlerAdapter类), 因为实现了InitializingBean, 在初始化时会调用afterPropertiesSet初始化逻辑, 会遍历所有的bean, 挑选出有@Controller或者@RequestMapping注解的bean, 进行requestMappingInfo封装(主要就是url和handler以及method对应数据)
  2. Tomcat启动之后, 会去反射创建DispatcherServlet, HttpServletBean实例化后会调用initServletBean, 然后走到FrameworkServlet的initServletBean方法, 之后会刷新容器, 调到DispatcherServlet的onRefresh方法, 注册九大组件.
  3. 一个Http(get)请求打到该项目后, 会走到HttpServlet的doGet方法, 然后走到DispatcherServlet的doService, 再到doDispatch, 会根据request拿到HandlerExecutionChain执行链, 走真正方法逻辑之前的拦截器, 通过handler拿到适配器执行@InitBinder和@ModelAttribute注解逻辑, 再执行真正自己的逻辑 封装ModelAndView返回, 再执行业务逻辑之后的拦截器. 最后异常执行异常处理逻辑.

猜你喜欢

转载自blog.csdn.net/weixin_45657738/article/details/110521903