前言
作为一个Java开发者,工作了几年后,越发觉力有点不从心了,技术的世界实在是太过于辽阔了,接触的东西越多,越感到前所未有的恐慌。
每天捣鼓这个捣鼓那个,结果回过头来,才发现这个也不通,那个也不精。就连我吃饭的家伙Java,现在想想,其实我根本就不了解。
可是每当编写简历的时候,总想把工作经验、工作年限写的长一点,半年写成一年,一年写成两年。可是每当有人问我技术原理的时候,又会想,
我的工作时间要是短一点的话,答不上来是不是就不会这么丢脸。
还记得刚工作不久,就在项目中使用过Spring了,但是那个时候,只是照着别人写的代码,照葫芦画瓢似得写着自己的代码,画瓢花久了,写的熟练了,心中就开始窃喜,
竟然也就敢厚着脸皮说,我也会Spring了,简历上就毫不犹豫的写上:了解Spring。(真是臭不要脸啊)
Spring真的是很流行,做过了好多项目,基本上都或多或少的使用了Spring框架或者集成了Spring框架,于是乎,无论进入了哪个项目中,竟然也都得心应手。
然后就有点飘飘然,领导还给安排带几个新人,开发时,还得意的给新人们讲解,这个注解这样使用,那个配置那样配置。但是,其实如何使用这个东西,只要是程序员,短短几天也都会了。
几天后,新人就能与你干同样的活了,你这个老人与新人的区别在哪里呢?其实这时,我有点慌了。
平时游荡于各个技术交流群,发现群里面其实有好多还是在校生,就开始钻研各种技术难题。回头再想想,我当时还在学校的时候在干嘛呢:打游戏、看小说、睡懒觉、泡校内网、约妹子、耗时间…
人家还在学生时期,就开始阅读Spring源码了。而我呢,竟然都用了好几年了,都从来没有提起过阅读源码的念头。
后来有一次面试,人家问:看你也有几年工作经验了,说一说Spring的原理吧、IOC、DI、AOP…再说一说,从一个请求开始,一直到得到响应,Spring都干了些什么?
我的个亲娘啊,你到底在说什么,我怎么听不懂。我忽然想起了一首歌:
我想了很久,我开始慌了,是不是我又做错了什么,你哭着对我说,童话里都是骗人的,你不可能是我要的程序猿
岔题了…
亡羊补牢,痛定思痛,总而言之,就读一下Spring源码(3.1版本)看看吧。
读读读读读…
对于没有任何源码阅读经验的人,而且大局观整体概念很差的人来说,源码真的是太难读了,可能还是人笨吧。所以我只能采取老办法,所谓书读百遍,其义自现,读源码应该也是同样的道理。
到开始写本篇笔记开始,前前后后已经花了整整3周时间,期间各种debug,打了上百个断点,不厌其烦的一遍又一遍跟踪跟进,从服务器启动开始,
从HttpServletBean的init()进入,再到initWebApplicationContext(),再到refresh(),再到refreshBeanFactory(),再到finishRefresh(),直到服务器启动成功。不知道读了多少遍,
但是源码的东西实在的太多了,想要完全读懂,完全理清头绪,还差很远啊。所以我只重点关注了两块内容,就是bean的定位加载解析注册、bean的实例化两大块内容,其他地方稍作了解,没有太过深入。
整个容器的启动流程,都在AbstractApplicationContext的refresh()的模板方法中了。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
其实,我并没有上来就看源码,而是先从看书开始,稍微了解,知道了一些关键点,关键流程,自己产生了一堆疑问,然后带着疑问去读源码,读着读着,发现有些疑问就这么解决了。
看书:主要以《SPRING技术内幕:深入解析SPRING架构与设计原理》为主,仅仅才看了前两章节,关于Spring Framework的核心实现,AOP什么,事务控制什么的都还没看。
看博客:看源码过程中,有些不理解的,就上网查博客,然后再回头读源码。觉得写得还可以的博客文章我记录了下来,供以后参考(希望管理员不要因为有外部链接,就把我踢掉了)。
正如上文所说,因为水平有限,本篇随笔并非是解析源码的文章,因为源码很多看不懂的地方并不敢妄下断言, 网上各种解析源码,画UML图,时序图(我不会画图%>_<%)的文章也有很多,
我是抱着解决疑问的态度来分析读源码的,另外在大神眼中,有些问题看起来可能问的很愚蠢,但是谁让我是菜鸟呢。
以上是我的心路历程,不看也罢。
那么,列出有疑问的地方(目前主要针对IOC相关部分、问题将不断补充)
(补充:我用来阅读并且debug的demo是基于注解配置的,另外以下说明都纯属个人谬论,如果错漏,请予以批评指正。)
问题1:我为什么要使用Spring?
问题2:Spring怎么这么聪明,知道哪些bean需要被实例化?
问题3:Spring中Bean是什么时候被实例化的?
问题4:依赖的bean是何时被注入的?
问题5:bean的单例模式?原型模式是什么东西?分别是何时被实例化的?
问题6:采用注解注入时,为什么只声明接口就可以将其实现类注入?
问题7:采用注解注入时,接口与实现类为何都能注入成功?区别?
问题8:加上了autowired的属性是什么时候实例化的?
问题9:网络上经常说的“第一次使用bean的时候实例化该bean”是什么意思?
问题10:发送一个请求,是怎么定位到具体的Controller的某一个方法的?
问题11:HandlerMapping、HandlerAdater与Controller到底是什么关系?
问题1:我为什么要使用Spring?
这个问题其实比较尴尬,因为其实我入行比较晚, 这个时候,已经有一大波比较成熟的框架体系了,早期的什么繁琐的臃肿的框架基本上没用过几个,再加上项目经验比较单一,用来用去就那么几个框架,用起来都大差不差,
用的熟练了,感觉都差不多。所以说,完全没有能力来横向比较,谁谁谁架构合理,谁谁谁扩展性强,只能勉强说这个框架的那种写法挺方便,比起单纯的Servlet写法要方便之类的….其他的,我都呵呵就好了。
如果你要强行问我为什么使用Spring,那我只能回答你:因为大家都在用啊,呵呵…
问题2:Spring怎么这么聪明,知道哪些bean需要被实例化?
什么控制反转、依赖注入,说到底就是程序在需要使用一个bean的时候,Spring框架确保该bean已经被实例化了,可以直接拿来使用。那么这时候,我想要知道,Spring是如何知道哪些bean需要实例化?
其实程序总归是程序,程序是很笨的,它并不知道要实例化哪些东西,除非你告诉它。
首先需要配置DispatcherServlet,这是web应用的总入口,也可以说是中央处理器,就是通过它,一步一步的启动Spring容器的。
在web.xml中
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/readspring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
另外还需要配置另外一个文件:readspring-servlet.xml,这个东西叫做上下文,主要是告诉Spring,在启动的时候干些什么事情,启动哪些功能,实例化哪些Bean。
上一个最简单的配置:
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- 扫描指定目录 -->
<context:component-scan base-package="com.readspring" />
<!-- 不拦截静态资源 -->
<mvc:default-servlet-handler />
<!-- 启动注解支持 -->
<mvc:annotation-driven />
<!-- 配置视图解析器 -->
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
我不逐一解读每句话的意思,还是针对上面的问题,Spring如何知道哪些bean要被实例化?
或者说的具体一点,Spring是如何知道要实例化我们写的Controller、Service、Dao的。
是这段配置告诉它的。
<!-- 扫描指定目录 -->
<context:component-scan base-package="com.readspring" />
从字面意思可以看出,component-scan就是组件扫描的意思,然后有一个base-package,告诉它需要扫描的文件路径。
组件自动扫描是在Spring2.5加上的,在一个稍大的项目中通常会有上百个组件,如果都使用XML的bean定义来配置组件的话,显然会增加配置文件的体积,
查找及维护也不方便,而Spring2.5就为我们引入了组件自动扫描机制,它可以在classpath下寻找标注了@Service、@Repository、@Controller、@Component注解的类
并把这些类纳入Spring容器中管理,它的作用和在XML中使用bean节点配置组件是一样的。
另外补充说明,既然是component扫描,为什么标注了@Service等也会被扫描到,可以看一下源码。
Service注解本身也被标注了@Component注解,所以说可以被扫描到,相当于继承自@Component。
下面,我大概的描述一下,容器启动的过程中,如何根据配置,来扫描指定类的。
容器启动过程中,首先调用DispatcherSerlvet的init方法,init方法内部根据web.xml的配置,读取配置的上下文readspring-servlet.xml,然后逐句解析该上下文,
当它读取到context:component-scan标签时,就启动对应的解析器,也可以叫做扫描器,对应的Class为:ComponentScanBeanDefinitionParser
有这么一个对应关系的Class,可以让Spring知道,为什么读取到这个标签,就实例化ComponentScanBeanDefinitionParser这个类。
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}