SpringMVC源码解析-HTTP请求处理和分发
1.HandlerMapping的配置和设计
在初始化完成时,所有的handlerMapping都已经被加载,handlerMapping存储着HTTP请求对应的映射数据,每一个handlerMapping持有从URL请求到Controller的映射。
这里以SimpleUrlHandlerMapping为例来分析它的设计与实现。在SimpleUrlHandlerMapping中,定义了一个map来维持映射关系,即URL请求和控制器对应关系,
是SpringMVC应用可以根据HTTP请求确定对一个对应的Controller,具体来说,这些映射关系是通过接口类HandlerMapping来封装,在HandlerMapping接口中定义了一个getHandler方法,
通过这个方法,可以获得与HTTP请求对应的HandlerExecutionChain,在这个HandlerExecutionChain中,封装具体的Controller对象。
SimpleUrlHandlerMapping中的urlMap:
- public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
- //url和controller映射的map
- private final Map<String, Object> urlMap = new HashMap<String, Object>();
HandlerMapping中的getHandler
- public interface HandlerMapping {
- //省略。。。
- HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
- }
接下来,看一下HandlerExecutionChain的实现。
- public class HandlerExecutionChain {
- private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
- private final Object handler;//处理对象,也就是controller对象
- private HandlerInterceptor[] interceptors;//拦截器
- private List<HandlerInterceptor> interceptorList;//拦截器
- private int interceptorIndex = -1;
- /**
- * Create a new HandlerExecutionChain.
- * @param handler the handler object to execute
- */
- public HandlerExecutionChain(Object handler) {
- this(handler, (HandlerInterceptor[]) null);
- }
- /**
- * Create a new HandlerExecutionChain.
- * @param handler the handler object to execute
- * @param interceptors the array of interceptors to apply
- * (in the given order) before the handler itself executes
- *///初始化
- public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
- if (handler instanceof HandlerExecutionChain) {
- HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
- this.handler = originalChain.getHandler();
- this.interceptorList = new ArrayList<HandlerInterceptor>();
- CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
- CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
- }
- else {
- this.handler = handler;
- this.interceptors = interceptors;
- }
- }
- /**
- * Return the handler object to execute.
- * @return the handler object
- *///获取handler,本质就是controller对象
- public Object getHandler() {
- return this.handler;
- }
- //添加拦截器
- public void addInterceptor(HandlerInterceptor interceptor) {
- initInterceptorList().add(interceptor);
- }
- //添加多个拦截器
- public void addInterceptors(HandlerInterceptor... interceptors) {
- if (!ObjectUtils.isEmpty(interceptors)) {
- initInterceptorList().addAll(Arrays.asList(interceptors));
- }
- }
- private List<HandlerInterceptor> initInterceptorList() {
- if (this.interceptorList == null) {
- this.interceptorList = new ArrayList<HandlerInterceptor>();
- if (this.interceptors != null) {
- // An interceptor array specified through the constructor
- this.interceptorList.addAll(Arrays.asList(this.interceptors));
- }
- }
- this.interceptors = null;
- return this.interceptorList;
- }
- /**
- * Return the array of interceptors to apply (in the given order).
- * @return the array of HandlerInterceptors instances (may be {@code null})
- */
- public HandlerInterceptor[] getInterceptors() {
- if (this.interceptors == null && this.interceptorList != null) {
- this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
- }
- return this.interceptors;
- }
- /**
- * Apply preHandle methods of registered interceptors.
- * @return {@code true} if the execution chain should proceed with the
- * next interceptor or the handler itself. Else, DispatcherServlet assumes
- * that this interceptor has already dealt with the response itself.
- *///执行之前调用拦截器
- boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = 0; i < interceptors.length; i++) {
- HandlerInterceptor interceptor = interceptors[i];
- if (!interceptor.preHandle(request, response, this.handler)) {
- triggerAfterCompletion(request, response, null);
- return false;
- }
- this.interceptorIndex = i;
- }
- }
- return true;
- }
- /**
- * Apply postHandle methods of registered interceptors.
- *///执行之后调用拦截器
- void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- interceptor.postHandle(request, response, this.handler, mv);
- }
- }
- }
- /**
- * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
- * Will just invoke afterCompletion for all interceptors whose preHandle invocation
- * has successfully completed and returned true.
- *///调用拦截器的afterCompletion方法
- void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
- throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = this.interceptorIndex; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- try {
- interceptor.afterCompletion(request, response, this.handler, ex);
- }
- catch (Throwable ex2) {
- logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
- }
- }
- }
- }
- /**
- * Apply afterConcurrentHandlerStarted callback on mapped AsyncHandlerInterceptors.
- */
- void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- if (interceptors[i] instanceof AsyncHandlerInterceptor) {
- try {
- AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
- asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
- }
- catch (Throwable ex) {
- logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
- }
- }
- }
- }
- }
- /**
- * Delegates to the handler's {@code toString()}.
- */
- @Override
- public String toString() {
- if (this.handler == null) {
- return "HandlerExecutionChain with no handler";
- }
- StringBuilder sb = new StringBuilder();
- sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
- if (!CollectionUtils.isEmpty(this.interceptorList)) {
- sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
- if (this.interceptorList.size() > 1) {
- sb.append("s");
- }
- }
- return sb.toString();
- }
- }
下面是拦截器的执行图
对应的applyPreHandle和applyPostHandle方法代码如下:
- /**
- * Apply preHandle methods of registered interceptors.
- * @return {@code true} if the execution chain should proceed with the
- * next interceptor or the handler itself. Else, DispatcherServlet assumes
- * that this interceptor has already dealt with the response itself.
- */
- boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = 0; i < interceptors.length; i++) {
- HandlerInterceptor interceptor = interceptors[i];
- if (!interceptor.preHandle(request, response, this.handler)) {
- triggerAfterCompletion(request, response, null);
- return false;
- }
- this.interceptorIndex = i;
- }
- }
- return true;
- }
- /**
- * Apply postHandle methods of registered interceptors.
- */
- void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
- HandlerInterceptor[] interceptors = getInterceptors();
- if (!ObjectUtils.isEmpty(interceptors)) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- interceptor.postHandle(request, response, this.handler, mv);
- }
- }
- }
回到SimpleUrlHandlerMapping中,有一个initApplicationContext方法
- /**
- * Calls the {@link #registerHandlers} method in addition to the
- * superclass's initialization.
- */
- @Override
- public void initApplicationContext() throws BeansException {
- super.initApplicationContext();
- registerHandlers(this.urlMap);
- }
继续看看registerHandlers方法,注册Handler
- protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
- if (urlMap.isEmpty()) {
- logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
- }
- else {
- for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
- String url = entry.getKey();
- Object handler = entry.getValue();
- // Prepend with slash if not already present.
- if (!url.startsWith("/")) {//不是以"/"开头的,加上"/"
- url = "/" + url;
- }
- // Remove whitespace from handler bean name.
- if (handler instanceof String) {
- handler = ((String) handler).trim();
- }
- registerHandler(url, handler);
- }
- }
- }
AbstractUrlHandlerMapping对handler的注册
- /**
- * Register the specified handler for the given URL path.
- * @param urlPath the URL the bean should be mapped to
- * @param handler the handler instance or handler bean name String
- * (a bean name will automatically be resolved into the corresponding handler bean)
- * @throws BeansException if the handler couldn't be registered
- * @throws IllegalStateException if there is a conflicting handler registered
- */
- 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;
- if (getApplicationContext().isSingleton(handlerName)) {
- resolvedHandler = getApplicationContext().getBean(handlerName);//直接从spring容器中获取handler
- }
- }
- Object mappedHandler = this.handlerMap.get(urlPath);//从map中获取handler
- if (mappedHandler != null) {
- if (mappedHandler != resolvedHandler) {
- throw new IllegalStateException(
- "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
- "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
- }
- }
- else {
- if (urlPath.equals("/")) {
- if (logger.isInfoEnabled()) {
- logger.info("Root mapping to " + getHandlerDescription(handler));
- }
- setRootHandler(resolvedHandler);//url是"/",将其设置为根handler
- }
- else if (urlPath.equals("/*")) {
- if (logger.isInfoEnabled()) {
- logger.info("Default mapping to " + getHandlerDescription(handler));
- }
- setDefaultHandler(resolvedHandler);//默认handler
- }
- else {
- this.handlerMap.put(urlPath, resolvedHandler);//注册handler,url为key,controller为value的map
- if (logger.isInfoEnabled()) {
- logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
- }
- }
- }
- }
有了上面的配置好了Url和Controller的映射关系,为SpringMVC响应HTTP请求准备好了基础的映射数据,至此SpringMVC就可以等待HTTP的请求的到来。
2.使用HandlerMapping完成请求的映射处理
当请求来时,首先AbstractHandlerMapping的getHandler被调用,方法参数是request
- @Override
- public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- Object handler = getHandlerInternal(request);
- if (handler == null) {//为空走默认的handler
- handler = getDefaultHandler();
- }
- if (handler == null) {
- return null;
- }
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;//从spring容器中获取
- handler = getApplicationContext().getBean(handlerName);
- }
- return getHandlerExecutionChain(handler, request);//把handler封装到HandlerExecutionChain中并加上拦截器
- }
封装HandlerExecutionChain
- protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
- HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
- (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
- chain.addInterceptors(getAdaptedInterceptors());
- String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
- for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
- if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
- chain.addInterceptor(mappedInterceptor.getInterceptor());
- }
- }
- return chain;
- }
上面的getHnadler会调用getHandlerInternal
- protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
- String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
- Object handler = lookupHandler(lookupPath, request);
- if (handler == null) {
- // We need to care for the default handler directly, since we need to
- // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
- Object rawHandler = null;
- if ("/".equals(lookupPath)) {
- rawHandler = getRootHandler();
- }
- if (rawHandler == null) {
- rawHandler = getDefaultHandler();
- }
- if (rawHandler != null) {
- // Bean name or resolved handler?
- if (rawHandler instanceof String) {
- String handlerName = (String) rawHandler;
- rawHandler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(rawHandler, request);
- handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
- }
- }
- if (handler != null && logger.isDebugEnabled()) {
- logger.debug("Mapping [" + lookupPath + "] to " + handler);
- }
- else if (handler == null && logger.isTraceEnabled()) {
- logger.trace("No handler mapping found for [" + lookupPath + "]");
- }
- return handler;
- }
这里调用的是lookupHandler方法,在来看一下这个方法。
- protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
- // Direct match?
- Object handler = this.handlerMap.get(urlPath);
- if (handler != null) {
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- return buildPathExposingHandler(handler, urlPath, urlPath, null);
- }
- // Pattern match?
- List<String> matchingPatterns = new ArrayList<String>();
- for (String registeredPattern : this.handlerMap.keySet()) {
- if (getPathMatcher().match(registeredPattern, urlPath)) {
- matchingPatterns.add(registeredPattern);
- }
- }
- String bestPatternMatch = null;
- Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
- if (!matchingPatterns.isEmpty()) {
- Collections.sort(matchingPatterns, patternComparator);
- if (logger.isDebugEnabled()) {
- logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
- }
- bestPatternMatch = matchingPatterns.get(0);
- }
- if (bestPatternMatch != null) {
- handler = this.handlerMap.get(bestPatternMatch);
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- validateHandler(handler, request);
- String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
- // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
- // for all of them
- Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
- for (String matchingPattern : matchingPatterns) {
- if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
- Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
- Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
- uriTemplateVariables.putAll(decodedVars);
- }
- }
- if (logger.isDebugEnabled()) {
- logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
- }
- return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
- }
- // No handler found...
- return null;
- }
经过一系列的对HTTP请求进行解析和匹配handler的过程,得到了与请求对应的处理器。在返回handler中,已经完成了对HandlerExecutionChain进行封装工作,为HTTP请求响应做准备。
3.SpringMVC对HTTP请求的分发处理
回到DispatcherServlet中,对HTTP的请求是在doService中,DispatcherServlet是HttpServlet的子类,和其他Servlet一样,通过doService来响应HTTP请求。doService直接调用的是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;
- Exception dispatchException = null;
- try {
- processedRequest = checkMultipart(request);
- multipartRequestParsed = (processedRequest != request);
- // Determine handler for the current request.
- mappedHandler = getHandler(processedRequest);//获取handler
- if (mappedHandler == null || mappedHandler.getHandler() == null) {
- noHandlerFound(processedRequest, response);
- return;
- }
- // Determine handler adapter for the current request.//获取adapter
- 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()) {
- logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
- }
- if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
- return;
- }
- }
- if (!mappedHandler.applyPreHandle(processedRequest, response)) {
- return;
- }
- // Actually invoke the handler.handler处理的结果封装到mv中
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- if (asyncManager.isConcurrentHandlingStarted()) {
- return;
- }
- applyDefaultViewName(request, mv);
- mappedHandler.applyPostHandle(processedRequest, response, mv);
- }
- catch (Exception ex) {
- dispatchException = ex;
- }
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- }
- catch (Exception ex) {
- triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
- }
- catch (Error err) {
- triggerAfterCompletionWithError(processedRequest, response, mappedHandler, 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);
- }
- }
- }
- }
看一下getHandlerAdapter方法
- protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
- for (HandlerAdapter ha : this.handlerAdapters) {
- if (logger.isTraceEnabled()) {
- logger.trace("Testing handler adapter [" + ha + "]");
- }
- if (ha.supports(handler)) {//判断是否支持
- return ha;
- }
- }
- throw new ServletException("No adapter for handler [" + handler +
- "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
- }
继续看一下supports方法的实现,以SimpleControlerHandlerAdapter类为例
- public class SimpleControllerHandlerAdapter implements HandlerAdapter {
- @Override
- public boolean supports(Object handler) {
- return (handler instanceof Controller);
- }
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- return ((Controller) handler).handleRequest(request, response);
- }
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
经过上面一系列的操作,得到handler对象,handler调用handleRequest方法,返回ModelAndView对象,最后通过render方法进行渲染。
问题:handlerMapping是在什么时候初始化的?
在DispatcherServlet调用init->initStrategies方法中,进行初始化。
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- //初始化handlerMapping
- initHandlerMappings(context);
- initHandlerAdapters(context);
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- initFlashMapManager(context);
- }
最后一个请求的流程图。
SpringMVC源码解析-HTTP请求处理和分发的更多相关文章
- SpringMVC源码解析- HandlerAdapter - ModelFactory(转)
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- springMVC源码解析--ViewResolver视图解析器执行(三)
之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...
- Netty 4源码解析:请求处理
Netty 4源码解析:请求处理 通过之前<Netty 4源码解析:服务端启动>的分析,我们知道在最前端"扛压力"的是NioEventLoop.run()方法.我们指定 ...
- SpringMVC源码解析- HandlerAdapter - ModelFactory
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- 深入了解SpringMVC源码解析
Spring MVC源码解析 Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理. 我们可以从web.xml里面看出这一点 & ...
- springmvc源码分析系列-请求处理流程
接上一篇-springmvc源码分析开头片 上一节主要说了一下springmvc与struts2的作为MVC中的C(controller)控制层的一些区别及两者在作为控制层方面的一些优缺点.今天就结合 ...
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...
- springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)
上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...
- springmvc源码解析MvcNamespaceHandler之<mvc:view-resolvers>
说在前面 本次主要介绍springmvc配置解析. springmvc配置解析 本次介绍MvcNamespaceHandler. 进入到这个方法org.springframework.web.serv ...
随机推荐
- UE4蓝图小记
http://www.element3ds.com/forum.php?mod=viewthread&tid=76930&page=1&authorid=104414 http ...
- Unity Shader学习笔记 - 用UV动画实现沙滩上的泡沫
这个泡沫效果来自远古时代的Unity官方海岛Demo, 原效果直接复制3个材质球在js脚本中做UV动画偏移,这里尝试在shader中做动画并且一个pass中完成: // Upgrade NOTE: r ...
- 前端开发工程师 - 01.页面制作 - 第4章.CSS
第4章.CSS CSS简介 Cascading Style Sheet 层叠样式表:定义页面中的表现样式 history: CSS1(1996)--CSS2(1998)--着手CSS3草案(拆分成很多 ...
- List和String数组相互转化
在工作中经常会遇到需要String[] 参数的地方,我们可以先定义一个list,再转成String[] 来使用,使用list的好处自然是可以随时方便的添加删除元素,下面是方法: List list = ...
- 165. Merge Two Sorted Lists【LintCode by java】
Description Merge two sorted (ascending) linked lists and return it as a new sorted list. The new so ...
- 深入理解java虚拟机学习笔记(二)
第三章 垃圾收集器与内存分配策略 概述 程序计数器.虚拟机栈.本地方法栈3个区随线程而生,随线程而灭.因此大体上可认为这几个区域的内存分配和回收都具备确定性.在方法/线程结束时,内存自然就跟着回收 ...
- NodeJs学习笔记01-你好Node
如果你对NodeJs略知一二,不禁会感叹,使用JS的语法和代码习惯就能开发一个网站的后台,实现复杂的数据交互,牛! 对于学习java和php就夹生的小码农来说,简直就是靡靡之音呐~~~ 今晚带着忐忑的 ...
- C语言struct中的长度可变数组(Flexible array member)
C_struct中的长度可变数组(Flexible array member) Flexible array member is a feature introduced in the C99 sta ...
- javascript打开新窗口
一.window.open()支持环境: JavaScript1.0+/JScript1.0+/Nav2+/IE3+/Opera3+ 二.基本语法: window.open(pageURL,name, ...
- 第十六次ScrumMeeting会议
第十六次Scrum Meeting 时间:2017/12/6 地点:线上+SPR咖啡馆 人员:蔡帜 王子铭 游心 解小锐 王辰昱 李金奇 杨森 陈鑫 照片: 目前工作进展 名字 今日 明天的工作 遇到 ...