zoukankan      html  css  js  c++  java
  • springmvc原理解析

    springmvc执行过程分析

    源码分析

    前端解析器的UML图

    • 因为是对前端请求的URL进行处理,我们只需要看Servlet的继承类就可以了

    初始化(主要工作:上下文初始化,和解析器初始化)

    • FrameworkServlet

      • 初始化Servlet的上下文, 调用子类的onFresh方法 进行初始化;
    
    protected final void initServletBean() throws ServletException {
           //。。。。。。。
    	long startTime = System.currentTimeMillis();
    
    	try {
    		this.webApplicationContext = initWebApplicationContext();
    		initFrameworkServlet();
    	}catch (ServletException | RuntimeException ex) {
    		logger.error("Context initialization failed", ex);
    		throw ex;
    	}
    }
    /**
     * 初始化应用
     */
    protected WebApplicationContext initWebApplicationContext() {
           //获取应用上下文
    	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();
    	}
             //需要创建一个Servlet的上下文,初始化上下文的设置   
    	if (wac == null) {
    		// No context instance is defined for this servlet -> create a local one
    		wac = createWebApplicationContext(rootContext);
    	}
            //  刷新上下文,这里的onRefresh方法在DispatcherServlet中进行实现  
    	if (!this.refreshEventReceived) {
    		synchronized (this.onRefreshMonitor) {
    			onRefresh(wac);
    		}
    	}
             //获取Servlet上下文,设置上下文属性。   
    	if (this.publishContext) {
    		String attrName = getServletContextAttributeName();
    		getServletContext().setAttribute(attrName, wac);
    	}
    	return wac;
    }
    
    • DispatcherServlet

      • 主要是解析器的初始化,总共有一下几种解析器
    
    @Override
    protected void onRefresh(ApplicationContext context) {
    	initStrategies(context);
    }
    
    //进行初始化
    protected void initStrategies(ApplicationContext context) {
    	initMultipartResolver(context);//初始化文件上传解析器
    	initLocaleResolver(context);//本地化解析器(多语言切换使用)
    	initThemeResolver(context);//当前主题解析
    	initHandlerMappings(context);//beanNameURL进行映射
    	initHandlerAdapters(context);//controller进行映射
    	initHandlerExceptionResolvers(context);//异常解析器
    	initRequestToViewNameTranslator(context);//请求URI转视图名称解析器
    	initViewResolvers(context);//视图解析器
    	initFlashMapManager(context);//FlashMap管理
    }
    
    

    接收发送来的请求

    • FrameworkServlet

      • 提供了接收请求的实现方法,提供了入口;

      • 提供了请求的大致流程,具体如下源码中进行了分析;

      • 请求URL业务处理在DispatcherServlet中进行完成,后续代码中会进行分析。

    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
        processRequest(request, response);
    }
    /**
     * 主要流程:
     * 1.创建localContext多语言切换实例
     * 2.从Request上下文处理器中获取request参数
     * 3.注册拦截器
     * 4.初始化上下文处理器,后续请求参数和多语言切换进行处理
     * 5.doService处理的是核心的URL隐射业务,这块业务在DispatcherServlet实现
     * 6.如果有异常则抛出异常
     * 7.请求处理完,则将上下文重置;打印请求日志
     * 8.异步执行应用的监听事件 
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
    
    	long startTime = System.currentTimeMillis();
    	Throwable failureCause = null;
            //获取本地语言上下文
    	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
            // 获取request新传入的语言
    	LocaleContext localeContext = buildLocaleContext(request);
            //从Request上下文获取请求参数
    	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
            //请求参数转换为ServletRequest的请求参数类型
    	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
            
            //新建通过管理及注册回调拦截器     
    	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
            // 初始化上下文    
    	initContextHolders(request, localeContext, requestAttributes);
    
    	try {
    		doService(request, response);//子类进行实现
    	}catch (ServletException | IOException ex) {
    		failureCause = ex;
    		throw ex;
    	}catch (Throwable ex) {
    		failureCause = ex;
    		throw new NestedServletException("Request processing failed", ex);
    	}finally {
                    //重置上下文
    		resetContextHolders(request, previousLocaleContext, previousAttributes);
                    //进行回调  
    		if (requestAttributes != null) {
    			requestAttributes.requestCompleted();
    		}
                    //打印响应的结果  
    		logResult(request, response, failureCause, asyncManager);
                    //异步执行应用监听事件  
    		publishRequestHandledEvent(request, response, startTime, failureCause);
    	}
    }
    
    //request完成
    public void requestCompleted() {
            //执行Request回调    
    	executeRequestDestructionCallbacks();
           //更新session权限
    	updateAccessedSessionAttributes();
    	this.requestActive = false;
    }
    
    private void logResult(HttpServletRequest request, HttpServletResponse response,
    		@Nullable Throwable failureCause, WebAsyncManager asyncManager) {
    
    	if (!logger.isDebugEnabled()) {
    		return;
    	}
            //转发类型    
    	String dispatchType = request.getDispatcherType().name();
    	boolean initialDispatch = request.getDispatcherType().equals(DispatcherType.REQUEST);
    
    	if (asyncManager.isConcurrentHandlingStarted()) {
    		logger.debug("Exiting but response remains open for further handling");
    		return;
    	}
           // 下面都是日志打印,省略代码。。。。。。	
    }
    
    
    • DispatcherServlet(请求业务的核心处理部分)

      • 核心在于,根据MethodHandler,生成ModelAndView 或存入response
    /**
     * 作用:1.格式化请求参数,派发请求任务到对应的方法处理
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	logRequest(request);
    
    	Map<String, Object> attributesSnapshot = null;
    	if (WebUtils.isIncludeRequest(request)) {//当请求有URI的时候,格式化请求参数
    		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));
    			}
    		}
    	}
    
    	// 设置请求参数
    	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 {//重置request的属性
    		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    			// Restore the original attribute snapshot, in case of an include.
    			if (attributesSnapshot != null) {
    				restoreAttributesAfterInclude(request, attributesSnapshot);
    			}
    		}
    	}
    }
    
    /**
     * 这里的主要处理过程是这样的:
     * 1.先判断是不是multipart 请求,如果是要进行标记,等处理完之后要进行清空
     * 2.判断能否找到request对应的handler处理器(一般都是url请求都是RequestMappingHandlerMapping),如果查找不到直接返回404;
     * 3.查找对应请求的处理适配器(一般的都是RequestMappingHandlerAdapter)
     * 4.判断如果是GET请求方式,且最后次修改没有变化,直接返回
     * 5.执行拦截器的pre方法,如果存在异常直接返回;
     * 6.处理器适配器进行业务处理返回ModelAndView,如果ModelAndView非空,且没有View的情况下
     * 7.处理拦截器的post方法 
     * 8.出现异常之后,处罚拦截器的complete方法  
     * 9.如果是multipart Request的话,进行清空请求数据
     */ 
    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);
    
    			// 查找当前请求的mappedHandler,下面有分析
    			mappedHandler = getHandler(processedRequest);
    			if (mappedHandler == null) {//映射为空的时候,返回404 错误。
    				noHandlerFound(processedRequest, response);
    				return;
    			}
    
    			/**
                             * 查找对应的Handler适配器,一般有对应的controller解析,还有requestMapping解析等
                             */     
    			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;
    				}
    			}
                            //在这里执行拦截器(interceptor),如果执行错误则,返回
    			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    				return;
    			}
    
    			//执行映射的处理器,是核心部分。后面单独分析
    			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return;
    			}
                            //如果ModelAndView中没有View,则返回请求的url
    			applyDefaultViewName(processedRequest, mv);
    
                            // 拦截器执行postHandler方法
    			mappedHandler.applyPostHandle(processedRequest, response, mv);
    		}
    		catch (Exception ex) {
    			dispatchException = ex;
    		}
    		catch (Throwable err) {
    			dispatchException = new NestedServletException("Handler dispatch failed", err);
    		}
                    /**
                     * 这里的代码不详细分析,大概执行过程如下:
                     * 1.判断是否有异常,如果有异常,按照ModelAndViewDefiningException生成对应的ModelAndView
                     * 2.判断是否有View视图要render,如果有,根据自己配置的渲染方式html,或者thymeleaf进行对应的渲染
                     */   
    		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()) {
    			if (mappedHandler != null) {
    				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    			}
    		}
    		else {
    			// 清空中 multipart request 中的文件信息
    			if (multipartRequestParsed) {
    				cleanupMultipart(processedRequest);
    			}
    		}
    	}
    }
    
    
    //获取mappedHandler的具体方法
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	Object handler = getHandlerInternal(request);//获取内部的handler
    	if (handler == null) {
    		handler = getDefaultHandler();
    	}
    	if (handler == null) {
    		return null;
    	}
    	// 如果处理器是字符串从工厂中招对应的bean
    	if (handler instanceof String) {
    		String handlerName = (String) handler;
    		handler = obtainApplicationContext().getBean(handlerName);
    	}
            // 获取执行链,也即MappedInteraptor相关操作  
    	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
            //如果有跨域的配置时,重新调整执行链    
    	if (hasCorsConfigurationSource(handler)) {
    		CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
    		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    		config = (config != null ? config.combine(handlerConfig) : handlerConfig);
    		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    	}
    
    	return executionChain;
    }
    
    //获取对应的HandlerMethod
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
            //对Request中的请求url进行解析请求url
    	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    	request.setAttribute(LOOKUP_PATH, lookupPath);
    	this.mappingRegistry.acquireReadLock();
    	try {    //这里是重点:获取对应的handlerMethod (包含请求uri对应的类及方法)
    		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    	}
    	finally {
    		this.mappingRegistry.releaseReadLock();
    	}
    }
    

    HandlerAdapter进行映射(以RequestMappingHandlerAdapter为例)

    • 这里是所有操作的核心所在,主要是,根据URI进行解析查找对应的方法,返回结果进行封装(代码细节太多没有解析)
    # AbstractHandlerMethodAdapter
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    	return handleInternal(request, response, (HandlerMethod) handler);
    }
    
    // 作用:处理uri映射
    protected ModelAndView handleInternal(HttpServletRequest request,
    		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    	ModelAndView mav;
            //检查请求头是否允许    
    	checkRequest(request);
    
    	//判断session是否要加锁,并执行映射处理方法
    	if (this.synchronizeOnSession) {
    	   //省略一部分代码。。。。。。。。。。	
    	}else {
                    //执行处理的方法  
    		mav = invokeHandlerMethod(request, response, handlerMethod);
    	}
              //判断请求头是否cache设置,设置response信息  
    	if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    		if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    			applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    		}
    		else {
    			prepareResponse(response);
    		}
    	}
    
    	return mav;
    }
    
    /**
     * 作用:
     *  1.查找bean对应的方法invocableMethod 
     *  2.通过反射执行方法;
     *  3.将获取到的结果进行封装,并返回ModelAndView.
     */
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    	ServletWebRequest webRequest = new ServletWebRequest(request, response);
    	try {   /**
                     * 将bean与对应的method进行绑定
                     * 将method设置参数解析器
                     */     
    		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
                    /**
                     * 1.获取bean类路径,查看是否有model请求中是否有,以bean类路径为key的访问,没有则初始化
                     * 2.model访问缓存中如果已经有了这个bean路径,则将对应的方法加入
                     * 3.新键一个ModelFactory
                     */           
    		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
    		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);
                    //ModelView容器进行初始化  
    		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
    		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    		asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		asyncManager.setTaskExecutor(this.taskExecutor);
    		asyncManager.setAsyncWebRequest(asyncWebRequest);
    		asyncManager.registerCallableInterceptors(this.callableInterceptors);
    		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
                     //执行映射,这里执行对应方法的映射,下面有分析 
    		invocableMethod.invokeAndHandle(webRequest, mavContainer);
    		if (asyncManager.isConcurrentHandlingStarted()) {
    			return null;
    		}
                    //这一步就是设置Model和View,如果Redirect则进行重定向  
    		return getModelAndView(mavContainer, modelFactory, webRequest);
    	}
    	finally {
                    //request进行销毁  
    		webRequest.requestCompleted();
    	}
    }
    
    
    
    //执行url映射,这里是核心的方法。
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    		Object... providedArgs) throws Exception {
             //这里就是执行任务并且返回结果   
    	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
            //设置响应的状态码    
    	setResponseStatus(webRequest);
             //判断返回值,如果没数据,或者response有返回原因,则设置请求跳转为true,否则设置为false   
    	if (returnValue == null) {
    		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    			disableContentCachingIfNecessary(webRequest);
    			mavContainer.setRequestHandled(true);
    			return;
    		}
    	}
    	else if (StringUtils.hasText(getResponseStatusReason())) {
    		mavContainer.setRequestHandled(true);
    		return;
    	}
    
    	mavContainer.setRequestHandled(false);
    	Assert.state(this.returnValueHandlers != null, "No return value handlers");
            //将返回数据进行格式化
    	try {
    		this.returnValueHandlers.handleReturnValue(
    				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    	}
    	catch (Exception ex) {
    		if (logger.isTraceEnabled()) {
    			logger.trace(formatErrorForReturnValue(returnValue), ex);
    		}
    		throw ex;
    	}
    }
    
    
    //执行方法的具体过程
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    		Object... providedArgs) throws Exception {
            //获取请求的参数    
    	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    	if (logger.isTraceEnabled()) {
    		logger.trace("Arguments: " + Arrays.toString(args));
    	}
            /**
             * 这里不具体分析里面的过程,其实就是通过反射调用方法,获取方法执行结果   
             * 再将执行的结果进行封装,有不同的封装解析器
             * RequestResponseBodyMethodProcessor这个类提供了将返回的数据进行格式化ServletServerHttpResponse的方法
             * 如果不需要ViewAndModel变量,会将requestHandled参数设置为true,表示程序已经处理,不需要视图。 
             */ 
    	return doInvoke(args);
    }
    

    拦截器的执行过程

    • 上面源码分析的过程中,已经分析了拦截器的执行过程,这些主要分析一下拦截器pre方法的执行逻辑;

      • 拦截器的任一pre前置方法返回false,则触发拦截器的afterCompletion方法
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = 0; i < interceptors.length; i++) {
    			HandlerInterceptor interceptor = interceptors[i];
                            //执行拦截器前置操作,返回true则继续,false的话进入方法里面
    			if (!interceptor.preHandle(request, response, this.handler)) {
    				triggerAfterCompletion(request, response, null);
    				return false;
    			}
    			this.interceptorIndex = i;
    		}
    	}
    	return true;
    }
    
    //触发完成后拦截器操作
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
    		throws Exception {
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = this.interceptorIndex; i >= 0; i--) {
    			HandlerInterceptor interceptor = interceptors[i];
    			try {    
                                  //执行完成后操作      
    			      interceptor.afterCompletion(request, response, this.handler, ex);
    			}
    			catch (Throwable ex2) {
    				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    			}
    		}
    	}
    }
    
    
    

    总结

    1. springmvc的重要组件有哪些?都有什么作用?

      这个问题,其实在我们前面解析源码的时候就已经看到了。DispatcherServlet初始化的时候,初始化的几个解析器都是常使用的组件。
      
      其中使用频率比较多的就是:
       
      1.HandlerMapping解析器,处理请求URI和对应方法的映射关系的处理器;
    
      2.HandlerAdapter解析器,根据HandleMapping进行对应方法调用处理的解析器;
    
      3.ViewResolver视图解析器,因为现在前后端分离其实很少使用了。
    
    
  • 相关阅读:
    MYSQL InnoDB二级索引存储主键值而不是存储行指针的优点与缺点
    公众号 苹果端点击事件委托不起作用而安卓可以
    php emoji表情转换
    PHP 获取网页所有链接
    node 一行一行的读取文件
    AsyncJS 异步流程控制DEMO详细介绍
    node.js 获取异步方法里面数据 的方式
    利用blob 加密防下载
    html css 3D轮播图
    transform和transition组合动画错误问题
  • 原文地址:https://www.cnblogs.com/perferect/p/13803990.html
Copyright © 2011-2022 走看看