Java线程状态
java线程一共有6种状态
转换关系图如下
说明如下:
new Thread()
后线程的状态就是新建,但还没调用start方法。start()
方法,无论是否运行,状态都为Runable,但Runnable是从JVM视图看是处于执行状态,但实际上操作系统可能在等待一些资源,如此时处理器在干其他的,此时Runnable可以分成两个子状态Ready
和Running
,所以通过上图当线程被调度器选中执行实际才处于Running状态。所以显示Runable状态指示表示线程可以运行,不表示线程当下一定在运行,线程是否运行由虚拟机所在操作系统调度决定。Object.wait()
Thread.join()
LockSupport.park()
Monitor
(进入synchronized
方法或synchronized
块)但是其他线程已经抢先获取,那此线程被阻塞,直到其他线程释放Monitor
并且线程调度器允许当前线程获取到Monitor
,此线程就恢复到可运行状态。1 | public class ThreadState { |
输出结果
1 | 调用new方法后 my thread state:NEW |
参考资料:https://www.uml-diagrams.org/java-thread-uml-state-machine-diagram-example.html
]]>Spring源码编译
前几天同事分享了下Spring源码编译,自己之前也下载了spring源码一直没编译过,正好借此机会自己编译下,本文记录了编译过程,和大家分享。
网上也找了一些文章,有的用命令行直接编译的,有的是直接导入idea编译的。这里我采用的是第一个。
相关依赖说明如下:
1 | Java:1.8.0_77 |
Spring源码下载:https://github.com/spring-projects/spring-framework
这里有一点需要说明的是5.2.x版本编译不需要在下载Gradle了,编译的时候Spring会直接下载Gradle
源码git clone下来之后,将分支由master切换为5.2.x
由于spring使用的是maven中央仓库,但中央仓库在国内网络不太稳定,这里配置仓库地址使用阿里云的,修改Spring目录下的build.gradle
文件,找到如下位置
1 | repositories { |
增加如下仓库镜像,最后修改结果如下:
1 | repositories { |
GRADLE_USER_HOME
值为D:\maven_repository
1 | cd spring源码目录 |
等待编译完成吧,成功截图如下
不编译成功的原因基本上就是网络的原因,所以一定要配置好国内的镜像哦
我使用的是IDEA社区版,导入方法如下
进入到Spring工作目录
使用命令gradlew.bat :spring-oxm:compileTestJava
提前预编译好spring-oxm
打开idea依次选择(File -> New -> Project from Existing Sources)选择spring源码根目录下的build.gradle
最后等待编译成功吧
被世界公认的效率最高学习法
福曼学习法,好好学习,天天向上。
相关阅读:https://blog.csdn.net/liwei16611/article/details/89816693
]]>快速切换node版本与node源方法
使用:nvm(node版本管理)
nvm就是nodejs version manage 叫做nodejs 版本管理,而nodejs有很多版本,场景如下:
1、而你手上开发的有多个项目又分别是不同的nodejs版本,咱们就可以用nvm轻松切换!
2、假设你正在开发的项目开始使用的nodejs版本是8.0,而现在因为某些原因,你需要升级 或者 降级 nodejs 版本,也可以使用 nvm 轻松切换
windows安装方法:https://cloud.tencent.com/developer/news/64123
linux安装方法:https://yq.aliyun.com/articles/688562
使用:nrm —— 快速切换 NPM 源 (附带测速功能)
1 | npm install -g nrm |
1 | nrm ls |
带 *
的是当前使用的源,上面的输出表明当前源是官方源。
切换到taobao
1 | nrm use taobao |
你可以增加定制的源,特别适用于添加企业内部的私有源。
1 | nrm add <registry> <url> [home] |
1 | nrm del <registry> |
你还可以通过 nrm test
测试相应源的响应时间。
例如,测试官方源的响应时间:
1 | nrm test npm |
测试所有源的响应时间:
1 | nrm test |
注意,为了取得较准确的结果,可以考虑多次测试取平均值。
nrm 为开源软件,使用 MIT 许可。
并发编程时有可能会遇到死锁的情况,但会在什么情况下发生死锁呢。Coffman已经帮我们总结好了。
满足下面4个条件就会死锁,具体如下:
所以解决方法就是破坏上面一个条件就可以了,死锁就解决了。
其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有
办法破坏掉的,到底如何做呢?
win10有许多新特性,其中之一就是可以安装linux子系统,这对于想要学习linux的同学不得不说是个福音,不用再装虚拟机等一系列东西了,网上搜了一些教程跟着教程安装了下,不得不说确实很好用
这里就不自己写了,下面是网上相关的好文章教程链接,大家自己可以参考下
https://www.jianshu.com/p/bc38ed12da1d
https://jingyan.baidu.com/article/c85b7a64a56c7f003aac954f.html
安装完成后再本地的存储位置
1 | C:\Users\用户名\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs |
进入命令行后输入bash
或者输入ubuntu
进入linux子系统,这时你可以尽情遨游在linux世界了
本文章使用Daocloud自动构建发布。
由于换工作换了电脑,博客一直没有更新,因为要写博客需要把hexo的一堆东西下载到自己的电脑上,工作电脑有一份,家里电脑有一份,太费劲了,而且每次都需要自己更新,所以就想到了能否自动构建发布了,我只提交文章到git上就可以了,之前看我同学的博客有用daocloud发布的,自己借鉴和研究了下,之前工作忙也没仔细研究,现在总算有点时间,所以抽时间研究了下,部署成功了,以后写博客就方便了,后续有时间会把自己此次成功自动构建发布总结下,写个文章发出来,共大家学习参考。
想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
参考资料
随时随地让Hexo持续部署
官方教程地址:简体中文版走你
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
.gitignore文件是git使用的管理规则文件,定义哪些可以上传到git服务器,哪些不可上传到服务器,在此在网上搜索了一些规则,自己整理下,做备忘使用,也分享出来方便大家查看。
告诉git哪些文件不需要添加到版本管理中。例如IDE的一些配置信息,像eclipse的.settings
文件夹和其中的文件,IDEA的.idea
目录和其文件等。
以斜杠/
开头表示目录
以星号*
通配多个字符
以问号?
通配单个字符
以方括号[]
包含单个字符的匹配列表
以叹号!
表示不忽略(跟踪)匹配到的文件或目录
git 对于 .ignore
配置文件是按行从上到下进行规则匹配的,意味着如果前面的规则匹配的范围更大,则后面的规则将不会生效
fd1/*
:忽略目录 fd1 下的全部内容;注意,不管是根目录下的 /fd1/ 目录,还是某个子目录 /child/fd1/ 目录,都会被忽略;/fd1/*
:忽略根目录下的 /fd1/ 目录的全部内容;1 | /* |
如果你不慎在创建.gitignore
文件之前就push了项目,那么即使你在.gitignore
文件中写入新的过滤规则,这些规则也不会起作用,Git仍然会对所有文件进行版本管理。
简单来说,出现这种问题的原因就是Git已经开始管理这些文件了,所以你无法再通过过滤规则过滤它们。因此一定要养成在项目开始就创建.gitignore
文件的习惯,否则一旦push,处理起来会非常麻烦
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
JVM判断对象是否可以回收采用的不是引用计数法,而是可达性分析算法,而通过什么能够判断对象是否可达呢,就是一系列称为GC Roots的对象。
算法基本思路就是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。就是可以回收的。
java中,可作为GC Roots的对象包括下面几种
想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
参考资料
《深入理解Java虚拟机:JVM高级特性与最佳实践》·周志明著·第二版
总结了下常用函数的时间复杂度,作为收藏,以备不时之需。数据的每个操作都是有代价的,以时间复杂度和对应查询集或者结果集大小为衡量。
网上冲浪时无意间看到markdown可以写书了,使用的是gitbook-一个先进可定制的文档格式工具。写出的电子书非常好看,所以萌发了自己写一写的念头,俗话说工欲善其事必先利其器,了解gitbook如何使用就很重要了,所以整理了网上各家使用方法,自己又整合了一下。
要说jQuery 最成功的地方,我认为是它的可扩展性吸引了众多开发者为其开发插件,从而建立起了一个生态系统。这好比大公司们争相做平台一样,得平台者得天下。苹果,微软,谷歌等巨头,都有各自的平台及生态圈
学会使用jQuery并不难,因为它简单易学,并且相信你接触jQuery后肯定也使用或熟悉了不少其插件。如果要将能力上升一个台阶,编写一个属于自己的插件是个不错的选择。
本教程可能不是最精品的,但一定是最细致的。
软件开发过程中是需要一定的设计模式来指导开发的,有了模式,我们就能更好地组织我们的代码,并且从这些前人总结出来的模式中学到很多好的实践。
根据《jQuery高级编程》的描述,jQuery插件开发方式主要有三种:
通常我们使用第二种方法来进行简单插件开发,说简单是相对于第三种方式。第三种方式是用来开发更高级jQuery部件的,该模式开发出来的部件带有很多jQuery内建的特性,比如插件的状态信息自动保存,各种关于插件的常用方法等,非常贴心,这里不细说。
而第一种方式又太简单,仅仅是在jQuery命名空间或者理解成jQuery身上添加了一个静态方法而以。所以我们调用通过$.extend()添加的函数时直接通过$符号调用($.myfunction())而不需要选中DOM元素($(‘#example’).myfunction())。请看下面的例子。
1 | $.extend({ |
运行结果:
上面代码中,通过$.extend()向jQuery添加了一个sayHello函数,然后通过$直接调用。到此你可以认为我们已经完成了一个简单的jQuery插件了。
但如你所见,这种方式用来定义一些辅助方法是比较方便的。比如一个自定义的console,输出特定格式的信息,定义一次后可以通过jQuery在程序中任何需要的地方调用它。
1 | $.extend({ |
但这种方式无法利用jQuery强大的选择器带来的便利,要处理DOM元素以及将插件更好地运用于所选择的元素身上,还是需要使用第二种开发方式。你所见到或使用的插件也大多是通过此种方式开发。
下面我们就来看第二种方式的jQuery插件开发。
先看一下它的基本格式:1
2
3$.fn.pluginName = function() {
//your code goes here
}
基本上就是往$.fn上面添加一个方法,名字是我们的插件名称。然后我们的插件代码在这个方法里面展开。
比如我们将页面上所有链接颜色转成红色,则可以这样写这个插件:1
2
3
4
5$.fn.myPlugin = function() {
//在这里面,this指的是用jQuery选中的元素
//example :$('a'),则this=$('a')
this.css('color', 'red');
}
在插件名字定义的这个函数内部,this指代的是我们在调用该插件时,用jQuery选择器选中的元素,一般是一个jQuery类型的集合。比如$(‘a’)返回的是页面上所有a标签的集合,且这个集合已经是jQuery包装类型了,也就是说,在对其进行操作的时候可以直接调用jQuery的其他方法而不需要再用美元符号来包装一下。
所以在上面插件代码中,我们在this身上调用jQuery的css()方法,也就相当于在调用 $(‘a’).css()。
理解this在这个地方的含义很重要。这样你才知道为什么可以直接商用jQuery方法同时在其他地方this指代不同时我们又需要用jQuery重新包装才能调用,下面会讲到。初学容易被this的值整晕,但理解了就不难。
现在就可以去页面试试我们的代码了,在页面上放几个链接,调用插件后链接字体变成红色。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<ul>
<li>
<a href="http://www.webo.com/liuwayong">我的微博</a>
</li>
<li>
<a href="http://http://www.cnblogs.com/Wayou/">我的博客</a>
</li>
<li>
<a href="http://wayouliu.duapp.com/">我的小站</a>
</li>
</ul>
<p>这是p标签不是a标签,我不会受影响</p>
<script src="jquery-1.11.0.min.js"></script>
<script src="jquery.myplugin.js"></script>
<script type="text/javascript">
$(function(){
$('a').myPlugin();
})
</script>
运行结果:
下面进一步,在插件代码里处理每个具体的元素,而不是对一个集合进行处理,这样我们就可以针对每个元素进行相应操作。
我们已经知道this指代jQuery选择器返回的集合,那么通过调用jQuery的.each()方法就可以处理合集中的每个元素了,但此刻要注意的是,在each方法内部,this指带的是普通的DOM元素了,如果需要调用jQuery的方法那就需要用$来重新包装一下。
比如现在我们要在每个链接显示链接的真实地址,首先通过each遍历所有a标签,然后获取href属性的值再加到链接文本后面。
更改后我们的插件代码为:
1 | $.fn.myPlugin = function() { |
调用代码还是一样的,我们通过选中页面所有的a标签来调用这个插件
运行结果:
到此,你已经可以编写功能简单的jQuery插件了。是不是也没那么难。
下面开始jQuery插件编写中一个重要的部分,参数的接收。
我们都知道jQuery一个时常优雅的特性是支持链式调用,选择好DOM元素后可以不断地调用其他方法。
要让插件不打破这种链式调用,只需return一下即可。1
2
3
4
5
6
7
8$.fn.myPlugin = function() {
//在这里面,this指的是用jQuery选中的元素
this.css('color', 'red');
return this.each(function() {
//对每个元素进行操作
$(this).append(' ' + $(this).attr('href'));
}))
}
一个强劲的插件是可以让使用者随意定制的,这要求我们提供在编写插件时就要考虑得全面些,尽量提供合适的参数。
比如现在我们不想让链接只变成红色,我们让插件的使用者自己定义显示什么颜色,要做到这一点很方便,只需要使用者在调用的时候传入一个参数即可。同时我们在插件的代码里面接收。另一方面,为了灵活,使用者可以不传递参数,插件里面会给出参数的默认值。
在处理插件参数的接收上,通常使用jQuery的extend方法,上面也提到过,但那是给extend方法传递单个对象的情况下,这个对象会合并到jQuery身上,所以我们就可以在jQuery身上调用新合并对象里包含的方法了,像上面的例子。当给extend方法传递一个以上的参数时,它会将所有参数对象合并到第一个里。同时,如果对象中有同名属性时,合并的时候后面的会覆盖前面的。
利用这一点,我们可以在插件里定义一个保存插件参数默认值的对象,同时将接收来的参数对象合并到默认对象上,最后就实现了用户指定了值的参数使用指定的值,未指定的参数使用插件默认值。
为了演示方便,再指定一个参数fontSize,允许调用插件的时候设置字体大小。
1 | $.fn.myPlugin = function(options) { |
现在,我们调用的时候指定颜色,字体大小未指定,会运用插件里的默认值12px。
1 | $('a').myPlugin({ |
运行结果:
同时指定颜色与字体大小:
1 | $('a').myPlugin({ |
注意到上面代码调用extend时会将defaults的值改变,这样不好,因为它作为插件因有的一些东西应该维持原样,另外就是如果你在后续代码中还要使用这些默认值的话,当你再次访问它时它已经被用户传进来的参数更改了。
一个好的做法是将一个新的空对象做为$.extend的第一个参数,defaults和用户传递的参数对象紧随其后,这样做的好处是所有值被合并到这个空对象上,保护了插件里面的默认值。1
2
3
4
5
6
7
8
9
10
11$.fn.myPlugin = function(options) {
var defaults = {
'color': 'red',
'fontSize': '12px'
};
var settings = $.extend({},defaults, options);//将一个空对象做为第一个参数
return this.css({
'color': settings.color,
'fontSize': settings.fontSize
});
}
到此,插件可以接收和处理参数后,就可以编写出更健壮而灵活的插件了。若要编写一个复杂的插件,代码量会很大,如何组织代码就成了一个需要面临的问题,没有一个好的方式来组织这些代码,整体感觉会杂乱无章,同时也不好维护,所以将插件的所有方法属性包装到一个对象上,用面向对象的思维来进行开发,无疑会使工作轻松很多。
为什么要有面向对象的思维,因为如果不这样,你可能需要一个方法的时候就去定义一个function,当需要另外一个方法的时候,再去随便定义一个function,同样,需要一个变量的时候,毫无规则地定义一些散落在代码各处的变量。
还是老问题,不方便维护,也不够清晰。当然,这些问题在代码规模较小时是体现不出来的。
如果将需要的重要变量定义到对象的属性上,函数变成对象的方法,当我们需要的时候通过对象来获取,一来方便管理,二来不会影响外部命名空间,因为所有这些变量名还有方法名都是在对象内部。
接着上面的例子,我们可以把这个插件抽象成一个美化页面的对象,因为他的功能是设置颜色啊字体啊什么的,当然我们还可以加入其他功能比如设置下划线啊什么的。当然对于这个例子抽象成对象有点小题大做,这里仅作演示用。以后我可能会介绍我编写的一个jQuery插件SlipHover,其中代码就比较多,这样的模式就用得上了。
所以我们新建一个对象命名为Beautifier,然后我们在插件里使用这个对象来编码。
1 | //定义Beautifier的构造函数 |
通过上面这样一改造,我们的代码变得更面向对象了,也更好维护和理解,以后要加新功能新方法,只需向对象添加新变量及方法即可,然后在插件里实例化后即可调用新添加的东西。
插件的调用还是一样的,我们对代码的改动并不影响插件其他地方,只是将代码的组织结构改动了而以。
1 | $(function() { |
指定文字带下划线(我们在Beautifier对象中新加的功能,默认不带下划线,如上面的例子)的调用:
1 | $(function() { |
到这里,你可以更好地编写复杂的插件同时很好地组织代码了。当我们回头去看上面的代码时,其实也还是有改进空间的。也就是下面介绍的关于命名空间及变量各什么的,一些杂项。
不仅仅是jQuery插件的开发,我们在写任何JS代码时都应该注意的一点是不要污染全局命名空间。因为随着你代码的增多,如果有意无意在全局范围内定义一些变量的话,最后很难维护,也容易跟别人写的代码有冲突。
比如你在代码中向全局window对象添加了一个变量status用于存放状态,同时页面中引用了另一个别人写的库,也向全局添加了这样一个同名变量,最后的结果肯定不是你想要的。所以不到万不得已,一般我们不会将变量定义成全局的。
一个好的做法是始终用自调用匿名函数包裹你的代码,这样就可以完全放心,安全地将它用于任何地方了,绝对没有冲突。
我们知道JavaScript中无法用花括号方便地创建作用域,但函数却可以形成一个作用域,域内的代码是无法被外界访问的。如果我们将自己的代码放入一个函数中,那么就不会污染全局命名空间,同时不会和别的代码冲突。
如上面我们定义了一个Beautifier全局变量,它会被附到全局的window对象上,为了防止这种事情发生,你或许会说,把所有代码放到jQuery的插件定义代码里面去啊,也就是放到$.fn.myPlugin里面。这样做倒也是种选择。但会让我们实际跟插件定义有关的代码变得臃肿,而在$.fn.myPlugin里面我们其实应该更专注于插件的调用,以及如何与jQuery互动。
所以保持原来的代码不变,我们将所有代码用自调用匿名函数包裹。
1 | (function() { |
这样做的好处,也就是上面所阐述的那样。另外还有一个好处就是,自调用匿名函数里面的代码会在第一时间执行,页面准备好过后,上面的代码就将插件准备好了,以方便在后面的代码中使用插件。
目前为止似乎接近完美了。如果再考虑到其他一些因素,比如我们将这段代码放到页面后,前面别人写的代码没有用分号结尾,或者前面的代码将window, undefined等这些系统变量或者关键字修改掉了,正好我们又在自己的代码里面进行了使用,那结果也是不可预测的,这不是 我们想要的。我知道其实你还没太明白,下面详细介绍。
来看下面的代码,你猜他会出现什么结果?
1 | var foo=function(){ |
本来别人的代码也正常工作,只是最后定义的那个函数没有用分号结尾而以,然后当页面中引入我们的插件时,报错了,我们的代码无法正常执行。
原因是我们用来充当自调用匿名函数的第一对括号与上面别人定义的函数相连,因为中间没有分号嘛,总之我们的代码无法正常解析了,所以报错。
所以好的做法是我们在代码开头加一个分号,这在任何时候都是一个好的习惯。
1 | var foo=function(){ |
同时,将系统变量以参数形式传递到插件内部也是个不错的实践。
当我们这样做之后,window等系统变量在插件内部就有了一个局部的引用,可以提高访问速度,会有些许性能的提升
最后我们得到一个非常安全结构良好的代码:1
2
3
4;(function($,window,document,undefined){
//我们的代码。。
//blah blah blah...
})(jQuery,window,document);
而至于这个undefined,稍微有意思一点,为了得到没有被修改的undefined,我们并没有传递这个参数,但却在接收时接收了它,因为实际并没有传,所以‘undefined’那个位置接收到的就是真实的’undefined’了。是不是有点hack的味道,值得细细体会的技术,当然不是我发明的,都是从前人的经验中学习。
所以最后我们的插件成了这样:
1 | ;(function($, window, document,undefined) { |
一个安全,结构良好,组织有序的插件编写完成。
现在谈谈关于变量及方法等的命名,没有硬性规定,但为了规范,遵循一些约定还是很有必要的。
变量定义:好的做法是把将要使用的变量名用一个var关键字一并定义在代码开头,变量名间用逗号隔开。原因有二:
变量及函数命名 一般使用驼峰命名法(CamelCase),即首个单词的首字母小写,后面单词首字母大写,比如resultArray,requestAnimationFrame。对于常量,所有字母采用大写,多个单词用下划线隔开,比如WIDTH=100,BRUSH_COLOR=’#00ff00’。当变量是jQuery类型时,建议以$开头,开始会不习惯,但经常用了之后会感觉很方便,因为可以很方便地将它与普通变量区别开来,一看到以$开头我们就知道它是jQuery类型可以直接在其身上调用jQuery相关的方法,比如var $element=$(‘a’); 之后就可以在后面的代码中很方便地使用它,并且与其他变量容易区分开来。
引号的使用:既然都扯了这些与插件主题无关的了,这里再多说一句,一般HTML代码里面使用双引号,而在JavaScript中多用单引号,比如下面代码所示:1
2var name = 'Wayou';
document.getElementById(‘example’).innerHTML = '< a href="http: //wayouliu.duapp.com/">'+name+'</a>'; //href=".." HTML中保持双引号,JavaScript中保持单引号
一方面,HTML代码中本来就使用的是双引号,另一方面,在JavaScript中引号中还需要引号的时候,要求我们单双引号间隔着写才是合法的语句,除非你使用转意符那也是可以的。再者,坚持这样的统一可以保持代码风格的一致,不会出现这里字符串用双引号包着,另外的地方就在用单引号。
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
最近在写前端的代码,顺便就再学习巩固下JavaScript,在网上看见了对JavaScript的原型和闭包讲解的很透彻的博客。自己就摘过来做成了电子书,分享出来,供大家学习参考。
本书是对深入理解javascript原型和闭包系列博客的整理,该博客的作者是王福朋,在此感谢王老师。整理人:wiliam
之前学习JavaScript就对原型和闭包的概念不是很理解,总是晕乎乎的,无意间看到了王老师写的深入理解javascript原型和闭包教程,有醍醐灌顶之感,王老师讲的很透彻,所以有想要将这系列收藏做成自己的知识储备,以后可以随时随地的巩固消化。最后整理出来了本电子书,在此分享出来,也希望大家看后对javascript原型和闭包的概念有所收获。最后再次感谢王老师。
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
我们经常使用spring的aop功能,比如声明性事务,日志打印等。但其原理是什么呢,接下来我们就详细分析spring的AOP实现原理。
使用Spring的AOP功能我们需要开启才能使用,Spring给我们提供了自定义标签启用AOP的功能。AOP常用的两个标签<aop:aspectj-autoproxy />
使用注解启用AOP功能,<aop:config>
使用xml配置启用AOP功能。我们分析AOP的实现也是从这两个标签开始。
默认的bean标签,我们通过class属性就知道注册了哪些类。自定义标签是不知道的,所以需要看源码。我们看自定义标签的解析,说白了也就是看Spring给我们默认注入了哪些bean类。
aop标签解析的整个类是org.springframework.aop.config. AopNamespaceHandler
。
从AopNamespaceHandler
类的init方法中知道aop:aspectj-autoproxy/标签解析使用的是AspectJAutoProxyBeanDefinitionParser
类,从AspectJAutoProxyBeanDefinitionParser
类的init方法中我们看到注册AnnotationAwareAspectJAutoProxyCreator
代理类。
从AopNamespaceHandler
类的init方法中知道config标签的解析使用ConfigBeanDefinitionParser
类
进入此类的parse方法。在parse方法中开头注册了AspectJAwareAdvisorAutoProxyCreator
代理,接着会对aop:config下的三个子标签分别解析,三个子标签是<aop:pointcut>
,<aop:advisor>
,<aop:aspect>
。
注册AspectJExpressionPointcut
类,scope是prototype
解析此标签时注册了以下bean:DefaultBeanFactoryPointcutAdvisor
AspectJExpressionPointcut
,scope是prototype
此标签如果下面存在aop:pointcut子标签会调用解析aop:pointcut代码去解析。
其余会注册MethodLocatingFactoryBean
,SimpleBeanFactoryAwareAspectInstanceFactory
和AspectJPointcutAdvisor
类还会针对before,after,around,after-returning,after-throwing标签增加不同的bean,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15if (BEFORE.equals(elementName)) {
return AspectJMethodBeforeAdvice.class;
}
else if (AFTER.equals(elementName)) {
return AspectJAfterAdvice.class;
}
else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
return AspectJAfterReturningAdvice.class;
}
else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
}
else if (AROUND.equals(elementName)) {
return AspectJAroundAdvice.class;
}
我们在spring配置文件中加入aop:aspectj-autoproxy/这个标签后,就可以在类中使用aop注解了,但背后的原理是什么呢。从1.1aop:aspectj-autoproxy/标签解析 中我们知道解析此标签时注册了AnnotationAwareAspectJAutoProxyCreator
的bean,那么分析aop注解的使用也是从此类开始。AnnotationAwareAspectJAutoProxyCreator
的继承关系如下:
可以看到实现了BeanPostProcessor
接口,BeanPostProcessor
接口是spring的重要接口BeanPostProcessor
源码如下。1
2
3
4
5
6public interface BeanPostProcessor {
//初始化前调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//初始化后调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
当我们使用ApplicationContext相关实现类的上下文时,spring会保证前者在实例化及依赖注入完成后,在任何初始化代码(比如配置文件中的init-method)调用之前调用;后者在初始化代码调用之后调用。
InstantiationAwareBeanPostProcessor
是BeanPostProcessor
的子接口,可以在Bean生命周期的另外两个时期提供扩展的回调接口,即实例化Bean之前(调用postProcessBeforeInstantiation
方法)和实例化Bean之后(调用postProcessAfterInstantiation
方法),该接口定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
//实例化前调用
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
//实例化后调用
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
//装配属性时调用
PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
throws BeansException;
}
我们分析也是从这些接口的实现方法入手
BeanPostProcessor .postProcessBeforeInitialization空实现
BeanPostProcessor .postProcessAfterInitialization方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
//根据给定的bean的class和name构建出个key,格式beanClassName_beanName
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//如果适合被代理,则需要封装指定的bean
if (!this.earlyProxyReferences.containsKey(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//如果bean已经被处理过
if (beanName != null && this.targetSourcedBeans.containsKey(beanName)) {
return bean;
}
//如果bean无需增强
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//如果bean类是一个基础设施类,基础设施类不应该代理,或者配置了指定bean不需要自动代理
//如果bean是Advice,Advisor和AopInfrastructureBean的实例代表是基础设置类
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 如果存在增强方法则创建代理
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果获取到了增强则需要针对增强创建代理
if (specificInterceptors != DO_NOT_PROXY) {
//将创建的代理bean放到advisedBeans中,用于前面的判断
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建代理
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
//将创建的代理bean放到advisedBeans中,用于前面的判断
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
可以看到Spring代码的精巧,先构建出骨架然后在逐步完善,在函数中我们可以看到代理创建的雏形。当然在正式开始之前需要经过一些判断,比如是否已经处理过或者是否需要跳过的bean。Spring真正开始创建代理是从Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
代码及以下开始的。
创建代理主要包含了两个步骤:
AbstractAdvisorAutoProxyCreator.java类对getAdvicesAndAdvisorsForBean方法重写,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) {
//查找合适的增强
List advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
/**
* Find all eligible Advisors for auto-proxying this class.
* @param beanClass the clazz to find advisors for
* @param beanName the name of the currently proxied bean
* @return the empty List, not {@code null},
* if there are no pointcuts or interceptors
* @see #findCandidateAdvisors
* @see #sortAdvisors
* @see #extendAdvisors
*/
protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
这里说下Spring中的Advisor。Advisor充当Advice和Pointcut的适配器。包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码。
我们可以看到Spring又是层层构建,清晰的描绘了aop的实现逻辑findEligibleAdvisors
方法中做了两件事:
由于我们分析的是使用注解进行的aop,即最终的代理处理类是AnnotationAwareAspectJAutoProxyCreator
,此类中重写了findCandidateAdvisors
方法,此方法如下:1
2
3
4
5
6
7
8
protected List<Advisor> findCandidateAdvisors() {
//获取父类中加载配置文件中的aop声明,使用注解配置aop并不是丢弃了对xml配置的支持。
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
return advisors;
}
调用父类中的方法super.findCandidateAdvisors()
主要是从配置文件中查找所有实现了Advisor
接口的bean,这里就不在分析,代码逻辑很清晰,感兴趣的读者可以自己分析下。
接下来的第二行就是查找标记了aop注解的bean并增加到结果集中。而使用的代码点就是this.aspectJAdvisorsBuilder.buildAspectJAdvisors()
,该方法整体逻辑如下:
方法代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 * Look for AspectJ-annotated aspect beans in the current bean factory,
* and return to a list of Spring AOP Advisors representing them.
* <p>Creates a Spring Advisor for each AspectJ advice method.
* the list of { org.springframework.aop.Advisor} beans
* #isEligibleBean
*/
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = null;
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new LinkedList<Advisor>();
aspectNames = new LinkedList<String>();
//获取所有的beanName
String[] beanNames =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
//循环所有的beanName 找出对应的增强方法
for (String beanName : beanNames) {
//不合法的bean则略过,有子类定义规则默认返回true
if (!isEligibleBean(beanName)) {
continue;
}
// We must be careful not to instantiate beans eagerly as in this
// case they would be cached by the Spring container but would not
// have been weaved
//获取对应的bean的类型
Class beanType = this.beanFactory.getType(beanName);
if (beanType == null) {
continue;
}
//如果存在Aspect注解
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//解析标记AspectJ注解中的增强方法
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
if (aspectNames.isEmpty()) {
return Collections.EMPTY_LIST;
}
//将结果集记录在结果中
List<Advisor> advisors = new LinkedList<Advisor>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}
上面代码中对增强advice的获取委托给了this.advisorFactory.getAdvisors(factory)
方法,此方法代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
//获取标记为AspectJ的类
final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
//获取标记为AspectJ的name
final String aspectName = maaif.getAspectMetadata().getAspectName();
//校验是否符合aop注解规则
validate(aspectClass);
// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
// so that it will only instantiate once.
final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(maaif);
final List<Advisor> advisors = new LinkedList<Advisor>();
//获取实现Advisor类的所有标识没有Pointcut的方法。
for (Method method : getAdvisorMethods(aspectClass)) {
//获取普通的增强器
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
// If it's a per target aspect, emit the dummy instantiating aspect.
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
//如果寻找的增强器不为空而且又配置了增强延迟初始化那么需要在首位加入同步实例化增强器
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}
// Find introduction fields.
//获取DeclareParents 注解
for (Field field : aspectClass.getDeclaredFields()) {
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}
return advisors;
}
此方法的逻辑是
下面详细讲解每个步骤
普通增强器获取
普通增强器的获取是通过Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
代码获取1
2
3
4
5
6
7
8
9
10
11
12
13
14public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif,
int declarationOrderInAspect, String aspectName) {
//校验
validate(aif.getAspectMetadata().getAspectClass());
//切点信息获取
AspectJExpressionPointcut ajexp =
getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
if (ajexp == null) {
return null;
}
//根据切点信息生成对应的增强器
return new InstantiationModelAwarePointcutAdvisorImpl(
this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
}
(1)切点信息获取
切点信息获取就是指定注解的表达式信息获取,如@Before("test")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
//获取方法上注解
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
//使用AspectJExpressionPointcut 实例封装获取的信息
AspectJExpressionPointcut ajexp =
new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
//提取得到的注解中的表达式如: //@Pointcut("execution(* *.* test* (..))")中的execution( * * .* test* (..))
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
return ajexp;
}
protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
//设置敏感的注解类
Class<? extends Annotation>[] classesToLookFor = new Class[] {
Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
for (Class<? extends Annotation> c : classesToLookFor) {
AspectJAnnotation foundAnnotation = findAnnotation(method, c);
if (foundAnnotation != null) {
return foundAnnotation;
}
}
return null;
}
//获取指定方法上的注解并使用AspectJAnnotation封装
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
A result = AnnotationUtils.findAnnotation(method, toLookFor);
if (result != null) {
return new AspectJAnnotation<A>(result);
}
else {
return null;
}
}
(2)根据切点信息生成增强。所有的增强都由Advisor的实现类InstantiationModelAwarePointcutAdvisorImpl统一封装的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public InstantiationModelAwarePointcutAdvisorImpl(AspectJAdvisorFactory af, AspectJExpressionPointcut ajexp,
MetadataAwareAspectInstanceFactory aif, Method method, int declarationOrderInAspect, String aspectName) {
this.declaredPointcut = ajexp;
this.method = method;
this.atAspectJAdvisorFactory = af;
this.aspectInstanceFactory = aif;
this.declarationOrder = declarationOrderInAspect;
this.aspectName = aspectName;
if (aif.getAspectMetadata().isLazilyInstantiated()) {
// Static part of the pointcut is a lazy type.
Pointcut preInstantiationPointcut =
Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
// If it's not a dynamic pointcut, it may be optimized out
// by the Spring AOP infrastructure after the first evaluation.
this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif);
this.lazy = true;
}
else {
// A singleton aspect.
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
this.pointcut = declaredPointcut;
this.lazy = false;
}
}
在封装过程中只是简单地将信息封装在类的实例中,所有的信息单纯地赋值,在实例初始化的过程中还完成了对于增强器的初始化。因为不同的增强所体现的逻辑是不同的,比如@Be-fore(“test()”)与@After(“test()”)标签的不同就是增强器增强的位置不同,所以就需要不同的增强器来完成不同的逻辑,而根据注解中的信息初始化对应的增强器就是在instantiateAdvice函数中实现的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {
return this.atAspectJAdvisorFactory.getAdvice(
this.method, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {
Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
// If we get here, we know we have an AspectJ method.
// Check that it's an AspectJ-annotated class
if (!isAspect(candidateAspectClass)) {
throw new AopConfigException("Advice must be declared inside an aspect type: " +
"Offending method '" + candidateAdviceMethod + "' in class [" +
candidateAspectClass.getName() + "]");
}
if (logger.isDebugEnabled()) {
logger.debug("Found AspectJ method: " + candidateAdviceMethod);
}
AbstractAspectJAdvice springAdvice;
switch (aspectJAnnotation.getAnnotationType()) {
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
case AtAround:
springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
break;
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method " + candidateAdviceMethod);
}
// Now to configure the advice...
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrderInAspect);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();
return springAdvice;
}
从函数中可以看到,Spring会根据不同的注解生成不同的增强器,例如AtBefore会对应AspectJMethodBeforeAdvice,而在AspectJMethodBeforeAdvice中完成了增强方法的逻辑。具体的实现感兴趣的读者可以自己看看。
寻找匹配的增强器
前面的函数中已经完成了所有增强器的解析,但是对于所有增强器来讲,并不一定都适用于当前的Bean,还要挑取出适合的增强器,也就是满足我们配置的通配符的增强器。具体实现在AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply中。1
2
3
4
5
6
7
8
9
10
11
12protected List<Advisor> findAdvisorsThatCanApply(
List<Advisor> candidateAdvisors, Class beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
//过滤已经得到的advisors
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
继续看findAdvisorsThatCanApply:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
//首先处理引介增强
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
//引介增强已经处理
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
//对于普通bean 的处理
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
findAdvisorsThatCanApply函数的主要功能是寻找所有增强器中适用于当前class的增强器。引介增强与普通的增强是处理不一样的,所以分开处理。而对于真正的匹配在canApply中实现。实现的逻辑很简单就是循环之前查找出来的所有增强,逐个判断该增强是否匹配当前的Bean,匹配则加进Advisor结果集的list中。
上面的代码已经将获取的增强器讲完了,那么接下来便是根据增强器对匹配的bean创建代理了。我们是否忘了从哪里分析了,那再回顾下,从AbstractAutoProxyCreator.wrapIfNecessary方法分析开始,先是获取匹配的增强器调用getAdvicesAndAdvisorsForBean方法。然后再是创建代理createProxy方法,接下来我们将会分析此方法
1 | protected Object createProxy( |
对于代理类的创建及处理,Spring委托给力ProxyFactory去处理,而在此函数中主要是对ProxyFactory的初始化操作,进而对真正的创建代理做准备,这些初始化内容包括:
其中封装Advisor并加入到ProxyFactory中以及创建代理是两个相对繁琐的过程,可以通过ProxyFactory提供的addAdvisor方法直接将增强器织入代理创建工厂中,但是将拦截器封装为增强器还是需要一定逻辑的,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64/**
* Determine the advisors for the given bean, including the specific interceptors
* as well as the common interceptor, all adapted to the Advisor interface.
* @param beanName the name of the bean
* @param specificInterceptors the set of interceptors that is
* specific to this bean (may be empty, but not null)
* @return the list of Advisors for the given bean
*/
protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
// 解析注册的所有InterceptorName
Advisor[] commonInterceptors = resolveInterceptorNames();
List<Object> allInterceptors = new ArrayList<Object>();
if (specificInterceptors != null) {
//加入拦截器
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (commonInterceptors != null) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
}
else {
allInterceptors.addAll(Arrays.asList(commonInterceptors));
}
}
}
if (logger.isDebugEnabled()) {
int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
" common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
}
Advisor[] advisors = new Advisor[allInterceptors.size()];
for (int i = 0; i < allInterceptors.size(); i++) {
//将拦截器转换为advisor
advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
}
return advisors;
}
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
//如果本身就是Advisor类型,无需处理直接转换
if (adviceObject instanceof Advisor) {
return (Advisor) adviceObject;
}
//因为此封装方法只对Advisor于advice两种类型的数据有效,如果不是将不能封装。
if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
}
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
// So well-known it doesn't even need an adapter.
//如果是MethodInterceptor类型则使用DefaultPointcutAdvisor封装。
return new DefaultPointcutAdvisor(advice);
}
for (AdvisorAdapter adapter : this.adapters) {
// Check that it is supported.
if (adapter.supportsAdvice(advice)) {
return new DefaultPointcutAdvisor(advice);
}
}
throw new UnknownAdviceTypeException(advice);
}
由于Spring中涉及过多的拦截器、增强器、增强方法等方式来对逻辑进行增强,所以非常有必要统一封装成Advisor来进行代理的创建,完成了增强的封装过程,那么解析最重要的一步就是代理的创建于获取了。1
2
3public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
创建代理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
//创建代理
return getAopProxyFactory().createAopProxy(this);
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return CglibProxyFactory.createCglibProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
到此已经完成了代理的创建,我们可以看到Spring会动态的选择使用Cglib或者jdk创建代理。这里就不细分析了。
至此我们分析完了<aop:aspectj-autoproxy/>
标签创建Spring的AOP全流程。
前面分析完了在使用<aop:aspectj-autoproxy/>
标签时,aop的实现原理,接着分析<aop:config>
实现原理。<aop:config>
在 1.2节中解析此标签时我们看到注册了AspectJAwareAdvisorAutoProxyCreator
看到此类是不是很熟悉,看下此类的集成关系图:
看到了吧,我们使用注解aop的AnnotationAwareAspectJAutoProxyCreator
就是直接继承此类的,回头看看2.1节的AnnotationAwareAspectJAutoProxyCreator
此类继承关系图。如果你理解了之前对注解AOP实现原理的分析,那么相信你对此标签的实现原理也是了然于胸了。这里主要说下步骤,就不进行相信分析了
<aop:config>
标签时,获取对应的子标签,注册对应的Bean,例如Advisor和pointcut。AnnotationAwareAspectJAutoProxyCreator
的postProcessBeforeInstantiation
方法进行代理的创建。postProcessBeforeInstantiation
方法我们在之前就分析了此流程。至此aop的流程就分析完了,感兴趣的可以留言探讨,一起学习进步。
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
Spring在3.1版本添加了org.springframework.core.env包,该包下面描述了Spring的环境架构体系,对Spring环境配置方面起到了重要作用。该包下面有PropertyResolver,Environment等接口描述Spring的环境信息。其中Environment接口作用可以让Spring根据不同的环境配置加载不同的配置信息。例如我们常用的测试环境和生产环境需要使用不同的数据源,通过配置环境信息即可达到无缝切换。PropertyResolver可以让我们加载属性信息。接下来就详细解说Spring的环境架构
PropertySource是抽象类,该类是对(name/value)属性的抽象,其中value可以是任何类型属性,例如可以是java.util.Properties对象,java.util.Map对象,ServletContext和ServletConfig对象(可以获取初始化参数)。其方法public abstract Object getProperty(String name)
是抽象方法,由不同的子类去实现。Spring对常用的属性都定义了对应的实现类,不同的实现代表了不同的类型属性。其类图如下:
上图其中最重要的是EnumerablePropertySource类,是枚举行属性类,该类下面的子类就有我们常用属性文件对象,如PropertiesPropertySource
PropertySource和Spring中的Resource接口很像,Resource对所有的资源文件进行了抽象,并提供了各种资源的实现类,如ClassPathResource,FileSystemResource等,这里就不详细介绍,感兴趣的可以自己去探索。
PropertyResolver接口中定义了属性资源解析方法,可以理解为对PropertySource类的属性value解析。
Environment接口继承PropertyResolver接口,之所以要单独列出来是因为Spring对外暴露的是Environment而不是PropertyResolver接口。类图如下:
从图中可以看出两大实体组件,分别是环境(StandardEnvironment)和属性解决器(PropertySourcesPropertyResolver)。spring框架把两个组件的对外提供的功能性接口和自身的配置性接口进行了拆分,组件的配置API都集中在ConfigurableXXX接口中。
两个组件都只有唯一的标准实现类,即StandardEnvironment和PropertySourcesPropertyResolver。
两个组件之间的关系可以这样理解:
环境对象通过持有属性解决器对象的引用,从而实现组件复用。同时,环境对象所定义的接口继承自属性解决器接口,因此,环境对象也具有属性解决器的所有功能,而且在属性解决器的功能基础之上,又扩展出了环境的功能。细心的读者可能已经看出,这实际上是使用了装饰器模式,StandardEnvironment通过对PropertySourcesPropertyResolver的装饰,进行了功能增强。
我们可以将整个体系拆分成Environment和PropertyResolver两个体系,然后分别讲解
通过第一节的介绍说明了Spring整体环境架构,那么整体环境架构已经构建好了,我们该如何使用呢?
Spring提供了EnvironmentCapable接口,我们实现该接口可以使组件具有环境。接口定义如下:1
2
3
4
5public interface EnvironmentCapable {
Environment getEnvironment();
}
接口定义的唯一方法,就是获取与当前组件相关联的Environment环境对象。
该接口的实现类如图:
![EnvironmentCapable实现类][5]
可以看到ApplicationContext接口继承自这个接口,也就是说所有应用上下文都是具有环境的,当使用ApplicationContext实现类时调用getEnvironment就可以获取环境信息了。
参考资料
spring-core组件详解——环境体系
详解Spring中的Profile
想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
Spring使用Environment表示应用的上下文环境,Environment接口作用可以让Spring根据不同的环境配置加载不同的配置信息。例如我们常用的测试环境和生产环境需要使用不同的数据源,通过配置环境信息即可达到无缝切换。
Environment环境代表当前应用运行时所处的环境。
整个应用环境模型包括2个关键方面:
一个profile组,是一个以name名称命名的、逻辑上的、要被注册到容器中的BeanDefinition的集合。简单一点说,一个profile就代表一组BeanDefinition,当是xml配置时使用
ServerEnv接口1
2
3
4
5
6package me.wiliam.spring.xmlconfig.bean;
public interface ServerEnv {
void getEnvData();
}
生产环境实现类1
2
3
4
5
6
7
8
9
10
11package me.wiliam.spring.xmlconfig.bean;
public class ProductEnv implements ServerEnv {
public void getEnvData() {
System.out.println("this is product Environment");
}
}
测试环境实现类1
2
3
4
5
6
7
8
9
10
11package me.wiliam.spring.xmlconfig.bean;
public class TestEnv implements ServerEnv {
public void getEnvData() {
System.out.println("this is Test Environment");
}
}
xml配置1
2
3
4
5
6
7
8
9<beans profile="product">
<bean id="serverEnv" class="me.wiliam.spring.xmlconfig.bean.ProductEnv">
</bean>
</beans>
<beans profile="test">
<bean id="serverEnv" class="me.wiliam.spring.xmlconfig.bean.TestEnv">
</bean>
</beans>
测试类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package me.wiliam.spring.xmlconfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import me.wiliam.spring.xmlconfig.bean.ServerEnv;
public class AppMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("me/wiliam/spring/xmlconfig/config/spring-profile.xml");
ServerEnv serverEnv = (ServerEnv)context.getBean("serverEnv");
serverEnv.getEnvData();
}
}
jvm环境变量设置1
-Dspring.profiles.active=product
执行结果1
this is product Environment
ProdcutConfig配置类1
2
3
4
5
6
7
8
9@Configuration
@Profile("product")
public class ProdcutConfig {
@Bean(name="serverEnv")
public ProductEnv productEnv(){
return new ProductEnv();
}
}
TestConfig配置类1
2
3
4
5
6
7
8
9@Configuration
@Profile("test")
public class TestConfig {
@Bean(name="serverEnv")
public TestEnv testEnv(){
return new TestEnv();
}
}
总配置1
2
3
4@Configuration
@Import({TestConfig.class,ProdcutConfig.class})
public class AppConfig {
}
测试1
2
3
4
5
6
7public class APP {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ServerEnv server =context.getBean(ServerEnv.class);
server.getEnvData();
}
}
jvm环境变量设置1
-Dspring.profiles.active=product
执行结果1
this is product Environment
上面切换配置使用的是jvm环境变量配置的,如果是web环境,可以针对测试环境和生产环境在启动时增加环境变量就可以自动区分生产环境还是测试环境了。
激活profile还有其他几种方式,例如使用xml,下面说明下:
Spring通过两个不同属性来决定哪些profile可以被激活(注意:profile是可以同时激活多个的),一个属性是spring.profiles.active和spring.profiles.default。这两个常量值在Spring的AbstractEnvironment中有定义,查看AbstractEnvironment源码:1
2
3public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
如果当spring.profiles.active属性被设置时,那么Spring会优先使用该属性对应值来激活Profile。当spring.profiles.active没有被设置时,那么Spring会根据spring.profiles.default属性的对应值来进行Profile进行激活。如果上面的两个属性都没有被设置,那么就不会有任务Profile被激活,只有定义在Profile之外的Bean才会被创建。我们发现这两个属性值其实是Spring容器中定义的属性,而我们在实际的开发中很少会直接操作Spring容器本身,所以如果要设置这两个属性,其实是需要定义在特殊的位置,让Spring容器自动去这些位置读取然后自动设置,这些位置主要为如下定义的地方:
上面的前三个Springwebmvc使用的是StandardServletEnvironment其继承StandardEnvironment,初始化时会读取ServletContext和ServletConfig的InitParameter这时就会将我们在配置文件中配置的spring.profiles.active配置项读入spring的上下文中了,然后spring就能根据配置的环境信息加载对应的配置项了。
我们在实际的使用过程中,可以定义默认的profile为开发环境,当实际部署的时候,主需要在实际部署的环境服务器中将spring.profiles.active定义在环境变量中来让Spring自动读取当前环境下的配置信息,这样就可以很好的避免不同环境而频繁修改配置文件的麻烦。
在几乎所有的应用中,Properties环境变量都扮演着非常重要的角色,且这些变量值可以来自于各种PropertySource属性源,如:properties文件、jvm虚拟机环境变量、操作系统环境变量、JNDI、Servlet上下文参数、自定义的属性对象、Map对象,等等。Environment环境对象为用户提供了方便的接口,用于配置和使用属性源。
整体类图如下:
![environment][1]
刚才提到环境模型具有2个关键方面:profiles和properties,从体系图中可以看出,properties方面的所有功能由PropertyResolver属性解决器来实现,环境模型只是通过装饰模式,在PropertyResolver功能的基础上,额外扩展出了profiles方面的功能。因此在接口方面,Environment继承自PropertyResolver,从实现类方面,AbstractEnvironment类内部持有一个PropertySourcesPropertyResolver类型对象的引用,PropertyResolver和ConfigurablePropertyResolver的接口,都委托调用了PropertySourcesPropertyResolver实现。
关于PropertyResolver,我前边的文章PropertyResolver属性解析器已经进行了详细的解释,因此在本文中,我们重点关注环境模型在profiles方面的实现原理,体系图如下:
1 | public interface Environment extends PropertyResolver { |
1 | public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver { |
该类实际上实现了以上接口的所有方法,且额外扩展了自定义属性源的入口:
protected void customizePropertySources(MutablePropertySources propertySources);
但是因为初始时属性源集合只是一个空集合,没有任何意义,因为该类定义为抽象基类,不能直接实例化使用。部分代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* 部分代码
* @author lixin
*
*/
public class AbstractEnvironment {
/**
* 可变属性源集合
*/
private final MutablePropertySources propertySources = new MutablePropertySources();
/**
* 在构造方法中直接调用自定义属性源集合
*/
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
/**
* 自定义属性源集合,
* 默认空实现,子类可重写,用来配置属性源。
*
* @param propertySources
*/
protected void customizePropertySources(MutablePropertySources propertySources) {
}
}
该类定义了Spring应用运行时使用的标准环境,其实就是重写了customizePropertySources方法,先后追加了jvm虚拟机环境变量属性源和操作系统环境变量属性源这两个属性源。当然对于特殊的spring运行环境,我们可以创建标准环境的子类,以实现属性源的扩充,比如:StandardServletEnvironment类,用于web应用环境。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class StandardEnvironment extends AbstractEnvironment {
// 操作系统环境变量属性源的名称
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
// jvm虚拟机系统环境变量属性源的名称
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
protected void customizePropertySources(MutablePropertySources propertySources) {
// 追加虚拟机环境变量属性源
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// 追加操作系统环境变量属性源
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
以上就是spring框架的基本环境体系。
[1]:http://imgcdn.yalongkeji.com/blog/20170225/120820499.jpg
[2]:http://imgcdn.yalongkeji.com/blog/20170225/122833815.jpg
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
对Spring的PropertyResolver属性解析器详细介绍。
PropertyResolver属性解析器,主要具有两个功能:
通过propertyName属性名获取与之对应的propertValue属性值(getProperty)。
把${propertyName:defaultValue}格式的属性占位符,替换为实际的值(resolvePlaceholders)。
注意:getProperty获取的属性值,全都是调用resolvePlaceholders进行占位符替换后的值。
组件体系图如下:
该接口定义了组件所具有的所有功能。其一是通过key获取对应的value,当获取不到value时,有3种选择:返回null值、使用指定的默认值或者抛出一个非法状态异常。获取到的value值默认是String类型,当然也可以认为指定一种类型,这依赖于ConversionService进行类型转换。
另外还有一个问题:属性值中可以包含${}格式的占位符,因此,接口添加了另一个功能就是替换属性值中的占位符(注意:属性名是不允许存在占位符的,就算存在,组件也不会当作占位符进行替换)。当占位符无法替换时,也有2种选择:保持原样或者抛出一个非法参数异常。具体接口如下:
该接口定义了如何对组件本身进行配置。如:刚刚提到获取value时可以指定任意类型,这依赖于ConversionService进行类型转换,当前接口就提供了对ConversionService的设置和获取。另外,可以配置属性占位符的格式,包括:占位符前缀(默认为”${“)、占位符后缀(默认为”}”)、占位符值分隔符(默认为”:”,用于分隔propertyName和defaultValue)。组件还可以设置哪些属性是必须存在的,还可以校验必须存在的属性是否真的存在(不存在的话会抛出异常)。具体接口如下:
上述两个接口的抽象实现类。它实现了ConfigurablePropertyResolver接口的所有方法。关于PropertyResolver接口方法,还有3个getProperty方法需要子类实现(其他重载方法均调用这3个方法):1
2
3
4
5
6
7
8String getProperty(String key);
<T> T getProperty(String key, Class<T> targetType);
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
/**
* 当前类还额外定义了一个抽象方法,用于直接返回获取到的value值(不进行占位符替换)。
* 一般的getProperty方法默认都会替换掉value值中的占位符后返回。
*/
protected abstract String getPropertyAsRawString(String key);
至于替换属性占位符,则借助2个PropertyPlaceholderHelper属性占位符助手(工具类)对象完成,这2个对象一个为严格模式,一个为非严格模式。
该类是体系中唯一的完整实现类。它以PropertySources属性源集合(内部持有属性源列表List
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
在网上浏览博客时经常看到页面的右上角或左上角有一个fork me on github的按钮,本文将介绍如何实现。
右上角截图
左上角截图
点我跳转 挑选你自己喜欢的样式。
打开文件:hexo博客根目录\themes\next\layout\_layout.swig
找到如下代码块1
2<div class="{{ container_class }} {% block page_class %}{% endblock %} ">
<div class="headband"></div>
添加自己喜欢的样式后结果如下1
2
3
4<div class="{{ container_class }} {% block page_class %}{% endblock %} ">
<div class="headband"></div>
<a target="_blank" href="https://github.com/wiliam2015"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
访问你的本地服务测试看看修改的效果吧。
按照上面的步骤当屏幕缩小后还会显示fork me on github图标,但这不是我想要的,我希望在大屏下显示,小屏后就不显示了。方法如下:
修改文件hexo博客根目录\themes\next\layout\_layout.swig
找到如下代码块1
2
3
4<html class="{{ html_class | lower }}">
<head>
{% include '_partials/head.swig' %}
<title>{% block title %}{% endblock %}</title>
添加如下代码,结果如下1
2
3
4
5
6
7
8
9
10
11
12
13
14<head>
{% include '_partials/head.swig' %}
<title>{% block title %}{% endblock %}</title>
<style>
.forkme{
display: none;
}
@media (min-width: 768px) {
.forkme{
display: inline;
}
}
</style>
</head>
最后在2.2节添加的代码块上套上div加上class就行了,如下1
2
3<div class="forkme">
<a target="_blank" href="https://github.com/wiliam2015"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
</div>
最后在试试效果达到自己的想要的了
]]>想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
2017年2月1日我和老婆的爱情结晶来到了这个世界,嘿嘿,我的贴心小棉袄,从此自己就当爸爸了。
宝宝你可知道,妈妈为了让你来到这个世界,可是受尽了辛苦,从最初有了你的时候爸爸和妈妈高兴的都睡不着觉到每次孕检时的担心,生怕医生说出有不好结果。在你还在妈妈肚肚的时候,妈妈贫血又缺钙等,为了你的好好的成长妈妈可是吃了很多药和食物进补哦,还有就是妈妈有甲减症状,需要每天都吃药维持指标正常,这些都是为了宝宝你的健康成长。最后都到了40周了,妈妈的羊水都偏少了,宝宝你还是没有动静,爸爸妈妈也是很着急也很担心,你的爷爷奶奶,姥姥姥爷,大姑,大姨等都是很担心,最后胎心监测你的心跳加速了,频率都达到170了,医生说是宫内窘迫,最后不得不进行剖宫产手术让你来到这个世界。最后终于等到你,来到了这个世界和爸爸妈妈见面了,我们一起组成了三口之家,希望宝宝你能好好的体验这个五彩缤纷的世界。
宝宝为了让你能够健康快乐的成长,爸爸妈妈会努力给你一个健康快乐的成长环境。最后希望宝宝以后健康快乐的成长,踏踏实实做人,活出自己的精彩人生。
]]>