Spring MVC 执行原理和源码分析

本文详细介绍了SpringMVC的初始化过程,包括IoC容器的初始化、映射请求上下文初始化等内容。此外,还深入分析了注解配置方式、开发流程及数据转换等关键环节,并通过源码分析揭示了SpringMVC的执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

​ Spring MVC 基于 Servlet,提供核心控制器DispatcherServlet,结构松散,以至于能适应各种灵活的需求

初始化

IoC初始化

ServletContextListener接口可以在web容器的初始化和结束期中执行一定的逻辑。所以,通过下面这个类,可以在ServletContextListener初始化之前初始化Ioc,也可以在结束期完成对Ioc容器的销毁

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }//初始化

    public void contextDestroyed(ServletContextEvent event) {
        //关闭应用上下文
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
        //清除属性
    }//销毁
}

映射请求上下文 初始化

​ 这个是由DispatcherServlet初始化的

FrameworkServlet

@Override
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 {
            //初始化操作
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

    	//下面都是日志,没啥意思,我直接删了
	}

	/**
	 * 初始化和注册WebApplicationContext为这个Servlet.
	 * 这个是能够被子类重写的,所以用的是protected方法
	 */
	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()) {
				//如果Ioc容器没有刷新,那么就刷新父容器的上下文
					//如果父容器为空
					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);
				}
			}
		}
        //下面逻辑判断好奇怪,如果是我写会判断是否初始化,是否有web Ioc容器,但是它这样的写法,怎么说,还行,毕竟操作的是一个对象,就是可读性不好
        
        //如果没有被初始化
		if (wac == null) {
			// 查找是否有存在web Ioc容器
			wac = findWebApplicationContext();
		}
        //如果没有初始化,而且没有找到存在的ioc容器
		if (wac == null) {
			//则我自己创建一个
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// 如果没有执行过onRefresh方法,就执行,注意一下,spring在这个版本加了锁,因为是发现并发的问题了
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// 作为Servlet的上下文属性发布Ioc容器
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

DispatcherServlet

​ onRefresh会跳转到这些方法执行,这个将初始化mvc的各个组件,是一个值得十分关注的方法

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

MultipartResolver

文件解析器,支持服务器上传文件的功能,不过现在公司一般用阿里云的OSS来完成文件服务器

LocaleResovler

国际化解析器,提供不同语言不同的返回语言结果,这个一般交给前端处理,写多套前端,比写后端,然后配置语言要方便的多,而且前端很多UI都支持国际化

ThemeResolver

主题解析器,也是交给前端,不用管

HandlerMapping

非常重要,它会包装用户提供一个控制器的方法和对它的一些拦截器,通过调用它就能运行控制器

handlerAdaptr

处理器适配器,因为处理器会在不同的上下文中运行,所以spring会找到合适的适配器,然后运行处理器服务方法,比如对于控制器的SimpleControllerHandlerAdapter,对于普通请求的HttpRequestHandlerAdapter

HandlerExceptionResolver

全局异常处理器,而且可以指定前端跳转到指定页面

RequestToViewNameTranlator

可以在控制器中返回一个视图的名称,通过它可以找到实际的视图,如果没有就根据url寻找,我猜是静态资源解析

ViewResolver

视图解析器,解析名称,定位实际视图


实际上DispatcherServlet会通过DispatcherServlet.properties

注解配置方式初始化

​ 由于Servlet3以后支持了注解配置,所以spring在3以后也做了支持,只需要我们继承一个AbstractAnnotationConfigDispatcherSerletInitializer然后实现它定义的方法,为什么MVC可以做到我们继承一个类,就能代替它原有的支持呢,而且我们在MVC开发的时候,也遇到了好多这样的情况,都是继承一个类,然后就能实现对应的方法
​ 因为Servlet3容许动态加载Servlet,所以spring提供了SpringSerletContainerInitializer,它继承了Serlet提供的接口,这样利用Servlet实现了动态配置加载

SpringServletContainerInitializer

spring的抽象类层级是不止一层的

  • getRootConfigClasses 获取Ioc容器的java配置类,用以装载各类的bean
  • getServletConfigClasses 获取各类MVC的URI控制器的配置关系类,用以生成web请求的上下文
  • getServletMappings 定义DispatchServlet拦截的请求

开发流程补充

​ 在这里记录一下,我平时开发没有注意到的地方
​ 解析的大体流程,用Controller标注一个类,而且只能用这个标注,restController一样,我们使用后者,然后spring会扫描这些配置,然后结合@XXXMapping,它的作用是把uri和方法绑定在一起,然后mvc结合配置的拦截器,组成了多个拦截器和一个控制器的形式,放到一个HandlerMapping中。当请求来服务器的时候,首先通过请求的信息找到对应的HandlerMapping,然后找到拦截器和处理的函数
​ spring mvc竟然还提供了@SessionAttibute来直接获取session中的值,所以,我在想,能不能自定义一个注解,把token里的信息取出来,如果能把这个加到我的权限框架中,我觉得会很好用

@RequestAttribute

​ 我们可以在request里面设置属性,它一次请求的作用内有效,其实本质就是同一个request对象,我们可以通过拦截器和web上下文设置额外的参数

多个拦截器的执行顺序

​ 多个拦截器是根据责任链的顺序执行的,但是如果其中有一个preHandler返回false,那么整个链就直接停止了

视图

​ 对于我们返回的对象,spring可以做任意的处理,可以把我们的对象转换为excel,pdf还有我们常见的json,这些就是视图,并不仅仅是指jsp页面
​ 视图实现了ViewResoler的接口,而且是支持国际化的,因为这个接口的唯一方法resolveViewName有Locale参数
​ 举个例子,我们一开始在spring中返回一个字符串,spring就能把他对应成html,是通过InternalResourceViewResolver来实现的

导出excel

​ 我们用视图来实现这个功能,spring推荐我们使用AbstractXlsView来实现(我发现框架的抽象类都是这个前缀)这个用到了ModelView
​ spring提供的这个我觉得有一点不好,因为他限制了只能用poi,他的方法入参应该是一个文件流,也就是说,我们方法参数的接受范围要尽可能的大,这样,我们的函数功能才会更加强大,但是有一点,如果你的方法里出现了大量的关于参数的判断,那么是完全没有必要的

文件上传

​ 这里我记录一些spring的设计思想,它的MultipartRequest类,它继承了HttpServletRequest然后扩充了它对文件操作的方法,利用这种继承,使一个类的功能得到了增强

数据转换和格式化

​ 首先,当一个请求到达了DispatchServlet的时候,需要找到对应的HandlerMapping,然后通过HandlerMapping找到对应的HandlerAdapter处理器,处理器在调用控制器(就是我们定义的controller)之前,会先获取HTTP发送过来的信息然后将其转变为控制器的各种参数,这就是我们能通过注解获取参数的方法,一句话概括的话,就是spring处理了我们的http请求。

​ spring用HttpMessageConverter消息转换器实现了消息的装换,但这只是一个比较简单的转换,所以spring提供了转换器和格式化器,然后就会对这些参数进行验证,只是我个人习惯自己写验证,因为一般涉及到增的地方,就会设计到改,如果用框架,需要写的代码比较多,而且有些验证逻辑很复杂,需要自定义复杂的类,所以我一般是让对象自己完成验证的

​ 然后,处理我们的业务逻辑,最后在返回结果的时候,spring会用HttpMessageConverter进行转换,比如我们的@ResponeBody

​ Http请求的参数会给HttpMessageConverter然后会给转换器(转换器又分为普通转换器Converter和集合转换器GenericConverter)和格式化器(Formatter),然后就变成了我们的java对象,接下来,spring会对它进行验证。然后就是调用我们编写的业务逻辑,接下来,又把交回了HttpMessageConverter然后进行比如Json之类的转换最后返回给我们的客户端
所以这个能够完成json和对象的双向转换,而且这些转换器是需要注册的,如果我们自己的写的话,这个类型转换器确实是很有必要的,我整理了一下spring boot的注册,因为spring的太繁琐了, 我还发现spring boot采用的方式是继承一个类,然后里面有一个参数,我们只要操作这个参数就能完成各种配置和注册

源码分析

​ 不过事到如今,我觉得一个方法,含boolen参数也是合理的,因为对于用户来说,只要我们bool参数的变量名起的比较好,那么是完全没有问题的,而且idea会帮助我们把参数名显示出来,这是非常好的,所以有些代码规则在编译器日新月异的改变中,代码规范是可能会发生改变的,又比如现在重构一个很长的类,很长的方法,也是很方便的

mvc执行流程图

mvc执行流程图

我根据这个图的顺序来自己分析一下源码,如果有不对的地方,希望大家指出来

从一个http请求进来出发

DispatchServlet

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {//这个就是我们在写servlet时常见的哪些参数
		logRequest(request);//spring自己也有为请求参数做日志的方法

		// 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)) {
            //这个方法应该是把request里面的属性放到了自己的map里面
			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.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);
		}
        //上面我们可以看到,spring在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);
				}
			}
			if (this.parseRequestPath) {
				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
			}
		}
	}

