SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段。在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过service()方法,委派到doGet()或者doPost()这些方法,完成Http请求的处理。
在初始化流程中,SpringMVC巧妙的运用依赖注入读取参数,并最终建立一个与容器上下文相关联的Spring子上下文。这个子上下文,就像Struts2中xwork容器一样,为接下来的Http处理流程中各种编程元素提供了容身之所。如果说将Spring上下文关联到Servlet容器中,是SpringMVC框架的第一个亮点,那么在请求转发流程中,SpringMVC对各种处理环节编程元素的抽象,就是另外一个独具匠心的亮点。
Struts2采取的是一种完全和Web容器隔离和解耦的事件机制。诸如Action对象、Result对象、Interceptor对象,这些都是完全脱离Servlet容器的编程元素。Struts2将数据流和事件处理完全剥离开来,从Http请求中读取数据后,下面的事件处理流程就只依赖于这些数据,而完全不知道有Web环境的存在。
反观SpringMVC,无论HandlerMapping对象、HandlerAdapter对象还是View对象,这些核心的接口所定义的方法中,HttpServletRequest和HttpServletResponse对象都是直接作为方法的参数出现的。这也就意味着,框架的设计者,直接将SpringMVC框架和容器绑定到了一起。或者说,整个SpringMVC框架,都是依托着Servlet容器元素来设计的。下面就来看一下,源码中是如何体现这一点的。
1.请求转发的入口
就像任何一个注册在容器中的Servlet一样,DispatcherServlet也是通过自己的service()方法来接收和转发Http请求到具体的doGet()或doPost()这些方法的。以一次典型的GET请求为例,经过HttpServlet基类中service()方法的委派,请求会被转发到doGet()方法中。doGet()方法,在DispatcherServlet的父类FrameworkServlet类中被覆写。
2 |
protected final void doGet(HttpServletRequest
request, HttpServletResponse response) |
3 |
throws ServletException,
IOException { |
5 |
processRequest(request,
response); |
可以看到,这里只是简单的转发到processRequest()这个方法。
01 |
protected final void processRequest(HttpServletRequest
request, HttpServletResponse response) |
02 |
throws ServletException,
IOException { |
04 |
long startTime
= System.currentTimeMillis(); |
05 |
Throwable
failureCause = null; |
07 |
//
Expose current LocaleResolver and request as LocaleContext. |
08 |
LocaleContext
previousLocaleContext = LocaleContextHolder.getLocaleContext(); |
09 |
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable); |
11 |
//
Expose current RequestAttributes to current thread. |
12 |
RequestAttributes
previousRequestAttributes = RequestContextHolder.getRequestAttributes(); |
13 |
ServletRequestAttributes
requestAttributes = null; |
14 |
if (previousRequestAttributes
== null ||
previousRequestAttributes.getClass().equals(ServletRequestAttributes.class))
{ |
15 |
requestAttributes
= new ServletRequestAttributes(request); |
16 |
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); |
19 |
if (logger.isTraceEnabled())
{ |
20 |
logger.trace("Bound
request context to thread: " +
request); |
24 |
doService(request,
response); |
26 |
catch (ServletException
ex) { |
30 |
catch (IOException
ex) { |
34 |
catch (Throwable
ex) { |
36 |
throw new NestedServletException("Request
processing failed",
ex); |
40 |
//
Clear request attributes and reset thread-bound context. |
41 |
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable); |
42 |
if (requestAttributes
!= null)
{ |
43 |
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable); |
44 |
requestAttributes.requestCompleted(); |
46 |
if (logger.isTraceEnabled())
{ |
47 |
logger.trace("Cleared
thread-bound request context: " +
request); |
50 |
if (logger.isDebugEnabled())
{ |
51 |
if (failureCause
!= null)
{ |
52 |
this.logger.debug("Could
not complete request",
failureCause); |
55 |
this.logger.debug("Successfully
completed request"); |
58 |
if (this.publishEvents)
{ |
59 |
//
Whether or not we succeeded, publish an event. |
60 |
long processingTime
= System.currentTimeMillis() - startTime; |
61 |
this.webApplicationContext.publishEvent( |
62 |
new ServletRequestHandledEvent(this, |
63 |
request.getRequestURI(),
request.getRemoteAddr(), |
64 |
request.getMethod(),
getServletConfig().getServletName(), |
65 |
WebUtils.getSessionId(request),
getUsernameForRequest(request), |
66 |
processingTime,
failureCause)); |
代码有点长,理解的要点是以doService()方法为区隔,前一部分是将当前请求的Locale对象和属性,分别设置到LocaleContextHolder和RequestContextHolder这两个抽象类中的ThreadLocal对象中,也就是分别将这两个东西和请求线程做了绑定。在doService()处理结束后,再恢复回请求前的LocaleContextHolder和RequestContextHolder,也即解除线程绑定。每次请求处理结束后,容器上下文都发布了一个ServletRequestHandledEvent事件,你可以注册监听器来监听该事件。
可以看到,processRequest()方法只是做了一些线程安全的隔离,真正的请求处理,发生在doService()方法中。点开FrameworkServlet类中的doService()方法。
1 |
protected abstract void doService(HttpServletRequest
request, HttpServletResponse response) |
又是一个抽象方法,这也是SpringMVC类设计中的惯用伎俩:父类抽象处理流程,子类给予具体的实现。真正的实现是在DispatcherServlet类中。
让我们接着看DispatcherServlet类中实现的doService()方法。
02 |
protected void doService(HttpServletRequest
request, HttpServletResponse response) throws Exception
{ |
03 |
if (logger.isDebugEnabled())
{ |
04 |
String
requestUri = urlPathHelper.getRequestUri(request); |
05 |
logger.debug("DispatcherServlet
with name '" +
getServletName() + "'
processing " +
request.getMethod() + |
06 |
"
request for [" +
requestUri + "]"); |
09 |
//
Keep a snapshot of the request attributes in case of an include, |
10 |
//
to be able to restore the original attributes after the include. |
11 |
Map<String,
Object> attributesSnapshot = null; |
12 |
if (WebUtils.isIncludeRequest(request))
{ |
13 |
logger.debug("Taking
snapshot of request attributes before include"); |
14 |
attributesSnapshot
= new HashMap<String,
Object>(); |
15 |
Enumeration<?>
attrNames = request.getAttributeNames(); |
16 |
while (attrNames.hasMoreElements())
{ |
17 |
String
attrName = (String) attrNames.nextElement(); |
18 |
if (this.cleanupAfterInclude
|| attrName.startsWith("org.springframework.web.servlet"))
{ |
19 |
attributesSnapshot.put(attrName,
request.getAttribute(attrName)); |
24 |
//
Make framework objects available to handlers and view objects. |
25 |
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,
getWebApplicationContext()); |
26 |
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); |
27 |
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); |
28 |
request.setAttribute(THEME_SOURCE_ATTRIBUTE,
getThemeSource()); |
30 |
FlashMap
inputFlashMap = this.flashMapManager.retrieveAndUpdate(request,
response); |
31 |
if (inputFlashMap
!= null)
{ |
32 |
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
Collections.unmodifiableMap(inputFlashMap)); |
34 |
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); |
35 |
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); |
38 |
doDispatch(request,
response); |
41 |
//
Restore the original attribute snapshot, in case of an include. |
42 |
if (attributesSnapshot
!= null)
{ |
43 |
restoreAttributesAfterInclude(request,
attributesSnapshot); |
几个requet.setAttribute()方法的调用,将前面在初始化流程中实例化的对象设置到http请求的属性中,供下一步处理使用,其中有容器的上下文对象、本地化解析器等SpringMVC特有的编程元素。不同于Struts2中的ValueStack,SpringMVC的数据并没有从HttpServletRequest对象中抽离出来再存进另外一个编程元素,这也跟SpringMVC的设计思想有关。因为从一开始,SpringMVC的设计者就认为,不应该将请求处理过程和Web容器完全隔离。
所以,你可以看到,真正发生请求转发的方法doDispatch()中,它的参数是HttpServletRequest和HttpServletResponse对象。这给我们传递的意思也很明确,从request中能获取到一切请求的数据,从response中,我们又可以往服务器端输出任何响应,Http请求的处理,就应该围绕这两个对象来设计。我们不妨可以将SpringMVC这种设计方案,是从Struts2的过度设计中吸取教训,而向Servlet编程的一种回归和简化。
2.请求转发的抽象描述
接下来让我们看看doDispatch()这个整个请求转发流程中最核心的方法。DispatcherServlet所接收的Http请求,经过层层转发,最终都是汇总到这个方法中来进行最后的请求分发和处理。doDispatch()这个方法的内容,就是SpringMVC整个框架的精华所在。它通过高度抽象的接口,描述出了一个MVC(Model-View-Controller)设计模式的实现方案。Model、View、Controller三种层次的编程元素,在SpringMVC中都有大量的实现类,各种处理细节也是千差万别。但是,它们最后都是由,也都能由doDispatch()方法来统一描述,这就是接口和抽象的威力,万变不离其宗。
先来看一下doDispatch()方法的庐山真面目。
001 |
protected void doDispatch(HttpServletRequest
request, HttpServletResponse response) throws Exception
{ |
002 |
HttpServletRequest
processedRequest = request; |
003 |
HandlerExecutionChain
mappedHandler = null; |
004 |
int interceptorIndex
= -1; |
008 |
boolean errorView
= false; |
011 |
processedRequest
= checkMultipart(request); |
013 |
//
Determine handler for the current request. |
014 |
mappedHandler
= getHandler(processedRequest, false); |
015 |
if (mappedHandler
== null ||
mappedHandler.getHandler() == null)
{ |
016 |
noHandlerFound(processedRequest,
response); |
020 |
//
Determine handler adapter for the current request. |
021 |
HandlerAdapter
ha = getHandlerAdapter(mappedHandler.getHandler()); |
023 |
//
Process last-modified header, if supported by the handler. |
024 |
String
method = request.getMethod(); |
025 |
boolean isGet
= "GET".equals(method); |
026 |
if (isGet
|| "HEAD".equals(method))
{ |
027 |
long lastModified
= ha.getLastModified(request, mappedHandler.getHandler()); |
028 |
if (logger.isDebugEnabled())
{ |
029 |
String
requestUri = urlPathHelper.getRequestUri(request); |
030 |
logger.debug("Last-Modified
value for [" +
requestUri + "]
is: " +
lastModified); |
032 |
if (new ServletWebRequest(request,
response).checkNotModified(lastModified) && isGet) { |
037 |
//
Apply preHandle methods of registered interceptors. |
038 |
HandlerInterceptor[]
interceptors = mappedHandler.getInterceptors(); |
039 |
if (interceptors
!= null)
{ |
040 |
for (int i
= 0;
i < interceptors.length; i++) { |
041 |
HandlerInterceptor
interceptor = interceptors[i]; |
042 |
if (!interceptor.preHandle(processedRequest,
response, mappedHandler.getHandler())) { |
043 |
triggerAfterCompletion(mappedHandler,
interceptorIndex, processedRequest, response, null); |
046 |
interceptorIndex
= i; |
050 |
//
Actually invoke the handler. |
051 |
mv
= ha.handle(processedRequest, response, mappedHandler.getHandler()); |
053 |
//
Do we need view name translation? |
054 |
if (mv
!= null &&
!mv.hasView()) { |
055 |
mv.setViewName(getDefaultViewName(request)); |
058 |
//
Apply postHandle methods of registered interceptors. |
059 |
if (interceptors
!= null)
{ |
060 |
for (int i
= interceptors.length - 1;
i >= 0;
i--) { |
061 |
HandlerInterceptor
interceptor = interceptors[i]; |
062 |
interceptor.postHandle(processedRequest,
response, mappedHandler.getHandler(), mv); |
066 |
catch (ModelAndViewDefiningException
ex) { |
067 |
logger.debug("ModelAndViewDefiningException
encountered",
ex); |
068 |
mv
= ex.getModelAndView(); |
070 |
catch (Exception
ex) { |
071 |
Object
handler = (mappedHandler != null ?
mappedHandler.getHandler() : null); |
072 |
mv
= processHandlerException(processedRequest, response, handler, ex); |
073 |
errorView
= (mv != null); |
076 |
//
Did the handler return a view to render? |
077 |
if (mv
!= null &&
!mv.wasCleared()) { |
078 |
render(mv,
processedRequest, response); |
080 |
WebUtils.clearErrorRequestAttributes(request); |
084 |
if (logger.isDebugEnabled())
{ |
085 |
logger.debug("Null
ModelAndView returned to DispatcherServlet with name '" +
getServletName() + |
086 |
"':
assuming HandlerAdapter completed request handling"); |
090 |
//
Trigger after-completion for successful outcome. |
091 |
triggerAfterCompletion(mappedHandler,
interceptorIndex, processedRequest, response, null); |
094 |
catch (Exception
ex) { |
095 |
//
Trigger after-completion for thrown exception. |
096 |
triggerAfterCompletion(mappedHandler,
interceptorIndex, processedRequest, response, ex); |
100 |
ServletException
ex = new NestedServletException("Handler
processing failed",
err); |
101 |
//
Trigger after-completion for thrown exception. |
102 |
triggerAfterCompletion(mappedHandler,
interceptorIndex, processedRequest, response, ex); |
107 |
//
Clean up any resources used by a multipart request. |
108 |
if (processedRequest
!= request) { |
109 |
cleanupMultipart(processedRequest); |
真是千呼万唤始出来,犹抱琵琶半遮面。我们在第一篇《SpringMVC源码剖析(一)- 从抽象和接口说起》中所描述的各种编程元素,依次出现在该方法中。HandlerMapping、HandlerAdapter、View这些接口的设计,我们在第一篇中已经讲过。现在我们来重点关注一下HandlerExecutionChain这个对象。
从上面的代码中,很明显可以看出一条线索,整个方法是围绕着如何获取HandlerExecutionChain对象,执行HandlerExecutionChain对象得到相应的视图对象,再对视图进行渲染这条主线来展开的。HandlerExecutionChain对象显得异常重要。
因为Http请求要进入SpringMVC的处理体系,必须由HandlerMapping接口的实现类映射Http请求,得到一个封装后的HandlerExecutionChain对象。再由HandlerAdapter接口的实现类来处理这个HandlerExecutionChain对象所包装的处理对象,来得到最后渲染的视图对象。
视图对象是用ModelAndView对象来描述的,名字已经非常直白,就是数据和视图,其中的数据,由HttpServletRequest的属性得到,视图就是由HandlerExecutionChain封装的处理对象处理后得到。当然HandlerExecutionChain中的拦截器列表HandlerInterceptor,会在处理过程的前后依次被调用,为处理过程留下充足的扩展点。
所有的SpringMVC框架元素,都是围绕着HandlerExecutionChain这个执行链来发挥效用。我们来看看,HandlerExecutionChain类的代码。
01 |
package org.springframework.web.servlet; |
03 |
import java.util.ArrayList; |
04 |
import java.util.Arrays; |
05 |
import java.util.List; |
07 |
import org.springframework.util.CollectionUtils; |
09 |
public class HandlerExecutionChain
{ |
11 |
private final Object
handler; |
13 |
private HandlerInterceptor[]
interceptors; |
15 |
private List<HandlerInterceptor>
interceptorList; |
17 |
public HandlerExecutionChain(Object
handler) { |
21 |
public HandlerExecutionChain(Object
handler, HandlerInterceptor[] interceptors) { |
22 |
if (handler instanceof HandlerExecutionChain)
{ |
23 |
HandlerExecutionChain
originalChain = (HandlerExecutionChain) handler; |
24 |
this.handler
= originalChain.getHandler(); |
25 |
this.interceptorList
= new ArrayList<HandlerInterceptor>(); |
26 |
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList); |
27 |
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList); |
30 |
this.handler
= handler; |
31 |
this.interceptors
= interceptors; |
35 |
public Object
getHandler() { |
39 |
public void addInterceptor(HandlerInterceptor
interceptor) { |
40 |
initInterceptorList(); |
41 |
this.interceptorList.add(interceptor); |
44 |
public void addInterceptors(HandlerInterceptor[]
interceptors) { |
45 |
if (interceptors
!= null)
{ |
46 |
initInterceptorList(); |
47 |
this.interceptorList.addAll(Arrays.asList(interceptors)); |
51 |
private void initInterceptorList()
{ |
52 |
if (this.interceptorList
== null)
{ |
53 |
this.interceptorList
= new ArrayList<HandlerInterceptor>(); |
55 |
if (this.interceptors
!= null)
{ |
56 |
this.interceptorList.addAll(Arrays.asList(this.interceptors)); |
57 |
this.interceptors
= null; |
61 |
public HandlerInterceptor[]
getInterceptors() { |
62 |
if (this.interceptors
== null && this.interceptorList
!= null)
{ |
63 |
this.interceptors
= this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]); |
65 |
return this.interceptors; |
69 |
public String
toString() { |
70 |
if (this.handler
== null)
{ |
71 |
return "HandlerExecutionChain
with no handler"; |
73 |
StringBuilder
sb = new StringBuilder(); |
74 |
sb.append("HandlerExecutionChain
with handler [").append(this.handler).append("]"); |
75 |
if (!CollectionUtils.isEmpty(this.interceptorList))
{ |
76 |
sb.append("
and ").append(this.interceptorList.size()).append("
interceptor"); |
77 |
if (this.interceptorList.size()
> 1)
{ |
一个拦截器列表,一个执行对象,这个类的内容十分的简单,它蕴含的设计思想,却十分的丰富。
1.拦截器组成的列表,在执行对象被调用的前后,会依次执行。这里可以看成是一个的AOP环绕通知,拦截器可以对处理对象随心所欲的进行处理和增强。这里明显是吸收了Struts2中拦截器的设计思想。这种AOP环绕式的扩展点设计,也几乎成为所有框架必备的内容。
2.实际的处理对象,即handler对象,是由Object对象来引用的。
1 |
private final Object
handler; |
之所以要用一个java世界最基础的Object对象引用来引用这个handler对象,是因为连特定的接口也不希望绑定在这个handler对象上,从而使handler对象具有最大程度的选择性和灵活性。
我们常说,一个框架最高层次的抽象是接口,但是这里SpringMVC更进了一步。在最后的处理对象上面,SpringMVC没有对它做任何的限制,只要是java世界中的对象,都可以用来作为最后的处理对象,来生成视图。极端一点来说,你甚至可以将另外一个MVC框架集成到SpringMVC中来,也就是为什么SpringMVC官方文档中,居然还有集成其他表现层框架的内容。这一点,在所有表现层框架中,是独领风骚,冠绝群雄的。

3.结语
SpringMVC的成功,源于它对开闭原则的运用和遵守。也正因此,才使得整个框架具有如此强大的描述和扩展能力。这也许和SpringMVC出现和兴起的时间有关,正是经历了Struts1到Struts2这些Web开发领域MVC框架的更新换代,它的设计者才能站在前人的肩膀上。知道了如何将事情做的糟糕之后,你或许才知道如何将事情做得好。
希望在这个系列里面分享的SpringMVC源码阅读经验,能帮助读者们从更高的层次来审视SpringMVC框架的设计,也希望这里所描述的一些基本设计思想,能在你更深入的了解SpringMVC的细节时,对你有帮助。哲学才是唯一的、最终的武器,在一个框架的设计上,尤其是如此。经常地体会一个框架设计者的设计思想,对你更好的使用它,是有莫大的益处的。
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- SpringMVC源码剖析(二)- DispatcherServlet的前世今生
上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...
- SpringMVC源码剖析1——执行流程
SpringMVC源码剖析1——执行流程 00.SpringMVC执行流程file:///C:/Users/WANGGA~1/AppData/Local/Temp/enhtmlclip/Image.p ...
- SpringMVC源码情操陶冶-DispatcherServlet
本文对springmvc核心类DispatcherServlet作下简单的向导,方便博主与读者查阅 DispatcherServlet-继承关系 分析DispatcherServlet的继承关系以及主 ...
- SpringMVC源码情操陶冶-DispatcherServlet父类简析
阅读源码有助于陶冶情操,本文对springmvc作个简单的向导 springmvc-web.xml配置 <servlet> <servlet-name>dispatch< ...
- SpringMVC源码情操陶冶-DispatcherServlet类简析(一)
阅读源码有利于陶冶情操,此文承接前文SpringMVC源码情操陶冶-DispatcherServlet父类简析 注意:springmvc初始化其他内容,其对应的配置文件已被加载至beanFactory ...
- SpringMVC源码情操陶冶-DispatcherServlet简析(二)
承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...
- Django Rest Framework源码剖析(四)-----API版本
一.简介 在我们给外部提供的API中,可会存在多个版本,不同的版本可能对应的功能不同,所以这时候版本使用就显得尤为重要,django rest framework也为我们提供了多种版本使用方法. 二. ...
- SpringMVC源码剖析(五)-消息转换器HttpMessageConverter
原文链接:https://my.oschina.net/lichhao/blog/172562 #概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分 ...
- SpringMVC源码剖析5:消息转换器HttpMessageConverter与@ResponseBody注解
转自 SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Spring源码 ...
随机推荐
- Flutter Engage China 开发者常见问题解答 | 上篇
再次感谢大家对 Flutter Engage China 活动 的关注和积极参与!我们在活动前后收到了很多来自开发者的反馈和问题,Flutter 团队和演讲嘉宾在直播 Q&A 环节中也针对部分 ...
- 加入 Flutter Engage,Pick 您的专属 Dash 形象!
Flutter Engage 活动精彩来袭 对 Flutter 团队的开发者们来说,交流的重要性不言而喻,和您一样,我们也希望开发者们能够在不同的情境下进行互动分享.于是我们为您准备了一场特别的线上活 ...
- BOOT跳转APP,STM32F4正常,但是GD32F4起不来的问题
问题描述: stm32F4可以正常从BOOT跳转执行APP,到了GD32F4,卡死在APP程序的这里. 临时解决办法: APP程序内 把这两句代码都屏蔽掉就好了. 相关资料搜索: 最佳解决方案: ...
- 系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的C库函数遇上fork
1. test1 #include <stdio.h> #include <unistd.h> #include <stdlib.h> /******全局变量位于数 ...
- SQL limit字句
limit用法介绍 limit子句可以返回检索查询行的某一连续的部分 用法介绍: SELECT column_list FROM table1 ORDER BY column_list LIMIT r ...
- PMP——如何区分项目启动会和开踢会?
在PMP考试中非常强调两个重要会议,一个叫做启动会(Initiating Meeting),另一个叫做开踢会议(Kick-off Meeting),俗称两会. 项目启动会的作用是通过发布项目章程来授权 ...
- 项目实战:Qt+OSG爆破动力学仿真三维引擎测试工具v1.1.0(加载.K模型,子弹轨迹模拟动画,支持windows、linux、国产麒麟系统)
需求 1.使用osg三维引擎进行动力学模型仿真性能测试: 2.打开动力学仿真模型文件,.k后缀的模型文件,测试加载解析过程: 3.解决第三方company的opengl制作的三维引擎,绘制面较 ...
- 【赵渝强老师】如何分析Java的内存溢出问题
一.什么是内存溢出? 内存溢出(OOM:out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出. 在J ...
- C++的并发编程历史
多线程环境 并非所有的语言都提供了多线程的环境.即便是C++语言,直到C++11标准之前,也是没有多线程支持的. 在这种情况下,Linux/Unix平台下的开发者通常会使用POSIX Threads, ...
- 如何快速定位 Linux Panic 出错的代码行
问题描述 内核调试中最常见的一个问题是:内核Panic后,如何快速定位到出错的代码行? 就是这样一个常见的问题,面试过的大部分同学都未能很好地回答,这里希望能够做很彻底地解答. 问题分析 内核Pani ...