Spring容器深入(li)
spring中最常用的控制反转和面向切面编程。
一、IOC
IoC(Inversion of Control,控制倒转)。对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,本来需要在A中自己编写代码来获得一个Connection对象,有了spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。
所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
二、IOC的好处
可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。代码中的每一个Class都可以单独测试,彼此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的好处。
每个开发团队的成员都只需要关心实现自身的业务逻辑,完全不用去关心其它的人工作进展,因为你的任务跟别人没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不清责任了。
可复用性好,我们可以把具有普遍性的常用组件独立出来,反复利用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。
IOC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。
三、IOC常见的注入方式
接口注入(Spring不支持),接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如其他两种注入模式,因而在 IOC 的专题世界内并不被看好。
setter注入,对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。 如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。
构造器注入,在构造期间完成一个完整的、合法的对象。 所有依赖关系在构造函数中集中呈现。 依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。 只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。
四、AOP
AOP(Aspect-OrientedProgramming,面向切面编程)。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
五、AOP概念
切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。切面用spring的 Advisor或拦截器实现。
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice。
切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。
引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入
六、AOP通知类型
前置通知(before advice):在切入点之前执行。
后置通知(after returning advice):在切入点执行完成后,执行通知。
环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。
异常通知(after throwing advice):在切入点抛出异常后,执行通知。
七、Spring启动过程
通过对ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");这条语句的跟踪,最重要的是调用了AbstractApplicationContext类的refresh()方法。refresh()方法有以下几个步骤:
1.第一个方法是prepareRefresh(),prepareRefresh()只包含了两个方法,这两个方法加载了服务器的一些信息和判断必填项是否都完整。第二个方法:ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();refreshBeanFactory()这个方法,如果容器现在已经启动,而我们要新启动一个容器,那么首要的就是销毁以前的容器,清空容器和容器中注册了的bean。新建容器对象DefaultListableBeanFactory beanFactory = createBeanFactory();新建一个DefaultListableBeanFactory.注意在这里面有一个方法getInternalParentBeanFactory(),这个方法获得并设置了父容器。当然在spring启动的过程中这个父容器是空的。
2.判断bean是否允许覆盖,bean是否允许循环引用,把java注解和spring标准注解都放到了容器里面来。
3.要加载bean loadBeanDefinitions(beanFactory)。实现了该方法的类一共有三个1、XmlWebApplicationContext 2、AbstractXmlApplicationContext 3、AnnotationConfigWebApplicationContext.在项目启动的过程中XmlWebApplicationContext真正的被执行。接下来的bean加载过程主要涉及到的就是配置文件解析。配置文件解析大体步骤1、根据命名空间将标签名称和解析类放置到map中2、读取配置文件的过程中遇到标签就将找到解析类去解析。
八、如何感知到Spring容器启动成功这件事情?
spring提供了事件监听器的处理机制,spring提供了内置的几类的事件:
ContextClosedEvent 当使用ConfigurableApplicationContext接口的close()方法关闭ApplicationContext容器时触发该事件。
ContextRefreshedEvent ApplicationContext容器初始化或者刷新时触发该事件。
ContextStartedEvent 当使用ConfigurableApplicationContext接口的start()方法启动ApplicationContext容器时触发该事件。
ContextStoppedEvent 当使用ConfigurableApplicationContext接口的stop()方法停止ApplicationContext容器时触发该事件。
RequestHandleEvent。
在spring容器启动完成后会触发ContextRefreshedEvent事件,在spring容器启动过程中调用AbstractApplicationContext的refresh()方法,其中调用了finishRefresh()用来发布这个事件。
ApplicationEventMulticaster在接收到ApplicationEvent事件之后,通过multicastEvent方法,通知所有的观察者ApplicationListener。
比如hsf中通过创建一个ContextRefreshedEvent,ContextClosedEvent事件监听器,在spring容器启动完成后和容器关闭时,做一些处理动作。
九、FactoryBean与BeanFactory
Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean,这两种Bean都被容器管理,但工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象。
BeanFactory是IoC容器的核心接口。它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。从本质上讲,BeanFactory仅仅只是一个维护bean定义以及相互依赖关系的高级工厂接口。通过BeanFactory我们可以访问bean定义。
十、如何在Bean初始化前后做一些事情
首先看下AbstractAutowireCapableBeanFactory的createBean方法:(删去些占地方的try catch)。
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException {
resolveBeanClass(mbd, beanName); /1解析Bean的class
mbd.prepareMethodOverrides(); //2 方法注入准备
Object bean = resolveBeforeInstantiation(beanName, mbd); //3 第一个BeanPostProcessor扩展点
if (bean != null) { //4 如果3处的扩展点返回的bean不为空,直接返回该bean,后续流程不需要执行
return bean;
}
Object beanInstance = doCreateBean(beanName, mbd, args); //5 执行spring的创建bean实例的流程啦
return beanInstance;
}
接着:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
// Make sure bean class is actually resolved at this point.
if (mbd.hasBeanClass() && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//3.1、执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation回调方法
bean = applyBeanPostProcessorsBeforeInstantiation(mbd.getBeanClass(), beanName);
if (bean != null) {
//3.2、执行InstantiationAwareBeanPostProcessor的postProcessAfterInitialization回调方法
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
接着:
protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName)
throws BeansException { for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
if (beanProcessor instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) beanProcessor;
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}
接着:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
通过代码我们可以看到BeanPostProcessor,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。
十、如何在bean销毁的时候做一些事情?
有两种方法:A.利用destroy-method配置 B.实现DisposableBean接口。实现DisposableBean接口,在destroy()方法里做一些操作。或者对配置文件加入destroy-method属性指定方法;如果两者同时出现,先执行DisposableBean接口的destroy()方法,然后再执行destroy-method属性指定方法。
十一、Bean的生命周期
什么时候初始化Bean?当scope=singleton,即默认情况,会在容器初始化时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean,这时候,只有第一次获取bean才会初始化bean,即第一次请求该bean时才初始化。如下配置所示:
<bean id=”xxx” class=”examples.test.OrderServiceBean” lazy-init=”true” />
如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init=”true”,如下所示:
<beans default-lazy-init=”true” …>
当scope=prototype时,也会延迟初始化bean,即第一次请求该bean时才初始化(如调用getBean()方法时)。
Bean的生命周期,构造器、init方法、获取bean后的操作、destroy方法(ctx.close时执行)。注意:如果bean的scope设为prototype时,当ctx.close时,destroy方法不会被调用。原因:对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会 被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用。)
在spring容器启动时,refresh方法中,调用invokeBeanFactoryPostProcessors(beanFactory);
在 invokeBeanFactoryPostProcessors 这个方法里,获取实现 BeanFactoryPostProcessor 接口的 bean ,将其排序后,依次调用invokeBeanFactoryPostProcessors。
最终调用了postProcessor.postProcessBeanFactory(beanFactory)方法,该方法会对bean做初始的处理,具体处理方式与子类具体实现有关。
。
Spring容器深入(li)的更多相关文章
- spring容器初始化执行某个方法
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查. 比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出 ...
- 当spring 容器初始化完成后执行某个方法
在做web项目开发中,尤其是企业级应用开发的时候,往往会在工程启动的时候做许多的前置检查. 比如检查是否使用了我们组禁止使用的Mysql的group_concat函数,如果使用了项目就不能启动,并指出 ...
- 7 -- Spring的基本用法 -- 5... Spring容器中的Bean;容器中Bean的作用域;配置依赖;
7.5 Spring容器中的Bean 7.5.1 Bean的基本定义和Bean别名 <beans.../>元素是Spring配置文件的根元素,该元素可以指定如下属性: default-la ...
- ServletContext与Web应用以及Spring容器启动
一.ServletContext对象获取Demo Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象. 可以把ServletContext看作一个 ...
- 一则spring容器启动死锁问题(DefaultListableBeanFactory/DefaultSingletonBeanRegistry)
线上发现一个问题,应用在启动时会卡死,log上并没有什么异常输出,初判应该是死锁问题. 抓现场的thread dump文件, 确实是有两个线程有deadlock问题. 线程一 "HSFBiz ...
- 通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理
一.先说一个结论:单元测试与主项目的spring容器是隔离的,也就是说,单元测试无法访问主项目spring容器,需要自己加载spring容器. 接下来是代码实例,WEB主项目出于运行状态,单元测试中可 ...
- 同时使用Junit4的@Parameterized参数化测试和Spring容器
转载:http://www.jianshu.com/p/d191fe54915f 整合Spring容器 @SpringApplicationConfiguration(classes = Applic ...
- Spring 容器
Spring提供了两个核心接口:BeanFactory和ApplicationContext,其中applicationContext是BeanFactory的子接口. 他们都可代表Spring容器, ...
- Spring-Context之四:Spring容器及bean的定义
Spring框架的核心功能之一就是控制反转(Inversion of Control, IoC),也叫做依赖注入(dependency injection, DI).关于依赖注入的具体内容可以参见Ma ...
随机推荐
- awk神器
序 产品经理(PM)过来找你要最近某某的数据,而你知道这些数据目前只能通过日志文件去分析,因为我们知道,我们不可能把所有数据都放入db中(这不科学啊!).每当有这样任务的时候,你就用php或j ...
- 如何创建一个RESTful WCF Service
原创地址:http://www.cnblogs.com/jfzhu/p/4044813.html 转载请注明出处 (一)web.config文件 要创建REST WCF Service,endpoin ...
- 海淘手表Invicta8926OB到手~晒图
3月3号通过国内代购网站Hai360海外购下单: 3月5号美亚发货: 3月6号到达转运仓: 3月12号到达天津清关: 清关等了7天: 3月19号转国内快递,我将原武汉地址,改上海,耽误了3天: 3月2 ...
- 修改注册表 去除Windows快捷方式图标小箭头
一些朋友不喜欢Windows系统中快捷方式图标上面的小箭头,下面介绍如何修改注册表去除快捷方式图标上的小箭头. 1.开始->运行->输入regedit,启动注册表编辑器,然后; 2.依次展 ...
- WinRAR 4.20 beta2 key!注册文件 注册码
WinRAR 4.20 beta2 key!注册文件 注册码 WinRAR 4.20 beta2注册文件 WinRAR 4.20 beta2 working key ================= ...
- [大数据之Spark]——快速入门
本篇文档是介绍如何快速使用spark,首先将会介绍下spark在shell中的交互api,然后展示下如何使用java,scala,python等语言编写应用.可以查看编程指南了解更多的内容. 为了良好 ...
- iOS----集成ijkplayer视频直播
ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijk ...
- Sql Server系列:排序函数
在SQL Server中有4个排序函数:ROW_NUMBER().RANK().DENSE_RANK()及NTILE()函数. 1. ROW_NUMBER()函数 ROW_NUMBER()函数为每条记 ...
- Sql Server系列:日期和时间函数
1. 获取系统当前日期函数GETDATE() GETDATE()函数用于返回当前数据库系统的日期和时间,返回值的类型为datetime. SELECT GETDATE() 2. 返回UTC日期的函数G ...
- 深入理解脚本化CSS系列第四篇——脚本化样式表
× 目录 [1]CSSStyleSheet [2]CSSRule 前面的话 关于脚本化CSS,查询样式时,查询的是计算样式:设置单个样式时,设置的是行间样式:设置多个样式时,设置的是CSS类名.脚本化 ...