logRequest

	private void logRequest(HttpServletRequest request) {
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String params;
            //如何判断上传的是否为文件类型
			if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {
				params = "multipart";
			}
			else if (isEnableLoggingRequestDetails()) {//如果容许答应请求参数细节
                //我发现这个默认是false,怎么开启?
                //我看到参数的注释上说可以通过debug日志级别开启
                //不得不说,一次请求的debug日志数量就超出了我的想象
				params = request.getParameterMap().entrySet().stream()
						.map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
						.collect(Collectors.joining(", "));
                //map怎么用Java8的流,用entrySet(),从形式上看就是一个List
                //collect提供了用,拼接字符串的方法,这个说实话挺常见的,所以你也要学会使用这个方法
			}
			else {
				params = (request.getParameterMap().isEmpty() ? "" : "masked");
			}

			String queryString = request.getQueryString();
			String queryClause = (StringUtils.hasLength(queryString) ? "?" + queryString : "");
			String dispatchType = (!DispatcherType.REQUEST.equals(request.getDispatcherType()) ?
					"\"" + request.getDispatcherType() + "\" dispatch for " : "");
			String message = (dispatchType + request.getMethod() + " \"" + getRequestUri(request) +
					queryClause + "\", parameters={" + params + "}");

			if (traceOn) {
				List<String> values = Collections.list(request.getHeaderNames());
				String headers = values.size() > 0 ? "masked" : "";
				if (isEnableLoggingRequestDetails()) {
					headers = values.stream().map(name -> name + ":" + Collections.list(request.getHeaders(name)))
							.collect(Collectors.joining(", "));//从日志上来看,就是打印了header的信息
				}
				return message + ", headers={" + headers + "} in DispatcherServlet '" + getServletName() + "'";
			}
			else {
				return message;
			}
		});
	}

