您的位置:首页 > 编程语言 > Java开发

《Spring实战》学习笔记-第一章:Spring之旅

2017-05-30 08:22 483 查看
<div id="article_content" class="article_content tracking-ad" data-mod="popu_307" data-dsm="post">

<h1><a name="t0" target="_blank"></a>简洁的Spring</h1>

<p>为了降低<a href="http://lib.csdn.net/base/javase" class="replace_word" title="Java SE知识库" target="_blank" style="color:#df3434; font-weight:bold;">Java</a>开发的复杂性,<a href="http://lib.csdn.net/base/javaee" class="replace_word" title="Java EE知识库" target="_blank"
style="color:#df3434; font-weight:bold;">spring</a>采取了以下4种关键策略:</p>

<ul>

<li>基于POJO的轻量级和最小侵入性编程;</li><li>通过依赖注入和面向接口实现松耦合;</li><li>基于切面和惯例进行声明式编程;</li><li>通过切面和模板减少样板式代码。</li></ul>

<h2><a name="t1" target="_blank"></a>激发POJO的潜能</h2>

<p>相对于EJB的臃肿,Spring尽量避免因自身的api而弄乱用户的应用代码,Spring不会强迫用户实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。</p>

<p>Spring赋予POJO魔力的方式之一就是通过<code>依赖注入</code>来装载它们。</p>

<h2><a name="t2" target="_blank"></a>依赖注入</h2>

<p>任何一个有意义的应用一般都需要多个组件,这些组件之间必定需要进行相互协作才能完成特定的业务,从而导致组件之间的紧耦合,<strong>牵一发而动全身</strong>。<br>

代码示例:</p>

<pre class="hljs java" name="code"><code class="java"><span class="hljs-keyword">package</span> com.springinaction.knights;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DamselRescuingKnight</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Knight</span> </span>{

    <span class="hljs-keyword">private</span> RescueDamselQuest quest;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DamselRescuingKnight</span><span class="hljs-params">()</span> </span>{

        quest = <span class="hljs-keyword">new</span> RescueDamselQuest();<span class="hljs-comment">// 与RescueDamselQuest紧耦合</span>

    }

    <span class="hljs-annotation">@Override</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">embarhOnQuest</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> QuestException </span>{

        quest.embark();

    }

}</code></pre>

<p>正如你所见,DamselRescuingKnight 在它的构造函数中自行创建了RescueDamselQuest,这使得Dams
4000
elRescuingKnight和RescueDamselQuest紧密地耦合到了一起,因此极大地限制了这个骑士的执行能力。如果一个少女需要救援,这个骑士能够召之即来。但是如果一条恶龙需要杀掉,那么这个骑士只能爱莫能助了。</p>

<p>另一方面,可以通过<code>依赖注入</code>的方式来完成对象之间的依赖关系,对象不再需要自行管理它们的依赖关系,而是通过依赖注入自动地注入到对象中去。</p>

<p>代码示例:</p>

<pre class="hljs java" name="code"><code class="java"><span class="hljs-keyword">package</span> com.springinaction.knights;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BraveKnight</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Knight</span> </span>{

    <span class="hljs-keyword">private</span> Quest quest;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BraveKnight</span><span class="hljs-params">(Quest quest)</span> </span>{

        <span class="hljs-keyword">this</span>.quest = quest;<span class="hljs-comment">// quest被注入到对象中</span>

    }

    <span class="hljs-annotation">@Override</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">embarhOnQuest</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> QuestException </span>{

        quest.embark();

    }

}</code></pre>

<p>不同于之前的DamselRescuingKnight,BraveKnight没有自行创建探险任务,而是在构造器中把探险任务作为参数注入,这也是依赖注入的一种方式,即<strong>构造器注入</strong>。</p>

<p>更为重要的是,BraveKnight中注入的探险类型是Quest,Quest只是一个探险任务所必须实现的接口。因此,BraveKnight能够响RescueDamselQuest、SlayDraonQuest等任意一种Quest实现,这正是<strong>多态</strong>的体现。</p>

