`
cheng.xinwei
  • 浏览: 78424 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

【spring】spring是如何处理请求的?源码分析

阅读更多
最近在处理一些问题的时候,突然想到spring的两个问题。
1.spring是如何回调我们的controller中定义的方法的,request response model 是怎么来的?为什么先后顺序可以颠倒?
2.spring是如何帮我们注入基本类型的参数的,比如我参数中有两个String,但是参数名字不一样,一个是id,一个是type,但是spring可以直接帮我们注入进来,不需要添加任何注解,他是怎么实现的?
抱着这两个问题,我开始看spring是如何处理请求的。


首先,spring是为我们封装了servlet,所以肯定会有一个类继承HttpServlet,从这个入口开始寻找,我们就找到了DispatcherServlet ,他是HttpServlet的一个实现类。其中重写了父类的doService方法。看到方法最后有调用doDispatch(request, response);方法,到了spring的处理器。

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	int interceptorIndex = -1;

	try {
		ModelAndView mv;
		boolean errorView = false;

		try {
			processedRequest = checkMultipart(request);

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest, false);
			if (mappedHandler == null || mappedHandler.getHandler() == 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 = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					String requestUri = urlPathHelper.getRequestUri(request);
					logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			// Apply preHandle methods of registered interceptors.
			HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
			if (interceptors != null) {
				for (int i = 0; i < interceptors.length; i++) {
					HandlerInterceptor interceptor = interceptors[i];
					if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
					        triggerAfterCompletion(mappedHandler, interceptorIndex, 
                                                     processedRequest, response, null);
						return;
					}
					interceptorIndex = i;
				}
			}

			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			// Do we need view name translation?
			if (mv != null && !mv.hasView()) {
				mv.setViewName(getDefaultViewName(request));
			}

			// Apply postHandle methods of registered interceptors.
			if (interceptors != null) {
				for (int i = interceptors.length - 1; i >= 0; i--) {
					HandlerInterceptor interceptor = interceptors[i];
					interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
				}
			}
		}catch (ModelAndViewDefiningException ex) {
			logger.debug("ModelAndViewDefiningException encountered", ex);
			mv = ex.getModelAndView();
		}catch (Exception ex) {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(processedRequest, response, handler, ex);
			errorView = (mv != null);
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, processedRequest, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
					"': assuming HandlerAdapter completed request handling");
			}
		}

		// Trigger after-completion for successful outcome.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
	}catch (Exception ex) {
		// Trigger after-completion for thrown exception.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
		throw ex;
	}catch (Error err) {
		ServletException ex = new NestedServletException("Handler processing failed", err);
		// Trigger after-completion for thrown exception.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
		throw ex;
	}finally {
		// Clean up any resources used by a multipart request.
		if (processedRequest != request) {
			cleanupMultipart(processedRequest);
		}
	}
}



    在这个方法中做了如下几件事情
1.寻找URL所对应的mappedHandler处理器,如果找不到的话,执行noHandlerFound方法,其中就是会返回404。
2.如果是get请求的话,并且HTTP请求头标签中包含If-Modified-Since,在发送HTTP请求时,把浏览器端缓存页面的最后修改时间一起发到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行比较。如果时间一致,那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文件显示到浏览器中。
3.获取到自己定义的拦截器,循环invoke拦截器。
4.回调我们所定义的mapperHandler 也就是我们再controller中所定义的处理这个请求的方法。
5.获取到返回的 MV 通过response写出。

下面主要来看下第四部,spring是怎么回调我们自己定义的controller的。

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这里ha是HandlerAdapter 接口,根据spring的配置文件,走不同的处理器。
 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="webBindingInitializer" ref="webBindingInitializer"/>
        <property name="requireSession" value="false"/>
        <property name="order" value="0"/>
    </bean>



    如上配置可以看到,配置的处理器为RequestMappingHandlerAdapter。他实现了父类的handler方法,然后调用子类的handleInternal方法处理请求。

/**
 * {@inheritDoc} <p>This implementation expects the handler to be an {@link HandlerMethod}.
 */
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) hrows Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}


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

	if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()){
	     // Always prevent caching in case of session attribute management.
	     checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
	}
	else {
		// Uses configured default cacheSeconds setting.
		checkAndPrepare(request, response, true);
	}
		
	// Execute invokeHandlerMethod in synchronized block if required.
	if (this.synchronizeOnSession) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
				return invokeHandlerMethod(request, response, handlerMethod);
			}
		}
	}
		
	return invokeHandlerMethod(request, response, handlerMethod);
}


    RequestMappingHandlerAdapter中实现了父类的handleInternal,在这个类里面主要做了一些限制判断。
1.如HTTP请求方式和方法上限制了HTTP请求不同的话,则会抛出HttpRequestMethodNotSupportedException。
2.如果在配置文件中配置了requireSession=true的话,此处会抛出 HttpSessionRequiredException("Pre-existing session required but none found");

现在回到我们handleInternal方法中,接下去如果我们配置了synchronizeOnSession配置,表示该控制器是否在执行时同步session,从而保证该会话的用户串行访问该控制器。接下去执行invokeHandlerMethod方法。


/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} if view resolution is required.
 */
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,
		HandlerMethod handlerMethod) throws Exception {
		
	ServletWebRequest webRequest = new ServletWebRequest(request, response);

	WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
	ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
	ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);

	ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
	modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
	mavContainer.setIgnoreDefaultModelOnRedirect
             (this.ignoreDefaultModelOnRedirect);

	requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
	modelFactory.updateModel(webRequest, mavContainer);

	if (mavContainer.isRequestHandled()) {
		return null;
	}else {
		ModelMap model = mavContainer.getModel();
		ModelAndView mav = new
                      ModelAndView(mavContainer.getViewName(), model);
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}
		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();				 
                        RequestContextUtils.getOutputFlashMap(request).
                              putAll(flashAttributes);
		}
		return mav;
	}
}

这里主要看一下 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); 中的invokeForRequest方法。

public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
	 Object... providedArgs) throws Exception {
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

	if (logger.isTraceEnabled()) {
		StringBuilder builder = new StringBuilder("Invoking [");
		builder.append(this.getMethod().getName()).append("] method with arguments ");
		builder.append(Arrays.asList(args));
		logger.trace(builder.toString());
	}

	Object returnValue = invoke(args);

	if (logger.isTraceEnabled()) {
		logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue + "]");
	}

	return returnValue;
}




    这个方法中,主要做两件事情,
1.获取到了本次请求所对应执行方法的参数。
2.通过反射invoke 调用方法。注入参数
这里也是让我一开始很困惑的地方,因为反射只能获取到方法参数的类型,无法获取到方法参数的变量名,那spring是如何做到获取到方法参数变量名呢?我们继续往下面看。

看到spring通过getMethodArgumentValues方法获取了所有的参数列表。

/**
 * Get the method argument values for the current request.
 */
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {

	MethodParameter[] parameters = getMethodParameters();
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(parameterNameDiscoverer);
		GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
		args[i] = resolveProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}

		if (argumentResolvers.supportsParameter(parameter)) {
			try {
				args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
				continue;
			} catch (Exception ex) {
				if (logger.isTraceEnabled()) {
					logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
				}
				throw ex;
			}
		}

		if (args[i] == null) {
			String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
			throw new IllegalStateException(msg);
		}
	}
	return args;
}


    其中resolveArgument的方法中HandlerMethodArgumentResolver是一个接口,通过 getArgumentResolver(parameter); 方法获取到不同的解析器。来解析参数值。
HandlerMethodArgumentResolver接口中定义了resolveArgument让子类来实现方法,获取到具体的参数,在@RequestParam注解的参数的解析器是使用的AbstractNamedValueMethodArgumentResolver类来实现的。我们具体来看一下AbstractNamedValueMethodArgumentResolver类中的实现。

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
	NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throws Exception {

	Class<?> paramType = parameter.getParameterType();

	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);

	Object arg = resolveName(namedValueInfo.name, parameter, webRequest);

	if (arg == null) {
		if (namedValueInfo.defaultValue != null) {
			arg = resolveDefaultValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required) {
			handleMissingValue(namedValueInfo.name, parameter);
		}
		arg = handleNullValue(namedValueInfo.name, arg, paramType);
	}

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		arg = binder.convertIfNecessary(arg, paramType, parameter);
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
}



    看到如上代码中,首先获取到了某个参数的类型,再通过getNamedValueInfo(parameter);方法返回了NamedValueInfo对象,在NamedValueInfo对象中,就已经包含了当前参数的名字是什么,那就继续看getNamedValueInfo方法。


/**
 * Obtain the named value for the given method parameter.
 */
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
	NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
	if (namedValueInfo == null) {
		namedValueInfo = createNamedValueInfo(parameter);
		namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
		this.namedValueInfoCache.put(parameter, namedValueInfo);
	}
	return namedValueInfo;
}



    spring代码中写了一个cache用于保存已经解析过的nameValueInfo,因为在程序运行期间,这个值肯定是固定的。
如果cache中返回空的话,则会通过createNamedValueInfo(parameter);方法创建一个nameValueInfo,再通过updateNamedValueInfo(parameter, namedValueInfo);更新内部的name,这里createNamedValueInfo是一个抽象方法,通过子类去实现,如PathVariableMethodArgumentResolver,RequestParamMethodArgumentResolver等等。这里我们主要看一下RequestParamMethodArgumentResolver中的createNamedValueInfo方法。和


@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
	RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
	return (annotation != null) ? 
			new RequestParamNamedValueInfo(annotation) : 
			new RequestParamNamedValueInfo();
}


    首先,spring先获取了我们家在参数上的RequestParam的注解,如果为空的话,就调用RequestParamNamedValueInfo的无参构造,否则通过annotation构建RequestParamNamedValueInfo。默认的值为value="" , require = false ,  DEFAULT_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"。
    下面来看一下updateNamedValueInfo的方法,


/**
 * Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
 */
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
	String name = info.name;
	if (info.name.length() == 0) {
		name = parameter.getParameterName();
		Assert.notNull(name, "Name for argument type [" + parameter.getParameterType().getName()
					+ "] not available, and parameter name information not found in class file either.");
	}
	String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
	return new NamedValueInfo(name, info.required, defaultValue);
}



    首先先获取了info里面的name,如果name非空的话,name=parameter.getParameterName();  , 在getParameterName方法中获取到了参数的变量名。

public String getParameterName() {
	if (this.parameterNameDiscoverer != null) {
		String[] parameterNames = (this.method != null ?
				this.parameterNameDiscoverer.getParameterNames(this.method) :
				this.parameterNameDiscoverer.getParameterNames(this.constructor));
		if (parameterNames != null) {
			this.parameterName = parameterNames[this.parameterIndex];
		}
		this.parameterNameDiscoverer = null;
	}
	return this.parameterName;
}


    可以看到根据当前的method是否为空来判断是根据method获取参数名还是根据构造器获取参数名。我们现在这里是通过LocalVariableTableParameterNameDiscoverer中的getParameterNames方法来获取参数名字,返回的是一个参数名字的数组,然后根据当前的索引值parameterIndex来确定返回的参数是哪一个,下面来看下一内部的具体实现。



public String[] getParameterNames(Method method) {
	Class<?> declaringClass = method.getDeclaringClass();
	Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
	if (map == null) {
		// initialize cache
		map = inspectClass(declaringClass);
		this.parameterNamesCache.put(declaringClass, map);
	}
	if (map != NO_DEBUG_INFO_MAP) {
		return map.get(method);
	}
	return null;
}


首先先获取当前method的作用的class,先在缓存中看看当前class有没有,(注意缓存的结构,Map<Class<?>, Map<Member, String[]>> key是class , value是Method 和 方法参数的对应关系)。如果有的话直接返回,否则把当前class传入inspectClass方法获取方法的参数值。



/**
 * Inspects the target class. Exceptions will be logged and a maker map returned
 * to indicate the lack of debug information.
 */
private Map<Member, String[]> inspectClass(Class<?> clazz) {
	InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
	if (is == null) {
		// We couldn't load the class file, which is not fatal as it
		// simply means this method of discovering parameter names won't work.
		if (logger.isDebugEnabled()) {
			logger.debug("Cannot find '.class' file for class [" + clazz
					+ "] - unable to determine constructors/methods parameter names");
		}
		return NO_DEBUG_INFO_MAP;
	}
	try {
		ClassReader classReader = new ClassReader(is);
		Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>();
		classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), false);
		return map;
	}
	catch (IOException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Exception thrown while reading '.class' file for class [" + clazz
					+ "] - unable to determine constructors/methods parameter names", ex);
		}
	}
	finally {
		try {
			is.close();
		}
		catch (IOException ex) {
			// ignore
		}
	}
	return NO_DEBUG_INFO_MAP;
}


    这里spring通过类加载器获取到这个类的inputStream,最后通过了ClassReader获取到了类中的所有方法和方法参数,依赖于asm的jar包。ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。具体请看
http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html

    看到这里才知道,原来spring不是通过反射来获取到的,而是通过asm jar包来解析class的字节获取到了方法的参数名字,用来注入到方法中。



下面是本人看完spring源码的一点心得
1.spring 在很多地方使用了缓存Map,用于提高性能,我们再编写代码的时候是否也可以做到呢?
2.java本来是反射是不支持获取到方法参数的变量名的,spring为了提供更好的服务,通过解析class的字节码,来完成这项功能,那我们在编写代码的时候是否可以做到呢?
分享到:
评论
2 楼 zhangdong92 2017-09-14  
终于找到这个我很疑惑的问题的答案了。之前也遇到过想在一个方法里想要获取传入的参数的名称的情况,但是代码中包括反射都拿不到这个方法名。所以很奇怪Spring的@RequestParam是怎么获取到我们定义的变量名的。据我了解,可能拿到参数名信息的,只有编译时-g参数写入的信息。(刚知道jdk1.8也可以用-parameter参数记录参数名信息)
搜到了这篇才知道,原来spring就是用这种方法通过asm读取class字节码取到参数名的。不过实现起来还是挺复杂的。org.springframework.core.LocalVariableTableParameterNameDiscoverer的inspectClass方法中,用classReader.accept(...)读取class文件字节码,解析方法时调用org.springframework.core.LocalVariableTableParameterNameDiscoverer.LocalVariableTableParameterNameDiscoverer的visitMethod方法,desc参数中包含了方法参数信息,包括参数名。
所以在jdk1.8之前,如果编译没有-g参数的话,根据源码,估计spring会抛出异常了:
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#updateNamedValueInfo():
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");

还学到了一个神奇的新用法,返回的map对象,如果想传递一个额外的错误信息,可以在拿到返回值后用==NO_DEBUG_INFO_MAP判断。
1 楼 krdsg 2014-02-27  
楼主牛逼啊 刨根问底 受教了

相关推荐

    Springboot请求处理源码分析(含源代码详细注释)

    Springboot请求处理源码分析(含源代码详细注释)

    Spring源码学习十:DispatcherServlet请求分发源码分析1

    总结:首先,SpringMVC框架在启动的时候会遍历Spring容器中的所有bean,对标注了@Controller或并,使用@RequestMapping注解

    Spring高级之注解驱动开发视频教程

    视频详细讲解,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 1、课程简介 Spring框架是一系列应用框架的核心,也可以说是整合其他应用框架的基座。...n 源码分析-TransactionSynchronizationManager

    Spring MVC+MyBatis开发从入门到项目实战

    第3篇是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和拦截...

    新版本SpringCloud2学习手册

    负载均衡器源码分析 33 负载均衡器重试机制 33 服务保护机制SpringCloud Hystrix 33 微服务高可用技术 33 服务降级、熔断、限流概念 34 服务学崩效应 34 服务降级 34 服务熔断 35 服务隔离 35 服务限流 36 ...

    SpringMVC源码分析.md

    Spring的源码分析 在分析SpringMVC源码之前我想先回顾一下JavaWeb的知识.JavaWeb的核心是Servlet,一个Servlet对应一个URL, 每次一个Http请求访问,那么对应URL的Servlet就会调用service方法处理。 其实这里我是对...

    SpringMVC源码分析.xmind

    该思维导入详细描述了SpringMVC的处理request请求的流程,每个流程都描述了方法入口以及具体业务处理步骤,适合spring源码入门新手的参考手册,指引读者一步步阅读源码。

    基于Java的学生成绩管理系统,Spring+Jquery UI+Ajax,优秀毕业设计源代码+答辩PPT,新手必看!

    Struts2——请求响应 Spring——java对象注入 Spring JDBC——数据库连接 Jquery UI——界面设计 JFreeChart——成绩分析 iText——成绩打印PDF格式 Ajax+Json——表单验证 系统登录用户: 学生用户(学号+密码+...

    spring mvc源码分析一

    spring mvc的版本是5.x的,sping mvc的入口是 DispatcherServlet 类 ,在之前的ssm项目中,我们是通过配置在web.xml中的servlet把请求的入口设置为DispatcherServlet,现在的版本官方不提倡编写web.xml,而是如下: ...

    学习SSM源码分析的一次实践,自己实现SSM框架

    关于spring中的注解,采用和spring一样的注解名字,在springMVC方面,采用统一的前端控制器dispatcherServlet,处理统一请求进行分发。 对应 mybatis 是采用代理的方式 对接口进行生成对应的代理对象,加载对应的xml...

    java_jsp项目源码_网上订餐系统(struts+spring+hibernate).rar

    * 订单处理:餐厅可以查看、接受或拒绝顾客的订单请求,并实时跟踪订单状态。 * 顾客反馈:餐厅可以查看顾客的评价和反馈,以便改进服务质量。 3. **管理员模块:** * 餐厅审核:管理员可以审核新注册的餐厅,...

    springmvc:Spring MVC教程

    Spring MVC教程 教程学习: 本教程完整地讲解了Spring MVC的每个知识点,包括...Spring MVC 核心源码分析 @RequestMapping注解 Spring MVC请求参数封装 Spring MVC 基本类型封装 Spring MVC Post中文乱码 @RequestPara

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+数据库+项目说明.zip

    基于springcloud+Netty+MQ+mysql的分布式即时聊天系统源码+数据库+项目说明.zip # KT-Chat 分布式即时聊天系统 **技术选型**:Java、SpringCloud、Nacos、Sentinel、Netty、MySQL、Redis、RocketMQ 等 **项目描述**...

    大厂学院SVIP十门合集|完结无秘

    Spring全家桶源码分析 Tomcat架构原理 Web请求处理原理 数据访问层框架原理 架构与设计思维模式 程序中的数学 数据分析 机器智能算法剖析与应用 云原生 自动化DevOps 流量治理 链路监控 弹性扩容 分布式存储Redis6.0...

    java-advance:java进阶,包含 常用设计模式、线程和并发、spring核心代码、mybatis核心代码、springboot2.0、springcloud G版本、docker的使用、各类面试题

    java进阶源码分析专题常用设计模式线程与并发锁的使用深度理解synchronized、volatile、cas手写ASQSpring5IOC容器设计原理及高级特性AOP设计原理FactoryBean与BeanFactorySpring事务处理机制Spring JDK动态代理...

    【spring-boot-seckill分布式秒杀系统 v1.0】从0到1构建的java秒杀系统源码+安装说明

    4、异步、异步、异步,分析并识别出可以异步处理的逻辑,比如日志,缩短系统响应时间。 5、主备、主备、主备,如果有条件做好主备容灾方案也是非常有必要的(参考某年锤子的活动被攻击)。 6、最后,为了支撑更高的...

    SpringCloud微服务架构笔记(二)1

    1.2 基于Feign的服务调用 1.4 负载均衡 2.2 请求压缩 2.3 日志级别 2.4 源码分析

    SpringMVC MyBatis项目实战 入门

    第三部分是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和...

    ribbon.zip

    Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix ... 在这一章中,我们将具体介绍如何使用Ribbon来实现客户端的负载均衡,并且通过源码分析来了解Ribbon实现客户端负载均衡的基本原理。

    基于Extjs的DirectCenter框架源码

    用户请求和后台业务层的中转,接收并分析用户请求,调用业务层完成请求,再分发给用户。(注意这里调用的是IBLL,是业务逻辑接口,也是通过Spring.Net的IOC注入具体的业务实现)。 DirectCenter.Web (Web Application...

Global site tag (gtag.js) - Google Analytics