​ 又有了一个想法,我可以设置如果发生线上异常情况,就报警,报警的内容要尽可能详细一点,然后只在上线的项目要通过报警,所以要设置环境参数,然后报警的内容,要尽可能的让我快速定位到问题,并且解决它

spring boot 设置日志级别

logging:
  level:
    root: debug

​ 记得要加上那个root,不然会出现报错,无法把字符串转换成枚举类

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;//这个是spring无论如何都要初始化的
			Exception dispatchException = null;//转发异常

			try {
				processedRequest = checkMultipart(request);//检测是否为文件上传
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);//选择handler
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//选择适配器

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {//这里特殊处理了get和head方法
					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()) {//目前的handler开始了吗
					return;
				}
				//这个明显就是上面那个if不成立
				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);
				}
			}
		}
	}

getHandler

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {//controller的url映射都放在了这个里面
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;//明显这个处理逻辑就是,如果我找到一个匹配的就返回
				}
			}
		}
		return null;
	}

​ 我发现这个handlerMapping竟然是一个接口,但是实现类特别多,我不知道具体用的是哪一个,怎么办?我目前知道最好的办法就是debug打断点去看了,不得不说,我发现依托于idea强大的debug能力,能让我发现好多东西,因为他显示的值都是实际项目的值,依托于这个值,我可以很容易的猜到这个属性是干什么的,看来以后得经常用了,而且我觉得尽量用,说不定能发现好多意想不到的收获呢

HandlerMapping

​ spring确实初始化了不少,真的不止一个接口实现类,不过我最后确定是这个SimpleUrlHandlerMapping,再次赞叹idea的debug强大

SimpleUrlHandlerMapping

下面这个类我删除了很多无用的代码,就是对分析代码没什么用的
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {

	//这个urlMap直接给人一种通透的感觉,这里就是把url和某个对象结合起来,然后根据这个url找到对应的Object来执行具体的操作,同时我还注意到,这是一个LinkedHashMap,说明是想保证一定顺序的
	private final Map<String, Object> urlMap = new LinkedHashMap<>();
	//我这里产生了一些疑惑的地方,如果我们的url是restFul风格,spring怎么来找呢?
	//不过自己分析源码还是很有意思的,当然这都建立在别人告诉我入口在哪里,不然我肯定不知道怎么找,然后就是我用过这个,可以根据自己平时的使用去猜测


	/**
	 * Register all handlers specified in the URL map for the corresponding paths.
	 * 注册全部的处理器
	 */
	protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.trace("No patterns in " + formatMappingName());
		}
		else {
			urlMap.forEach((url, handler) -> {
				// Prepend with slash if not already present.
				if (!url.startsWith("/")) {//你看这里,明确的告诉我们,前面的url是可以不加/的,但是spring肯定会给你补上这个
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String) {//而且会帮助我们去除字符串首尾的空格
					handler = ((String) handler).trim();
				}
				registerHandler(url, handler);//注册
			});
			logMappings();//打印日志的方法
		}
	}

}