<p>这里的要点是BraveKnight没有与任何特定的Quest实现发生耦合。对它来说,被要求挑战的探险任务只要实现了Quest接口,那么具体是哪一类型的探险就无关紧要了。这就是依赖注入最大的好处--<strong>松耦合</strong>。如果一个对象只通过<strong>接口</strong>(而不是具体实现或初始化的过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。</p>

<h3><a name="t3" target="_blank"></a>注入一个Quest到Knight</h3>

<p>创建应用组件之间协作关系的行为称为<strong>装配</strong>,Spring有多种装配Bean的方式,其中最常用的就是通过XML配置文件的方式装配。<br>

示例代码:使用Spring将SlayDragonQuest注入到BraveKnight中。</p>

<pre class="hljs xml" name="code"><code class="xml"><span class="hljs-pi"><?xml version="1.0" encoding="UTF-8"?></span>

<span class="hljs-tag"><<span class="hljs-title">beans</span> <span class="hljs-attribute">xmlns</span>=<span class="hljs-value">"http://www.springframework.org/schema/beans"</span>

    <span class="hljs-attribute">xmlns:xsi</span>=<span class="hljs-value">"http://www.w3.org/2001/XMLSchema-instance"</span>

    <span class="hljs-attribute">xsi:schemaLocation</span>=<span class="hljs-value">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span>
    <span class="hljs-tag"><<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"knight"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"com.springinaction.knights.BraveKnight"</span>></span>

        <span class="hljs-tag"><<span class="hljs-title">constructor-arg</span> <span class="hljs-attribute">ref</span>=<span class="hljs-value">"quest"</span>></span><span class="hljs-tag"></<span class="hljs-title">constructor-arg</span>></span>

    <span class="hljs-tag"></<span class="hljs-title">bean</span>></span>

    <span class="hljs-tag"><<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"quest"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"com.springinaction.knights.SlayDragonQuest"</span>></span><span
class="hljs-tag"></<span class="hljs-title">bean</span>></span>

<span class="hljs-tag"></<span class="hljs-title">beans</span>></span></code></pre>

<h3><a name="t4" target="_blank"></a>Spring是如何注入的?</h3>

<p>Spring通过应用上下文(<code>ApplicationContext</code>)来装载Bean,<code>ApplicationContext</code>全权负责对象的创建和组装。</p>

<p>Spring自带了多种ApplicationContext来加载配置,比如,Spring可以使用<code>ClassPathXmlApplicationContext</code>来装载XML文件中的Bean对象。</p>

<pre class="hljs java" name="code"><code class="java"><span class="hljs-keyword">package</span> com.springinaction.knights;

<span class="hljs-keyword">import</span> org.springframework.context.ApplicationContext;

<span class="hljs-keyword">import</span> org.springframework.context.support.ClassPathXmlApplicationContext;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">KnightMain</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{

        ApplicationContext context = <span class="hljs-keyword">new</span> ClassPathXmlApplicationContext(<span class="hljs-string">"knights.xml"</span>);<span class="hljs-comment">// 加载Spring上下文</span>

        Knight knight = (Knight) context.getBean(<span class="hljs-string">"knight"</span>);<span class="hljs-comment">// 获取knight Bean</span>

        knight.embarhOnQuest();<span class="hljs-comment">// 使用knight</span>

    }

}</code></pre>

<p>这个示例代码中,Spring上下文加载了<code>knights.xml</code>文件,随后获取了一个ID为knight的Bean的实例,得到该对象实例后,就可以进行正常的使用了。需要注意的是,这个类中完全不知道是由哪个Knight来执行何种Quest任务,只有<code>knights.xml</code>文件知道。</p>

<h2><a name="t5" target="_blank"></a>应用切面</h2>

<p>通常情况下,系统由许多不同组件组成,其中的每一个组件分别负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责,诸如日志、事务管理和安全等,此类的系统服务经常融入到有自身核心业务逻辑的组件中去,这些系统服务通常被称为<strong>横切关注点</strong>,因为它们总是跨越系统的多个组件,如下图所示。</p>

<div class="image-package imagebubble"><img src="http://hoxis-github-io.qiniudn.com/160104-spring-in-action-1.2.png" class="imagebubble-image" alt=""><br>

<div class="image-caption">对遍布系统的横切关注点的调用散布在各个组件里,而这些关注点并不是组件的核心业务</div>

</div>

<p>AOP可以使得这些服务模块化,并以声明的方式将它们应用到相应的组件中去,这样,这些组件就具有更高内聚性以及更加关注自身业务,完全不需要了解可能涉及的系统服务的复杂性。总之,AOP确保POJO保持简单。</p>

