Spring 源码学习笔记(七) SpringMVC

个人学习Spring源码系列 SpringMVC

 


(一)SpringMVC概述

1.SpringMVC介绍

Spring Web MVC是基于Servlet API构建,实现了Web MVC设计模式的原始轻量级Web框架,从一开始就包含在Spring框架中,其正式名称"Spring Web MVC".

2.SpringMVC核心流程

具体流程:

(1)首先浏览器发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

(2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;

(3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

(4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

(5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;

(6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;

(7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

3.SpringMVC总结

(1)SpringMVC解决的痛点

1.将web页面的请求传给服务器

2.根据不同的请求处理不同的逻辑页面

3.返回处理结果数据并跳转页面

(2)SpringMVC相关文件

1. web.xml  必须 配置contexLoadListener配置DispatcherServlet拦截请求

2. applicationContext.xml 非必须 可以用来配置InternalResourceViewResolver来给返回的页面加上前缀进行试图查找

3. model  pojo,必须

4.controller 继承AbstractController,用于处理Web请求

5.view 可以由jsp或其他试图框架完成

6.Spring-servlet.xml(非必需,一般用于模块化开发)


(二)DispatchServlet

1.DispatchServlet实现了Servlet接口

DispatchServlet实际上是实现了Servlet接口的类,Servlet的生命周期是由servlet容器来控制。

1.初始化阶段

servlet容器加载servlet类,将.class文件读入内存;
servlet容器创建ServletConfig对象,包含了servlet的初始化配置信息;
servlet容器创建servlet对象
servlet容器调用servlet对象的init方法进行初始化

2.运行阶段

servlet接受请求时,针对请求创建servletRequest,servletResponse,调用service方法(通过servletRequest获得请求信息,根据servletResponse生成相应结果),最后销毁servletRequest,servletResponse两个对象
servlet被设计为请求驱动,请求会被封装为HttpServletRequest对象,然后传给对应服务方法。请求的类型一共有八中(常用的:GET,POST,PUT,DELETE,不太常用的:HEAD,TRACE,CONNECT.OPTIONS


3.销毁阶段

调用servlet容器的destory方法之后再销毁servlet对象,我们可以将一些servlet占用的资源的释放放在destroy方法中实现

2. 初始化

(1)DispatchServlet类中没有init方法,查找父类HttpServletBean中有init方法

	public final void init() throws ServletException {

		// Set bean properties from init parameters.
                //从init配置参数set bean配置参数
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				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;
			}
		}

		// Let subclasses do whatever initialization they like.
                //初始化web环境
		initServletBean();
	}

(2)FrameworkServlet.java中initServletBean()

	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
                        //初始化web环境
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

(3)FrameworkServlet.java中initWebApplicationContext()

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		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);
					}
                                            //配置和刷新Spring容器
                                            //这个无非就是初始化spring ioc的环境,创建bean和实例化bean等操作
				            //这个方法最终也是调用refresh()方法,已在spring源码解析中解析过了
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

(4)执行DispatcherServlet类的onRefresh()方法,初始化SpringMVC的配置

	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}


	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context); //上传文件
		initLocaleResolver(context); //国际化
		initThemeResolver(context); //前段的主题样式
		initHandlerMappings(context); //初始化HandlerMappings 请求映射器重点
		initHandlerAdapters(context); //初始化HandlerAdapters 处理适配器
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context); //视图转换器
		initFlashMapManager(context); //重定向数据管理器
	}

(5)执行initHandlerMappings()

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
                //通过配置文件中的配置信息,得到handlerMappings		
		if (this.handlerMappings == null) {
                        //使用defaultStrategies获取数据
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}

		for (HandlerMapping mapping : this.handlerMappings) {
			if (mapping.usesPathPatterns()) {
				this.parseRequestPath = true;
				break;
			}
		}
	}

(6)执行getDefaultStrategies()方法

	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		if (defaultStrategies == null) {
			try {
				// Load default strategy implementations from properties file.
				// This is currently strictly internal and not meant to be customized
				// by application developers.
                                //从属性文件加载默认策略实现,说白了这里的意思就是从    DEFAULT_STRATEGIES_PATH这个文件当中拿出所有的配置,可以去数一下一共有8个: DispatcherServlet.properties == DEFAULT_STRATEGIES_PATH

				ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
				defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
			}
			catch (IOException ex) {
				throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
			}
		}

		String key = strategyInterface.getName();
		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 {
                                        //获取class字节码文件
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                                        //底层是通过调用Spring getBean()的方式创建该对象
                                        //请求映射就是在这个方法实现装配的
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					throw new BeanInitializationException(
							"Could not find DispatcherServlet's default strategy class [" + className +
							"] for interface [" + key + "]", ex);
				}
				catch (LinkageError err) {
					throw new BeanInitializationException(
							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
							className + "] for interface [" + key + "]", err);
				}
			}
			return strategies;
		}
		else {
			return Collections.emptyList();
		}
	}

3.初始化分析

(1) initHandlerMappings方法,就是初始化我们的handlerMapping(请求映射器).
(2) handlerMapping的主要作用是,找到请求路径对应的controller的方法.

例如:请求的路径 “/index”,然后这个handlerMapping,在初始化的时候,已经将所有controller的请求路径映射保存在一个map集合,当请求过来的时候,就将"/index"作为一个key,从map集合中找到对应的controller的index方法。

(3) 这里初始化handlerMappings ,默认是有两个handlerMappings ,是直接在defaultStrategies配置文件中获取.
(4) 那么defaultStrategies的值是在defaultStrategies = PropertiesLoaderUtils.loadProperties(resource)

   

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

4.SpringMVC 请求阶段

(1)doService()方法

用户发出一个请求过来,会由Servlet接收,并调用到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));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
                // 设置Request参数
		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);
		}

		RequestPath previousRequestPath = null;
		if (this.parseRequestPath) {
			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
			ServletRequestPathUtils.parseAndCache(request);
		}

		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);
				}
			}
			ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
		}
	}

(2)doDispatch()方法

核心方法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
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
                                //检查请求中是否有文件上传操作
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
                                //确定当前请求的处理程序(重点),推断controller和handler的类型
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                                //推断适配器,不同的controller类型,交给不同的适配器去处理
                                //如果是一个bean,mappedHandler.getHandler()返回的是一个对象
			        //如果是一个method,mappedHandler.getHandler()返回的是一个方法	
				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;
				}

				// Actually invoke the handler.
                                //通过适配器,处理请求(可以理解为,反射调用方法)(重点)
				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);
				}
			}
		}
	}

核心处理方法是doDispatch(),主要是分了几步:
(1) 检查请求中是否有文件上传操作
(2) 确定当前请求的处理的handler(重点)
(3) 推断适配器,不同的controller类型,交给不同的适配器去处理
(4) 执行前置拦截器处理interceptor
(5) 通过找到的HandlerAdapter ,反射执行相关的业务代码controller的方法。
(6) 返回结果

 

(3)getHandler()方法

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
                        //循环所有的HandlerMappings
		        //this.handlerMappings这个是什么时候初始化的?(重点)
		        //在handlerMappings初始化的时候
			for (HandlerMapping mapping : this.handlerMappings) {
                                //把请求传过去看能不能得到一个handler
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

(1) getHandler()方法,主要是遍历在DispatcherServlet初始化是,初始化的handlerMappings。
(2) 这个方法的主要思想是,通过request的路径,去匹配对应的controller去处理

	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
                //获取请求的路径
		String lookupPath = initLookupPath(request);
		Object handler;
                //找到对应的handler
		if (usesPathPatterns()) {
			RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
			handler = lookupHandler(path, lookupPath, request);
		}
		else {
			handler = lookupHandler(lookupPath, request);
		}
		if (handler == null) {
			// We need to care for the default handler directly, since we need to
			// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
			Object rawHandler = null;
			if (StringUtils.matchesCharacter(lookupPath, '/')) {
				rawHandler = getRootHandler();
			}
			if (rawHandler == null) {
				rawHandler = getDefaultHandler();
			}
			if (rawHandler != null) {
				// Bean name or resolved handler?
				if (rawHandler instanceof String) {
					String handlerName = (String) rawHandler;
					rawHandler = obtainApplicationContext().getBean(handlerName);
				}
				validateHandler(rawHandler, request);
				handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
			}
		}
		return handler;
	}
	protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
                //通过请求的路径,在handlerMap中去匹配
		Object handler = getDirectMatch(lookupPath, request);
		if (handler != null) {
			return handler;
		}

		// Pattern match?
		List<String> matchingPatterns = new ArrayList<>();
		for (String registeredPattern : this.handlerMap.keySet()) {
			if (getPathMatcher().match(registeredPattern, lookupPath)) {
				matchingPatterns.add(registeredPattern);
			}
			else if (useTrailingSlashMatch()) {
				if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) {
					matchingPatterns.add(registeredPattern + "/");
				}
			}
		}

		String bestMatch = null;
		Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
		if (!matchingPatterns.isEmpty()) {
			matchingPatterns.sort(patternComparator);
			if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
				logger.trace("Matching patterns " + matchingPatterns);
			}
			bestMatch = matchingPatterns.get(0);
		}
		if (bestMatch != null) {
			handler = this.handlerMap.get(bestMatch);
			if (handler == null) {
				if (bestMatch.endsWith("/")) {
					handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
				}
				if (handler == null) {
					throw new IllegalStateException(
							"Could not find handler for best pattern match [" + bestMatch + "]");
				}
			}
			// Bean name or resolved handler?
			if (handler instanceof String) {
				String handlerName = (String) handler;
				handler = obtainApplicationContext().getBean(handlerName);
			}
                        //验证Handler
			validateHandler(handler, request);
			String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath);

			// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
			// for all of them
			Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
			for (String matchingPattern : matchingPatterns) {
				if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
					Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath);
					Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
					uriTemplateVariables.putAll(decodedVars);
				}
			}
			if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
				logger.trace("URI variables " + uriTemplateVariables);
			}
			return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
		}

		// No handler found...
		return null;
	}

(4) getHandlerAdapter()

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

一开始只是找到了Handler 现在要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来了,对应不同的Handler的执行方案。当找到合适的适配器的时候, 基本上就已经收尾了。

其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器)
通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler
然后再通过HandlerAdapters找到一个合适的HandlerAdapter 来执行Handler即Controller里面的逻辑
最后再返回ModlAndView

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值