registerHandler

	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		// 如果通过名称引用单例,则优先解析处理程序,这个是字符串,可能是视图解析优先吧
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {//如果你不小心定义了重复的urlPath,spring是会帮助你处理的
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {//这种会被当成Root来处理
				if (logger.isTraceEnabled()) {
					logger.trace("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {//通配符号
				if (logger.isTraceEnabled()) {
					logger.trace("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {//这个应该就是一般的
				this.handlerMap.put(urlPath, resolvedHandler);
				if (getPatternParser() != null) {
					this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);//这个方法来了,spring是怎么处理url的
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

parse

    public PathPattern parse(String pathPattern) throws PatternParseException {
        Assert.notNull(pathPattern, "Path pattern must not be null");
        this.pathPatternData = pathPattern.toCharArray();
        this.pathPatternLength = this.pathPatternData.length;
        this.headPE = null;
        this.currentPE = null;
        this.capturedVariableNames = null;
        this.pathElementStart = -1;
        this.pos = 0;
        this.resetPathElementState();

        for(; this.pos < this.pathPatternLength; ++this.pos) {//这是逐个遍历,有点惊讶,不过好像确实没有什么好的办法
            char ch = this.pathPatternData[this.pos];
            char separator = this.parser.getPathOptions().separator();//分割符号
            if (ch == separator) {//如果你是分割符号
                if (this.pathElementStart != -1) {
                    this.pushPathElement(this.createPathElement());
                }

                if (this.peekDoubleWildcard()) {
                    this.pushPathElement(new WildcardTheRestPathElement(this.pos, separator));
                    this.pos += 2;
                } else {
                    this.pushPathElement(new SeparatorPathElement(this.pos, separator));
                }
            } else {
                if (this.pathElementStart == -1) {
                    this.pathElementStart = this.pos;
                }

                if (ch == '?') {
                    ++this.singleCharWildcardCount;
                } else if (ch == '{') {//这个地方应该就是restFul风格的解析
                    if (this.insideVariableCapture) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_NESTED_CAPTURE, new Object[0]);
                    }

                    this.insideVariableCapture = true;
                    this.variableCaptureStart = this.pos;
                } else if (ch == '}') {
                    if (!this.insideVariableCapture) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE, new Object[0]);
                    }

                    this.insideVariableCapture = false;
                    if (this.isCaptureTheRestVariable && this.pos + 1 < this.pathPatternLength) {
                        throw new PatternParseException(this.pos + 1, this.pathPatternData, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST, new Object[0]);
                    }

                    ++this.variableCaptureCount;
                } else if (ch == ':') {//这个我好像没有遇到过
                    if (this.insideVariableCapture && !this.isCaptureTheRestVariable) {
                        this.skipCaptureRegex();
                        this.insideVariableCapture = false;
                        ++this.variableCaptureCount;
                    }
                } else if (ch == '*') {//这个应该是通配符号
                    if (this.insideVariableCapture && this.variableCaptureStart == this.pos - 1) {
                        this.isCaptureTheRestVariable = true;
                    }

                    this.wildcard = true;
                }

                if (this.insideVariableCapture) {
                    if (this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) == this.pos && !Character.isJavaIdentifierStart(ch)) {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
                    }

                    if (this.pos > this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) && !Character.isJavaIdentifierPart(ch) && ch != '-') {
                        throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
                    }
                }
            }
        }

        if (this.pathElementStart != -1) {
            this.pushPathElement(this.createPathElement());
        }

        return new PathPattern(pathPattern, this.parser, this.headPE);
    }

​ 说实话,上面这个把我劝退了,很难,就像是算法题一样,不过,还有一点,就是我不知道这个handler是什么时候生成的,从代码里面,我只能看到string类型的hander生成,怎么办,我试着把端点放在了那个类的属性上,好像不行,所以我在下面补充一些我学习到的debug技巧

debug

步出:向上指的蓝色箭头。如果我们点了步入,发现它执行了我们不想要的代码,那么我们就可以点击这个步出,而且不是直接关掉再重新调试一遍
运行到关标处:蓝色箭头指着一个关标,我们完全没有必要一步一步的点着它去执行
计算表达式的值:我发现这个其实可以执行java的代码,所以很多时候,我们不需要,为了debug专门的计算某些值
最好的用的是绿色箭头,它可以直接走到下一个断点
​ 点击debug的两个红点,选择添加,可以发现idea有那种被动的debug机制,可以给字段和异常打上断点,这个时候我就发现了,如果我们想看某个字段的变化,可以在这里用,然后就是如果我们触发了某个异常,但是不知道异常触发的原因,我们也可以通过debug的方法查看,比如最头疼的空指针异常,而且这个是可以支持多线程调试的
​ 说实话,这个处理的结果和我想象的不太一样,那就先继续分析其他的流程了,不过我至少知道了,如果是外部的项目,我们可以通过Ctrl+Alt+F7来决定,到底是哪些类用了这个类的方法

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");
	}

SimpleServletHandlerAdapter