<div class="image-package imagebubble"><img src="http://hoxis-github-io.qiniudn.com/160104-spring-in-action-1.3.png" class="imagebubble-image" alt=""><br>

<div class="image-caption">利用AOP,可以将横切关注点覆盖在所需的组件之上,而这些组件不再需要额外的关注这些非核心业务。</div>

</div>

<p>如图所示,我们可以把切面想象为覆盖在很多组件之上的一个<strong>外壳</strong>。利用AOP,你可以使用各种功能层去包裹核心业务层。这些层以<strong>声明的方式</strong>灵活应用到你的系统中,甚至你的核心应用根本不知道它们的存在。</p>

<h3><a name="t6" target="_blank"></a>AOP应用</h3>

<p>接上面骑士的故事,现在需要一个诗人来歌颂骑士的勇敢事迹,代码如下「Minstrel是中世纪的音乐记录器」:</p>

<pre class="hljs java" name="code"><code class="java"><span class="hljs-keyword">package</span> com.springinaction.knights;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Minstrel</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">singBeforeQuest</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 探险之前调用</span>

        System.out.println(<span class="hljs-string">"Fa la la; The knight is so brave!"</span>);

    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">singAfterQuest</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">// 探险之后调用</span>

        System.out.println(<span class="hljs-string">"Tee hee he; The brave knight did embark on a quest!"</span>);

    }

}</code></pre>

<p>如代码中所示,诗人会在骑士每次执行探险前和结束时被调用,完成骑士事迹的歌颂。骑士必须调用诗人的方法完成歌颂:</p>

<pre class="hljs java" name="code"><code class="java"><span class="hljs-keyword">package</span> com.springinaction.knights;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BraveKnight</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Knight</span> </span>{

    <span class="hljs-keyword">private</span> Quest quest;

    <span class="hljs-keyword">private</span> Minstrel minstrel;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BraveKnight</span><span class="hljs-params">(Quest quest)</span> </span>{

        <span class="hljs-keyword">this</span>.quest = quest;<span class="hljs-comment">// quest被注入到对象中</span>

    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BraveKnight</span><span class="hljs-params">(Quest quest, Minstrel minstrel)</span> </span>{

        <span class="hljs-keyword">this</span>.quest = quest;<span class="hljs-comment">// quest被注入到对象中</span>

        <span class="hljs-keyword">this</span>.minstrel = minstrel;

    }

    <span class="hljs-annotation">@Override</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">embarhOnQuest</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> QuestException </span>{

        minstrel.singAfterQuest();

        quest.embark();

        minstrel.singAfterQuest();

    }

}</code></pre>

<p>但是,感觉是骑士在路边抓了一个诗人为自己「歌功颂德」,而不是诗人主动地为其传扬事迹。简单的BraveKnight类开始变得复杂,如果骑士不需要诗人,那么代码将会更加复杂。</p>

<p>但是有了AOP,骑士就不再需要自己调用诗人的方法为自己服务了,这就需要把Minstrel声明为一个切面:</p>

<pre class="hljs xml" name="code"><code class="xml"><span class="hljs-pi"><?xml version="1.0" encoding="UTF-8"?></span>

<span class="hljs-tag"><<span class="hljs-title">beans</span> <span class="hljs-attribute">xmlns</span>=<span class="hljs-value">"http://www.springframework.org/schema/beans"</span>

    <span class="hljs-attribute">xmlns:xsi</span>=<span class="hljs-value">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="hljs-attribute">xmlns:aop</span>=<span class="hljs-value">"http://www.springframework.org/schema/aop"</span>

    <span class="hljs-attribute">xsi:schemaLocation</span>=<span class="hljs-value">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        

    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd"</span>></span>
    <span class="hljs-tag"><<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"knight"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"com.springinaction.knights.BraveKnight"</span>></span>

        <span class="hljs-tag"><<span class="hljs-title">constructor-arg</span> <span class="hljs-attribute">ref</s
bb1c
pan>=<span class="hljs-value">"quest"</span>></span><span class="hljs-tag"></<span class="hljs-title">constructor-arg</span>></span>

    <span class="hljs-tag"></<span class="hljs-title">bean</span>></span>

    <span class="hljs-tag"><<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"quest"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"com.springinaction.knights.SlayDragonQuest"</span>></span><span
