代码拉取完成,页面将自动刷新
<?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://zhengweishan.oschina.io/"/>
<updated>2017-03-27T10:19:12.313Z</updated>
<id>http://zhengweishan.oschina.io/</id>
<author>
<name>郑伟山</name>
<email>wesley5201314@live.com</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>java并发包里的CountDownLatch的用法</title>
<link href="http://zhengweishan.oschina.io/2017/03/27/CountDownLatch/"/>
<id>http://zhengweishan.oschina.io/2017/03/27/CountDownLatch/</id>
<published>2017-03-26T16:00:00.000Z</published>
<updated>2017-03-27T10:19:12.313Z</updated>
<content type="html"><![CDATA[<h2 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch:"></a>CountDownLatch:</h2><p>官方的解释为:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。</p>
<p>我们现在就把它理解为倒数计数器,什么是倒数计数器呢,通俗的理解就是这个计数器事先有一个初始计数,在这个计数减到0之前,所有的线程等待。</p>
<p>最近公司有出去旅游,一个业务场景浮现在脑海:部门一共十个人出去旅游,必须10个人上车之后大巴才能开车,下面就来模拟这个上车的过程。<br><a id="more"></a></p>
<h2 id="模拟上车过程:"><a href="#模拟上车过程:" class="headerlink" title="模拟上车过程:"></a>模拟上车过程:</h2><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></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> <span class="keyword">implements</span> <span class="title">Runnable</span></span>{</div><div class="line"> </div><div class="line"> </div><div class="line"> <span class="keyword">private</span> Car car;</div><div class="line"> </div><div class="line"> <span class="keyword">private</span> String name;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Person</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Person</span><span class="params">(Car car,String name)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.car = car;</div><div class="line"> <span class="keyword">this</span>.name = name;</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> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// TODO Auto-generated method stub</span></div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> TimeUnit.SECONDS.sleep((<span class="keyword">long</span>)(Math.random()*<span class="number">10</span>));</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="comment">// TODO Auto-generated catch block</span></div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> car.getton(name);</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<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><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">import</span> java.util.concurrent.CountDownLatch;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Car</span> <span class="keyword">implements</span> <span class="title">Runnable</span></span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> CountDownLatch countDownLatch;</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Car</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>();</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Car</span><span class="params">(<span class="keyword">int</span> count)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.countDownLatch = <span class="keyword">new</span> CountDownLatch(count);</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> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</div><div class="line"> System.out.println(<span class="string">"一共需要上车屌丝数:"</span>+countDownLatch.getCount());</div><div class="line"> </div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> countDownLatch.await();</div><div class="line"> System.out.println(<span class="string">"屌丝全部上车了--->>>老司机准备开车了。。。。"</span>);</div><div class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</div><div class="line"> <span class="comment">// TODO Auto-generated catch block</span></div><div class="line"> e.printStackTrace();</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">void</span> <span class="title">getton</span><span class="params">(String name)</span> </span>{</div><div class="line"> System.err.println(name+<span class="string">"上车"</span>);</div><div class="line"> countDownLatch.countDown();</div><div class="line"> System.err.println(<span class="string">"还剩下"</span>+countDownLatch.getCount()+<span class="string">"个屌丝没有上车"</span>);</div><div class="line"> }</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure>
<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></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"></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"> </div><div class="line"> Car c = <span class="keyword">new</span> Car(<span class="number">10</span>);</div><div class="line"> Thread thread = <span class="keyword">new</span> Thread(c);</div><div class="line"> thread.start();</div><div class="line"> </div><div class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i<=<span class="number">10</span>;i++){</div><div class="line"> Person p = <span class="keyword">new</span> Person(c, <span class="string">"屌丝"</span>+i);</div><div class="line"> Thread t = <span class="keyword">new</span> Thread(p);</div><div class="line"> t.start();</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>执行结果:</p>
<p><img src="http://i.imgur.com/RVZxszU.png" alt=""></p>
<h2 id="原理分析:"><a href="#原理分析:" class="headerlink" title="原理分析:"></a>原理分析:</h2><p>当创建CountDownLatch对象时,对象使用构造函数来初始化内部计数器。</p>
<pre><code>CountDownLatch类只提供了一个构造器:public CountDownLatch(intcount) { };//参数count为计数值
</code></pre><p>每次调用countDown()方法,对象内部计数器减一。当内部计数器达到0时,CountDownLatch对象唤醒全部使用await()方法睡眠的线程。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结:"></a>总结:</h2><p>CountDownLatch对象的内部计数器的值初始化之后是不能修改的,唯一可以修改的方式就是调用countDown()方法,当计数器为0时,await()方法会立即返回,任何方法的调用都是无效的,如果想再次使用同步,必须重新初始化。</p>
]]></content>
<summary type="html">
<h2 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch:"></a>CountDownLatch:</h2><p>官方的解释为:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。</p>
<p>我们现在就把它理解为倒数计数器,什么是倒数计数器呢,通俗的理解就是这个计数器事先有一个初始计数,在这个计数减到0之前,所有的线程等待。</p>
<p>最近公司有出去旅游,一个业务场景浮现在脑海:部门一共十个人出去旅游,必须10个人上车之后大巴才能开车,下面就来模拟这个上车的过程。<br>
</summary>
<category term="并发" scheme="http://zhengweishan.oschina.io/categories/%E5%B9%B6%E5%8F%91/"/>
<category term="并发,CountDownLatch" scheme="http://zhengweishan.oschina.io/tags/%E5%B9%B6%E5%8F%91%EF%BC%8CCountDownLatch/"/>
</entry>
<entry>
<title>spring boot + mybatis + quartz + druid + swagger2</title>
<link href="http://zhengweishan.oschina.io/2017/03/24/spring-boot-demo/"/>
<id>http://zhengweishan.oschina.io/2017/03/24/spring-boot-demo/</id>
<published>2017-03-23T16:00:00.000Z</published>
<updated>2017-03-27T10:28:17.417Z</updated>
<content type="html"><![CDATA[<p>spring boot + mybatis + quartz + druid + Swagger2 演示demo</p>
<p>说明:主要演示如何整合,简单的任务调用。</p>
<p>环境准备:</p>
<ul>
<li>jdk:1.7</li>
<li>maven:3.2.3</li>
<li>开发工具:IDEA</li>
</ul>
<p>源码地址:</p>
<p>gitosc: <a href="https://git.oschina.net/zhengweishan/spring-boot_demo" target="_blank" rel="external">https://git.oschina.net/zhengweishan/spring-boot_demo</a></p>
<p>github: <a href="https://github.com/wesley5201314/spring-boot-demo" target="_blank" rel="external">https://github.com/wesley5201314/spring-boot-demo</a></p>
<p>项目结构:</p>
<p><img src="http://i.imgur.com/LNzimT3.png" alt=""></p>
<a id="more"></a>
<p><strong>备注:</strong><br>每个包的作用大家一看就明白了,这里主要说明下application.properties:这里主要配置针对于不同的环境用那个配置文件,这里我只提供了开发,测试的环境属性文件。配置如下:</p>
<pre><code>spring.profiles.active = dev
</code></pre><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></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="comment">//启动入口</span></div><div class="line"><span class="meta">@SpringBootApplication</span></div><div class="line"><span class="meta">@ServletComponentScan</span> <span class="comment">//扫描Servlet</span></div><div class="line"><span class="meta">@MapperScan</span>(<span class="string">"com.springboot.demo.dao"</span>) <span class="comment">//扫描dao</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span> </span></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"> SpringApplication.run(App.class, args);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>启动之后访问:<br>durid : <a href="http://localhost:8080/druid/index.html" target="_blank" rel="external">http://localhost:8080/druid/index.html</a> 如图:</p>
<p><img src="http://i.imgur.com/qD8hyb4.png" alt=""></p>
<p>登录之后:</p>
<p><img src="http://i.imgur.com/Eb4k89i.png" alt=""></p>
<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></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="comment">//过滤资源</span></div><div class="line"><span class="meta">@WebFilter</span>(filterName=<span class="string">"druidWebStatFilter"</span>,urlPatterns=<span class="string">"/*"</span>,</div><div class="line"> initParams={</div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"exclusions"</span>,value=<span class="string">"*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"</span>)<span class="comment">// 忽略资源</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">DruidStatFilter</span> <span class="keyword">extends</span> <span class="title">WebStatFilter</span> <span class="keyword">implements</span> <span class="title">Serializable</span></span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">//配置访问路径,用户名,密码</span></div><div class="line"><span class="meta">@WebServlet</span>(urlPatterns = <span class="string">"/druid/*"</span>, </div><div class="line"> initParams={</div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"allow"</span>,value=<span class="string">""</span>),<span class="comment">// IP白名单 (没有配置或者为空,则允许所有访问)</span></div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"deny"</span>,value=<span class="string">""</span>),<span class="comment">// IP黑名单 (存在共同时,deny优先于allow)</span></div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"loginUsername"</span>,value=<span class="string">"root"</span>),<span class="comment">// 用户名</span></div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"loginPassword"</span>,value=<span class="string">"root"</span>),<span class="comment">// 密码</span></div><div class="line"> <span class="meta">@WebInitParam</span>(name=<span class="string">"resetEnable"</span>,value=<span class="string">"false"</span>)<span class="comment">// 禁用HTML页面上的“Reset All”功能</span></div><div class="line"> })</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DruidStatViewServlet</span> <span class="keyword">extends</span> <span class="title">StatViewServlet</span> <span class="keyword">implements</span> <span class="title">Serializable</span></span>{</div><div class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>swagger2 : <a href="http://localhost:8080/swagger-ui.html" target="_blank" rel="external">http://localhost:8080/swagger-ui.html</a> 如图:</p>
<p><img src="http://i.imgur.com/MkwOuR0.png" alt=""></p>
<p><img src="http://i.imgur.com/8U2WVwG.png" alt=""></p>
<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"></div><div class="line"><span class="comment">//SwaggerConfig</span></div><div class="line"><span class="meta">@Configuration</span></div><div class="line"><span class="meta">@EnableSwagger</span>2</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SwaggerConfig</span> </span>{</div><div class="line"> <span class="comment">/**</span></div><div class="line"> * 可以定义多个组,比如本类中定义把test和demo区分开了 (访问页面就可以看到效果了)</div><div class="line"> * </div><div class="line"> */</div><div class="line"> <span class="meta">@Bean</span></div><div class="line"> <span class="function"><span class="keyword">public</span> Docket <span class="title">testApi</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Docket(DocumentationType.SWAGGER_2)</div><div class="line"> .apiInfo(apiInfo())</div><div class="line"> .select()</div><div class="line"> .apis(RequestHandlerSelectors</div><div class="line"> .basePackage(<span class="string">"com.springboot.demo.controller"</span>))</div><div class="line"> .paths(PathSelectors.any()).build();</div><div class="line"> }</div><div class="line"> </div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">private</span> ApiInfo <span class="title">apiInfo</span><span class="params">()</span> </span>{</div><div class="line"> ApiInfo apiInfo = <span class="keyword">new</span> ApiInfo(<span class="string">"SpringBootDemo"</span>, <span class="comment">// 大标题</span></div><div class="line"> <span class="string">"Spring boot + swagger + mybatis + druid"</span>, <span class="comment">// 小标题</span></div><div class="line"> <span class="string">"1.0"</span>, <span class="comment">// 版本</span></div><div class="line"> <span class="string">"spring-boot-demo"</span>,</div><div class="line"> <span class="string">"zhengweishan"</span>, <span class="comment">// 作者</span></div><div class="line"> <span class="string">"blog"</span>, <span class="comment">// 链接显示文字</span></div><div class="line"> <span class="string">"http://zhengweishan.oschina.io/"</span><span class="comment">// 网站链接</span></div><div class="line"> );</div><div class="line"> <span class="keyword">return</span> apiInfo;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>添加jsp支持</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></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="keyword">import</span> com.springboot.demo.App;</div><div class="line"><span class="keyword">import</span> org.springframework.boot.builder.SpringApplicationBuilder;</div><div class="line"><span class="keyword">import</span> org.springframework.boot.context.web.SpringBootServletInitializer;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * Created by wesley on 2017-03-24.</div><div class="line"> * spring boot jsp支持</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JspInitContext</span> <span class="keyword">extends</span> <span class="title">SpringBootServletInitializer</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">protected</span> SpringApplicationBuilder <span class="title">configure</span><span class="params">(SpringApplicationBuilder application)</span> </span>{</div><div class="line"> <span class="keyword">return</span> application.sources(App.class);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>配置文件配置添加如下:</p>
<pre><code>#jsp视图设置
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
</code></pre>]]></content>
<summary type="html">
<p>spring boot + mybatis + quartz + druid + Swagger2 演示demo</p>
<p>说明:主要演示如何整合,简单的任务调用。</p>
<p>环境准备:</p>
<ul>
<li>jdk:1.7</li>
<li>maven:3.2.3</li>
<li>开发工具:IDEA</li>
</ul>
<p>源码地址:</p>
<p>gitosc: <a href="https://git.oschina.net/zhengweishan/spring-boot_demo">https://git.oschina.net/zhengweishan/spring-boot_demo</a></p>
<p>github: <a href="https://github.com/wesley5201314/spring-boot-demo">https://github.com/wesley5201314/spring-boot-demo</a></p>
<p>项目结构:</p>
<p><img src="http://i.imgur.com/LNzimT3.png" alt=""></p>
</summary>
<category term="SpringBoot" scheme="http://zhengweishan.oschina.io/categories/SpringBoot/"/>
<category term="SpingBoot" scheme="http://zhengweishan.oschina.io/tags/SpingBoot/"/>
</entry>
<entry>
<title>分布式rpc框架</title>
<link href="http://zhengweishan.oschina.io/2017/03/23/boy-rpc-framework/"/>
<id>http://zhengweishan.oschina.io/2017/03/23/boy-rpc-framework/</id>
<published>2017-03-22T16:00:00.000Z</published>
<updated>2017-03-23T02:53:29.037Z</updated>
<content type="html"><![CDATA[<h2 id="什么是RPC"><a href="#什么是RPC" class="headerlink" title="什么是RPC"></a>什么是RPC</h2><p>RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。它可以有不同的实现方式:如RMI(远程方法调用)、Hessian、Http invoker等。另外,RPC是与语言无关的。</p>
<p>RPC示意图(来源网络)<br><img src="http://i.imgur.com/NxofKwx.png" alt=""></p>
<a id="more"></a>
<h2 id="如何开发一个rpc框架"><a href="#如何开发一个rpc框架" class="headerlink" title="如何开发一个rpc框架"></a>如何开发一个rpc框架</h2><p>首先我们要考虑我们这个rpc框架需要具备哪些东西,比如:用什么作为底层协议,是否支持高并发,是否支持高效的序列化方式,能否同时具备服务的发现与注册。</p>
<p>RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。会两方面会直接影响 RPC 的性能,一是传输方式,二是序列化。</p>
<p>众所周知,TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,在数据传输方面,越底层越快,因此,在一般情况下,TCP 一定比 HTTP 快。就序列化而言,Java 提供了默认的序列化方式,但在高并发的情况下,这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能。</p>
<p>为了支持高并发,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持,用 Java 实现 NIO 并不是遥不可及的事情,只是需要我们熟悉 NIO 的技术细节。</p>
<p>我们需要将服务部署在分布式环境下的不同节点上,通过服务注册的方式,让客户端来自动发现当前可用的服务,并调用这些服务。这需要一种服务注册表(Service Registry)的组件,让它来注册分布式环境下所有的服务地址(包括:主机名与端口号)。</p>
<p>应用、服务、服务注册表之间的关系见下图:</p>
<p><img src="http://i.imgur.com/QeMu3tX.png" alt=""></p>
<p>每台 Server 上可发布多个 Service,这些 Service 共用一个 host 与 port,在分布式环境下会提供 Server 共同对外提供 Service。此外,为防止 Service Registry 出现单点故障,因此需要将其搭建为集群环境。</p>
<p>综合考虑,我们可以选用以下技术作为我们开发rpc框架的技术选型:</p>
<ol>
<li>Spring:它是最强大的依赖注入框架,也是业界的权威标准。</li>
<li>Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。</li>
<li>Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。</li>
<li>ZooKeeper/redis:提供服务注册与发现功能,开发分布式系统的必备选择,同时它也具备天生的集群能力。</li>
</ol>
<h2 id="开发准备"><a href="#开发准备" class="headerlink" title="开发准备"></a>开发准备</h2><p>项目整体构思如图:</p>
<p><img src="http://i.imgur.com/2Oj5TyE.png" alt=""></p>
<p>各部分的作用:</p>
<p><img src="http://i.imgur.com/DkGd4Cy.png" alt=""></p>
<h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><p>开发之前我们先看下整个服务的请求流程:<br><img src="http://i.imgur.com/g1txCNQ.png" alt=""></p>
<p>这里用redis作为注册中心,来演示一个开发过程。</p>
<h3 id="编写服务接口:"><a href="#编写服务接口:" class="headerlink" title="编写服务接口:"></a>编写服务接口:</h3><pre><code>public interface HelloRedisService {
String sayHello(String str);
}
</code></pre><p>将该接口放在独立的客户端 jar 包中,以供应用使用。</p>
<h3 id="编写服务接口实现类:"><a href="#编写服务接口实现类:" class="headerlink" title="编写服务接口实现类:"></a>编写服务接口实现类:</h3><pre><code>@BoyRpcService(HelloRedisService.class)
public class HelloRedisServiceImpl implements HelloRedisService {
@Override
public String sayHello(String str) {
return "redis say:"+str+",Hello!";
}
}
</code></pre><p>BoyRpcService注解定义在服务接口的实现类上,需要对该实现类指定远程接口,因为实现类可能会实现多个接口,一定要告诉框架哪个才是远程接口。</p>
<pre><code>@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface BoyRpcService {
/**
* 服务接口类
*/
Class<?> value();
/**
* 服务版本号
*/
String version() default "";
}
</code></pre><p>该注解具备 Spring 的Component注解的特性,可被 Spring 扫描。</p>
<p>该实现类放在服务端 jar 包中,该 jar 包还提供了一些服务端的配置文件与启动服务的引导程序。</p>
<h3 id="配置服务端:"><a href="#配置服务端:" class="headerlink" title="配置服务端:"></a>配置服务端:</h3><p>服务端 Spring 配置文件名为spring-by-redis.xml,内容如下:</p>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.boy.rpc.framework.sample.server"/>
<context:property-placeholder location="classpath:rpc-redis.properties"/>
<bean id="redisConfig" class="com.boy.rpc.framework.registry.redis.bean.RedisConfig">
<property name="redisAddress" value="${rpc.registry_address}"/>
<property name="redisPassword" value="${rpc.registry_password}"/>
<property name="redisPort" value="${rpc.registry_port}"/>
</bean>
<bean id="serviceRegistry" class="com.boy.rpc.framework.registry.redis.RedisServiceRegistry">
<constructor-arg name="redisConfig" ref ="redisConfig"/>
</bean>
<bean id="rpcServer" class="com.boy.rpc.framework.server.BoyRpcServer">
<constructor-arg name="serviceAddress" value="${rpc.redis_service_address}"/>
<constructor-arg name="serviceRegistry" ref="serviceRegistry"/>
</bean>
</beans>
</code></pre><p>具体的配置参数在rpc-redis.properties文件中,内容如下:</p>
<pre><code>#服务部署地址#
rpc.redis_service_address = 127.0.0.1:8081
#注册中心地址#
rpc.registry_address = 127.0.0.1
#注册中心端口#
rpc.registry_port = 6379
#注册中心密码#
rpc.registry_password = 25362e3e047b413d:Redis123
</code></pre><p>以上配置表明:连接本地的 redis 服务器,并在 8081 端口上发布 RPC 服务。</p>
<h2 id="启动服务器并发布服务:"><a href="#启动服务器并发布服务:" class="headerlink" title="启动服务器并发布服务:"></a>启动服务器并发布服务:</h2><pre><code>public class RedisRpcBootstrap {
private static final Logger logger = LoggerFactory.getLogger(RedisRpcBootstrap.class);
public static void main(String[] args) {
logger.debug("redis rpc start server");
new ClassPathXmlApplicationContext("spring-by-redis.xml");
}
}
</code></pre><h2 id="实现服务注册"><a href="#实现服务注册" class="headerlink" title="实现服务注册:"></a>实现服务注册:</h2><pre><code>public class RedisServiceRegistry implements ServiceRegistry {
private static final Logger logger = LoggerFactory.getLogger(RedisServiceRegistry.class);
private RedisClient redisClient = null;
public RedisServiceRegistry(RedisConfig redisConfig){
redisClient = new RedisClient(redisConfig);
}
@Override
public void register(String serviceName, String serviceAddress) {
logger.debug("redis register start!");
if(redisClient.existsKey(serviceName)){
List<String> oldList = (List<String>) redisClient.getObject(serviceName);
oldList.add(serviceAddress);
logger.debug("service exits create service address : {}", oldList);
redisClient.setObject(serviceName,oldList);
}else{
List<String> addressList = new ArrayList<>();
addressList.add(serviceAddress);
logger.debug("service not exits create service address : {}", addressList);
redisClient.setObject(serviceName,addressList);
}
logger.debug("redis register end!");
}
}
</code></pre><h2 id="实现-RPC-服务器"><a href="#实现-RPC-服务器" class="headerlink" title="实现 RPC 服务器:"></a>实现 RPC 服务器:</h2><p>使用 Netty 可实现一个支持 NIO 的 RPC 服务器,需要使用ServiceRegistry注册服务地址,代码如下</p>
<pre><code>public class BoyRpcServer implements ApplicationContextAware, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(BoyRpcServer.class);
private String serviceAddress;
private ServiceRegistry serviceRegistry;
/**
* 存放 服务名 与 服务对象 之间的映射关系
*/
private Map<String, Object> handlerMap = new HashMap<>();
public BoyRpcServer(String serviceAddress) {
this.serviceAddress = serviceAddress;
}
public BoyRpcServer(String serviceAddress, ServiceRegistry serviceRegistry) {
this.serviceAddress = serviceAddress;
this.serviceRegistry = serviceRegistry;
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
// 扫描带有 RpcService 注解的类并初始化 handlerMap 对象
Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(BoyRpcService.class);
if (MapUtils.isNotEmpty(serviceBeanMap)) {
for (Object serviceBean : serviceBeanMap.values()) {
BoyRpcService rpcService = serviceBean.getClass().getAnnotation(BoyRpcService.class);
String serviceName = rpcService.value().getName();
String serviceVersion = rpcService.version();
if (StringUtil.isNotEmpty(serviceVersion)) {
serviceName += "-" + serviceVersion;
}
handlerMap.put(serviceName, serviceBean);
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建并初始化 Netty 服务端 Bootstrap 对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new BoyRpcDecoder(BoyRpcRequest.class)); // 解码 RPC 请求
pipeline.addLast(new BoyRpcEncoder(BoyRpcResponse.class)); // 编码 RPC 响应
pipeline.addLast(new BoyRpcServerHandler(handlerMap)); // 处理 RPC 请求
}
});
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// 获取 RPC 服务器的 IP 地址与端口号
String[] addressArray = StringUtil.split(serviceAddress, ":");
String ip = addressArray[0];
int port = Integer.parseInt(addressArray[1]);
// 启动 RPC 服务器
ChannelFuture future = bootstrap.bind(ip, port).sync();
// 注册 RPC 服务地址
if (serviceRegistry != null) {
for (String interfaceName : handlerMap.keySet()) {
serviceRegistry.register(interfaceName, serviceAddress);
logger.debug("register service: {} => {}", interfaceName, serviceAddress);
}
}
logger.debug("server started on port {}", port);
// 关闭 RPC 服务器
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
</code></pre><p>以上代码中,有两个重要的 POJO 需要描述一下,它们分别是BoyRpcRequest与BoyRpcResponse。使用RpcRequest封装 RPC 请求,使用RpcResponse封装 RPC 响应.使用BoyRpcDecoder提供 RPC 解码,只需扩展 Netty 的ByteToMessageDecoder抽象类的decode方法即可,使用BoyRpcEncoder提供 RPC 编码,只需扩展 Netty 的MessageToByteEncoder抽象类的encode方法即可.使用RpcHandler中处理 RPC 请求,只需扩展 Netty 的SimpleChannelInboundHandler抽象类即可,具体代码才看源码:</p>
<p><a href="https://git.oschina.net/zhengweishan/boy-rpc-framework" target="_blank" rel="external">https://git.oschina.net/zhengweishan/boy-rpc-framework</a></p>
<h2 id="配置客户端:"><a href="#配置客户端:" class="headerlink" title="配置客户端:"></a>配置客户端:</h2><p>同样使用 Spring 配置文件来配置 RPC 客户端,spring-by-redis.xml代码如下:</p>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:rpc-redis.properties"/>
<bean id="redisConfig" class="com.boy.rpc.framework.registry.redis.bean.RedisConfig">
<property name="redisAddress" value="${rpc.registry_address}"/>
<property name="redisPassword" value="${rpc.registry_password}"/>
<property name="redisPort" value="${rpc.registry_port}"/>
</bean>
<bean id="serviceDiscovery" class="com.boy.rpc.framework.registry.redis.RedisServiceDiscovery">
<constructor-arg name="redisConfig" ref="redisConfig"/>
</bean>
<bean id="rpcProxy" class="com.boy.rpc.framework.client.BoyRpcProxy">
<constructor-arg name="serviceDiscovery" ref="serviceDiscovery"/>
</bean>
</beans>
</code></pre><p>其中rpc-redis.properties提供了具体的配置:</p>
<pre><code>#redis注册中心地址#
rpc.registry_address = 127.0.0.1
#redis注册中心端口#
rpc.registry_port = 6379
#redis注册中心密码#
rpc.registry_password = 25362e3e047b413d:Redis123
</code></pre><h2 id="实现服务发现"><a href="#实现服务发现" class="headerlink" title="实现服务发现:"></a>实现服务发现:</h2><p>同样使用 redis 实现服务发现功能,见如下代码:</p>
<pre><code>public class RedisServiceDiscovery implements ServiceDiscovery {
private static final Logger logger = LoggerFactory.getLogger(RedisServiceRegistry.class);
private RedisClient redisClient = null;
public RedisServiceDiscovery(RedisConfig redisConfig) {
redisClient = new RedisClient(redisConfig);
}
@Override
public String discover(String serviceName) {
String address = null;
if(redisClient.existsKey(serviceName)){
List<String> list = (List<String>) redisClient.getObject(serviceName);
int size = list.size();
if(size == 1){
//只有一个地址
address = list.get(0);
logger.debug("get only address : {}", address);
} else {
// 若存在多个地址,则随机获取一个地址
address = list.get(ThreadLocalRandom.current().nextInt(size));
logger.debug("get random address : {}", address);
}
}
return address;
}
}
</code></pre><h2 id="实现-RPC-代理"><a href="#实现-RPC-代理" class="headerlink" title="实现 RPC 代理:"></a>实现 RPC 代理:</h2><p>这里使用 Java 提供的动态代理技术实现 RPC 代理(当然也可以使用 CGLib 来实现),具体代码如下:</p>
<pre><code>public class BoyRpcProxy {
private static final Logger logger = LoggerFactory.getLogger(BoyRpcProxy.class);
private String serviceAddress;
private ServiceDiscovery serviceDiscovery;
public BoyRpcProxy(String serviceAddress) {
this.serviceAddress = serviceAddress;
}
public BoyRpcProxy(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@SuppressWarnings("unchecked")
public <T> T create(final Class<?> interfaceClass) {
return create(interfaceClass, "");
}
@SuppressWarnings("unchecked")
public <T> T create(final Class<?> interfaceClass, final String serviceVersion) {
// 创建动态代理对象
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建 RPC 请求对象并设置请求属性
BoyRpcRequest request = new BoyRpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setInterfaceName(method.getDeclaringClass().getName());
request.setServiceVersion(serviceVersion);
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
// 获取 RPC 服务地址
if (serviceDiscovery != null) {
String serviceName = interfaceClass.getName();
if (StringUtil.isNotEmpty(serviceVersion)) {
serviceName += "-" + serviceVersion;
}
serviceAddress = serviceDiscovery.discover(serviceName);
logger.debug("discover service: {} => {}", serviceName, serviceAddress);
}
if (StringUtil.isEmpty(serviceAddress)) {
throw new RuntimeException("server address is empty");
}
// 从 RPC 服务地址中解析主机名与端口号
String[] array = StringUtil.split(serviceAddress, ":");
String host = array[0];
int port = Integer.parseInt(array[1]);
// 创建 RPC 客户端对象并发送 RPC 请求
BoyRpcClient client = new BoyRpcClient(host, port);
long time = System.currentTimeMillis();
BoyRpcResponse response = client.send(request);
logger.debug("time: {}ms", System.currentTimeMillis() - time);
if (response == null) {
throw new RuntimeException("response is null");
}
// 返回 RPC 响应结果
if (response.hasException()) {
throw response.getException();
} else {
return response.getResult();
}
}
}
);
}
}
</code></pre><h2 id="发送-RPC-请求"><a href="#发送-RPC-请求" class="headerlink" title="发送 RPC 请求:"></a>发送 RPC 请求:</h2><p>使用 main 方法结合 Spring 编写一个测试,代码如下:</p>
<pre><code>public class RedisHelloClient {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-by-redis.xml");
BoyRpcProxy rpcProxy = context.getBean(BoyRpcProxy.class);
HelloRedisService helloService = rpcProxy.create(HelloRedisService.class);
String result = helloService.sayHello("World");
System.out.println(result);
System.exit(0);
}
}
</code></pre><p>如果不出意外的话,您应该会看到:redis say:World Hello!</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文通过 Spring + Netty + Protostuff + ZooKeeper/Redis 实现了一个轻量级 RPC 框架,使用 Spring 提供依赖注入与参数配置,使用 Netty 实现 NIO 方式的数据传输,使用 Protostuff 实现对象序列化,使用 ZooKeeper/Redis 实现服务注册与发现。使用该框架,可将服务部署到分布式环境中的任意节点上,客户端通过远程接口来调用服务端的具体实现,让服务端与客户端的开发完全分离,为实现大规模分布式应用提供了基础支持。</p>
]]></content>
<summary type="html">
<h2 id="什么是RPC"><a href="#什么是RPC" class="headerlink" title="什么是RPC"></a>什么是RPC</h2><p>RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。它可以有不同的实现方式:如RMI(远程方法调用)、Hessian、Http invoker等。另外,RPC是与语言无关的。</p>
<p>RPC示意图(来源网络)<br><img src="http://i.imgur.com/NxofKwx.png" alt=""></p>
</summary>
<category term="rpc" scheme="http://zhengweishan.oschina.io/categories/rpc/"/>
<category term="rpc 分布式" scheme="http://zhengweishan.oschina.io/tags/rpc-%E5%88%86%E5%B8%83%E5%BC%8F/"/>
</entry>
<entry>
<title>Tomcat源码学习(六)--Tomcat_7.0.70 生命周期管理</title>
<link href="http://zhengweishan.oschina.io/2017/02/10/%EF%BC%88%E5%85%AD%EF%BC%89Tomcat_7.0.70%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AE%A1%E7%90%86/"/>
<id>http://zhengweishan.oschina.io/2017/02/10/(六)Tomcat_7.0.70生命周期管理/</id>
<published>2017-02-09T16:00:00.000Z</published>
<updated>2017-03-01T08:53:36.617Z</updated>
<content type="html"><![CDATA[<p><strong>想必大家都知道,从server.xml文件解析出来的各个对象都是容器,比如:Server、Service、Connector等。这些容器都具有新建、初始化完成、启动、停止、失败、销毁等状态。Tomcat的实现机制是通过实现org.apache.catalina.Lifecycle接口来管理。</strong></p>
<h2 id="Tomcat–Lifecycle接口"><a href="#Tomcat–Lifecycle接口" class="headerlink" title="Tomcat–Lifecycle接口"></a>Tomcat–Lifecycle接口</h2><p>定义了容器生命周期、容器状态转换及容器状态迁移事件的监听器注册和移除等主要接口。代码清单:</p>
<pre><code> public interface Lifecycle {
public static final String BEFORE_INIT_EVENT = "before_init";
public static final String AFTER_INIT_EVENT = "after_init";
public static final String START_EVENT = "start";
public static final String BEFORE_START_EVENT = "before_start";
public static final String AFTER_START_EVENT = "after_start";
public static final String STOP_EVENT = "stop";
public static final String BEFORE_STOP_EVENT = "before_stop";
public static final String AFTER_STOP_EVENT = "after_stop";
public static final String AFTER_DESTROY_EVENT = "after_destroy";
public static final String BEFORE_DESTROY_EVENT = "before_destroy";
public static final String PERIODIC_EVENT = "periodic";
public static final String CONFIGURE_START_EVENT = "configure_start";
public static final String CONFIGURE_STOP_EVENT = "configure_stop";
public void addLifecycleListener(LifecycleListener listener);
public LifecycleListener[] findLifecycleListeners();
public void removeLifecycleListener(LifecycleListener listener);
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
public interface SingleUse {
}
}
</code></pre><p>其中,最重要的方法时start和stop方法。父组件通过这两个方法来启动/关闭该组件。addLifecycleListener,findLifecycleListeners,removeLifecycleListener三个方法用于向组件注册/查找/删除监听器。当事件发生时,会触发监听器。接口中还定义了相关事件。</p>
<a id="more"></a>
<p>下面从一幅图来了解Tomcat涉及生命周期管理的主要类:</p>
<p><img src="http://i.imgur.com/ZIi07NG.jpg" alt=""></p>
<ul>
<li>Lifecycle:定义了容器生命周期、容器状态转换及容器状态迁移事件的监听器注册和移除等主要接口;</li>
<li>LifecycleBase:作为Lifecycle接口的抽象实现类,运用抽象模板模式将所有容器的生命周期及状态转换衔接起来,此外还提供了生成LifecycleEvent事件的接口;</li>
<li>LifecycleSupport:提供有关LifecycleEvent事件的监听器注册、移除,并且使用经典的监听器模式,实现事件生成后触打监听器的实现;</li>
<li>MBeanRegistration:Java jmx框架提供的注册MBean的接口,引入此接口是为了便于使用JMX提供的管理功能;</li>
<li>LifecycleMBeanBase:Tomcat提供的对MBeanRegistration的抽象实现类,运用抽象模板模式将所有容器统一注册到JMX;</li>
</ul>
<p>从上图可以看出ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都继承了LifecycleMBeanBase,因此这些容器都具有了同样的生命周期并可以通过JMX进行管理。</p>
<h2 id="什么是JMX"><a href="#什么是JMX" class="headerlink" title="什么是JMX?"></a>什么是JMX?</h2><p>JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。</p>
<p><img src="http://i.imgur.com/nHRPZr2.jpg" alt=""></p>
<p>JMX体系结构分为以下四个层次:</p>
<p><strong>设备层</strong></p>
<p>设备层(Instrumentation Level):主要定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册。该层还定义了通知机制以及一些辅助元数据类。</p>
<p><strong>代理层</strong></p>
<p>代理层(Agent Level):主要定义了各种服务以及通信模型。该层的核心是一个MBean服务器,所有的管理构件都需要向它注册,才能被管理。注册在MBean服务器上管理构件并不直接和远程应用程序进行通信,它们通过协议适配器和连接器进行通信。而协议适配器和连接器也以管理构件的形式向MBean服务器注册才能提供相应的服务。</p>
<p><strong>分布服务层</strong></p>
<p>分布服务层(Distributed Service Level):主要定义了能对代理层进行操作的管理接口和构件,这样管理者就可以操作代理。然而,当前的JMX规范并没有给出这一层的具体规范。</p>
<p><strong>附加管理协议API</strong></p>
<p>定义的API主要用来支持当前已经存在的网络管理协议,如SNMP、TMN、CIM/WBEM等。</p>
<h2 id="Tomcat–事件、监听"><a href="#Tomcat–事件、监听" class="headerlink" title="Tomcat–事件、监听"></a>Tomcat–事件、监听</h2><p>每个容器由于继承自LifecycleBase,当容器状态发生变化时,都会调用fireLifecycleEvent方法,生成LifecycleEvent,并且交由此容器的事件监听器处理。</p>
<p>LifecycleBase的fireLifecycleEvent方法的实现:</p>
<pre><code> protected void fireLifecycleEvent(String type, Object data) {
lifecycle.fireLifecycleEvent(type, data);
}
//lifecycle定义
private LifecycleSupport lifecycle = new LifecycleSupport(this);
//LifecycleSupport的fireLifecycleEvent方法的实现
public void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
interested[i].lifecycleEvent(event);
}
</code></pre><p>然后将事件通知给所有监听当前容器的生命周期监听器LifecycleListener,并调用LifecycleListener的lifecycleEvent方法。</p>
<p>那么监听器LifecycleListener是何时注册进来的?其实每个容器在新建、初始化、启动,销毁,被添加到父容器的过程中都会调用父类LifecycleBase的addLifecycleListener方法:</p>
<pre><code> public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
</code></pre><p>LifecycleBase的addLifecycleListener方法实际是对LifecycleSupport的addLifecycleListener方法的简单代理,LifecycleSupport的addLifecycleListener方法的实现:</p>
<pre><code>public void addLifecycleListener(LifecycleListener listener) {
synchronized (listenersLock) {
LifecycleListener results[] =
new LifecycleListener[listeners.length + 1];
for (int i = 0; i < listeners.length; i++)
results[i] = listeners[i];
results[listeners.length] = listener;
listeners = results;
}
}
</code></pre><p>容器会最终调用每个对此容器感兴趣的LifecycleListener的lifecycleEvent方法,那么LifecycleListener的lifecycleEvent方法会做些什么呢?为了简单起见,我们以监听器JasperListener为例,JasperListener的lifecycleEvent方法的实现:</p>
<pre><code> public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
try {
// Set JSP factory
Class.forName("org.apache.jasper.compiler.JspRuntimeContext",
true,
this.getClass().getClassLoader());
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Should not occur, obviously
log.warn("Couldn't initialize Jasper", t);
}
// Another possibility is to do directly:
// JspFactory.setDefaultFactory(new JspFactoryImpl());
}
}
</code></pre><h2 id="Tomcat–容器生命周期"><a href="#Tomcat–容器生命周期" class="headerlink" title="Tomcat–容器生命周期"></a>Tomcat–容器生命周期</h2><p>StandardServer、StandardService、Connector、StandardContext这些容器,彼此之间都有父子关系,每个容器都可能包含零个或者多个子容器,这些子容器可能存在不同类型或者相同类型的多个。在一个容器创建成功后,会有以下状态:</p>
<ul>
<li><p>NEW:容器刚刚创建时,即在LifecycleBase实例构造完成时的状态。</p>
</li>
<li><p>INITIALIZED:容器初始化完成时的状态。</p>
</li>
<li><p>STARTING_PREP:容器启动前的状态。</p>
</li>
<li><p>STARTING:容器启动过程中的状态。</p>
</li>
<li><p>STARTED:容器启动完成的状态。</p>
</li>
<li><p>STOPPING_PREP:容器停止前的状态。</p>
</li>
<li><p>STOPPING:容器停止过程中的状态。</p>
</li>
<li><p>STOPPED:容器停止完成的状态。</p>
</li>
<li><p>DESTROYED:容器销毁后的状态。</p>
</li>
<li><p>FAILED:容器启动、停止过程中出现异常的状态。</p>
</li>
<li><p>MUST_STOP:此状态未使用。</p>
</li>
<li><p>MUST_DESTROY:此状态未使用。</p>
</li>
</ul>
<p>这些状态都定义在枚举类LifecycleState中。代码详单:</p>
<pre><code>public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null),
/**
* @deprecated Unused. Will be removed in Tomcat 9.0.x. The state transition
* checking in {@link org.apache.catalina.util.LifecycleBase}
* makes it impossible to use this state. The intended behaviour
* can be obtained by setting the state to
* {@link LifecycleState#FAILED} in
* <code>LifecycleBase.startInternal()</code>
*/
@Deprecated
MUST_STOP(true, null),
/**
* @deprecated Unused. Will be removed in Tomcat 9.0.x. The state transition
* checking in {@link org.apache.catalina.util.LifecycleBase}
* makes it impossible to use this state. The intended behaviour
* can be obtained by implementing {@link Lifecycle.SingleUse}.
*/
@Deprecated
MUST_DESTROY(false, null);
private final boolean available;
private final String lifecycleEvent;
private LifecycleState(boolean available, String lifecycleEvent) {
this.available = available;
this.lifecycleEvent = lifecycleEvent;
}
/**
* May the public methods other than property getters/setters and lifecycle
* methods be called for a component in this state? It returns
* <code>true</code> for any component in any of the following states:
* <ul>
* <li>{@link #STARTING}</li>
* <li>{@link #STARTED}</li>
* <li>{@link #STOPPING_PREP}</li>
* <li>{@link #MUST_STOP}</li>
* </ul>
*/
public boolean isAvailable() {
return available;
}
/**
*
*/
public String getLifecycleEvent() {
return lifecycleEvent;
}
}
</code></pre><p>每个容器都会有自身的生命周期,其中也涉及状态的迁移,以及伴随的事件生成。所有容器的状态转换(如新建、初始化、启动、停止等)都是由外到内,由上到下进行,即先执行父容器的状态转换及相关操作,然后再执行子容器的转态转换,这个过程是层层迭代执行的。</p>
<h3 id="Tomcat容器生命周期—-新建"><a href="#Tomcat容器生命周期—-新建" class="headerlink" title="Tomcat容器生命周期—-新建"></a>Tomcat容器生命周期—-新建</h3><p>所有容器在构造的过程中,都会首先对父类LifecycleBase进行构造。LifecycleBase中定义了所有容器的起始状态为LifecycleState.NEW。</p>
<pre><code>private volatile LifecycleState state = LifecycleState.NEW;
</code></pre><h3 id="Tomcat容器生命周期—-初始化"><a href="#Tomcat容器生命周期—-初始化" class="headerlink" title="Tomcat容器生命周期—-初始化"></a>Tomcat容器生命周期—-初始化</h3><p>每个容器的init方法是自身初始化的入口,其初始化过程如图所示:</p>
<p><img src="http://i.imgur.com/qSZYYc3.jpg" alt=""></p>
<ol>
<li>调用方调用容器父类LifecycleBase的init方法,LifecycleBase的init方法主要完成一些所有容器公共抽象出来的动作;</li>
<li>LifecycleBase的init方法调用具体容器的initInternal方法实现,此initInternal方法用于对容器本身真正的初始化;</li>
<li>具体容器的initInternal方法调用父类LifecycleMBeanBase的initInternal方法实现,此initInternal方法用于将容器托管到JMX,便于运维管理;</li>
<li>LifecycleMBeanBase的initInternal方法调用自身的register方法,将容器作为MBean注册到MBeanServer;</li>
<li>容器如果有子容器,会调用子容器的init方法;</li>
<li>容器初始化完毕,LifecycleBase会将容器的状态更改为初始化完毕,即LifecycleState.INITIALIZED。</li>
</ol>
<pre><code>**init方法的实现**
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
setStateInternal(LifecycleState.INITIALIZING, null, false);
try {
initInternal();//调用具体容器的initInternal方法实现
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
</code></pre><p>只有当前容器的状态处于LifecycleState.NEW的才可以被初始化,真正执行初始化的方法是initInternal,当初始化完毕,当前容器的状态会被更改为LifecycleState.INITIALIZED。以StandardService这个容器为例举例分析,StandardService容器的initInternal方法实现:</p>
<pre><code> protected void initInternal() throws LifecycleException {
super.initInternal();
if (container != null) {
container.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
</code></pre><p>其处理过程:</p>
<p><strong><em>a、调用父类LifecycleBase的initInternal方法,为当前容器创建DynamicMBean,并注册到JMX中。</em></strong></p>
<pre><code>protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via
// preRegister().
if (oname == null) {
mserver = Registry.getRegistry(null, null).getMBeanServer();
oname = register(this, getObjectNameKeyProperties());
}
}
//getObjectNameKeyProperties()方法
public final String getObjectNameKeyProperties() {
return "type=Service";
}
</code></pre><p>LifecycleBase的register方法会为当前容器创建对应的注册名称,以StandardService为例,getDomain默认返回Catalina,因此StandardService的JMX注册名称默认为Catalina:type=Service,真正的注册在registerComponent方法中实现。</p>
<pre><code>//register方法
protected final ObjectName register(Object obj,
String objectNameKeyProperties) {
// Construct an object name with the right domain
StringBuilder name = new StringBuilder(getDomain());
name.append(':');
name.append(objectNameKeyProperties);
ObjectName on = null;
try {
on = new ObjectName(name.toString());
Registry.getRegistry(null, null).registerComponent(obj, on, null);
} catch (MalformedObjectNameException e) {
log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
e);
} catch (Exception e) {
log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
e);
}
return on;
}
//registerComponent方法
public void registerComponent(Object bean, ObjectName oname, String type)
throws Exception
{
if( log.isDebugEnabled() ) {
log.debug( "Managed= "+ oname);
}
if( bean ==null ) {
log.error("Null component " + oname );
return;
}
try {
if( type==null ) {
type=bean.getClass().getName();
}
ManagedBean managed = findManagedBean(bean.getClass(), type);
// The real mbean is created and registered
DynamicMBean mbean = managed.createMBean(bean);
if( getMBeanServer().isRegistered( oname )) {
if( log.isDebugEnabled()) {
log.debug("Unregistering existing component " + oname );
}
getMBeanServer().unregisterMBean( oname );
}
getMBeanServer().registerMBean( mbean, oname);
} catch( Exception ex) {
log.error("Error registering " + oname, ex );
throw ex;
}
}
</code></pre><p>Registry的registerComponent方法会为当前容器(如StandardService)创建DynamicMBean,并且注册到MBeanServer中。</p>
<p><strong><em>b、将StringCache、MBeanFactory、globalNamingResources注册到JMX</em></strong></p>
<p>其中StringCache的注册名为Catalina:type=StringCache,MBeanFactory的注册名为Catalina:type=MBeanFactory,globalNamingResources的注册名为Catalina:type=NamingResources(如StandardService则为:Catalina:type=Service)</p>
<p><strong><em>c、初始化子容器</em></strong></p>
<p>主要对Service子容器进行初始化,默认是StandardService。</p>
<p>注意:个别容器并不完全遵循以上的初始化过程,比如ProtocolHandler作为Connector的子容器,其初始化过程并不是由Connector的initInternal方法调用的,而是与启动过程一道被Connector的startInternal方法所调用。</p>
<h3 id="Tomcat容器生命周期—-容器启动"><a href="#Tomcat容器生命周期—-容器启动" class="headerlink" title="Tomcat容器生命周期—-容器启动"></a>Tomcat容器生命周期—-容器启动</h3><p>每个容器的start方法是自身启动的入口</p>
<p><img src="http://i.imgur.com/tKjahP2.jpg" alt=""></p>
<ol>
<li>调用方调用容器父类LifecycleBase的start方法,LifecycleBase的start方法主要完成一些所有容器公共抽象出来的动作;</li>
<li>LifecycleBase的start方法先将容器状态改为LifecycleState.STARTING_PREP,然后调用具体容器的startInternal方法实现,此startInternal方法用于对容器本身真正的初始化;</li>
<li>具体容器的startInternal方法会将容器状态改为LifecycleState.STARTING,容器如果有子容器,会调用子容器的start方法启动子容器;</li>
<li><p>容器启动完毕,LifecycleBase会将容器的状态更改为启动完毕,即LifecycleState.STARTED。</p>
<pre><code>//LifecycleBase的start方法
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
startInternal();
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
}
</code></pre></li>
</ol>
<p>在真正启动容器之前需要做2种检查:</p>
<p>如果当前容器已经处于启动过程(即容器状态为LifecycleState.STARTING_PREP、LifecycleState.STARTING、LifecycleState.STARTED)中,则会产生并且用日志记录LifecycleException异常并退出。<br>如果容器依然处于LifecycleState.NEW状态,则在启动之前,首先确保初始化完毕。</p>
<p>启动容器完毕后,需要做1种检查:<br>即如果容器启动异常导致容器进入LifecycleState.FAILED或者LifecycleState.MUST_STOP状态,则需要调用stop方法停止容器。</p>
<p>以StandardService为例,其startInternal的实现:</p>
<pre><code>protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);//将自身状态更改为LifecycleState.STARTING;
// Start our defined Container first
if (container != null) {
synchronized (container) {
container.start();//调用子容器Service的start方法启动子容器。
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
</code></pre><p>除了初始化、启动外,各个容器还有停止和销毁的生命周期,其原理与初始化、启动类似。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Tomcat通过将内部所有组件都抽象为容器,为容器提供统一的生命周期管理,各个子容器只需要关心各自的具体实现,这便于Tomcat以后扩展更多的容器。</p>
]]></content>
<summary type="html">
<p><strong>想必大家都知道,从server.xml文件解析出来的各个对象都是容器,比如:Server、Service、Connector等。这些容器都具有新建、初始化完成、启动、停止、失败、销毁等状态。Tomcat的实现机制是通过实现org.apache.catalina.Lifecycle接口来管理。</strong></p>
<h2 id="Tomcat–Lifecycle接口"><a href="#Tomcat–Lifecycle接口" class="headerlink" title="Tomcat–Lifecycle接口"></a>Tomcat–Lifecycle接口</h2><p>定义了容器生命周期、容器状态转换及容器状态迁移事件的监听器注册和移除等主要接口。代码清单:</p>
<pre><code> public interface Lifecycle {
public static final String BEFORE_INIT_EVENT = &quot;before_init&quot;;
public static final String AFTER_INIT_EVENT = &quot;after_init&quot;;
public static final String START_EVENT = &quot;start&quot;;
public static final String BEFORE_START_EVENT = &quot;before_start&quot;;
public static final String AFTER_START_EVENT = &quot;after_start&quot;;
public static final String STOP_EVENT = &quot;stop&quot;;
public static final String BEFORE_STOP_EVENT = &quot;before_stop&quot;;
public static final String AFTER_STOP_EVENT = &quot;after_stop&quot;;
public static final String AFTER_DESTROY_EVENT = &quot;after_destroy&quot;;
public static final String BEFORE_DESTROY_EVENT = &quot;before_destroy&quot;;
public static final String PERIODIC_EVENT = &quot;periodic&quot;;
public static final String CONFIGURE_START_EVENT = &quot;configure_start&quot;;
public static final String CONFIGURE_STOP_EVENT = &quot;configure_stop&quot;;
public void addLifecycleListener(LifecycleListener listener);
public LifecycleListener[] findLifecycleListeners();
public void removeLifecycleListener(LifecycleListener listener);
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
public LifecycleState getState();
public String getStateName();
public interface SingleUse {
}
}
</code></pre><p>其中,最重要的方法时start和stop方法。父组件通过这两个方法来启动/关闭该组件。addLifecycleListener,findLifecycleListeners,removeLifecycleListener三个方法用于向组件注册/查找/删除监听器。当事件发生时,会触发监听器。接口中还定义了相关事件。</p>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>Tomcat源码学习(五)-- Tomcat_7.0.70 类加载体系分析</title>
<link href="http://zhengweishan.oschina.io/2017/02/09/%EF%BC%88%E4%BA%94%EF%BC%89Tomcat_7.0.70%E7%B1%BB%E5%8A%A0%E8%BD%BD%E4%BD%93%E7%B3%BB/"/>
<id>http://zhengweishan.oschina.io/2017/02/09/(五)Tomcat_7.0.70类加载体系/</id>
<published>2017-02-08T16:00:00.000Z</published>
<updated>2017-03-01T09:01:05.987Z</updated>
<content type="html"><![CDATA[<h2 id="1、前言"><a href="#1、前言" class="headerlink" title="1、前言"></a>1、前言</h2><p>Tomcat遵循J2EE规范,实现了Web容器。Java虚拟机有自己的一套类加载体系,同样Tomcat也有自己的一套类加载体系。</p>
<h2 id="2、概述"><a href="#2、概述" class="headerlink" title="2、概述"></a>2、概述</h2><p>首先简单介绍下Java虚拟机的主要的类加载器:</p>
<p><img src="http://i.imgur.com/FjDnfWq.png" alt=""></p>
<ol>
<li><p>启动类加载器(bootstrap classloader)</p>
<p> 它用来加载 Java 的核心库,是用原生代码(本地代码,与平台有关)来实现的,并不继承自java.lang.ClassLoader。这个类加载器负责将存放在<java_home>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识加的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。</java_home></p>
</li>
<li><p>扩展类加载器(extensions classloader)</p>
<p> 扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量java.ext.dir 指定位置中的类库加载到内存中</p>
</li>
<li><p>应用程序类加载器(application classloader)</p>
<p> 系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,由于这个类加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。</p>
</li>
<li><p>用户自定义的类装载器 </p>
<p> 用户自定义的类装载器是普通的Java对象,它的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序为程序提供了访问类装载器机制的接口。此外,对于每一个被装载的类型,Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和所有其它对象一样,用户自定义的类装载器以有Class类的实例都放在内存中的堆区,而装载的类型信息则都放在方法区。</p>
</li>
</ol>
<a id="more"></a>
<p>然后在来一张图简要说明Tomcat的类加载体系(图画的不好):</p>
<p><img src="http://i.imgur.com/9MPidXk.png" alt=""></p>
<ul>
<li>ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现</li>
<li>commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问</li>
<li>catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见</li>
<li>sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见</li>
<li><p>WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见</p>
<h2 id="3、分析"><a href="#3、分析" class="headerlink" title="3、分析"></a>3、分析</h2><p>commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一开始,即调用Bootstrap的init方法时创建。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身容器下的class。Bootstrap的init方法的部分代码如下:</p>
<p> /**</p>
<ul>
<li><p>Initialize daemon.<br>*/<br>public void init()<br> throws Exception<br>{<br> setCatalinaHome();<br> setCatalinaBase();</p>
<p> initClassLoaders();</p>
<p> Thread.currentThread().setContextClassLoader(catalinaLoader);</p>
<p> SecurityClassLoad.securityClassLoad(catalinaLoader);<br> …..<br>}</p>
</li>
</ul>
</li>
</ul>
<p>initClassLoaders方法:</p>
<pre><code>private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
</code></pre><p>创建类加载器的createClassLoader方法的实现:</p>
<pre><code>private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<Repository>();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken().trim();
if (repository.length() == 0) {
continue;
}
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
</code></pre><p>createClassLoader最终使用ClassLoaderFactory.createClassLoader(locations, types, parent)方法创建ClassLoader。</p>
<p>我们在看SecurityClassLoad.securityClassLoad(catalinaLoader);</p>
<pre><code> public static void securityClassLoad(ClassLoader loader)
throws Exception {
if( System.getSecurityManager() == null ){
return;
}
loadCorePackage(loader);
loadCoyotePackage(loader);
loadLoaderPackage(loader);
loadRealmPackage(loader);
loadServletsPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadValvesPackage(loader);
loadJavaxPackage(loader);
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
</code></pre><p>securityClassLoad方法主要加载Tomcat容器所需的class,包括:</p>
<ul>
<li>Tomcat核心class,即org.apache.catalina.core路径下的class;</li>
<li>org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName</li>
<li>Tomcat有关session的class,即org.apache.catalina.session路径下的class</li>
<li>Tomcat工具类的class,即org.apache.catalina.util路径下的class</li>
<li>javax.servlet.http.Cookie</li>
<li>Tomcat处理请求的class,即org.apache.catalina.connector路径下的class</li>
<li>Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class</li>
</ul>
<p>我们以加载Tomcat核心class的loadCorePackage方法为例,查看其实现:</p>
<pre><code>private static final void loadCorePackage(ClassLoader loader)
throws Exception {
final String basePackage = "org.apache.catalina.core.";
loader.loadClass
(basePackage +
"AccessLogAdapter");
loader.loadClass
(basePackage +
"ApplicationContextFacade$1");
loader.loadClass
(basePackage +
"ApplicationDispatcher$PrivilegedForward");
loader.loadClass
(basePackage +
"ApplicationDispatcher$PrivilegedInclude");
loader.loadClass
(basePackage +
"AsyncContextImpl");
loader.loadClass
(basePackage +
"AsyncContextImpl$DebugException");
loader.loadClass
(basePackage +
"AsyncContextImpl$1");
loader.loadClass
(basePackage +
"AsyncListenerWrapper");
loader.loadClass
(basePackage +
"ContainerBase$PrivilegedAddChild");
loader.loadClass
(basePackage +
"DefaultInstanceManager$1");
loader.loadClass
(basePackage +
"DefaultInstanceManager$2");
loader.loadClass
(basePackage +
"DefaultInstanceManager$3");
loader.loadClass
(basePackage +
"DefaultInstanceManager$AnnotationCacheEntry");
loader.loadClass
(basePackage +
"DefaultInstanceManager$AnnotationCacheEntryType");
loader.loadClass
(basePackage +
"ApplicationHttpRequest$AttributeNamesEnumerator");
}
</code></pre><p>至此为止,我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,StandardContext的方法startInternal的部分代码如下:</p>
<pre><code> protected synchronized void startInternal() throws LifecycleException {
......
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
......
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
// 省略后边的代码
}
</code></pre><p>从上面代码看到最后会调用WebappLoader的start方法:</p>
<pre><code>public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
startInternal();//start再次调用了startInternal方法(WebappLoader中的方法)
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
}
</code></pre><p>start又调用了startInternal方法,startInternal的实现如下:</p>
<pre><code>protected void startInternal() throws LifecycleException {
if (log.isDebugEnabled())
log.debug(sm.getString("webappLoader.starting"));
if (container.getResources() == null) {
log.info("No resources for " + container);
setState(LifecycleState.STARTING);
return;
}
// Register a stream handler factory for the JNDI protocol
URLStreamHandlerFactory streamHandlerFactory =
DirContextURLStreamHandlerFactory.getInstance();
if (first) {
first = false;
try {
URL.setURLStreamHandlerFactory(streamHandlerFactory);
} catch (Exception e) {
// Log and continue anyway, this is not critical
log.error("Error registering jndi stream handler", e);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This is likely a dual registration
log.info("Dual registration of jndi stream handler: "
+ t.getMessage());
}
}
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesRmiTargets(
((StandardContext) container).getClearReferencesRmiTargets());
classLoader.setClearReferencesStatic(
((StandardContext) container).getClearReferencesStatic());
classLoader.setClearReferencesStopThreads(
((StandardContext) container).getClearReferencesStopThreads());
classLoader.setClearReferencesStopTimerThreads(
((StandardContext) container).getClearReferencesStopTimerThreads());
classLoader.setClearReferencesHttpClientKeepAliveThread(
((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
}
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
// Configure our repositories
setRepositories();
setClassPath();
setPermissions();
((Lifecycle) classLoader).start();
// Binding the Webapp class loader to the directory context
DirContextURLStreamHandler.bind(classLoader,
this.container.getResources());
StandardContext ctx=(StandardContext)container;
String contextName = ctx.getName();
if (!contextName.startsWith("/")) {
contextName = "/" + contextName;
}
ObjectName cloname = new ObjectName
(MBeanUtils.getDomain(ctx) + ":type=WebappClassLoader,context="
+ contextName + ",host=" + ctx.getParent().getName());
Registry.getRegistry(null, null)
.registerComponent(classLoader, cloname, null);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
log.error( "LifecycleException ", t );
throw new LifecycleException("start: ", t);
}
setState(LifecycleState.STARTING);
}
</code></pre><p>最后我们看看createClassLoader的实现:</p>
<pre><code> private WebappClassLoaderBase createClassLoader()
throws Exception {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
</code></pre><p>至此Tomcat类加载完毕。</p>
]]></content>
<summary type="html">
<h2 id="1、前言"><a href="#1、前言" class="headerlink" title="1、前言"></a>1、前言</h2><p>Tomcat遵循J2EE规范,实现了Web容器。Java虚拟机有自己的一套类加载体系,同样Tomcat也有自己的一套类加载体系。</p>
<h2 id="2、概述"><a href="#2、概述" class="headerlink" title="2、概述"></a>2、概述</h2><p>首先简单介绍下Java虚拟机的主要的类加载器:</p>
<p><img src="http://i.imgur.com/FjDnfWq.png" alt=""></p>
<ol>
<li><p>启动类加载器(bootstrap classloader)</p>
<p> 它用来加载 Java 的核心库,是用原生代码(本地代码,与平台有关)来实现的,并不继承自java.lang.ClassLoader。这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识加的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。</p>
</li>
<li><p>扩展类加载器(extensions classloader)</p>
<p> 扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 &lt; Java_Runtime_Home &gt;/lib/ext 或者由系统变量java.ext.dir 指定位置中的类库加载到内存中</p>
</li>
<li><p>应用程序类加载器(application classloader)</p>
<p> 系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,由于这个类加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。</p>
</li>
<li><p>用户自定义的类装载器 </p>
<p> 用户自定义的类装载器是普通的Java对象,它的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序为程序提供了访问类装载器机制的接口。此外,对于每一个被装载的类型,Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和所有其它对象一样,用户自定义的类装载器以有Class类的实例都放在内存中的堆区,而装载的类型信息则都放在方法区。</p>
</li>
</ol>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>Tommcat源码学习(四)--Tomcat_7.0.70 server.xml文件的加载与解析</title>
<link href="http://zhengweishan.oschina.io/2017/02/08/%EF%BC%88%E5%9B%9B%EF%BC%89Tomcat_7.0.70%20server.xml%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD%E4%B8%8E%E8%A7%A3%E6%9E%90/"/>
<id>http://zhengweishan.oschina.io/2017/02/08/(四)Tomcat_7.0.70 server.xml文件的加载与解析/</id>
<published>2017-02-07T16:00:00.000Z</published>
<updated>2017-03-01T08:54:07.663Z</updated>
<content type="html"><![CDATA[<h1 id="1、文件的加载"><a href="#1、文件的加载" class="headerlink" title="1、文件的加载"></a>1、文件的加载</h1><p>Bootstrap的load方法是加载Tomcat的server.xml的入口,load方法实际通过反射调用catalinaDaemon(类型为Catalina)的load方法:</p>
<pre><code>/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);//通过反射机制调用Catalina类的load方法。
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
</code></pre><a id="more"></a>
<p>Catalina类的load方法:</p>
<pre><code> /*
* Load using arguments
*/
public void load(String args[]) {
try {
if (arguments(args)) {
load();//调用自身的load方法
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
/**
* Start a new server instance.
*/
public void load() {
long t1 = System.nanoTime();
initDirs(); //用于对catalina.home和catalina.base的一些检查工作
// Before digester - it may be needed
initNaming();//给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial
// Create and execute our Digester
Digester digester = createStartDigester();//实例化Digester
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();//获取conf/server.xml配置文件
inputStream = new FileInputStream(file);获取conf/server.xml配置文件输入流
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);//将FileInputStream封装为InputSource
digester.push(this);
digester.parse(inputSource);//调用Digester的parse方法进行解析
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
// Stream redirection
initStreams();//initStreams对输出流、错误流重定向
// Start the new server
try {
getServer().init();//初始化server
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
</code></pre><p>下面开始分析整个过程:</p>
<h2 id="1-1、initDirs()方法用于对catalina-home和catalina-base的一些检查工作"><a href="#1-1、initDirs()方法用于对catalina-home和catalina-base的一些检查工作" class="headerlink" title="1.1、initDirs()方法用于对catalina.home和catalina.base的一些检查工作"></a>1.1、initDirs()方法用于对catalina.home和catalina.base的一些检查工作</h2><pre><code>protected void initDirs() {
String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
if (catalinaHome == null) {
// Backwards compatibility patch for J2EE RI 1.3
String j2eeHome = System.getProperty("com.sun.enterprise.home");
if (j2eeHome != null) {
catalinaHome=System.getProperty("com.sun.enterprise.home");
} else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) {
catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP);
}
}
// last resort - for minimal/embedded cases.
if(catalinaHome==null) {
catalinaHome=System.getProperty("user.dir");
}
if (catalinaHome != null) {
File home = new File(catalinaHome);
if (!home.isAbsolute()) {
try {
catalinaHome = home.getCanonicalPath();
} catch (IOException e) {
catalinaHome = home.getAbsolutePath();
}
}
System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome);
}
if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) {
System.setProperty(Globals.CATALINA_BASE_PROP,
catalinaHome);
} else {
String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP);
File base = new File(catalinaBase);
if (!base.isAbsolute()) {
try {
catalinaBase = base.getCanonicalPath();
} catch (IOException e) {
catalinaBase = base.getAbsolutePath();
}
}
System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase);
}
String temp = System.getProperty("java.io.tmpdir");
if (temp == null || (!(new File(temp)).exists())
|| (!(new File(temp)).isDirectory())) {
log.error(sm.getString("embedded.notmp", temp));
}
}
</code></pre><h2 id="1-2、initNaming-方法给系统设置java-naming-factory-url-pkgs和java-naming-factory-initial"><a href="#1-2、initNaming-方法给系统设置java-naming-factory-url-pkgs和java-naming-factory-initial" class="headerlink" title="1.2、initNaming()方法给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial"></a>1.2、initNaming()方法给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial</h2><pre><code> protected void initNaming() {
// Setting additional variables
if (!useNaming) {
log.info( "Catalina naming disabled");
System.setProperty("catalina.useNaming", "false");
} else {
System.setProperty("catalina.useNaming", "true");
String value = "org.apache.naming";
String oldValue =
System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
if (oldValue != null) {
value = value + ":" + oldValue;
}
System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
if( log.isDebugEnabled() ) {
log.debug("Setting naming prefix=" + value);
}
value = System.getProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY);
if (value == null) {
System.setProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
} else {
log.debug( "INITIAL_CONTEXT_FACTORY already set " + value );
}
}
}
</code></pre><p>在创建JNDI上下文时,使用Context.INITIAL <em> CONTEXT </em> FACTORY(”java.naming.factory.initial”)属性,来指定创建JNDI上下文的工厂类;Context.URL <em> PKG </em> PREFIXES(“java.naming.factory.url.pkgs”)用在查询url中包括scheme方法id时创建对应的JNDI上下文.</p>
<h2 id="1-3、createStartDigester-创建并配置将要用来启动的Digester实例,并且设置一些列Rule,具体映射到server-xml"><a href="#1-3、createStartDigester-创建并配置将要用来启动的Digester实例,并且设置一些列Rule,具体映射到server-xml" class="headerlink" title="1.3、createStartDigester()创建并配置将要用来启动的Digester实例,并且设置一些列Rule,具体映射到server.xml"></a>1.3、createStartDigester()创建并配置将要用来启动的Digester实例,并且设置一些列Rule,具体映射到server.xml</h2><pre><code>/**
* Create and configure the Digester we will be using for startup.
*/
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class<?>, List<String>> fakeAttributes =
new HashMap<Class<?>, List<String>>();
ArrayList<String> attrs = new ArrayList<String>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResources");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResources");
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
digester.addObjectCreate("Server/Service/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
//Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor");
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
digester.addObjectCreate("Server/Service/Connector/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// Add RuleSets for nested elements
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return (digester);
}
</code></pre><p>从上面的代码可以看出:首先创建Digester,Digester继承了DefaultHandler,而DefaultHandler默认实现了ContentHander、DTDHander、ErrorHandler及EntityResolver 这4个接口,代码如下:</p>
<pre><code>public class DefaultHandler implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
</code></pre><p>通过源码可以发现DefaultHandler的所有实现都是空实现,所以解析还需要Digester。(具体分析后面在说)。</p>
<h2 id="1-4、configFile-获取配置文件conf-server-xml,并使用FileInputStream获取conf-server-xml配置文件输入流"><a href="#1-4、configFile-获取配置文件conf-server-xml,并使用FileInputStream获取conf-server-xml配置文件输入流" class="headerlink" title="1.4、configFile()获取配置文件conf/server.xml,并使用FileInputStream获取conf/server.xml配置文件输入流"></a>1.4、configFile()获取配置文件conf/server.xml,并使用FileInputStream获取conf/server.xml配置文件输入流</h2><pre><code>/**
* Return a File object representing our configuration file.
*/
protected File configFile() {
File file = new File(configFile);
if (!file.isAbsolute()) {
file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile);
}
return (file);
}
</code></pre><h2 id="1-5、将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析"><a href="#1-5、将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析" class="headerlink" title="1.5、将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析"></a>1.5、将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析</h2><pre><code>inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
</code></pre><h2 id="1-6、initStreams-对输出流、错误流重定向"><a href="#1-6、initStreams-对输出流、错误流重定向" class="headerlink" title="1.6、initStreams()对输出流、错误流重定向"></a>1.6、initStreams()对输出流、错误流重定向</h2><pre><code> protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
</code></pre><h2 id="1-7、初始化server"><a href="#1-7、初始化server" class="headerlink" title="1.7、初始化server"></a>1.7、初始化server</h2><pre><code>// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
</code></pre><h1 id="2、文件的解析"><a href="#2、文件的解析" class="headerlink" title="2、文件的解析"></a>2、文件的解析</h1><p>当加载server.xml配置文件到内存后,开始对XML文件中的内容进行解析,主要包含两个步骤:</p>
<ol>
<li>构造server.xml的规则,这些规则即可以用于构造Tomcat内部的容器(如StandardServer,StandardService等),也可以对server.xml进行合法性检查。如果server.xml不符合Tomcat内置的规则,在解析时将抛出异常,进而导致Tomcat无法启动。</li>
<li>使用SAX解析server.xml,边解析边应用规则,最终使用server.xml中的配置构建好Tomcat所需的各种容器。</li>
</ol>
<p><img src="http://i.imgur.com/0G69ss1.jpg" alt=""></p>
<p>Tomcat将server.xml文件中的所有元素上的属性都抽象为Rule,以Server元素为例,在内存中对应Server实例,Server实例的属性值就来自于Server元素的属性值。通过对规则(Rule)的应用,最终改变Server实例的属性值。Rule是一个抽象类,其中定义了以下方法:</p>
<p><img src="http://i.imgur.com/5TROnzW.png" alt=""></p>
<ul>
<li>getDigester:获取Digester实例;</li>
<li>setDigester:设置Digester实例;</li>
<li>getNamespaceURI:获取Rule所在的相对命名空间URI;</li>
<li>setNamespaceURI:设置Rule所在的相对命名空间URI;</li>
<li>begin(String namespace, String name, Attributes attributes):此方法在遇到一个匹配的XML元素的开头时被调用,如<server>;</server></li>
<li>body(String namespace, String name, String text):在遇到匹配XML元素的body时,此方法被调用,如进入标签内部时;</li>
<li>end(String namespace, String name):此方法在遇到一个匹配的XML元素的末尾时被调用。如:< /Server>;</li>
</ul>
<p>Rule目前有很多实现类,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等。下图展示了Rule的部分实现类:</p>
<p><img src="http://i.imgur.com/AU4umDE.png" alt=""></p>
<p>Tomcat使用SAX(Simple API for XML)解析XML:</p>
<p>SAX解析XML采用的是从上而下的基于事件驱动的解析方式,在解析过程中会视情况自动调用ContentHandler接口中的startDocument()、startElement()、characters()、endElement()、endDocument()等相关的方法。</p>
<p>由编译执行的结果分析:</p>
<ul>
<li>startDocument()方法只会在文档开始解析的时候被调用,每次解析只会调用一次。</li>
<li>startElement()方法每次在开始解析一个元素,即遇到元素标签开始的时候都会调用。</li>
<li>characters()方法也是在每次解析到元素标签携带的内容时都会调用,即使该元素标签的内容为空或换行。而且如果元素内嵌套元素,在父元素结束标签前, characters()方法会再次被调用,此处需要注意。</li>
<li>endElement()方法每次在结束解析一个元素,即遇到元素标签结束的时候都会调用。</li>
<li>endDocument()方法只会在文档解析结束的时候被调用,每次解析只会调用一次。</li>
</ul>
<p>使用SAX解析XML的好处:</p>
<ul>
<li>SAX 不用解析完整个文档</li>
<li>相比于 DOM 而言 SAX 是一种速度更快,更有效,占用内存更少的解析 XML 文件的方法</li>
<li>逐行扫描,可以做到边扫描边解析,因此 SAX 可以在解析文档的任意时刻停止解析</li>
</ul>
<p>由于SAX是基于事件驱动的,不用解析完整个文档,在按内容顺序解析文档过程中, SAX 会判断当前读到的字符是否符合 XML 文件语法中的某部分。如果符合某部分,则会触发事件。所谓触发事件,就是调用一些回调方法。在用 SAX 解析 xml 文档时候,在读取到文档开始和结束标签时候就会回调一个事件,在读取到其他节点与内容时候也会回调一个事件。在 SAX 接口中,事件源是 org.xml.sax 包中的 XMLReader ,它通过 parser() 方法来解析 XML 文档,并产生事件。事件处理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 这4个接口:</p>
<ol>
<li>ContentHander ( XML 文档的开始与结束)setContentHandler(ContentHandler h) </li>
<li>DTDHander ( 处理 DTD 解析) setDTDHandler(DTDHandler h) </li>
<li>ErrorHandler ( 处理 XML 时产生的错误)setErrorHandler(ErrorHandler h) </li>
<li>EntityResolver (处理外部实体) setEntityResolver(EntityResolver e) </li>
</ol>
<p>回调方法一般都定义在ContentHandler接口中,上面已经对这些回调方法的加载顺序已经说了就不在介绍了。</p>
<p>使用 SAX 解析 XML 文件一般有以下五个步骤: </p>
<ol>
<li>创建一个 SAXParserFactory 对象; </li>
<li>调用 SAXParserFactory 中的 newSAXParser 方法创建一个 SAXParser 对象; </li>
<li>然后在调用 SAXParser 中的 getXMLReader 方法获取一个 XMLReader 对象;</li>
<li>实例化一个 DefaultHandler 对象;</li>
<li>连接事件源对象 XMLReader 到事件处理类 DefaultHandler 中;</li>
<li>调用 XMLReader 的 parse 方法从输入源中获取到的 xml 数据;</li>
<li>通过 DefaultHandler 返回我们需要的数据集合。</li>
</ol>
<p>我们通过源码可以发现DefaultHandler的所有实现都是空实现,所以解析还需要Digester自身,代码如下:</p>
<pre><code> @Override
public void startDocument() throws SAXException {
if (saxLog.isDebugEnabled()) {
saxLog.debug("startDocument()");
}
// ensure that the digester is properly configured, as
// the digester could be used as a SAX ContentHandler
// rather than via the parse() methods.
configure();
}
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled();
if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
qName + ")");
}
// Parse system properties
list = updateAttributes(list);
// Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
bodyText = new StringBuilder();
// the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
// Compute the current matching rule
StringBuilder sb = new StringBuilder(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
}
// Fire "begin" events for all relevant rules
List<Rule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
}
}
@Override
public void endDocument() throws SAXException {
if (saxLog.isDebugEnabled()) {
if (getCount() > 1) {
saxLog.debug("endDocument(): " + getCount() +
" elements left");
} else {
saxLog.debug("endDocument()");
}
}
while (getCount() > 1) {
pop();
}
// Fire "finish" events for all defined rules
Iterator<Rule> rules = getRules().rules().iterator();
while (rules.hasNext()) {
Rule rule = rules.next();
try {
rule.finish();
} catch (Exception e) {
log.error("Finish event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Finish event threw error", e);
throw e;
}
}
// Perform final cleanup
clear();
}
@Override
public void endElement(String namespaceURI, String localName,
String qName) throws SAXException {
boolean debug = log.isDebugEnabled();
if (debug) {
if (saxLog.isDebugEnabled()) {
saxLog.debug("endElement(" + namespaceURI + "," + localName +
"," + qName + ")");
}
log.debug(" match='" + match + "'");
log.debug(" bodyText='" + bodyText + "'");
}
// Parse system properties
bodyText = updateBodyText(bodyText);
// the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
// Fire "body" events for all relevant rules
List<Rule> rules = matches.pop();
if ((rules != null) && (rules.size() > 0)) {
String bodyText = this.bodyText.toString();
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire body() for " + rule);
}
rule.body(namespaceURI, name, bodyText);
} catch (Exception e) {
log.error("Body event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Body event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
if (rulesValidation) {
log.warn(" No rules found matching '" + match + "'.");
}
}
// Recover the body text from the surrounding element
bodyText = bodyTexts.pop();
// Fire "end" events for all relevant rules in reverse order
if (rules != null) {
for (int i = 0; i < rules.size(); i++) {
int j = (rules.size() - i) - 1;
try {
Rule rule = rules.get(j);
if (debug) {
log.debug(" Fire end() for " + rule);
}
rule.end(namespaceURI, name);
} catch (Exception e) {
log.error("End event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("End event threw error", e);
throw e;
}
}
}
// Recover the previous match expression
int slash = match.lastIndexOf('/');
if (slash >= 0) {
match = match.substring(0, slash);
} else {
match = "";
}
}
</code></pre><p>当我们创建好Digester后,会调用addObjectCreate、addSetProperties、addSetNext方法陆续添加很多Rule,这些方法的实现如代码:</p>
<pre><code> public void addObjectCreate(String pattern, String className,
String attributeName) {
addRule(pattern,
new ObjectCreateRule(className, attributeName));
}
public void addSetProperties(String pattern) {
addRule(pattern,
new SetPropertiesRule());
}
public void addSetNext(String pattern, String methodName,
String paramType) {
addRule(pattern,
new SetNextRule(methodName, paramType));
}
</code></pre><p>这三个方法分别创建ObjectCreateRule、SetPropertiesRule及SetNextRule,为了便于理解,我们举例说明(Server标签):</p>
<pre><code>digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer", "org.apache.catalina.Server");
</code></pre><p>我们知道最终会创建ObjectCreateRule、SetPropertiesRule及SetNextRule,并且调用addRule方法。</p>
<pre><code> public void addRule(String pattern, Rule rule) {
rule.setDigester(this);
getRules().add(pattern, rule);
}
</code></pre><p>从代码可以看出,addRule方法首先调用getRules方法获取RulesBase,然后调用RulesBase的add方法。代码如下:</p>
<pre><code>//getRules()获取RulesBase
public Rules getRules() {
if (this.rules == null) {
this.rules = new RulesBase();
this.rules.setDigester(this);
}
return (this.rules);
}
//RulesBase的add方法
@Override
public void add(String pattern, Rule rule) {
// to help users who accidently add '/' to the end of their patterns
int patternLength = pattern.length();
if (patternLength>1 && pattern.endsWith("/")) {
pattern = pattern.substring(0, patternLength-1);
}
List<Rule> list = cache.get(pattern);
if (list == null) {
list = new ArrayList<Rule>();
cache.put(pattern, list);
}
list.add(rule);
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
}
}
</code></pre><p>其中,cache的数据结构为HashMap<string,list<rule>>,每个键值维护一个List<rule>,由此可知,对Server标签来说,对应的Rule列表为ObjectCreateRule、SetPropertiesRule及SetNextRule。</rule></string,list<rule></p>
<p>Digester解析XML的入口是其parse方法,其处理步骤如下:</p>
<p>1.创建XMLReader ;</p>
<p>2.使用XMLReader解析XML。</p>
<pre><code>public Object parse(InputSource input) throws IOException, SAXException {
configure();
getXMLReader().parse(input);
return (root);
}
//configure()
protected void configure() {
// Do not configure more than once
if (configured) {
return;
}
log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester");
saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");
// Perform lazy configuration as needed
initialize(); // call hook method for subclasses that want to be initialized once only
// Nothing else required by default
// Set the configuration flag to avoid repeating
configured = true;
}
</code></pre><p>getXMLReader方法调用getParser创建SAXParser ,然后调用SAXParser 的getXMLReader方法创建XMLReader.</p>
<pre><code>public XMLReader getXMLReader() throws SAXException {
if (reader == null){
reader = getParser().getXMLReader();
}
reader.setDTDHandler(this);
reader.setContentHandler(this);
if (entityResolver == null){
reader.setEntityResolver(this);
} else {
reader.setEntityResolver(entityResolver);
}
reader.setProperty(
"http://xml.org/sax/properties/lexical-handler", this);
reader.setErrorHandler(this);
return reader;
}
</code></pre><p>getParser方法调用getFactory方法创建SAXParserFactory,然后调用SAXParserFactory的newSAXParser方法创建SAXParser</p>
<pre><code>public SAXParser getParser() {
// Return the parser we already created (if any)
if (parser != null) {
return (parser);
}
// Create a new parser
try {
parser = getFactory().newSAXParser();
} catch (Exception e) {
log.error("Digester.getParser: ", e);
return (null);
}
return (parser);
}
</code></pre><p>getFactory方法使用SAX的API生成SAXParserFactory实例.</p>
<pre><code>public SAXParserFactory getFactory() throws SAXNotRecognizedException, SAXNotSupportedException,ParserConfigurationException {
if (factory == null) {
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// Preserve xmlns attributes
if (namespaceAware) {
factory.setFeature(
"http://xml.org/sax/features/namespace-prefixes",
true);
}
factory.setValidating(validating);
if (validating) {
// Enable DTD validation
factory.setFeature(
"http://xml.org/sax/features/validation",
true);
// Enable schema validation
factory.setFeature(
"http://apache.org/xml/features/validation/schema",
true);
}
}
return (factory);
}
</code></pre><p>XMLReader解析XML时,会生成事件,回调Digester的startDocument方法,解析的第一个元素是Server,此时回调Digester的startElement方法,入参Attributes list即Server上的属性,如port、shutdown等,入参qName即为Server.</p>
<pre><code> @Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled();
if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
qName + ")");
}
// Parse system properties
list = updateAttributes(list);
// Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
bodyText = new StringBuilder();
// the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
// Compute the current matching rule
StringBuilder sb = new StringBuilder(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
}
// Fire "begin" events for all relevant rules
List<Rule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
}
}
</code></pre><p>startElement方法的处理步骤如下:</p>
<p>1.match刚开始为空字符串,拼接Server后变为Server。</p>
<p>2.调用RulesBase的match方法,返回cache中按照键值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。</p>
<p>3.循环列表依次遍历ObjectCreateRule、SetPropertiesRule及SetNextRule,并调用它们的begin方法。</p>
<p>ObjectCreateRule的begin方法将生成Server的实例(默认为”org.apache.catalina.core.StandardServer”,用户可以通过给Server标签指定className使用其它Server实现),最后将Server的实例压入Digester的栈中.</p>
<pre><code>@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
// Identify the name of the class to instantiate
String realClassName = className;
if (attributeName != null) {
String value = attributes.getValue(attributeName);
if (value != null) {
realClassName = value;
}
}
if (digester.log.isDebugEnabled()) {
digester.log.debug("[ObjectCreateRule]{" + digester.match +
"}New " + realClassName);
}
if (realClassName == null) {
throw new NullPointerException("No class name specified for " +
namespace + " " + name);
}
// Instantiate the new object and push it on the context stack
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.newInstance();
digester.push(instance);
}
</code></pre><p>SetPropertiesRule的begin方法首先将刚才压入栈中的Server实例出栈,然后给Server实例设置各个属性值,如port、shutdown等:</p>
<pre><code>@Override
public void begin(String namespace, String theName, Attributes attributes)
throws Exception {
// Populate the corresponding properties of the top object
Object top = digester.peek();
if (digester.log.isDebugEnabled()) {
if (top != null) {
digester.log.debug("[SetPropertiesRule]{" + digester.match +
"} Set " + top.getClass().getName() +
" properties");
} else {
digester.log.debug("[SetPropertiesRule]{" + digester.match +
"} Set NULL properties");
}
}
// set up variables for custom names mappings
int attNamesLength = 0;
if (attributeNames != null) {
attNamesLength = attributeNames.length;
}
int propNamesLength = 0;
if (propertyNames != null) {
propNamesLength = propertyNames.length;
}
for (int i = 0; i < attributes.getLength(); i++) {
String name = attributes.getLocalName(i);
if ("".equals(name)) {
name = attributes.getQName(i);
}
String value = attributes.getValue(i);
// we'll now check for custom mappings
for (int n = 0; n<attNamesLength; n++) {
if (name.equals(attributeNames[n])) {
if (n < propNamesLength) {
// set this to value from list
name = propertyNames[n];
} else {
// set name to null
// we'll check for this later
name = null;
}
break;
}
}
if (digester.log.isDebugEnabled()) {
digester.log.debug("[SetPropertiesRule]{" + digester.match +
"} Setting property '" + name + "' to '" +
value + "'");
}
if (!digester.isFakeAttribute(top, name)
&& !IntrospectionUtils.setProperty(top, name, value)
&& digester.getRulesValidation()) {
digester.log.warn("[SetPropertiesRule]{" + digester.match +
"} Setting property '" + name + "' to '" +
value + "' did not find a matching property.");
}
}
}
</code></pre><p>SetNextRule的begin不做什么动作。当遇到Server的结束标签时,还会依次调用ObjectCreateRule、SetPropertiesRule及SetNextRule的end方法,不再赘述。所有元素的解析都与Server标签同理,最终将server.xml文件中设置的元素及其属性值,构造出tomcat中的容器,如:Server、Service、Connector等.</p>
]]></content>
<summary type="html">
<h1 id="1、文件的加载"><a href="#1、文件的加载" class="headerlink" title="1、文件的加载"></a>1、文件的加载</h1><p>Bootstrap的load方法是加载Tomcat的server.xml的入口,load方法实际通过反射调用catalinaDaemon(类型为Catalina)的load方法:</p>
<pre><code>/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = &quot;load&quot;;
Object param[];
Class&lt;?&gt; paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);//通过反射机制调用Catalina类的load方法。
if (log.isDebugEnabled())
log.debug(&quot;Calling startup class &quot; + method);
method.invoke(catalinaDaemon, param);
}
</code></pre>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>Tommcat源码学习(三)--Tomcat_7.0.70停止过程分析</title>
<link href="http://zhengweishan.oschina.io/2017/02/07/%EF%BC%88%E4%B8%89%EF%BC%89Tomcat_7.0.70%E5%81%9C%E6%AD%A2%E5%88%86%E6%9E%90/"/>
<id>http://zhengweishan.oschina.io/2017/02/07/(三)Tomcat_7.0.70停止分析/</id>
<published>2017-02-06T16:00:00.000Z</published>
<updated>2017-03-01T08:50:57.655Z</updated>
<content type="html"><![CDATA[<p>Tomcat关闭命令(Linux下,大部分生产环境都是部署在Linux系统下):</p>
<pre><code>sh shutdown.sh
</code></pre><p>执行这个命令之后,tomcat会为我们做了哪些操作呢?下面就来简单分析下。</p>
<p>shutdown.sh代码清单如下:</p>
<pre><code># Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" stop "$@"
</code></pre><a id="more"></a>
<p>从上面的代码可以看出来,这里和启动文件是一样的,也有主要的两个变量:</p>
<pre><code>PRGDIR:当前shell脚本所在的路径;
EXECUTABLE:脚本catalina.sh
</code></pre><p>从最后一行代码可以知道,执行catalina.sh,并传递参数:stop</p>
<p>catalina.sh 与之相关的代码清单如下:</p>
<pre><code>elif [ "$1" = "stop" ] ; then
shift
SLEEP=5
if [ ! -z "$1" ]; then
echo $1 | grep "[^0-9]" >/dev/null 2>&1
if [ $? -gt 0 ]; then
SLEEP=$1
shift
fi
fi
FORCE=0
if [ "$1" = "-force" ]; then
shift
FORCE=1
fi
if [ ! -z "$CATALINA_PID" ]; then
if [ -f "$CATALINA_PID" ]; then
if [ -s "$CATALINA_PID" ]; then
kill -0 `cat "$CATALINA_PID"` >/dev/null 2>&1
if [ $? -gt 0 ]; then
echo "PID file found but no matching process was found. Stop aborted."
exit 1
fi
else
echo "PID file is empty and has been ignored."
fi
else
echo "\$CATALINA_PID was set but the specified file does not exist. Is Tomcat running? Stop aborted."
exit 1
fi
fi
eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" stop
</code></pre><p>最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是stop。</p>
<pre><code> public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
</code></pre><p>当传递参数stop的时候,command等于stop,此时main方法的执行步骤如下:</p>
<ul>
<li>初始化Bootstrap(启动的时候已经介绍过就不在介绍)</li>
<li><p>停止服务</p>
<p> 通过调用Bootstrap的stopServer方法停止Tomcat,其实质是用反射调用catalinaDaemon(类型是Catalina)的stopServer方法。</p>
<pre><code>/**
* Stop the standalone server.
*/
public void stopServer(String[] arguments)
throws Exception {
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod("stopServer", paramTypes);//反射调用catalinaDaemon(类型是Catalina)的stopServer方法
method.invoke(catalinaDaemon, param);
}
</code></pre><p> Catalina的stopServer方法的执行步骤如下:</p>
<p> 代码清单:</p>
<pre><code>public void stopServer() {
stopServer(null);
}
public void stopServer(String[] arguments) {
if (arguments != null) {
arguments(arguments);
}
Server s = getServer();
if( s == null ) {//服务不存在
// Create and execute our Digester
Digester digester = createStopDigester();//Digester解析server.xml文件,以构造出Server容器
File file = configFile();
FileInputStream fis = null;
try {
InputSource is =
new InputSource(file.toURI().toURL().toString());
fis = new FileInputStream(file);
is.setByteStream(fis);
digester.push(this);
digester.parse(is);
} catch (Exception e) {
log.error("Catalina.stop: ", e);
System.exit(1);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// Ignore
}
}
}
} else {
// Server object already present. Must be running as a service
try {
s.stop();
} catch (LifecycleException e) {
log.error("Catalina.stop: ", e);
}
return;
}
// Stop the existing server
s = getServer();
if (s.getPort()>0) {
Socket socket = null;
OutputStream stream = null;
try {
socket = new Socket(s.getAddress(), s.getPort());//创建Socket对象连接启动Tomcat时创建的ServerSocket
stream = socket.getOutputStream();
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));//发送shutdown命令
}
stream.flush();
} catch (ConnectException ce) {
log.error(sm.getString("catalina.stopServer.connectException",
s.getAddress(),
String.valueOf(s.getPort())));
log.error("Catalina.stop: ", ce);
System.exit(1);
} catch (IOException e) {
log.error("Catalina.stop: ", e);
System.exit(1);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
}
} else {
log.error(sm.getString("catalina.stopServer"));
System.exit(1);
}
}
</code></pre></li>
</ul>
<pre><code>- 创建Digester解析server.xml文件(此处只解析标签),以构造出Server容器(此时Server容器的子容器没有被实例化);
- 从实例化的Server容器获取Server的socket监听端口和地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令。根据
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));//创建socket连接的服务端对象ServerSocket
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();//创建一个对象循环接收socket中的字符
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) { //如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
内容,ServerSocket循环等待接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat。
最后,我们看看Catalina的stop方法的实现,其执行步骤如下:
1. 将启动过程中添加的关闭钩子移除。Tomcat启动过程辛辛苦苦添加的关闭钩子为什么又要去掉呢?因为关闭钩子是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以不再需要这个钩子了。
2. 停止Server容器。有关容器的停止内容,请阅读后续文章。
3. 代码清单:
/**
* Stop an existing server instance.****
*/
public void stop() {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);//将启动过程中添加的关闭钩子移除
// If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Shut down the server
try {
Server s = getServer();
LifecycleState state = s.getState();
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();//停止Server容器。
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
总结:
通过对Tomcat源码的分析我们了解到Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。
当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令
,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了socket的通信。
</code></pre>]]></content>
<summary type="html">
<p>Tomcat关闭命令(Linux下,大部分生产环境都是部署在Linux系统下):</p>
<pre><code>sh shutdown.sh
</code></pre><p>执行这个命令之后,tomcat会为我们做了哪些操作呢?下面就来简单分析下。</p>
<p>shutdown.sh代码清单如下:</p>
<pre><code># Better OS/400 detection: see Bugzilla 31132
os400=false
case &quot;`uname`&quot; in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG=&quot;$0&quot;
while [ -h &quot;$PRG&quot; ] ; do
ls=`ls -ld &quot;$PRG&quot;`
link=`expr &quot;$ls&quot; : &apos;.*-&gt; \(.*\)$&apos;`
if expr &quot;$link&quot; : &apos;/.*&apos; &gt; /dev/null; then
PRG=&quot;$link&quot;
else
PRG=`dirname &quot;$PRG&quot;`/&quot;$link&quot;
fi
done
PRGDIR=`dirname &quot;$PRG&quot;`
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x &quot;$PRGDIR&quot;/&quot;$EXECUTABLE&quot; ]; then
echo &quot;Cannot find $PRGDIR/$EXECUTABLE&quot;
echo &quot;The file is absent or does not have execute permission&quot;
echo &quot;This file is needed to run this program&quot;
exit 1
fi
fi
exec &quot;$PRGDIR&quot;/&quot;$EXECUTABLE&quot; stop &quot;$@&quot;
</code></pre>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>Tomcat源码学习(二)--Tomcat_7.0.70 启动分析</title>
<link href="http://zhengweishan.oschina.io/2017/02/06/%EF%BC%88%E4%BA%8C%EF%BC%89Tomcat_7.0.70%E5%90%AF%E5%8A%A8%E5%88%86%E6%9E%90/"/>
<id>http://zhengweishan.oschina.io/2017/02/06/(二)Tomcat_7.0.70启动分析/</id>
<published>2017-02-05T16:00:00.000Z</published>
<updated>2017-03-01T08:50:27.024Z</updated>
<content type="html"><![CDATA[<h2 id="1、运行Tomcat-7-0-70源码"><a href="#1、运行Tomcat-7-0-70源码" class="headerlink" title="1、运行Tomcat_7.0.70源码"></a>1、运行Tomcat_7.0.70源码</h2><p>项目build成功后,刷新整个项目,会发现多出一个output目录:</p>
<p><img src="http://i.imgur.com/lEmnktv.png" alt=""></p>
<p>为了让应用跑起来,可以检查一下output\build\conf下是否已经有配置文件,这些文件实际是从项目根路径conf目录下拷贝过来的。</p>
<p><img src="http://i.imgur.com/7riC9qx.png" alt=""></p>
<p>找到BootStarp.java文件,Debug前加入默认的catalina home路径作为启动参数。</p>
<p><img src="http://i.imgur.com/vOat4iI.png" alt=""></p>
<p>路径设置为output下build的绝对路径。比如我自己的机器设置的值是-Dcatalina.home=”W:\workspace\tc7.0.70\output\build”</p>
<p><img src="http://i.imgur.com/ac9aXEQ.png" alt=""><br>这样就可以愉快的在文件中加入断点Debug源码分析了。运行之后的效果图:<br><img src="http://i.imgur.com/KxbTJD8.png" alt=""><br><img src="http://i.imgur.com/XlnUToa.png" alt=""><br>OK,源码到此运行成功,完美~</p>
<a id="more"></a>
<h2 id="2、启动分析"><a href="#2、启动分析" class="headerlink" title="2、启动分析"></a>2、启动分析</h2><p>上面运行源码用的BootStarp.java这个类中的main方法(后面再对这个main方法做分析),实际上我们在用Tomcat的时候,大部分都是使用脚本文件startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令来启动Tomcat的.大家一定知道改如何使用它,但是它们究竟是如何实现的,下面就一点一点的分析。</p>
<p>由于在生产环境中,Tomcat一般部署在Linux系统下,所以将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与停止进行分析。</p>
<p>Linux下启动Tomcat的命令:</p>
<pre><code>sh startup.sh
</code></pre><p>下面将从shell脚本startup.sh开始分析Tomcat的启动过程。</p>
<p>startup.sh脚本代码清单:</p>
<pre><code># Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
</code></pre><p>从代码清单可以看出有两个主要的变量,分别是:</p>
<pre><code>1. PRGDIR:当前shell脚本所在的路径;
2. EXECUTABLE:脚本catalina.sh。
</code></pre><p><code>exec "$PRGDIR"/"$EXECUTABLE" start "$@"</code> 我们知道执行了shell脚本catalina.sh,并且传递参数start。</p>
<p>catalina.sh 脚本代码(部分)清单:</p>
<pre><code> shift
touch "$CATALINA_OUT"
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq 1 ]; then
echo "Using Security Manager"
fi
shift
eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
else
eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
fi
if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi
echo "Tomcat started."
</code></pre><p>从上面可以看出,脚本最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。Bootstrap的main方法的实现如下:</p>
<pre><code>public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
//传递参数为start
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
</code></pre><p>当传递参数start的时候,command等于start,此时main方法的执行步骤如下:</p>
<ul>
<li>初始化Bootstrap</li>
</ul>
<pre><code>public void init() throws Exception{
// Set Catalina path
setCatalinaHome(); //1.设置Catalina路径,默认为Tomcat的根目录
setCatalinaBase();
initClassLoaders();//2.初始化Tomcat的类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);//3.并设置线程上下文类加载器
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
//4.用反射实例化org.apache.catalina.startup.Catalina对象,并且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外)
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
</code></pre><ul>
<li><p>加载、解析server.xml配置文件</p>
<p> 当传递参数start的时候,会调用Bootstrap的load方法:</p>
<pre><code> /**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);//用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件。
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
</code></pre><p>备注:如何加载和解析server.xml配置文件,后面会博客会陆续给出。</p>
</li>
<li><p>启动Tomcat</p>
<p> 当传递参数start的时候,调用Bootstrap的load方法之后会接着调用start方法:</p>
<pre><code>/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);//启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法
method.invoke(catalinaDaemon, (Object [])null);
}
</code></pre><p>Catalina的start方法如下:</p>
<pre><code> /**
* Start a new server instance.
*/
public void start() {
//1.验证Server容器是否已经实例化
if (getServer() == null) {
load(); //如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer。
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start(); //2.启动Server容器
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();//3.设置关闭钩子
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();//4.最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令
stop();//5.如果Tomcat运行正常且没有收到shutdown命令,是不会向下执行此方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat
}
}
</code></pre><p>Catalina的await方法实际只是代理执行了Server容器的await方法。</p>
<pre><code>/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
</code></pre><p>以Server的默认实现StandardServer为例,其await方法如下:</p>
<pre><code>@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));//创建socket连接的服务端对象ServerSocket
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();//创建一个对象循环接收socket中的字符
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) { //如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
</code></pre><p>至此,Tomcat启动完毕。</p>
</li>
</ul>
<p>备注:如何启动server,这里不做过多解释,后面会有专门的博客介绍《容器启动过程分析》。</p>
]]></content>
<summary type="html">
<h2 id="1、运行Tomcat-7-0-70源码"><a href="#1、运行Tomcat-7-0-70源码" class="headerlink" title="1、运行Tomcat_7.0.70源码"></a>1、运行Tomcat_7.0.70源码</h2><p>项目build成功后,刷新整个项目,会发现多出一个output目录:</p>
<p><img src="http://i.imgur.com/lEmnktv.png" alt=""></p>
<p>为了让应用跑起来,可以检查一下output\build\conf下是否已经有配置文件,这些文件实际是从项目根路径conf目录下拷贝过来的。</p>
<p><img src="http://i.imgur.com/7riC9qx.png" alt=""></p>
<p>找到BootStarp.java文件,Debug前加入默认的catalina home路径作为启动参数。</p>
<p><img src="http://i.imgur.com/vOat4iI.png" alt=""></p>
<p>路径设置为output下build的绝对路径。比如我自己的机器设置的值是-Dcatalina.home=”W:\workspace\tc7.0.70\output\build”</p>
<p><img src="http://i.imgur.com/ac9aXEQ.png" alt=""><br>这样就可以愉快的在文件中加入断点Debug源码分析了。运行之后的效果图:<br><img src="http://i.imgur.com/KxbTJD8.png" alt=""><br><img src="http://i.imgur.com/XlnUToa.png" alt=""><br>OK,源码到此运行成功,完美~</p>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>Tomcat 源码学习(一)--Tomcat_7.0.70 源码运行环境搭建</title>
<link href="http://zhengweishan.oschina.io/2017/02/05/%EF%BC%88%E4%B8%80%EF%BC%89Tomcat7.0.70%E6%BA%90%E4%BB%A3%E7%A0%81%E8%BF%90%E8%A1%8C%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<id>http://zhengweishan.oschina.io/2017/02/05/(一)Tomcat7.0.70源代码运行环境搭建/</id>
<published>2017-02-04T16:00:00.000Z</published>
<updated>2017-03-01T08:48:29.343Z</updated>
<content type="html"><![CDATA[<h1 id="1、源码下载"><a href="#1、源码下载" class="headerlink" title="1、源码下载"></a>1、源码下载</h1><p><a href="http://apache.fayea.com/tomcat/tomcat-7/v7.0.70/src/apache-tomcat-7.0.70-src.zip" target="_blank" rel="external">下载地址</a></p>
<h1 id="2、环境配置"><a href="#2、环境配置" class="headerlink" title="2、环境配置"></a>2、环境配置</h1><h2 id="2-1-新建eclipse项目"><a href="#2-1-新建eclipse项目" class="headerlink" title="2.1 新建eclipse项目"></a>2.1 新建eclipse项目</h2><pre><code>新建项目tc7.0.70,其他设置默认就好。
</code></pre><p><img src="http://i.imgur.com/MB1PvoU.png" alt=""></p>
<pre><code>项目建立好之后修改此项目的编译环境,
请选择编译环境为1.6,选择1.7会报错(tomcat-dbcp.jar依赖的是jdk1.6,
选择1.7在建立的时候会报错,这个大家可以尝试下看看是不是),下图就是选择编译环境为1.7的时候报的错误。
</code></pre><p><img src="http://i.imgur.com/NxcOK1m.png" alt=""></p>
<pre><code>修改编译环境:选中项目右键->Properties->Java Complier 如下图:
</code></pre><p><img src="http://i.imgur.com/OWOEZBE.png" alt=""></p>
<a id="more"></a>
<h2 id="2-2-导入源码"><a href="#2-2-导入源码" class="headerlink" title="2.2 导入源码"></a>2.2 导入源码</h2><pre><code>将下载的tomcat源码包解压开:
</code></pre><p><img src="http://i.imgur.com/7Dqz8Ry.png" alt=""></p>
<pre><code>修改bin目录为script(为什么要这样做?因为新建的Java项目中默认编译后的文件
存放目录是bin,这样做防止编译后的文件覆盖原来拷贝过去的内容。)
</code></pre><p><img src="http://i.imgur.com/9kHqG1Q.png" alt=""></p>
<pre><code>修改之后拷贝到Eclipse里新建的项目根目录下:
</code></pre><p><img src="http://i.imgur.com/p0itBCq.png" alt=""></p>
<pre><code>将项目中默认的src目录删掉,java和test作为源目录。如图这样操作选中java和test目录:
</code></pre><p><img src="http://i.imgur.com/HuvMgE0.png" alt=""></p>
<pre><code>转换之后如图:(这里有红叉,不要管它,后面就解决这个问题)
</code></pre><p><img src="http://i.imgur.com/rQHdiwu.png" alt=""></p>
<h2 id="2-3-添加需要的jar"><a href="#2-3-添加需要的jar" class="headerlink" title="2.3 添加需要的jar"></a>2.3 添加需要的jar</h2><pre><code>上面说到转换之后有红叉,很明显是缺少一些jar,这就需要我们自己手动添加了,所以我新建了一个depend的文件:
</code></pre><p><img src="http://i.imgur.com/DlWT9WE.png" alt=""></p>
<pre><code>缺少的这些jar包,我整理下统一放在云盘了,大家从下面的地址下载就可以了,不用再来回找了。
</code></pre><p><a href="http://pan.baidu.com/s/1eSjrr1o" target="_blank" rel="external">缺少的jar下载</a>,大家下载之后直接解压,然后放在depend下:<br><img src="http://i.imgur.com/FQ5eorf.png" alt=""></p>
<pre><code>把这些jar包加入到编译路径里,如图:
</code></pre><p><img src="http://i.imgur.com/bcaGd4p.png" alt=""></p>
<pre><code>此时发现test目录下还有错误,实际上是因为test里面用到了junit的一些注解,所以需要将junit4引进来
选中项目右键->Properties->Java Build Path如下图:
</code></pre><p><img src="http://i.imgur.com/Egwm8ZO.png" alt=""><br><img src="http://i.imgur.com/SXTAHII.png" alt=""></p>
<pre><code>自此整个项目应该可以编译了。
</code></pre><h2 id="2-4-build(验证能否构建成功)"><a href="#2-4-build(验证能否构建成功)" class="headerlink" title="2.4 build(验证能否构建成功)"></a>2.4 build(验证能否构建成功)</h2><pre><code>运行项目根目录下的build.xml,执行默认的ant任务,看看项目构建的有没有问题。
控制台输出"BUILD SUCCESSFUL"表示编译构建成功。如图:
</code></pre><p><img src="http://i.imgur.com/JExmB3J.png" alt=""><br><img src="http://i.imgur.com/6Qgs4TP.png" alt=""></p>
<pre><code>至此源码运行环境搭建完毕。
</code></pre>]]></content>
<summary type="html">
<h1 id="1、源码下载"><a href="#1、源码下载" class="headerlink" title="1、源码下载"></a>1、源码下载</h1><p><a href="http://apache.fayea.com/tomcat/tomcat-7/v7.0.70/src/apache-tomcat-7.0.70-src.zip">下载地址</a></p>
<h1 id="2、环境配置"><a href="#2、环境配置" class="headerlink" title="2、环境配置"></a>2、环境配置</h1><h2 id="2-1-新建eclipse项目"><a href="#2-1-新建eclipse项目" class="headerlink" title="2.1 新建eclipse项目"></a>2.1 新建eclipse项目</h2><pre><code>新建项目tc7.0.70,其他设置默认就好。
</code></pre><p><img src="http://i.imgur.com/MB1PvoU.png" alt=""></p>
<pre><code>项目建立好之后修改此项目的编译环境,
请选择编译环境为1.6,选择1.7会报错(tomcat-dbcp.jar依赖的是jdk1.6,
选择1.7在建立的时候会报错,这个大家可以尝试下看看是不是),下图就是选择编译环境为1.7的时候报的错误。
</code></pre><p><img src="http://i.imgur.com/NxcOK1m.png" alt=""></p>
<pre><code>修改编译环境:选中项目右键-&gt;Properties-&gt;Java Complier 如下图:
</code></pre><p><img src="http://i.imgur.com/OWOEZBE.png" alt=""></p>
</summary>
<category term="Tomcat源码" scheme="http://zhengweishan.oschina.io/categories/Tomcat%E6%BA%90%E7%A0%81/"/>
<category term="tomcat" scheme="http://zhengweishan.oschina.io/tags/tomcat/"/>
</entry>
<entry>
<title>kafka学习(四)---- Kafka整合SpringMVC实例(二)</title>
<link href="http://zhengweishan.oschina.io/2017/02/04/kafka%E5%AD%A6%E4%B9%A0%EF%BC%88%E5%9B%9B%EF%BC%89----%20Kafka%E6%95%B4%E5%90%88SpringMVC%E5%AE%9E%E4%BE%8B%EF%BC%88%E4%BA%8C%EF%BC%89/"/>
<id>http://zhengweishan.oschina.io/2017/02/04/kafka学习(四)---- Kafka整合SpringMVC实例(二)/</id>
<published>2017-02-03T16:00:00.000Z</published>
<updated>2017-03-01T08:46:49.305Z</updated>
<content type="html"><![CDATA[<h2 id="1、概述"><a href="#1、概述" class="headerlink" title="1、概述"></a>1、概述</h2><p>目前没有很好的整合Kafka的案例,自己参考着使用spring-integration-kafka框架写了一个:<br><a href="http://my.oschina.net/zhengweishan/blog/736213" target="_blank" rel="external">Kafka整合SpringMVC实例</a>,但同时也发现官方文档也不全,所以又用spring简单的实现了一下,感觉这个比使用spring-integration-kafka框架更简单一点,但是需要对kafka作深入的了解,废话不多说直接切入正题。</p>
<h2 id="2、实例"><a href="#2、实例" class="headerlink" title="2、实例"></a>2、实例</h2><p><strong>1. 安装Zookeeper</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/693163" target="_blank" rel="external">Zookeeper下载基本使用</a></p>
<p><strong>2. 安装Kafka</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/731330" target="_blank" rel="external">kafka基本概念以及环境搭建</a></p>
<a id="more"></a>
<p><strong>3. 创建spring项目(建议使用maven方式创建)</strong></p>
<blockquote>
<p>项目截图(小红叉不影响项目的启动)</p>
</blockquote>
<p><img src="http://i.imgur.com/wTOil58.png" alt=""></p>
<blockquote>
<p>pom.xml配置</p>
</blockquote>
<pre><code><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kafka.demo</groupId>
<artifactId>SpringWithKafka</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringWithKafka Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>4.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.6</version>
</dependency>
</dependencies>
<build>
<finalName>SpringWithKafka</finalName>
</build>
</project>
</code></pre><blockquote>
<p>kafka-producer.xml配置</p>
</blockquote>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:init.properties" />
<!-- 定义producer的参数 -->
<bean id="producerProperties" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="bootstrap.servers" value="${bootstrap.servers}"/>
<entry key="group.id" value="0"/>
<entry key="retries" value="10"/>
<entry key="batch.size" value="16384"/>
<entry key="linger.ms" value="1"/>
<entry key="buffer.memory" value="33554432"/>
<entry key="key.serializer" value="org.apache.kafka.common.serialization.IntegerSerializer"/>
<entry key="value.serializer" value="org.apache.kafka.common.serialization.StringSerializer"/>
</map>
</constructor-arg>
</bean>
<!-- 创建kafkatemplate需要使用的producerfactory bean -->
<bean id="producerFactory" class="org.springframework.kafka.core.DefaultKafkaProducerFactory">
<constructor-arg>
<ref bean="producerProperties"/>
</constructor-arg>
</bean>
<!-- 创建kafkatemplate,使用的时候,只需要注入这个bean,即可使用template的send消息方法 -->
<bean id="KafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate">
<constructor-arg ref="producerFactory"/>
<constructor-arg name="autoFlush" value="true"/>
<property name="defaultTopic" value="myTopic"/>
</bean>
</beans>
</code></pre><blockquote>
<p>kafka-consumer.xml配置 </p>
</blockquote>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:init.properties" />
<!-- 定义consumer的参数 -->
<bean id="consumerProperties" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="bootstrap.servers" value="${bootstrap.servers}"/>
<entry key="group.id" value="0"/>
<entry key="enable.auto.commit" value="true"/>
<entry key="auto.commit.interval.ms" value="1000"/>
<entry key="session.timeout.ms" value="15000"/>
<entry key="key.deserializer" value="org.apache.kafka.common.serialization.IntegerDeserializer"/>
<entry key="value.deserializer" value="org.apache.kafka.common.serialization.StringDeserializer"/>
</map>
</constructor-arg>
</bean>
<!-- 创建consumerFactory bean -->
<bean id="consumerFactory" class="org.springframework.kafka.core.DefaultKafkaConsumerFactory">
<constructor-arg>
<ref bean="consumerProperties"/>
</constructor-arg>
</bean>
<!-- 实际执行消息消费的类 -->
<bean id="messageListernerConsumerService" class="com.kafka.demo.service.KafkaConsumerService"/>
<!-- 消费者容器配置信息 -->
<bean id="containerProperties" class="org.springframework.kafka.listener.config.ContainerProperties">
<constructor-arg value="myTopic"/>
<property name="messageListener" ref="messageListernerConsumerService"/>
</bean>
<!-- 注册消费者容器到监听器 -->
<bean id="messageListenerContainer" class="org.springframework.kafka.listener.KafkaMessageListenerContainer" init-method="doStart">
<constructor-arg ref="consumerFactory"/>
<constructor-arg ref="containerProperties"/>
</bean>
</beans>
</code></pre><p>其他代码请参看实例源码:<a href="http://git.oschina.net/zhengweishan/Kafka_study_demo" target="_blank" rel="external">源码下载</a></p>
<h2 id="三、实例演示"><a href="#三、实例演示" class="headerlink" title="三、实例演示"></a>三、实例演示</h2><p>a、运行项目访问 <a href="http://localhost:8080/SpringMvcWithKafka/kafka/test" target="_blank" rel="external">http://localhost:8080/SpringMvcWithKafka/kafka/test</a> //测试地址<br>效果如图:</p>
<p><img src="http://i.imgur.com/H5UagCh.png" alt=""></p>
<p>b、查看kafka控制台信息输出,如下图:</p>
<p><img src="http://i.imgur.com/E1LMhg4.png" alt=""></p>
]]></content>
<summary type="html">
<h2 id="1、概述"><a href="#1、概述" class="headerlink" title="1、概述"></a>1、概述</h2><p>目前没有很好的整合Kafka的案例,自己参考着使用spring-integration-kafka框架写了一个:<br><a href="http://my.oschina.net/zhengweishan/blog/736213">Kafka整合SpringMVC实例</a>,但同时也发现官方文档也不全,所以又用spring简单的实现了一下,感觉这个比使用spring-integration-kafka框架更简单一点,但是需要对kafka作深入的了解,废话不多说直接切入正题。</p>
<h2 id="2、实例"><a href="#2、实例" class="headerlink" title="2、实例"></a>2、实例</h2><p><strong>1. 安装Zookeeper</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/693163">Zookeeper下载基本使用</a></p>
<p><strong>2. 安装Kafka</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/731330">kafka基本概念以及环境搭建</a></p>
</summary>
<category term="kafka" scheme="http://zhengweishan.oschina.io/categories/kafka/"/>
<category term="kafka" scheme="http://zhengweishan.oschina.io/tags/kafka/"/>
</entry>
<entry>
<title>kafka学习(三)----- Kafka整合SpringMVC实例</title>
<link href="http://zhengweishan.oschina.io/2017/02/03/kafka%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89----%20Kafka%E6%95%B4%E5%90%88SpringMVC%E5%AE%9E%E4%BE%8B/"/>
<id>http://zhengweishan.oschina.io/2017/02/03/kafka学习(三)---- Kafka整合SpringMVC实例/</id>
<published>2017-02-02T16:00:00.000Z</published>
<updated>2017-03-01T08:44:20.839Z</updated>
<content type="html"><![CDATA[<h2 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h2><p>kafka一个高吞吐量的分布式发布订阅消息系统。有关知识请参看:<a href="http://my.oschina.net/zhengweishan/blog/731330" target="_blank" rel="external">kafka基本概念以及环境搭建</a>,kafka整合springMVC需要用到一个开源框架:<a href="https://github.com/spring-projects/spring-integration-kafka" target="_blank" rel="external">spring-integration-kafka</a>,这个官方框架我就不介绍了,请自行百度。</p>
<h2 id="二、实例"><a href="#二、实例" class="headerlink" title="二、实例"></a>二、实例</h2><p><strong>1. 安装Zookeeper</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/693163" target="_blank" rel="external">Zookeeper下载基本使用</a></p>
<p><strong>2. 安装Kafka</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/731330" target="_blank" rel="external">kafka基本概念以及环境搭建</a><br><a id="more"></a></p>
<p><strong>3. 创建spring项目(建议使用maven方式创建)</strong></p>
<blockquote>
<p>项目截图(小红叉不影响项目的启动)</p>
</blockquote>
<p><img src="http://i.imgur.com/mzhKhlQ.png" alt=""></p>
<blockquote>
<p>pom.xml配置 </p>
</blockquote>
<pre><code><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kafka.demo</groupId>
<artifactId>SpringMvcWithKafka</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringMvcWithKafka Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>4.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-kafka</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
<build>
<finalName>SpringMvcWithKafka</finalName>
</build>
</project>
</code></pre><blockquote>
<p>spring-kafka-consumer.xml配置</p>
</blockquote>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-kafka="http://www.springframework.org/schema/integration/kafka"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/integration/kafka
http://www.springframework.org/schema/integration/kafka/spring-integration-kafka.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">
<!-- topic test conf -->
<int:channel id="inputFromKafka">
<int:dispatcher task-executor="kafkaMessageExecutor" />
</int:channel>
<!-- zookeeper配置 可以配置多个 -->
<int-kafka:zookeeper-connect id="zookeeperConnect"
zk-connect="localhost:2181" zk-connection-timeout="6000"
zk-session-timeout="6000" zk-sync-time="2000" />
<!-- channel配置 auto-startup="true" 否则接收不发数据 -->
<int-kafka:inbound-channel-adapter
id="kafkaInboundChannelAdapter" kafka-consumer-context-ref="consumerContext"
auto-startup="true" channel="inputFromKafka">
<int:poller fixed-delay="1" time-unit="MILLISECONDS" />
</int-kafka:inbound-channel-adapter>
<task:executor id="kafkaMessageExecutor" pool-size="8" keep-alive="120" queue-capacity="500" />
<bean id="kafkaDecoder" class="org.springframework.integration.kafka.serializer.common.StringDecoder" />
<bean id="consumerProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<prop key="auto.offset.reset">smallest</prop>
<prop key="socket.receive.buffer.bytes">10485760</prop> <!-- 10M -->
<prop key="fetch.message.max.bytes">5242880</prop>
<prop key="auto.commit.interval.ms">1000</prop>
</props>
</property>
</bean>
<!-- 消息接收的BEEN -->
<bean id="kafkaConsumerService" class="com.kafka.demo.service.impl.KafkaConsumerService" />
<!-- 指定接收的方法 -->
<int:outbound-channel-adapter channel="inputFromKafka"
ref="kafkaConsumerService" method="processMessage" />
<int-kafka:consumer-context id="consumerContext"
consumer-timeout="1000" zookeeper-connect="zookeeperConnect"
consumer-properties="consumerProperties">
<int-kafka:consumer-configurations>
<int-kafka:consumer-configuration
group-id="default1" value-decoder="kafkaDecoder" key-decoder="kafkaDecoder"
max-messages="5000">
<!-- 两个TOPIC配置 -->
<int-kafka:topic id="myTopic" streams="4" />
<int-kafka:topic id="testTopic" streams="4" />
</int-kafka:consumer-configuration>
</int-kafka:consumer-configurations>
</int-kafka:consumer-context>
</beans>
</code></pre><blockquote>
<p>spring-kafka-producer.xml配置</p>
</blockquote>
<pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-kafka="http://www.springframework.org/schema/integration/kafka"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/integration/kafka http://www.springframework.org/schema/integration/kafka/spring-integration-kafka.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<!-- commons config -->
<bean id="stringSerializer" class="org.apache.kafka.common.serialization.StringSerializer" />
<bean id="kafkaEncoder"
class="org.springframework.integration.kafka.serializer.avro.AvroReflectDatumBackedKafkaEncoder">
<constructor-arg value="java.lang.String" />
</bean>
<bean id="producerProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<prop key="topic.metadata.refresh.interval.ms">3600000</prop>
<prop key="message.send.max.retries">5</prop>
<prop key="serializer.class">kafka.serializer.StringEncoder</prop>
<prop key="request.required.acks">1</prop>
</props>
</property>
</bean>
<!-- topic test config -->
<int:channel id="kafkaTopicTest">
<int:queue />
</int:channel>
<int-kafka:outbound-channel-adapter
id="kafkaOutboundChannelAdapterTopicTest" kafka-producer-context-ref="producerContextTopicTest"
auto-startup="true" channel="kafkaTopicTest" order="3">
<int:poller fixed-delay="1000" time-unit="MILLISECONDS"
receive-timeout="1" task-executor="taskExecutor" />
</int-kafka:outbound-channel-adapter>
<task:executor id="taskExecutor" pool-size="5"
keep-alive="120" queue-capacity="500" />
<int-kafka:producer-context id="producerContextTopicTest"
producer-properties="producerProperties">
<int-kafka:producer-configurations>
<!-- 多个topic配置 -->
<int-kafka:producer-configuration
broker-list="localhost:9092" key-serializer="stringSerializer"
value-class-type="java.lang.String" value-serializer="stringSerializer"
topic="testTopic" />
<int-kafka:producer-configuration
broker-list="localhost:9092" key-serializer="stringSerializer"
value-class-type="java.lang.String" value-serializer="stringSerializer"
topic="myTopic" />
</int-kafka:producer-configurations>
</int-kafka:producer-context>
</beans>
</code></pre><p>其他代码请参看实例源码:<a href="http://git.oschina.net/zhengweishan/Kafka_study_demo" target="_blank" rel="external">源码下载</a></p>
<h2 id="三、实例演示"><a href="#三、实例演示" class="headerlink" title="三、实例演示"></a>三、实例演示</h2><p>a、先根据配置文件使用命令行创建两个topic,截图如下:</p>
<p><img src="http://i.imgur.com/w4q0VZE.png" alt=""></p>
<p>b、运行项目访问 <a href="http://localhost:8080/SpringMvcWithKafka/kafka/test" target="_blank" rel="external">http://localhost:8080/SpringMvcWithKafka/kafka/test</a> //测试地址<br>效果如图:</p>
<p><img src="http://i.imgur.com/aXG3zKL.png" alt=""></p>
<p>c、查看kafka控制台信息输出,如下图:</p>
<p><img src="http://i.imgur.com/QzFa2Je.png" alt=""></p>
<p>说明:如果使用最新版本的kafka,上面的例子可能就跑步起来了,猜测应该是kafka版本问题,所以推荐使用稳定版本。</p>
]]></content>
<summary type="html">
<h2 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h2><p>kafka一个高吞吐量的分布式发布订阅消息系统。有关知识请参看:<a href="http://my.oschina.net/zhengweishan/blog/731330">kafka基本概念以及环境搭建</a>,kafka整合springMVC需要用到一个开源框架:<a href="https://github.com/spring-projects/spring-integration-kafka">spring-integration-kafka</a>,这个官方框架我就不介绍了,请自行百度。</p>
<h2 id="二、实例"><a href="#二、实例" class="headerlink" title="二、实例"></a>二、实例</h2><p><strong>1. 安装Zookeeper</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/693163">Zookeeper下载基本使用</a></p>
<p><strong>2. 安装Kafka</strong></p>
<p><a href="http://my.oschina.net/zhengweishan/blog/731330">kafka基本概念以及环境搭建</a><br>
</summary>
<category term="kafka" scheme="http://zhengweishan.oschina.io/categories/kafka/"/>
<category term="kafka" scheme="http://zhengweishan.oschina.io/tags/kafka/"/>
</entry>
<entry>
<title>kafka学习(二)---- Kafka简单的Java版本的Hello World实例</title>
<link href="http://zhengweishan.oschina.io/2017/02/02/kafka%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89----%20Kafka%E7%AE%80%E5%8D%95%E7%9A%84Java%E7%89%88%E6%9C%AC%E7%9A%84HelloWorld%E5%AE%9E%E4%BE%8B/"/>
<id>http://zhengweishan.oschina.io/2017/02/02/kafka学习(二)---- Kafka简单的Java版本的HelloWorld实例/</id>
<published>2017-02-01T16:00:00.000Z</published>
<updated>2017-03-01T08:41:22.175Z</updated>
<content type="html"><![CDATA[<h2 id="1、开发环境"><a href="#1、开发环境" class="headerlink" title="1、开发环境"></a>1、开发环境</h2><p>我使用的是官网的kafka_2.11-0.10.0.0版本,最新的是kafka_2.11-0.10.0.1版本,大家自行下载安装配置。<a href="http://kafka.apache.org/downloads.html" target="_blank" rel="external">点击进入下载地址</a>,<a href="http://my.oschina.net/zhengweishan/blog/731330" target="_blank" rel="external">点击进入如何win下配置开发环境</a></p>
<p>##2、 创建项目 ##<br>两种方式:</p>
<p><strong>(a)普通的方式创建</strong></p>
<blockquote>
<p>注意:开发时候,需要将下载kafka-2.11-0.10.0.0.jar包加入到classpath下面,这个包包含了所有Kafka的api的实现。由于kafka是使用Scala编写的,所以可能下载的kafka中的libs文件中的kafka-2.11-0.10.0.0.jar放到项目中不能用,而且还依赖scala-library-2.11.8.jar,所以推荐使用第二种方式构建项目。</p>
</blockquote>
<p>项目结构图:</p>
<p><img src="http://i.imgur.com/Jjzv5Gv.png" alt=""></p>
<p><strong>(b)maven构建项目</strong><br>maven下载配置这里不再叙述,请参看:<a href="http://my.oschina.net/zhengweishan/blog/690195" target="_blank" rel="external">eclipse创建maven多模块项目</a>中有关maven的介绍。好处在于不用自己去添加依赖了,maven自己帮我们加载依赖。</p>
<p>项目结构图:</p>
<p><img src="http://i.imgur.com/R74N0K3.png" alt=""></p>
<a id="more"></a>
<h2 id="3、实例源码"><a href="#3、实例源码" class="headerlink" title="3、实例源码"></a>3、实例源码</h2><h3 id="3-1-生产者"><a href="#3-1-生产者" class="headerlink" title="3.1 生产者"></a>3.1 生产者</h3><pre><code>package com.kafka.demo;
import java.util.Date;
import java.util.Properties;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
/**
* @see https://cwiki.apache.org/confluence/display/KAFKA/0.8.0+Producer+Example
* @see http://kafka.apache.org/documentation.html#producerapi
* @author wesley
*
*/
public class ProducerDemo {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
int events = 20;
// @see http://kafka.apache.org/08/configuration.html-- 3.3 Producer
// Configs
// @see http://kafka.apache.org/documentation.html#producerconfigs
// 设置配置属性
Properties props = new Properties();
props.put("metadata.broker.list", "127.0.0.1:9092"); // 配置kafka的IP和端口
props.put("serializer.class", "kafka.serializer.StringEncoder");
// key.serializer.class默认为serializer.class
props.put("key.serializer.class", "kafka.serializer.StringEncoder");
// 可选配置,如果不配置,则使用默认的partitioner
props.put("partitioner.class", "com.kafka.demo.PartitionerDemo");
// 触发acknowledgement机制,否则是fire and forget,可能会引起数据丢失
// 值为0,1,-1,可以参考
props.put("request.required.acks", "1");
ProducerConfig config = new ProducerConfig(props);
// 创建producer
Producer<String, String> producer = new Producer<String, String>(config);
// 产生并发送消息
long start = System.currentTimeMillis();
for (long i = 0; i < events; i++) {
long runtime = new Date().getTime();
String ip = "192.168.1." + i;
String msg = runtime + "--www.kafkademo.com--" + ip;
// 如果topic不存在,则会自动创建,默认replication-factor为1,partitions为0
KeyedMessage<String, String> data = new KeyedMessage<String, String>("page_visits", ip, msg);
System.out.println("-----Kafka Producer----createMessage----" + data);
producer.send(data);
}
System.out.println("Time consuming:" + (System.currentTimeMillis() - start));
// 关闭producer
producer.close();
}
}
</code></pre><h3 id="3-2-生产者需要配置的Partition类"><a href="#3-2-生产者需要配置的Partition类" class="headerlink" title="3.2 生产者需要配置的Partition类"></a>3.2 生产者需要配置的Partition类</h3><pre><code>package com.kafka.demo;
import kafka.producer.Partitioner;
import kafka.utils.VerifiableProperties;
@SuppressWarnings("deprecation")
public class PartitionerDemo implements Partitioner {
public PartitionerDemo (VerifiableProperties props) {
}
public int partition(Object key, int a_numPartitions) {
int partition = 0;
String stringKey = (String) key;
int offset = stringKey.lastIndexOf('.');
if (offset > 0) {
partition = Integer.parseInt( stringKey.substring(offset+1)) % a_numPartitions;
}
return partition;
}
}
</code></pre><h4 id="运行之后的效果:"><a href="#运行之后的效果:" class="headerlink" title="运行之后的效果:"></a>运行之后的效果:</h4><p><img src="http://i.imgur.com/EAEoUzo.png" alt=""></p>
<h4 id="查看控制台:"><a href="#查看控制台:" class="headerlink" title="查看控制台:"></a>查看控制台:</h4><p><img src="http://i.imgur.com/TV4V8aL.png" alt=""></p>
<blockquote>
<p>红色部分就是新生成的待消费的信息。</p>
</blockquote>
<h3 id="3-3-消费者-单线程实例"><a href="#3-3-消费者-单线程实例" class="headerlink" title="3.3 消费者(单线程实例)"></a>3.3 消费者(单线程实例)</h3><pre><code>package com.kafka.demo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
/**
* @see http://kafka.apache.org/documentation.html#consumerapi
* @see https://cwiki.apache.org/confluence/display/KAFKA/Consumer+Group+Example
* @see https://cwiki.apache.org/confluence/display/KAFKA/0.8.0+SimpleConsumer+Example
* @author wesley
*
*/
public class ConsumerSimpleDemo extends Thread {
// 消费者连接
private final ConsumerConnector consumer;
// 要消费的话题
private final String topic;
public ConsumerSimpleDemo(String topic) {
consumer = kafka.consumer.Consumer.createJavaConsumerConnector(createConsumerConfig());
this.topic = topic;
}
// 配置相关信息
private static ConsumerConfig createConsumerConfig() {
Properties props = new Properties();
// props.put("zookeeper.connect","localhost:2181,10.XX.XX.XX:2181,10.XX.XX.XX:2181");
// 配置要连接的zookeeper地址与端口
props.put("zookeeper.connect", "127.0.0.1:2181");
// 配置zookeeper的组id
props.put("group.id", "group-1");
// 配置zookeeper连接超时间隔
props.put("zookeeper.session.timeout.ms", "10000");
// 配置zookeeper异步执行时间
props.put("zookeeper.sync.time.ms", "200");
// 配置自动提交时间间隔
props.put("auto.commit.interval.ms", "1000");
return new ConsumerConfig(props);
}
public void run() {
Map<String, Integer> topickMap = new HashMap<String, Integer>();
topickMap.put(topic, 1);
Map<String, List<KafkaStream<byte[], byte[]>>> streamMap = consumer.createMessageStreams(topickMap);
KafkaStream<byte[], byte[]> stream = streamMap.get(topic).get(0);
ConsumerIterator<byte[], byte[]> it = stream.iterator();
System.out.println("*********Results********");
while (true) {
if (it.hasNext()) {
// 打印得到的消息
System.err.println(Thread.currentThread() + " get data:" + new String(it.next().message()));
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ConsumerSimpleDemo consumerThread = new ConsumerSimpleDemo("page_visits");
consumerThread.start();
}
}
</code></pre><h4 id="运行之后的效果:-1"><a href="#运行之后的效果:-1" class="headerlink" title="运行之后的效果:"></a>运行之后的效果:</h4><p><img src="http://i.imgur.com/TGZSlHp.png" alt=""></p>
<h3 id="3-4-消费者-线程池实例"><a href="#3-4-消费者-线程池实例" class="headerlink" title="3.4 消费者(线程池实例)"></a>3.4 消费者(线程池实例)</h3><pre><code>package com.kafka.demo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
/* https://cwiki.apache.org/confluence/display/KAFKA/Consumer+Group+Example
* http://kafka.apache.org/documentation.html#consumerapi
*/
public class ConsumerDemo {
private final ConsumerConnector consumer;
private final String topic;
private ExecutorService executor;
public ConsumerDemo(String a_zookeeper, String a_groupId, String a_topic) {
consumer = Consumer.createJavaConsumerConnector(createConsumerConfig(a_zookeeper,a_groupId));
this.topic = a_topic;
}
public void shutdown() {
if (consumer != null)
consumer.shutdown();
if (executor != null)
executor.shutdown();
try {
if (!executor.awaitTermination(5000, TimeUnit.MILLISECONDS)) {
System.out.println("Timed out waiting for consumer threads to shut down, exiting uncleanly");
}
} catch (InterruptedException e) {
System.out.println("Interrupted during shutdown, exiting uncleanly");
}
}
public void run(int numThreads) {
System.out.println("-----Consumers begin to execute-------");
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put(topic, new Integer(numThreads));
Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer
.createMessageStreams(topicCountMap);
List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);
System.err.println("-----Need to consume content----"+streams);
// now launch all the threads
executor = Executors.newFixedThreadPool(numThreads);
// now create an object to consume the messages
int threadNumber = 0;
for (final KafkaStream<byte[], byte[]> stream : streams) {
System.out.println("-----Consumers begin to consume-------"+stream);
executor.submit(new ConsumerMsgTask(stream, threadNumber));
threadNumber++;
}
}
private static ConsumerConfig createConsumerConfig(String a_zookeeper,
String a_groupId) {
Properties props = new Properties();
// see http://kafka.apache.org/08/configuration.html --3.2 Consumer Configs
// http://kafka.apache.org/documentation.html#consumerconfigs
props.put("zookeeper.connect", a_zookeeper); //配置ZK地址
props.put("group.id", a_groupId); //必填字段
props.put("zookeeper.session.timeout.ms", "400");
props.put("zookeeper.sync.time.ms", "200");
props.put("auto.commit.interval.ms", "1000");
return new ConsumerConfig(props);
}
public static void main(String[] arg) {
String[] args = { "127.0.0.1:2181", "group-1", "page_visits", "10" };
String zooKeeper = args[0];
String groupId = args[1];
String topic = args[2];
int threads = Integer.parseInt(args[3]);
ConsumerDemo demo = new ConsumerDemo(zooKeeper, groupId, topic);
demo.run(threads);
try {
Thread.sleep(10000);
} catch (InterruptedException ie) {
}
demo.shutdown();
}
}
</code></pre><blockquote>
<p>注意:这里要调用处理消息的类</p>
</blockquote>
<h3 id="3-5-处理消息的类"><a href="#3-5-处理消息的类" class="headerlink" title="3.5 处理消息的类"></a>3.5 处理消息的类</h3><pre><code>package com.kafka.demo;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
public class ConsumerMsgTask implements Runnable {
private KafkaStream<byte[], byte[]> m_stream;
private int m_threadNumber;
public ConsumerMsgTask(KafkaStream<byte[], byte[]> stream, int threadNumber) {
m_threadNumber = threadNumber;
m_stream = stream;
}
public void run() {
System.out.println("-----Consumers begin to consume-------");
ConsumerIterator<byte[], byte[]> it = m_stream.iterator();
while (it.hasNext()){
System.out.println("Thread " + m_threadNumber + ": "+ new String(it.next().message()));
}
System.out.println("Shutting down Thread: " + m_threadNumber);
}
}
</code></pre><h4 id="运行效果图:"><a href="#运行效果图:" class="headerlink" title="运行效果图:"></a>运行效果图:</h4><p><img src="http://i.imgur.com/t4OVBh1.png" alt=""></p>
<p><img src="http://i.imgur.com/DujHiGq.png" alt=""></p>
<p><img src="http://i.imgur.com/42cPZ1T.png" alt=""></p>
<p>实例到此结束,大家可以多看看kafka的文档,多了解一些kafka的知识,这里只是演示了怎么用,其实也都是文档中的东西,自己总结了一下。</p>
<p><strong>说明</strong>:</p>
<p>为什么使用High Level Consumer?</p>
<p>有些场景下,从Kafka中读取消息的逻辑不处理消息的offset,仅仅是获取消息数据。High Level Consumer就提供了这种功能。首先要知道的是,High Level Consumer在ZooKeeper上保存最新的offset(从指定的分区中读取)。这个offset基于consumer group名存储。Consumer group名在Kafka集群上是全局性的,在启动新的consumer group的时候要小心集群上没有关闭的consumer。当一个consumer线程启动了,Kafka会将它加入到相同的topic下的相同consumer group里,并且触发重新分配。在重新分配时,Kafka将partition分配给consumer,有可能会移动一个partition给另一个consumer。如果老的、新的处理逻辑同时存在,有可能一些消息传递到了老的consumer上。使用High LevelConsumer首先要知道的是,它应该是多线程的。消费者线程的数量跟tipic的partition数量有关,它们之间有一些特定的规则:</p>
<ul>
<li><p>如果线程数量大于主题的分区数量,一些线程将得不到任何消息</p>
</li>
<li><p>如果分区数大于线程数,一些线程将得到多个分区的消息</p>
</li>
<li><p>如果一个线程处理多个分区的消息,它接收到消息的顺序是不能保证的。比如,先从分区10获取了5条消息,从分区11获取了6条消息,然后从分区10获取了5条,紧接着又从分区10获取了5条,虽然分区11还有消息。</p>
</li>
<li><p>添加更多了同consumer group的consumer将触发Kafka重新分配,某个分区本来分配给a线程的,从新分配后,有可能分配给了b线程。</p>
</li>
</ul>
<h2 id="4、参考资料:"><a href="#4、参考资料:" class="headerlink" title="4、参考资料:"></a>4、参考资料:</h2><ol>
<li><a href="http://kafka.apache.org/documentation.html" target="_blank" rel="external">http://kafka.apache.org/documentation.html</a></li>
<li><a href="https://cwiki.apache.org/confluence/display/KAFKA/Index" target="_blank" rel="external">https://cwiki.apache.org/confluence/display/KAFKA/Index</a></li>
</ol>
]]></content>
<summary type="html">
<h2 id="1、开发环境"><a href="#1、开发环境" class="headerlink" title="1、开发环境"></a>1、开发环境</h2><p>我使用的是官网的kafka_2.11-0.10.0.0版本,最新的是kafka_2.11-0.10.0.1版本,大家自行下载安装配置。<a href="http://kafka.apache.org/downloads.html">点击进入下载地址</a>,<a href="http://my.oschina.net/zhengweishan/blog/731330">点击进入如何win下配置开发环境</a></p>
<p>##2、 创建项目 ##<br>两种方式:</p>
<p><strong>(a)普通的方式创建</strong></p>
<blockquote>
<p>注意:开发时候,需要将下载kafka-2.11-0.10.0.0.jar包加入到classpath下面,这个包包含了所有Kafka的api的实现。由于kafka是使用Scala编写的,所以可能下载的kafka中的libs文件中的kafka-2.11-0.10.0.0.jar放到项目中不能用,而且还依赖scala-library-2.11.8.jar,所以推荐使用第二种方式构建项目。</p>
</blockquote>
<p>项目结构图:</p>
<p><img src="http://i.imgur.com/Jjzv5Gv.png" alt=""></p>
<p><strong>(b)maven构建项目</strong><br>maven下载配置这里不再叙述,请参看:<a href="http://my.oschina.net/zhengweishan/blog/690195">eclipse创建maven多模块项目</a>中有关maven的介绍。好处在于不用自己去添加依赖了,maven自己帮我们加载依赖。</p>
<p>项目结构图:</p>
<p><img src="http://i.imgur.com/R74N0K3.png" alt=""></p>
</summary>
<category term="kafka" scheme="http://zhengweishan.oschina.io/categories/kafka/"/>
<category term="kafka" scheme="http://zhengweishan.oschina.io/tags/kafka/"/>
</entry>
<entry>
<title>kafka学习(一) ---- 基本概念以及环境搭建</title>
<link href="http://zhengweishan.oschina.io/2017/02/01/kafka%E5%AD%A6%E4%B9%A0(%E4%B8%80)%20----%20%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
<id>http://zhengweishan.oschina.io/2017/02/01/kafka学习(一) ---- 基本概念以及环境搭建/</id>
<published>2017-01-31T16:00:00.000Z</published>
<updated>2017-03-01T08:44:51.568Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>由于项目涉及到kafka,自己以前没有接触过这方面的,学习了下,将搭建kafka运行环境同大家分享(单机搭建的Windows下的运行环境,Linux下的由于懒得装虚拟机就没有搭建,以后有时间在分享一次,搭建这个环境就是只为了学习)。</p>
<h2 id="1、基本概念"><a href="#1、基本概念" class="headerlink" title="1、基本概念"></a>1、基本概念</h2><h3 id="1-1-什么是kafka"><a href="#1-1-什么是kafka" class="headerlink" title="1.1 什么是kafka"></a>1.1 什么是kafka</h3><blockquote>
<p>Apache Kafka is publish-subscribe messaging rethought as a distributed commit log。Kafka is a distributed, partitioned, replicated commit log service. It provides the functionality of a messaging system, but with a unique design.//官方解释</p>
</blockquote>
<p>Kafka是一种高吞吐量的分布式发布订阅消息系统,它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。kafka对消息保存时根据Topic进行归类,发送消息者成为Producer,消息接受者成为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)成为broker。无论是kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息。<br>整体运行机制如下图:</p>
<p><img src="http://i.imgur.com/LmF8AIe.png" alt=""> </p>
<p>图来源于官网</p>
<p>更多内容请参看:<a href="http://kafka.apache.org/documentation.html" target="_blank" rel="external">官方文档</a>,这里就不一一翻译了。<br><a id="more"></a></p>
<h3 id="1-2-kafka的特点"><a href="#1-2-kafka的特点" class="headerlink" title="1.2 kafka的特点"></a>1.2 kafka的特点</h3><ul>
<li>通过I/O的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。</li>
<li>高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息。</li>
<li>支持通过Kafka服务器和消费机集群来分区消息。</li>
<li>支持Hadoop并行数据加载<h3 id="1-3-涉及的术语"><a href="#1-3-涉及的术语" class="headerlink" title="1.3 涉及的术语"></a>1.3 涉及的术语</h3></li>
<li>Broker<br>Kafka集群包含一个或多个服务器,这种服务器被称为broker</li>
<li>Topic<br>每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)</li>
<li>Partition<br>Partition是物理上的概念,每个Topic包含一个或多个Partition.</li>
<li>Producer<br>负责发布消息到Kafka broker</li>
<li>Consumer<br>消息消费者,向Kafka broker读取消息的客户端。</li>
<li>Consumer Group<br>每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)</li>
</ul>
<h2 id="2、环境搭建"><a href="#2、环境搭建" class="headerlink" title="2、环境搭建"></a>2、环境搭建</h2><h3 id="2-1-下载"><a href="#2-1-下载" class="headerlink" title="2.1 下载"></a>2.1 下载</h3><p><a href="http://kafka.apache.org/downloads.html" target="_blank" rel="external">下载地址</a>:<a href="http://kafka.apache.org/downloads.html" target="_blank" rel="external">http://kafka.apache.org/downloads.html</a></p>
<p><img src="http://i.imgur.com/qvyQfPW.png" alt=""></p>
<h3 id="2-2-单机搭建win下的运行环境"><a href="#2-2-单机搭建win下的运行环境" class="headerlink" title="2.2 单机搭建win下的运行环境"></a>2.2 单机搭建win下的运行环境</h3><p>官网没有win版本的,下载之后直接按照Linux下解压缩一样,解压之后文件结构如下:</p>
<p><img src="http://i.imgur.com/tsVmV0m.png" alt=""></p>
<h4 id="2-2-1-配置kafka"><a href="#2-2-1-配置kafka" class="headerlink" title="2.2.1 配置kafka"></a>2.2.1 配置kafka</h4><ol>
<li>进入Kafka配置目录:S:\devTools\kafka_2.11-0.10.0.0\config</li>
<li>编辑文件“server.properties” 主要配置日志所在目录(也可以使用默认的)</li>
<li>找到并编辑“log.dirs=/tmp/kafka-logs” to “log.dirs= S:/devTools/kafka_2.11-0.10.0.0/kafka-logs”</li>
<li>如果Zookeeper在某些其他的机器或集群上运行,可以将“zookeeper.connect:2181”修改为自定义IP与端口。在这个演示中我们使用了同一个机器,因此没必要做修改。文件中的Kafka端口和broker.id也是可以配置的。其他设置不变。</li>
<li>Kafka会按照默认在9092端口上运行,并连接zookeeper的默认端口:2181。</li>
</ol>
<h4 id="2-2-2-运行kafka"><a href="#2-2-2-运行kafka" class="headerlink" title="2.2.2 运行kafka"></a>2.2.2 运行kafka</h4><blockquote>
<p>重要:请确保在启动Kafka服务器前,Zookeeper实例已经准备好并开始运行。</p>
</blockquote>
<p>Zookeeper如何安装运行请参看我的博客:<a href="http://my.oschina.net/zhengweishan/blog/693163" target="_blank" rel="external">Dubbo与Zookeeper、SpringMVC整合和使用</a>中有关Zookeeper的介绍。</p>
<p>kafka在win下的启动命令都在bin目录下的Windows文件下,如下图:<br><img src="http://i.imgur.com/RItJHpN.png" alt=""></p>
<p>不知道是不是win脚本问题,直接运行这些命令都是失败的启动不了kafka,没有办法还是cmd启动吧。</p>
<ol>
<li>进入Kafka安装目录S:\devTools\kafka_2.11-0.10.0.0\</li>
<li>按下Shift+右键,选择“打开命令窗口”选项,打开命令行。</li>
</ol>
<p><img src="http://i.imgur.com/B1PBz1G.png" alt=""></p>
<ol>
<li>现在输入.\bin\windows\kafka-server-start.bat .\config\server.properties 并回车。</li>
</ol>
<p><img src="http://i.imgur.com/Ti0yQRQ.png" alt=""></p>
<ol>
<li>如果一切正常,命令行应当是这样:</li>
</ol>
<p><img src="http://i.imgur.com/8D3ZPjY.png" alt=""></p>
<ol>
<li>现在Kafka已经准备好并开始运行,可以创建主题来存储消息了。我们也能从Java/Scala代码中,或直接从命令行中生成或使用数据。</li>
</ol>
<h4 id="2-2-3-创建主题"><a href="#2-2-3-创建主题" class="headerlink" title="2.2.3 创建主题"></a>2.2.3 创建主题</h4><ol>
<li>现在创建主题,命名为“demo”,replication factor=1(因为只有1个Kafka服务器在运行)。如果集群中所运行的Kafka服务器不止1个,可以相应增加replication-factor,从而提高数据可用性和系统容错性。</li>
<li>在S:\devTools\kafka_2.11-0.10.0.0\bin\windows打开新的命令行。</li>
<li>输入下面的命令,回车:<blockquote>
<p>kafka-topics.bat –create –zookeeper localhost:2181 –replication-factor 1 –partitions 1 –topic demo</p>
</blockquote>
</li>
</ol>
<p>结果如下:</p>
<p><img src="http://i.imgur.com/KwVpYhY.png" alt=""></p>
<h4 id="2-2-4-创建生产者"><a href="#2-2-4-创建生产者" class="headerlink" title="2.2.4 创建生产者"></a>2.2.4 创建生产者</h4><ol>
<li>在S:\devTools\kafka_2.11-0.10.0.0\bin\windows打开新的命令行。</li>
<li>输入下面的命令,回车:</li>
</ol>
<blockquote>
<p>kafka-console-producer.bat –broker-list localhost:9092 –topic demo</p>
</blockquote>
<h4 id="2-2-5-创建消费者"><a href="#2-2-5-创建消费者" class="headerlink" title="2.2.5 创建消费者"></a>2.2.5 创建消费者</h4><ol>
<li>在S:\devTools\kafka_2.11-0.10.0.0\bin\windows打开新的命令行。</li>
<li>输入下面的命令,回车:</li>
</ol>
<blockquote>
<p>kafka-console-consumer.bat –zookeeper localhost:2181 –topic demo</p>
</blockquote>
<h4 id="2-2-6-演示"><a href="#2-2-6-演示" class="headerlink" title="2.2.6 演示"></a>2.2.6 演示</h4><p>执行以上的命令之后有两个窗口:一个生产者,一个消费者。<br><img src="http://i.imgur.com/jtleTlg.png" alt=""><br>在producer命令行中任意输入内容,回车;在其他consumer命令行中能看到相应消息。如果能够将消息推送到consumer端并显示出来的话,Kafka安装就完成了。</p>
<p>最后上个成功的截图:</p>
<p><img src="http://i.imgur.com/cc10oms.png" alt=""></p>
<h3 id="2-3-常用的命令"><a href="#2-3-常用的命令" class="headerlink" title="2.3 常用的命令"></a>2.3 常用的命令</h3><ol>
<li>列出主题:kafka-topics.bat –-list –-zookeeper localhost:2181</li>
<li>描述主题:kafka-topics.bat –-describe –-zookeeper localhost:2181 –-topic [Topic Name]</li>
<li>从头读取消息:kafka-console-consumer.bat –-zookeeper localhost:2181 –-topic [Topic Name] –-from –beginning</li>
<li>删除主题:kafka-run-class.bat kafka.admin.TopicCommand –-delete –-topic [topic_to_delete] –-zookeeper localhost:2181</li>
<li>演示一个删除的:</li>
</ol>
<p><img src="http://i.imgur.com/FpHhmUb.png" alt=""></p>
]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>由于项目涉及到kafka,自己以前没有接触过这方面的,学习了下,将搭建kafka运行环境同大家分享(单机搭建的Windows下的运行环境,Linux下的由于懒得装虚拟机就没有搭建,以后有时间在分享一次,搭建这个环境就是只为了学习)。</p>
<h2 id="1、基本概念"><a href="#1、基本概念" class="headerlink" title="1、基本概念"></a>1、基本概念</h2><h3 id="1-1-什么是kafka"><a href="#1-1-什么是kafka" class="headerlink" title="1.1 什么是kafka"></a>1.1 什么是kafka</h3><blockquote>
<p>Apache Kafka is publish-subscribe messaging rethought as a distributed commit log。Kafka is a distributed, partitioned, replicated commit log service. It provides the functionality of a messaging system, but with a unique design.//官方解释</p>
</blockquote>
<p>Kafka是一种高吞吐量的分布式发布订阅消息系统,它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。kafka对消息保存时根据Topic进行归类,发送消息者成为Producer,消息接受者成为Consumer,此外kafka集群有多个kafka实例组成,每个实例(server)成为broker。无论是kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性集群保存一些meta信息。<br>整体运行机制如下图:</p>
<p><img src="http://i.imgur.com/LmF8AIe.png" alt=""> </p>
<p>图来源于官网</p>
<p>更多内容请参看:<a href="http://kafka.apache.org/documentation.html">官方文档</a>,这里就不一一翻译了。<br>
</summary>
<category term="kafka" scheme="http://zhengweishan.oschina.io/categories/kafka/"/>
<category term="kafka" scheme="http://zhengweishan.oschina.io/tags/kafka/"/>
</entry>
<entry>
<title>JMS学习(四)-----Spring和ActiveMQ整合的完整实例</title>
<link href="http://zhengweishan.oschina.io/2017/01/25/JMS%E5%AD%A6%E4%B9%A0%EF%BC%88%E5%9B%9B%EF%BC%89Spring%E5%92%8CActiveMQ%E6%95%B4%E5%90%88%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E4%BE%8B/"/>
<id>http://zhengweishan.oschina.io/2017/01/25/JMS学习(四)Spring和ActiveMQ整合的完整实例/</id>
<published>2017-01-24T16:00:00.000Z</published>
<updated>2017-03-01T08:40:05.554Z</updated>
<content type="html"><![CDATA[<h2 id="1、前言"><a href="#1、前言" class="headerlink" title="1、前言"></a>1、前言</h2><p>我们基于Spring+JMS+ActiveMQ+Tomcat,做一个Spring4.2.6和ActiveMQ5.7.0整合实例,实现了Point-To-Point的异步队列消息和PUB/SUB(发布/订阅)模型,简单实例,不包含任何业务。</p>
<h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><p><img src="http://i.imgur.com/EGswcsP.png" alt=""></p>
<a id="more"></a>
<h2 id="2、配置文件"><a href="#2、配置文件" class="headerlink" title="2、配置文件"></a>2、配置文件</h2><h3 id="2-1-pom-xml"><a href="#2-1-pom-xml" class="headerlink" title="2.1 pom.xml"></a>2.1 pom.xml</h3><pre><code><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.activemq.demo</groupId>
<artifactId>SpringActiveMq</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringActiveMq Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.springframework.version>4.2.6.RELEASE</org.springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- activeMQ与spring整合所需要的包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- activeMQ -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.4.3</version>
</dependency>
</dependencies>
<build>
<finalName>SpringActiveMq</finalName>
</build>
</project>
</code></pre><h3 id="2-2-spring-activeMQ-xml"><a href="#2-2-spring-activeMQ-xml" class="headerlink" title="2.2 spring-activeMQ.xml"></a>2.2 spring-activeMQ.xml</h3><pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.0.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.7.0.xsd">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的pooledConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
<property name="sessionCacheSize" value="100" />
</bean>
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!--这个是队列目的地,点对点的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>queue</value>
</constructor-arg>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
<!-- 消息监听器 -->
<bean id="consumerMessageListener" class="com.activemq.demo.ConsumerMessageListener" />
<!-- 消息监听容器 -->
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="consumerMessageListener" />
</bean>
</beans>
</code></pre><h3 id="2-3-servlet-context-xml"><a href="#2-3-servlet-context-xml" class="headerlink" title="2.3 servlet-context.xml"></a>2.3 servlet-context.xml</h3><pre><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
default-lazy-init="false">
<mvc:annotation-driven >
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html; charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 静态资源 -->
<mvc:resources mapping="/resources/**" location="/resources/"/>
<!-- 中文乱码解决 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
<property name="messageConverters">
<list>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<context:component-scan base-package="com.activemq.demo"/>
</beans>
</code></pre><h3 id="2-4-web-xml"><a href="#2-4-web-xml" class="headerlink" title="2.4 web.xml"></a>2.4 web.xml</h3><pre><code><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>dubbo_consumer</display-name>
<description>dubbo_consumer test</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring-activeMQ.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
</code></pre><h2 id="3、部署运行"><a href="#3、部署运行" class="headerlink" title="3、部署运行"></a>3、部署运行</h2><p>部署之后访问:<a href="http://localhost:8081/SpringActiveMq/test/sendMessage,效果如下:" target="_blank" rel="external">http://localhost:8081/SpringActiveMq/test/sendMessage,效果如下:</a></p>
<p><img src="http://i.imgur.com/8lPxbUB.png" alt=""></p>
<p>ActiveMq控制台:</p>
<p><img src="http://i.imgur.com/gqBbYAH.png" alt=""></p>
<h2 id="4、spring-activeMQ-xml解析"><a href="#4、spring-activeMQ-xml解析" class="headerlink" title="4、spring-activeMQ.xml解析"></a>4、spring-activeMQ.xml解析</h2><h4 id="4-1-配置ConnectionFactory"><a href="#4-1-配置ConnectionFactory" class="headerlink" title="4.1 配置ConnectionFactory"></a>4.1 配置ConnectionFactory</h4><p>connectionFactory是Spring用于创建到JMS服务器链接的,Spring提供了多种connectionFactory,我们介绍两个SingleConnectionFactory和CachingConnectionFactory。</p>
<ol>
<li><p>SingleConnectionFactory:对于建立JMS服务器链接的请求会一直返回同一个链接,并且会忽略Connection的close方法调用。</p>
</li>
<li><p>CachingConnectionFactory:继承了SingleConnectionFactory,所以它拥有SingleConnectionFactory的所有功能,同时它还新增了缓存功能,它可以缓存Session、MessageProducer和MessageConsumer。我们使用CachingConnectionFactory来作为示例。</p>
</li>
</ol>
<p>Spring提供的ConnectionFactory只是Spring用于管理ConnectionFactory的,真正产生到JMS服务器链接的ConnectionFactory还得是由JMS服务厂商提供,并且需要把它注入到Spring提供的ConnectionFactory中。我们这里使用的是ActiveMQ实现的JMS,所以在我们这里真正的可以产生Connection的就应该是由ActiveMQ提供的ConnectionFactory。所以定义一个ConnectionFactory的完整代码应该如下所示:</p>
<pre><code> <!-- 连接池 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
</bean>
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的pooledConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
<property name="sessionCacheSize" value="100" />
</bean>
</code></pre><p>或者这样配置:</p>
<pre><code> <!-- ActiveMQ 连接工厂 -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码-->
<amq:connectionFactory id="amqConnectionFactory"
brokerURL="tcp://192.168.3.3:61616" userName="admin" password="admin" />
<!-- Spring Caching连接工厂 -->
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- 同上,同理 -->
<!-- <constructor-arg ref="amqConnectionFactory" /> -->
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="100" />
</bean>
</code></pre><h4 id="4-2-配置生产者"><a href="#4-2-配置生产者" class="headerlink" title="4.2 配置生产者"></a>4.2 配置生产者</h4><p>配置好ConnectionFactory之后我们就需要配置生产者。生产者负责产生消息并发送到JMS服务器。但是我们要怎么进行消息发送呢?通常是利用Spring为我们提供的JmsTemplate类来实现的,所以配置生产者其实最核心的就是配置消息发送的JmsTemplate。对于消息发送者而言,它在发送消息的时候要知道自己该往哪里发,为此,我们在定义JmsTemplate的时候需要注入一个Spring提供的ConnectionFactory对象。</p>
<p>在利用JmsTemplate进行消息发送的时候,我们需要知道发送哪种消息类型:一个是点对点的ActiveMQQueue,另一个就是支持订阅/发布模式的ActiveMQTopic。如下所示:</p>
<pre><code><!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 如果value是true pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="false" />
</bean>
</code></pre><h4 id="4-3-配置消费者"><a href="#4-3-配置消费者" class="headerlink" title="4.3 配置消费者"></a>4.3 配置消费者</h4><p>生产者往指定目的地Destination发送消息后,接下来就是消费者对指定目的地的消息进行消费了。那么消费者是如何知道有生产者发送消息到指定目的地Destination了呢?每个消费者对应每个目的地都需要有对应的MessageListenerContainer。对于消息监听容器而言,除了要知道监听哪个目的地之外,还需要知道到哪里去监听,也就是说它还需要知道去监听哪个JMS服务器,通过配置MessageListenerContainer的时候往里面注入一个ConnectionFactory来实现的。所以我们在配置一个MessageListenerContainer的时候有三个属性必须指定:一个是表示从哪里监听的ConnectionFactory;一个是表示监听什么的Destination;一个是接收到消息以后进行消息处理的MessageListener。</p>
<pre><code><!-- 消息监听器 -->
<bean id="consumerMessageListener" class="com.activemq.demo.ConsumerMessageListener" />
<!-- 消息监听容器 -->
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="consumerMessageListener" />
</bean>
</code></pre><h2 id="5、总结"><a href="#5、总结" class="headerlink" title="5、总结"></a>5、总结</h2><p>Spring提供了对JMS的支持,ActiveMQ提供了很好的实现,而此时我们已经将两者完美的结合在了一起。</p>
]]></content>
<summary type="html">
<h2 id="1、前言"><a href="#1、前言" class="headerlink" title="1、前言"></a>1、前言</h2><p>我们基于Spring+JMS+ActiveMQ+Tomcat,做一个Spring4.2.6和ActiveMQ5.7.0整合实例,实现了Point-To-Point的异步队列消息和PUB/SUB(发布/订阅)模型,简单实例,不包含任何业务。</p>
<h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><p><img src="http://i.imgur.com/EGswcsP.png" alt=""></p>
</summary>
<category term="JMS" scheme="http://zhengweishan.oschina.io/categories/JMS/"/>
<category term="JMS" scheme="http://zhengweishan.oschina.io/tags/JMS/"/>
<category term="ActiveMQ" scheme="http://zhengweishan.oschina.io/tags/ActiveMQ/"/>
</entry>
<entry>
<title>JMS学习(三)----- ActiveMQ简单的HelloWorld实例</title>
<link href="http://zhengweishan.oschina.io/2017/01/24/JMS%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89ActiveMQ%E7%AE%80%E5%8D%95%E7%9A%84HelloWorld%E5%AE%9E%E4%BE%8B/"/>
<id>http://zhengweishan.oschina.io/2017/01/24/JMS学习(三)ActiveMQ简单的HelloWorld实例/</id>
<published>2017-01-23T16:00:00.000Z</published>
<updated>2017-03-01T08:40:01.239Z</updated>
<content type="html"><![CDATA[<h2 id="开发环境"><a href="#开发环境" class="headerlink" title="开发环境"></a>开发环境</h2><p>我使用的是ActiveMQ 5.13.3 Release的Windows版,官网最新版是ActiveMQ 5.13.4 Release,大家可以自行下载,<a href="http://activemq.apache.org/download-archives.html" target="_blank" rel="external">下载地址</a>。</p>
<p>需要注意的是,开发时候,要将apache-activemq-5.13.3-bin.zip解压缩后里面的activemq-all-5.13.3.jar包加入到classpath下面,这个包包含了所有jms接口api的实现。</p>
<p>项目截图:</p>
<p><img src="http://i.imgur.com/pPp8eMU.png" alt=""></p>
<h2 id="ActiviteMQ消息有3中形式"><a href="#ActiviteMQ消息有3中形式" class="headerlink" title="ActiviteMQ消息有3中形式"></a>ActiviteMQ消息有3中形式</h2><p><strong>JMS 公共 ———-点对点域 ———-发布/订阅域</strong></p>
<p>ConnectionFactory ———- QueueConnectionFactory ———- TopicConnectionFactory</p>
<p>Connection ———- QueueConnection ———- TopicConnection</p>
<p>Destination ———- Queue ———- Topic</p>
<p>Session ———- QueueSession ———- TopicSession</p>
<p>MessageProducer ———- QueueSender ———- TopicPublisher</p>
<p>MessageConsumer ———- QueueReceiver ———- TopicSubscriber</p>
<p>(1)、点对点方式(point-to-point)</p>
<p>点对点的消息发送方式主要建立在 Message Queue,Sender,reciever上,Message Queue 存贮消息,Sneder 发送消息,receive接收消息.具体点就是Sender Client发送Message Queue ,而 receiver Cliernt从Queue中接收消息和”发送消息已接受”到Quere,确认消息接收。消息发送客户端与接收客户端没有时间上的依赖,发送客户端可以在任何时刻发送信息到Queue,而不需要知道接收客户端是不是在运行</p>
<p>(2)、发布/订阅 方式(publish/subscriber Messaging)</p>
<p>发布/订阅方式用于多接收客户端的方式.作为发布订阅的方式,可能存在多个接收客户端,并且接收端客户端与发送客户端存在时间上的依赖。一个接收端只能接收他创建以后发送客户端发送的信息。作为subscriber ,在接收消息时有两种方法,destination的receive方法,和实现message listener 接口的onMessage 方法。</p>
<a id="more"></a>
<h2 id="ActiviteMQ接收和发送消息基本流程"><a href="#ActiviteMQ接收和发送消息基本流程" class="headerlink" title="ActiviteMQ接收和发送消息基本流程"></a>ActiviteMQ接收和发送消息基本流程</h2><p><img src="http://i.imgur.com/g71ThMY.png" alt=""></p>
<p><strong>发送消息的基本步骤:</strong></p>
<p>(1)、创建连接使用的工厂类JMS ConnectionFactory</p>
<p>(2)、使用管理对象JMS ConnectionFactory建立连接Connection,并启动</p>
<p>(3)、使用连接Connection 建立会话Session</p>
<p>(4)、使用会话Session和管理对象Destination创建消息生产者MessageSender</p>
<p>(5)、使用消息生产者MessageSender发送消息</p>
<p><strong>消息接收者从JMS接受消息的步骤</strong></p>
<p>(1)、创建连接使用的工厂类JMS ConnectionFactory</p>
<p>(2)、使用管理对象JMS ConnectionFactory建立连接Connection,并启动</p>
<p>(3)、使用连接Connection 建立会话Session</p>
<p>(4)、使用会话Session和管理对象Destination创建消息接收者MessageReceiver</p>
<p>(5)、使用消息接收者MessageReceiver接受消息,需要用setMessageListener将MessageListener接口绑定到MessageReceiver消息接收者必须实现了MessageListener接口,需要定义onMessage事件方法。</p>
<h3 id="使用JMS方式发送接收消息"><a href="#使用JMS方式发送接收消息" class="headerlink" title="使用JMS方式发送接收消息"></a>使用JMS方式发送接收消息</h3><pre><code>package com.active.mq.demo;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class MQConnectionFactory {
private static final String USERNAME = ActiveMQConnection.DEFAULT_USER;//默认连接用户名
private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;//默认连接密码
private static final String BROKEURL = ActiveMQConnection.DEFAULT_BROKER_URL;//默认连接地址
private static ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD, BROKEURL);//连接工厂
/**
* 通过连接工厂获取连接
* @return
*/
public static Connection getConnection(){
Connection connection = null;
try {
connection = connectionFactory.createConnection();
} catch (JMSException e) {
e.printStackTrace();
}
return connection;
}
}
package com.active.mq.demo;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
public class JMSConsumer {
public static void main(String[] args) {
Connection connection = null;//连接
Session session = null;//会话 接受或者发送消息的线程
Destination destination;//消息的目的地
MessageConsumer messageConsumer;//消息的消费者
try {
//通过连接工厂获取连接
connection = MQConnectionFactory.getConnection();
//启动连接
connection.start();
//创建session
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建一个连接HelloWorld的消息队列
destination = session.createQueue("HelloWorld");
//创建消息消费者
messageConsumer = session.createConsumer(destination);
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);
if(textMessage != null){
System.out.println("收到的消息:" + textMessage.getText());
}else {
break;
}
}
//提交回话
session.commit();
} catch (JMSException e) {
e.printStackTrace();
}finally{
if(connection != null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(session !=null){
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
package com.active.mq.demo;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
public class JMSProducer {
//发送的消息数量
private static final int SENDNUM = 10;
public static void main(String[] args) {
//连接
Connection connection = null;
//会话 接受或者发送消息的线程
Session session = null;
//消息的目的地
Destination destination;
//消息生产者
MessageProducer messageProducer;
try {
//通过连接工厂获取连接
connection = MQConnectionFactory.getConnection();
//启动连接
connection.start();
//创建session
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//创建一个名称为HelloWorld的消息队列
destination = session.createQueue("HelloWorld");
//创建消息生产者
messageProducer = session.createProducer(destination);
//发送消息
sendMessage(session, messageProducer);
//提交回话
session.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(connection != null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(session !=null){
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* 发送消息
* @param session
* @param messageProducer 消息生产者
* @throws Exception
*/
public static void sendMessage(Session session,MessageProducer messageProducer) throws Exception{
for (int i = 0; i < JMSProducer.SENDNUM; i++) {
//创建一条文本消息
TextMessage message = session.createTextMessage("发送JMS消息第" + (i + 1) + "条");
System.out.println("发送消息:Activemq 发送JMS消息" + (i + 1));
//通过消息生产者发出消息
messageProducer.send(message);
}
}
}
</code></pre><h3 id="Queue队列方式发送点对点消息数据"><a href="#Queue队列方式发送点对点消息数据" class="headerlink" title="Queue队列方式发送点对点消息数据"></a>Queue队列方式发送点对点消息数据</h3><p>在获取工厂类中加入如下代码:</p>
<pre><code>private static QueueConnectionFactory queueConnectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD, BROKEURL);
/**
* 通过连接工厂获取连接(Queue方式)
* @return
*/
public static QueueConnection getQueueConnection(){
QueueConnection connection = null;
try {
connection = queueConnectionFactory.createQueueConnection();
} catch (JMSException e) {
e.printStackTrace();
}
return connection;
}
//消息生产者
package com.active.mq.demo;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
public class QueueProducer {
private static final int SEND_NUM = 10;
public static void main(String[] args) {
QueueConnection queueConnection = null;
QueueSession queueSession = null;
try {
// 通过工厂创建一个连接
queueConnection = MQConnectionFactory.getQueueConnection();
// 启动连接
queueConnection.start();
// 创建一个session会话
queueSession = queueConnection.createQueueSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Queue queue = queueSession.createQueue("QueueMsgDemo");
// 创建消息发送者
QueueSender sender = queueSession.createSender(queue);
// 设置持久化模式
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
sendMessage(queueSession, sender);
// 提交会话
queueSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭释放资源
if (queueSession != null) {
try {
queueSession.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (queueConnection != null) {
try {
queueConnection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
public static void sendMessage(QueueSession session, QueueSender sender) throws Exception {
for (int i = 0; i < SEND_NUM; i++) {
String message = "发送queue消息第" + (i + 1) + "条";
//创建一个Map集合信息
MapMessage map = session.createMapMessage();
map.setString("text", message);
map.setLong("time", System.currentTimeMillis());
System.out.println("ActiveMQ 发送queue消息:"+(i + 1));
sender.send(map);
}
}
}
//消费者
package com.active.mq.demo;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
public class QueueConsumer {
public static void main(String[] args) {
QueueConnection queueConnection = null;
QueueSession queueSession = null;
try {
// 通过工厂创建一个连接
queueConnection = MQConnectionFactory.getQueueConnection();
// 启动连接
queueConnection.start();
// 创建一个session会话
queueSession = queueConnection.createQueueSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Queue queue = queueSession.createQueue("QueueMsgDemo");
// 创建消息接收者
QueueReceiver receiver = queueSession.createReceiver(queue);
receiver.setMessageListener(new MessageListener() {
public void onMessage(Message msg) {
if (msg != null) {
MapMessage map = (MapMessage) msg;
try {
System.out.println(map.getLong("time") + "接收到消息#" + map.getString("text"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
// 休眠100ms再关闭
Thread.sleep(1000 * 100);
// 提交会话
queueSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭释放资源
if (queueSession != null) {
try {
queueSession.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (queueConnection != null) {
try {
queueConnection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
</code></pre><h3 id="Topic主题发布和订阅消息"><a href="#Topic主题发布和订阅消息" class="headerlink" title="Topic主题发布和订阅消息"></a>Topic主题发布和订阅消息</h3><p>在获取工厂类中加入如下代码:</p>
<pre><code> private static TopicConnectionFactory topicConnectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD, BROKEURL);
/**
* 通过连接工厂获取连接(Topic方式)
* @return
*/
public static TopicConnection getTopicConnection(){
TopicConnection topicConnection = null;
try {
topicConnection = topicConnectionFactory.createTopicConnection();
} catch (JMSException e) {
e.printStackTrace();
}
return topicConnection;
}
//生产者
package com.active.mq.demo;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
public class TopicProducer {
private static final int SEND_NUM = 10;
public static void main(String[] args) {
TopicConnection connection = null;
TopicSession session = null;
try {
// 通过工厂创建一个连接
connection = MQConnectionFactory.getTopicConnection();
// 启动连接
connection.start();
// 创建一个session会话
session = connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Topic topic = session.createTopic("TopicDemo");
// 创建消息发送者
TopicPublisher publisher = session.createPublisher(topic);
// 设置持久化模式
publisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
sendMessage(session, publisher);
// 提交会话
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭释放资源
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
public static void sendMessage(TopicSession session, TopicPublisher publisher) throws Exception {
for (int i = 0; i < SEND_NUM; i++) {
String message = "发送Topic消息第" + (i + 1) + "条";
MapMessage map = session.createMapMessage();
map.setString("text", message);
map.setLong("time", System.currentTimeMillis());
System.out.println("ActiveMQ 发送Topic消息:"+(i + 1));
publisher.send(map);
}
}
}
//消费者
package com.active.mq.demo;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
public class TopicConsumer {
public static void main(String[] args) {
TopicConnection connection = null;
TopicSession session = null;
try {
// 通过工厂创建一个连接
connection = MQConnectionFactory.getTopicConnection();
// 启动连接
connection.start();
// 创建一个session会话
session = connection.createTopicSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Topic topic = session.createTopic("TopicDemo");
// 创建消息消费者
TopicSubscriber subscriber = session.createSubscriber(topic);
subscriber.setMessageListener(new MessageListener() {
public void onMessage(Message msg) {
if (msg != null) {
MapMessage map = (MapMessage) msg;
try {
System.out.println(map.getLong("time") + "Topic接收消息#" + map.getString("text"));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
// 休眠100ms再关闭
Thread.sleep(1000 * 100);
// 提交会话
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭释放资源
if (session != null) {
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
</code></pre><h2 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h2><p>以使用JMS方式发送接收消息为例说明<br>1、首先,启动ActiveMQ<br>2、运行发送者,eclipse控制台输出,如下图:</p>
<p><img src="http://i.imgur.com/pxZmjXj.png" alt=""><br>3、查看ActiveMQ服务器,Queues内容如下:</p>
<p><img src="http://i.imgur.com/a7R59y3.png" alt=""></p>
<p>我们可以看到创建了一个名称为HelloWorld的消息队列,队列中有10条消息未被消费,我们也可以通过Browse查看是哪些消息,如果这些队列中的消息,被删除,消费者则无法消费。</p>
<p><img src="http://i.imgur.com/AQpd48x.png" alt=""></p>
<p>4、运行一下消费者,eclipse控制台打印消息,如下:</p>
<p><img src="http://i.imgur.com/T66lsxc.png" alt=""></p>
<p>5、我们在查看一下ActiveMQ服务器,Queues内容如下:</p>
<p><img src="http://i.imgur.com/l63Vrxg.png" alt=""></p>
<p>我们可以看到HelloWorld的消息队列发生变化,多一个消息者,队列中的10条消息被消费了,点击Browse查看,已经为空了。<br>点击Active Consumers,我们可以看到这个消费者的详细信息。</p>
<p><img src="http://i.imgur.com/44UYCg0.png" alt=""></p>
<p>实例到此就结束了,大家可以自己多看点ActiveMQ服务器的内容,进一步熟悉ActiveMQ。</p>
]]></content>
<summary type="html">
<h2 id="开发环境"><a href="#开发环境" class="headerlink" title="开发环境"></a>开发环境</h2><p>我使用的是ActiveMQ 5.13.3 Release的Windows版,官网最新版是ActiveMQ 5.13.4 Release,大家可以自行下载,<a href="http://activemq.apache.org/download-archives.html">下载地址</a>。</p>
<p>需要注意的是,开发时候,要将apache-activemq-5.13.3-bin.zip解压缩后里面的activemq-all-5.13.3.jar包加入到classpath下面,这个包包含了所有jms接口api的实现。</p>
<p>项目截图:</p>
<p><img src="http://i.imgur.com/pPp8eMU.png" alt=""></p>
<h2 id="ActiviteMQ消息有3中形式"><a href="#ActiviteMQ消息有3中形式" class="headerlink" title="ActiviteMQ消息有3中形式"></a>ActiviteMQ消息有3中形式</h2><p><strong>JMS 公共 ———-点对点域 ———-发布/订阅域</strong></p>
<p>ConnectionFactory ———- QueueConnectionFactory ———- TopicConnectionFactory</p>
<p>Connection ———- QueueConnection ———- TopicConnection</p>
<p>Destination ———- Queue ———- Topic</p>
<p>Session ———- QueueSession ———- TopicSession</p>
<p>MessageProducer ———- QueueSender ———- TopicPublisher</p>
<p>MessageConsumer ———- QueueReceiver ———- TopicSubscriber</p>
<p>(1)、点对点方式(point-to-point)</p>
<p>点对点的消息发送方式主要建立在 Message Queue,Sender,reciever上,Message Queue 存贮消息,Sneder 发送消息,receive接收消息.具体点就是Sender Client发送Message Queue ,而 receiver Cliernt从Queue中接收消息和”发送消息已接受”到Quere,确认消息接收。消息发送客户端与接收客户端没有时间上的依赖,发送客户端可以在任何时刻发送信息到Queue,而不需要知道接收客户端是不是在运行</p>
<p>(2)、发布/订阅 方式(publish/subscriber Messaging)</p>
<p>发布/订阅方式用于多接收客户端的方式.作为发布订阅的方式,可能存在多个接收客户端,并且接收端客户端与发送客户端存在时间上的依赖。一个接收端只能接收他创建以后发送客户端发送的信息。作为subscriber ,在接收消息时有两种方法,destination的receive方法,和实现message listener 接口的onMessage 方法。</p>
</summary>
<category term="JMS" scheme="http://zhengweishan.oschina.io/categories/JMS/"/>
<category term="JMS" scheme="http://zhengweishan.oschina.io/tags/JMS/"/>
<category term="ActiveMQ" scheme="http://zhengweishan.oschina.io/tags/ActiveMQ/"/>
</entry>
<entry>
<title>JMS学习(二)----- ActiveMQ简单介绍以及安装</title>
<link href="http://zhengweishan.oschina.io/2017/01/23/JMS%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89ActiveMQ%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D%E4%BB%A5%E5%8F%8A%E5%AE%89%E8%A3%85/"/>
<id>http://zhengweishan.oschina.io/2017/01/23/JMS学习(二)ActiveMQ简单介绍以及安装/</id>
<published>2017-01-22T16:00:00.000Z</published>
<updated>2017-03-01T08:39:57.456Z</updated>
<content type="html"><![CDATA[<h2 id="1、简介"><a href="#1、简介" class="headerlink" title="1、简介"></a>1、简介</h2><p>ActiveMQ是一个易于使用的消息中间件(MOM:Message Orient middleware)。</p>
<p>消息中间件有很多的用途和优点: </p>
<ol>
<li>将数据从一个应用程序传送到另一个应用程序,或者从软件的一个模块传送到另外一个模块; </li>
<li>负责建立网络通信的通道,进行数据的可靠传送。 </li>
<li>保证数据不重发,不丢失 </li>
<li>能够实现跨平台操作,能够为不同操作系统上的软件集成技工数据传送服务</li>
</ol>
<p>首先我们先说下什么是MQ,MQ英文名MessageQueue,中文名也就是大家用的消息队列,干嘛用的呢,说白了就是一个消息的接受和转发的容器,可用于消息推送。下面介绍主题,就是ActiveMQ:</p>
<p>ActiveMQ是由Apache出品的,一款最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。</p>
<a id="more"></a>
<h2 id="2、下载与配置"><a href="#2、下载与配置" class="headerlink" title="2、下载与配置"></a>2、下载与配置</h2><h3 id="2-1-下载"><a href="#2-1-下载" class="headerlink" title="2.1 下载"></a>2.1 下载</h3><p><a href="http://activemq.apache.org/download.html" target="_blank" rel="external">官网下载ActiveMQ</a> 现在ActiveMQ最新的版本是5.13.3。</p>
<h3 id="2-2-启动"><a href="#2-2-启动" class="headerlink" title="2.2 启动"></a>2.2 启动</h3><p>下载之后解压缩目录结构如图:</p>
<p><img src="http://i.imgur.com/i6foU0f.png" alt=""></p>
<p>从它的目录来说,还是很简单的: </p>
<ul>
<li>bin存放的是脚本文件</li>
<li>conf存放的是基本配置文件</li>
<li>data存放的是日志文件</li>
<li>docs存放的是说明文档</li>
<li>examples存放的是简单的实例</li>
<li>lib存放的是activemq所需jar包</li>
<li>webapps用于存放项目的目录</li>
</ul>
<p>启动ActiveMQ:<br>进去bin目录下你会发现下图这样的内容:</p>
<p><img src="http://i.imgur.com/zfysAGw.png" alt=""></p>
<p>win32文件夹是32位操作系统的启动命令,win64文件夹是64位操作系统的启动命令,文件内容都是如下:</p>
<p><img src="http://i.imgur.com/ICmekqG.png" alt=""></p>
<p>这样就会有三个activemq.bat脚本文件,如果直接启动bin目录下的这个脚本文件,直接闪退(这个问题一直不知道怎么回事,回头看看脚本执行文件),启动不了服务。如果根据自己的操作系统选择对应的执行脚本则可以启动成功,可以看到下图的效果。</p>
<p><img src="http://i.imgur.com/RxA5Ksa.png" alt=""></p>
<p>从上图我们可以看到activemq的存放地址,以及浏览器要访问的地址. </p>
<p>ActiveMQ默认使用的TCP连接端口是61616, 通过查看该端口的信息可以测试ActiveMQ是否成功启动 netstat -an|find “61616” 如图:</p>
<p><img src="http://i.imgur.com/XPNcEm6.png" alt=""></p>
<p>ActiveMQ默认启动时,启动了内置的jetty服务器,提供一个用于监控ActiveMQ的admin应用。<br>url:<a href="http://127.0.0.1:8161/admin/" target="_blank" rel="external">http://127.0.0.1:8161/admin/</a> 用户名和密码都是admin</p>
<p><img src="http://i.imgur.com/BHPuZPw.png" alt=""></p>
<p><img src="http://i.imgur.com/zrlOcHP.png" alt=""></p>
<p>至此,服务端启动完毕</p>
]]></content>
<summary type="html">
<h2 id="1、简介"><a href="#1、简介" class="headerlink" title="1、简介"></a>1、简介</h2><p>ActiveMQ是一个易于使用的消息中间件(MOM:Message Orient middleware)。</p>
<p>消息中间件有很多的用途和优点: </p>
<ol>
<li>将数据从一个应用程序传送到另一个应用程序,或者从软件的一个模块传送到另外一个模块; </li>
<li>负责建立网络通信的通道,进行数据的可靠传送。 </li>
<li>保证数据不重发,不丢失 </li>
<li>能够实现跨平台操作,能够为不同操作系统上的软件集成技工数据传送服务</li>
</ol>
<p>首先我们先说下什么是MQ,MQ英文名MessageQueue,中文名也就是大家用的消息队列,干嘛用的呢,说白了就是一个消息的接受和转发的容器,可用于消息推送。下面介绍主题,就是ActiveMQ:</p>
<p>ActiveMQ是由Apache出品的,一款最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。</p>
</summary>
<category term="JMS" scheme="http://zhengweishan.oschina.io/categories/JMS/"/>
<category term="JMS" scheme="http://zhengweishan.oschina.io/tags/JMS/"/>
<category term="ActiveMQ" scheme="http://zhengweishan.oschina.io/tags/ActiveMQ/"/>
</entry>
<entry>
<title>JMS学习(一)-----基本概念</title>
<link href="http://zhengweishan.oschina.io/2017/01/22/JMS%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"/>
<id>http://zhengweishan.oschina.io/2017/01/22/JMS学习(一)基本概念/</id>
<published>2017-01-21T16:00:00.000Z</published>
<updated>2017-01-22T10:51:10.585Z</updated>
<content type="html"><![CDATA[<h2 id="1、简介"><a href="#1、简介" class="headerlink" title="1、简介"></a>1、简介</h2><p>JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。</p>
<h2 id="2、体系架构"><a href="#2、体系架构" class="headerlink" title="2、体系架构"></a>2、体系架构</h2><p><strong>JMS提供者</strong>:连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。</p>
<p><strong>JMS客户</strong>:生产或消费基于消息的Java的应用程序或对象。</p>
<p><strong>JMS生产者</strong>:创建并发送消息的JMS客户。</p>
<p><strong>JMS消费者</strong>:接收消息的JMS客户。</p>
<p><strong>JMS消息</strong>:包括可以在JMS客户之间传递的数据的对象</p>
<p><strong>JMS队列</strong>:一个容纳那些被发送的等待阅读的消息的区域。与队列名字所暗示的意思不同,消息的接受顺序并不一定要与消息的发送顺序相同。一旦一个消息被阅读,该消息将被从队列中移走。</p>
<p><strong>JMS主题</strong>:一种支持发送消息给多个订阅者的机制。</p>
<a id="more"></a>
<h2 id="3、对象模型"><a href="#3、对象模型" class="headerlink" title="3、对象模型"></a>3、对象模型</h2><p><img src="http://i.imgur.com/oQGLPVh.gif" alt=""></p>
<p><strong>(1) ConnectionFactory(连接工厂)</strong></p>
<p>创建Connection对象的工厂,针对两种不同的jms消息模型,分别有QueueConnectionFactory和TopicConnectionFactory两种。可以通过JNDI来查找ConnectionFactory对象。</p>
<p><strong>(2) Destination(JMS目的)</strong></p>
<p>Destination的意思是消息生产者的消息发送目标或者说消息消费者的消息来源。对于消息生产者来说,它的Destination是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,它的Destination也是某个队列或主题(即消息来源)。</p>
<p>所以,Destination实际上就是两种类型的对象:Queue、Topic可以通过JNDI来查找Destination。</p>
<p><strong>(3) Connection(JMS连接)</strong></p>
<p>Connection表示在客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection可以产生一个或多个Session。跟ConnectionFactory一样,Connection也有两种类型:QueueConnection和TopicConnection。</p>
<p><strong>(4) Session(JMS会话)</strong></p>
<p>Session是我们操作消息的接口。可以通过session创建生产者、消费者、消息等。Session提供了事务的功能。当我们需要使用session发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。同样,也分QueueSession和TopicSession。</p>
<p><strong>(5) 消息的生产者(JMS生产者)</strong></p>
<p>消息生产者由Session创建,并用于将消息发送到Destination。同样,消息生产者分两种类型:QueueSender和TopicPublisher。可以调用消息生产者的方法(send或publish方法)发送消息。</p>
<p><strong>(6) 消息消费者(JMS消费者)</strong></p>
<p>消息消费者由Session创建,用于接收被发送到Destination的消息。两种类型:QueueReceiver和TopicSubscriber。可分别通过session的createReceiver(Queue)或createSubscriber(Topic)来创建。当然,也可以session的creatDurableSubscriber方法来创建持久化的订阅者。</p>
<p><strong>(7) MessageListener</strong></p>
<p>消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的onMessage方法。EJB中的MDB(Message-Driven Bean)就是一种MessageListener。</p>
<p><strong>(8) JMS消息</strong></p>
<p>JMS消息通常有两种类型:</p>
<p>① 点对点(Point-to-Point)。在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列(javax.jms.Queue)相关联。</p>
<p>② 发布/订阅(Publish/Subscribe)。发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题(javax.jms.Topic)关联。</p>
<h2 id="4、消息类型分析"><a href="#4、消息类型分析" class="headerlink" title="4、消息类型分析"></a>4、消息类型分析</h2><p><strong>(1)Point-to-Point</strong></p>
<p><img src="http://i.imgur.com/CP8tFXO.jpg" alt=""></p>
<p>(Point-to-Point)模型是基于队列(Queue)的,对于PTP消息模型而言,它的消息目的是一个消息队列(Queue),消息生产者每次发送消息总是把消息送入消息队列中,消息消费者总是从消息队列中读取消息.先进队列的消息将先被消息消费者读取.</p>
<p>特点:</p>
<ol>
<li>每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)</li>
<li>发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列</li>
<li>接收者在成功接收消息之后需向队列应答成功</li>
</ol>
<p><strong>(2)Publish/Subscribe</strong></p>
<p><img src="http://i.imgur.com/F4mjvpZ.jpg" alt=""></p>
<p>定义了如何向一个内容节点发布和订阅消息,这些节点被称作主题(topic). 主题可以被认为是消息的传输中介,发布者(publisher)发布消息到主题,订阅者(subscribe) 从主题订阅消息.主题使得消息订阅者和消息发布者保持互相独立,不需要接触即可保证消息的传送.</p>
<p>特点:</p>
<ol>
<li>每个消息可以有多个消费者</li>
<li>发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。</li>
<li>为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。</li>
</ol>
<h2 id="5、好处"><a href="#5、好处" class="headerlink" title="5、好处"></a>5、好处</h2><p>应用程序A 发送一条消息到消息服务器(也就是JMS Provider)的某个目得地(Destination),然后消息服务器把消息转发给应用程序B。因为应用程序A 和应用程序B 没有直接的代码关连,所以两者实现了解偶。如下图:</p>
<p><img src="http://i.imgur.com/ILqZigV.jpg" alt=""></p>
<p>结合上面的图总结以下三点好处:</p>
<ol>
<li>提供消息灵活性</li>
<li>松散耦合</li>
<li>异步性</li>
</ol>
]]></content>
<summary type="html">
<h2 id="1、简介"><a href="#1、简介" class="headerlink" title="1、简介"></a>1、简介</h2><p>JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。</p>
<h2 id="2、体系架构"><a href="#2、体系架构" class="headerlink" title="2、体系架构"></a>2、体系架构</h2><p><strong>JMS提供者</strong>:连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。</p>
<p><strong>JMS客户</strong>:生产或消费基于消息的Java的应用程序或对象。</p>
<p><strong>JMS生产者</strong>:创建并发送消息的JMS客户。</p>
<p><strong>JMS消费者</strong>:接收消息的JMS客户。</p>
<p><strong>JMS消息</strong>:包括可以在JMS客户之间传递的数据的对象</p>
<p><strong>JMS队列</strong>:一个容纳那些被发送的等待阅读的消息的区域。与队列名字所暗示的意思不同,消息的接受顺序并不一定要与消息的发送顺序相同。一旦一个消息被阅读,该消息将被从队列中移走。</p>
<p><strong>JMS主题</strong>:一种支持发送消息给多个订阅者的机制。</p>
</summary>
<category term="JMS" scheme="http://zhengweishan.oschina.io/categories/JMS/"/>
<category term="JMS" scheme="http://zhengweishan.oschina.io/tags/JMS/"/>
<category term="ActiveMQ" scheme="http://zhengweishan.oschina.io/tags/ActiveMQ/"/>
</entry>
<entry>
<title>使用Hexo+OSChina+git搭建自己的博客网站</title>
<link href="http://zhengweishan.oschina.io/2017/01/22/Hexo+OSChina/"/>
<id>http://zhengweishan.oschina.io/2017/01/22/Hexo+OSChina/</id>
<published>2017-01-21T16:00:00.000Z</published>
<updated>2017-01-22T03:50:05.209Z</updated>
<content type="html"><![CDATA[<h1 id="基础工具介绍"><a href="#基础工具介绍" class="headerlink" title="基础工具介绍"></a>基础工具介绍</h1><h3 id="码云Pages"><a href="#码云Pages" class="headerlink" title="码云Pages"></a>码云Pages</h3><p>码云 Pages 是一个免费的静态网页托管服务,您可以使用码云 Pages 托管博客、项目官网等静态网页。如果您使用过 Github Pages 那么您会很快上手使用码云的Pages服务。</p>
<p>官方介绍:<a href="http://www.oschina.net/news/73980/gitosc-pages" target="_blank" rel="external">http://www.oschina.net/news/73980/gitosc-pages</a></p>
<h3 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h3><p>Hexo 是一个简单地、轻量地、基于Node的一个静态博客框架,可以方便的生成静态网页托管在github和Heroku上,引用Hexo作者 <a href="https://github.com/hexojs/hexo" target="_blank" rel="external">[@tommy351]</a> 的话:</p>
<p>快速、简单且功能强大的 Node.js 博客框架。A fast, simple & powerful blog framework, powered by Node.js.</p>
<p>官网地址:<a href="https://hexo.io" target="_blank" rel="external">https://hexo.io</a></p>
<h3 id="git"><a href="#git" class="headerlink" title="git"></a>git</h3><p>Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。</p>
<p>官网地址:<a href="https://git-scm.com" target="_blank" rel="external">https://git-scm.com</a><br><a id="more"></a></p>
<h1 id="搭建环境"><a href="#搭建环境" class="headerlink" title="搭建环境"></a>搭建环境</h1><h3 id="码云Pages使用"><a href="#码云Pages使用" class="headerlink" title="码云Pages使用"></a>码云Pages使用</h3><p>这里使用开源中国码云,没有使用github(你懂的,支持下国内软件),没有开源中国码云的小伙伴赶紧注册了。具体可以参见 :<a href="http://www.oschina.net/news/73980/gitosc-pages" target="_blank" rel="external">官网</a> 的介绍以及使用,这里就不在啰里啰嗦的介绍了。</p>
<p>因为我想以根目录访问网站,所以我创建了一个这样子的项目:</p>
<p><a href="https://git.oschina.net/zhengweishan/zhengweishan" target="_blank" rel="external">https://git.oschina.net/zhengweishan/zhengweishan</a>.</p>
<p>启动码云Pages,就可以访问了,访问地址:</p>
<p><a href="http://zhengweishan.oschina.io/">http://zhengweishan.oschina.io/</a></p>
<p>官方说明如下:</p>
<p><strong>*如果你想以根目录的形式访问自己的静态网站,只需要建立一个与自己个性地址同名的项目即可,如<a href="http://git.oschina.net/ipvb" target="_blank" rel="external">http://git.oschina.net/ipvb</a> 这个用户,想要创建一个自己的站点,但不想以子目录的方式访问,想以ipvb.oschina.io 直接访问,那么他就可以创建一个名字为ipvb的项目<a href="http://git.oschina.net/ipvb/ipvb" target="_blank" rel="external">http://git.oschina.net/ipvb/ipvb</a> 部署完成后,就可以以<a href="http://ipvb.oschina.io" target="_blank" rel="external">http://ipvb.oschina.io</a> 进行访问了。</strong> *</p>
<h3 id="git安装"><a href="#git安装" class="headerlink" title="git安装"></a>git安装</h3><p>请参考<a href="http://www.runoob.com/git/git-install-setup.html" target="_blank" rel="external">http://www.runoob.com/git/git-install-setup.html</a>,这里就不在累赘的介绍了。</p>
<h3 id="Node-js安装"><a href="#Node-js安装" class="headerlink" title="Node.js安装"></a>Node.js安装</h3><p>请参考<a href="http://www.runoob.com/nodejs/nodejs-install-setup.html" target="_blank" rel="external">http://www.runoob.com/nodejs/nodejs-install-setup.html</a>,这里就不在累赘的介绍了。可能有人会问:为什么要安装Node.js?很简单嘛,我们的Hexo是基于Node的一个静态博客框架嘛。</p>
<h3 id="Hexo安装"><a href="#Hexo安装" class="headerlink" title="Hexo安装"></a>Hexo安装</h3><ul>
<li><p><strong>安装</strong></p>
<p> 如果您的电脑中已经安装上述必备程序,那么恭喜您!接下来只需要使用 npm 即可完成 Hexo 的安装(可以参看<a href="https://hexo.io/zh-cn/docs/" target="_blank" rel="external">官网文档</a>)。打开命令窗口输入下面代码:</p>
<p> <code>npm install hexo-cli -g</code></p>
<p> 安装成功后输入hexo 如果得到下面这个结果,恭喜你!安装成功!</p>
<p> <img src="http://i.imgur.com/W3lmqUy.png" alt=""></p>
<p> Hexo常用命令请参考:<a href="https://hexo.io/zh-cn/docs/commands.html" target="_blank" rel="external">https://hexo.io/zh-cn/docs/commands.html</a></p>
</li>
<li><p><strong>本地运行</strong></p>
<p> 安装 Hexo 完成后,Hexo 将会在指定文件夹中新建所需要的文件。这里本地先新建了与博客地址一样的文件夹:<br> zhengweishan.oschina.io.然后请进入你创建的改文件夹的上级目录,在该目录下打开命令行窗口,依次执行下列命令:</p>
<ol>
<li><code>hexo init zhengweishan.oschina.io</code></li>
<li><code>cd zhengweishan.oschina.io</code></li>
<li><code>npm install #install before start blogging</code></li>
<li><p><code>hexo generate #生成静态文件到项目根目录的public文件夹中</code></p>
<p>新建完成后,指定文件夹的目录如下:</p>
<p><strong>.</strong></p>
<p>├── .deploy #需要部署的文件</p>
<p>├── node_modules #Hexo插件</p>
<p>├── public #生成的静态网页文件</p>
<p>├── scaffolds #模板</p>
<p>├── source #博客正文和其他源文件, 404 favicon CNAME 等都应该放在这里</p>
<p>| ├── _drafts #草稿</p>
<p>| └── _posts #文章</p>
<p>├── themes #主题</p>
<p>├── _config.yml #全局配置文件</p>
<p>└── package.json</p>
<p>进行到这步后就可以先在本地运行下,看看效果了。执行下面命令:</p>
</li>
<li><p><code>cd zhengweishan.oschina.io</code></p>
</li>
<li><code>npm install #install before start blogging</code></li>
<li><p><code>hexo server #运行本地服务</code></p>
<p>浏览器输入<a href="http://localhost:4000" target="_blank" rel="external">http://localhost:4000</a>就可以看到效果。如下:</p>
<p><img src="http://i.imgur.com/Umw13aq.png" alt=""></p>
</li>
</ol>
</li>
<li><p><strong>远程发布</strong></p>
<p> 注册一个码云帐号,并创建一个项目,这里使用已经创建好的项目<a href="https://git.oschina.net/zhengweishan/zhengweishan" target="_blank" rel="external">https://git.oschina.net/zhengweishan/zhengweishan</a>,然后获取git地址:<a href="https://git.oschina.net/zhengweishan/zhengweishan.git" target="_blank" rel="external">https://git.oschina.net/zhengweishan/zhengweishan.git</a></p>
<p> 这里使用git将项目中public文件夹下的文件管理起来,并推送到码云上。借用一个插件来帮助我们完成,安装 hexo-deployer-git。安装代码如下:<br> <code>npm install hexo-deployer-git --save</code><br> 配置项目根目录_config.yml 文件,修改deploy的值,如下图:</p>
<p> <img src="http://i.imgur.com/7eS6yvB.png" alt=""> </p>
<p> 参考:<a href="https://hexo.io/docs/deployment.html" target="_blank" rel="external">https://hexo.io/docs/deployment.html</a></p>
<p> 修改完后在命令窗口执行下面命令:</p>
<ol>
<li><code>cd zhengweishan.oschina.io</code></li>
<li><code>npm install #install before start blogging</code></li>
<li><p><code>hexo deploy #一键部署功能</code></p>
<p>之后会弹出一个对话框,输入码云的帐号密码。大家最后在安装好git以后配置一个全局的文件,这样就可以不用每次提交都输入账号密码了。</p>
<p>部署成功之后,登录码云,查看之前创建的项目中出现了public文件夹中的文件,这时候代表之前的部署是成功的。</p>
<p>最后启动码云的Pages功能,详细参看:<a href="http://www.oschina.net/news/73980/gitosc-pages" target="_blank" rel="external">http://www.oschina.net/news/73980/gitosc-pages</a></p>
<p>启动之后如下图,</p>
<p><img src="http://i.imgur.com/qWvBXKb.png" alt=""></p>
<p>然后浏览器输入生成的网址就可以访问了,如下图所示:</p>
<p><img src="http://i.imgur.com/r6WAvKY.png" alt=""></p>
</li>
</ol>
</li>
</ul>
<p>看到之前和本地启动一样的效果了,这样子博客网站就部署完成了。</p>
]]></content>
<summary type="html">
<h1 id="基础工具介绍"><a href="#基础工具介绍" class="headerlink" title="基础工具介绍"></a>基础工具介绍</h1><h3 id="码云Pages"><a href="#码云Pages" class="headerlink" title="码云Pages"></a>码云Pages</h3><p>码云 Pages 是一个免费的静态网页托管服务,您可以使用码云 Pages 托管博客、项目官网等静态网页。如果您使用过 Github Pages 那么您会很快上手使用码云的Pages服务。</p>
<p>官方介绍:<a href="http://www.oschina.net/news/73980/gitosc-pages">http://www.oschina.net/news/73980/gitosc-pages</a></p>
<h3 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h3><p>Hexo 是一个简单地、轻量地、基于Node的一个静态博客框架,可以方便的生成静态网页托管在github和Heroku上,引用Hexo作者 <a href="https://github.com/hexojs/hexo">[@tommy351]</a> 的话:</p>
<p>快速、简单且功能强大的 Node.js 博客框架。A fast, simple &amp; powerful blog framework, powered by Node.js.</p>
<p>官网地址:<a href="https://hexo.io">https://hexo.io</a></p>
<h3 id="git"><a href="#git" class="headerlink" title="git"></a>git</h3><p>Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。</p>
<p>官网地址:<a href="https://git-scm.com">https://git-scm.com</a><br>
</summary>
<category term="Hexo" scheme="http://zhengweishan.oschina.io/categories/Hexo/"/>
<category term="Hexo" scheme="http://zhengweishan.oschina.io/tags/Hexo/"/>
</entry>
<entry>
<title>JAVA常用开发环境配置</title>
<link href="http://zhengweishan.oschina.io/2017/01/22/java%E5%B8%B8%E7%94%A8%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
<id>http://zhengweishan.oschina.io/2017/01/22/java常用开发环境配置/</id>
<published>2017-01-21T16:00:00.000Z</published>
<updated>2017-01-22T10:40:15.897Z</updated>
<content type="html"><![CDATA[<h2 id="1、jdk配置"><a href="#1、jdk配置" class="headerlink" title="1、jdk配置"></a>1、jdk配置</h2><h3 id="1、1下载安装"><a href="#1、1下载安装" class="headerlink" title="1、1下载安装"></a>1、1下载安装</h3><p><a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html" target="_blank" rel="external">http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html</a> 下载安装就好(选择和你系统一样的版本32位或者64位)</p>
<h3 id="1、2环境配置"><a href="#1、2环境配置" class="headerlink" title="1、2环境配置"></a>1、2环境配置</h3><pre><code>计算机-->属性-->高级系统设置-->环境变量
</code></pre><p><img src="http://i.imgur.com/xP2tKUb.png" alt=""></p>
<pre><code>在系统变量中新建 变量名:JAVA_HOME
变量值:C:\Program Files\Java\jdk1.7.0_80 (ps:如果你没有改变默认路径复制就可以,如果修改过请选择jdk的安装目录)
JAVA_HOME是用来表示jdk的安装目录。
</code></pre><p><img src="http://i.imgur.com/VHMZxz7.png" alt=""></p>
<pre><code>配置JAVA_HOME的原因是:(1)方便引用。(2)其他软件会引用约定好的JAVA_HOME变量。比如tomcat就需要引用JAVA_HOME。
在系统变量中查找 Path 编辑
追加新的变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/BGVgS7z.png" alt=""></p>
<pre><code>在系统变量中新建
变量名:CLASSPATH
变量值:.;%JAVA_HOME%lib;%JAVA_HOME%lib\tools.jar;
(ps:前面有个 .; 这个是告诉JDK,搜索CLASS时先查找当前目录的CLASS文件 )
</code></pre><p><img src="http://i.imgur.com/1IRscXU.png" alt=""></p>
<pre><code>验证是否成功
win+R 输入cmd 进入命令提示符界面
输入java -version (ps:java空格-version)
查看当前jdk的版本,显示版本信息 则说明安装和配置成功。
</code></pre><p><img src="http://i.imgur.com/DE1ZqFN.png" alt=""><br><a id="more"></a></p>
<h2 id="2、tomcat配置"><a href="#2、tomcat配置" class="headerlink" title="2、tomcat配置"></a>2、tomcat配置</h2><h3 id="2、1下载安装"><a href="#2、1下载安装" class="headerlink" title="2、1下载安装"></a>2、1下载安装</h3><p><a href="http://tomcat.apache.org/download-70.cgi" target="_blank" rel="external">http://tomcat.apache.org/download-70.cgi</a> 下载解压到你指定的地方,选择和你系统一样的版本32位或者64位<br>下载页面:<br><img src="http://i.imgur.com/ISuO7w4.png" alt=""></p>
<h3 id="2、2环境配置"><a href="#2、2环境配置" class="headerlink" title="2、2环境配置"></a>2、2环境配置</h3><pre><code>步骤同1.2
在系统变量中新建 变量名:CATALINA_HOME
变量值:S:\devTools\apache-tomcat-7.0.69
</code></pre><p><img src="http://i.imgur.com/ks0ds4b.png" alt=""></p>
<pre><code>在系统变量中查找 CLASSPATH 编辑
追加新的变量值:%CATALINA_HOME%\common\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/zKplzau.png" alt=""></p>
<pre><code>在系统变量中查找 Path 编辑
追加新的变量值:%CATALINA_HOME%\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/iNopGL6.png" alt=""></p>
<pre><code>验证是否成功
找到安装路径下的bin 文件夹,找到里面的startup.bat执行文件,运行,然后执行下面的操作。
打开浏览器,输入http://localhost:8080.如果出现下面的内容说明成功了。
</code></pre><p><img src="http://i.imgur.com/zYt9sLQ.png" alt=""></p>
<h2 id="3、maven配置"><a href="#3、maven配置" class="headerlink" title="3、maven配置"></a>3、maven配置</h2><h3 id="3、1下载安装"><a href="#3、1下载安装" class="headerlink" title="3、1下载安装"></a>3、1下载安装</h3><p><a href="https://maven.apache.org/download.cgi" target="_blank" rel="external">https://maven.apache.org/download.cgi</a> 下载解压到指定的地方就可以。下载页面:<br><img src="http://i.imgur.com/IFsu1oD.png" alt=""></p>
<h3 id="3、2环境配置"><a href="#3、2环境配置" class="headerlink" title="3、2环境配置"></a>3、2环境配置</h3><pre><code>步骤同1.2
在系统变量中新建 变量名:MAVEN_HOME
变量值:S:\devTools\apache-maven-3.2.3
</code></pre><p><img src="http://i.imgur.com/MuV95kj.png" alt=""></p>
<pre><code>在系统变量中查找 Path 编辑
追加新的变量值:%MAVEN_HOME%\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/ZCSj31a.png" alt=""></p>
<pre><code>验证是否成功
win+R 输入cmd 进入命令提示符界面
输入mvn -version (ps:mvn空格-version)
查看当前mvn的版本,显示版本信息 则说明安装和配置成功。
</code></pre><p><img src="http://i.imgur.com/i2ekoy0.png" alt=""></p>
<h2 id="4、ant配置"><a href="#4、ant配置" class="headerlink" title="4、ant配置"></a>4、ant配置</h2><h3 id="4、1下载安装"><a href="#4、1下载安装" class="headerlink" title="4、1下载安装"></a>4、1下载安装</h3><p><a href="http://ant.apache.org/bindownload.cgi" target="_blank" rel="external">http://ant.apache.org/bindownload.cgi</a> 下载解压到指定的地方就可以。下载页面:<br><img src="http://i.imgur.com/H2P91Xr.png" alt=""></p>
<h3 id="4、2环境配置"><a href="#4、2环境配置" class="headerlink" title="4、2环境配置"></a>4、2环境配置</h3><pre><code>步骤同1.2
在系统变量中新建 变量名:ANT_HOME
变量值:S:\devTools\apache-ant-1.9.7
</code></pre><p><img src="http://i.imgur.com/lAwWl0y.png" alt=""></p>
<pre><code>在系统变量中查找 Path 编辑
追加新的变量值:%ANT_HOME%\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/zH4WBl5.png" alt=""></p>
<pre><code>验证是否成功
win+R 输入cmd 进入命令提示符界面
输入ant -version (ps:ant空格-version)
查看当前mvn的版本,显示版本信息 则说明安装和配置成功。
</code></pre><p><img src="http://i.imgur.com/6CT2akK.png" alt=""></p>
<h2 id="5、eclipse整合maven配置"><a href="#5、eclipse整合maven配置" class="headerlink" title="5、eclipse整合maven配置"></a>5、eclipse整合maven配置</h2><p>参考我的博客:<a href="http://my.oschina.net/zhengweishan/blog/690195" target="_blank" rel="external">eclipse创建maven多模块项目</a> 第三部分:<a href="http://my.oschina.net/zhengweishan/blog/690195" target="_blank" rel="external">Eclipse配置maven</a></p>
]]></content>
<summary type="html">
<h2 id="1、jdk配置"><a href="#1、jdk配置" class="headerlink" title="1、jdk配置"></a>1、jdk配置</h2><h3 id="1、1下载安装"><a href="#1、1下载安装" class="headerlink" title="1、1下载安装"></a>1、1下载安装</h3><p><a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html">http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html</a> 下载安装就好(选择和你系统一样的版本32位或者64位)</p>
<h3 id="1、2环境配置"><a href="#1、2环境配置" class="headerlink" title="1、2环境配置"></a>1、2环境配置</h3><pre><code>计算机--&gt;属性--&gt;高级系统设置--&gt;环境变量
</code></pre><p><img src="http://i.imgur.com/xP2tKUb.png" alt=""></p>
<pre><code>在系统变量中新建 变量名:JAVA_HOME
变量值:C:\Program Files\Java\jdk1.7.0_80 (ps:如果你没有改变默认路径复制就可以,如果修改过请选择jdk的安装目录)
JAVA_HOME是用来表示jdk的安装目录。
</code></pre><p><img src="http://i.imgur.com/VHMZxz7.png" alt=""></p>
<pre><code>配置JAVA_HOME的原因是:(1)方便引用。(2)其他软件会引用约定好的JAVA_HOME变量。比如tomcat就需要引用JAVA_HOME。
在系统变量中查找 Path 编辑
追加新的变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
(ps:原来Path的变量值末尾如果没有;号,先输入;号再输入上面的代码)
</code></pre><p><img src="http://i.imgur.com/BGVgS7z.png" alt=""></p>
<pre><code>在系统变量中新建
变量名:CLASSPATH
变量值:.;%JAVA_HOME%lib;%JAVA_HOME%lib\tools.jar;
(ps:前面有个 .; 这个是告诉JDK,搜索CLASS时先查找当前目录的CLASS文件 )
</code></pre><p><img src="http://i.imgur.com/1IRscXU.png" alt=""></p>
<pre><code>验证是否成功
win+R 输入cmd 进入命令提示符界面
输入java -version (ps:java空格-version)
查看当前jdk的版本,显示版本信息 则说明安装和配置成功。
</code></pre><p><img src="http://i.imgur.com/DE1ZqFN.png" alt=""><br>
</summary>
<category term="JAVA" scheme="http://zhengweishan.oschina.io/categories/JAVA/"/>
<category term="JAVA" scheme="http://zhengweishan.oschina.io/tags/JAVA/"/>
</entry>
<entry>
<title>JVM简介及工作原理分析</title>
<link href="http://zhengweishan.oschina.io/2017/01/20/jvm/"/>
<id>http://zhengweishan.oschina.io/2017/01/20/jvm/</id>
<published>2017-01-19T16:00:00.000Z</published>
<updated>2017-01-20T07:23:48.909Z</updated>
<content type="html"><![CDATA[<h2 id="1、什么是JVM"><a href="#1、什么是JVM" class="headerlink" title="1、什么是JVM"></a>1、什么是JVM</h2><p>JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。</p>
<p>简单来说,JVM是用于执行Java应用程序和字节码的软件模块,并且可以将字节码转换为特定硬件和特定操作系统的本地代码。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行,这就是Java的能够“一次编译,到处运行”的原因。</p>
<p>JVM包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。</p>
<h2 id="2、JRE-JDK-JVM是什么关系"><a href="#2、JRE-JDK-JVM是什么关系" class="headerlink" title="2、JRE/JDK/JVM是什么关系"></a>2、JRE/JDK/JVM是什么关系</h2><p>JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。</p>
<p>JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。</p>
<p>JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。<a id="more"></a></p>
<h2 id="3、JVM体系结构"><a href="#3、JVM体系结构" class="headerlink" title="3、JVM体系结构"></a>3、JVM体系结构</h2><p><img src="http://i.imgur.com/5JmCfMv.jpg" alt=""></p>
<p>JVM的内部体系结构分为三部分(图片来自网络):</p>
<p>(1)类装载器(ClassLoader)子系统</p>
<pre><code>用来装载.class文件
</code></pre><p>(2)执行引擎</p>
<pre><code>执行字节码,或者执行本地方法
</code></pre><p>(3)运行时数据区</p>
<pre><code>方法区,堆,java栈,PC寄存器,本地方法栈
</code></pre><h2 id="4、JVM工作原理"><a href="#4、JVM工作原理" class="headerlink" title="4、JVM工作原理"></a>4、JVM工作原理</h2><p>JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种基于下层的操作系统和硬件平台并利用软件方法来实现的抽象的计算机,可以在上面执行java的字节码程序。java编译器只需面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译器,编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。可以用下图来表示编译执行的简化过程:</p>
<p><img src="http://i.imgur.com/ALjoef3.png" alt=""></p>
<h2 id="5、JVM执行过程"><a href="#5、JVM执行过程" class="headerlink" title="5、JVM执行过程"></a>5、JVM执行过程</h2><blockquote>
<p>1、加载class文件;</p>
<p>2、分配内存;</p>
<p>3、解释字节码成机器码;</p>
<p>4、运行过程垃圾收集;</p>
<p>5、结束。</p>
</blockquote>
<p>JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。</p>
<p>JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机。 操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境:</p>
<blockquote>
<p>1) 创建JVM装载环境和配置 </p>
<p>2) 装载JVM.dll </p>
<p>3) 初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例</p>
<p>4) 调用JNIEnv实例装载并处理class类。</p>
</blockquote>
<h2 id="6、JVM的生命周期"><a href="#6、JVM的生命周期" class="headerlink" title="6、JVM的生命周期"></a>6、JVM的生命周期</h2><h3 id="a、两个概念"><a href="#a、两个概念" class="headerlink" title="a、两个概念"></a>a、两个概念</h3><p>JVM实例和JVM执行引擎实例</p>
<blockquote>
<ul>
<li><p>JVM实例对应了一个独立运行的Java程序 (进程级别)</p>
</li>
<li><p>JVM执行引擎实例则对应了属于用户运行程序的线程 (线程级别)</p>
</li>
</ul>
</blockquote>
<h3 id="b、JVM的生命周期"><a href="#b、JVM的生命周期" class="headerlink" title="b、JVM的生命周期"></a>b、JVM的生命周期</h3><blockquote>
<p>JVM实例的诞生</p>
</blockquote>
<p> 当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点</p>
<blockquote>
<p>JVM实例的运行</p>
</blockquote>
<p> main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。</p>
<blockquote>
<p>JVM实例的消亡</p>
</blockquote>
<p>当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程 序也可以使用Runtime类或者System.exit()来退出。</p>
<h2 id="7、ClassLoader(类加载器)"><a href="#7、ClassLoader(类加载器)" class="headerlink" title="7、ClassLoader(类加载器)"></a>7、ClassLoader(类加载器)</h2><h3 id="a、JVM整个类加载过程"><a href="#a、JVM整个类加载过程" class="headerlink" title="a、JVM整个类加载过程"></a>a、JVM整个类加载过程</h3><p>JVM将整个类加载过程划分为了三个步骤:</p>
<p><strong><em>(1)装载</em></strong></p>
<p> 装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID。</p>
<p><strong><em>(2)链接</em></strong></p>
<p> 链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。在完成了校验后,JVM初始化类中的静态变量,并将其值赋为默认值。最后一步为对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。</p>
<p><strong><em>(3)初始化</em></strong></p>
<p> 初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:</p>
<ol>
<li>调用了new;</li>
<li>反射调用了类中的方法;</li>
<li>子类调用了初始化;</li>
<li>JVM启动过程中指定的初始化类。</li>
</ol>
<h3 id="b、JVM类加载顺序"><a href="#b、JVM类加载顺序" class="headerlink" title="b、JVM类加载顺序"></a>b、JVM类加载顺序</h3><p>JVM有两种类加载器:</p>
<blockquote>
<ul>
<li><p>启动类装载器:是JVM实现的一部分</p>
</li>
<li><p>用户自定义类装载器:是Java程序的一部分,必须是ClassLoader类的子类</p>
</li>
</ul>
</blockquote>
<p>当JVM启动时,由Bootstrap向User-Defined方向加载类;应用进行ClassLoader时,由User-Defined向Bootstrap方向查找并加载类;</p>
<p><strong>1. Bootstrap ClassLoader</strong></p>
<p>这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。</p>
<p><strong>2. Extension ClassLoader</strong></p>
<p>JVM用此classloader来加载扩展功能的一些jar包。</p>
<p><strong>3. System ClassLoader</strong></p>
<p>JVM用此classloader来加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。</p>
<p><strong>4. User-Defined ClassLoader</strong></p>
<p>User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。</p>
<p>有关ClassLoader抽象类的几个关键方法:</p>
<blockquote>
<p><strong>loadClass</strong></p>
</blockquote>
<p>此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法</p>
<blockquote>
<p><strong>findLoadedClass</strong></p>
</blockquote>
<p>此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。</p>
<blockquote>
<p><strong>findClass</strong></p>
</blockquote>
<p>此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。</p>
<blockquote>
<p><strong>findSystemClass</strong></p>
</blockquote>
<p>此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。</p>
<blockquote>
<p><strong>defineClass</strong></p>
</blockquote>
<p>此方法负责将二进制的字节码转换为Class对象</p>
<blockquote>
<p><strong>resolveClass</strong></p>
</blockquote>
<p>此方法负责完成Class对象的链接,如已链接过,则会直接返回。</p>
<h2 id="8、执行引擎"><a href="#8、执行引擎" class="headerlink" title="8、执行引擎"></a>8、执行引擎</h2><p>JVM通过执行引擎来完成字节码的执行,在执行过程中JVM采用的是自己的一套指令系统,每个线程在创建后,都会产生一个程序计数器(pc)和栈(Stack),其中程序计数器中存放了下一条将要执行的指令,Stack中存放Stack Frame,表示的为当前正在执行的方法,每个方法的执行都会产生Stack Frame,Stack Frame中存放了传递给方法的参数、方法内的局部变量以及操作数栈,操作数栈用于存放指令运算的中间结果,指令负责从操作数栈中弹出参与运算的操作数,指令执行完毕后再将计算结果压回到操作数栈,当方法执行完毕后则从Stack中弹出,继续其他方法的执行。</p>
<p>在执行方法时JVM提供了四种指令来执行:</p>
<blockquote>
<p>(1)invokestatic:调用类的static方法</p>
<p>(2)invokevirtual:调用对象实例的方法</p>
<p>(3)invokeinterface:将属性定义为接口来进行调用</p>
<p>(4)invokespecial:JVM对于初始化对象(Java构造器的方法为:<init>)以及调用对象实例中的私有方法时。</init></p>
</blockquote>
<p>主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行</p>
<blockquote>
<p>(1)解释属于第一代JVM,</p>
<p>(2)即时编译JIT属于第二代JVM,</p>
<p>(3)自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。</p>
</blockquote>
<h2 id="9、JVM运行时数据区"><a href="#9、JVM运行时数据区" class="headerlink" title="9、JVM运行时数据区"></a>9、JVM运行时数据区</h2><p><img src="http://i.imgur.com/6XGqJJq.png" alt=""></p>
<blockquote>
<p><strong>PC寄存器(Program Counter Register)</strong></p>
</blockquote>
<p>(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 </p>
<p>由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。 </p>
<p>如果线程正在执行的是一个Java方法,那这个计数器记录的是正在执行的字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(undefined)。<br>此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。 </p>
<p>程序计数器是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)。</p>
<blockquote>
<p><strong>JVM栈(JVM Stacks)</strong></p>
</blockquote>
<p> 每个JVM 线程都有一个私有的JVM 栈(Stacks),它将和线程同时创建。JVM 栈用来存储帧(后面会讲解)。JVM 栈类似于传统语言例如C 的栈,它持有局部变量和部分结果并且参与方法的调用和返回。 由于JVM 栈除了压入弹出帧外不会被直接操作,所以帧可以由堆(Heap)来分配。对于JVM 栈的内存不必是连续的。</p>
<p> JVM 规范允许JVM 栈的大小是固定的,也可以是根据需求计算来扩展和收缩。如果JVM 栈是固定大小,则每个JVM 栈大小可以在栈创建时独立地选择。一个JVM 实现可以让程序员或用户控制JVM 初始栈的大小,以及在动态扩展或收缩JVM 栈时,控制其最大值和最小值。</p>
<p>以下异常情况常与JVM 栈有关:</p>
<p>如果线程中的计算需要一个比允许的JVM 栈更大时,JVM 将会抛出StackOverflowError.</p>
<p>如果JVM 栈可动态扩展,当没有足够的内存分配给所尝试的扩展,或者没有足够的内存来为一个新线程创建初始化JVM 栈,JVM 将会抛出OutOfMemoryError.</p>
<blockquote>
<p><strong>堆(Heap)</strong></p>
</blockquote>
<p> JVM 有一个所有JVM 线程间共享的堆(Heap)。堆是分配所有类实例和数组内存的运行期数据区域。<br>堆在虚拟机启动时被创建。堆中对象的存储由自动存储管理系统(常被称为垃圾回收器或GC)回收,对象从来不会被显示的回收。JVM 承担着非特殊类型的自动存储管理系统,当然存储管理技术也可以根据实现者的系统要求来选择。堆可以是固定大小或是根据需求计算进行扩展,或者也可以是当一个大的堆不必要时进行收缩。堆的内存不需要是连续的。</p>
<p>一个JVM 实现可以让开发者或者用户控制堆初始的大小,同样的,如果堆能够动态扩展或者收缩,可以控制其最大值和最小值。</p>
<p>以下异常情况常与堆有关:如果计算需求所须更多的堆无法由自动存储管理系统提供时,JVM 将会抛出OutOfMemoryError.</p>
<blockquote>
<p><strong>方法区域(Method Area)</strong></p>
</blockquote>
<p>(1)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,可见方法区域的重要性,同样,方法区域也是全局共享的,在一定的条件下它也会被GC;当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。</p>
<p>(2)方法区在虚拟机启动时被创建。虽然方法区逻辑上是堆的一部分,但是简单的实现可以选择既不垃圾回收也不压缩它。该版本的JVM 规范不要求指定方法区的位置或者用于管理编译后代码的策略。方法区可以是固定大小,也可以根据需求计算扩展,并且当大的方法区不再需要时进行收缩。方法区的内存不需要是连续的。 一个JVM 实现可以让开发者或用户控制方法区初始的大小,同样的,在可变大小方法区时,控制方法区的最大值和最小值。在Sun JDK中这块区域对应的为Permanet Generation,又称为持久代,默认为64M,可通过-XX:PermSize以及-XX:MaxPermSize来指定其大小。</p>
<blockquote>
<p><strong>运行时常量池(Runtime Constant Pool)</strong></p>
</blockquote>
<p>运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。</p>
<p>以下异常情况常与类或接口的常量池有关:当创建类或接口时,如果常量池的建立需要的内存不能被JVM 的方法区分配,JVM 会抛出OutOfMenoryError.</p>
<blockquote>
<p><strong>本地方法堆栈(Native Method Stacks)</strong></p>
</blockquote>
<p>本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常类似,它们之间的区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由的实现它。</p>
<p>以下异常情况常与本地方法栈有关:</p>
<p>如果线程中计算所需的本地方法栈大于允许范围,JVM 会抛出StackOverflowError。</p>
<p>如果本地方法栈能动态扩展,当没有足够的内存分配给所尝试的扩展,或者没有足够的内存分配给新线程中创建的初始本地方法栈,JVM 就会抛出OutOfMemoryError。</p>
<p>与虚拟机栈一样,本地方法栈也是线程私有的。</p>
]]></content>
<summary type="html">
<h2 id="1、什么是JVM"><a href="#1、什么是JVM" class="headerlink" title="1、什么是JVM"></a>1、什么是JVM</h2><p>JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。</p>
<p>简单来说,JVM是用于执行Java应用程序和字节码的软件模块,并且可以将字节码转换为特定硬件和特定操作系统的本地代码。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行,这就是Java的能够“一次编译,到处运行”的原因。</p>
<p>JVM包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。</p>
<h2 id="2、JRE-JDK-JVM是什么关系"><a href="#2、JRE-JDK-JVM是什么关系" class="headerlink" title="2、JRE/JDK/JVM是什么关系"></a>2、JRE/JDK/JVM是什么关系</h2><p>JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。</p>
<p>JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。</p>
<p>JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
</summary>
<category term="JVM" scheme="http://zhengweishan.oschina.io/categories/JVM/"/>
<category term="JAVA" scheme="http://zhengweishan.oschina.io/tags/JAVA/"/>
<category term="GC" scheme="http://zhengweishan.oschina.io/tags/GC/"/>
<category term="JVM" scheme="http://zhengweishan.oschina.io/tags/JVM/"/>
</entry>
</feed>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。