代码拉取完成,页面将自动刷新
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>钱唯の个人博客</title>
<subtitle>佳思忽来,书能下酒;侠情一往,云可赠人。</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://paranoidq.github.io/"/>
<updated>2017-09-21T10:56:26.000Z</updated>
<id>http://paranoidq.github.io/</id>
<author>
<name>Paranoid Qian</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>定制log4j中的RollingFileAppender</title>
<link href="http://paranoidq.github.io/2017/09/19/customize-rollingFileAppender-inlog4j/"/>
<id>http://paranoidq.github.io/2017/09/19/customize-rollingFileAppender-inlog4j/</id>
<published>2017-09-19T09:22:04.000Z</published>
<updated>2017-09-21T10:56:26.000Z</updated>
<content type="html"><![CDATA[<h2 id="场景描述"><a href="#场景描述" class="headerlink" title="场景描述"></a>场景描述</h2><p>在开发中,一直使用的<code>RollingFileAppender</code>,但是存在几个问题:</p><ul><li>这个appender是按照序号作为后缀来命名日志文件的,生产中日志较为频繁的情况下,根据发生时间定位问题日志需要全局grep,耗费时间并且对业务运行有一定的影响。</li><li>rolling的时候会统一将所有已经存在的日志文件序号向后偏移,即原来是<code>test.log、test.log.1</code>,现在变成了<code>test.log(新建的空文件)、test.log.1、test.log.2</code>。这就导致可能之前查找的问题日志在<code>test.log.1</code>中,现在跑到<code>test.log.2</code>里面去了,那么想要保存日志以备后续分析就容易搞错。</li></ul><p>因此为了解决上面的问题,决定采用下面的逻辑:</p><ul><li>一旦日志满了,被backUp了,就不再改变文件名了,保持内容和文件名不动</li><li>用backUp的时间节点作为后缀,这样可以比较容易地根据时间来到对应的日志文件中查找,不需要全局grep了。</li></ul><p>显然,需要对<code>RollingFileAppender</code>进行定制,更改其中的rollOver的逻辑。</p><h2 id="具体方案"><a href="#具体方案" class="headerlink" title="具体方案"></a>具体方案</h2><p>RollingFileAppender继承与FIleAppender,内部函数包括:<br><img src="/img/log4j-rollingAppender/1.png" alt="Alt text"></p><p>大致可以知道,其实就是要修改<code>rollOver()</code>函数的逻辑。我们先看原有<code>rollOver()</code>函数相关的代码:</p><p><code>subAppend</code>函数在当前文件写满之后,会调用rollOver函数,进行“滚动”操作<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">subAppend</span><span class="params">(LoggingEvent event)</span> </span>{</div><div class="line"><span class="keyword">super</span>.subAppend(event);</div><div class="line"><span class="keyword">if</span>(fileName != <span class="keyword">null</span> && qw != <span class="keyword">null</span>) {</div><div class="line"><span class="keyword">long</span> size = ((CountingQuietWriter) qw).getCount();</div><div class="line"><span class="keyword">if</span> (size >= maxFileSize && size >= nextRollover) {</div><div class="line"> rollOver();</div><div class="line">}</div><div class="line">}</div><div class="line">}</div></pre></td></tr></table></figure></p><p><code>rollOver</code>函数做了几个事情:</p><ol><li>首先确保maxBackups值大于0,如果小于等于0则没有必要做滚动,直接重置当前文件即可;检查是否超过maxBackup值,超过了则删除最旧的日志备份文件,这个最旧的文件文件名是<code>fileName.maxBackupInex</code>。</li><li>对当前已经存在的backUp后缀名进行逐个偏移,例如<code>test.log.1</code>变为<code>test.log.2</code>。</li><li>将当前文件重命名为<code>test.log.1</code>,如果重命名没有成功,则恢复fileName为当前文件名,还是继续往后写。</li><li>如果前面的重命名都成功了,那么关闭当前文件,系统在下次写日志时会重建一个新的空日志文件作为写入目标。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="comment">// synchronization not necessary since doAppend is alreasy synched</span></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">rollOver</span><span class="params">()</span> </span>{</div><div class="line">File target;</div><div class="line"> File file;</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (qw != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">long</span> size = ((CountingQuietWriter) qw).getCount();</div><div class="line"> LogLog.debug(<span class="string">"rolling over count="</span> + size);</div><div class="line"> <span class="comment">// if operation fails, do not roll again until</span></div><div class="line"> <span class="comment">// maxFileSize more bytes are written</span></div><div class="line"> nextRollover = size + maxFileSize;</div><div class="line"> }</div><div class="line"> LogLog.debug(<span class="string">"maxBackupIndex="</span>+maxBackupIndex);</div><div class="line"></div><div class="line"> <span class="keyword">boolean</span> renameSucceeded = <span class="keyword">true</span>;</div><div class="line"> <span class="comment">// If maxBackups <= 0, then there is no file renaming to be done.</span></div><div class="line"> <span class="keyword">if</span>(maxBackupIndex > <span class="number">0</span>) {</div><div class="line"> ① ······················································</div><div class="line"> <span class="comment">// Delete the oldest file, to keep Windows happy.</span></div><div class="line"> file = <span class="keyword">new</span> File(fileName + <span class="string">'.'</span> + maxBackupIndex);</div><div class="line"><span class="keyword">if</span> (file.exists())</div><div class="line"> renameSucceeded = file.delete();</div><div class="line"></div><div class="line">② ······················································</div><div class="line"> <span class="comment">// Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}</span></div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = maxBackupIndex - <span class="number">1</span>; i >= <span class="number">1</span> && renameSucceeded; i--) {</div><div class="line"> file = <span class="keyword">new</span> File(fileName + <span class="string">"."</span> + i);</div><div class="line"><span class="keyword">if</span> (file.exists()) {</div><div class="line"> target = <span class="keyword">new</span> File(fileName + <span class="string">'.'</span> + (i + <span class="number">1</span>));</div><div class="line"> LogLog.debug(<span class="string">"Renaming file "</span> + file + <span class="string">" to "</span> + target);</div><div class="line"> renameSucceeded = file.renameTo(target);</div><div class="line">}</div><div class="line">}</div><div class="line"></div><div class="line"> ③ ······················································</div><div class="line"> <span class="keyword">if</span>(renameSucceeded) {</div><div class="line"> <span class="comment">// Rename fileName to fileName.1</span></div><div class="line"> target = <span class="keyword">new</span> File(fileName + <span class="string">"."</span> + <span class="number">1</span>);</div><div class="line"></div><div class="line"> <span class="keyword">this</span>.closeFile(); <span class="comment">// keep windows happy.</span></div><div class="line"></div><div class="line"> file = <span class="keyword">new</span> File(fileName);</div><div class="line"> LogLog.debug(<span class="string">"Renaming file "</span> + file + <span class="string">" to "</span> + target);</div><div class="line"> renameSucceeded = file.renameTo(target);</div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="comment">// if file rename failed, reopen file with append = true</span></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">if</span> (!renameSucceeded) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">this</span>.setFile(fileName, <span class="keyword">true</span>, bufferedIO, bufferSize);</div><div class="line"> } <span class="keyword">catch</span>(IOException e) {</div><div class="line"> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> InterruptedIOException) {</div><div class="line"> Thread.currentThread().interrupt();</div><div class="line"> }</div><div class="line"> LogLog.error(<span class="string">"setFile("</span>+fileName+<span class="string">", true) call failed."</span>, e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="comment">// if all renames were successful, then</span></div><div class="line"> <span class="comment">//</span></div><div class="line"> ④ ······················································</div><div class="line"> <span class="keyword">if</span> (renameSucceeded) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// This will also close the file. This is OK since multiple</span></div><div class="line"> <span class="comment">// close operations are safe.</span></div><div class="line"> <span class="keyword">this</span>.setFile(fileName, <span class="keyword">false</span>, bufferedIO, bufferSize);</div><div class="line"> nextRollover = <span class="number">0</span>;</div><div class="line"> } <span class="keyword">catch</span>(IOException e) {</div><div class="line"> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> InterruptedIOException) {</div><div class="line"> Thread.currentThread().interrupt();</div><div class="line"> }</div><div class="line"> LogLog.error(<span class="string">"setFile("</span>+fileName+<span class="string">", false) call failed."</span>, e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure><p>根据以上的逻辑我们修改rollOver函数中的文件命名规则,在重命名时采用当前时间作为后缀即可。但是,还有一个问题需要解决:<code>如何判定最旧的文件名字</code>。显然,<code>RollingFileAppender</code>以序号作为后缀是很简单的,直接找最大序号即可;但是如果用时间作为后缀就需要做一些额外的处理。这里有两种思路:</p><ul><li>按照后缀排序</li><li>将文件名存在一个FILO队列中,每次需要删除的时候取出队首的文件名进行删除</li></ul><p>第二种思路相对比较简单,因此我采用了第二种方式。在实现过程中,还需要解决一个问题,一旦系统重启,FILO队列将丢失,如果不保存FILO队列到磁盘,那么下次系统启动的时候就无法知道已经有了哪些backUp日志文件,FILO就失去了作用,每次系统重启就会导致日志备份文件增长一倍。因此还需要做的一件事情是:</p><ul><li>在改变FILO队列之后,立马将其序列化到磁盘中;然后在<code>Logger</code>初始化的时候加载FILO队列。这样可以还原系统之前的<code>Logger</code>备份文件的所有名称,当达到<code>maxBackUpIndex</code>的时候就能够正确找到文件名进行删除了。</li></ul><p>基本的实现思路介绍完了,下面是修改的代码:</p><p>首先需要定义两个变量,一个是FILO队列,另一个是序列化FILO队列到磁盘时的文件后缀名,注意要与日志备份文件区分开。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 序列化existedLogFileNames队列的文件名后缀</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String SERIALIZE_POSTFIX = <span class="string">".restore"</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 存储backUp的文件名</span></div><div class="line"><span class="comment"> * existedLogFileNames.capacity == maxBackUp</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">private</span> LinkedBlockingDeque<String> existedLogFileNames;</div></pre></td></tr></table></figure></p><p>然后定义一个函数,用当前时间和原始文件名构造新的backup文件名:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * 将当前的日志文件重命名为backUp文件</span></div><div class="line"><span class="comment"> * 以当前时间作为后缀,方便查找</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span></span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="function"><span class="keyword">private</span> String <span class="title">renameAsBackup</span><span class="params">()</span> </span>{</div><div class="line"> SimpleDateFormat format = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyyMMdd_HHmmss_SSS"</span>);</div><div class="line"> String dateString = format.format(<span class="keyword">new</span> Date(System.currentTimeMillis()));</div><div class="line"> <span class="keyword">return</span> fileName + <span class="string">"."</span> + dateString;</div><div class="line">}</div></pre></td></tr></table></figure></p><p>然后我们改写rollOver函数:</p><ol><li>删除文件的时候,从FILO队列中poll头部文件名删除</li><li>移除了滚动重命名的部分代码,只需要将当前已满的日志文件重命名,重命名的名字由<code>renameBackup()</code>函数根据当前时间来构造</li><li>重命名成功之后,将重命名后的文件放入FILO队列中,同时立即序列化FILO队列到磁盘<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="comment">// synchronization not necessary since doAppend is alreasy synched</span></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">rollOver</span><span class="params">()</span> </span>{</div><div class="line"> LogLog.debug(<span class="string">"当前队列剩余空间: "</span> + existedLogFileNames.remainingCapacity());</div><div class="line"> System.out.println(<span class="string">"当前队列剩余空间: "</span> + existedLogFileNames.remainingCapacity());</div><div class="line"></div><div class="line"> File file;</div><div class="line"> <span class="keyword">if</span> (qw != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">long</span> size = ((CountingQuietWriter) qw).getCount();</div><div class="line"> LogLog.debug(<span class="string">"rolling over count="</span> + size);</div><div class="line"> <span class="comment">// if operation fails, do not roll again until</span></div><div class="line"> <span class="comment">// maxFileSize more bytes are written</span></div><div class="line"> nextRollover = size + maxFileSize;</div><div class="line"> }</div><div class="line"> LogLog.debug(<span class="string">"maxBackupIndex="</span>+maxBackupIndex);</div><div class="line"></div><div class="line"> <span class="keyword">boolean</span> deleteSuccess = <span class="keyword">true</span>;</div><div class="line"> <span class="comment">// If maxBackups <= 0, then there is no file renaming to be done.</span></div><div class="line"> <span class="keyword">if</span>(maxBackupIndex > <span class="number">0</span> && existedLogFileNames.size() >= maxBackupIndex) {</div><div class="line"> <span class="comment">// Delete the oldest file, to keep Windows happy.</span></div><div class="line"> ① ······················································</div><div class="line"> file = <span class="keyword">new</span> File(existedLogFileNames.pollFirst());</div><div class="line"> <span class="keyword">if</span> (file.exists()) {</div><div class="line"> deleteSuccess = file.delete();</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">boolean</span> renameSuccess = <span class="keyword">true</span>;</div><div class="line"> <span class="keyword">if</span> (deleteSuccess) {</div><div class="line"> ② ······················································</div><div class="line"> <span class="comment">// 将当前日志文件backUp重命名</span></div><div class="line"> File target = <span class="keyword">new</span> File(renameAsBackup());</div><div class="line"> <span class="keyword">this</span>.closeFile();</div><div class="line"> file = <span class="keyword">new</span> File(fileName);</div><div class="line"> LogLog.debug(<span class="string">"Renaming file "</span> + file + <span class="string">" to "</span> + target);</div><div class="line"> renameSuccess = file.renameTo(target);</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (renameSuccess) {</div><div class="line"> <span class="comment">// 将重命名后的文件名存入FILO队列</span></div><div class="line"> ③ ······················································</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> renameSuccess = existedLogFileNames.offerLast(target.getCanonicalPath());</div><div class="line"> } <span class="keyword">catch</span> (IOException e) {</div><div class="line"> renameSuccess = <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (renameSuccess) {</div><div class="line"> <span class="comment">// 将FILO队列序列化到磁盘,以便系统重启时读取</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> File f = <span class="keyword">new</span> File(fileName + SERIALIZE_POSTFIX);</div><div class="line"> f.createNewFile();</div><div class="line"> SerializationUtils.serialize(existedLogFileNames, <span class="keyword">new</span> FileOutputStream(f, <span class="keyword">false</span>));</div><div class="line"> } <span class="keyword">catch</span> (IOException e) {</div><div class="line"> LogLog.debug(<span class="string">"Error serialize existed file queue"</span>, e);</div><div class="line"> renameSuccess = <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="comment">// if file rename failed, reopen file with append = true</span></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">if</span> (!renameSuccess) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="keyword">this</span>.setFile(fileName, <span class="keyword">true</span>, bufferedIO, bufferSize);</div><div class="line"> }</div><div class="line"> <span class="keyword">catch</span>(IOException e) {</div><div class="line"> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> InterruptedIOException) {</div><div class="line"> Thread.currentThread().interrupt();</div><div class="line"> }</div><div class="line"> LogLog.error(<span class="string">"setFile("</span>+fileName+<span class="string">", true) call failed."</span>, e);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="comment">// if deletes were successful, then</span></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="keyword">if</span> (renameSuccess) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// This will also close the file. This is OK since multiple</span></div><div class="line"> <span class="comment">// close operations are safe.</span></div><div class="line"> <span class="keyword">this</span>.setFile(fileName, <span class="keyword">false</span>, bufferedIO, bufferSize);</div><div class="line"> nextRollover = <span class="number">0</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">catch</span>(IOException e) {</div><div class="line"> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> InterruptedIOException) {</div><div class="line"> Thread.currentThread().interrupt();</div><div class="line"> }</div><div class="line"> LogLog.error(<span class="string">"setFile("</span>+fileName+<span class="string">", false) call failed."</span>, e);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></li></ol><p>rollOver的逻辑写完了,还需要添加Logger初始化时从磁盘加载FILO队列的逻辑。首先将这段逻辑封装为一个函数:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">restoreBackUpsIfNull</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">if</span> (existedLogFileNames == <span class="keyword">null</span>) {</div><div class="line"> existedLogFileNames = <span class="keyword">new</span> LinkedBlockingDeque<String>(getMaxBackupIndex());</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> LinkedBlockingDeque<String> restore = (LinkedBlockingDeque<String>) SerializationUtils.deserialize(</div><div class="line"> <span class="keyword">new</span> FileInputStream(fileName + SERIALIZE_POSTFIX));</div><div class="line"> existedLogFileNames.addAll(restore);</div><div class="line"> } <span class="keyword">catch</span> (FileNotFoundException e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>然后通过debug可以发现Logger初始化的时候会调用<code>setMaxBackUpIndex()</code>来将配置文件的值设置进来,因此重写<code>setMaxBackUpIndex()</code>函数,添加加载FILO队列的逻辑:(<strong>这种写法实际上是存在BUG的,后面会分析到</strong>)<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setMaxBackupIndex</span><span class="params">(<span class="keyword">int</span> maxBackups)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.maxBackupIndex = maxBackups;</div><div class="line"> restoreBackUpsIfNull();</div><div class="line"> }</div></pre></td></tr></table></figure></p><p>以上就是定制<code>RollingFileAppender</code>的整个思路。</p><h2 id="运行测试及BUG出现"><a href="#运行测试及BUG出现" class="headerlink" title="运行测试及BUG出现"></a>运行测试及BUG出现</h2><p><code>log4j.properties</code>配置文件如下:<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">log4j.<span class="attribute">rootLogger</span>=DEBUG</div><div class="line">log4j.logger.me.<span class="attribute">zex</span>=DEBUG, file</div><div class="line">log4j.logger.org.<span class="attribute">log4j</span>=DEBUG, console</div><div class="line"></div><div class="line">log4j.appender.<span class="attribute">console</span>=org.apache.log4j.ConsoleAppender</div><div class="line">log4j.appender.console.<span class="attribute">threshold</span>=INFO</div><div class="line">log4j.appender.console.<span class="attribute">layout</span>=org.apache.log4j.PatternLayout</div><div class="line">log4j.appender.console.layout.<span class="attribute">ConversionPattern</span>=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n</div><div class="line"></div><div class="line">log4j.appender.<span class="attribute">file</span>=me.zex.util.logger.namedLogger.NamedRollingFileAppender</div><div class="line">log4j.appender.file.<span class="attribute">File</span>=/Users/paranoidq/test/test.log</div><div class="line">log4j.appender.file.<span class="attribute">layout</span>=org.apache.log4j.PatternLayout</div><div class="line">log4j.appender.file.layout.<span class="attribute">ConversionPattern</span>=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n</div><div class="line">log4j.appender.file.<span class="attribute">MaxFileSize</span>=2KB</div><div class="line">log4j.appender.file.<span class="attribute">MaxBackupIndex</span>=10</div></pre></td></tr></table></figure></p><p>运行测试代码,包括了单线程写和多线程写:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Test.class);</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"><span class="comment">// normalTest();</span></div><div class="line"> parallelTest();</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">normalTest</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</div><div class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</div><div class="line"> logger.info(<span class="string">"test"</span>);</div><div class="line"> Thread.sleep(<span class="number">100</span>);</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">parallelTest</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</div><div class="line"> <span class="keyword">final</span> CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">2000</span>; i++) {</div><div class="line"> <span class="keyword">int</span> finalI = i;</div><div class="line"> <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> latch.await();</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < <span class="number">1</span>; j++) {</div><div class="line"> logger.info(<span class="string">"Thread-"</span> + finalI + <span class="string">": test log "</span> + j);</div><div class="line"><span class="comment">// Thread.sleep(2000);</span></div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }).start();</div><div class="line"> }</div><div class="line"> latch.countDown();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><p>测试通过,能够正常写文件和删除文件。重启之后也没问题。<br><img src="/img/log4j-rollingAppender/2.png" alt="Alt text"></p><p>但是在同事测试的时候却发现重启之后无法加载FILO队列,在加载FILO队列时抛出<code>FileNotFoundException</code>:<br><img src="/img/log4j-rollingAppender/3.png" alt="Alt text"><br>从报错信息可以看出,在<code>setMaxBackUpIndex()</code>中调用<code>restoreBackUpsIfNull()</code>获取的fileName为null。经过一系列的排查,最终定位到同事配置文件的写法与我不同:<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">log4j.<span class="attribute">rootLogger</span>=DEBUG</div><div class="line">log4j.logger.me.<span class="attribute">zex</span>=DEBUG, FILE</div><div class="line">log4j.logger.org.<span class="attribute">log4j</span>=DEBUG, console</div><div class="line"></div><div class="line">log4j.appender.<span class="attribute">console</span>=org.apache.log4j.ConsoleAppender</div><div class="line">log4j.appender.console.<span class="attribute">threshold</span>=INFO</div><div class="line">log4j.appender.console.<span class="attribute">layout</span>=org.apache.log4j.PatternLayout</div><div class="line">log4j.appender.console.layout.<span class="attribute">ConversionPattern</span>=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n</div><div class="line"></div><div class="line">log4j.appender.<span class="attribute">FILE</span>=me.zex.util.logger.namedLogger.NamedRollingFileAppender</div><div class="line">log4j.appender.FILE.<span class="attribute">File</span>=/Users/paranoidq/test/test.log</div><div class="line">log4j.appender.FILE.<span class="attribute">layout</span>=org.apache.log4j.PatternLayout</div><div class="line">log4j.appender.FILE.layout.<span class="attribute">ConversionPattern</span>=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n</div><div class="line">log4j.appender.FILE.<span class="attribute">MaxFileSize</span>=2KB</div><div class="line">log4j.appender.FILE.<span class="attribute">MaxBackupIndex</span>=10</div></pre></td></tr></table></figure></p><p>我的配置是<code>log4j.appender.file</code>,而他的配置则是<code>log4j.appender.FILE</code>。难道仅仅是大小写的不同就造成了log4j在执行时的差别么?我又重新将<code>log4j.appender</code>改为了其他大写的名字,例如<code>XXX</code>,<code>DDD</code>,诡异的问题出现了,某些大写名称是可以正常读到<code>fileName</code>的,但是某些大写名称读到的<code>fileName</code>就为空。于是,为了解决这个BUG,我决定从源码入手,搞清楚log4j在加载Logger时的初始化流程。</p><h2 id="源码级别的问题定位"><a href="#源码级别的问题定位" class="headerlink" title="源码级别的问题定位"></a>源码级别的问题定位</h2><p>通过断点一步步进行debug,log4j加载Logger的大致流程如下:</p><h5 id="1-Logger调用LoggerManager-getLogger-clazz-getName-:"><a href="#1-Logger调用LoggerManager-getLogger-clazz-getName-:" class="headerlink" title="1. Logger调用LoggerManager.getLogger(clazz.getName()):"></a>1. <code>Logger</code>调用<code>LoggerManager.getLogger(clazz.getName())</code>:</h5><p><img src="/img/log4j-rollingAppender/4.png" alt="Alt text"></p><h5 id="2-LoggerManager中会从LoggerRepository中根据名称获取Logger"><a href="#2-LoggerManager中会从LoggerRepository中根据名称获取Logger" class="headerlink" title="2. LoggerManager中会从LoggerRepository中根据名称获取Logger"></a>2. <code>LoggerManager</code>中会从<code>LoggerRepository</code>中根据名称获取Logger</h5><p><img src="/img/log4j-rollingAppender/5.png" alt="Alt text"><br>而这个LoggerRepository则是在LoggerManager的static初始化块中进行一系列的初始化工作的。那么继续定位static初始化块。</p><h5 id="3-初始化块做了几件事情:"><a href="#3-初始化块做了几件事情:" class="headerlink" title="3. 初始化块做了几件事情:"></a>3. 初始化块做了几件事情:</h5><ol><li>首选构造了一个<code>Hirarchy</code>对象,这个对象用于表示<code>Logger</code>之间的层级关系</li><li>然后依次检查<code>log4j.configuration</code>是否配置、<code>log4j.xml</code>配置和<code>log4j.properties</code>配置文件,如果有就利用Loader加载。</li><li>加载完成后,通过<code>OptionConverter.selectAndConfigure()</code>函数进行初始化工作。显然,下一步我们需要到这个函数中取一探究竟。<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">static</span> {</div><div class="line"> <span class="comment">// By default we use a DefaultRepositorySelector which always returns 'h'.</span></div><div class="line"> Hierarchy h = <span class="keyword">new</span> Hierarchy(<span class="keyword">new</span> RootLogger((Level) Level.DEBUG));</div><div class="line"> repositorySelector = <span class="keyword">new</span> DefaultRepositorySelector(h);</div><div class="line"></div><div class="line"> <span class="comment">/** Search for the properties file log4j.properties in the CLASSPATH. */</span></div><div class="line"> String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"></div><div class="line"> <span class="comment">// if there is no default init override, then get the resource</span></div><div class="line"> <span class="comment">// specified by the user or the default config file.</span></div><div class="line"> <span class="keyword">if</span>(override == <span class="keyword">null</span> || <span class="string">"false"</span>.equalsIgnoreCase(override)) {</div><div class="line"></div><div class="line"> String configurationOptionStr = OptionConverter.getSystemProperty(</div><div class="line"> DEFAULT_CONFIGURATION_KEY,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"></div><div class="line"> String configuratorClassName = OptionConverter.getSystemProperty(</div><div class="line"> CONFIGURATOR_CLASS_KEY,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"></div><div class="line"> URL url = <span class="keyword">null</span>;</div><div class="line"></div><div class="line"> <span class="comment">// if the user has not specified the log4j.configuration</span></div><div class="line"> <span class="comment">// property, we search first for the file "log4j.xml" and then</span></div><div class="line"> <span class="comment">// "log4j.properties"</span></div><div class="line"> <span class="keyword">if</span>(configurationOptionStr == <span class="keyword">null</span>) {</div><div class="line">url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);</div><div class="line"><span class="keyword">if</span>(url == <span class="keyword">null</span>) {</div><div class="line"> url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);</div><div class="line">}</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"><span class="keyword">try</span> {</div><div class="line"> url = <span class="keyword">new</span> URL(configurationOptionStr);</div><div class="line">} <span class="keyword">catch</span> (MalformedURLException ex) {</div><div class="line"> <span class="comment">// so, resource is not a URL:</span></div><div class="line"> <span class="comment">// attempt to get the resource from the class path</span></div><div class="line"> url = Loader.getResource(configurationOptionStr);</div><div class="line">}</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// If we have a non-null url, then delegate the rest of the</span></div><div class="line"> <span class="comment">// configuration to the OptionConverter.selectAndConfigure</span></div><div class="line"> <span class="comment">// method.</span></div><div class="line"> <span class="keyword">if</span>(url != <span class="keyword">null</span>) {</div><div class="line"> LogLog.debug(<span class="string">"Using URL ["</span>+url+<span class="string">"] for automatic log4j configuration."</span>);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> OptionConverter.selectAndConfigure(url, configuratorClassName,</div><div class="line"> LogManager.getLoggerRepository());</div><div class="line"> } <span class="keyword">catch</span> (NoClassDefFoundError e) {</div><div class="line"> LogLog.warn(<span class="string">"Error during default initialization"</span>, e);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> LogLog.debug(<span class="string">"Could not find resource: ["</span>+configurationOptionStr+<span class="string">"]."</span>);</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> LogLog.debug(<span class="string">"Default initialization of overridden by "</span> +</div><div class="line"> DEFAULT_INIT_OVERRIDE_KEY + <span class="string">"property."</span>);</div><div class="line"> } </div><div class="line">}</div></pre></td></tr></table></figure></li></ol><h5 id="4-OptionConverter"><a href="#4-OptionConverter" class="headerlink" title="4. OptionConverter"></a>4. OptionConverter</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">static</span> <span class="keyword">public</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">selectAndConfigure</span><span class="params">(URL url, String clazz, LoggerRepository hierarchy)</span> </span>{</div><div class="line"> Configurator configurator = <span class="keyword">null</span>;</div><div class="line"> String filename = url.getFile();</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(clazz == <span class="keyword">null</span> && filename != <span class="keyword">null</span> && filename.endsWith(<span class="string">".xml"</span>)) {</div><div class="line"> clazz = <span class="string">"org.apache.log4j.xml.DOMConfigurator"</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(clazz != <span class="keyword">null</span>) {</div><div class="line"> LogLog.debug(<span class="string">"Preferred configurator class: "</span> + clazz);</div><div class="line"> configurator = (Configurator) instantiateByClassName(clazz,</div><div class="line"> Configurator.class,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span>(configurator == <span class="keyword">null</span>) {</div><div class="line"> LogLog.error(<span class="string">"Could not instantiate configurator ["</span>+clazz+<span class="string">"]."</span>);</div><div class="line"> <span class="keyword">return</span>;</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> configurator = <span class="keyword">new</span> PropertyConfigurator();</div><div class="line"> }</div><div class="line"></div><div class="line"> configurator.doConfigure(url, hierarchy);</div><div class="line"> }</div></pre></td></tr></table></figure><p>在selectAndConfigure中,判断是采用哪一种<code>Configurator</code>解析配置文件,如果是xml就采用<code>org.apache.log4j.xml.DOMConfigurator</code>,如果不是,则采用<code>PropertyConfigurator</code>。为了扩展性,也可以自定义configurator来解析自定义格式的配置。最后调用<code>configurator.doConfigure(url, hierarchy)</code>进行解析。显然,这里调用的应该是<code>PropertyConfigurator.doConfigure()</code>实现。</p><h5 id="5-PropertyConfigurator"><a href="#5-PropertyConfigurator" class="headerlink" title="5. PropertyConfigurator"></a>5. PropertyConfigurator</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">doConfigure</span><span class="params">(Properties properties, LoggerRepository hierarchy)</span> </span>{</div><div class="line">repository = hierarchy;</div><div class="line"> String value = properties.getProperty(LogLog.DEBUG_KEY);</div><div class="line"> <span class="keyword">if</span>(value == <span class="keyword">null</span>) {</div><div class="line"> value = properties.getProperty(<span class="string">"log4j.configDebug"</span>);</div><div class="line"> <span class="keyword">if</span>(value != <span class="keyword">null</span>)</div><div class="line">LogLog.warn(<span class="string">"[log4j.configDebug] is deprecated. Use [log4j.debug] instead."</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(value != <span class="keyword">null</span>) {</div><div class="line"> LogLog.setInternalDebugging(OptionConverter.toBoolean(value, <span class="keyword">true</span>));</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//</span></div><div class="line"> <span class="comment">// if log4j.reset=true then</span></div><div class="line"> <span class="comment">// reset hierarchy</span></div><div class="line"> String reset = properties.getProperty(RESET_KEY);</div><div class="line"> <span class="keyword">if</span> (reset != <span class="keyword">null</span> && OptionConverter.toBoolean(reset, <span class="keyword">false</span>)) {</div><div class="line"> hierarchy.resetConfiguration();</div><div class="line"> }</div><div class="line"></div><div class="line"> String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,</div><div class="line"> properties);</div><div class="line"> <span class="keyword">if</span>(thresholdStr != <span class="keyword">null</span>) {</div><div class="line"> hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,</div><div class="line"> (Level) Level.ALL));</div><div class="line"> LogLog.debug(<span class="string">"Hierarchy threshold set to ["</span>+hierarchy.getThreshold()+<span class="string">"]."</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> configureRootCategory(properties, hierarchy);</div><div class="line"> configureLoggerFactory(properties);</div><div class="line"> parseCatsAndRenderers(properties, hierarchy);</div><div class="line"></div><div class="line"> LogLog.debug(<span class="string">"Finished configuring."</span>);</div><div class="line"> <span class="comment">// We don't want to hold references to appenders preventing their</span></div><div class="line"> <span class="comment">// garbage collection.</span></div><div class="line"> registry.clear();</div><div class="line"> }</div></pre></td></tr></table></figure><p>前面的代码都是做一些debug性的工作,我们暂时忽略,关键的加载和初始化代码是三句话:<br><img src="/img/log4j-rollingAppender/6.png" alt="Alt text"></p><h6 id="5-1-configureRootCategory"><a href="#5-1-configureRootCategory" class="headerlink" title="5.1 configureRootCategory"></a>5.1 configureRootCategory</h6><p>configureRootCategory主要负责解析根Logger相关的Category,Category在log4j中已经基本被Logger代替了。<br>这段根我们的appender没有太大的关系。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">configureRootCategory</span><span class="params">(Properties props, LoggerRepository hierarchy)</span> </span>{</div><div class="line"> String effectiveFrefix = ROOT_LOGGER_PREFIX;</div><div class="line"> String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(value == <span class="keyword">null</span>) {</div><div class="line"> value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);</div><div class="line"> effectiveFrefix = ROOT_CATEGORY_PREFIX;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(value == <span class="keyword">null</span>)</div><div class="line"> LogLog.debug(<span class="string">"Could not find root logger information. Is this OK?"</span>);</div><div class="line"> <span class="keyword">else</span> {</div><div class="line"> Logger root = hierarchy.getRootLogger();</div><div class="line"> <span class="keyword">synchronized</span>(root) {</div><div class="line">parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure></p><h6 id="5-2-configureLoggerFactory"><a href="#5-2-configureLoggerFactory" class="headerlink" title="5.2 configureLoggerFactory"></a>5.2 configureLoggerFactory</h6><p>这段从字面意思上值用于配置自定义的Logger工厂的,与我们的Appender也没有关系。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configureLoggerFactory</span><span class="params">(Properties props)</span> </span>{</div><div class="line"> String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,</div><div class="line"> props);</div><div class="line"> <span class="keyword">if</span>(factoryClassName != <span class="keyword">null</span>) {</div><div class="line"> LogLog.debug(<span class="string">"Setting category factory to ["</span>+factoryClassName+<span class="string">"]."</span>);</div><div class="line"> loggerFactory = (LoggerFactory)</div><div class="line"> OptionConverter.instantiateByClassName(factoryClassName,</div><div class="line"> LoggerFactory.class,</div><div class="line"> loggerFactory);</div><div class="line"> PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + <span class="string">"."</span>);</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure></p><h6 id="5-3-parseCatsAndRenders"><a href="#5-3-parseCatsAndRenders" class="headerlink" title="5.3 parseCatsAndRenders"></a>5.3 parseCatsAndRenders</h6><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">protected</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">parseCatsAndRenderers</span><span class="params">(Properties props, LoggerRepository hierarchy)</span> </span>{</div><div class="line"> Enumeration enumeration = props.propertyNames();</div><div class="line"> <span class="keyword">while</span>(enumeration.hasMoreElements()) {</div><div class="line"> String key = (String) enumeration.nextElement();</div><div class="line"> <span class="keyword">if</span>(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {</div><div class="line">String loggerName = <span class="keyword">null</span>;</div><div class="line"><span class="keyword">if</span>(key.startsWith(CATEGORY_PREFIX)) {</div><div class="line"> loggerName = key.substring(CATEGORY_PREFIX.length());</div><div class="line">} <span class="keyword">else</span> <span class="keyword">if</span>(key.startsWith(LOGGER_PREFIX)) {</div><div class="line"> loggerName = key.substring(LOGGER_PREFIX.length());</div><div class="line">}</div><div class="line">String value = OptionConverter.findAndSubst(key, props);</div><div class="line">Logger logger = hierarchy.getLogger(loggerName, loggerFactory);</div><div class="line"><span class="keyword">synchronized</span>(logger) {</div><div class="line"> parseCategory(props, logger, key, loggerName, value);</div><div class="line"> parseAdditivityForLogger(props, logger, loggerName);</div><div class="line">}</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(key.startsWith(RENDERER_PREFIX)) {</div><div class="line">String renderedClass = key.substring(RENDERER_PREFIX.length());</div><div class="line">String renderingClass = OptionConverter.findAndSubst(key, props);</div><div class="line"><span class="keyword">if</span>(hierarchy <span class="keyword">instanceof</span> RendererSupport) {</div><div class="line"> RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,</div><div class="line"> renderingClass);</div><div class="line">}</div><div class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (key.equals(THROWABLE_RENDERER_PREFIX)) {</div><div class="line"> <span class="keyword">if</span> (hierarchy <span class="keyword">instanceof</span> ThrowableRendererSupport) {</div><div class="line"> ThrowableRenderer tr = (ThrowableRenderer)</div><div class="line"> OptionConverter.instantiateByKey(props,</div><div class="line"> THROWABLE_RENDERER_PREFIX,</div><div class="line"> org.apache.log4j.spi.ThrowableRenderer.class,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span>(tr == <span class="keyword">null</span>) {</div><div class="line"> LogLog.error(</div><div class="line"> <span class="string">"Could not instantiate throwableRenderer."</span>);</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> PropertySetter setter = <span class="keyword">new</span> PropertySetter(tr);</div><div class="line"> setter.setProperties(props, THROWABLE_RENDERER_PREFIX + <span class="string">"."</span>);</div><div class="line"> ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);</div><div class="line"></div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure><p>这段代码主要是解析非root的配置元素<br>它首先会根据配置文件的前缀通过subString获取到loggerName:例如如果配置的是<code>log4j.logger.me.zex</code>,那么就会通过<code>loggerName = key.substring(LOGGER_PREFIX.length());</code>获取到loggerName为<code>me.zex</code>。这个loggerName会唯一确定一个Logger。<br><img src="/img/log4j-rollingAppender/7.png" alt="Alt text"></p><p>获取Logger后,对Logger进行初始化,包括设置Category和Additivity。<br><img src="/img/log4j-rollingAppender/8.png" alt="Alt text"></p><h6 id="5-4-parseCategory"><a href="#5-4-parseCategory" class="headerlink" title="5.4 parseCategory"></a>5.4 parseCategory</h6><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">parseCategory</span><span class="params">(Properties props, Logger logger, String optionKey,</span></span></div><div class="line"><span class="function"><span class="params"> String loggerName, String value)</span> </span>{</div><div class="line"></div><div class="line"> LogLog.debug(<span class="string">"Parsing for ["</span> +loggerName +<span class="string">"] with value=["</span> + value+<span class="string">"]."</span>);</div><div class="line"> <span class="comment">// We must skip over ',' but not white space</span></div><div class="line"> StringTokenizer st = <span class="keyword">new</span> StringTokenizer(value, <span class="string">","</span>);</div><div class="line"></div><div class="line"> <span class="comment">// If value is not in the form ", appender.." or "", then we should set</span></div><div class="line"> <span class="comment">// the level of the loggeregory.</span></div><div class="line"></div><div class="line"> <span class="keyword">if</span>(!(value.startsWith(<span class="string">","</span>) || value.equals(<span class="string">""</span>))) {</div><div class="line"></div><div class="line"> <span class="comment">// just to be on the safe side...</span></div><div class="line"> <span class="keyword">if</span>(!st.hasMoreTokens())</div><div class="line"><span class="keyword">return</span>;</div><div class="line"></div><div class="line"> String levelStr = st.nextToken();</div><div class="line"> LogLog.debug(<span class="string">"Level token is ["</span> + levelStr + <span class="string">"]."</span>);</div><div class="line"></div><div class="line"> <span class="comment">// If the level value is inherited, set category level value to</span></div><div class="line"> <span class="comment">// null. We also check that the user has not specified inherited for the</span></div><div class="line"> <span class="comment">// root category.</span></div><div class="line"> <span class="keyword">if</span>(INHERITED.equalsIgnoreCase(levelStr) ||</div><div class="line"> NULL.equalsIgnoreCase(levelStr)) {</div><div class="line"><span class="keyword">if</span>(loggerName.equals(INTERNAL_ROOT_NAME)) {</div><div class="line"> LogLog.warn(<span class="string">"The root logger cannot be set to null."</span>);</div><div class="line">} <span class="keyword">else</span> {</div><div class="line"> logger.setLevel(<span class="keyword">null</span>);</div><div class="line">}</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line">logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));</div><div class="line"> }</div><div class="line"> LogLog.debug(<span class="string">"Category "</span> + loggerName + <span class="string">" set to "</span> + logger.getLevel());</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// Begin by removing all existing appenders.</span></div><div class="line"> logger.removeAllAppenders();</div><div class="line"></div><div class="line"> Appender appender;</div><div class="line"> String appenderName;</div><div class="line"> <span class="keyword">while</span>(st.hasMoreTokens()) {</div><div class="line"> appenderName = st.nextToken().trim();</div><div class="line"> <span class="keyword">if</span>(appenderName == <span class="keyword">null</span> || appenderName.equals(<span class="string">","</span>))</div><div class="line"><span class="keyword">continue</span>;</div><div class="line"> LogLog.debug(<span class="string">"Parsing appender named \""</span> + appenderName +<span class="string">"\"."</span>);</div><div class="line"> appender = parseAppender(props, appenderName);</div><div class="line"> <span class="keyword">if</span>(appender != <span class="keyword">null</span>) {</div><div class="line">logger.addAppender(appender);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div></pre></td></tr></table></figure><p>parseCategory函数会根据log4j.properties中读取的配置行提取出appender和level,并进行初始化工作。例如从<code>log4j.logger.me.zes=DEBUG, FILE</code>提取leve为<code>DEBUG</code>,而通过逗号分隔提取出appender。</p><p>appender的解析在parseAppender中进行,配置行中的逗号可以分隔多个appender,会因此进行解析。<br><img src="/img/log4j-rollingAppender/9.png" alt="Alt text"></p><h6 id="5-5-parseAppender"><a href="#5-5-parseAppender" class="headerlink" title="5.5 parseAppender"></a>5.5 parseAppender</h6><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div></pre></td><td class="code"><pre><div class="line"><span class="function">Appender <span class="title">parseAppender</span><span class="params">(Properties props, String appenderName)</span> </span>{</div><div class="line"> Appender appender = registryGet(appenderName);</div><div class="line"> <span class="keyword">if</span>((appender != <span class="keyword">null</span>)) {</div><div class="line"> LogLog.debug(<span class="string">"Appender \""</span> + appenderName + <span class="string">"\" was already parsed."</span>);</div><div class="line"> <span class="keyword">return</span> appender;</div><div class="line"> }</div><div class="line"> <span class="comment">// Appender was not previously initialized.</span></div><div class="line"> String prefix = APPENDER_PREFIX + appenderName;</div><div class="line"> String layoutPrefix = prefix + <span class="string">".layout"</span>;</div><div class="line"></div><div class="line"> appender = (Appender) OptionConverter.instantiateByKey(props, prefix,</div><div class="line"> org.apache.log4j.Appender.class,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span>(appender == <span class="keyword">null</span>) {</div><div class="line"> LogLog.error(</div><div class="line"> <span class="string">"Could not instantiate appender named \""</span> + appenderName+<span class="string">"\"."</span>);</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line"> appender.setName(appenderName);</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(appender <span class="keyword">instanceof</span> OptionHandler) {</div><div class="line"> <span class="keyword">if</span>(appender.requiresLayout()) {</div><div class="line">Layout layout = (Layout) OptionConverter.instantiateByKey(props,</div><div class="line"> layoutPrefix,</div><div class="line"> Layout.class,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"><span class="keyword">if</span>(layout != <span class="keyword">null</span>) {</div><div class="line"> appender.setLayout(layout);</div><div class="line"> LogLog.debug(<span class="string">"Parsing layout options for \""</span> + appenderName +<span class="string">"\"."</span>);</div><div class="line"> <span class="comment">//configureOptionHandler(layout, layoutPrefix + ".", props);</span></div><div class="line"> PropertySetter.setProperties(layout, props, layoutPrefix + <span class="string">"."</span>);</div><div class="line"> LogLog.debug(<span class="string">"End of parsing for \""</span> + appenderName +<span class="string">"\"."</span>);</div><div class="line">}</div><div class="line"> }</div><div class="line"> <span class="keyword">final</span> String errorHandlerPrefix = prefix + <span class="string">".errorhandler"</span>;</div><div class="line"> String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);</div><div class="line"> <span class="keyword">if</span> (errorHandlerClass != <span class="keyword">null</span>) {</div><div class="line"> ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,</div><div class="line"> errorHandlerPrefix,</div><div class="line"> ErrorHandler.class,</div><div class="line"> <span class="keyword">null</span>);</div><div class="line"> <span class="keyword">if</span> (eh != <span class="keyword">null</span>) {</div><div class="line"> appender.setErrorHandler(eh);</div><div class="line"> LogLog.debug(<span class="string">"Parsing errorhandler options for \""</span> + appenderName +<span class="string">"\"."</span>);</div><div class="line"> parseErrorHandler(eh, errorHandlerPrefix, props, repository);</div><div class="line"> <span class="keyword">final</span> Properties edited = <span class="keyword">new</span> Properties();</div><div class="line"> <span class="keyword">final</span> String[] keys = <span class="keyword">new</span> String[] {</div><div class="line"> errorHandlerPrefix + <span class="string">"."</span> + ROOT_REF,</div><div class="line"> errorHandlerPrefix + <span class="string">"."</span> + LOGGER_REF,</div><div class="line"> errorHandlerPrefix + <span class="string">"."</span> + APPENDER_REF_TAG</div><div class="line"> };</div><div class="line"> <span class="keyword">for</span>(Iterator iter = props.entrySet().iterator();iter.hasNext();) {</div><div class="line"> Map.Entry entry = (Map.Entry) iter.next();</div><div class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</div><div class="line"> <span class="keyword">for</span>(; i < keys.length; i++) {</div><div class="line"> <span class="keyword">if</span>(keys[i].equals(entry.getKey())) <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (i == keys.length) {</div><div class="line"> edited.put(entry.getKey(), entry.getValue());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> PropertySetter.setProperties(eh, edited, errorHandlerPrefix + <span class="string">"."</span>);</div><div class="line"> LogLog.debug(<span class="string">"End of errorhandler parsing for \""</span> + appenderName +<span class="string">"\"."</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> }</div><div class="line"> <span class="comment">//configureOptionHandler((OptionHandler) appender, prefix + ".", props);</span></div><div class="line"> PropertySetter.setProperties(appender, props, prefix + <span class="string">"."</span>);</div><div class="line"> LogLog.debug(<span class="string">"Parsed \""</span> + appenderName +<span class="string">"\" options."</span>);</div><div class="line"> }</div><div class="line"> parseAppenderFilters(props, appenderName, appender);</div><div class="line"> registryPut(appender);</div><div class="line"> <span class="keyword">return</span> appender;</div><div class="line"> }</div></pre></td></tr></table></figure><p>该函数负责根据appenderName解析对应的appender配置。<br>首先检查是否已经解析过了,如果已经解析过了,则直接返回。<br><img src="/img/log4j-rollingAppender/10.png" alt="Alt text"><br>然后通过调用<code>OptionConveter.instantizeByKey</code>方法根据配置的appender类名来反射构造一个appender<br><img src="/img/log4j-rollingAppender/11.png" alt="Alt text"></p><p><strong>–> –> –></strong><br><img src="/img/log4j-rollingAppender/12.png" alt="Alt text"></p><p><strong>–> –> –></strong><br><img src="/img/log4j-rollingAppender/13.png" alt="Alt text"></p><p>此时会进入到自定义的NamedRollingFileAppender的默认构造函数,创建一个对象,并执行初始化。<br><img src="/img/log4j-rollingAppender/14.png" alt="Alt text"><br>但是需要注意的是,由于调用的是默认构造函数,因此除了显示指定的成员变量值外,其他成员变量没有赋值。<strong>fileName此时也是null。</strong><br><img src="/img/log4j-rollingAppender/15.png" alt="Alt text"></p><p>返回到parseAppender之后,NamedRollingFileAppender已经构造完成。此时需要进一步对他进行配置<br><img src="/img/log4j-rollingAppender/16.png" alt="Alt text"></p><p>首先配置layout,通过<code>OptionConveter.instantizeByKey</code>反射构造layout对象,并set进appender中:<br><img src="/img/log4j-rollingAppender/17.png" alt="Alt text"></p><p>然后通过<code>PropertySetter</code>类对layout中的属性进行设置。<br><img src="/img/log4j-rollingAppender/18.png" alt="Alt text"></p><p><code>PropertySetter类</code>利用了Java中的<strong>内省机制</strong>(java.beans.Instrospect),根据剔除前缀后的配置项的key来找到对应的对象中的成员变量,并将配置项的value设置到成员变量中。例如:对于layout的配置<code>log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n</code>,<code>PropertySetter</code>会根据前缀<code>log4j.appender.console.layout</code>进行匹配,然后提取<code>ConversionPattern</code>这个key,通过<code>getPropertyDescriptor(Introspector.decapitalize(key))</code>来内省地获取已经构造的layout对象中的对应属性,并通过set函数完成设值。<br><img src="/img/log4j-rollingAppender/19.png" alt="Alt text"></p><p>layout初始化完毕之后,进行appender的初始化。这里与layout的做法相同,也是通过<code>PropertySetter</code>将配置设置到对象中去。这些都完成后就一个Logger就正式初始化完毕,可以被使用了。<br><img src="/img/log4j-rollingAppender/20.png" alt="Alt text"></p><h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><p>以上就是整个Logger从构造到初始化完成的大致源码流程。那么回到上面的BUG上,显然在进行最后一步appender的内省赋值之前,fileName都是为null的。内省赋值会根据配置项的key依次调用对应的set函数进行设置,比如配置了<code>log4j.appender.FILE.maxBackUpIndex</code>就会被反射调用<code>setMaxBackUpIndex</code>函数,配置了<code>log4j.appender.FILE.File</code>就会被反射地调用<code>setFile</code>函数。</p><p>那么问题的关键其实就是,<strong>调用<code>setMaxBackUpIndex</code>函数时fileName没有被正确的设置,也就是<code>setMaxBackUpIndex</code>在<code>setFile</code>之前被反射调用了。</strong><br>根据<code>PropertySetter.setProperties()</code>的源码,我们不难分析出原因:</p><blockquote><p>由于<code>Properties</code>内部是以<code>HashTable</code>的方式存储配置项的,因此在遍历配置项的时候,是按照key值的所在的hash桶来顺序访问。而这个顺序是随着不同的配置项的名称而变化的。因此才会出现有些appender名称会先调用<code>setFile</code>函数,而有些appender名称会先调用<code>setMaxBackupIndex</code>函数,从而导致了fileName还没有被设值的情况发生。<br><img src="/img/log4j-rollingAppender/21.png" alt="Alt text"></p></blockquote><p>为了验证上面解释,我们在不同的appender名字下,debug观察Properties中HashTable的元素的顺序:</p><ul><li>配置<code>log4j.appender.FILE</code>的情况下:<code>log4j.appender.FILE.MaxBackUpIndex</code>排在<code>log4j.appender.FILE.File</code>前面:<br><img src="/img/log4j-rollingAppender/22.png" alt="Alt text"></li><li>配置配置<code>log4j.appender.file</code>的情况下:<code>log4j.appender.file.MaxBackUpIndex</code>排在<code>log4j.appender.file.File</code>后面:<br><img src="/img/log4j-rollingAppender/23.png" alt="Alt text"></li></ul><p>因此造成问题的根源就在于此。</p><h2 id="如何FIX"><a href="#如何FIX" class="headerlink" title="如何FIX"></a>如何FIX</h2><p>从源码级别的分析可以知道,由于Properties内的HashTable不能保证顺序,因此我们不能将加载FILO队列的逻辑放到任何设值函数中。解决方案就是<strong>在<code>rollOver()</code>开始处添加</strong>,这样可以保证任何设置函数都已经执行完毕,<code>fileName</code>和<code>maxBackupIndex</code>都已经正确获得了配置值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="comment">// synchronization not necessary since doAppend is alreasy synched</span></div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">rollOver</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// 如果没有在log4j中配置maxBackupIndex,那么set函数不会被调用,因此要在这里检查existedLogFileNames队列是否成功初始化</span></div><div class="line"> restoreBackUpsIfNull();</div><div class="line"> ...</div></pre></td></tr></table></figure><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="http://blog.csdn.net/s464036801/article/details/22915963" target="_blank" rel="external">http://blog.csdn.net/s464036801/article/details/22915963</a></li></ul>]]></content>
<summary type="html">
<h2 id="场景描述"><a href="#场景描述" class="headerlink" title="场景描述"></a>场景描述</h2><p>在开发中,一直使用的<code>RollingFileAppender</code>,但是存在几个问题:</p>
<ul>
</summary>
<category term="log4j" scheme="http://paranoidq.github.io/categories/log4j/"/>
<category term="log4j" scheme="http://paranoidq.github.io/tags/log4j/"/>
<category term="RollingFileAppender" scheme="http://paranoidq.github.io/tags/RollingFileAppender/"/>
</entry>
<entry>
<title>如何用jstack命令分析java应用的性能问题</title>
<link href="http://paranoidq.github.io/2017/01/08/how-to-analysis-java-application-using-jstack/"/>
<id>http://paranoidq.github.io/2017/01/08/how-to-analysis-java-application-using-jstack/</id>
<published>2017-01-08T15:09:53.000Z</published>
<updated>2017-01-08T15:38:16.000Z</updated>
<content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在进行WCG项目优化的时候,性能测试的效果一直不理想。于是乎进行性能分析,除了查找内存、CPU等基本的参数之外,还从业务角度入手,查找了日志打印出来的时间,按照报文的时间进行分析对比。其中一项就是用jstack分析线程的情况。这里记录一下整个分析的过程以及收获。</p><h3 id="整体过程"><a href="#整体过程" class="headerlink" title="整体过程"></a>整体过程</h3><ol><li>首先用jps查看java应用的进程号</li><li><p>然后根据进程号查找java中各个线程的情况,命令如下:</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="attribute">top</span> -Hp <span class="selector-attr">[pid]</span></div></pre></td></tr></table></figure></li><li><p>然后会看到哪个线程占用了较高的CPU,记住这个线程号。</p></li><li><p>用jstack命令dump下来应用的线程栈,命令如下:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="selector-tag">jstack</span> <span class="selector-tag">-l</span> <span class="selector-attr">[pid]</span> > <span class="selector-tag">jstack</span><span class="selector-class">.log</span></div></pre></td></tr></table></figure><p>-l选项表示长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表</p></li><li><p>将第3步中的线程号转换为16进制,然后在dump下来的线程栈中查找到对应的线程调用栈,然后对其进行分析和判断,就有可能找到问题的所在,例如发生了死锁、大量线程等待某一个condition等。从而能够分析出问题的原因。</p></li><li><p>如果需要的话,还可以统计一些线程状态的数量,这里需要用到shell的一些命令:</p><figure class="highlight coq"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">cat thread.tmp | <span class="type">grep</span> <span class="string">"java.lang.Thread.State"</span> | <span class="type">sort</span> | <span class="type">uniq</span> -c</div></pre></td></tr></table></figure></li></ol><h3 id="Java线程的状态"><a href="#Java线程的状态" class="headerlink" title="Java线程的状态"></a>Java线程的状态</h3><p><img src="http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clipboard%20-%20%E5%89%AF%E6%9C%AC039.png" alt="java-thread-states"></p><ol><li><p><strong>NEW</strong> 状态是指线程刚创建, 尚未启动</p></li><li><p><strong>RUNNABLE</strong> 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等。<strong>所以一般IO操作的线程会进入这个状态</strong>。</p></li><li><p><strong>BLOCKED</strong> 这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区</p></li><li><p><strong>WAITING</strong> 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, <strong>一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束</strong></p></li><li><p><strong>TIMED_WAITING</strong> 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态</p></li><li><p><strong>TERMINATED</strong> 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)</p></li></ol><h3 id="线程dump文件的分析"><a href="#线程dump文件的分析" class="headerlink" title="线程dump文件的分析"></a>线程dump文件的分析</h3><p>分析如下的一段线程栈:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="string">"RMI TCP Connection(267865)-172.16.5.25"</span> daemon prio=<span class="number">10</span> tid=<span class="number">0</span>x00007fd508371000 nid=<span class="number">0</span>x55ae waiting <span class="keyword">for</span> monitor entry [<span class="number">0</span>x00007fd4f8684000]</div><div class="line"> java<span class="selector-class">.lang</span><span class="selector-class">.Thread</span><span class="selector-class">.State</span>: BLOCKED (on <span class="selector-tag">object</span> monitor)</div><div class="line">at org<span class="selector-class">.apache</span><span class="selector-class">.log4j</span><span class="selector-class">.Category</span><span class="selector-class">.callAppenders</span>(Category<span class="selector-class">.java</span>:<span class="number">201</span>)</div><div class="line">- waiting to lock <<span class="number">0</span>x00000000acf4d0c0> (<span class="selector-tag">a</span> org<span class="selector-class">.apache</span><span class="selector-class">.log4j</span><span class="selector-class">.Logger</span>)</div><div class="line">at org<span class="selector-class">.apache</span><span class="selector-class">.log4j</span><span class="selector-class">.Category</span><span class="selector-class">.forcedLog</span>(Category<span class="selector-class">.java</span>:<span class="number">388</span>)</div><div class="line">at org<span class="selector-class">.apache</span><span class="selector-class">.log4j</span><span class="selector-class">.Category</span><span class="selector-class">.log</span>(Category<span class="selector-class">.java</span>:<span class="number">853</span>)</div><div class="line">at org<span class="selector-class">.apache</span><span class="selector-class">.commons</span><span class="selector-class">.logging</span><span class="selector-class">.impl</span><span class="selector-class">.Log4JLogger</span><span class="selector-class">.warn</span>(Log4JLogger<span class="selector-class">.java</span>:<span class="number">234</span>)</div><div class="line">at com<span class="selector-class">.tuan</span><span class="selector-class">.core</span><span class="selector-class">.common</span><span class="selector-class">.lang</span><span class="selector-class">.cache</span><span class="selector-class">.remote</span><span class="selector-class">.SpyMemcachedClient</span><span class="selector-class">.get</span>(SpyMemcachedClient<span class="selector-class">.java</span>:<span class="number">110</span>)</div></pre></td></tr></table></figure></p><p>1)线程状态是 Blocked,阻塞状态。说明线程等待资源超时!<br>2)“ waiting to lock <0x00000000acf4d0c0>”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁(英文可描述为:trying to obtain 0x00000000acf4d0c0 lock)。<br>3)在 dump 日志里查找字符串 0x00000000acf4d0c0,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如locked < 0x00000000acf4d0c0 >),就可以顺藤摸瓜了。<br>4)“waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了上图中的“Entry Set”队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待。<br>5)第一行里,”RMI TCP Connection(267865)-172.16.5.25”是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。</0x00000000acf4d0c0></p><p>当java系统运行慢的时候, 我们想到的应该先找到性能的瓶颈, 而jstack等工具, 通过jvm当前的stack可以看到当前整个vm所有线程的状态, 当我们看到一个线程状态经常处于<br>WAITING 或者 BLOCKED的时候, 要小心了, 他可能在等待资源经常没有得到释放(当然, 线程池的调度用的也是各种队列各种锁, 要区分一下, 比如下图)<br><img src="http://www.jiacheo.org/wp-content/uploads/2013/04/6db341bbd7680bbc2e6ae37a66329397.png" alt="pool-waiting-demo"><br>这是个经典的并发包里面的线程池, 其调度队列用的是LinkedBlockingQueue, 执行take的时候会block住, 等待下一个任务进入队列中, 然后进入执行, <strong>这种理论上不是系统的性能瓶颈, 找瓶颈一般先找自己的代码stack,再去排查那些开源的组件/JDK的问题</strong></p><h3 id="如何排查"><a href="#如何排查" class="headerlink" title="如何排查"></a>如何排查</h3><ol><li><p>发现有线程进入BLOCK, 而且持续好久, 这说明性能瓶颈存在于synchronized块中, 因为他一直block住, 进不去, 说明另一个线程一直没有处理好, 也就这个synchronized块中处理速度比较慢, 然后再深入查看.<br><strong>当然也有可能同时block的线程太多, 排队太久造成.</strong></p></li><li><p>发现有线程进入WAITING, 而且持续好久, 说明<strong>性能瓶颈存在于触发notify的那段逻辑</strong>. <strong>当然还有就是同时WAITING的线程过多, 老是等不到释放</strong>.</p></li><li><p>线程进入TIME_WAITING 状态且持续好久的, 跟2的排查方式一样.</p></li></ol><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ol><li><a href="http://www.jointforce.com/jfperiodical/article/3729" target="_blank" rel="external">http://www.jointforce.com/jfperiodical/article/3729</a> </li><li><a href="http://www.jiacheo.org/blog/338" target="_blank" rel="external">http://www.jiacheo.org/blog/338</a></li><li><a href="http://blog.csdn.net/yaowj2/article/details/48735247" target="_blank" rel="external">http://blog.csdn.net/yaowj2/article/details/48735247</a></li></ol>]]></content>
<summary type="html">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在进行WCG项目优化的时候,性能测试的效果一直不理想。于是乎进行性能分析,除了查找内存、CPU等基本的参数之外,还从业务角度入手,查找了日志
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="jstack" scheme="http://paranoidq.github.io/tags/jstack/"/>
</entry>
<entry>
<title>我的书单</title>
<link href="http://paranoidq.github.io/2016/11/29/just-reading/"/>
<id>http://paranoidq.github.io/2016/11/29/just-reading/</id>
<published>2016-11-29T13:45:07.000Z</published>
<updated>2016-11-29T13:45:22.000Z</updated>
<content type="html"><![CDATA[<h3 id="人文"><a href="#人文" class="headerlink" title="人文"></a>人文</h3><p><a href="http://book.douban.com/subject/1059336/" target="_blank" rel="external">往事并不如烟</a> (完成)<br><a href="http://book.douban.com/subject/1438394/" target="_blank" rel="external">更多的人死于心碎</a></p><a id="more"></a><h3 id="社会"><a href="#社会" class="headerlink" title="社会"></a>社会</h3><p><a href="https://book.douban.com/subject/1472854/" target="_blank" rel="external">山坳上的中国</a><br><a href="http://book.douban.com/subject/26306686/" target="_blank" rel="external">创业维艰</a></p><h3 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h3><p><a href="https://book.douban.com/subject/26315806/" target="_blank" rel="external">台湾往事:台湾经济改革故事</a> (完成)<br><a href="https://book.douban.com/subject/26754615/" target="_blank" rel="external">极简人类史</a> (完成)</p><h3 id="科普"><a href="#科普" class="headerlink" title="科普"></a>科普</h3><p><a href="https://book.douban.com/subject/1636374/" target="_blank" rel="external">人类的群星闪耀时</a></p><h3 id="IT"><a href="#IT" class="headerlink" title="IT"></a>IT</h3><p><a href="https://book.douban.com/subject/6709783/" target="_blank" rel="external">浪潮之巅</a> (完成)</p>]]></content>
<summary type="html">
<h3 id="人文"><a href="#人文" class="headerlink" title="人文"></a>人文</h3><p><a href="http://book.douban.com/subject/1059336/" target="_blank" rel="external">往事并不如烟</a> (完成)<br><a href="http://book.douban.com/subject/1438394/" target="_blank" rel="external">更多的人死于心碎</a></p>
</summary>
<category term="阅读" scheme="http://paranoidq.github.io/categories/%E9%98%85%E8%AF%BB/"/>
<category term="top" scheme="http://paranoidq.github.io/tags/top/"/>
<category term="todo" scheme="http://paranoidq.github.io/tags/todo/"/>
<category term="阅读" scheme="http://paranoidq.github.io/tags/%E9%98%85%E8%AF%BB/"/>
</entry>
<entry>
<title>Http KeepAlive的理解</title>
<link href="http://paranoidq.github.io/2016/11/29/http-keepalive-vs-pipeline/"/>
<id>http://paranoidq.github.io/2016/11/29/http-keepalive-vs-pipeline/</id>
<published>2016-11-29T13:11:10.000Z</published>
<updated>2016-11-29T13:18:41.000Z</updated>
<content type="html"><![CDATA[<p>本篇结合资料,阐述了Http协议中的keep alive的理解。<br><a id="more"></a></p><h3 id="keepalive为什么不能连续发送多个请求报文"><a href="#keepalive为什么不能连续发送多个请求报文" class="headerlink" title="keepalive为什么不能连续发送多个请求报文"></a>keepalive为什么不能连续发送多个请求报文</h3><p>知乎原贴:<a href="https://www.zhihu.com/question/26515427" target="_blank" rel="external">https://www.zhihu.com/question/26515427</a></p><p>keepalive之前,是发出一个request,然后等待收取 response,在没有 conten-length的时候利用关闭链接的事件来判断 response接收完毕。因为那个时候网站就是一个HTML的文本,你仅仅需要请求一次就可以得到整个页面了。</p><p>后来,随着网页表现形式的多样化,一个页面的资源增多,构成一个页面的文件数不再是一个,为了快速加载网页(是快速,理论上你只开一个socket用完了关,再用再开也行),同时并行地打开多个socket来获取资源(网络IO时间和页面渲染时间的比重越大,这个效果越明显)。</p><p>这个时候有一个问题就来了,我们为什么不在 一个socket上连续发送多个request。原因是因为HTTP1.1 及更低版本的协议,并没有一个字段用来区分一个response是归属于哪一个request的。但HTTP 2 就有这个字段了。因此在HTTP1.1 及更低版本,你只能在发送一个request之后,等待response的到来。</p><p>直到今日,应用最广泛的依然是HTTP1.1协议,这就造成了目前浏览器都是并行加载的,是的都是并行的。</p><p>在真正试图解决你的疑问的之前,我们来看一下,从发出request之前到接收respon之后,都发生了什么。</p><ul><li>你向浏览器的地址栏输入一个域名.如 <a href="http://www.zhihu.com" target="_blank" rel="external">http://www.zhihu.com</a></li><li>浏览器向你的本地DNS服务器请求解析该域名,即将你的<a href="http://www.zhihu.com" target="_blank" rel="external">http://www.zhihu.com</a> 解析为真实的IP地址.详细协议请查询RFC文档,其中对DNS协议的格式内容,指令意义,压缩算法,等都作出了规定。</li><li>拿到ip地址之后,发起TCP 握手(3次),详情请看计算机网络TCP协议部分</li><li>握手成功,构造request,即 HTTP 中request请求.并发送到目的地。有关HTTP协议的内容请查阅RFC文档可以购买HTTP权威指南作为参考和释疑.</li><li>服务器接受到一个完整的request(该边界的指定一般是conten-length,chunked也有),根据用户的request内容运算出相应的response。</li><li>服务器将response 沿着request建立的连接,向浏览器(客户端)发送数据。</li><li>keepalive的时候不关闭该连接,没有keepalive的时候发起tcp close,4次握手</li><li>浏览器根据接收到的response开始渲染页面。</li></ul><p>至此,一个网页的打开过程完毕,我们从中提取出耗时的部分。</p><ul><li>DNS查询时间(一来一回,走UDP协议) 网络IO</li><li>tcp 建立连接握手 网络IO</li><li>request构造时间(cpu运算)</li><li>request发送完毕时间(网络IO)</li><li>服务器接收request运算构造response(CPU运算,特指构造response过程中没有任何IO操作)</li><li>服务器发送response到客户端的时间(网络IO)</li><li>服务器关闭连接时间(IO)</li><li>客户端接收数据渲染页面时间(cpu运算)。</li></ul><p>至此,一个流程就这样简单地构造完毕了。</p><p>好了,我们的目标是,尽可能地缩短完成一个页面加载的时间。<br>那么我们就需要不断地削减上述时间中的某一个。<br>到底什么才是最好的方案,将随着上述时间的比重不断地变化,没有什么是最优的。</p><p>浏览器默认的常用做法是,并行打开多个连接向服务器请求资源.(这里要注意)请求第一个页面的时候,只有一个连接(有的浏览器为了加速后面的多tcp问题,会有预连接,但是效果却因为资源不一定在一个ip上或者服务器不支持过多的连接而没有太大效果),这里请求道的response之后解析出其关联的其他资源,才开始并行连接获取资源。</p><p>但是,tcp的握手和关闭是一个想当耗时,而且重复的过程(当传输速度变快的时候这个的比重相当大),因此 keepalive的作用就是用来 复用已经打开的tcp连接.</p><p>这里有个数学问题,请求6个资源是,开3个连接复用三个呢,还是开6个连接,这就看情况了。</p><p>1.浏览器已经并行了,这个跟你想的不一样.<br>2.keepalive,是复用的意思,不是连续发出多个request.(这是HTTP1.1),不算是串行。</p><p>个人看法:<br>HTTP1.1 request和response 的无标识符问题(即response无法指明是哪一个request的),就造就了现在这种情况。HTTP 2 协议解除了这个问题。</p><p>HTTP 1.1,也没有一个request 多个 response的机制,没request,光有response的机制。关于这个特性HTTP 2有实现,但是争议依然比较大。</p>]]></content>
<summary type="html">
<p>本篇结合资料,阐述了Http协议中的keep alive的理解。<br>
</summary>
<category term="network" scheme="http://paranoidq.github.io/categories/network/"/>
<category term="http" scheme="http://paranoidq.github.io/tags/http/"/>
<category term="network" scheme="http://paranoidq.github.io/tags/network/"/>
<category term="keepalive" scheme="http://paranoidq.github.io/tags/keepalive/"/>
</entry>
<entry>
<title>HttpClient使用实践</title>
<link href="http://paranoidq.github.io/2016/11/29/httpclient-usage-practice/"/>
<id>http://paranoidq.github.io/2016/11/29/httpclient-usage-practice/</id>
<published>2016-11-29T12:51:09.000Z</published>
<updated>2016-11-29T13:37:14.000Z</updated>
<content type="html"><![CDATA[<p>本篇主要总结在HttpClient使用过程中的最佳实践、注意点以及踩到的坑。<br><a id="more"></a></p><h3 id="包含连接池的HttpClient"><a href="#包含连接池的HttpClient" class="headerlink" title="包含连接池的HttpClient"></a>包含连接池的HttpClient</h3><figure class="highlight haxe"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line">PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = <span class="keyword">new</span> <span class="type">PoolingHttpClientConnectionManager</span>();</div><div class="line">poolingHttpClientConnectionManager.setDefaultMaxPerRoute(WcgProperties.WCG_HTTP_CLIENT_POOL_MAX_CONNECTION_PER_ROUTE);</div><div class="line">poolingHttpClientConnectionManager.setMaxTotal(WcgProperties.WCG_HTTP_CLIENT_POOL_MAX_CONNECTION_PER_TARGET);</div><div class="line"></div><div class="line">HttpClientBuilder clientBuilder = HttpClientBuilder.create();</div><div class="line">clientBuilder.setConnectionManager(poolingHttpClientConnectionManager)</div><div class="line"> .setConnectionTimeToLive(WcgProperties.WCG_HTTP_CLIENT_POOL_TTL, TimeUnit.SECONDS)</div><div class="line"> .setConnectionManagerShared(<span class="literal">true</span>)</div><div class="line"> .setKeepAliveStrategy(<span class="keyword">new</span> <span class="type">DefaultConnectionKeepAliveStrategy</span>() {</div><div class="line"> @Override</div><div class="line"> <span class="keyword">public</span> long getKeepAliveDuration(HttpResponse response, HttpContext context) {</div><div class="line"> <span class="keyword">return</span> WcgProperties.WCG_HTTP_CLIENT_KEEP_ALIVE_TIMEOUT * <span class="number">1000</span>;</div><div class="line"> }</div><div class="line"> });</div><div class="line">client = clientBuilder.build();</div><div class="line"></div><div class="line"><span class="comment">// 是否开启idle connection auto close</span></div><div class="line"><span class="keyword">if</span> (WcgProperties.ENABLE_IDLE_CONNECTION_AUTO_CLOSE == <span class="number">1</span>) {</div><div class="line"> IdleConnManageUtil.closeIdleConnectionsPeriodically(poolingHttpClientConnectionManager);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**********************************************/</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">IdleConnManageUtil</span> </span>{</div><div class="line"> <span class="keyword">public</span> <span class="keyword">static</span> void closeIdleConnectionsPeriodically(PoolingHttpClientConnectionManager connectionManager) {</div><div class="line"> ScheduledExecutorService scheduledExecutorService = Executors.<span class="keyword">new</span><span class="type">SingleThreadScheduledExecutor</span>(<span class="keyword">new</span> <span class="type">ThreadFactory</span>() {</div><div class="line"> @Override</div><div class="line"> <span class="keyword">public</span> Thread <span class="keyword">new</span><span class="type">Thread</span>(Runnable runnable) {</div><div class="line"> Thread thread = <span class="keyword">new</span> <span class="type">Thread</span>(runnable);</div><div class="line"> thread.setDaemon(<span class="literal">true</span>);</div><div class="line"> <span class="keyword">return</span> thread;</div><div class="line"> }</div><div class="line"> });</div><div class="line"> scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="type">IdleConnectionMonitorThread</span>(connectionManager), <span class="number">60</span>, WcgProperties.WCG_HTTP_IDLE_CONNECTION_ALIVE_TIMEOUT, TimeUnit.SECONDS);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>注意点:</p><ul><li>可以理解为<code>setDefaultMaxPerRoute</code>针对单个IP,<code>setMaxTotal</code>针对所有请求</li><li><code>setKeepAliveStrategy</code>可以自定义TCP连接KeepAlive的时间。但是需要注意的是,如果TCP连接在池中空闲的期间到期了,那么即使Server端关闭连接,Client端的这个TCP socket也感知不到。此时Server处于<code>CLOSE_WAIT</code>状态,知道TCP Socket从池中被唤醒或者超过CLOSE_WAIT时间或者被池自己关掉。当TCP被唤醒时,C端会感知到S端的关闭连接操作,因此会关闭这个TCP链路,重新建立一条链路发送下一次请求。(等于说每次request之前都会检查一下从池中取出的TCP链路是否有效,通过这个机制来保证不会出现C端拿着无效的TCP链路去请求)但是同样,以上的机制可能会导致S端有很多的<code>CLOSE_WAIT</code>状态,严重时可能造成Socket耗尽,这点需要注意。</li><li><code>closeIdleConnectionsPeriodically</code>是自定义函数,可以定时关闭连接池中长时间不用的连接</li></ul><h3 id="发送request的几种方法和注意点"><a href="#发送request的几种方法和注意点" class="headerlink" title="发送request的几种方法和注意点"></a>发送request的几种方法和注意点</h3><h4 id="推荐的方式"><a href="#推荐的方式" class="headerlink" title="推荐的方式"></a>推荐的方式</h4><figure class="highlight vbscript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="built_in">response</span> = this.client.<span class="keyword">execute</span>(<span class="built_in">request</span>, <span class="keyword">new</span> ResponseHandler<<span class="built_in">Response</span>>() {</div><div class="line"> @Override</div><div class="line"> <span class="keyword">public</span> <span class="built_in">Response</span> handleResponse(HttpResponse <span class="built_in">response</span>) throws ClientProtocolException, IOException {</div><div class="line"> return <span class="built_in">Response</span>.create(</div><div class="line"> <span class="built_in">response</span>.getStatusLine().getStatusCode(),</div><div class="line"> <span class="built_in">response</span>.getStatusLine().getReasonPhrase(),</div><div class="line"> HttpClient.this.getHttpResponse(</div><div class="line"> IOUtils.toByteArray(<span class="built_in">response</span>.getEntity().getContent()),</div><div class="line"> paramsPack)</div><div class="line"> );</div><div class="line"> }</div><div class="line">});</div></pre></td></tr></table></figure><p>这种方式下,HttpClient将会替你<strong>回收</strong>连接。这里用<strong>回收</strong>而不是销毁,是因为HttpClient在默认的情况下,是会进行连接复用的。</p><h4 id="自行控制的方式"><a href="#自行控制的方式" class="headerlink" title="自行控制的方式"></a>自行控制的方式</h4><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">HttpClient httpClient = <span class="keyword">new</span> HttpClient();</div><div class="line">HttpMethod <span class="function"><span class="keyword">method</span> = <span class="title">new</span> <span class="title">GetMethod</span><span class="params">(uri)</span>;</span></div><div class="line"><span class="keyword">try</span> <span class="comment">{</span></div><div class="line"><span class="comment"> int statusCode = httpClient.executeMethod(method);</span></div><div class="line"><span class="comment"> byte[] responseBody = method.getResponseBody();</span></div><div class="line"><span class="comment"> // ...</span></div><div class="line"><span class="comment"> return stuff;</span></div><div class="line"><span class="comment">}</span> <span class="keyword">finally</span> <span class="comment">{</span></div><div class="line"><span class="comment"> method.releaseConnection();</span></div><div class="line"><span class="comment">}</span></div></pre></td></tr></table></figure><p>调用<code>method.releaseConnection();</code>释放了connection使得connection对于HttpClient而言重新可用,而非真正的关闭该connection,原因在于使用了Http1.1协议,HttpClient可以在同一个connection中批量发送后续的请求。</p><h4 id="自行控制的方式2"><a href="#自行控制的方式2" class="headerlink" title="自行控制的方式2"></a>自行控制的方式2</h4><figure class="highlight actionscript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">do</span> {</div><div class="line"> CloseableHttpResponse resp = httpClient.execute(httpGet);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> <span class="comment">// Do what you have to do </span></div><div class="line"> <span class="comment">// but make sure the response gets closed no matter what</span></div><div class="line"> <span class="comment">// even if do not care about its content</span></div><div class="line"> } <span class="keyword">finally</span> {</div><div class="line"> resp.close();</div><div class="line"> } </div><div class="line">} <span class="keyword">while</span> (nextPage);</div></pre></td></tr></table></figure><p>注意:必须要在finally块中调用<code>resp.close()</code>,否则这次请求会一直占用连接,不会被回收。而<strong>默认的情况下,一个正常new出来的HttpClient实例只会给一个route留2个connection</strong> [1],因此一旦没有关闭,那么在多线程的情况下,上面的代码至多只能执行2次就会阻塞。</p><p>另一种释放连接的方法是:调用<code>EntityUtils.consume(resp);</code>,该函数内部会调用<code>resp.close()</code>从而释放连接。但是需要注意的是这种方式存在安全隐患,要明确resp不会过大或者是恶意的应答,从而挤爆缓冲区。最好能判断一下resp的大小,对于明显过大的应答要进行过滤。</p><h3 id="取消request"><a href="#取消request" class="headerlink" title="取消request"></a>取消request</h3><figure class="highlight haxe"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">HttpGet <span class="keyword">get</span> = <span class="keyword">new</span> <span class="type">HttpGet</span>();</div><div class="line"><span class="keyword">get</span>.abor1.</div></pre></td></tr></table></figure><h3 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h3><ol><li><a href="http://stackoverflow.com/questions/22069821/apache-httpclient-4-3-3-execute-method-for-a-get-request-blocks-and-never-return" target="_blank" rel="external">http://stackoverflow.com/questions/22069821/apache-httpclient-4-3-3-execute-method-for-a-get-request-blocks-and-never-return</a></li></ol>]]></content>
<summary type="html">
<p>本篇主要总结在HttpClient使用过程中的最佳实践、注意点以及踩到的坑。<br>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="httpclient" scheme="http://paranoidq.github.io/tags/httpclient/"/>
</entry>
<entry>
<title>Java程序和Jvm编码机制的分析</title>
<link href="http://paranoidq.github.io/2016/10/25/java-and-jvm-encoding-analysis/"/>
<id>http://paranoidq.github.io/2016/10/25/java-and-jvm-encoding-analysis/</id>
<published>2016-10-25T13:55:12.000Z</published>
<updated>2016-12-11T15:32:01.000Z</updated>
<content type="html"><![CDATA[<p>本文起源于一次测试环境下构造报文发送时对于java编码的疑惑,经过查找资料和分析总结而成。对于JVM和Java代码中的编码问题和原理进行了较为深入的分析。如有错漏,欢迎指正。</p><a id="more"></a><p>首先上一个原理图,借用自<a href="https://www.zhihu.com/question/30977092" target="_blank" rel="external">知乎</a>。个人认为非常恰当地描述了整个编码的原理,除了output部分不够详细。</p><p><img src="/img/java-jvm-encoding.jpg" alt="java-jvm-encoding"></p><h3 id="Java程序内部编码的分析"><a href="#Java程序内部编码的分析" class="headerlink" title="Java程序内部编码的分析"></a>Java程序内部编码的分析</h3><p>该过程的分析参考了<a href="http://blog.csdn.net/dslztx/article/details/47005107" target="_blank" rel="external">这篇文章</a>。<br><br></p><ol><li>在IDE中编写java代码然后保存,此时会根据<code>file.encoding</code>或<code>system.defaultEncoding</code>将文件编码为二进制的流,然后写入磁盘。<ul><li>这个过程用到了系统编码或文件编码,一般为GBK或者UTF-8等<br><br></li></ul></li><li>然后IDE进行编译或javac进行编译:<ul><li>这个过程会从磁盘读取二进制流,然后根据<code>file.encoding</code>或<code>system.defaultEncoding</code>来解码出原始文件的内容</li><li>可以指定解码的方式,如:<ul><li><code>javac -encoding utf-8 Main.java</code></li><li><code>maven compiler plugin中指定encoding</code></li></ul></li><li>然后javac编译器会将读取的文件编译为Unicode的格式,因此此时源文件中的所有字符都是Unicode编码方式存储在内存中的*.class文件中</li><li>然后将内存中的.class文件以二进制流的方式输入磁盘,使用系统默认编码即可(<strong>实际上,这时候不存在编码的问题了,因为都是Unicode字符</strong>)</li><li>需要注意的是:在简体中文的Windows上,平台默认编码会是GBK,那么<strong>javac就会默认假定输入的Java源码文件是以GBK编码的</strong>。javac能够正确读取文件内容并将其中的字符串以Unicode输出到Class文件里,就跟自己写个程序以GBK读文件以UTF-8写文件一样。如果实际输入的确实是GBK编码(GBK兼容ASCII编码)的文件,那么一切都会正常。但如果实际输入的是别的编码的文件,例如超过了ASCII范围的UTF-8,那javac读进来的内容就会出问题,就“乱码”了</li><li>对于任何的字符串,如String a= “我是字符串”而言,不论在java源码中以何种编码方式存在,编译为class文件之后,就全都是Unicode了。也就是,<strong>我们可以认为,源文件的编码方式丢失了</strong>。<br><br></li></ul></li><li><p>编译过程完成之后,运行程序,此时JVM开始载入.class文件来运行,此时,在JVM内存中的各个字符仍然是Unicode编码的方式存在</p></li><li><p>当产生output的行为时,就需要显式或隐式做编码转换的工作了,也即是<strong>从Unicode转换为另一种具体的编码</strong>。output的行为可以有多种:</p><ul><li>取得字节数组,即a.getBytes()或a.getBytes(“GBK”)。此时就从Unicode转换为了系统默认编码方式的二进制表示或指定编码方式的二进制表示</li><li>显示在命令行界面上,如System.out.println(a)。此时隐式调用了转换和解码,将Unicode转为了系统默认编码的二进制表示,然后利用系统默认编码方式解码出具体的字符显示出来。</li><li>输出为文件,同样系统默认编码方式或指定编码方式</li><li>网络传输</li><li>需要注意的是:<strong>单纯以上过程只是在Unicode和具体某种编码之间转,不会出现乱码问题</strong>。只有在不同的具体编码之间,才可能出现乱码问题。并且<strong>编码本身没有问题,乱码只会出现在需要解码的时候</strong>。<blockquote><p>一个典型的错误是:new String(a.getBytes(“UTF-8”), “GBK”);<br>1) 其中a.getBytes()是按照UTF-8将a在内存中的Unicode表示转换出来,也就是这是一个编码的过程,使用了UTF-8编码方式<br>2) 而new String()这个过程是将转换得到的byte[]数组用GBK解码,然后显示实际的字符<br>3) 显然会发生乱码。这个问题的关键在于,没有理解字符串在JVM内存中的表示形式,以为是UTF-8的形式存储的。</p></blockquote></li></ul></li></ol><h3 id="Java外部编码的分析"><a href="#Java外部编码的分析" class="headerlink" title="Java外部编码的分析"></a>Java外部编码的分析</h3><p>java程序中的字符串很多来自于外部,这个时候就需要注意乱码的问题了</p><ul><li>首先,外部过来的字符串肯定是以某种编码方式编好的byte[]。数组,无论从文件读取、命令行输入还是从网络传输,本质上都是byte[]</li><li>java程序需要将这些byte[]读取到JVM中进行处理,也就是<strong>从具体的编码方式转换为Unicode的过程,因此提前知道数据源的编码方式才能正确转换</strong>。这点很重要,如果不指定编码方式,那么默认就会采用系统编码,此时就有可能解码解的不对。</li><li>尽量要使用带有明确指定编码方式的method,而不要不知情的情况下使用了系统默认的编码,否则就可能造成解码错误,解出了乱码。</li></ul><h3 id="参考资料:"><a href="#参考资料:" class="headerlink" title="参考资料:"></a>参考资料:</h3><ul><li><a href="http://blog.csdn.net/jessenpan/article/details/15505515" target="_blank" rel="external">http://blog.csdn.net/jessenpan/article/details/15505515</a></li><li><a href="https://www.zhihu.com/question/30977092" target="_blank" rel="external">https://www.zhihu.com/question/30977092</a></li><li><a href="http://bbs.csdn.net/topics/390806040" target="_blank" rel="external">http://bbs.csdn.net/topics/390806040</a></li><li><a href="http://xm-koma.iteye.com/blog/2074191" target="_blank" rel="external">http://xm-koma.iteye.com/blog/2074191</a></li></ul>]]></content>
<summary type="html">
<p>本文起源于一次测试环境下构造报文发送时对于java编码的疑惑,经过查找资料和分析总结而成。对于JVM和Java代码中的编码问题和原理进行了较为深入的分析。如有错漏,欢迎指正。</p>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="基础" scheme="http://paranoidq.github.io/tags/%E5%9F%BA%E7%A1%80/"/>
<category term="encoding" scheme="http://paranoidq.github.io/tags/encoding/"/>
</entry>
<entry>
<title>Java Webservice实践(3)在tomcat容器中发布webservice服务</title>
<link href="http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-3/"/>
<id>http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-3/</id>
<published>2016-10-20T13:39:04.000Z</published>
<updated>2016-10-25T13:31:28.000Z</updated>
<content type="html"><![CDATA[<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>在<a href="http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-2/">第一篇实践</a>中,我们讲述了如何用standalone的方式部署一个webservice服务。但是很多情况下,我们需要将webservice部署在web服务器上,因此本文实践如何在tomcat服务器上部署webservice。</p><p>本文中,我们将讲述web容器的两种配置部署方式,一种是<strong>基于Java Servlet的方式</strong>,一种是<strong>基于Spring框架的方式</strong>。我们知道CXF本身设计就是与Spring无缝集成的,因此基于Spring框架的方式更方便通用;但是,有时候我们也希望通过简单的servlet就能够快速搞定一个webservice的发布。</p> <a id="more"></a><h3 id="基于spring-framework"><a href="#基于spring-framework" class="headerlink" title="基于spring framework"></a>基于spring framework</h3><p>基于Spring的配置方式很简单,我们只需要关联对应的webservice bean,然后定义访问路径就可以了。</p><p>项目的结构类似下图:<br><img src="/img/webservice/spring-config-1.png" alt="cxf-spring-intergeration"></p><h4 id="Step-1"><a href="#Step-1" class="headerlink" title="Step 1"></a>Step 1</h4><p>在<code>webservice-definition-beans.xml</code>中,我们给出jaxws的endpoint的定义,配置某一个webservice的实现类以及这个服务<strong>对应于项目路径的相对URL</strong>。需要注意的是,这里可以直接写具体的实现类,而不一定需要引用<code>service-definition-beans.xml</code>中定义的bean。当然,如果统一在spring的beans定义文件中定义了,那么在webservice的xml定义中只需要简单引用一下即可。<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="php"><span class="meta"><?</span>xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span><span class="meta">?></span></span></div><div class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></div><div class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:jaxws</span>=<span class="string">"http://cxf.apache.org/jaxws"</span></span></div><div class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans</span></span></div><div class="line"><span class="tag"><span class="string"> http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></div><div class="line"><span class="tag"><span class="string"> http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"</span>></span></div><div class="line"> <span class="comment"><!--<import resource="classpath:service-definition-beans.xml"/>--></span></div><div class="line"> <span class="tag"><<span class="name">import</span> <span class="attr">resource</span>=<span class="string">"classpath:META-INF/cxf/cxf.xml"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">import</span> <span class="attr">resource</span>=<span class="string">"classpath:META-INF/cxf/cxf-extension-soap.xml"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">import</span> <span class="attr">resource</span>=<span class="string">"classpath:META-INF/cxf/cxf-servlet.xml"</span> /></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">jaxws:endpoint</span> <span class="attr">id</span>=<span class="string">"webservice"</span> <span class="attr">implementor</span>=<span class="string">"com.cup.wcg.access.webservice.WcgWsImpl"</span> <span class="attr">address</span>=<span class="string">"/request"</span>/></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">beans</span>></span></div></pre></td></tr></table></figure></p><h4 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h4><p>配置完webservice bean的xml文件之后,配置web.xml:<br><figure class="highlight dts"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="params"><context-param></span></div><div class="line"> <span class="params"><param-name></span>contextConfigLocation<span class="params"></param-name></span></div><div class="line"> <span class="params"><param-value></span>WEB-INF/webservice-definition-beans.xml<span class="params"></param-value></span></div><div class="line"><span class="params"></context-param></span></div><div class="line"><span class="params"><servlet></span></div><div class="line"> <span class="params"><servlet-name></span>CXFServlet<span class="params"></servlet-name></span></div><div class="line"> <span class="params"><servlet-class></span>org.apache.cxf.transport.servlet.CXFServlet<span class="params"></servlet-class></span></div><div class="line"> <span class="params"><load-on-startup></span><span class="number">1</span><span class="params"></load-on-startup></span></div><div class="line"><span class="params"></servlet></span></div><div class="line"><span class="params"><servlet-mapping></span></div><div class="line"> <span class="params"><servlet-name></span>CXFServlet<span class="params"></servlet-name></span></div><div class="line"> <span class="params"><url-pattern></span><span class="meta-keyword">/ws/</span>*<span class="params"></url-pattern></span></div><div class="line"><span class="params"></servlet-mapping></span></div><div class="line"><span class="params"><listener></span></div><div class="line"> <span class="params"><listener-class></span></div><div class="line"> org.springframework.web.context.ContextLoaderListener</div><div class="line"> <span class="params"></listener-class></span></div><div class="line"><span class="params"></listener></span></div></pre></td></tr></table></figure></p><p>其中CXFServlet指明了路径到webservice的映射关系。</p><h3 id="基于Servlet"><a href="#基于Servlet" class="headerlink" title="基于Servlet"></a>基于Servlet</h3><p>参考这篇文章<a href="https://www.oschina.net/question/54100_26066" target="_blank" rel="external">https://www.oschina.net/question/54100_26066</a></p><h4 id="Step-1-1"><a href="#Step-1-1" class="headerlink" title="Step 1"></a>Step 1</h4><p>继承CXFNoSpringServlet,并override其中的loadBus方法<br><figure class="highlight scala"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> javax.servlet.<span class="type">ServletConfig</span>;</div><div class="line"><span class="keyword">import</span> javax.xml.ws.<span class="type">Endpoint</span>;</div><div class="line"> </div><div class="line"><span class="keyword">import</span> org.apache.cxf.transport.servlet.<span class="type">CXFNonSpringServlet</span>;</div><div class="line"> </div><div class="line">public <span class="class"><span class="keyword">class</span> <span class="title">WebServiceServlet</span> <span class="keyword">extends</span> <span class="title">CXFNonSpringServlet</span> </span>{</div><div class="line"> <span class="keyword">private</span> static <span class="keyword">final</span> long serialVersionUID = <span class="number">-5314312869027558456</span>L;</div><div class="line"> </div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="keyword">protected</span> void loadBus(<span class="type">ServletConfig</span> servletConfig) {</div><div class="line"> <span class="keyword">super</span>.loadBus(servletConfig);</div><div class="line"> <span class="type">System</span>.out.println(<span class="string">"#####################"</span>);</div><div class="line"> <span class="type">Endpoint</span>.publish(<span class="string">"/helloWorldService"</span>, <span class="keyword">new</span> <span class="type">HelloWorldServiceImpl</span>());</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p><h4 id="Step-2-1"><a href="#Step-2-1" class="headerlink" title="Step 2"></a>Step 2</h4><p>配置web.xml,指定webapp的URL以及对应的servlet<br><figure class="highlight dts"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="params"><servlet></span></div><div class="line"> <span class="params"><servlet-name></span>webservice<span class="params"></servlet-name></span></div><div class="line"> <span class="params"><servlet-class></span>com.crazycoder2010.webservice.cxf.server.servlet.WebServiceServlet<span class="params"></servlet-class></span></div><div class="line"><span class="params"></servlet></span></div><div class="line"><span class="params"><servlet-mapping></span></div><div class="line"> <span class="params"><servlet-name></span>webservice<span class="params"></servlet-name></span></div><div class="line"> <span class="params"><url-pattern></span><span class="meta-keyword">/webservice/</span>*<span class="params"></url-pattern></span></div><div class="line"><span class="params"></servlet-mapping></span></div></pre></td></tr></table></figure></p><p>部署之后就可以通过<code>http://localhost:8080/CXF-Server/webservice/helloWorldService?wsdl</code>访问webservice服务了。</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="https://www.oschina.net/question/54100_26066" target="_blank" rel="external">https://www.oschina.net/question/54100_26066</a></li></ul>]]></content>
<summary type="html">
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>在<a href="http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-2/">第一篇实践</a>中,我们讲述了如何用standalone的方式部署一个webservice服务。但是很多情况下,我们需要将webservice部署在web服务器上,因此本文实践如何在tomcat服务器上部署webservice。</p>
<p>本文中,我们将讲述web容器的两种配置部署方式,一种是<strong>基于Java Servlet的方式</strong>,一种是<strong>基于Spring框架的方式</strong>。我们知道CXF本身设计就是与Spring无缝集成的,因此基于Spring框架的方式更方便通用;但是,有时候我们也希望通过简单的servlet就能够快速搞定一个webservice的发布。</p>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="webservice" scheme="http://paranoidq.github.io/tags/webservice/"/>
</entry>
<entry>
<title>Java Webservice实践(2)构建Webservice客户端</title>
<link href="http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-2/"/>
<id>http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-2/</id>
<published>2016-10-20T13:39:04.000Z</published>
<updated>2017-09-21T10:42:16.000Z</updated>
<content type="html"><![CDATA[<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>总体而言,构建Webservice的方式有很多种,概括起来可以分为以下几种:</p><ol><li>通过Stub的方式,用工具生成客户端的代理,具体的工具可以细分为很多:<br>a. jdk1.6+/bin中自带工具: <code>wsimport</code><br>b. cxf/bin中的工具: <code>wsdl2java</code><br>c. axis/bin中工具</li><li>通过CXF动态调用:<br>a. DynamicClientFactory<br>b. JaxWsDynamicClientFactory</li><li>通过Axis动态调用:<br>a. Service.createCall()</li><li>通过HTTPURLConnection的方式调用(手动组装SOAP报文)</li><li>通过SOAPConnection的方式调用</li></ol><a id="more"></a><h3 id="Stub的生成"><a href="#Stub的生成" class="headerlink" title="Stub的生成"></a>Stub的生成</h3><h4 id="直接拷贝接口"><a href="#直接拷贝接口" class="headerlink" title="直接拷贝接口"></a>直接拷贝接口</h4><p>拷贝接口,然后利用JaxWsServerFactoryBean方式进行调用<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();</div><div class="line">factory.setServiceClass(CXFDemo.class);</div><div class="line">factory.setAddress(ADDRESS);</div><div class="line">CXFDemo<span class="built_in"> client </span>= (CXFDemo)factory.create();</div></pre></td></tr></table></figure></p><h4 id="通过wsimport生成stub"><a href="#通过wsimport生成stub" class="headerlink" title="通过wsimport生成stub"></a>通过wsimport生成stub</h4><p>wsimport调用的方法是:<code>wsimport -d [目录] -keep -Xnocompile -verbose [wsdl]</code><br>其中<code>-d</code>后面表示生成文件存放的目录,<code>-keep</code>表示生成源文件,<code>-verbose</code>表示显示生成的详细过程,<code>-Xnocompile</code>表示不自动编译源码。<br>生成之后,导入到客户端的项目中。生成文件的结构类似下图:<br><img src="/img/webservice/wsimport.png" alt="wsimport-java-structure"></p><p>接着,就可以在客户端调用了<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">WcgWebService serverStub = new WcgWebService();</div><div class="line">com.up.client.WcgWsInterface<span class="built_in"> client </span>= serverStub.getWcgWsImplPort();</div><div class="line">String resp = client.wsRequest(<span class="string">"service"</span>, <span class="string">"requestMsg"</span>);</div><div class="line">System.out.println(resp);</div></pre></td></tr></table></figure></p><h4 id="通过wsdl2java生成"><a href="#通过wsdl2java生成" class="headerlink" title="通过wsdl2java生成"></a>通过wsdl2java生成</h4><p>wsdl2java的调用方法与wsimport类似:<code>sh wsdl2java -d src -client http://localhost:8080/cxf-server/wcg/webservice/interface\?wsdl</code><br>其中<code>-d</code>指定输出的目录,<code>-client</code>输出客户端代码,也可以指定<code>-server</code>输出服务端代码<br>生成之后,导入客户端的项目中。生成的文件结构类似下图:<br><img src="/img/webservice/wsdl2java.png" alt="wsdl2java"></p><p>接着,在客户端调用如下:<br><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">WcgWebserviceInterface<span class="built_in"> client </span>= new Webservice().getWcgWebserviceImplPort();</div><div class="line">String requestResponse = client.request(<span class="string">"param"</span>, <span class="string">"param"</span>);</div><div class="line">System.out.println(requestResponse);</div></pre></td></tr></table></figure></p><h4 id="通过Axis生成"><a href="#通过Axis生成" class="headerlink" title="通过Axis生成"></a>通过Axis生成</h4><p>调用方式:<br>…</p><p>这里需要注意的是:需要指定编码方式时,可以通过以下方式设置<br><figure class="highlight sqf"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Stub.<span class="variable">_setProperty</span>(<span class="built_in">Call</span>.CHARACTER_SET_ENCODING, <span class="string">"GBK"</span>);</div></pre></td></tr></table></figure></p><p>参考自:<a href="http://stackoverflow.com/questions/9093078/apache-axis-charset-wrong-encoding-on-tomcat" target="_blank" rel="external">http://stackoverflow.com/questions/9093078/apache-axis-charset-wrong-encoding-on-tomcat</a></p><h3 id="通过CXF动态调用"><a href="#通过CXF动态调用" class="headerlink" title="通过CXF动态调用"></a>通过CXF动态调用</h3><p>通过以上的几种方法,可以看出Stub调用的方式比较简单,只需要根据wsdl生成好对应的代理类就可以了,一般3-4行代码可以搞定。但是缺点就是接口不能随意改动,并且扩展起来不方便。</p><h3 id="通过Axis动态调用"><a href="#通过Axis动态调用" class="headerlink" title="通过Axis动态调用"></a>通过Axis动态调用</h3><figure class="highlight gauss"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">String</span> namespace = <span class="string">"http://ws.wcg.unionpay.com"</span>; <span class="comment">// 定义命名空间</span></div><div class="line"><span class="keyword">String</span> serviceName = <span class="string">"wcgWebService"</span>; <span class="comment">// 定义服务名</span></div><div class="line">QName service = <span class="keyword">new</span> QName(namespace, serviceName);</div><div class="line"><span class="keyword">String</span> operationName = <span class="string">"WsRequest"</span>;</div><div class="line"></div><div class="line"><span class="keyword">Call</span> <span class="keyword">call</span> = <span class="keyword">new</span> Service().createCall();</div><div class="line"><span class="keyword">call</span>.setPortTypeName(service);</div><div class="line"><span class="keyword">call</span>.setOperationName(<span class="keyword">new</span> QName(namespace, operationName));</div><div class="line"><span class="keyword">call</span>.setProperty(...)</div><div class="line"><span class="keyword">call</span>.addParameter(<span class="string">"service"</span>, ParameterMode.IN);</div><div class="line"><span class="keyword">call</span>.addParameter(<span class="string">"requestMsg"</span>, ParameterMode.IN);</div><div class="line"><span class="keyword">call</span>.setReturnType();</div><div class="line">Object[] inParams = <span class="keyword">new</span> Object[]{<span class="string">"demo"</span>, <span class="string">"demo"</span>};</div><div class="line"><span class="keyword">String</span> ret = (<span class="keyword">String</span>)<span class="keyword">call</span>.invoke(inParams);</div></pre></td></tr></table></figure><h3 id="通过HTTPURLConnection调用"><a href="#通过HTTPURLConnection调用" class="headerlink" title="通过HTTPURLConnection调用"></a>通过HTTPURLConnection调用</h3><p>这种调用方式就需要手动去组装SOAP报文,是一种最为原始的方式。但其实这种方式体现除了Webservice的本质,就是SOAP + HTTP POST。只不过这种方式在便利程度和扩展性方面都是很不友好的。<br>具体的代码如下:<br><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line">private static String <span class="keyword">buildSOAP() </span>{</div><div class="line"> String soap = <span class="string">"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ws=\"http://ws.wcg.unionpay.com\">\n"</span> +</div><div class="line"> <span class="string">" <soapenv:Header/>\n"</span> +</div><div class="line"> <span class="string">" <soapenv:Body>\n"</span> +</div><div class="line"> <span class="string">" <ws:WsRequest>\n"</span> +</div><div class="line"> <span class="string">" <service>apram</service>\n"</span> +</div><div class="line"> <span class="string">" <requestMsg>param</requestMsg>\n"</span> +</div><div class="line"> <span class="string">" </ws:WsRequest>\n"</span> +</div><div class="line"> <span class="string">" </soapenv:Body>\n"</span> +</div><div class="line"> <span class="string">" </soapenv:Envelope>"</span><span class="comment">;</span></div><div class="line"> return soap<span class="comment">;</span></div><div class="line">}</div><div class="line"></div><div class="line">public static void main(String[] args) throws Exception {</div><div class="line"> String soap = <span class="keyword">buildSOAP();</span></div><div class="line"><span class="keyword"></span></div><div class="line"><span class="keyword"> </span> HttpURLConnection connection = (HttpURLConnection) (new URL(<span class="string">"http://localhost:8080/cxf-server/ws/request?wsdl"</span>)).openConnection()<span class="comment">;</span></div><div class="line"> connection.setDoInput(true)<span class="comment">;</span></div><div class="line"> connection.setDoOutput(true)<span class="comment">;</span></div><div class="line"> connection.setRequestMethod(<span class="string">"POST"</span>)<span class="comment">;</span></div><div class="line"> connection.setRequestProperty(<span class="string">"Content-Length"</span>, String.valueOf(soap.length()))<span class="comment">;</span></div><div class="line"> connection.setRequestProperty(<span class="string">"Content-Type"</span>, <span class="string">"application/xop+xml; charset=utf-8; type=\"test/xml\""</span>)<span class="comment">;</span></div><div class="line"> connection.setRequestProperty(<span class="string">"SOAPAction"</span>, <span class="string">"soap_action"</span>)<span class="comment">;</span></div><div class="line"> connection.setUseCaches(false)<span class="comment">;</span></div><div class="line"> connection.connect()<span class="comment">;</span></div><div class="line"></div><div class="line"> connection.getOutputStream().write(soap.getBytes(<span class="string">"UTF-8"</span>))<span class="comment">;</span></div><div class="line"> connection.getOutputStream().flush()<span class="comment">;</span></div><div class="line"> connection.getOutputStream().<span class="keyword">close();</span></div><div class="line"><span class="keyword"></span></div><div class="line"><span class="keyword"> </span> InputStream in = connection.getInputStream()<span class="comment">;</span></div><div class="line"> String resp = IOUtils.toString(in)<span class="comment">;</span></div><div class="line"> System.out.println(resp)<span class="comment">;</span></div><div class="line">}</div></pre></td></tr></table></figure></p><h3 id="通过SOAPConnection的方式调用"><a href="#通过SOAPConnection的方式调用" class="headerlink" title="通过SOAPConnection的方式调用"></a>通过SOAPConnection的方式调用</h3><p>相比于之前的HttpURLConnection方式,SOAPConnection的方式稍微高级一些,不需要通过字符串的方式手动拼报文了,SOAPConnection的API会帮你拼,你只需要做一些定制化的工作就可以了,并且可以进行一些复杂的配置。</p><p>参考这篇文章:<a href="http://blog.csdn.net/wanghuan203/article/details/9219565" target="_blank" rel="external">http://blog.csdn.net/wanghuan203/article/details/9219565</a></p><h3 id="Axis和Apache的对比"><a href="#Axis和Apache的对比" class="headerlink" title="Axis和Apache的对比"></a>Axis和Apache的对比</h3><table><thead><tr><th></th><th>Axis2</th><th>CXF</th></tr></thead><tbody><tr><td>目标</td><td>WebService引擎</td><td>简易的SOA框架,可以作为ESB</td></tr><tr><td>ws* 标准支持</td><td>不支持WS-Policy</td><td>WS-Addressing,WS-Policy, WS-RM, WS-Security,WS-I Basic Profile</td></tr><tr><td>数据绑定支持</td><td>XMLBeans、JiBX、JaxMe 、JaxBRI、ADB</td><td>JAXB, Aegis, XMLBeans, SDO, JiBX</td></tr><tr><td>spring集成</td><td>不支持</td><td>支持</td></tr><tr><td>应用集成</td><td>困难</td><td>简单</td></tr><tr><td>多语言</td><td>支持C/C++</td><td>不支持</td></tr><tr><td>部署</td><td>web应用</td><td>嵌入式</td></tr><tr><td>服务监控和管理</td><td>支持</td><td>不支持</td></tr></tbody></table><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ul><li><a href="http://www.cnblogs.com/holbrook/archive/2012/12/12/2814821.html" target="_blank" rel="external">http://www.cnblogs.com/holbrook/archive/2012/12/12/2814821.html</a></li><li><a href="http://blog.csdn.net/zolalad/article/details/31735791" target="_blank" rel="external">http://blog.csdn.net/zolalad/article/details/31735791</a></li></ul>]]></content>
<summary type="html">
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>总体而言,构建Webservice的方式有很多种,概括起来可以分为以下几种:</p>
<ol>
<li>通过Stub的方式,用工具生成客户端的代理,具体的工具可以细分为很多:<br>a. jdk1.6+/bin中自带工具: <code>wsimport</code><br>b. cxf/bin中的工具: <code>wsdl2java</code><br>c. axis/bin中工具</li>
<li>通过CXF动态调用:<br>a. DynamicClientFactory<br>b. JaxWsDynamicClientFactory</li>
<li>通过Axis动态调用:<br>a. Service.createCall()</li>
<li>通过HTTPURLConnection的方式调用(手动组装SOAP报文)</li>
<li>通过SOAPConnection的方式调用</li>
</ol>
</summary>
<category term="webservice" scheme="http://paranoidq.github.io/categories/webservice/"/>
<category term="webservice" scheme="http://paranoidq.github.io/tags/webservice/"/>
</entry>
<entry>
<title>Java Webservice实践(1)构建简单的Webservice服务</title>
<link href="http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-1/"/>
<id>http://paranoidq.github.io/2016/10/20/java-webservice-tutorial-1/</id>
<published>2016-10-20T12:45:04.000Z</published>
<updated>2016-10-25T11:32:35.000Z</updated>
<content type="html"><![CDATA[<h3 id="JAX-WS"><a href="#JAX-WS" class="headerlink" title="JAX-WS"></a>JAX-WS</h3><p>JAX-WS(Java API for XML Web Services),是SOAP协议的一个Java的实现规范,这个新规范是为了简化基于SOAP的Java开发。JAX-WS规范其实就是一组XMLweb services的JAVA API,JAX-WS允许开发者可以选择RPCoriented或者message-oriented来实现自己的web services。通过使用Java™ API for XMLWeb Services (JAX-WS) 技术设计和开发 Web服务,可以带来很多好处,能简化 Web 服务的开发和部署,并能加速 Web 服务的开发。</p><p>JAX-WS将本地的远程调用转换为XML协议(一般为SOAP格式),从而开发者不需要编写任何SOAP组装消息代码;同样,在服务端的JAX-WS会将SOAP消息解析为具体的函数调用,并返回结果。</p><p><img src="/img/webservice/jax-ws-tutorials.gif" alt="jax-runtime"></p><a id="more"></a><h3 id="实现一个webservice的需要的大致流程"><a href="#实现一个webservice的需要的大致流程" class="headerlink" title="实现一个webservice的需要的大致流程"></a>实现一个webservice的需要的大致流程</h3><ol><li>服务端,定义服务接口(SEI: service endpoint interface),并提供相关的实现类(SIB: service implementation bean)</li><li>通过JAX-WS服务API接口发布为webservice服务,从而可以产生一个公开的?wsdl网页,能够访问到提供了哪些服务</li><li><p>客户端通过JAX-WS的API根据公开的wsdl生成本地的代理(Stub)来实现对于远程方法的调用,调用过程就像在调用本地方法一样,JAX-WS Runtime会处理SOAP报文组装和网络传输等底层细节</p><p>当然,JAX-WS 也提供了一组针对底层消息进行操作的API调用,你可以通过Dispatch 直接使用SOAP消息或XML消息发送请求或者使用Provider处理SOAP或XML消息。</p></li></ol><h3 id="一个简单的实现"><a href="#一个简单的实现" class="headerlink" title="一个简单的实现"></a>一个简单的实现</h3><h4 id="首先定义接口:"><a href="#首先定义接口:" class="headerlink" title="首先定义接口:"></a>首先定义接口:</h4><figure class="highlight less"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="selector-tag">package</span> <span class="selector-tag">com</span><span class="selector-class">.up</span><span class="selector-class">.ws</span><span class="selector-class">.server</span>;</div><div class="line"></div><div class="line"><span class="selector-tag">import</span> <span class="selector-tag">javax</span><span class="selector-class">.jws</span><span class="selector-class">.WebMethod</span>;</div><div class="line"><span class="selector-tag">import</span> <span class="selector-tag">javax</span><span class="selector-class">.jws</span><span class="selector-class">.WebParam</span>;</div><div class="line"><span class="selector-tag">import</span> <span class="selector-tag">javax</span><span class="selector-class">.jws</span><span class="selector-class">.WebResult</span>;</div><div class="line"><span class="selector-tag">import</span> <span class="selector-tag">javax</span><span class="selector-class">.jws</span><span class="selector-class">.WebService</span>;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * @author paranoidq</span></div><div class="line"><span class="comment"> * @since 0.1</span></div><div class="line"><span class="comment"> */</span></div><div class="line">@<span class="selector-tag">WebService</span>(targetNamespace = <span class="string">"http://ws.wcg.unionpay.com"</span>)</div><div class="line"><span class="selector-tag">public</span> <span class="selector-tag">interface</span> <span class="selector-tag">WcgWsInterface</span> {</div><div class="line"></div><div class="line"> <span class="variable">@WebMethod</span>(operationName = <span class="string">"WsRequest"</span>)</div><div class="line"> <span class="variable">@WebResult</span>(name = <span class="string">"WsResponse"</span>)</div><div class="line"> String request(<span class="variable">@WebParam</span>(name = <span class="string">"service"</span>) String service, <span class="variable">@WebParam</span>(name = <span class="string">"requestMsg"</span>) String requestMsg);</div><div class="line">}</div></pre></td></tr></table></figure><p>注意点:</p><ol><li>这里定义了targetNamespace, 如果没有定义,则默认为该类的包名</li><li>用@WebMethod注解来指定了operationName,如果没有指定,则操作名默认为方法名</li><li>用@WebResult定义了返回值的名字,如果没有指定,默认为response</li><li>用@WebParamd定义了参数名字,如果没有指定,默认为param1(或arg1?)</li></ol><p>实际上,这些注解提高了wsdl的可读性。</p><h4 id="定义实现类"><a href="#定义实现类" class="headerlink" title="定义实现类"></a>定义实现类</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> com.up.ws.server;</div><div class="line"></div><div class="line"><span class="keyword">import</span> javax.jws.WebService;</div><div class="line"><span class="keyword">import</span> javax.jws.soap.SOAPBinding;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * <span class="doctag">@author</span> paranoidq</span></div><div class="line"><span class="comment"> * <span class="doctag">@since</span> 0.1</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="meta">@WebService</span>(endpointInterface = <span class="string">"com.up.ws.server.WcgWsInterface"</span>, serviceName = <span class="string">"wcgWebService"</span>, targetNamespace = <span class="string">"http://ws.wcg.unionpay.com"</span>)</div><div class="line"><span class="meta">@SOAPBinding</span>(style = SOAPBinding.Style.DOCUMENT)</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WcgWsImpl</span> <span class="keyword">implements</span> <span class="title">WcgWsInterface</span> </span>{</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">request</span><span class="params">(String service, String requestMsg)</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="string">"hello world"</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><p>这里指定了实现类对应的接口,以及服务名。同时,指定了SOAPBinding的类型。<br>一般而言,SOAP消息的格式有两种:RPC和DOCUMENT。(<strong>后面再理解区别</strong>)</p><h4 id="发布接口"><a href="#发布接口" class="headerlink" title="发布接口"></a>发布接口</h4><p>这里我们采用两种简单的方式发布接口</p><ol><li><p>第一种方式:</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">IHelloServices impl = <span class="keyword">new</span> WcgWsImpl(); </div><div class="line"><span class="comment">// 创建WebServices服务接口 </span></div><div class="line">WcgWsInterface <span class="keyword">factory</span> = <span class="keyword">new</span> JaxWsServerFactoryBean(); </div><div class="line"><span class="comment">// 注册webservices接口 </span></div><div class="line"><span class="keyword">factory</span>.setServiceClass(WcgWsInterface.<span class="keyword">class</span>); </div><div class="line"><span class="comment">// 发布接口 </span></div><div class="line"><span class="keyword">factory</span>.setAddress(<span class="string">"http://localhost:8090/webservice"</span>); </div><div class="line"><span class="keyword">factory</span>.setServiceBean(impl); </div><div class="line"><span class="comment">// 创建服务 </span></div><div class="line"><span class="keyword">factory</span>.create();</div></pre></td></tr></table></figure></li><li><p>第二种方式:</p><figure class="highlight haxe"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">Endpoint.publish(<span class="string">"http://localhost:8090/webservice"</span>, <span class="keyword">new</span> <span class="type">WcgWsImpl</span>());</div></pre></td></tr></table></figure></li></ol><p>发布之后,我们就可以通过<code>http://localhost:8090/webservice?wsdl</code>查看提供的服务了。<br>需要注意的是:CXF看上去没有在tomcat容器中运行,其实还是有容器的,而容器就是jetty。因此,需要我们在pom文件中包含相应的依赖。这里列出所有的依赖:(利用Endpoint的方式发布的时候,不需要<code>cxf-rt-frontend-jaxws</code>这个依赖)<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>commons-discovery<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>commons-discovery<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>20040218.194635<span class="tag"></<span class="name">version</span>></span></div><div class="line"><span class="tag"></<span class="name">dependency</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.cxf<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>cxf-api<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.12<span class="tag"></<span class="name">version</span>></span></div><div class="line"><span class="tag"></<span class="name">dependency</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.cxf<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>cxf-rt-transports-http<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.12<span class="tag"></<span class="name">version</span>></span></div><div class="line"><span class="tag"></<span class="name">dependency</span>></span></div><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.cxf<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>cxf-rt-frontend-jaxws<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.12<span class="tag"></<span class="name">version</span>></span></div><div class="line"> <span class="tag"></<span class="name">dependency</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">dependency</span>></span></div><div class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.apache.cxf<span class="tag"></<span class="name">groupId</span>></span></div><div class="line"> <span class="tag"><<span class="name">artifactId</span>></span>cxf-rt-transports-http-jetty<span class="tag"></<span class="name">artifactId</span>></span></div><div class="line"> <span class="tag"><<span class="name">version</span>></span>2.2.12<span class="tag"></<span class="name">version</span>></span></div><div class="line"><span class="tag"></<span class="name">dependency</span>></span></div></pre></td></tr></table></figure></p><p><strong>注意</strong>:<code>cxf-rt-transports-http-jetty</code>这个依赖一定要包含,否则会出现<code>CXF BusException No DestinationFactory for Namespace: http://cxf.apache.org/transport/http</code>的错误, 参考:<br><a href="http://stackoverflow.com/questions/24447280/cxf-busexception-no-destinationfactory-for-namespace-http-cxf-apache-org-trans" target="_blank" rel="external">http://stackoverflow.com/questions/24447280/cxf-busexception-no-destinationfactory-for-namespace-http-cxf-apache-org-trans</a></p><p>这样我们的一个webservice就构建好了。<br>下一篇将讲解如何构建客户端来访问我们的服务。</p>]]></content>
<summary type="html">
<h3 id="JAX-WS"><a href="#JAX-WS" class="headerlink" title="JAX-WS"></a>JAX-WS</h3><p>JAX-WS(Java API for XML Web Services),是SOAP协议的一个Java的实现规范,这个新规范是为了简化基于SOAP的Java开发。JAX-WS规范其实就是一组XMLweb services的JAVA API,JAX-WS允许开发者可以选择RPCoriented或者message-oriented来实现自己的web services。通过使用Java™ API for XMLWeb Services (JAX-WS) 技术设计和开发 Web服务,可以带来很多好处,能简化 Web 服务的开发和部署,并能加速 Web 服务的开发。</p>
<p>JAX-WS将本地的远程调用转换为XML协议(一般为SOAP格式),从而开发者不需要编写任何SOAP组装消息代码;同样,在服务端的JAX-WS会将SOAP消息解析为具体的函数调用,并返回结果。</p>
<p><img src="/img/webservice/jax-ws-tutorials.gif" alt="jax-runtime"></p>
</summary>
<category term="webservice" scheme="http://paranoidq.github.io/categories/webservice/"/>
<category term="webservice" scheme="http://paranoidq.github.io/tags/webservice/"/>
</entry>
<entry>
<title>NoHttpResponseException问题分析</title>
<link href="http://paranoidq.github.io/2016/10/17/NoHttpResponseException-problem-analysis/"/>
<id>http://paranoidq.github.io/2016/10/17/NoHttpResponseException-problem-analysis/</id>
<published>2016-10-17T02:11:30.000Z</published>
<updated>2016-10-17T02:21:56.000Z</updated>
<content type="html"><![CDATA[<h3 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h3><p>在性能测试的时候,使用apache httpclient连续发送报文到server进行压测。压测的配置如下:<br><figure class="highlight lsl"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">总数:<span class="number">10000</span></div><div class="line">并发:<span class="number">400</span></div><div class="line">每个发送线程的QPS限制:<span class="number">20</span></div></pre></td></tr></table></figure></p><p>出现的问题是: 大概1000左右的报文会出现 <code>NoHttpResponseException [server ip] failed to respond</code>。调低并发度到300一下时,不会出现这样的异常。<br><a id="more"></a></p><h3 id="原因分析"><a href="#原因分析" class="headerlink" title="原因分析"></a>原因分析</h3><p>查阅相关资料,官方对于<code>NoHttpResponseException</code>解释是:</p><blockquote><p>In some circumstances, usually when under heavy load, the web server may be able to receive requests but unable to process them. A lack of sufficient resources like worker threads is a good example. This may cause the server to drop the connection to the client without giving any response. HttpClient throws NoHttpResponseException when it encounters such a condition. In most cases it is safe to retry a method that failed with NoHttpResponseException.</p></blockquote><p>大致意思跟我之前构想的一样,在某些情况下,通常在重负载下时,Web服务器可能能够接收请求,但无法处理它们。缺乏足够的资源,比如工作线程,这可能会导致服务器断开客户端连接,并且没有给予任何回应。当它遇到这样的条件HttpClient会抛出NoHttpResponseException。此异常是由于服务器端过载而拒绝接受请求(不再响应)所致。<br>但是细想,为什么服务端会drop掉连接呢?首先贴一则查阅到的stackoverflow上的分析:</p><blockquote><p>Most likely persistent connections that are kept alive by the connection manager become stale. That is, the target server shuts down the connection on its end without HttpClient being able to react to that event, while the connection is being idle, thus rendering the connection half-closed or ‘stale’. Usually this is not a problem. HttpClient employs several techniques to verify connection validity upon its lease from the pool. Even if the stale connection check is disabled and a stale connection is used to transmit a request message the request execution usually fails in the write operation with SocketException and gets automatically retried. However under some circumstances the write operation can terminate without an exception and the subsequent read operation returns -1 (end of stream). In this case HttpClient has no other choice but to assume the request succeeded but the server failed to respond most likely due to an unexpected error on the server side.</p></blockquote><p>大致含义:httpclient的连接一般由一个poolConnectionManager管理,而往往会有keep-alive的设置,即保持这个连接多久。但是当server端主动关闭连接的时候,由于<strong>Connection可能处于idle状态,因此pool没有通知到HttpClient对应的connection,从而造成了connection处于半关闭的状态</strong>。<br>因此在这种状态下,之后connection从pool中被唤醒或取出,然后发送报文在某些情况下write会在没有报异常的情况下结束,然后httpclient尝试从connection中read应答时返回了-1(注意,不是没有返回,否则应该是WaitTimeoutException)。httpclient认为server返回了应答,但实际上没有,因此抛出了NoHttpResponseException异常。</p><p>进一步思考:为什么server会主动关闭Connection呢?如果是处理能力有限,那么应该更加利用connection才对,而不应该主动drop。我的思考如下:</p><blockquote><p>1.假设server的连接限制是10,那么显然,如果现在只有5个请求来了,那么会创建5个connection。<br>2.如果现在来了20个请求,那么显然server是处理不过来的</p></blockquote><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="1-增加服务器处理能力"><a href="#1-增加服务器处理能力" class="headerlink" title="1. 增加服务器处理能力"></a>1. 增加服务器处理能力</h4><p>这种方法在我们的项目中有效,我们通过修改JBoss服务器的两个参数来达到增强服务器处理能力的效果:</p><ul><li>maxThreads 最大线程数</li><li>acceptCount 最大等待线程数</li></ul><p>Jboss的线程模型:(引用自<a href="http://blog.csdn.net/lengyuhong/article/details/6319069" target="_blank" rel="external">http://blog.csdn.net/lengyuhong/article/details/6319069</a>)<br><img src="/img/jboss-thread-model.gif" alt="jboss-thread-model"></p><h4 id="2-retry"><a href="#2-retry" class="headerlink" title="2. retry"></a>2. retry</h4><figure class="highlight haxe"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">httpClientBuilder.setRetryHandler(<span class="keyword">new</span> <span class="type">StandardHttpRequestRetryHandler</span>(<span class="number">3</span>, <span class="literal">true</span>));</div></pre></td></tr></table></figure><h4 id="3-限制keepAlive的时间"><a href="#3-限制keepAlive的时间" class="headerlink" title="3. 限制keepAlive的时间"></a>3. 限制keepAlive的时间</h4><figure class="highlight pony"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="type">ConnectionKeepAliveStrategy</span> connectionKeepAliveStrategy = <span class="function"><span class="keyword">new</span> <span class="title">ConnectionKeepAliveStrategy</span>() {</span></div><div class="line"><span class="function"> @<span class="title">Override</span></span></div><div class="line"><span class="function"> <span class="title">public</span> <span class="title">long</span> <span class="title">getKeepAliveDuration</span>(<span class="type">HttpResponse</span> httpResponse, <span class="type">HttpContext</span> httpContext) {</span></div><div class="line"><span class="function"> <span class="title">return</span> 20 * 1000; <span class="comment">// tomcat默认keepAliveTimeout为20s</span></span></div><div class="line"><span class="function"> }</span></div><div class="line"><span class="function"> };</span></div><div class="line"><span class="function"><span class="title">PoolingHttpClientConnectionManager</span> <span class="title">connManager</span> = <span class="title">new</span> <span class="title">PoolingHttpClientConnectionManager</span>(<span class="number">20</span>, <span class="type">TimeUnit</span>.<span class="type">SECONDS</span>);</span></div><div class="line"><span class="function"><span class="title">connManager</span>.<span class="title">setMaxTotal</span>(<span class="number">200</span>);</span></div><div class="line"><span class="function"><span class="title">connManager</span>.<span class="title">setDefaultMaxPerRoute</span>(<span class="number">200</span>);</span></div><div class="line"><span class="function"><span class="title">RequestConfig</span> <span class="title">requestConfig</span> = <span class="title">RequestConfig</span>.<span class="title">custom</span>()</span></div><div class="line"><span class="function"> .<span class="title">setConnectTimeout</span>(<span class="number">10</span> * <span class="number">1000</span>)</span></div><div class="line"><span class="function"> .<span class="title">setSocketTimeout</span>(<span class="number">10</span> * <span class="number">1000</span>)</span></div><div class="line"><span class="function"> .<span class="title">setConnectionRequestTimeout</span>(<span class="number">10</span> * <span class="number">1000</span>)</span></div><div class="line"><span class="function"> .<span class="title">build</span>();</span></div><div class="line"><span class="function"><span class="title">HttpClientBuilder</span> <span class="title">httpClientBuilder</span> = <span class="title">HttpClientBuilder</span>.<span class="title">create</span>();</span></div><div class="line"><span class="function"><span class="title">httpClientBuilder</span>.<span class="title">setConnectionManager</span>(connManager);</span></div><div class="line"><span class="function"><span class="title">httpClientBuilder</span>.<span class="title">setDefaultRequestConfig</span>(requestConfig);</span></div><div class="line"><span class="function"><span class="title">httpClientBuilder</span>.<span class="title">setRetryHandler</span>(new <span class="type">DefaultHttpRequestRetryHandler</span>());</span></div><div class="line"><span class="function"><span class="title">httpClientBuilder</span>.<span class="title">setKeepAliveStrategy</span>(connectionKeepAliveStrategy);</span></div><div class="line"><span class="function"><span class="title">HttpClient</span> <span class="title">httpClient</span> = <span class="title">httpClientBuilder</span>.<span class="title">build</span>();</span></div><div class="line"><span class="function"><span class="title">ClientHttpRequestFactory</span> <span class="title">requestFactory</span> = <span class="title">new</span> <span class="title">HttpComponentsClientHttpRequestFactory</span>(httpClient);</span></div><div class="line"><span class="function"><span class="title">restTemplate</span> = <span class="title">new</span> <span class="title">RestTemplate</span>(requestFactory);</span></div></pre></td></tr></table></figure><p>主要是增加keepalive的策略,但这又带来一个问题,所有的连接只有20秒,无法使用长连接的性能优势</p><h4 id="4-closeIdleConnections"><a href="#4-closeIdleConnections" class="headerlink" title="4. closeIdleConnections"></a>4. closeIdleConnections</h4><p>PoolingHttpClientConnectionManager.closeIdleConnections 方法<br><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">closeIdleConnections</span><span class="params">(<span class="keyword">final</span> <span class="keyword">long</span> idleTimeout, <span class="keyword">final</span> TimeUnit tunit)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.log.isDebugEnabled()) {</div><div class="line"> <span class="keyword">this</span>.log.debug(<span class="string">"Closing connections idle longer than "</span> + idleTimeout + <span class="string">" "</span> + tunit);</div><div class="line"> }</div><div class="line"> <span class="keyword">this</span>.pool.closeIdle(idleTimeout, tunit); </div><div class="line">}</div></pre></td></tr></table></figure></p><p>三种方案暂且列出,准备实践一下效果,后续再更新到博客中来。相比而言,retry的方案比较sb,但是估计很好用;keepAlive限制需要考虑场景,某些需要长连接的场景下可能不是很合适,并且把统一把连接过期关掉,显然会带来不小的性能损失。最后一种方案,应该是keepAlive的折中优化,本质上是如果connection一直被pool拿出来使用,那我就不关闭你;如果connection一直idle,那么过了一段时间我就主动关闭你了。这样就避免了keepAlive统一限制的问题,同时如果一个connection被pool拿出来用了,那么显然他不可能被server关闭;而idle connection被关闭了,所以不会出现上述server端drop connection而pool又无法通知idle connection的问题。</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><ol><li><a href="https://my.oschina.net/sannychan/blog/485677" target="_blank" rel="external">https://my.oschina.net/sannychan/blog/485677</a></li><li><a href="http://stackoverflow.com/questions/10558791/apache-httpclient-interim-error-nohttpresponseexception" target="_blank" rel="external">http://stackoverflow.com/questions/10558791/apache-httpclient-interim-error-nohttpresponseexception</a></li><li><a href="http://stackoverflow.com/questions/10570672/get-nohttpresponseexception-for-load-testing/10680629#10680629" target="_blank" rel="external">http://stackoverflow.com/questions/10570672/get-nohttpresponseexception-for-load-testing/10680629#10680629</a></li><li><a href="https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/" target="_blank" rel="external">https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/</a></li><li><a href="http://luan.iteye.com/blog/1820054" target="_blank" rel="external">http://luan.iteye.com/blog/1820054</a></li></ol>]]></content>
<summary type="html">
<h3 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h3><p>在性能测试的时候,使用apache httpclient连续发送报文到server进行压测。压测的配置如下:<br><figure class="highlight lsl"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">总数:<span class="number">10000</span></div><div class="line">并发:<span class="number">400</span></div><div class="line">每个发送线程的QPS限制:<span class="number">20</span></div></pre></td></tr></table></figure></p>
<p>出现的问题是: 大概1000左右的报文会出现 <code>NoHttpResponseException [server ip] failed to respond</code>。调低并发度到300一下时,不会出现这样的异常。<br>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="httpclient" scheme="http://paranoidq.github.io/tags/httpclient/"/>
<category term="tcp" scheme="http://paranoidq.github.io/tags/tcp/"/>
</entry>
<entry>
<title>(翻译) Using HttpClient properly to avoid CLOSE_WAIT TCP connections</title>
<link href="http://paranoidq.github.io/2016/10/11/Using-HttpClient-properly-to-avoid-CLOSE-WAIT-TCP-connections/"/>
<id>http://paranoidq.github.io/2016/10/11/Using-HttpClient-properly-to-avoid-CLOSE-WAIT-TCP-connections/</id>
<published>2016-10-11T12:46:41.000Z</published>
<updated>2016-10-11T13:26:29.000Z</updated>
<content type="html"><![CDATA[<p>原文链接:<a href="https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/" target="_blank" rel="external">https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/</a></p><p>在我帮助我的客户debug一个TCP connection关于CLOSE_WAIT状态的问题时,我发现我们错误的使用了HttpClient。在这个问题上,如果你试图google <a href="http://www.google.com/search?q=HttpClient+CLOSE_WAIT" target="_blank" rel="external">HttpClient CLOSE_WAIT</a>,你会发现很多人跟我们一样存在疑惑。但是关于这个问题,很多的解答不够直观,甚至<a href="http://hc.apache.org/httpclient-legacy/tutorial.html" target="_blank" rel="external">官方文档</a>都是错误的。所以我在这篇文章中进行了分析。</p><p>Apache HttpClient的基本用法如下:<br><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">HttpClient httpClient = <span class="keyword">new</span> HttpClient();</div><div class="line">HttpMethod <span class="function"><span class="keyword">method</span> = <span class="title">new</span> <span class="title">GetMethod</span><span class="params">(uri)</span>;</span></div><div class="line"><span class="keyword">try</span> <span class="comment">{</span></div><div class="line"><span class="comment"> int statusCode = httpClient.executeMethod(method);</span></div><div class="line"><span class="comment"> byte[] responseBody = method.getResponseBody();</span></div><div class="line"><span class="comment"> // ...</span></div><div class="line"><span class="comment"> return stuff;</span></div><div class="line"><span class="comment">}</span> <span class="keyword">finally</span> <span class="comment">{</span></div><div class="line"><span class="comment"> method.releaseConnection();</span></div><div class="line"><span class="comment">}</span></div></pre></td></tr></table></figure></p><a id="more"></a><p>但事实上,这是不够的。问题在于释放connection使得connection对于HttpClient而言重新可用,而非真正的关闭该connection,原因在于使用了Http1.1协议,HttpClient可以在同一个connection中批量发送后续的请求(?)。</p><p>尽管,server端可能单向关闭了连接,但是客户端的connection还是打开的,并且一直持续到下一次尝试从connection中读报文时(此时,客户端才会意识到服务端已经关闭了这个连接)。TCP就是采用这种方式工作的。上面的情况我们称之为<code>半关闭的连接</code>,<strong>因为close()操作仅仅意味着我不会再往发送任何数据了,但是我还是可以从已经“closed”的连接中读取数据,只要另一端没有调用这个close()操作。</strong>[译注: 理解这一点非常重要]</p><p>因此,当HttpClient实例超过作用域的时候,它会被GC标记为可回收状态,但是GC并不会立即回收它。在GC真正回收它之前,HttpClient内部的connection仍然处于打开的状态,此时的TCP状态就处于CLOSE-WAIT。</p><p>为了解决这个问题,最简单的方法是在调用method之前设置如下代码:<br><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">method</span>.<span class="title">setRequestHeader</span><span class="params">("Connection", "close")</span>;</span></div></pre></td></tr></table></figure></p><p>这会导致HttpClient在接受完应答报文之后立即关闭connection。</p><p>另外一个方法是在finally块中添加如下代码:<br><figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="selector-tag">httpClient</span><span class="selector-class">.getHttpConnectionManager</span>()<span class="selector-class">.closeIdleConnections</span>(0);</div></pre></td></tr></table></figure></p><p>效果应该跟上面的一样,都是让connection在idle之后立即销毁connection。</p><p>更好的方法是:不要每次都new HttpClient,而重用经过<code>MultiThreadedHttpConnectionManager</code>配置的一个client实例。当然,这种情况下,就需要最终记得清理MultiThreadedHttpConnectionManager。<br><figure class="highlight arduino"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> MultiThreadedHttpConnectionManager connectionManager;</div><div class="line"><span class="keyword">private</span> <span class="built_in">HttpClient</span> httpClient;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">void</span> init() {</div><div class="line"> connectionManager = <span class="keyword">new</span> MultiThreadedHttpConnectionManager()</div><div class="line"> <span class="comment">// ... configure connectionManager ...</span></div><div class="line"> httpClient = <span class="keyword">new</span> <span class="built_in">HttpClient</span>(connectionManager);</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="built_in">shutdown</span>() {</div><div class="line"> connectionManager.<span class="built_in">shutdown</span>();</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">String</span> <span class="built_in">process</span>(<span class="keyword">String</span> uri) {</div><div class="line"> HttpMethod method = <span class="keyword">new</span> GetMethod(uri);</div><div class="line"> <span class="built_in">try</span> {</div><div class="line"> <span class="keyword">int</span> statusCode = httpClient.executeMethod(method);</div><div class="line"> <span class="keyword">byte</span>[] responseBody = method.getResponseBody();</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> <span class="built_in">return</span> stuff;</div><div class="line"> } finally {</div><div class="line"> method.releaseConnection();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<p>原文链接:<a href="https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/" target="_blank" rel="external">https://www.nuxeo.com/blog/using-httpclient-properly-avoid-closewait-tcp-connections/</a></p>
<p>在我帮助我的客户debug一个TCP connection关于CLOSE_WAIT状态的问题时,我发现我们错误的使用了HttpClient。在这个问题上,如果你试图google <a href="http://www.google.com/search?q=HttpClient+CLOSE_WAIT" target="_blank" rel="external">HttpClient CLOSE_WAIT</a>,你会发现很多人跟我们一样存在疑惑。但是关于这个问题,很多的解答不够直观,甚至<a href="http://hc.apache.org/httpclient-legacy/tutorial.html" target="_blank" rel="external">官方文档</a>都是错误的。所以我在这篇文章中进行了分析。</p>
<p>Apache HttpClient的基本用法如下:<br><figure class="highlight oxygene"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">HttpClient httpClient = <span class="keyword">new</span> HttpClient();</div><div class="line">HttpMethod <span class="function"><span class="keyword">method</span> = <span class="title">new</span> <span class="title">GetMethod</span><span class="params">(uri)</span>;</span></div><div class="line"><span class="keyword">try</span> <span class="comment">&#123;</span></div><div class="line"><span class="comment"> int statusCode = httpClient.executeMethod(method);</span></div><div class="line"><span class="comment"> byte[] responseBody = method.getResponseBody();</span></div><div class="line"><span class="comment"> // ...</span></div><div class="line"><span class="comment"> return stuff;</span></div><div class="line"><span class="comment">&#125;</span> <span class="keyword">finally</span> <span class="comment">&#123;</span></div><div class="line"><span class="comment"> method.releaseConnection();</span></div><div class="line"><span class="comment">&#125;</span></div></pre></td></tr></table></figure></p>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="httpclient" scheme="http://paranoidq.github.io/tags/httpclient/"/>
<category term="tcp" scheme="http://paranoidq.github.io/tags/tcp/"/>
</entry>
<entry>
<title>转载:大棋局与大智慧 —— 《时寒冰说》豆瓣书评</title>
<link href="http://paranoidq.github.io/2016/08/26/%E6%97%B6%E5%AF%92%E5%86%B0%E8%AF%B4-%E8%B1%86%E7%93%A3%E8%AF%84%E8%AE%BA/"/>
<id>http://paranoidq.github.io/2016/08/26/时寒冰说-豆瓣评论/</id>
<published>2016-08-26T05:21:30.000Z</published>
<updated>2016-10-21T02:17:54.000Z</updated>
<content type="html"><![CDATA[<p>《时寒冰说:经济大棋局,我们怎么办》是一部窥透世界大棋局的智慧之作,也是一部还原真相,普及常识的启蒙之作,时寒冰在书中淋漓尽致的演绎了他的利益与趋势分析法。</p><p>赵汀阳说,思想的主体部分虽然是知识,但思想的奠基部分却是智慧,尽管我们无法保证那些代表智慧的观念是真理。① 全球化时代,智慧的稀缺使得信息爆炸与思想贫乏成为最具讽刺意义的矛盾,而中国的现状尤为突出。时下,主旋律的和谐之音,左派救亡图存的革命论调,右派改革体制的变法主张,由“左右之争”②衍生出的国家主义、民族主义、民粹主义意识形态以及专靠胡说八道混饭吃的砖家叫兽的大放厥词③,可谓“五色令人目盲,五音令人耳聋,五味令人口爽” ④。可见,比通货膨胀更可怕的是信息爆炸和思想贫乏导致的大脑膨胀,我们需要用智慧来稀释膨胀。<br><a id="more"></a></p><p>时寒冰说:经济大棋局,我们怎么办》透过政治、经济、金融、军事等纷繁复杂的乱象,紧扣全球范围内货币迅猛膨胀与资源日益减少所造成的“资源为王”和债务危机并存这一条主线,指出经济大棋局的本质就是主要经济体转嫁债务危机的博弈,这既是经济大棋局的结论,也是趋势分析的起点。在此基础上,作者给出了“我们怎么办”的答案。具体而言,对于国家,只有贯彻“资源为王”和“藏富于民”的战略才可化解危机,对于个人,只有“固化财富”才可避免财富的不断缩水。本书中,时寒冰将“良性的制度”纳入“资源为王”的战略之中,这样以来“资源为王”战略包括了保护现有资源,通过技术创新提高资源的附加值,通过优良的制度和环境确保资源最大限度发挥效用三个方面,依次涵盖了资源命脉、技术创新、制度保障三个层面,由此可见,时寒冰的思想体系已初具雏形。虽然该书尚算不上纯正的思想之作,但这种先融天地于一炉⑤,再抽丝剥茧,化繁为简,进而窥透大棋局的才华和境界,非大智慧者不能有。这种大智慧不仅体现在作者对各种知识和多家思想的驾驭上,更体现在作者用绝大多数人都能接受的常识完成了对大棋局的推理和演绎。</p><p>在经济大棋局中,货币战争、石油战争、粮食危机,中东危机、朝韩危机、人民币升值、汇率操纵、贸易战只是大棋局中居于不同节点的一个个棋子。美国不仅因为其经济军事势力,更因为深谙“资源为王”之道,因此在整个大棋局中,始终掌握着主动权。虽然,美国是世界上债务危机最严重的国家,但每当危机出现时,要么引爆欧元区某个主权国家的债务危机,要么制造中东危机,操纵国际油价,要么通过压迫人民币升值或汇率战,迫使中国政府送上采购订单,这些屡试不爽的招数,既转移了视线,也转移了危机,更重要的是为寻找新的经济增长点争取了时间,如金融危机后奥巴马政府的新能源战略,既是稀释美元泡沫的“海绵”,也是美国主导下一轮经济周期的货币之锚。相反,西班牙等“笨猪五国”自身经济的脆弱,日本巨额债务的内生性及社会的老龄化,中国的体制弊端和内需不足,都成为悬挂在各自债务危机头顶的达摩克利斯之剑,因此,未来的变数必然发生在这些国家。正因为大智慧窥透了大棋局的本质,因此,时寒冰在解读已经发生的事情时总是得心应手,在预测即将发生的事情时绝不模棱两可,这是智者和学者的区别,前者有卓越的智慧,后者只有渊博的知识。</p><p>在对经济大棋局分析论证的逻辑体系中,地缘政治学、意识形态、宗教信仰、国家主义、民族主义、体制改革、“阴谋论”则成为时寒冰演绎利益分析法的一个个“棋子”。近年来“阴谋论”甚嚣尘上,何新⑥、刘军洛⑦、郎咸平⑧等人从不同角度阐述华尔街和美国政府合谋控制全世界特别控制中国的阴谋,而布热津斯基早在10多年前就提出建立美国在世界范围内战略大棋局的构想⑨。但如果回归常识,战略和阴谋不过是一枚硬币的两面。大棋局中,掌握主动权者,棋局只是自己的战略,而被动承受者,棋局则是别人的阴谋。但无论阴谋还是战略,我们所能做的只有反省自身的不足并加以修正,唯有此才可能绝处逢生,这也是时寒冰贯穿全书的基本格调,正是这种超越利益,尊重常识的智者情怀,才有本书中对各种思想批判吸收后让其成为利益分析的一道道工具,也才有用常识还原真相的启蒙意义。比如,作者对“破窗理论”的批判,对美国在对外扩张中兼顾意识形态和国家利益的分析,对民主体制有利于促进财富创造的论断,都是超越意识形态和利益羁绊的常识启蒙。</p><p>智者总是独孤悲壮的,当抽丝剥茧后发现中国的经济问题并不在经济本身,而在经济之外,这对一个智者而言,是一种既尴尬又无奈的结果,或许只有在字里行间对民主制度不吝赞美,对领导人政治智慧满怀期望,对人应常怀爱心、公心大声呼唤。权力至上,蔑视常识与规则,缺乏信仰,投机盛行,这是中国国家性、国民性的基本“土壤”,我非常赞同乔良将军关于土壤的改造重于制度移植的观点⑩,但对制度的批判,对常识的启蒙本身就是对土壤的改造,所以,当时寒冰用他的智慧窥透经济大棋局,并告诉我们应该怎么办的时候,作为一名先知,他的使命就已经完成了,剩下的就是接受启蒙的每个人的行动。正如作者书中所言:“良性机制是大棋局中至关重要的保障。如果我们的制度也完善起来,中国将不惧怕任何挑战和博弈,而这,需要每个个体的努力。每个人都不应该简单的做一个等待者,而应该是一个推动者、行动者。”</p><h3 id="注释:"><a href="#注释:" class="headerlink" title="注释:"></a>注释:</h3><p>①赵汀阳:《每个人的政治》,社会科学文献出版社。</p><p>②张宏良在《当前中国左派和右派的斗争》、《中华民族再次到了最危险的时候》等文章中,提出新“左派”、“右派”概念,并打出毛泽东思想的旗帜,反对资本主义和平演变的“颜色革命”。本文除有说明外,所引“左”“右”,仅是从革命与改良路径选择上的“左”“右”区分。</p><p>③“砖家叫兽”本是一个错别“词”,但用拼音输入后,第一个选项竟然就是“砖家叫兽”,不得不发出“时势造错字”的感叹。思考我们身边的现象,一条道路,今天建,明天拆,这竟然是砖家叫兽的“破冰理论”,岂不知这是在用消灭今天的财富来恢复本来就存在的财富。</p><p>④老子,《道德经·德经第十二章》。本文寓指今天中国思想界的混乱,这种混乱因本身的极端功利化,尚不等于思想的多元化。“五色”指种种丑恶离奇的乱像,“五音”指狭隘的民族主义、民粹主义、国家主义;被政治化的“左”“右”之争,以及被妖魔化的“阴谋论”等;“五味”指因为价值观与信仰迷失,人们行为的普遍功利化。</p><p>⑤《时寒冰说:经济大棋局,我们怎么办》一书中,政治、经济、金融、军事、历史、宗教等无所不包,地缘政治、阴谋论等或经典或流行的思想流派均有涉及。因此,本书被普遍认为是一本“小百科全书”。</p><p>⑥何新在《统治世界:神秘共济会揭秘》中分析指出,世界上存在一个集金融、政治和意识形态于一身,隐秘遥控着重大国际事件,并企图通过生物战争等方式消灭世界上“垃圾人口”的共济会。</p><p>⑦刘军洛在《被绑架的中国经济》、《高等文化的控制》等书中分析指出,美国对外经济扩展是由跨国公司、军火商、农场主、华尔街、美国政府组成的多维一体的立体作战模式,而最重要的手段就是培植利益代言人,让这些代言人给国民“洗脑”,从而达到通过文化控制实现战略利益的目标。</p><p>⑧郎咸平在《新帝国主义在中国》等书中分析指出,华尔街和国家机器默契配合,开始侵吞中国民族工业和优质企业。</p><p>⑨美国当代著名的战略思想家,美国总统前国家安全事务助理布热津斯基博士在20世纪90年代所著的《大棋局:美国的首要地位及其地缘战略》将欧亚大陆视为美国全球战略构想的关键区域。</p><p>⑩乔良在《时寒冰说:经济大棋局,我们怎么办》序言《悲壮的先知》结尾指出,国家性、国民性是构成制度和体制的土壤,不改变土壤结构去移植体制或制度,是本末倒置。</p><h3 id="原文链接:"><a href="#原文链接:" class="headerlink" title="原文链接:"></a>原文链接:</h3><p><a href="https://book.douban.com/review/4980808/" target="_blank" rel="external">https://book.douban.com/review/4980808/</a></p>]]></content>
<summary type="html">
<p>《时寒冰说:经济大棋局,我们怎么办》是一部窥透世界大棋局的智慧之作,也是一部还原真相,普及常识的启蒙之作,时寒冰在书中淋漓尽致的演绎了他的利益与趋势分析法。</p>
<p>赵汀阳说,思想的主体部分虽然是知识,但思想的奠基部分却是智慧,尽管我们无法保证那些代表智慧的观念是真理。① 全球化时代,智慧的稀缺使得信息爆炸与思想贫乏成为最具讽刺意义的矛盾,而中国的现状尤为突出。时下,主旋律的和谐之音,左派救亡图存的革命论调,右派改革体制的变法主张,由“左右之争”②衍生出的国家主义、民族主义、民粹主义意识形态以及专靠胡说八道混饭吃的砖家叫兽的大放厥词③,可谓“五色令人目盲,五音令人耳聋,五味令人口爽” ④。可见,比通货膨胀更可怕的是信息爆炸和思想贫乏导致的大脑膨胀,我们需要用智慧来稀释膨胀。<br>
</summary>
<category term="阅读" scheme="http://paranoidq.github.io/categories/%E9%98%85%E8%AF%BB/"/>
<category term="社会" scheme="http://paranoidq.github.io/tags/%E7%A4%BE%E4%BC%9A/"/>
<category term="思考" scheme="http://paranoidq.github.io/tags/%E6%80%9D%E8%80%83/"/>
<category term="趋势" scheme="http://paranoidq.github.io/tags/%E8%B6%8B%E5%8A%BF/"/>
</entry>
<entry>
<title>Linux服务器性能调优常用工具及实例</title>
<link href="http://paranoidq.github.io/2016/08/02/linux-server-optimization-tools/"/>
<id>http://paranoidq.github.io/2016/08/02/linux-server-optimization-tools/</id>
<published>2016-08-01T16:37:17.000Z</published>
<updated>2016-08-01T17:00:58.000Z</updated>
<content type="html"><![CDATA[<h3 id="查看进程情况-ps"><a href="#查看进程情况-ps" class="headerlink" title="查看进程情况: ps"></a>查看进程情况: ps</h3><h4 id="显示所有进程信息"><a href="#显示所有进程信息" class="headerlink" title="显示所有进程信息"></a>显示所有进程信息</h4><figure class="highlight groovy"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">$ ps -A</div><div class="line">PID TTY TIME CMD</div><div class="line"><span class="number">1</span> ? 00:<span class="number">00</span>:<span class="number">00</span> init</div><div class="line"><span class="number">2</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">0</span></div><div class="line"><span class="number">3</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">0</span></div><div class="line"><span class="number">4</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">1</span></div><div class="line"><span class="number">5</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">1</span></div><div class="line"><span class="number">6</span> ? 00:<span class="number">29</span>:<span class="number">57</span> events/<span class="number">0</span></div><div class="line">...</div></pre></td></tr></table></figure><a id="more"></a><h4 id="显示指定用户"><a href="#显示指定用户" class="headerlink" title="显示指定用户"></a>显示指定用户</h4><figure class="highlight groovy"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">$ ps -u root</div><div class="line">PID TTY TIME CMD</div><div class="line"><span class="number">1</span> ? 00:<span class="number">00</span>:<span class="number">00</span> init</div><div class="line"><span class="number">2</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">0</span></div><div class="line"><span class="number">3</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">0</span></div><div class="line"><span class="number">4</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">1</span></div><div class="line"><span class="number">5</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">1</span></div><div class="line"><span class="number">6</span> ? 00:<span class="number">29</span>:<span class="number">57</span> events/<span class="number">0</span></div><div class="line"><span class="number">7</span> ? 00:<span class="number">00</span>:<span class="number">00</span> events/<span class="number">1</span></div><div class="line">...</div></pre></td></tr></table></figure><h4 id="ps-与grep-组合使用,查找特定进程-常用"><a href="#ps-与grep-组合使用,查找特定进程-常用" class="headerlink" title="ps 与grep 组合使用,查找特定进程 (常用)"></a>ps 与grep 组合使用,查找特定进程 (常用)</h4><figure class="highlight tap"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">$ ps -ef|grep ssh</div><div class="line">root <span class="number"> 2720 </span> <span class="number"> 1 </span><span class="number"> 0 </span>Nov02 ? 00:00:00 /usr/sbin/sshd</div><div class="line">root <span class="number"> 17394 </span><span class="number"> 2720 </span><span class="number"> 0 </span>14:58 ? 00:00:00 sshd: root@pts/0</div><div class="line">root <span class="number"> 17465 </span>17398 <span class="number"> 0 </span>15:57 pts/0 00:00:00 grep ssh</div></pre></td></tr></table></figure><h4 id="列出目前所有的正在内存中的程序-常用)"><a href="#列出目前所有的正在内存中的程序-常用)" class="headerlink" title="列出目前所有的正在内存中的程序 (常用)"></a>列出目前所有的正在内存中的程序 (常用)</h4><figure class="highlight tap"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">$ ps aux</div><div class="line">USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND</div><div class="line">root <span class="number"> 1 </span> 0.0 0.0 <span class="number"> 10368 </span> <span class="number"> 676 </span>? Ss Nov02 0:00 init [3]</div><div class="line">root <span class="number"> 2 </span> 0.0 0.0 <span class="number"> 0 </span> <span class="number"> 0 </span>? S< Nov02 0:01 [migration/0]</div><div class="line">root <span class="number"> 3 </span> 0.0 0.0 <span class="number"> 0 </span> <span class="number"> 0 </span>? SN Nov02 0:00 [ksoftirqd/0]</div><div class="line">root <span class="number"> 4 </span> 0.0 0.0 <span class="number"> 0 </span> <span class="number"> 0 </span>? S< Nov02 0:01 [migration/1]</div><div class="line">root <span class="number"> 5 </span> 0.0 0.0 <span class="number"> 0 </span> <span class="number"> 0 </span>? SN Nov02 0:00 [ksoftirqd/1]</div></pre></td></tr></table></figure><p>输出含义:<br><figure class="highlight vhdl"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">USER:该 <span class="keyword">process</span> 属于那个使用者账号的</div><div class="line">PID :该 <span class="keyword">process</span> 的号码</div><div class="line">%CPU:该 <span class="keyword">process</span> 使用掉的 CPU 资源百分比</div><div class="line">%MEM:该 <span class="keyword">process</span> 所占用的物理内存百分比</div><div class="line">VSZ :该 <span class="keyword">process</span> 使用掉的虚拟内存量 (Kbytes)</div><div class="line">RSS :该 <span class="keyword">process</span> 占用的固定的内存量 (Kbytes)</div><div class="line">TTY :该 <span class="keyword">process</span> 是在那个终端机上面运作,若与终端机无关,则显示 ?,另外, tty1-tty6 是本机上面的登入者程序,若为 pts/<span class="number">0</span> 等等的,则表示为由网络连接进主机的程序。</div><div class="line">STAT:该程序目前的状态,主要的状态有</div><div class="line">R :该程序目前正在运作,或者是可被运作</div><div class="line">S :该程序目前正在睡眠当中 (可说是 idle 状态),但可被某些讯号 (<span class="keyword">signal</span>) 唤醒。</div><div class="line">T :该程序目前正在侦测或者是停止了</div><div class="line">Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态</div><div class="line">START:该 <span class="keyword">process</span> 被触发启动的时间</div><div class="line"><span class="built_in">TIME</span> :该 <span class="keyword">process</span> 实际使用 CPU 运作的时间</div><div class="line">COMMAND:该程序的实际指令</div></pre></td></tr></table></figure></p><h4 id="ps-ef与-ps-aux的区别"><a href="#ps-ef与-ps-aux的区别" class="headerlink" title="ps -ef与 ps aux的区别"></a>ps -ef与 ps aux的区别</h4><p><code>ps aux</code>最初用到Unix Style中,而<code>ps -ef</code>被用在System V Style中,两者输出略有不同。现在的大部分Linux系统都是可以同时使用这两种方式的。</p><p><code>ps aux</code>中与<code>ps -ef</code>不同的列有:<br><figure class="highlight cos"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">USER <span class="comment">//用户名</span></div><div class="line"><span class="built_in">%CPU</span> <span class="comment">//进程占用的CPU百分比</span></div><div class="line"><span class="built_in">%MEM</span> <span class="comment">//占用内存的百分比</span></div><div class="line">VSZ <span class="comment">//该进程使用的虚拟內存量(KB)</span></div><div class="line">RSS <span class="comment">//该进程占用的固定內存量(KB)(驻留中页的数量)</span></div><div class="line">STAT <span class="comment">//进程的状态</span></div><div class="line">START <span class="comment">//该进程被触发启动时间</span></div><div class="line">TIME <span class="comment">//该进程实际使用CPU运行的时间</span></div></pre></td></tr></table></figure></p><p>其中<code>STAT</code>状态为的常见字符有:<br><figure class="highlight 1c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">D <span class="comment">//无法中断的休眠状态(通常 IO 的进程);</span></div><div class="line">R <span class="comment">//正在运行可中在队列中可过行的;</span></div><div class="line">S <span class="comment">//处于休眠状态;</span></div><div class="line">T <span class="comment">//停止或被追踪;</span></div><div class="line">W <span class="comment">//进入内存交换 (从内核2.6开始无效);</span></div><div class="line">X <span class="comment">//死掉的进程 (基本很少见);</span></div><div class="line">Z <span class="comment">//僵尸进程;</span></div><div class="line">< <span class="comment">//优先级高的进程</span></div><div class="line">N <span class="comment">//优先级较低的进程</span></div><div class="line">L <span class="comment">//有些页被锁进内存;</span></div><div class="line">s <span class="comment">//进程的领导者(在它之下有子进程);</span></div><div class="line">l <span class="comment">//多线程,克隆线程(使用 CLONE_THREAD, 类似 NPTL pthreads);</span></div><div class="line">+ <span class="comment">//位于后台的进程组;</span></div></pre></td></tr></table></figure></p><h3 id="查看端口情况-netstat"><a href="#查看端口情况-netstat" class="headerlink" title="查看端口情况 netstat"></a>查看端口情况 netstat</h3><h4 id="列出所有连接"><a href="#列出所有连接" class="headerlink" title="列出所有连接"></a>列出所有连接</h4><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">$ netstat -a</div><div class="line">Active Internet connections (servers <span class="keyword">and</span> established)</div><div class="line">Proto Recv-Q Send-Q Local<span class="built_in"> Address </span> Foreign<span class="built_in"> Address </span> State </div><div class="line">tcp 0 0 enlightened:domain *:* LISTEN </div><div class="line">tcp 0 0 localhost:ipp *:* LISTEN </div><div class="line">tcp 0 0 enlightened.local:54750 li240-5.members.li:http ESTABLISHED</div><div class="line">tcp 0 0 enlightened.local:49980 del01s07-in-f14.1:https ESTABLISHED</div><div class="line">tcp6 0 0 ip6-localhost:ipp [::]:* LISTEN </div><div class="line"><span class="built_in">..</span>.</div></pre></td></tr></table></figure><h4 id="列出tcp-udp连接-u和-t"><a href="#列出tcp-udp连接-u和-t" class="headerlink" title="列出tcp/udp连接 -u和-t"></a>列出tcp/udp连接 <code>-u</code>和<code>-t</code></h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="meta">$</span><span class="bash"> netstat -at</span></div><div class="line"><span class="meta">$</span><span class="bash"> netstat -au</span></div></pre></td></tr></table></figure><h4 id="禁用反向域名解析,加快查询速度-n"><a href="#禁用反向域名解析,加快查询速度-n" class="headerlink" title="禁用反向域名解析,加快查询速度 -n"></a>禁用反向域名解析,加快查询速度 <code>-n</code></h4><p>没有必要知道主机名,就使用 -n 选项禁用域名解析功能<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">$</span><span class="bash"> netstat -ant</span></div></pre></td></tr></table></figure></p><h4 id="只列出监听中的端口-l"><a href="#只列出监听中的端口-l" class="headerlink" title="只列出监听中的端口 -l"></a>只列出监听中的端口 <code>-l</code></h4><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">$ netstat -tnl</div><div class="line">Active Internet connections (only servers)</div><div class="line">Proto Recv-Q Send-Q Local<span class="built_in"> Address </span> Foreign<span class="built_in"> Address </span> State </div><div class="line">tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN </div><div class="line">tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN </div><div class="line">tcp6 0 0 ::1:631 :::* LISTEN</div></pre></td></tr></table></figure><p>不要使用<code>-a</code>,否则linux会列出所有端口,而不只是监听(LISTEN)端口</p><h4 id="只列出active端口"><a href="#只列出active端口" class="headerlink" title="只列出active端口"></a>只列出active端口</h4><figure class="highlight x86asm"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">$ netstat -atnp | grep ESTA</div><div class="line">(<span class="keyword">Not</span> all processes could be identified, non-owned process info</div><div class="line"> will <span class="keyword">not</span> be shown, you would have to be root to see it all.)</div><div class="line">tcp <span class="number">0</span> <span class="number">0</span> <span class="number">192.168</span><span class="meta">.1</span><span class="meta">.2</span>:<span class="number">49156</span> <span class="number">173.255</span><span class="meta">.230</span><span class="meta">.5</span>:<span class="number">80</span> ESTABLISHED <span class="number">1691</span>/chrome </div><div class="line">tcp <span class="number">0</span> <span class="number">0</span> <span class="number">192.168</span><span class="meta">.1</span><span class="meta">.2</span>:<span class="number">33324</span> <span class="number">173.194</span><span class="meta">.36</span><span class="meta">.117</span>:<span class="number">443</span> ESTABLISHED <span class="number">1691</span>/chrome</div></pre></td></tr></table></figure><p>active 状态的套接字连接用 “ESTABLISHED” 字段表示</p><h4 id="列出进程名,进程号和用户ID-p"><a href="#列出进程名,进程号和用户ID-p" class="headerlink" title="列出进程名,进程号和用户ID -p"></a>列出进程名,进程号和用户ID <code>-p</code></h4><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">~$ sudo netstat -nlpt</div><div class="line">Active Internet connections (only servers)</div><div class="line">Proto Recv-Q Send-Q Local<span class="built_in"> Address </span> Foreign<span class="built_in"> Address </span> State PID/Program name</div><div class="line">tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN 1144/dnsmasq </div><div class="line">tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 661/cupsd </div><div class="line">tcp6 0 0 ::1:631 :::* LISTEN 661/cupsd</div></pre></td></tr></table></figure><p>必须要root权限才能显示!如果没有root需要查看端口对应的进程,请参考<code>lsof</code><br><code>-ep</code>选项可以同时查看进程名和用户名</p><h4 id="实战"><a href="#实战" class="headerlink" title="实战"></a>实战</h4><ol><li>查看服务是否运行<figure class="highlight tap"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">$ sudo netstat -aple | grep ntp</div><div class="line">udp <span class="number"> 0 </span> <span class="number"> 0 </span>enlightened.local:ntp *:* root <span class="number"> 17430 </span> 1789/ntpd </div><div class="line">udp <span class="number"> 0 </span> <span class="number"> 0 </span>localhost:ntp *:* root <span class="number"> 17429 </span> 1789/ntpd </div><div class="line">udp <span class="number"> 0 </span> <span class="number"> 0 </span>*:ntp *:* root <span class="number"> 17422 </span> 1789/ntpd </div><div class="line">udp6 <span class="number"> 0 </span> <span class="number"> 0 </span>fe80::216:36ff:fef8:ntp [::] root <span class="number"> 17432 </span> 1789/ntpd</div></pre></td></tr></table></figure></li></ol><!--more--><ol><li><p>查看端口号的占用情况</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">$</span><span class="bash"> netstat -an | grep 12000</span></div></pre></td></tr></table></figure></li><li><p>结合<code>watch</code>监控active状态的连接</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">$</span><span class="bash"> watch -d -n0 <span class="string">"netstat -atnp | grep ESTA"</span></span></div></pre></td></tr></table></figure></li></ol><h4 id="附:watch命令"><a href="#附:watch命令" class="headerlink" title="附:watch命令"></a>附:watch命令</h4><p>watch可以帮助使用者监测一个命令的运行结果,避免重复手动运行。watch命令会周期执行<br>参数:</p><ul><li>-n 时间间隔,缺省值为2s</li><li>-d 高亮显示变化区域</li><li>-t 关闭watch命令在顶部的时间间隔</li></ul><p>实例:<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">每隔一秒高亮显示http链接数的变化情况</div><div class="line"><span class="meta">$</span><span class="bash"> watch -n 1 -d <span class="string">'pstree|grep http'</span></span></div><div class="line"></div><div class="line">10秒一次输出系统的平均负载</div><div class="line"><span class="meta">$</span><span class="bash"> watch -n 10 <span class="string">'cat /proc/loadavg'</span></span></div></pre></td></tr></table></figure></p><h3 id="查看使用CPU-MEM最多的进程"><a href="#查看使用CPU-MEM最多的进程" class="headerlink" title="查看使用CPU\MEM最多的进程"></a>查看使用CPU\MEM最多的进程</h3><h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><p><a href="http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/ps.html" target="_blank" rel="external">ps</a><br><a href="https://linux.cn/article-2434-1.html" target="_blank" rel="external">netstat</a><br><a href="http://www.cnblogs.com/peida/archive/2012/12/31/2840241.html" target="_blank" rel="external">watch</a></p>]]></content>
<summary type="html">
<h3 id="查看进程情况-ps"><a href="#查看进程情况-ps" class="headerlink" title="查看进程情况: ps"></a>查看进程情况: ps</h3><h4 id="显示所有进程信息"><a href="#显示所有进程信息" class="headerlink" title="显示所有进程信息"></a>显示所有进程信息</h4><figure class="highlight groovy"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">$ ps -A</div><div class="line">PID TTY TIME CMD</div><div class="line"><span class="number">1</span> ? 00:<span class="number">00</span>:<span class="number">00</span> init</div><div class="line"><span class="number">2</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">0</span></div><div class="line"><span class="number">3</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">0</span></div><div class="line"><span class="number">4</span> ? 00:<span class="number">00</span>:<span class="number">01</span> migration/<span class="number">1</span></div><div class="line"><span class="number">5</span> ? 00:<span class="number">00</span>:<span class="number">00</span> ksoftirqd/<span class="number">1</span></div><div class="line"><span class="number">6</span> ? 00:<span class="number">29</span>:<span class="number">57</span> events/<span class="number">0</span></div><div class="line">...</div></pre></td></tr></table></figure>
</summary>
<category term="linux" scheme="http://paranoidq.github.io/categories/linux/"/>
<category term="linux" scheme="http://paranoidq.github.io/tags/linux/"/>
<category term="server" scheme="http://paranoidq.github.io/tags/server/"/>
</entry>
<entry>
<title>BASE64换行符的坑</title>
<link href="http://paranoidq.github.io/2016/08/01/base64-newline-trap/"/>
<id>http://paranoidq.github.io/2016/08/01/base64-newline-trap/</id>
<published>2016-08-01T13:29:38.000Z</published>
<updated>2016-10-25T12:47:26.000Z</updated>
<content type="html"><![CDATA[<h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>client发报文给server,server端接收到http post发来的报文之后做base64解码,然后通过消息中间件传给核心系统进行处理。然后发现,client端发过来的报文会多出很多<code>/r/n</code>这样的字符,导致核心系统base64解码后也多出了<code>/r/n</code>,而核心系统之前没有考虑到这些问题,从而报错。</p><a id="more"></a><h3 id="问题的分析"><a href="#问题的分析" class="headerlink" title="问题的分析"></a>问题的分析</h3><p>问题在于为什么发送方的报文会多出来<code>/r/n</code>呢?</p><h4 id="Step-1"><a href="#Step-1" class="headerlink" title="Step 1"></a>Step 1</h4><p>首先看回车和换行符的区别:</p><blockquote><p>在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。<br> 于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。<br>这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。<br> 后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。<br>Unix 系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“ <回车><换 行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打 开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。</p></blockquote><p>所以导致的问题应该就是client端是windows系统,而我们这边处理的系统在linux下,因此就会有<code>/r/n</code>的问题。</p><h4 id="Step-2"><a href="#Step-2" class="headerlink" title="Step 2"></a>Step 2</h4><p>OK,让对方去掉报文中的换行之后,问题还是存在。而且还有新的发现:</p><blockquote><p>BASE64之后,当字符串过长(一般超过76)时会自动在中间加一个换行符。及时我们自己测试的报文完全没有任何换行存在。</p></blockquote><p>于是想办法去研究<code>sun.misc.BASE64Encoder</code>的源码,有了一些发现。</p><p>BASE64主要调用的方法是:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">byte</span>[] bytes = <span class="string">"abcd"</span>.getBytes();</div><div class="line">BASE64Encoder encoder = <span class="keyword">new</span> BASE64Encoder();</div><div class="line">encoder.encodeBuffer(bytes);</div></pre></td></tr></table></figure></p><p>encodeBuffer源码的大致情况:(大部分源码位于<code>BASE64Encoder</code>的父类<code>CharacterEncoder</code>中)<br><figure class="highlight processing"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">String</span> encodeBuffer(<span class="built_in">byte</span> aBuffer[]) {</div><div class="line"> ByteArrayOutputStream outStream = <span class="keyword">new</span> ByteArrayOutputStream();</div><div class="line"> ByteArrayInputStream inStream = <span class="keyword">new</span> ByteArrayInputStream(aBuffer);</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> encodeBuffer(inStream, outStream);</div><div class="line"> } <span class="keyword">catch</span> (Exception IOException) {</div><div class="line"> <span class="comment">// This should never happen.</span></div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"CharacterEncoder.encodeBuffer internal error"</span>);</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> (outStream.toString());</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * Encode bytes from the input stream, and write them as text characters</span></div><div class="line"><span class="comment"> * to the output stream. This method will run until it exhausts the</span></div><div class="line"><span class="comment"> * input stream, but does not print the line suffix for a final</span></div><div class="line"><span class="comment"> * line that is shorter than bytesPerLine().</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">void</span> encode(InputStream inStream, OutputStream outStream)</div><div class="line"> <span class="keyword">throws</span> IOException {</div><div class="line"> <span class="built_in">int</span> j;</div><div class="line"> <span class="built_in">int</span> numBytes;</div><div class="line"> <span class="built_in">byte</span> tmpbuffer[] = <span class="keyword">new</span> <span class="built_in">byte</span>[bytesPerLine()];</div><div class="line"></div><div class="line"> encodeBufferPrefix(outStream); </div><div class="line"></div><div class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</div><div class="line"> numBytes = readFully(inStream, tmpbuffer);</div><div class="line"> <span class="keyword">if</span> (numBytes == <span class="number">0</span>) {</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> }</div><div class="line"> encodeLinePrefix(outStream, numBytes); </div><div class="line"> <span class="keyword">for</span> (j = <span class="number">0</span>; j < numBytes; j += bytesPerAtom()) {</div><div class="line"></div><div class="line"> <span class="keyword">if</span> ((j + bytesPerAtom()) <= numBytes) {</div><div class="line"> encodeAtom(outStream, tmpbuffer, j, bytesPerAtom());</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> encodeAtom(outStream, tmpbuffer, j, (numBytes)- j);</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="keyword">if</span> (numBytes < bytesPerLine()) {</div><div class="line"> <span class="keyword">break</span>;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> encodeLineSuffix(outStream); <span class="comment">// 这一行会输出换行</span></div><div class="line"> }</div><div class="line"> }</div><div class="line"> encodeBufferSuffix(outStream); </div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * Encode the suffix that ends every output line. By default</span></div><div class="line"><span class="comment"> * this method just prints a <newline> into the output stream.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">protected</span> <span class="keyword">void</span> encodeLineSuffix(OutputStream aStream) <span class="keyword">throws</span> IOException {</div><div class="line"> pStream.<span class="built_in">println</span>();</div><div class="line">}</div></pre></td></tr></table></figure></p><p>注意,<code>encodeLineSuffix</code>会输出换行。也就是每次读满一个buffer大小的时候,都会输出一个换行。buffer的大小是由<code>bytesPerLine()</code>函数决定的,该函数是一个抽象函数,由子类实现。而在BASE64Encoder中,该函数的返回值为57.<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * this class encodes 57 bytes per line. This results in a maximum</span></div><div class="line"><span class="comment"> * of 57/3 * 4 or 76 characters per output line. Not counting the</span></div><div class="line"><span class="comment"> * line termination.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">int</span> <span class="title">bytesPerLine</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> (<span class="number">57</span>);</div><div class="line">}</div></pre></td></tr></table></figure></p><p><a href="http://stackoverflow.com/questions/9341047/carriage-return-issue-decoding-base64-from-java-and-sending-to-browser" target="_blank" rel="external">StackOverflow上有回答</a>这是做了一种<code>chunking</code>,在每一个<code>chunk</code>后面添加了<code>/n</code>。并且sun的库函数只存在于oracle的jvm下面,而不存在于其他jvm中。</p><h3 id="Step-3"><a href="#Step-3" class="headerlink" title="Step 3"></a>Step 3</h3><p><code>encode</code>与<code>encodeBuffer</code>有细微的区别:<code>encodeBuffer</code>会在最后一行不足<code>bytesPerline()</code>时添加一个换行符,而encode则不会做处理。</p><p>貌似很坑爹,做了如此多的隐含处理,让调用者想死的心都有了。</p><h3 id="一劳永逸的办法"><a href="#一劳永逸的办法" class="headerlink" title="一劳永逸的办法"></a>一劳永逸的办法</h3><p>建议使用<code>org.apache.commons.codec.binary.Base64</code>库:<br><figure class="highlight gcode"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">Base<span class="number">64.</span>e<span class="symbol">ncodeBase64</span><span class="comment">(..)</span>;</div><div class="line">Base<span class="number">64.</span>decodeBase<span class="number">64</span><span class="comment">(..)</span></div></pre></td></tr></table></figure></p><p>并且该库显示指明了,你是否需要<code>chunk</code>选项和<code>urlsafe</code>选项(避免输出<code>+</code>和<code>/</code>,而是输出<code>-</code>和<code>_</code>):<br><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">encodeBase64Chunked(<span class="keyword">final</span> <span class="keyword">byte</span>[] binaryData)</div><div class="line">encodeBase64(<span class="keyword">final</span> <span class="keyword">byte</span>[] binaryData, <span class="keyword">final</span> <span class="keyword">boolean</span> isChunked)</div></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><p>client发报文给server,server端接收到http post发来的报文之后做base64解码,然后通过消息中间件传给核心系统进行处理。然后发现,client端发过来的报文会多出很多<code>/r/n</code>这样的字符,导致核心系统base64解码后也多出了<code>/r/n</code>,而核心系统之前没有考虑到这些问题,从而报错。</p>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="todo" scheme="http://paranoidq.github.io/tags/todo/"/>
<category term="base64" scheme="http://paranoidq.github.io/tags/base64/"/>
</entry>
<entry>
<title>个人已经破产,靠还没倒闭的行业活着(转载)</title>
<link href="http://paranoidq.github.io/2016/07/25/%E4%B8%AA%E4%BA%BA%E5%B7%B2%E7%BB%8F%E7%A0%B4%E4%BA%A7/"/>
<id>http://paranoidq.github.io/2016/07/25/个人已经破产/</id>
<published>2016-07-25T13:14:31.000Z</published>
<updated>2016-10-21T02:10:59.000Z</updated>
<content type="html"><![CDATA[<blockquote><p>本文转载自: <a href="http://mp.weixin.qq.com/s?__biz=MjM5OTM4MDY4MQ==&mid=2650101881&idx=1&sn=19f426423f01f34015685ab146119469&scene=23&srcid=0725foRg46546T20CysgOR9b#rd" target="_blank" rel="external">读库-张立宪</a></p></blockquote><p>遇到几件职场上的事情,引发一些感触,在这里一并写出来,与大家一起怀疑下人生。</p><h3 id="一"><a href="#一" class="headerlink" title="一"></a>一</h3><p>我属于那种乐观型选手,相信在现在这个世道,不管是来自家人扶助、社会救济,或者有基本社会能力的成年人随便找个工作,温饱都不会有问题,至少饿不死。有了这个底气,再去做什么,就看自己的能力、志趣和因缘了。去年在上海,有位杂志编辑,说他们的杂志快不行了。我便与他共勉:只有破产的公司,没有倒闭的个人。</p><p>两个月前又见到一位同行,他本是一家颇有名望的杂志社老总,如今再创业,风生水起。我问他用人之道,他说很重要的一条心得是:尽量不用媒体人,尤其是那些老部下。</p><p>这种说法让我很是意外。听他一一道来:当年纸媒红火的时候,那些以“名记”身份行走传媒江湖的人,既眼高手低,又好吃懒做,真真徒有虚名,名不副实。整天乐于听人恭维,忙于开发布会拿红包,急于炫耀自己社会关系之广,却连篇软文也写不好。我不由得表示赞同,听他接着说:这样的名记,是被他所在的媒体赋予的名声,并且也被惯坏了,我要真把他招过来,既不好用,也用不起。</p><p>这位仁兄的偏见实在是颇有道理:有的人实际上已经个人破产,只是在靠所供职的还没倒闭的机构活着。</p><p>冷静想一下,我们是否已经让自己处于这种境地?</p><a id="more"></a><h3 id="二"><a href="#二" class="headerlink" title="二"></a>二</h3><p>想起当年电视台如日中天炙手可热的时候,我领教过的一个台里员工。那位负责灯光的人被称为“灯爷”,对别人永远是颐指气使的口吻,对自己永远觉得含着天大的委屈,找他做最简单的事情都得陪着笑脸,而他做最分内的事情都觉得是别人在给他添麻烦。</p><p>更可怕的是,我们都对这样的大爷习以为常。他老人家稍微嘴脸好看点儿,手脚勤快点儿,便觉得是恩赐。</p><p>直到后来见识了一位香港“灯爷”:永远不用你操心、催促,在规定的时间内到位,黑着脸不许别人碰他的器材,手脚麻利地快速解决一切事情,工作成效之高、之专业,几乎都让你意识不到他的存在。</p><p>再看我们这位爷,遇到潜在的金主,想给自己捞点野活挣点外快,就倨傲又殷勤地给人家递名片:我是中央台的。</p><p>那时的我年轻气盛,看到这一幕,鄙夷地想:把你名片上“中央电视台”那几个字划掉,你什么也不是。</p><h3 id="三"><a href="#三" class="headerlink" title="三"></a>三</h3><p>这些年,中国房地产行业空前繁荣。建筑师这个职业,应该是机会大大的,挣钱多多的,心里美美的吧。</p><p>一位建筑界的老师却对我说,高歌猛进的房地产行业,还有那些地标式的公共建筑,不仅对城市、对环境造成破坏,对公众审美形成摧残,还把一代建筑师给毁了。</p><p>我吃惊地问为什么。他的要点是:因为活儿太多,素质不高的设计师也可以有干不完的单子应接不暇;因为钱太好挣,许多建筑师没有了自我提高的主动与自觉;因为工程太赶,缺乏原创、智慧含量和时间成本越少的设计成为首选,行业的水准线便越来越低。</p><p>一位做建筑图书的出版业同行,准备引进一套欧洲建筑丛书,全套有二三百本,囊括了当代建筑的各位大师,全面呈现其作品和建筑理念。我想当然地认为这套书会很好卖,因为它本来口碑就好,建筑装饰类图书又永远在书店里占据相当比重,中国的建筑设计行业人多,钱多,需求又大。</p><p>她说给我的发行量却低得惊人。这套书只引进了十几种,原来宏伟的出版计划看来会中途夭折。</p><p>她的观点也是:大家的学习动力没有了,因为钱太容易挣。一个建筑师不用看这些书,照样有挣不完的钱。</p><p>一个行业的繁荣,对个人来说是好事还是坏事?</p><h3 id="四"><a href="#四" class="headerlink" title="四"></a>四</h3><p>我们去年签下一套英文书的版权,写人类伟大的历史文化遗迹。因为它和建筑有很大关系,所以我想邀请建筑界的专家——外语又好、又懂建筑的人来翻译。</p><p>找到一位人脉广的老师求助,他说,你可能在建筑界找不到人。没人愿意接你的活儿。你看,能够胜任翻译的人,得是具备一定能力和资历的人。一本书的翻译至少要几个月的时间,稿费最多几万块钱,可人家用几个晚上时间画建筑图纸,就能挣几万块。</p><p>我说,这不正好吗?用几天时间画图,把几万块钱挣出来,那不就没有后顾之忧,更能踏踏实实、专心致志搞翻译了吗?</p><p>你这个逻辑太自作多情了,也把你的书看得太重要了。人家想的是,花几个月时间来翻译你这本书的话,就意味着耽误了画多少图、失去了挣多少钱的机会。并且这种活儿都还排着队等他来接呢,谁还稀罕为你翻译,谁算不明白这笔账呢?</p><p>一个人都温饱无忧了,何必还为挣钱,把自己搞得连翻译一本书这么有乐趣的事都不做呢?我兀自不甘心地咕哝。</p><p>有了小房子还要改善性住宅,有了大房子还要弄别墅。永远挣不完的钱,永远画不完的图。大家的时间,都用来赶这些行活了。</p><p>好吧,我之蜜糖,彼之砒霜。</p><h3 id="五"><a href="#五" class="headerlink" title="五"></a>五</h3><p>改革开放三十多年,破产的公司、机构不计其数,但中国几百家出版社,好像自始至终没有一家倒闭的。</p><p>行业的繁荣或依赖政策形成的稳定,会给一些鱼龙混杂、蜂拥而入的从业人员造成错觉,相信自己可以高枕无忧不思进取,甚至以为自己“亦有贡献”。可在危机来临之前,一个人的能力储备、职业素养、知识更新、自我成长,会自觉地被激发、强调出来么?</p><p>写到这里,我突然有一种担心:这些永不倒闭的出版社,已经把一些编辑养残,自我破产了。</p><p>我们有没有勇气和清醒,独立于外部环境和行业冷暖,明白自己要做些什么?</p>]]></content>
<summary type="html">
<blockquote>
<p>本文转载自: <a href="http://mp.weixin.qq.com/s?__biz=MjM5OTM4MDY4MQ==&amp;mid=2650101881&amp;idx=1&amp;sn=19f426423f01f34015685ab146119469&amp;scene=23&amp;srcid=0725foRg46546T20CysgOR9b#rd" target="_blank" rel="external">读库-张立宪</a></p>
</blockquote>
<p>遇到几件职场上的事情,引发一些感触,在这里一并写出来,与大家一起怀疑下人生。</p>
<h3 id="一"><a href="#一" class="headerlink" title="一"></a>一</h3><p>我属于那种乐观型选手,相信在现在这个世道,不管是来自家人扶助、社会救济,或者有基本社会能力的成年人随便找个工作,温饱都不会有问题,至少饿不死。有了这个底气,再去做什么,就看自己的能力、志趣和因缘了。去年在上海,有位杂志编辑,说他们的杂志快不行了。我便与他共勉:只有破产的公司,没有倒闭的个人。</p>
<p>两个月前又见到一位同行,他本是一家颇有名望的杂志社老总,如今再创业,风生水起。我问他用人之道,他说很重要的一条心得是:尽量不用媒体人,尤其是那些老部下。</p>
<p>这种说法让我很是意外。听他一一道来:当年纸媒红火的时候,那些以“名记”身份行走传媒江湖的人,既眼高手低,又好吃懒做,真真徒有虚名,名不副实。整天乐于听人恭维,忙于开发布会拿红包,急于炫耀自己社会关系之广,却连篇软文也写不好。我不由得表示赞同,听他接着说:这样的名记,是被他所在的媒体赋予的名声,并且也被惯坏了,我要真把他招过来,既不好用,也用不起。</p>
<p>这位仁兄的偏见实在是颇有道理:有的人实际上已经个人破产,只是在靠所供职的还没倒闭的机构活着。</p>
<p>冷静想一下,我们是否已经让自己处于这种境地?</p>
</summary>
<category term="社会" scheme="http://paranoidq.github.io/categories/%E7%A4%BE%E4%BC%9A/"/>
<category term="社会" scheme="http://paranoidq.github.io/tags/%E7%A4%BE%E4%BC%9A/"/>
<category term="思考" scheme="http://paranoidq.github.io/tags/%E6%80%9D%E8%80%83/"/>
</entry>
<entry>
<title>人生新阶段随笔(1)</title>
<link href="http://paranoidq.github.io/2016/07/15/%E4%BA%BA%E7%94%9F%E6%96%B0%E9%98%B6%E6%AE%B5%E9%9A%8F%E7%AC%94-1/"/>
<id>http://paranoidq.github.io/2016/07/15/人生新阶段随笔-1/</id>
<published>2016-07-15T12:02:53.000Z</published>
<updated>2016-07-15T12:45:30.000Z</updated>
<content type="html"><![CDATA[<p>开始工作接近两周时间,算是成功从一个天真的在校学生变成了半个社会人士。<br>两周的时间虽然工作上没写什么代码,每天也没怎么需要加班加点,但感觉还是挺忙碌的。上周去苏州陪女友玩了两天,这周也算得空了,自己可以好好想想这两周以来的生活和工作。</p><p>工作上的变化确实是蛮大的,跟学校里那种轻松自由的节奏完全不同。倒不是主管push你,反而是自己觉得如果没事情做,就会很着急。所以总是想方设法让自己忙碌起来,找事情做。感觉这样的想法还是挺好的,毕竟作为一个刚毕业的学生,最重要的还是能尽快学到知识和技能吧。</p><a id="more"></a><p>说道知识和技能,这个其实是我不太满意的地方。虽说之前可能也有心理准备,但是进入到公司才发现,国企确实在技术上的发展不够好,或者说不够有技术的激情。当然,已经工作多年的前辈肯定是比我懂得多,但总感觉整个技术开发中心的氛围不够好,这点可能真的无法跟互联网公司相比。不过,其实互联网公司的技术其实也就集中于几个核心的部门,其他大部分也都是完成业务而已。这点上,在EMC这样的外企实习过的同事其实还蛮有发言权的。因此,<strong>公司可能只是一个平台,更多的还是要考自己去主动地学习和探索。问题其实天天都会有,就看你愿不愿意花时间去学了</strong>。</p><p>工作的同事其实还是蛮nice的,大家人都非常随和,没有传统国企那种等级森严的感觉。虽然有些师兄已经工作了近十年了,但是还是可以跟我们聊得很好。当然,我之前定下的目标就是要努力处理好自己的人事关系,目前看来还算不错,基本上跟组里的同事相处的很好,能够主动跟他们交流。搁过去的话,我估计还不会有这样的表现,所以算是步入社会之后强迫自己的一种改变吧。能够跟各种人正常的交往也算是一种极为重要的生活技能。</p><p>目前组里做的东西说白了就是适配,适配各种行业和机构的接口。本质上,这东西其实还是有一定技术含量的,不过就我目前研究的线上系统WCG的源码来看,写的确实不怎么样,感觉连我这个在校生都不如,各种magic number随便乱写,配置也随便乱写,系统模块和类设计不合理的地方很多。所以目前我的想法是能够重构这个系统,感觉如果能够进行下去的话,应该是一次难得的学习机会,毕竟无论是个人技术成长还是将来跳槽,这都是一个有利的加分项。而<strong>难点在于,如何设计一个好的系统架构,并且考虑到测试成本的问题,系统重构的过程需要尽量的step-by-step,类似于持续集成吧</strong>。这方面,在没有人带我的情况下,感觉是最困难的地方了。架构的问题,可能需要我上网查阅很多资料,如果能够有人请教的话就最好了。而关于重构成本的问题,这估计也是领导最为关心的问题,所以我接下来要做的工作应该是<strong>证明系统重构能够带来很大的好处,并且尽量减小重构对于目前运行的功能的稳定性影响</strong>。</p><p>跳槽的问题其实也是这两周困扰我的问题之一,虽然目前还算适应现在的工作环境,但是跳槽可能还是在我的计划中,毕竟我向往更加富有技术激情和技术发展空间的平台。因此,每天晚上我都会固定时间学习,但是总感觉时间不够用。觉得可能是效率不够高。反思一下,应该是学习的东西太过零散,然后每次看到比较复杂的东西,总是想往后拖,然后就不了了之了。所以,后续的改进方向应该是: <strong>大概每周确定一个学习的目标,围绕着这个主线学习。然后抽出一部分时间,解决工作中遇到的问题,每次解决一个问题的时候,就尽量深入弄懂,不要一知半解地结束。这样每接触到一个知识,就能解决一个了。然后每周的主线应该围绕将来自己想深入的方向去学习探索,主要的手段是看书+看系列博客+写demo相结合的方式。</strong><br>其实,做site project是个不错的选择,但是缺的是idea。比较好的方案是,写一些算法相关的project或者小游戏,这些花费的时间不多,并且应该能有一些帮助。这个后续需要再思考思考!!!毕竟,<strong>项目经验在未来的跳槽中估计有着很重要的作用,这点需要时刻提醒自己注意一下</strong>!!!</p><p>生活上,发现自己基本上没什么娱乐时间了,感觉回来学习一会儿,运动一会洗个澡,再视频半小时就睡觉了。唉,发现到了工作中真的是时间不够用了。之前计划好的,学日语、学上海话还有看电影写影评之类的通通没时间完成,估计应该调整一下思路,这些东西应该放到周末休息时间去做。这样比较合理。</p><p>另外就是去苏州看了一下房子,海胥澜庭地段确实好,也是个三学区的房子。不过价格超贵,感觉还是有点承受不起。前几日,考虑到我跳槽不一定能跳到苏州的问题,觉得可能园区还是比较适合。以来跳槽之后在园区的可能性比较大,上班方便;而来,如果不能跳,将来工作重新找到浦西的话,去上海上班还是比较方便的。而且园区房价的抗跌性还是很好的。如果想去杭州的话,房子卖了应该不成问题。另一个考虑就是学区房的问题,这个需要调查一下,到底学区房需要提前几年买的问题。目前我的估计是5年之内我应该用不上学区房了。所以可以不考虑学区房的问题暂时。目前是这样打算的。</p><p>恩,总结到这里,最后摘一句话自勉:<br>—— <em>其实学习是个慢功夫,讲究节奏,就像认真的模仿,看上去很慢很循规蹈矩,但一步步走下来,确是成长最快的途径。这在我们这个事事都追求效率的时代,显得弥足珍贵</em>。</p>]]></content>
<summary type="html">
<p>开始工作接近两周时间,算是成功从一个天真的在校学生变成了半个社会人士。<br>两周的时间虽然工作上没写什么代码,每天也没怎么需要加班加点,但感觉还是挺忙碌的。上周去苏州陪女友玩了两天,这周也算得空了,自己可以好好想想这两周以来的生活和工作。</p>
<p>工作上的变化确实是蛮大的,跟学校里那种轻松自由的节奏完全不同。倒不是主管push你,反而是自己觉得如果没事情做,就会很着急。所以总是想方设法让自己忙碌起来,找事情做。感觉这样的想法还是挺好的,毕竟作为一个刚毕业的学生,最重要的还是能尽快学到知识和技能吧。</p>
</summary>
<category term="随笔" scheme="http://paranoidq.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="随笔" scheme="http://paranoidq.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>关于Java局部类不能访问外围的非final局部变量的探索</title>
<link href="http://paranoidq.github.io/2016/07/04/java-inner-class-final-problem/"/>
<id>http://paranoidq.github.io/2016/07/04/java-inner-class-final-problem/</id>
<published>2016-07-04T13:04:47.000Z</published>
<updated>2016-07-15T12:43:03.000Z</updated>
<content type="html"><![CDATA[<h3 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h3><p>关于java中的内部类,有很多坑,其中条就是:</p><blockquote><p>对于定义在函数中的内部类而言,在内部类中可以访问外部函数的局部变量,但这些局部变量必须被申明为final。</p></blockquote><a id="more"></a><p>为了清晰,首先用例子探索一下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> java.util.Date;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * Created by paranoidq on 16/7/4.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LocalInnerClassDemo</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">demo</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">int</span> counter = <span class="number">0</span>;</div><div class="line"> String str = <span class="string">"test"</span>;</div><div class="line"></div><div class="line"> Date[] dates = <span class="keyword">new</span> Date[<span class="number">100</span>];</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < dates.length; i++) {</div><div class="line"> dates[i] = <span class="keyword">new</span> Date() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compareTo</span><span class="params">(Date anotherDate)</span> </span>{</div><div class="line"> System.out.println(counter); <span class="comment">// case1: int不修改 -> 通过</span></div><div class="line"> System.out.println(counter++); <span class="comment">// case2: int修改 -> compiler error</span></div><div class="line"> System.out.println(str); <span class="comment">// case3: string不修改 -> 通过</span></div><div class="line"> System.out.println(str + <span class="string">"t"</span>); <span class="comment">// case4: string为不可变对象 -> 通过</span></div><div class="line"></div><div class="line"> str = <span class="string">"aaa"</span>;</div><div class="line"> System.out.println(str); <span class="comment">// case5: 修改了string -> 不通过</span></div><div class="line"> <span class="comment">// Error:</span></div><div class="line"> <span class="comment">// Variable str is accessed from within inner class,</span></div><div class="line"> <span class="comment">// need to be final or effectively final</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.compareTo(anotherDate);</div><div class="line"> }</div><div class="line"> };</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>我们发现,其实编译器足够智能,对于case1和case3而言,虽然访问了非final局部变量,但是还是通过编译了,而只有在case2、case5中修改了局部变量时,才报错。而对于case4而言,涉及到string对象不可变的另一个知识点,这里略过。</p><p>分析报错的提示,可以知道,实际上对于局部类访问外部变量的规则,相对比较宽松,只要是<code>final or effectively final</code>即可,所谓<code>effectively final</code>其实也就是在局部类内没有做出实质性的修改动作,这一类情况编译器也是让过的。</p><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><p>为什么在局部类内不能访问外部的非final局部变量呢?参考<a href="http://android.blog.51cto.com/268543/384844" target="_blank" rel="external">这个帖子</a>,写的很到位。引用如下</p><p>这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。<br>首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。<br>当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用例如: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">dosome</span><span class="params">(<span class="keyword">final</span> String a,<span class="keyword">final</span> <span class="keyword">int</span> b)</span></span>{ </div><div class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Dosome</span></span>{<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">dosome</span><span class="params">()</span></span>{System.out.println(a+b)}}; </div><div class="line"> Dosome some=<span class="keyword">new</span> Dosome(); </div><div class="line"> some.dosome(); </div><div class="line">}</div></pre></td></tr></table></figure><p>从代码来看好像是那个内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是:</p><pre><code>class Outer$Dosome{ public Dosome(final String a,final int b){ this.Dosome$a=a; this.Dosome$b=b; } public void dosome(){ System.out.println(this.Dosome$a+this.Dosome$b); } }</code></pre><p>从以上代码看来,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。 </p><p>这样理解就很容易得出为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。”</p><p>(简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变)</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p>内部类的原理分析:<a href="http://android.blog.51cto.com/268543/384809" target="_blank" rel="external">http://android.blog.51cto.com/268543/384809</a></p>]]></content>
<summary type="html">
<h3 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h3><p>关于java中的内部类,有很多坑,其中条就是:</p>
<blockquote>
<p>对于定义在函数中的内部类而言,在内部类中可以访问外部函数的局部变量,但这些局部变量必须被申明为final。</p>
</blockquote>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="inner-class" scheme="http://paranoidq.github.io/tags/inner-class/"/>
</entry>
<entry>
<title>Java动态代理与CgLib对比</title>
<link href="http://paranoidq.github.io/2016/06/08/java-dynamic-proxy-implementations/"/>
<id>http://paranoidq.github.io/2016/06/08/java-dynamic-proxy-implementations/</id>
<published>2016-06-08T07:48:27.000Z</published>
<updated>2016-06-08T03:59:59.000Z</updated>
<content type="html"><![CDATA[<h3 id="静态代理的问题"><a href="#静态代理的问题" class="headerlink" title="静态代理的问题"></a>静态代理的问题</h3><p><img src="http://images.techhive.com/images/idge/imported/article/jvw/2000/11/jw-1110-proxy-100157716-orig.gif" alt="proxy pattern"></p><ol><li>紧耦合:代理类必须实现被代理对象的接口</li><li>硬编码:项目中大量充斥着类似**proxy这样的类</li><li>无法动态添加方法的拦截,会导致代码侵入</li></ol><p>如何解决问题?实际上也就是解决依赖的问题,代理类的创建不依赖于硬编码,想什么时候创建就什么时候创建,本质上也就是动态构建类和实例吧。JDK动态代理就是利用了Java的反射机制动态构建代理类和实例的。</p><a id="more"></a><h3 id="JDK动态代理"><a href="#JDK动态代理" class="headerlink" title="JDK动态代理"></a>JDK动态代理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Subject</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">doSomething</span><span class="params">()</span></span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RealSubject</span> <span class="keyword">implements</span> <span class="title">Subject</span> </span>{</div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doSomething</span><span class="params">()</span> </span>{</div><div class="line"> System.out.println(<span class="string">"This is real object."</span>);</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * Jdk 动态代理必须代理接口,不能代理正常的类.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * 创建速度快于Cgi,但是运行速度大约比Cgi慢10倍.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * Created by paranoidq on 16/1/18.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdkProxy</span> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> Object proxied;</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">JdkProxy</span><span class="params">(Object proxied)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.proxied = proxied;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> proxy The proxy parameter passed to the invoke() method is the dynamic proxy object implementing the interface.</span></div><div class="line"><span class="comment"> * Most often you don't need this object.</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> method</span></div><div class="line"><span class="comment"> * <span class="doctag">@param</span> args</span></div><div class="line"><span class="comment"> * <span class="doctag">@return</span></span></div><div class="line"><span class="comment"> * <span class="doctag">@throws</span> Throwable</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"> System.out.println(<span class="string">"proxy: "</span> + proxy.getClass());</div><div class="line"> System.out.println(<span class="string">"method: "</span> + method);</div><div class="line"> System.out.println(<span class="string">"args: "</span> + args);</div><div class="line"> <span class="keyword">if</span> (method.getName().contains(<span class="string">"do"</span>)) {</div><div class="line"> System.out.println(<span class="string">"method contains do*"</span>);</div><div class="line"> }</div><div class="line"> Method[] methods = proxy.getClass().getDeclaredMethods();</div><div class="line"> <span class="keyword">for</span> (Method m : methods) {</div><div class="line"> System.out.println(m.getName());</div><div class="line"> }</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * output:</span></div><div class="line"><span class="comment"> * equals</span></div><div class="line"><span class="comment"> * toString</span></div><div class="line"><span class="comment"> * hashCode</span></div><div class="line"><span class="comment"> * doSomething !!!</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> Class[] interfaces = proxy.getClass().getInterfaces();</div><div class="line"> <span class="keyword">for</span> (Class c : interfaces) {</div><div class="line"> System.out.println(c); <span class="comment">// interface me.util.proxy.jdkproxy.Subject</span></div><div class="line"> }</div><div class="line"> <span class="comment">/**</span></div><div class="line"><span class="comment"> * output:</span></div><div class="line"><span class="comment"> * interface me.util.proxy.jdkproxy.Subject</span></div><div class="line"><span class="comment"> */</span></div><div class="line"> <span class="keyword">return</span> method.invoke(proxied, args); <span class="comment">// 在实际对象上invoke方法,同时传入参数</span></div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</div><div class="line"> Subject subject = <span class="keyword">new</span> RealSubject();</div><div class="line"> Subject proxy = (Subject) Proxy.newProxyInstance(</div><div class="line"> Subject.class.getClassLoader(),</div><div class="line"> subject.getClass().getInterfaces(),</div><div class="line"> <span class="keyword">new</span> JdkProxy(subject));</div><div class="line"></div><div class="line"> proxy.doSomething();</div><div class="line"> System.out.println(<span class="string">"======="</span>);</div><div class="line"> System.out.println(proxy); <span class="comment">// toString的调用同样会dispatch到invoke,因此会被也"包装"</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><h4 id="总结几点:"><a href="#总结几点:" class="headerlink" title="总结几点:"></a>总结几点:</h4><ol><li>只能代理接口,不能代理类(原因在与newProxyInstane参数中需要被代理类的接口数组)。如果将newProxyInstance返回的Object转为RealSubject,则报异常:<code>java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to me.util.proxy.jdkproxy.RealSubject</code></li><li><code>java.lang.Object</code>的方法<code>hashCode()</code>、<code>equals()</code>和<code>toString()</code>也会被代理类拦截。(原因在代码最后的<code>toString(proxy)</code>也触发了代理类的输出)</li><li>代理实例本身会被传递给invoke,作为第一个参数,即proxy。可以通过这个获取代理实例及其类型信息(代码中,我们获得了代理实例实际上有doSomething()这个方法,因为代理实例也继承了接口Subject!<strong>所以说为什么要传入classloader,因为实际上是Java在用bytecode生成一个实现了Subject接口的动态代理类啊!这不就是隐式地在用反射构建一个类么?</strong>)</li></ol><p>JDK动态代理类的字节码是由Java在运行时通过反射动态生成的。</p><p>上面的例子基本已经显示了JDK代理的重要特性,下面整理说明一些重点:(主要参考Oracle JavaDoc)</p><ol><li>invoke()的返回值会传递给代理实例,从而返回给客户端,因此客户端的代理实例声明的返回值类型要注意匹配。</li><li></li><li>invoke代理的函数的参数列表以数组形式给出,对基本类型做了默认的boxing。另外,注意,在invoke内部可以任意修改这个参数数组,这里Java没有约束。(当然,一般来说修改函数的参数是很危险的,尤其还是这种经过代理的调用,会让调用方完全不知情!)</li></ol><h3 id="CGLib动态代理"><a href="#CGLib动态代理" class="headerlink" title="CGLib动态代理"></a>CGLib动态代理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/**</span></div><div class="line"><span class="comment"> * cgi代理可以代理任何类,采用的方式是创建类的子类,然后在子类中调用父类的方法,并织入aop的逻辑</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * 创建慢,但运行性能快于jdk.</span></div><div class="line"><span class="comment"> * 适用于对象创建少,长期使用的情况,如singleton.</span></div><div class="line"><span class="comment"> *</span></div><div class="line"><span class="comment"> * Created by paranoidq on 16/1/18.</span></div><div class="line"><span class="comment"> */</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CgLibProxy</span> <span class="keyword">implements</span> <span class="title">MethodInterceptor</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> Enhancer enhancer = <span class="keyword">new</span> Enhancer();</div><div class="line"></div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">getProxy</span><span class="params">(Class clazz)</span> </span>{</div><div class="line"> enhancer.setSuperclass(clazz); <span class="comment">// 设置被代理类, CgLib根据字节码生成被代理类的子类</span></div><div class="line"> enhancer.setCallback(<span class="keyword">this</span>);</div><div class="line"> <span class="keyword">return</span> enhancer.create();</div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">intercept</span><span class="params">(Object o, Method method, Object[] objects, MethodProxy methodProxy)</span> <span class="keyword">throws</span> Throwable </span>{</div><div class="line"></div><div class="line"> System.out.println(<span class="string">"Before method"</span>);</div><div class="line"> <span class="comment">// invoke()会造成循环调用, 因为调用的还是子类对象的方法, 而子类对象的方法还是会被拦截.</span></div><div class="line"> Object result = methodProxy.invokeSuper(o, objects);</div><div class="line"> System.out.println(<span class="string">"After method"</span>);</div><div class="line"> <span class="keyword">return</span> result;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</div><div class="line"> CgLibProxy proxyHandler = <span class="keyword">new</span> CgLibProxy();</div><div class="line"> <span class="comment">// proxy normal class: RealSubject</span></div><div class="line"> RealSubject proxy = (RealSubject) proxyHandler.getProxy(RealSubject.class);</div><div class="line"> proxy.doSomething();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure><ol><li>使用ASM(JAVA字节码处理框架)在内存中动态的生成被代理类的子类</li><li>可以代理没有接口的类(JDK动态代理则不行)</li><li>通过字节码技术为被代理的类创建子类,并在子类中采用方法<code>intercept</code>拦截所有父类方法的调用</li><li>显然,基于第三点,CGlib不能代理final类</li><li>pom包: cglib + asm (底层依赖于asm)</li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html" target="_blank" rel="external">Java Doc</a><br><a href="http://blog.csdn.net/janice0529/article/details/42884019" target="_blank" rel="external">http://blog.csdn.net/janice0529/article/details/42884019</a><br><a href="http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html" target="_blank" rel="external">http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html</a><br><a href="http://www.techavalanche.com/2011/08/24/understanding-java-dynamic-proxy/" target="_blank" rel="external">http://www.techavalanche.com/2011/08/24/understanding-java-dynamic-proxy/</a></p>]]></content>
<summary type="html">
<h3 id="静态代理的问题"><a href="#静态代理的问题" class="headerlink" title="静态代理的问题"></a>静态代理的问题</h3><p><img src="http://images.techhive.com/images/idge/imported/article/jvw/2000/11/jw-1110-proxy-100157716-orig.gif" alt="proxy pattern"></p>
<ol>
<li>紧耦合:代理类必须实现被代理对象的接口</li>
<li>硬编码:项目中大量充斥着类似**proxy这样的类</li>
<li>无法动态添加方法的拦截,会导致代码侵入</li>
</ol>
<p>如何解决问题?实际上也就是解决依赖的问题,代理类的创建不依赖于硬编码,想什么时候创建就什么时候创建,本质上也就是动态构建类和实例吧。JDK动态代理就是利用了Java的反射机制动态构建代理类和实例的。</p>
</summary>
<category term="java" scheme="http://paranoidq.github.io/categories/java/"/>
<category term="java" scheme="http://paranoidq.github.io/tags/java/"/>
<category term="proxy" scheme="http://paranoidq.github.io/tags/proxy/"/>
<category term="cglib" scheme="http://paranoidq.github.io/tags/cglib/"/>
</entry>
<entry>
<title>node.js安装express框架时出现command not found问题</title>
<link href="http://paranoidq.github.io/2016/06/07/nodejs-express-install/"/>
<id>http://paranoidq.github.io/2016/06/07/nodejs-express-install/</id>
<published>2016-06-07T12:23:45.000Z</published>
<updated>2016-06-07T12:37:47.000Z</updated>
<content type="html"><![CDATA[<p>在安装nodejs的web框架express时遇到的问题及解决方案。<br><a id="more"></a></p><p>安装时在文件夹下输入:<br><figure class="highlight cmake"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm <span class="keyword">install</span> -g express</div></pre></td></tr></table></figure></p><p>但是无法使用express命令,出现<code>express: command not found</code>。原因在于在express4.0中,cli需要单独安装才能使用,cli功能被包含在<code>express-generator</code> package中。</p><p>因此需要如下操作:<br><figure class="highlight cmake"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">npm <span class="keyword">install</span> -g express-generator</div></pre></td></tr></table></figure></p><p>参考: <a href="http://stackoverflow.com/questions/23002448/express-command-not-found" target="_blank" rel="external">http://stackoverflow.com/questions/23002448/express-command-not-found</a></p>]]></content>
<summary type="html">
<p>在安装nodejs的web框架express时遇到的问题及解决方案。<br>
</summary>
<category term="nodejs" scheme="http://paranoidq.github.io/categories/nodejs/"/>
<category term="碎片" scheme="http://paranoidq.github.io/tags/%E7%A2%8E%E7%89%87/"/>
<category term="nodejs" scheme="http://paranoidq.github.io/tags/nodejs/"/>
<category term="express" scheme="http://paranoidq.github.io/tags/express/"/>
</entry>
<entry>
<title>pyenv神器</title>
<link href="http://paranoidq.github.io/2016/06/04/pyenv%E7%A5%9E%E5%99%A8/"/>
<id>http://paranoidq.github.io/2016/06/04/pyenv神器/</id>
<published>2016-06-04T09:23:24.000Z</published>
<updated>2016-06-07T12:09:46.000Z</updated>
<content type="html"><![CDATA[<p>python版本管理神器: pyenv</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">brew </span><span class="keyword">install </span>pyenv</div></pre></td></tr></table></figure><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>将一下shell加入.bash_profile或.zshrc<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="comment"># set up pyenv</span></div><div class="line"><span class="built_in">export</span> PYENV_ROOT=/usr/<span class="built_in">local</span>/var/pyenv</div><div class="line"><span class="keyword">if</span> <span class="built_in">which</span> pyenv > /dev/null; <span class="keyword">then</span> <span class="built_in">eval</span> <span class="string">"<span class="variable">$(pyenv init -)</span>"</span>; <span class="keyword">fi</span></div></pre></td></tr></table></figure></p><a id="more"></a><h3 id="设置国内镜像"><a href="#设置国内镜像" class="headerlink" title="设置国内镜像"></a>设置国内镜像</h3><figure class="highlight routeros"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="comment"># mirrors</span></div><div class="line"><span class="builtin-name">export</span> <span class="attribute">PYTHON_BUILD_MIRROR_URL</span>=<span class="string">"http://pyenv.qiniudn.com/pythons/"</span></div></pre></td></tr></table></figure><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><figure class="highlight lsl"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line">pyenv versions</div><div class="line"></div><div class="line">pyenv version <span class="comment">// 正在使用的版本</span></div><div class="line"></div><div class="line">pyenv install --<span class="type">list</span></div><div class="line"></div><div class="line">pyenv install <span class="number">3.5</span><span class="number">.0</span></div><div class="line"></div><div class="line">pyenv uninstall <span class="number">3.5</span><span class="number">.0</span></div><div class="line"></div><div class="line">pyenv local <span class="number">3.5</span><span class="number">.0</span> <span class="comment">// 全局设置</span></div><div class="line"></div><div class="line">pyenv global <span class="number">3.5</span><span class="number">.0</span> <span class="comment">// 本地目录设置</span></div><div class="line"></div><div class="line">pyenv local system <span class="comment">// 直接使用系统自带版本</span></div></pre></td></tr></table></figure><h3 id="PS"><a href="#PS" class="headerlink" title="PS"></a>PS</h3><ol><li>如何删除已经安装的python版本: <a href="http://stackoverflow.com/questions/22774529/what-is-the-safest-way-to-removing-python-framework-files-that-are-located-in-di" target="_blank" rel="external">http://stackoverflow.com/questions/22774529/what-is-the-safest-way-to-removing-python-framework-files-that-are-located-in-di</a></li><li>一般而言,系统库放/System/Library,而应用程序依赖的放/Library,所以,苹果自带的python放在前者,而用户自己装的python(比如官方网站下载的)会自动装在后者。(homebrew安装的就在后者)</li></ol><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://github.com/yyuu/pyenv" target="_blank" rel="external">https://github.com/yyuu/pyenv</a><br><a href="http://v2in.com/pyenv-installation-and-usage.html" target="_blank" rel="external">http://v2in.com/pyenv-installation-and-usage.html</a></p>]]></content>
<summary type="html">
<p>python版本管理神器: pyenv</p>
<h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">brew </span><span class="keyword">install </span>pyenv</div></pre></td></tr></table></figure>
<h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>将一下shell加入.bash_profile或.zshrc<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="comment"># set up pyenv</span></div><div class="line"><span class="built_in">export</span> PYENV_ROOT=/usr/<span class="built_in">local</span>/var/pyenv</div><div class="line"><span class="keyword">if</span> <span class="built_in">which</span> pyenv &gt; /dev/null; <span class="keyword">then</span> <span class="built_in">eval</span> <span class="string">"<span class="variable">$(pyenv init -)</span>"</span>; <span class="keyword">fi</span></div></pre></td></tr></table></figure></p>
</summary>
<category term="python" scheme="http://paranoidq.github.io/categories/python/"/>
<category term="碎片" scheme="http://paranoidq.github.io/tags/%E7%A2%8E%E7%89%87/"/>
<category term="python" scheme="http://paranoidq.github.io/tags/python/"/>
<category term="pyenv" scheme="http://paranoidq.github.io/tags/pyenv/"/>
</entry>
</feed>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。