class="hljs-tag"></<span class="hljs-title">bean</span>></span>

    <span class="hljs-comment"><!-- 声明诗人Minstrel,待切入的对象(刀) --></span>

    <span class="hljs-tag"><<span class="hljs-title">bean</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"minstrel"</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"com.springinaction.knights.Minstrel"</span>></span><span
class="hljs-tag"></<span class="hljs-title">bean</span>></span>

    <span class="hljs-tag"><<span class="hljs-title">aop:config</span>></span>

        <span class="hljs-tag"><<span class="hljs-title">aop:aspect</span> <span class="hljs-attribute">ref</span>=<span class="hljs-value">"minstrel"</span>></span>

            <span class="hljs-comment"><!-- 定义切面,即定义从哪里切入 --></span>

            <span class="hljs-tag"><<span class="hljs-title">aop:pointcut</span> <span class="hljs-attribute">expression</span>=<span class="hljs-value">"execution(* *.embarkOnQuest(..))"</span>

                <span class="hljs-attribute">id</span>=<span class="hljs-value">"embark"</span> /></span>

            <span class="hljs-comment"><!-- 声明前置通知,在切入点之前执行的方法 --></span>

            <span class="hljs-tag"><<span class="hljs-title">aop:before</span> <span class="hljs-attribute">method</span>=<span class="hljs-value">"singBeforeQuest"</span> <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"embark"</span>
/></span>

            <span class="hljs-comment"><!-- 声明后置通知,在切入点之后执行的方法  --></span>

            <span class="hljs-tag"><<span class="hljs-title">aop:after</span> <span class="hljs-attribute">method</span>=<span class="hljs-value">"singAfterQuest"</span> <span class="hljs-attribute">pointcut-ref</span>=<span class="hljs-value">"embark"</span>
/></span>

        <span class="hljs-tag"></<span class="hljs-title">aop:aspect</span>></span>

    <span class="hljs-tag"></<span class="hljs-title">aop:config</span>></span>

<span class="hljs-tag"></<span class="hljs-title">beans</span>></span></code></pre>

<p>通过运行结果可以发现,在没有改动BraveKnight的代码的情况下,就完成了Minstrel对其的歌颂,而且BraveKnight并不知道Minstrel的存在。</p>

<h2><a name="t7" target="_blank"></a>使用Spring模版</h2>

<p>使用Spring模版可以消除很多样板式代码,比如JDBC、JMS、JNDI、REST等。</p>

<h1><a name="t8" target="_blank"></a>容纳Bean</h1>

<p>在Spring中,应用对象生存于Spring容器中,如图所示,Spring容器可以创建、装载、配置这些Bean,并且可以管理它们的生命周期。</p>

<div class="image-package imagebubble"><img src="http://hoxis-github-io.qiniudn.com/160104-spring-in-action-1.4.png" class="imagebubble-image" alt=""><br>

<div class="image-caption">在Spring中,对象由Spring容器创建、装配、管理</div>

</div>

<h2><a name="t9" target="_blank"></a>Spring的容器实现</h2>

<ul>

<li>Bean工厂(<code>org.springframework.beans.factory.BeanFactory</code>):最简单的容器,提供基本的DI支持;</li><li>应用上下文(<code>org.springframework.context.ApplicationContext</code>):基于BeanFactory之上构建,提供面向应用的服务。</li></ul>

<h2><a name="t10" target="_blank"></a>常用的几种应用上下文</h2>

<ul>

<li>ClassPathXmlApplicationContext:从类路径中的XML配置文件加载上下文,会在所有的类路径(包括jar文件)下查找;</li><li>FileSystemXmlApplicationContext:从文件系统中读取XML配置文件并加载上下文,在指定的文件系统路径下查找;</li><li>XmlWebApplicationContext:读取Web应用下的XML配置文件并加载上下文;</li></ul>

<h2><a name="t11" target="_blank"></a>Bean的生命周期</h2>

<div class="image-package imagebubble"><img src="http://hoxis-github-io.qiniudn.com/160104-spring-in-action-1.5.png" class="imagebubble-image" alt=""><br>

<div class="image-caption">Spring中Bean的生命周期</div>

</div>

<ol>