public class SimpleServletHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Servlet);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		//原来handler竟然是一个Servlet,不过想想确实很合理,看来经过spring的层层包装,最后还是我们的Servlet执行了最后的方法
		return null;
	}

	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		return -1;
	}

}

下面这个是doDispatch方法,这个就执行了我们定义的前置处理器,而我们定义的早就在mapperChain这条处理责任链里面

	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;//如果你判断假的话,一个!把这个变成真,然后停止方法的执行
	}
	//不过不得不说的是,这个!还是有点饶人的

AbstractHandlerMethodAdapter

这个好像就做了一个参数类的转换然后就丢给具体的实现类了,只有一个实现类

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {

	return handleInternal(request, response, (HandlerMethod) handler);
}

RequestMappingHandlerAdapter

这个类还是很复杂的,所以我只弄了断点跑过来的这个方法

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

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		//完成了session的判断,不过现在已经都是jwt,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 {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
			//我调试的时候,执行的是这个方法
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

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

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		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);

		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);

		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);
		}
		//上面对请求过来的参数做了相当多的处理,看名字能猜出一些,但是我不是很敢写这些注释,不过也不是什么很重要的方法

		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}

ServletInvocableHandlerMethod

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);//设置404,500,200这种状态码

	if (returnValue == null) {//如果你返回了一个null,spring也会帮你处理
		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);//我猜这是把对象处理成json的方法
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

InvocableHandlerMethod

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);//方法参数
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}

	return this.doInvoke(args);
}

InvocableHandlerMethod

@Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = this.getBridgedMethod();//终于走到真正的执行方法了

        try {
            return KotlinDetector.isSuspendingFunction(method) ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
        } catch (IllegalArgumentException var5) {
            this.assertTargetBean(method, this.getBean(), args);
            String text = var5.getMessage() != null ? var5.getMessage() : "Illegal argument";
            throw new IllegalStateException(this.formatInvokeError(text, args), var5);
        } catch (InvocationTargetException var6) {
            Throwable targetException = var6.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException)targetException;
            } else if (targetException instanceof Error) {
                throw (Error)targetException;
            } else if (targetException instanceof Exception) {
                throw (Exception)targetException;
            } else {
                throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
            }
        }
    }

!现在方法执行完成,开始处理返回结果,我们只关注到json对象的生成,然后一直交给servlet方法

HandlerMethodReturnValueHandlerComposite

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	} else {
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}
}

RequestResponseBodyMethodProcessor

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	mavContainer.setRequestHandled(true);
	ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

	// Try even with null return value. ResponseBodyAdvice could get involved.
	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

AbstractMessageConverterMethodProcessor

你看下面那个方法名,用MessageConverters来实现了,这样就和我们之前学的结合起来了

	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {//字符类型,字符串也是,因为它实现了这个接口,所以我们定义的类,不要乱实现这个,根据我的经验,这个应该是处理视图的
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

		MediaType selectedMediaType = null;
		MediaType contentType = outputMessage.getHeaders().getContentType();
		boolean isContentTypePreset = contentType != null && contentType.isConcrete();
		if (isContentTypePreset) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> acceptableTypes;
			try {
				acceptableTypes = getAcceptableMediaTypes(request);
			}
			catch (HttpMediaTypeNotAcceptableException ex) {
				int series = outputMessage.getServletResponse().getStatus() / 100;
				if (body == null || series == 4 || series == 5) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignoring error response content (if any). " + ex);
					}
					return;
				}
				throw ex;
			}
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}

			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
			//你会发现上面的方法在根据http协议定义的接受类型来决定自己要做什么事情,当然这些是通过debug看出来的

			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;//这里选择了Json类型然后break了
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}
		

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			//没想到spring在决定用哪个类型处理器的时候,用的是for循环,我也是在for了几次之后,发现用的处理器实际上是MappingJackson2CborHttpMessageConverter
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

		if (body != null) {
			Set<MediaType> producibleMediaTypes =
					(Set<MediaType>) inputMessage.getServletRequest()
							.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

			if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
				throw new HttpMessageNotWritableException(
						"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
			}
			throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
		}
	}

下面的比较重复,今天看的太多了,而且返回其实不很重要,这个环节是很少会出现错误的。

其他

​ 其他的地方,书上讲的不太全,而且不是很常用,我打算从网上找一些常用的东西,然后补充一下,其他的部分,我会在之后看更深入的书籍来补充自己的知识

如何获取request

ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

​ 通过上面这个方法,我们就可以在AOP中获取对应的参数,然后做我们想做的处理,我发现,可以把用户的id,用户的常用信息设置在request的属性里面,然后我们可以通过Controller的相关注解获取

弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件。本文主要介绍使用注解方式配置的spring mvc,之前写的spring3.0 mvcrest小例子没有介绍到数据层的内容,现在这一篇补上。下面开始贴代码。 文中用的框架版本:spring 3,hibernate 3,没有的,自己上网下。 先说web.xml配置: [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 03. <display-name>s3h3</display-name> 04. <context-param> 05. <param-name>contextConfigLocation</param-name> 06. <param-value>classpath:applicationContext*.xml</param-value> 07. </context-param> 08. <listener> 09. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10. </listener> 11. 12. <servlet> 13. <servlet-name>spring</servlet-name> 14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15. <load-on-startup>1</load-on-startup> 16. </servlet> 17. <servlet-mapping> 18. <servlet-name>spring</servlet-name> <!-- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --> 19. <url-pattern>*.do</url-pattern> 20. </servlet-mapping> 21. <welcome-file-list> 22. <welcome-file>index.jsp</welcome-file> 23. </welcome-file-list> 24.</web-app> spring-servlet,主要配置controller的信息 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02. <beans 03. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 04. xmlns:context="http://www.springframework.org/schema/context" 05. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 06. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 07. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 09. 10. <context:annotation-config /> 11. <!-- 把标记了@Controller注解的类转换为bean --> 12. <context:component-scan base-package="com.mvc.controller" /> 13. <!-- 启动Spring MVC的注解功能,完成请求注解POJO的映射 --> 14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 18. p:prefix="/WEB-INF/view/" p:suffix=".jsp" /> 19. 20. <bean id="multipartResolver" 21. class="org.springframework.web.multipart.commons.CommonsMultipartResolver" 22. p:defaultEncoding="utf-8" /> 23. </beans> applicationContext.xml代码 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<beans 03. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 04. xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" 05. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 06. xsi:schemaLocation=" 07. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 09. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 11. 12. <context:annotation-config /> 13. <context:component-scan base-package="com.mvc" /> <!-- 自动扫描所有注解该路径 --> 14. 15. <context:property-placeholder location="classpath:/hibernate.properties" /> 16. 17. <bean id="sessionFactory" 18. class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 19. <property name="dataSource" ref="dataSource" /> 20. <property name="hibernateProperties"> 21. <props> 22. <prop key="hibernate.dialect">${dataSource.dialect}</prop> 23. <prop key="hibernate.hbm2ddl.auto">${dataSource.hbm2ddl.auto}</prop> 24. <prop key="hibernate.hbm2ddl.auto">update</prop> 25. </props> 26. </property> 27. <property name="packagesToScan"> 28. <list> 29. <value>com.mvc.entity</value><!-- 扫描实体类,也就是平时所说的model --> 30. </list> 31. </property> 32. </bean> 33. 34. <bean id="transactionManager" 35. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 36. <property name="sessionFactory" ref="sessionFactory" /> 37. <property name="dataSource" ref="dataSource" /> 38. </bean> 39. 40. <bean id="dataSource" 41. class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 42. <property name="driverClassName" value="${dataSource.driverClassName}" /> 43. <property name="url" value="${dataSource.url}" /> 44. <property name="username" value="${dataSource.username}" /> 45. <property name="password" value="${dataSource.password}" /> 46. </bean> 47. <!-- Dao的实现 --> 48. <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> 49. <property name="sessionFactory" ref="sessionFactory" /> 50. </bean> 51. <tx:annotation-driven transaction-manager="transactionManager" /> 52. <tx:annotation-driven mode="aspectj"/> 53. 54. <aop:aspectj-autoproxy/> 55.</beans> hibernate.properties数据库连接配置 [java] view plaincopy 01.dataSource.password=123 02.dataSource.username=root 03.dataSource.databaseName=test 04.dataSource.driverClassName=com.mysql.jdbc.Driver 05.dataSource.dialect=org.hibernate.dialect.MySQL5Dialect 06.dataSource.serverName=localhost:3306 07.dataSource.url=jdbc:mysql://localhost:3306/test 08.dataSource.properties=user=${dataSource.username};databaseName=${dataSource.databaseName};serverName=${dataSource.serverName};password=${dataSource.password} 09.dataSource.hbm2ddl.auto=update 配置已经完成,下面开始例子 先在数据库建表,例子用的是mysql数据库 [java] view plaincopy 01.CREATE TABLE `test`.`student` ( 02. `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 03. `name` varchar(45) NOT NULL, 04. `psw` varchar(45) NOT NULL, 05. PRIMARY KEY (`id`) 06.) 建好表后,生成实体类 [java] view plaincopy 01.package com.mvc.entity; 02. 03.import java.io.Serializable; 04. 05.import javax.persistence.Basic; 06.import javax.persistence.Column; 07.import javax.persistence.Entity; 08.import javax.persistence.GeneratedValue; 09.import javax.persistence.GenerationType; 10.import javax.persistence.Id; 11.import javax.persistence.Table; 12. 13.@Entity 14.@Table(name = "student") 15.public class Student implements Serializable { 16. private static final long serialVersionUID = 1L; 17. @Id 18. @Basic(optional = false) 19. @GeneratedValue(strategy = GenerationType.IDENTITY) 20. @Column(name = "id", nullable = false) 21. private Integer id; 22. @Column(name = "name") 23. private String user; 24. @Column(name = "psw") 25. private String psw; 26. public Integer getId() { 27. return id; 28. } 29. public void setId(Integer id) { 30. this.id = id; 31. } 32. 33. public String getUser() { 34. return user; 35. } 36. public void setUser(String user) { 37. this.user = user; 38. } 39. public String getPsw() { 40. return psw; 41. } 42. public void setPsw(String psw) { 43. this.psw = psw; 44. } 45.} Dao层实现 [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.public interface EntityDao { 06. public List<Object> createQuery(final String queryString); 07. public Object save(final Object model); 08. public void update(final Object model); 09. public void delete(final Object model); 10.} [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.import org.hibernate.Query; 06.import org.springframework.orm.hibernate3.HibernateCallback; 07.import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 08. 09.public class EntityDaoImpl extends HibernateDaoSupport implements EntityDao{ 10. public List<Object> createQuery(final String queryString) { 11. return (List<Object>) getHibernateTemplate().execute( 12. new HibernateCallback<Object>() { 13. public Object doInHibernate(org.hibernate.Session session) 14. throws org.hibernate.HibernateException { 15. Query query = session.createQuery(queryString); 16. List<Object> rows = query.list(); 17. return rows; 18. } 19. }); 20. } 21. public Object save(final Object model) { 22. return getHibernateTemplate().execute( 23. new HibernateCallback<Object>() { 24. public Object doInHibernate(org.hibernate.Session session) 25. throws org.hibernate.HibernateException { 26. session.save(model); 27. return null; 28. } 29. }); 30. } 31. public void update(final Object model) { 32. getHibernateTemplate().execute(new HibernateCallback<Object>() { 33. public Object doInHibernate(org.hibernate.Session session) 34. throws org.hibernate.HibernateException { 35. session.update(model); 36. return null; 37. } 38. }); 39. } 40. public void delete(final Object model) { 41. getHibernateTemplate().execute(new HibernateCallback<Object>() { 42. public Object doInHibernate(org.hibernate.Session session) 43. throws org.hibernate.HibernateException { 44. session.delete(model); 45. return null; 46. } 47. }); 48. } 49.} Dao在applicationContext.xml注入 <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Dao只有一个类的实现,直接供其它service层调用,如果你想更换为其它的Dao实现,也只需修改这里的配置就行了。 开始写view页面,WEB-INF/view下新建页面student.jsp,WEB-INF/view这路径是在spring-servlet.xml文件配置的,你可以配置成其它,也可以多个路径。student.jsp代码 [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>添加</title> 09.<mce:script language="javascript" src="<%=request.getContextPath()%><!-- 10./script/jquery.min.js"> 11.// --></mce:script> 12.<mce:style><!-- 13.table{ border-collapse:collapse; } 14.td{ border:1px solid #f00; } 15.--></mce:style><style mce_bogus="1">table{ border-collapse:collapse; } 16.td{ border:1px solid #f00; }</style> 17.<mce:script type="text/javascript"><!-- 18.function add(){ 19. [removed].href="<%=request.getContextPath() %>/student.do?method=add"; 20.} 21. 22.function del(id){ 23.$.ajax( { 24. type : "POST", 25. url : "<%=request.getContextPath()%>/student.do?method=del&id;=" + id, 26. dataType: "json", 27. success : function(data) { 28. if(data.del == "true"){ 29. alert("删除成功!"); 30. $("#" + id).remove(); 31. } 32. else{ 33. alert("删除失败!"); 34. } 35. }, 36. error :function(){ 37. alert("网络连接出错!"); 38. } 39.}); 40.} 41.// --></mce:script> 42.</head> 43.<body> 44. 45.<input id="add" type="button" value="添加"/> 46.<table > 47. <tr> 48. <td>序号</td> 49. <td>姓名</td> 50. <td>密码</td> 51. <td>操作</td> 52. </tr> 53. <c:forEach items="${list}" var="student"> 54. <tr id="<c:out value="${student.id}"/>"> 55. <td><c:out value="${student.id}"/></td> 56. <td><c:out value="${student.user}"/></td> 57. <td><c:out value="${student.psw}"/></td> 58. <td> 59. <input type="button" value="编辑"/> 60. <input type="button" value="${student.id}"/>')" value="删除"/> 61. </td> 62. </tr> 63. </c:forEach> 64. 65.</table> 66.</body> 67.</html> student_add.jsp [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>学生添加</title> 09.<mce:script type="text/javascript"><!-- 10.function turnback(){ 11. [removed].href="<%=request.getContextPath() %>/student.do"; 12.} 13.// --></mce:script> 14.</head> 15.<body> 16.<form method="post" action="<%=request.getContextPath() %>/student.do?method=save"> 17.<div><c:out value="${addstate}"></c:out></div> 18.<table> 19. <tr><td>姓名</td><td><input id="user" name="user" type="text" /></td></tr> 20. <tr><td>密码</td><td><input id="psw" name="psw" type="text" /></td></tr> 21. <tr><td colSpan="2" align="center"><input type="submit" value="提交"/><input type="button" value="返回" /> </td></tr> 22.</table> 23. 24.</form> 25.</body> 26.</html> controller类实现,只需把注解写上,spring就会自动帮你找到相应的bean,相应的注解标记意义,不明白的,可以自己查下@Service,@Controller,@Entity等等的内容。 [java] view plaincopy 01.package com.mvc.controller; 02. 03.import java.util.List; 04. 05.import javax.servlet.http.HttpServletRequest; 06.import javax.servlet.http.HttpServletResponse; 07. 08.import org.apache.commons.logging.Log; 09.import org.apache.commons.logging.LogFactory; 10.import org.springframework.beans.factory.annotation.Autowired; 11.import org.springframework.stereotype.Controller; 12.import org.springframework.ui.ModelMap; 13.import org.springframework.web.bind.annotation.RequestMapping; 14.import org.springframework.web.bind.annotation.RequestMethod; 15.import org.springframework.web.bind.annotation.RequestParam; 16.import org.springframework.web.servlet.ModelAndView; 17. 18.import com.mvc.entity.Student; 19.import com.mvc.service.StudentService; 20. 21.@Controller 22.@RequestMapping("/student.do") 23.public class StudentController { 24. protected final transient Log log = LogFactory 25. .getLog(StudentController.class); 26. @Autowired 27. private StudentService studentService; 28. public StudentController(){ 29. 30. } 31. 32. @RequestMapping 33. public String load(ModelMap modelMap){ 34. List<Object> list = studentService.getStudentList(); 35. modelMap.put("list", list); 36. return "student"; 37. } 38. 39. @RequestMapping(params = "method=add") 40. public String add(HttpServletRequest request, ModelMap modelMap) throws Exception{ 41. return "student_add"; 42. } 43. 44. @RequestMapping(params = "method=save") 45. public String save(HttpServletRequest request, ModelMap modelMap){ 46. String user = request.getParameter("user"); 47. String psw = request.getParameter("psw"); 48. Student st = new Student(); 49. st.setUser(user); 50. st.setPsw(psw); 51. try{ 52. studentService.save(st); 53. modelMap.put("addstate", "添加成功"); 54. } 55. catch(Exception e){ 56. log.error(e.getMessage()); 57. modelMap.put("addstate", "添加失败"); 58. } 59. 60. return "student_add"; 61. } 62. 63. @RequestMapping(params = "method=del") 64. public void del(@RequestParam("id") String id, HttpServletResponse response){ 65. try{ 66. Student st = new Student(); 67. st.setId(Integer.valueOf(id)); 68. studentService.delete(st); 69. response.getWriter().print("{/"del/":/"true/"}"); 70. } 71. catch(Exception e){ 72. log.error(e.getMessage()); 73. e.printStackTrace(); 74. } 75. } 76.} service类实现 [java] view plaincopy 01.package com.mvc.service; 02. 03.import java.util.List; 04. 05.import org.springframework.beans.factory.annotation.Autowired; 06.import org.springframework.stereotype.Service; 07.import org.springframework.transaction.annotation.Transactional; 08. 09.import com.mvc.dao.EntityDao; 10.import com.mvc.entity.Student; 11. 12.@Service 13.public class StudentService { 14. @Autowired 15. private EntityDao entityDao; 16. 17. @Transactional 18. public List<Object> getStudentList(){ 19. StringBuffer sff = new StringBuffer(); 20. sff.append("select a from ").append(Student.class.getSimpleName()).append(" a "); 21. List<Object> list = entityDao.createQuery(sff.toString()); 22. return list; 23. } 24. 25. public void save(Student st){ 26. entityDao.save(st); 27. } 28. public void delete(Object obj){ 29. entityDao.delete(obj); 30. } 31.} OK,例子写完。有其它业务内容,只需直接新建view,并实现相应comtrollerservice就行了,配置dao层的内容基本不变,也就是每次只需写jsp(view),controllerservice调用dao就行了。 怎样,看了这个,spring mvc是不是比ssh实现更方便灵活。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值