精华内容
下载资源
问答
  • 博主曾经有幸参加一次北京某心某力的笔试海选,深感技术基础的重要,没怎么准备,加上自己对于算法和排序方面的知识欠缺,显然是没有成功得到面谈的机会,都是这么多年面向百度编程的原因,欠缺了系统的学习和思考...

           博主曾经有幸参加一次北京某心某力的笔试海选,深感技术基础的重要性,没怎么准备,加上自己对于算法和排序方面的知识欠缺,显然是没有成功得到面谈的机会,都是这么多年面向百度编程的原因,欠缺了系统的学习和思考,长时间使用开发工具编程,纸上手写确实难上不少,总之一句话,还是自己太菜,基础知识完全不懂,每天做着知识搬运工的工作,留下的没有技术的眼泪。

          其中的一道试题给大家分享下,希望对看到文章的人有所帮助,有了新的内容的话,我会不定时更新,争取能和大家一起学习。

    目录

    Java判定括号的对称性包含{}()[]

    stack.peek();与stack.pop();区别

    实现代码如下:

    运行结果:

    网上大牛写的思路清晰的代码如下:

    QQ群:763774519(Java极客技术部落)

    最后,希望我们能共同成长进步吧,翻过这座山他们就会听到你的故事。


     

    Java判定括号的对称性包含{}()[]

    解法思路代码如下,主要用到了Stack栈技术帮助解决

    stack.peek();与stack.pop();区别

    peek能获取到栈顶数据不会移除栈,pop获取到栈顶数据会移除栈

    实现代码如下:

    	/**
    	 * 判定括号的对称性
    	 */
    	public static boolean anaylis(String s) {
    		char[] temps = s.toCharArray();
    		if (temps.length==0) {
    			return false;
    		}
    		Stack<Character> stack=new Stack<Character>();
    		for (int i = 0; i < temps.length; i++) {
    			char temp=temps[i];
    			if ( temp== '(') {
    				stack.push(temp);
    			} else if (temps[i] == '{') {
    				stack.push(temp);
    			} else if (temps[i] == '[') {
    				stack.push(temp);
    			}else if (temps[i] == ')') {
    				if (stack.isEmpty()) {
    					return false;
    				}else{
    					char top=stack.peek();
    					if (top=='(') {
    						stack.pop();
    					}
    				}
    			}else if (temps[i] == '}') {
    				if (stack.isEmpty()) {
    					return false;
    				}else{
    					char top=stack.peek();
    					if (top=='{') {
    						stack.pop();
    					}
    				}
    			}else if (temps[i] == ']') {
    				if (stack.isEmpty()) {
    					return false;
    				}else{
    					char top=stack.peek();
    					if (top=='[') {
    						stack.pop();
    					}
    				}
    			}else{
    				//非括号不处理
    			}
    			
    		}
    		if (stack.isEmpty()) {
    			return true;
    		}
    
    		return false;
    	}
    

    运行结果:

    输入内容:()[(]}
    括号对称性:false
    输入内容:()[()]{}
    括号对称性:true

    网上大牛写的思路清晰的代码如下:

    private static int whatParentheses(char ch) {
    		switch (ch) {
    		case '(':
    			return -1;
    		case '[':
    			return -2;
    		case '{':
    			return -3;
    		case ')':
    			return 1;
    		case ']':
    			return 2;
    		case '}':
    			return 3;
    		}
    		return 0;
    	}
    
    	private static boolean isParenthesesValid(String s) {
    		// 字符串为空
    		if (s == null) {
    			return false;
    		}
    		int length = s.length();
    		Stack<Character> stack = new Stack<>();
    		// 存储遍历的字符
    		char ch;
    		// 存储字符转换后的数字
    		int parentNum;
    		// 记录下括号出现的次数
    		int count = 0;
    		for (int i = 0; i < length; i++) {
    			ch = s.charAt(i);
    			parentNum = whatParentheses(ch);
    			if (parentNum < 0) {
    				count++;
    				// <0表示这是个左括号
    				stack.push(ch);
    			} else if (parentNum > 0) {
    				count++;
    				// >0表示这是个右括号
    				if (stack.isEmpty()) {
    					// 右括号左边没有左括号的特殊情况
    					return false;
    				}
    				if (parentNum + whatParentheses(stack.peek()) == 0) {
    					// 栈顶是对应的左括号
    					stack.pop();
    				} else {
    					return false;
    				}
    			} else {
    				// =0 这不是一个括号,不管
    			}
    		}
    		// 字符串中有括号且栈最后被清空了,表示括号成对出现且有效
    		if (count > 0 && stack.isEmpty()) {
    			return true;
    		}
    		return false;
    	}

    对比学习后,还是觉得大牛的思路解析更好,还是得活学活用,做到能举一反三才算是真正的掌握,好了文章就分享到这里,希望能与大家共勉,并能看到文章的各位有一些帮助,我只是一个“知识的搬运工”。

    以下是个人的技术交流群,有兴趣一起学习交流的可以加入

    QQ群:763774519(Java极客技术部落)

    或者可以扫一扫关注微信订阅号

    最后,希望我们能共同成长进步吧,翻过这座山他们就会听到你的故事。

    展开全文
  • 写在前面:为什么要学习react-router底层源码? 为什么要弄明白整个路由流程? 笔者个人感觉学习react-router,有助于我们学习单页面应用...个人理解,单页面应用是使用一个html下,一次性加载js, css等资源,所有页面.

    写在前面:为什么要学习react-router底层源码? 为什么要弄明白整个路由流程? 笔者个人感觉学习react-router,有助于我们学习单页面应用(spa)路由跳转原理,让我们理解从history.push,到组件页面切换的全套流程,使我们在面试的时候不再为路由相关的问题发怵,废话不说,让我们开启深入react-router源码之旅吧。

    一 正确理解react-router

    1 理解单页面应用

    什么是单页面应用?

    个人理解,单页面应用是使用一个html下,一次性加载js, css等资源,所有页面都在一个容器页面下,页面切换实质是组件的切换。

    2 react-router初探,揭露路由原理面纱

    react-router-domreact-routerhistory库三者什么关系

    history 可以理解为react-router的核心,也是整个路由原理的核心,里面集成了popState,history.pushState等底层路由实现的原理方法,接下来我们会一一解释。

    react-router可以理解为是react-router-dom的核心,里面封装了Router,Route,Switch等核心组件,实现了从路由的改变到组件的更新的核心功能,在我们的项目中只要一次性引入react-router-dom就可以了。

    react-router-dom,在react-router的核心基础上,添加了用于跳转的Link组件,和histoy模式下的BrowserRouter和hash模式下的HashRouter组件等。所谓BrowserRouter和HashRouter,也只不过用了history库中createBrowserHistory和createHashHistory方法

    react-router-dom 我们不多说了,这里我们重点看一下react-router

    ②来个小demo尝尝鲜?

    import { BrowserRouter as Router, Switch, Route, Redirect,Link } from 'react-router-dom'
    
    import Detail from '../src/page/detail'
    import List from '../src/page/list'
    import Index from '../src/page/home/index'
    
    const menusList = [
      {
        name: '首页',
        path: '/index'
      },
      {
        name: '列表',
        path: '/list'
      },
      {
        name: '详情',
        path: '/detail'
      },
    ]
    const index = () => {
      return <div >
        <div >
         
          <Router  >
          <div>{
            /* link 路由跳转 */
             menusList.map(router=><Link key={router.path} to={ router.path } >
               <span className="routerLink" >{router.name}</span>
             </Link>)
          }</div>
            <Switch>
              <Route path={'/index'} component={Index} ></Route>
              <Route path={'/list'} component={List} ></Route>
              <Route path={'/detail'} component={Detail} ></Route>
              {/*  路由不匹配,重定向到/index  */}
              <Redirect from='/*' to='/index' />
            </Switch>
          </Router>
        </div>
      </div>
    }
    

    效果如下

    二 单页面实现核心原理

    单页面应用路由实现原理是,切换url,监听url变化,从而渲染不同的页面组件。

    主要的方式有history模式和hash模式。

    1 history模式原理

    ①改变路由

    history.pushState

    history.pushState(state,title,path)
    

    1 state:一个与指定网址相关的状态对象, popstate 事件触发时,该对象会传入回调函数。如果不需要可填 null。

    2 title:新页面的标题,但是所有浏览器目前都忽略这个值,可填 null。

    3 path:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址。

    history.replaceState

    history.replaceState(state,title,path)
    

    参数和pushState一样,这个方法会修改当前的 history 对象记录, history.length 的长度不会改变。

    ②监听路由

    popstate事件

    window.addEventListener('popstate',function(e){
        /* 监听改变 */
    })
    

    同一个文档的 history 对象出现变化时,就会触发 popstate 事件
    
    history.pushState 可以使浏览器地址改变,但是无需刷新页面。注意⚠️的是:用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()、history.forward()、history.go()方法。

    2 hash模式原理

    ①改变路由

    window.location.hash

    通过window.location.hash 属性获取和设置 hash 值。

    ②监听路由

    onhashchange

    window.addEventListener('hashchange',function(e){
        /* 监听改变 */
    })
    

    三 理解history库

    react-router路由离不开history库,history专注于记录路由history状态,以及path改变了,我们应该做写什么
    在history模式下用popstate监听路由变化,在hash模式下用hashchange监听路由的变化。

    接下来我们看 Browser模式下的createBrowserHistoryHash模式下的 createHashHistory方法。

    1 createBrowserHistory

    Browser模式下路由的运行 ,一切都从createBrowserHistory开始。这里我们参考的history-4.7.2版本,最新版本中api可能有些出入,但是原理都是一样的,在解析history过程中,我们重点关注setState ,push ,handlePopState,listen方法

    const PopStateEvent = 'popstate'
    const HashChangeEvent = 'hashchange'
    /* 这里简化了createBrowserHistory,列出了几个核心api及其作用 */
    function createBrowserHistory(){
        /* 全局history  */
        const globalHistory = window.history
        /* 处理路由转换,记录了listens信息。 */
        const transitionManager = createTransitionManager()
        /* 改变location对象,通知组件更新 */
        const setState = () => { /* ... */ }
        
        /* 处理当path改变后,处理popstate变化的回调函数 */
        const handlePopState = () => { /* ... */ }
       
        /* history.push方法,改变路由,通过全局对象history.pushState改变url, 通知router触发更新,替换组件 */
        const push=() => { /*...*/ }
        
        /* 底层应用事件监听器,监听popstate事件 */
        const listen=()=>{ /*...*/ } 
        return {
           push,
           listen,
           /* .... */ 
        }
    }
    

    下面逐一分析各个api,和他们之前的相互作用

    const PopStateEvent = 'popstate'
    const HashChangeEvent = 'hashchange'
    

    popstatehashchange是监听路由变化底层方法。

    ①setState

    const setState = (nextState) => {
        /* 合并信息 */
        Object.assign(history, nextState)
        history.length = globalHistory.length
        /* 通知每一个listens 路由已经发生变化 */
        transitionManager.notifyListeners(
          history.location,
          history.action
        )
      }
    

    代码很简单:统一每个transitionManager管理的listener路由状态已经更新。

    什么时候绑定litener, 我们在接下来的React-Router代码中会介绍。

    ②listen

    const listen = (listener) => {
        /* 添加listen */
        const unlisten = transitionManager.appendListener(listener)
        checkDOMListeners(1)
    
        return () => {
          checkDOMListeners(-1)
          unlisten()
        }
    }
    

    checkDOMListeners

    const checkDOMListeners = (delta) => {
        listenerCount += delta
        if (listenerCount === 1) {
          addEventListener(window, PopStateEvent, handlePopState)
          if (needsHashChangeListener)
            addEventListener(window, HashChangeEvent, handleHashChange)
        } else if (listenerCount === 0) {
          removeEventListener(window, PopStateEvent, handlePopState)
          if (needsHashChangeListener)
            removeEventListener(window, HashChangeEvent, handleHashChange)
        }
      }
    

    listen本质通过checkDOMListeners的参数 1-1 来绑定/解绑 popstate 事件,当路由发生改变的时候,调用处理函数handlePopState

    接下来我们看看push方法。

    ③push

     const push = (path, state) => {
        const action = 'PUSH'
        /* 1 创建location对象 */
        const location = createLocation(path, state, createKey(), history.location)
        /* 确定是否能进行路由转换,还在确认的时候又开始了另一个转变 ,可能会造成异常 */
        transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
          if (!ok)
            return
          const href = createHref(location)
          const { key, state } = location
          if (canUseHistory) {
            /* 改变 url */
            globalHistory.pushState({ key, state }, null, href)
            if (forceRefresh) {
              window.location.href = href
            } else {
              /* 改变 react-router location对象, 创建更新环境 */
              setState({ action, location })
            }
          } else {
            window.location.href = href
          }
        })
      }
    

    push ( history.push ) 流程大致是 首先生成一个最新的location对象,然后通过window.history.pushState方法改变浏览器当前路由(即当前的path),最后通过setState方法通知React-Router更新,并传递当前的location对象,由于这次url变化的,是history.pushState产生的,并不会触发popState方法,所以需要手动setState,触发组件更新

    ④handlePopState

    最后我们来看看当popState监听的函数,当path改变的时候会发生什么,

    /* 我们简化一下handlePopState */
    const handlePopState = (event)=>{
         /* 获取当前location对象 */
        const location = getDOMLocation(event.state)
        const action = 'POP'
    
        transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
            if (ok) {
              setState({ action, location })
            } else {
              revertPop(location)
            }
        })
    }
    

    handlePopState 代码很简单 ,判断一下action类型为pop,然后 setState ,从新加载组件。

    2 createHashHistory

    hash 模式和 history API类似,我们重点讲一下 hash模式下,怎么监听路由,和push , replace方法是怎么改变改变路径的。

    监听哈希路由变化

      const HashChangeEvent = 'hashchange'
      const checkDOMListeners = (delta) => {
        listenerCount += delta
        if (listenerCount === 1) {
          addEventListener(window, HashChangeEvent, handleHashChange)
        } else if (listenerCount === 0) {
          removeEventListener(window, HashChangeEvent, handleHashChange)
        }
      }
    

    和之前所说的一样,就是用hashchange来监听hash路由的变化。

    改变哈希路由

    
    /* 对应 push 方法 */
    const pushHashPath = (path) =>
      window.location.hash = path
    
    /* 对应replace方法 */
    const replaceHashPath = (path) => {
      const hashIndex = window.location.href.indexOf('#')
    
      window.location.replace(
        window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path
      )
    }
    
    

    hash模式下 ,history.push 底层是调用了window.location.href来改变路由。history.replace底层是掉用
    window.location.replace改变路由。

    总结

    我们用一幅图来描述了一下history库整体流程。

    四 核心api

    1 Router-接收location变化,派发更新流

    Router 作用是把 history location 等路由信息 传递下去

    Router

    /* Router 作用是把 history location 等路由信息 传递下去  */
    class Router extends React.Component {
      static computeRootMatch(pathname) {
        return { path: '/', url: '/', params: {}, isExact: pathname === '/' };
      }
      constructor(props) {
        super(props);
        this.state = {
          location: props.history.location
        };
        //记录pending位置
        //如果存在任何<Redirect>,则在构造函数中进行更改
        //在初始渲染时。如果有,它们将在
        //在子组件身上激活,我们可能会
        //在安装<Router>之前获取一个新位置。
        this._isMounted = false;
        this._pendingLocation = null;
        /* 此时的history,是history创建的history对象 */
        if (!props.staticContext) {
          /* 这里判断 componentDidMount 和 history.listen 执行顺序 然后把 location复制 ,防止组件重新渲染 */
          this.unlisten = props.history.listen(location => {
            /* 创建监听者 */
            if (this._isMounted) {
    
              this.setState({ location });
            } else {
              this._pendingLocation = location;
            }
          });
        }
      }
      componentDidMount() {
        this._isMounted = true;
        if (this._pendingLocation) {
          this.setState({ location: this._pendingLocation });
        }
      }
      componentWillUnmount() {
        /* 解除监听 */
        if (this.unlisten) this.unlisten();
      }
      render() {
        return (
          /*  这里可以理解 react.createContext 创建一个 context上下文 ,保存router基本信息。children */
          <RouterContext.Provider
              children={this.props.children || null}
              value={{
              history: this.props.history,
              location: this.state.location,
              match: Router.computeRootMatch(this.state.location.pathname),
              staticContext: this.props.staticContext
            }}
          />
        );
      }
    }
    

    总结:

    初始化绑定listen, 路由变化,通知改变location,改变组件。 react的history路由状态是保存在React.Content上下文之间, 状态更新。

    一个项目应该有一个根Router , 来产生切换路由组件之前的更新作用。
    如果存在多个Router会造成,会造成切换路由,页面不更新的情况。

    2 Switch-匹配正确的唯一的路由

    根据router更新流,来渲染当前组件。

    /* switch组件 */
    class Switch extends React.Component {
      render() {
        return (
          <RouterContext.Consumer>
            {/* 含有 history location 对象的 context */}
            {context => {
              invariant(context, 'You should not use <Switch> outside a <Router>');
              const location = this.props.location || context.location;
              let element, match;
              //我们使用React.Children.forEach而不是React.Children.toArray().find()
              //这里是因为toArray向所有子元素添加了键,我们不希望
              //为呈现相同的两个<Route>s触发卸载/重新装载
              //组件位于不同的URL。
              //这里只需然第一个 含有 match === null 的组件
              React.Children.forEach(this.props.children, child => {
                if (match == null && React.isValidElement(child)) {
                  element = child;
                  // 子组件 也就是 获取 Route中的 path 或者 rediect 的 from
                  const path = child.props.path || child.props.from;
                  match = path
                    ? matchPath(location.pathname, { ...child.props, path })
                    : context.match;
                }
              });
              return match
                ? React.cloneElement(element, { location, computedMatch: match })
                : null;
            }}
          </RouterContext.Consumer>
        );
      }
    }
    
    

    找到与当前path,匹配的组件进行渲染。 通过pathname和组件的path进行匹配。找到符合path的router组件。

    matchPath

    function matchPath(pathname, options = {}) {
      if (typeof options === "string" || Array.isArray(options)) {
        options = { path: options };
      }
    
      const { path, exact = false, strict = false, sensitive = false } = options;
    
      const paths = [].concat(path);
    
      return paths.reduce((matched, path) => {
        if (!path && path !== "") return null;
        if (matched) return matched;
    
        const { regexp, keys } = compilePath(path, {
          end: exact,
          strict,
          sensitive
        });
        const match = regexp.exec(pathname);
        /* 匹配不成功,返回null */
        if (!match) return null;
    
        const [url, ...values] = match;
        const isExact = pathname === url;
    
        if (exact && !isExact) return null;
    
        return {
          path, // the path used to match
          url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
          isExact, // whether or not we matched exactly
          params: keys.reduce((memo, key, index) => {
            memo[key.name] = values[index];
            return memo;
          }, {})
        };
      }, null);
    }
    

    匹配符合的路由。

    3 Route-组件页面承载容器

    /**
     * The public API for matching a single path and rendering.
     */
    class Route extends React.Component {
      render() {
        return (
          <RouterContext.Consumer>
            {context => {
              /* router / route 会给予警告警告 */
              invariant(context, "You should not use <Route> outside a <Router>");
              // computedMatch 为 经过 swich处理后的 path
              const location = this.props.location || context.location;
              const match = this.props.computedMatch 
                ? this.props.computedMatch // <Switch> already computed the match for us
                : this.props.path
                ? matchPath(location.pathname, this.props)
                : context.match;
              const props = { ...context, location, match };
              let { children, component, render } = this.props;
    
              if (Array.isArray(children) && children.length === 0) {
                children = null;
              }
    
              return (
                <RouterContext.Provider value={props}>
                  {props.match
                    ? children
                      ? typeof children === "function"
                        ? __DEV__
                          ? evalChildrenDev(children, props, this.props.path)
                          : children(props)
                        : children
                      : component
                      ? React.createElement(component, props)
                      : render
                      ? render(props)
                      : null
                    : typeof children === "function"
                    ? __DEV__
                      ? evalChildrenDev(children, props, this.props.path)
                      : children(props)
                    : null}
                </RouterContext.Provider>
              );
            }}
          </RouterContext.Consumer>
        );
      }
    }
    

    匹配path,渲染组件。作为路由组件的容器,可以根据将实际的组件渲染出来。通过RouterContext.Consume 取出当前上一级的location,match等信息。作为prop传递给页面组件。使得我们可以在页面组件中的props中获取location ,match等信息。

    4 Redirect-没有符合的路由,那么重定向

    重定向组件, 如果来路由匹配上,会重定向对应的路由。

    function Redirect({ computedMatch, to, push = false }) {
      return (
        <RouterContext.Consumer>
          {context => {
            const { history, staticContext } = context;
            /* method就是路由跳转方法。 */
            const method = push ? history.push : history.replace;
            /* 找到符合match的location ,格式化location */
            const location = createLocation(
              computedMatch
                ? typeof to === 'string'
                  ? generatePath(to, computedMatch.params)
                  : {
                      ...to,
                      pathname: generatePath(to.pathname, computedMatch.params)
                    }
                : to
            )
            /* 初始化的时候进行路由跳转,当初始化的时候,mounted执行push方法,当组件更新的时候,如果location不相等。同样会执行history方法重定向 */
            return (
              <Lifecycle
                  onMount={() => {
                  method(location);
                }}
                  onUpdate={(self, prevProps) => {
                  const prevLocation = createLocation(prevProps.to);
                  if (
                    !locationsAreEqual(prevLocation, {
                      ...location,
                      key: prevLocation.key
                    })
                  ) {
                    method(location);
                  } 
                }}
                  to={to}
              />
            );
          }}
        </RouterContext.Consumer>
      );
    }
    
    

    初始化的时候进行路由跳转,当初始化的时候,mounted执行push方法,当组件更新的时候,如果location不相等。同样会执行history方法重定向。

    五 总结 + 流程分析

    总结

    history提供了核心api,如监听路由,更改路由的方法,已经保存路由状态state。

    react-router提供路由渲染组件,路由唯一性匹配组件,重定向组件等功能组件。

    流程分析

    当地址栏改变url,组件的更新渲染都经历了什么?😊😊😊
    拿history模式做参考。当url改变,首先触发histoy,调用事件监听popstate事件, 触发回调函数handlePopState,触发history下面的setstate方法,产生新的location对象,然后通知Router组件更新location并通过context上下文传递,switch通过传递的更新流,匹配出符合的Route组件渲染,最后有Route组件取出context内容,传递给渲染页面,渲染更新。

    当我们调用history.push方法,切换路由,组件的更新渲染又都经历了什么呢?

    我们还是拿history模式作为参考,当我们调用history.push方法,首先调用history的push方法,通过history.pushState来改变当前url,接下来触发history下面的setState方法,接下来的步骤就和上面一模一样了,这里就不一一说了。

    我们用一幅图来表示各个路由组件之间的关系。

    希望读过此篇文章的朋友,能够明白react-router的整个流程,代码逻辑不是很难理解。整个流程我给大家分析了一遍,希望同学们能主动看一波源码,把整个流程搞明白。纸上得来终觉浅,绝知此事要躬行。

    写在最后,谢谢大家鼓励与支持🌹🌹🌹,喜欢的可以给笔者点赞关注,公众号:前端Sharing

    微信扫码关注公众号,定期分享技术文章

    在这里插入图片描述

    展开全文
  • 源码解析2.1 获取日志实例2.1.1 获取日志工厂2.1.1.1 工厂初始化2.1.1.1.1 绑定2.1.1.1.2 版本兼容检查2.1.1.2 获取工厂结果处理及返回2.1.1.2.1 初始化的ONGOING_INITIALIZATION处理2.1.2 从工厂获取日志实例2.2 ...

    1.前期准备

    1.1 下载源代码

    github网站: https://github.com/qos-ch/slf4j

    1.2 项目结构

    使用idea构建maven项目如下
    在这里插入图片描述

    1.3 编写测试代码

    这里,我们主要来看slf4j-api的内容,在源代码包下编写测试代码

    package org.slf4j;
    
    /**
     * @author Kern
     * @date 2020/3/23 13:25
     * @description TODO
     */
    public class Demo {
    
        public static void main(String[] args) {
            Logger logger = LoggerFactory.getLogger("demo");
            logger.info("Hello SLF4J");
        }
    }
    

    2.源码解析

    来看看一次获取日志实例并输出日志的过程,slf4j是如何进行处理的。

    2.1 获取日志实例

    首先是获取日志对象的一个实例。在我们平常的项目中,这个实例可能是以静态成员编程的形式初始化在具体的业务类中。可能是以这样的形式编写的,

    private static Logger Logger = LoggerFactory.getLogger(XxxService.class);
    

    在测试中,为了更好的解释流程,我们就使用main方法进行日志的实例化。

    	//工厂类的静态方法获取日志对象的一个实例("demo")
    	Logger logger = LoggerFactory.getLogger("demo");
    

    获取日志实例的流程很清晰,获取工厂,然后从工厂中获取日志实例。

    	public static Logger getLogger(String name) {
            //获取一个ILoggerFactory的工厂实例
    		ILoggerFactory iLoggerFactory = getILoggerFactory();
    		//获取一个Logger实例
    		return iLoggerFactory.getLogger(name);
        }
    

    这里看一下ILoggerFactory 这个接口,一个简单的接口,一个获取Logger实例的get方法。

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.slf4j;
    
    public interface ILoggerFactory {
        Logger getLogger(String var1);
    }
    

    实现类
    在这里插入图片描述
    其中NOP 表示no operation无操作的工厂,由其产生的Logger实例将不对消息对任何处理。如下

    //代码过多,删除了大部分内容
    public class NOPLoggerFactory implements ILoggerFactory {
        public NOPLoggerFactory() {
            // nothing to do
        }
        public Logger getLogger(String name) {
            return NOPLogger.NOP_LOGGER;
        }
    }
    //代码过多,删除了大部分内容
    public class NOPLogger extends MarkerIgnoringBase {
     	/**
         * The unique instance of NOPLogger.
         */
        public static final NOPLogger NOP_LOGGER = new NOPLogger();
        public final void info(String msg) {
    	    // NOP
        }
    }
    

    2.1.1 获取日志工厂

    继续看下一步getILoggerFactory()方法

    	public static ILoggerFactory getILoggerFactory() {
    		/**
    		* getProvider 获取日志工厂的提供器类,然后由提供器中获取日志工厂类。
    		* 提供器Provider的概念在早期版本中是没有的,
    		* 例如1.7.30本本中 getILoggerFactory方法的代码
    		* 与本例中2.0.0-alpha2-SNAPSHOT版本的getProvider方法的内容大致一致。
    		* 应当是作者为了进一步解耦而做出的设计。
    		*/
            return getProvider().getLoggerFactory();
        }
    

    slf4j 的LoggerFactory 的 getProvider方法最终返回一个SLF4JServiceProvider对象的实例,
    而不同的SLF4JServiceProvider的getLoggerFactory方法返回的是内嵌了具体实现日志功能的日志框架(Log4J / JCL / JUL 等等)的工厂类,
    由于本篇仅讨论SLF4J,所以不进行扩展。那么我们的主要就放在了getProvider方法中。如下

    	static SLF4JServiceProvider getProvider() {
    		//首先判断初始化状态是否时未初始状态
            if (INITIALIZATION_STATE == UNINITIALIZED) {
            	//加同步锁
                synchronized (LoggerFactory.class) {
                    if (INITIALIZATION_STATE == UNINITIALIZED) {
                    	//变更初始化状态为正在初始化中
                        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                        //进行初始化
                        performInitialization();
                    }
                }
            }
            //判断初始化状态
            switch (INITIALIZATION_STATE) {
            //初始化成功 就返回Provider
            case SUCCESSFUL_INITIALIZATION:
                return PROVIDER;
            //没有找到日志实现就返回NOP provider,将不输出任何日志消息。
            case NOP_FALLBACK_INITIALIZATION:
                return NOP_FALLBACK_FACTORY;
            //初始化失败就抛出异常
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
            //正在初始化中,就返回 临时替代的 provider SUBST_PROVIDER
            //static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
            case ONGOING_INITIALIZATION:
                // support re-entrant behavior.
                // See also http://jira.qos.ch/browse/SLF4J-97
                return SUBST_PROVIDER;
            }
            throw new IllegalStateException("Unreachable code");
        }
    

    由上我们也可以看到一个简单的流程,其实就是初始化和结果处理
    初始化
    没初始化就初始化,针对多线程访问直接加类锁。
    结果处理

    • 成功: 就返回当前的Provider,
    • 失败: 抛出异常,
    • 查找不到具体实现: 就是啥也没有的NOP Provider。
    • 正在进行中: 就给出一个默认的替代的 SUBT Provider

    2.1.1.1 工厂初始化

    初始化过程 绑定和兼容性检查

    	private final static void performInitialization() {
    		//绑定
    		bind();
    		
            //如果初始化成功,就进行兼容性检查
            if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
                versionSanityCheck();
            }
        }
    
    
    2.1.1.1.1 绑定

    查找实现
    记录查找状态

    
    	private final static void bind() {
            try {
            	//使用ServiceLoader机制来加载实现了SLF4JServiceProvider接口的实现类
            	//针对ServiceLoader机制,可以查阅 JDK1.6+ API文档
                List<SLF4JServiceProvider> providersList = findServiceProviders();
                /**
                * 方法名很准确,报告多个绑定的不确定性。
                * 当slf4j查找到不止一个Provider的时候,
                * 例如JCL 和 JUL 的日志都有,slf4j将向用户汇报这一情况。
                * 因为当多个实现时,通过下面的代码可以看到,slf4j是获取了
                * provider数组的第一个并返回的。
                * 那么这时候因为排序的不同将可能导致日志的实现者是随机的。
                */
                reportMultipleBindingAmbiguity(providersList);
    			//如果provider数组不为空,对provider进行初始化。
                if (providersList != null && !providersList.isEmpty()) {
                	PROVIDER = providersList.get(0);
                	// SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
                	//初始化provider,注意作者的这句话,provider的初始化只在这个地方得到初始化。
                	PROVIDER.initialize();
                	//设置初始化状态为成功初始化。
                	INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                	//报告实际采用的日志实现。这里面也是判空然后report数组第一个
                    reportActualBinding(providersList);
                } 
                //如果初始化数组为空,报告这一情况,并设置状态为NOP
    			else {
    				//设置初始化状态为 NOP 
                    INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                    //报告
                    Util.report("No SLF4J providers were found.");
                    Util.report("Defaulting to no-operation (NOP) logger implementation");
                    Util.report("See " + NO_PROVIDERS_URL + " for further details.");
    				/**
    				* 这里是使用旧的日志实现查找机制查找可能的日志路径集。
    				* 因为1.8 以前的slf4j 绑定日志实现的机制通过ClassLLoader
    				* 1.8以后是使用ServiceLoader,ServiceLoader需要日志实现框架进行类似注册的行为,但是的旧版的日志实现没有进行。
    				* 简单的说,就是版本不兼容,所以slf4j会在控制台打印出你已经配置的不兼容的日志实现
    				* 同时他会打印出官方网站来说明这一情况的解决方案
    				* http://www.slf4j.org/codes.html#ignoredBindings
    				*/
                    Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                    reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
                }
                //初始化替代记录器,同时并在初始化时进行的日志事件
                postBindCleanUp();
            } catch (Exception e) {
            	//failedBinding上提,设置状态为失败,报告。
                //failedBinding(e);
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
            	Util.report("Failed to instantiate SLF4J LoggerFactory", t);
                throw new IllegalStateException("Unexpected initialization failure", e);
            }
    	}
    
    2.1.1.1.2 版本兼容性检查

    当绑定成功时,slf4j将对绑定结果的provider与当前使用的slf4j版本进行兼容性检查。
    如果兼容性检查未通过,但是slf4j 仅仅在控制台进行了输出,并没有产生异常,所以尽管可能部分不兼容,但运气好的话,日志组件仍然可以正常工作。

    	private final static void versionSanityCheck() {
            try {
            	//获取版本号
                String requested = PROVIDER.getRequesteApiVersion();
                boolean match = false;
                //static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.8", "1.7" }; 笔者使用的版本是2.0.0 兼容性版本是1.8  1.7。
               	//匹配
                for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
                    if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
                        match = true;
                    }
                }
                //如果匹配没成功,将给用户报告这一情况。
                if (!match) {
                    Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
                                    + Arrays.asList(API_COMPATIBILITY_LIST).toString());
                    Util.report("See " + VERSION_MISMATCH + " for further details.");
                }
            } catch (java.lang.NoSuchFieldError nsfe) {
                // given our large user base and SLF4J's commitment to backward
                // compatibility, we cannot cry here. Only for implementations
                // which willingly declare a REQUESTED_API_VERSION field do we
                // emit compatibility warnings.
            } catch (Throwable e) {
                // we should never reach here
                Util.report("Unexpected problem occured during version sanity check", e);
            }
        }
    

    2.1.1.2 获取工厂结果处理及返回

    上面我们已经对初始化过程进行了部分的解析,下面来看一下之后的switch块中的结果处理。

    	static SLF4JServiceProvider getProvider() {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                synchronized (LoggerFactory.class) {
                    if (INITIALIZATION_STATE == UNINITIALIZED) {
                        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                        performInitialization();
                    }
                }
            }
    		/**
    		* 看下面
    		* 下面这块代码也很清晰。根据不同的结果返回不同的工厂
    		*/
            switch (INITIALIZATION_STATE) {
            case SUCCESSFUL_INITIALIZATION:
                return PROVIDER;
            case NOP_FALLBACK_INITIALIZATION:
                return NOP_FALLBACK_FACTORY;
            case FAILED_INITIALIZATION:
                throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
            case ONGOING_INITIALIZATION:
                // support re-entrant behavior.
                // See also http://jira.qos.ch/browse/SLF4J-97
                return SUBST_PROVIDER;
            }
            throw new IllegalStateException("Unreachable code");
        }
    

    需要注意的是,当状态为ONGOING_INITIALIZATION时,返回的SUBST_PROVIDER,最终用户得到的Logger对象将无法完成日志输出功能,同时slf4j将得到一个失败的日志事件,在初始化阶段的末尾也就是LoggerFactory的postBindCleanUp()方法,将初始化这个SUBT Provider,同时对失败的日志事件进行报告。

    2.1.1.2.1 初始化的ONGOING_INITIALIZATION处理

    这部分只希望了解大致过程的人可以跳过不看。
    这里我为了实验,加了logback的依赖。不然就是nop了
    部分代码如下:

    //工厂类持有一个静态的SubstituteServiceProvider对象实例SUBST_PROVIDER 
    public final class LoggerFactory {
    	static final SubstituteServiceProvider SUBST_PROVIDER = new SubstituteServiceProvider();
    }
    //SubstituteServiceProvider中的属性SubstituteLoggerFactory
    public class SubstituteServiceProvider implements SLF4JServiceProvider {
        private SubstituteLoggerFactory loggerFactory = new SubstituteLoggerFactory();
    }
    //SubstituteLoggerFactory中的map  loggers 存储着SubstituteLogger实例
    public class SubstituteLoggerFactory implements ILoggerFactory {
    
    	final Map<String, SubstituteLogger> loggers = new ConcurrentHashMap<String, SubstituteLogger>();
    	//当LoggerFactory的SUBST_PROVIDER.getLogger()时,首次进入创建一个SubstituteLogger 放在loggers中
    	synchronized public  Logger getLogger(String name) {
            SubstituteLogger logger = loggers.get(name);
            if (logger == null) {
                logger = new SubstituteLogger(name, eventQueue, postInitialization);
                loggers.put(name, logger);
            }
            //最终将这个SubstituteLogger对象的实例返回给用户
            return logger;
        }
    }
    //当调用logger.info("HELLO SLF4J!")时,如下
    public class SubstituteLogger implements Logger {
    	
    	public void info(String msg) {
            delegate().info(msg);
        }
        private volatile Logger _delegate;
        public final boolean createdPostInitialization;
    	public Logger delegate() {
    		//首次进入为null
            if(_delegate != null) {
                return _delegate;
            }
            //boolean默认为false
            if(createdPostInitialization) {
                return NOPLogger.NOP_LOGGER;
            } else {
            	//进入这个方法
                return getEventRecordingLogger();
            }
        }
        private EventRecodingLogger eventRecodingLogger;
       	private Logger getEventRecordingLogger() {
       		//首次进入为null
            if (eventRecodingLogger == null) {
            	//创建了一个事件记录日志EventRecodingLogger
                eventRecodingLogger = new EventRecodingLogger(this, eventQueue);
            }
            return eventRecodingLogger;
        }
    }
    //EventRecodingLogger 没有info方法,往上层找到AbstractLogger后发现,最终调用的是EventRecodingLogger的handleNormalizedLoggingCall方法,也就是记录了一次记录事件,此时注意日志最终没有得到正确的输出。
    public class EventRecodingLogger extends LegacyAbstractLogger {
    	// WARNING: this method assumes that any throwable is properly extracted
    	protected void handleNormalizedLoggingCall(Level level, Marker marker, String msg, Object[] args,
    			Throwable throwable) {
    		//记录一次记录事件
    		SubstituteLoggingEvent loggingEvent = new SubstituteLoggingEvent();
    		loggingEvent.setTimeStamp(System.currentTimeMillis());
    		loggingEvent.setLevel(level);
    		loggingEvent.setLogger(logger);
    		loggingEvent.setLoggerName(name);
    		if (marker != null) {
    			loggingEvent.addMarker(marker);
    		}
    		loggingEvent.setMessage(msg);
    		loggingEvent.setThreadName(Thread.currentThread().getName());
    		loggingEvent.setArgumentArray(args);
    		loggingEvent.setThrowable(throwable);
    		eventQueue.add(loggingEvent);
    	}
    }
    

    那么slf4j在记录了这次在初始化完成前的记录事件后,是如何对此进行处理的?
    我们回到LoggerFactory的bind()方法,其中在代码末尾
    在这里插入图片描述
    postBindCleanUp()方法中有我们想要的答案
    看代码

    	private static void postBindCleanUp() {
    		//修复替代的日志
    		fixSubstituteLoggers();
    		//回放之前发生的记录事件
    		replayEvents();
    		// release all resources in SUBST_FACTORY
    		SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
    	}
    	private static void fixSubstituteLoggers() {
            synchronized (SUBST_PROVIDER) {
               SUBST_PROVIDER.getSubstituteLoggerFactory().postInitialization();
                for (SubstituteLogger substLogger : SUBST_PROVIDER.getSubstituteLoggerFactory().getLoggers()) {
                    Logger logger = getLogger(substLogger.getName());
                    substLogger.setDelegate(logger);
                }
            }
        }
        
        private static void replayEvents() {
            final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_PROVIDER.getSubstituteLoggerFactory().getEventQueue();
            final int queueSize = queue.size();
            int count = 0;
            final int maxDrain = 128;
            List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain);
            while (true) {
                int numDrained = queue.drainTo(eventList, maxDrain);
                if (numDrained == 0)
                    break;
                for (SubstituteLoggingEvent event : eventList) {
                    replaySingleEvent(event);
                    if (count++ == 0)
                        emitReplayOrSubstituionWarning(event, queueSize);
                }
                eventList.clear();
            }
        }
        
        private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
            if (event.getLogger().isDelegateEventAware()) {
                emitReplayWarning(queueSize);
            } else if (event.getLogger().isDelegateNOP()) {
                // nothing to do
            } else {
                emitSubstitutionWarning();
            }
        }
        
        private static void emitReplayWarning(int eventCount) {
            Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
            Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
            Util.report("See also " + REPLAY_URL);
        }
        private static void emitSubstitutionWarning() {
            Util.report("The following set of substitute loggers may have been accessed");
            Util.report("during the initialization phase. Logging calls during this");
            Util.report("phase were not honored. However, subsequent logging calls to these");
            Util.report("loggers will work as normally expected.");
            Util.report("See also " + SUBSTITUTE_LOGGER_URL);
        }
    

    最终 emitReplayWarning 方法和 emitSubstitutionWarning将在控制台输出有一次失败的记录事件。
    我们简单进行一下实验。

    	public static void main(String[] args) {
    		//线程0
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Logger logger = LoggerFactory.getLogger("demo");
                    logger.info("Hello SLF4J");
                }
            }).start();
    
            Logger logger = LoggerFactory.getLogger("demo");
            logger.info("Hello SLF4J");
    		//线程1
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Logger logger = LoggerFactory.getLogger("demo");
                    logger.info("Hello SLF4J");
                }
            }).start();
    
    
        }
    

    结果

    SLF4J: A number (1) of logging calls during the initialization phase have been intercepted and are
    SLF4J: now being replayed. These are subject to the filtering rules of the underlying logging system.
    SLF4J: See also http://www.slf4j.org/codes.html#replay
    18:29:43.829 [main] INFO demo - Hello SLF4J
    18:29:43.833 [Thread-1] INFO demo - Hello SLF4J
    

    可以看到其中一个线程与持有锁的线程成功初始化前进入了方法,因此日志输出未成功,slf4j在控制台进行了提示。(这个结果进行了多次多角度的实验,有兴趣的可以自己去试试,测试的时候记得先加入实现的日志框架依赖)

    2.1.2 从工厂获取日志实例

    因为从provider中获取loggerFactory以及从具体的工厂中获取日志实例涉及到不同的日志实现框架,所以这里不多赘述,有兴趣的可以自己去看看。

    2.2 打印日志

    这里我使用的logback实现,代码很长,不多赘述。

    	public void info(String msg) {
            this.filterAndLog_0_Or3Plus(FQCN, (Marker)null, Level.INFO, msg, (Object[])null, (Throwable)null);
        }
    

    3 SLF4J 绑定不同的日志框架实现测试

    简单对slf4j绑定不同的日志框架实现进行测试

    package org.slf4j;
    
    /**
     * @author Kern
     * @date 2020/3/23 13:25
     * @description TODO
     */
    public class Demo {
    
        public static void main(String[] args) {
            Logger logger = LoggerFactory.getLogger("demo");
            logger.info("HELLO SLF4J!");
        }
    }
    
    

    3.1 NOP

      <dependencies>
    <!--            <dependency>-->
    <!--                <groupId>ch.qos.logback</groupId>-->
    <!--                <artifactId>logback-classic</artifactId>-->
    <!--                <version>1.3.0-alpha1</version>-->
    <!--            </dependency>-->
      </dependencies>
    

    maven没有依赖,也就是只有slf4j-api
    在这里插入图片描述
    只有一些提示!

    3.2 logback

      <dependencies>
                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                    <version>1.3.0-alpha1</version>
                </dependency>
      </dependencies>
    

    在这里插入图片描述

    3.3 log4j 2

    maven依赖

      <dependencies>
    
          <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
          <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.10.0</version>
          </dependency>
    
          <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
          <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.10.0</version>
          </dependency>
    
      </dependencies>
    

    log4j2.xml 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="INFO">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="INFO">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    在这里插入图片描述

    3.4 JUL

    <dependencies>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-jdk14</artifactId>
                    <version>2.0.0-alpha1</version>
                </dependency>
      </dependencies>
    

    在这里插入图片描述
    还是中文的

    3.5 错误版本测试(logback)

      <dependencies>
                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                    <version>1.2.3</version>
                </dependency>
      </dependencies>
    

    引入的是1.2.3 的版本,适用于slf4j-api 1.8以前的版本,我使用的是github的2.0.0版本。所以版本是不兼容的。
    在这里插入图片描述
    箭头所指的就是为老方式加载的jar文件,所以说还是能找到。为什么作者不向后兼容呢? 也许还有其他地方的不兼容吧。

    4 附图

    到这里,关于slf4j的一次简单日志输出过程源码解析完了。最后是一个流程图,希望能够帮助看这篇博文的小伙伴加强理解。
    在这里插入图片描述

    展开全文
  • Volley源码解析

    2016-12-18 18:59:35
     很早就想写篇Volley源码解析的文章,但是写文章,特别是写源码解析的文章其实是需要决心的,也是需要时间的,因为你需要把你老早就看过的代码重新理一遍,然后加上注释,然后解析,继上一次写了个Volley的引子后,...

    尊重原创,转载请注明出处,谢谢


      很早就想写篇Volley源码解析的文章,但是写文章,特别是写源码解析的文章其实是需要决心的,也是需要时间的,因为你需要把你老早就看过的代码重新理一遍,然后加上注释,然后解析,继上一次写了个Volley的引子后,过去也有一段时间了,终于可以抽出一个周末下午的时间来写一下了,希望对需要的朋友有用的,另外,Volley的源码写的十分出色,可拓展性方面非常强,功能模块十分清晰,所以也是对自我的一次洗礼吧,学习他的设计以及编码也是具有很大的价值的,废话不多说了。


         我觉得解析的最好方式就是对照源码给出注释,然后得出结果,所以本文很大一部分解析都写在了代码的注释方面,从代码中来,到代码中去嘛!


    先来看一下Volley的使用方式:

    private RequestQueue mRequestQueue;
    mRequestQueue =  Volley.newRequestQueue(this); 

    JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET,url,null,new Response.Listener<JSONObject>() { 
                @Override 
                public void onResponse(JSONObject response) { 

                     ......... 
                } 
            },new Response.ErrorListener() { 
                @Override 
                public void onErrorResponse(VolleyError error) { 
                   ............
                } 
            }); 

    mRequestQueue.add(request);

     
    接下来对Volley的源码进行解析:

    1.从Volley类入手,这里面就是提供了一个创建RequestQueue的方法:newRequestQueue(),有两个重载方法,一个是使用默认的HttpStack,一个是可以自定义HttpStack作为参数传进去。
    下面是newRequestQueue()代码及解析:

    public static RequestQueue newRequestQueue(Context contextHttpStack stack) {
        File cacheDir = new File(context.getCacheDir()DEFAULT_CACHE_DIR);//缓存的文件路径

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName0);
            userAgent = packageName + "/" + info.versionCode;
       catch (NameNotFoundException e) {
        }

        if (stack == null) {//使用Volley默认的HttpStack
            if (Build.VERSION.SDK_INT >= 9) {//API版本在9以上则使用HurlStack,里面的performRequest()是通过HttpUrlConnection去实现网络请求的
                stack = new HurlStack();

           else {//API版本在9以上则使用HttpClientStack,里面的performRequest()是通过HttpClient去实现网络请求的
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        //new 一个BasicNetWork,这里面主要就是一个perfromRequest()方法区发起请求,是通过调stack里面的perfromRequest()来实现的
        Network network = new BasicNetwork(stack);

        //创建一个RequestQueue,关注传进去的两个参数
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir)network);

        queue.start();//这个start之后直接开启了整个队请求队列的处理过程,可以看下面对这个方法的解析

        return queue;
    }

    所以可以知道真正发送请求的就是BasicNetWork里的performRequest(),它里面是通过HttpStack的perfromRequest()去发送请求的,而这里面就是利用HttpUrlConnection或HttpClient。所以对Volley最基础的发送请求部分有兴趣的话可以看BasicNetWork.performRequest()。

    接下来解析一下RequestQueue:

    先来看看RequestQueue类里的几个变量,也是整个框架最重要的几个组成部分:
    1.CacheDispatcher :继承自Thread,是一个线程,负责处理走缓存缓存的请求,当调用start()启动之后,会不断的从缓存请求队列中取出请求进行处理,请求处理结束则把结果传递给ResponseDelivery去执行之后的处理,如果结果为缓存过,或者缓存失效或需要重新刷新的情况下,该请求都会重新进入NetWorkDispathcher.

    2.NetworkDispatcher:也是一个线程,专门处理走网络的请求,start()后也是不断的从请求队列里取请求,处理请求,获取到结果后把结果传递到ResponseDelivery去执行后续处理,然后在判断是否需要缓存结果。

    3.ResponseDelivery:返回结果分发接口

    4.Network:就是执行网络请求的,里面的performRequest()就是发送请求并接收结果, 并将结果转换为可被ResponseDelivery处理的NetworkResponse。

    5.Cache:缓存请求结果的,默认是DiskBaseCache

    上面的这几个类各司其职,比如调度处理请求的、实现发送网络请求的、做缓存的等的,完成各自的工作,还有一个特点就是他们大都都是接口,而不是具体实现类,Volley有大量的面向接口的编程,这极大的加大了框架的灵活性和可拓展性,用户甚至可以自定义实现某个部分的功能。

    RequestQueue类内部还有一些变量:

    1.mCacheQueue:是一个存放结果已被缓存过的请求的队列,CacheDispather就是每次去这里面取请求进行处理
    2.mNetworkQueue:存放进行网络请求的队列,NetworkDispather就是每次去这里面取请求进行处理的

    上面两个队列的类型都是BlockingQueue:是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用,十分适合这种场景。

    3.NetworkDispatcher[] mDispatchers:就是一个放Networkdispather的数组,默认定义容量是4

    上面就是RequestQueue类里面的一些重要变量了,接下来看看它里面的方法,主要就是两个,一个start(),可以看到上面Volley.newRequestQueue()里面最后就调了start();还有一个是add(),就是把请求加入Queue中。

    1.start():
    public void start() {
        stop() // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueuemNetworkQueuemCachemDelivery);
        mCacheDispatcher.start();//开启CacheDispather线程,开始处理mCacheQueue中的请求

        // Create network dispatchers (and corresponding threads) up to the pool size.
        //循环开启数组里的NetworkDispatcher线程,开始处理mNetworkQueue中的请求
        for (int i = 0i < mDispatchers.lengthi++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueuemNetwork,
                    mCachemDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    下面具体看一下CacheDispather start之后,里面的run是如果处理mCacheQueue里的请求的,也就是CacheDispather里面的run()方法:

    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {//无限循环取请求,处理请求
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();//取出一个请求
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {//如果请求已被取消
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                //Cache根据key取出相应的结果entry
                Cache.Entry entry = mCache.get(request.getCacheKey());

                //如果结果为null,则打上缓存消失的标志,然后把请求加入NetworkQueue,重新进行网络请求
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }

                // If it is completely expired, just send it to the network.
                //如果缓存的请求结果已过期,则同样打个标志,然后放进NetwoekQueue重新请求
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                //取到结果且不过期(命中)
                request.addMarker("cache-hit");
                //把结果转化为NetworkResponse类型
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.dataentry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                //如果结果是不需要刷新的,则直接发送给ResponseDelivery进行分发
                if (!entry.refreshNeeded()) {
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(requestresponse);
                }
                //如果结果是需要刷新的,则也是先把结果发送给ResponseDelivery进行分发,然后再放进NetworkQueue中进行网络请求
                //这样可以先展示缓存的结果,然后又去请求网络回来就可以刷新结果了
                else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(requestresponse, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                           catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

    上面的代码已经十分清晰的的解析了CacheDispatcher线程取请求处理请求的过程,可以看到取结果是到Cache里面去取的,那下面就解析一下这个Cache:

    Cache只是一个接口,里面定义了对结果的存、取、删除等方法,而DiskBasedCache才是真正的实现类,先来看一下Cache里面缓存结果的一个实体Entry:
    public static class Entry {
        /** The data returned from cache. */
        public byte[] data;//结果数据

        /** ETag for cache coherency. */
        public String etag;//标签,如消失、过期等

        /** Date of this response as reported by the server. */
        public long serverDate;//网络请求回来的时间

        /** The last modified date for the requested object. */
        public long lastModified;//上一次刷新的时间

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map<StringString> responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        //是否过期
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        //是否需要刷新
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }


    下面接着看一下NetworkDispatcher里面是如何处理mNetworkQueue里的请求的,同样看他的run()方法里:

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {//开启无限循环取请求,处理请求
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();//取请求
           catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // If the request was cancelled already, do not perform the
                // network request.
                //如果请求被取消
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                //调用NetWorkperformRequest()来发送网络请求,并获取到结果NetworkResponse
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                //转化response为我们想要的结果实体类型
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                //判断结果是否需要被缓存,如果需要,则缓存进Cache
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey()response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                //发送结果给ResponseDelivery分发出去
                mDelivery.postResponse(requestresponse);
           catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(requestvolleyError);
           catch (Exception e) {
                VolleyLog.e(e"Unhandled exception %s"e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(requestvolleyError);
            }
        }
    }

    上面我们已经知道了Volley是如何取请求,处理请求的,接下来看一下ResponseDelivery这个类,上面我们发现得到结果后都会调ResponseDelivery.postResponse()来分发结果:
    同样的ResponseDelivery是一个接口,里面主要有一下三个方法:

    1.postResponse(Request<?> request, Response<?> response) :分发Response
    2.postResponse(Request<?> request, Response<?> response, Runnable runnable):同上
    3.postError(Request<?> request, VolleyError error):分发error

    而它的具体实现类是:ExecutorDelivery
    所以来看看ExecutorDelivery中的PostResponse()方法:
    public void postResponse(Request<?> requestResponse<?> responseRunnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(requestresponserunnable));
    }

    可以看到最后是执行了一个Runable:ResponseDeliveryRunable,所以我们看看这个Runable里面的run():

    @Override
    public void run() {
        // If this request has canceled, finish it and don't deliver.
        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }

        // Deliver a normal response or error, depending.
        //调用Request里面的deliverResponse()方法
        if (mResponse.isSuccess()) {
            mRequest.deliverResponse(mResponse.result);
       else {
            mRequest.deliverError(mResponse.error);
        }

        // If this is an intermediate response, add a marker, otherwise we're done
        // and the request can be finished.
        if (mResponse.intermediate) {
            mRequest.addMarker("intermediate-response");
       else {
            mRequest.finish("done");
        }

        // If we have been provided a post-delivery runnable, run it.
        if (mRunnable != null) {
            mRunnable.run();
        }
    }

    可以看到是调用了Request里面的deliverResponse(),而Request是一个抽象类,具体的分发工作要由继承了Request的具体类来实现这个deliverResponse(),比如Volley封装的StringRequest等等,那么我们可以继承Request来实现这个deliverResponse()实现对结果的分发。

    首先是定义一个listener接口:
    public interface IResponseListener<extends IResponse> {

        void onSuccess(BaseRequest<T> requestresponse);

        void onFail(BaseRequest<T> requestresponseErrorCodeExtend error);
    }

    然后实现一个类继承自Request,实现deliverResponse():
    @Override
    protected void deliverResponse(response) {
        if (responseListener != null) {
            responseListener.onSuccess(this, response);
        }
    }

    这样就通过回调实现了对返回结果的分发,只要我们创建一个Request的时候,这个responseListener就是参数之一,这个时候想起Volley的用法:
    Request request=new Request(new IResponseListener(){
                 onSuccess(){
                  ..........
                 }
    })

    也就知道了。


    上面基本以及解析了Volley是如何从队列中取请求,处理请求,缓存,以及分发返回结果的过程,那么还有一个过程是把请求添加进队列里,这个过程我们使用Volley的时候是通过调用RequestQueue.add()方法来实现的,那么最后就来解析一下这个方法:
    public <T> Request<Tadd(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        //如果此请求是不需要缓存的,则直接加进mNetworkQueue,结束
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        //如果请求是需要缓存的
        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKeystagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold."cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                //加进mCacheQueue
                mCacheQueue.add(request);
            }
            return request;
        }
    }

    这就是把Request加进队列的过程。
    至此,对Volley源码的解析也差不多结束了。


    总结:
    通过对Volley源码的解析,可以看出来这是一个写的十分优秀的开源项目,各模块分工十分明确,基本上都是面对接口编程,框架的可拓展性非常强,你可以自己实现某一模块的功能,然后像插件一样插进去,当然你也可以使用Volley帮你实现的默认的模块,拔插十分方便,这就是一个优秀的框架该有的样子吧,对用户十分友好,我想Volley应该是很多想自己写框架的朋友一个良好的借鉴。


    展开全文
  • zeromq源码解析

    2020-09-07 11:05:46
    这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持,...
  • 前面我们学了一次性的闭锁CountDownLatch,下面我们会接触到一个可以循环的同步辅助类:CyclicBarrier,它允许一组线程互相等待,直到到达某个公共屏障点,在涉及一组固定大小的线程的程序中,这些线程必须不时地互相...
  • 包含已知提供一次性电子邮件服务的域的列表。 包含此项目将拒绝添加到任一列表中的域的列表 域只能是一个列表的成员。 更新数据库 运行./update以从sources.txt列出的源中获取最新域。 默认情况下,所有新域都将...
  • ArrayList源码解析

    2021-01-27 09:25:13
    谈起ArrayList,首先它是基于数组的数据结构实现的,与我们普通定义的数组不同,数组的长度是可以动态扩展的,可以在...这种情况下,数组的长度会在第一次插入元素时候设置。 List<String> list = new ArrayLis
  • tinyxml源码解析

    2017-07-17 10:45:35
    tinyxml是一个小型的xml文件解析器,它使用文档对象模型(DOM)来进行解析,这种模型的处理方式为在解析时,一次性的将整个XML文档进行分析,并在内存中形成对应的树结构,同时,向用户提供一系列的接口来访问和编辑该...
  • 其流畅的界面使您可以一次性发送HTTP请求并解析响应-隐藏了诸如反序列化,内容协商,可选的重试逻辑和URL编码之类的详细信息: Blog result = await new FluentClient ( " https://example.org/api " ) . ...
  • vue源码解析video.txt

    2020-05-12 21:57:15
    某课 -- vue源码解析;资源全,不分上下部分,一次到位。 技术资源通常有时效,大家且学且珍惜啊
  • CountDownLatch源码解析

    2018-05-20 21:39:22
    CountDownLatch:允许一个或多个线程等待其他线程中执行完成的同步辅助工具。 CountDownLatch使用一个给定的... 这是一个一次性的现象 - 计数器无法重置。 如果您需要重置计数器的版本,可以考虑使用CyclicBar...
  • 操作一个GPIO,需要仔细对照芯片手册,好繁琐;每换一个主控芯片,所有工作从头来过;想开发个现代点支持各种动效的UI,发现几乎是不可能的事情...一次编写,到处运行,不仅仅是个口号,而且还是XBOOT存在的唯一原因。
  • 本来是想把整个dubbo源码解析一次性弄完,再做成一个系列来发布的,但是正巧最近有位好朋友要去杭州面试,就和我交流了一下.本着对dubbo源码略有心得的心态,在交流过程中也发表了个人的一些粗劣的拙见.但是非常不幸的是...
  • Flutter 路由源码解析

    2019-03-17 21:30:36
    一次,我尝试以不贴一行源代码的方式向你介绍 Flutter 路由的实现原理,同时为了提高你阅读源码的积极,除了原理介绍以外,又补充了两个新的模块:从源码中学习到的编程技巧,以及 阅读源码之后对实际应用开发...
  • 该项目基于XML文件的使用,如何使用XPath解析它们以及对它们进行序列化/反序列化。 ###如何实施有4个主要软件包: xpath :包含PeopleXMLReader ,用于加载和解析people.xml文件 jaxb :包含JAXBMarshaller ,它...
  • 前言json报文相信大家都接触过,对于前段JavaScript来说,它是最方便处理的数据格式,而对于后端应用来说,解析json报文并没有xml格式来的那么清晰明了,尤其是对于通用的处理来说很难做到,这里在参考了阿里巴巴的...
  • B树 解析源码

    千次阅读 2018-01-31 11:17:10
    概念 B树,是普遍运用于文件系统...所以在利用索引查找时,不会一次性把整个索引加载到内存,而是每次只加载一个磁盘页(这里的磁盘页对应索引树的结点)。 若索引树采用二叉树结构,则一个页面只能存放一个值。因此
  • Dubbo SPI 源码解析

    2021-04-25 18:47:23
    Java SPI是一次性加载、实例化所有的扩展点实现,不支持根据key值去加载、实例化指定的扩展点实现。 Java SPI不支持IOC、AOP,Dubbo SPI则通过自动包装、自动装配等特性实现了IOC、AOP。 ServerLoader在加载类的过程...
  • 将XML一次性加载到内存中构造树形结构,操作简单,但内存占用大 SAX 部分加载,加载到某类型节点时会调用对应注册的回调函数,只能向后单向进行,且无法提供写操作 StAX JDK提供的API,类似SAX,将XML文档作为一个...
  • Redis事务源码解析

    2018-05-24 13:23:45
    事务定义:将多个命令打包,然后一次性、按顺序执行多个命令。在执行命令期间(EXEC),不会中断事务而去执行其他客户端的命令请求。满足ACID中的原子性、一致性和隔离性。举个例子:redis 127.0.0.1:6379&gt; ...
  • 2、周期事件:让一段程序每隔指定的时间就执行一次。(比如serverCron函数,每秒执行次数通过redis.conf/hz设置)目前版本的Redis只使用周期事件,而没有使用定时事件。具体源码参考ae.c/ae.h文件中。文中的源码...
  • 折纸 总览 折纸是用纯Ruby编写的用于处理PDF文件的框架。 它提供了解析PDF内容,修改和保存PDF结构以及创建新文档的可能。 折纸支持PDF规范的一些高级功能: ... 默认行为是一次解析文档的全部
  • client-go源码解析():client-go客户端对象 client-go的重要 client-go是对K8s集群的二开发工具,所以client-go是k8s开发者的必备工具之。 本篇目的 本篇的目的旨在教会大家k8s集群的客户端有哪些种类,...
  • 部分:Java基础 第二部分:热门框架 第三部分:数据库相关 第四部分:Java进阶 23.说一下JVM的内存布局和运行原理? 24.垃圾回收算法有什么? 25.你用过一些垃圾回收器?它们有什么区别? 26.生产环境...
  • HashMap源码解析笔记

    2017-10-23 11:06:00
    ) 总结: 0.初始化:大于initialCapacity的最小的2的n幂,如果initailCapacity为空,取16,为什么取2的n幂为数组长度,见ex 1.Object.hashCode() 取得32位原始int h 2.h的高位与低位作异或处理,加大...
  • jdk 8 之前,其内部是由数组+链表来实现... //节点数组,第一次使用时初始化,后面根据需要调整, transient Node<K,V>[] table; //实际存储的键值对个数 transient int size; //用于迭代防止结构破坏...
  • Apache源于NCSAhttpd服务器,经过多修改,由于其跨平台和安全被广泛使用,成为世界上最流行的Web服务器软件之。Apache取自“a patchy server”的读音,意思是充满补丁的服务器,因为它是自由软件,所以不断有...
  • ROIAlign源码详细解析

    千次阅读 2019-12-25 23:10:43
    ROI Align 是在Mask-RCNN这篇论文里提出的种区域特征聚集方式, 很好地解决了ROI Pooling操作中两量化造成的区域不匹配(mis-alignment)的问题。 其中ROI Align用的是双线插值(内插)来实现的,在分析源码前我们...
  • 一次性的通过Diff算法进行渲染或者更新,而不是每次都要直接操作真实的DOM。 在即时编译的时代,调用DOM的开销是很大的。而Virtual DOM的执行完全都在Javascript 引擎中,完全不会有这个开销。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 515
精华内容 206
关键字:

一次性解析源码