<li>Spring对Bean进行实例化;</li><li>Spring将值和Bean的引用注入进Bean对应的属性中;</li><li>如果Bean实现了<code>BeanNameAware</code>接口,Spring将Bean的ID传递给<code>setBeanName()</code>接口方法;</li><li>如果Bean实现了<code>BeanFactoryAware</code>接口,Spring将调<code>setBeanFactory()</code>接口方法,将BeanFactory容器实例传入;</li><li>如果Bean实现了<code>ApplicationContextAware</code>接口,Spring将调用<code>setApplicationContext()</code>接口方法,将应用上下文的引用传入;</li><li>如果Bean实现了<code>BeanPostProcessor</code>接口,Spring将调用<code>postProcessBeforeInitialization()</code>接口方法;</li><li>如果Bean实现了<code>InitializationBean</code>接口,Spring将调用<code>afterPropertiesSet()</code>方法。类似的如果Bean使用了<code>init-method</code>声明了初始化方法,该方法也会被调用;</li><li>如果Bean实现了<code>BeanPostProcessor</code>接口,Spring将调用<code>ProcessAfterInitialization()</code>方法;</li><li>此时此刻,Bean已经准备就绪,可以被应用程序使用了,它们将一直<code>驻留在应用上下文中</code>,直到该应用上下文被销毁;</li><li>如果Bean实现了<code>DisposableBean</code>接口,Spring将调用<code>destory()</code>方法,同样的,如果Bean中使用了<code>destroy-method</code>声明了销毁方法,也会调用该方法;</li></ol>

<h1><a name="t12" target="_blank"></a>纵观Spring</h1>

<h2><a name="t13" target="_blank"></a>Spring模块</h2>

<div class="image-package imagebubble"><img src="http://hoxis-github-io.qiniudn.com/160104-spring-in-action-1.7.png" class="imagebubble-image" alt=""><br>

<div class="image-caption">Spring中的6个重要模块</div>

</div>

<h3><a name="t14" target="_blank"></a>核心Spring容器</h3>

<p><strong>容器</strong>是Spring框架最核心的部分,它负责Spring应用中Bean的创建、配置和管理。Spring模块都构建与核心容器之上,当配置应用时,其实都隐式地使用了相关的核心容器类。另外,该模块还提供了许多企业级服务,如邮件、JNDI访问、EJB集成和调度等。</p>

<h3><a name="t15" target="_blank"></a>AOP</h3>

<p>AOP是Spring应用系统开发切面的基础,与依赖注入一样,可以帮助应用对象<code>解耦</code>。借助于AOP,可以将遍布于应用的关注点(如事务和安全等)从所应用的对象中解耦出来。</p>

<h3><a name="t16" target="_blank"></a>数据访问与集成</h3>

<p>Spring的JDBC和DAO模块封装了大量的样板代码,这样可以使得在<a href="http://lib.csdn.net/base/mysql" class="replace_word" title="MySQL知识库" target="_blank" style="color:#df3434; font-weight:bold;">数据库</a>代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败而引发的问题。另外,Spring AOP为数据访问提供了事务管理服务。同时,Spring还与流程的ORM(Object-Relational
Mapping)进行了集成,如<a href="http://lib.csdn.net/base/javaee" class="replace_word" title="Java EE知识库" target="_blank" style="color:#df3434; font-weight:bold;">hibernate</a>、MyBatis等。</p>

<h3><a name="t17" target="_blank"></a>Web和远程调用</h3>

<p>Spring提供了两种Web层框架:面向传统Web应用的基于Servlet的框架和面向使用<a href="http://lib.csdn.net/base/java" class="replace_word" title="Java 知识库" target="_blank" style="color:#df3434; font-weight:bold;">Java </a>Portlet API的基于Portlet的应用。Spring远程调用服务集成了RMI、Hessian、Burlap、JAX-WS等。</p>

<h3><a name="t18" target="_blank"></a>测试</h3>

<p>Spring提供了<a href="http://lib.csdn.net/base/softwaretest" class="replace_word" title="软件测试知识库" target="_blank" style="color:#df3434; font-weight:bold;">测试</a>模块来测试Spring应用。</p>

<br>

<br>

<div>文/hoxis(简书作者)<br>

原文链接:http://www.jianshu.com/p/133614e407bd<br>

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。</div>

   

</div>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: