这个是学习刘Java的Spring学习的第三个笔记,确实一个文章一两万字,看着比较累,但是看完以后感觉有点意思。一点点来,把这个专栏看完,还有很多欠缺的地方,这个Spring是目前最基本要巩固的一个知识点。
前面知道了IoC容器,和DI依赖注入的DI的两种方式(构造器和setter方法)。
value字面量
对于基本类型,String,包装类型的属性,直接使用value属性的字符串来描述具体的值。在注入的时候,会把这些做一个转换,转换成属性和参数的具体属性。对应的Spring的有value标签。
(这个基本类型,字符串,以及个基本类型对应的包装类型比较特殊,和那种其他的对象类型。)
<bean id="simpleSetterBased" class="com.spring.core.SimpleSetterBased"><constructor-arg name="property1"><value>xxx</value></constructor-arg><constructor-arg name="property2"><value>yyy</value></constructor-arg><!--setter方法 name表示属性名 value 表示属性值--><property name="property3"><value>123</value></property><property name="property4"><value>false</value></property>
</bean>
properties的快捷转换
Spring容器支持通过propertyEditor直接解析value中的特定格式字符串的字面量,转换为一个Propertise集合。(就是读取配置文件,只不过这个和我们后面工程里面读取配置文件那种.propertise不太一样,他这个是读取xml文件配置文件。)
/*** @author lx*/
public class PropertiesDI {private Hashtable properties;/*** setter*/public void setProperties(Properties properties) {this.properties = properties;}@Overridepublic String toString() {return "PropertiesDI{" +"properties=" + properties +'}';}
}
xml文件
<!--properties-->
<bean class="com.spring.core.PropertiesDI" id="propertiesDI"><property name="properties"><!--直接写配置即可,自动转换为Properties--><value>! 注释# 注释# “#”“!”开头的一行被算作注释不会解析。# key和value可以使用 “=”、“:”、“ ”等符号分割,详见properties说明key=valuejdbc.driver.className=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mydbccc:dddaaa bbbeee fff</value></property>
</bean>
引用其他Bean
ref引用
在< constructor-arg >、< property >、< entry >标签中有一个ref属性,用于将bean的指定属性的值设置为对容器管理的另一个bean的引用。
被引用的bean是要设置其属性的bean的依赖项,在设置该属性之前,需要对其进行初始化。ref属性的值需要与引用的目标bean的id或者name属性中的一个值相同。
<!--ref-->
<!--定义一个Bean-->
<bean name="helloSpring3" class="com.spring.core.HelloSpring"/><bean class="com.spring.core.RefDI" id="refDI"><!--使用ref属性引用helloSpring3的bean--><constructor-arg name="helloSpring1" ref="helloSpring3"/><!--使用ref标签引用helloSpring3的bean--><constructor-arg name="helloSpring2"><ref bean="helloSpring3"/></constructor-arg>
</bean>
parent继承
< bean >,< ref >等标签中还有一个parent属性,这个属性用于指定bean将使用父bean的属性和配置,除了Autowire,scope和lazy init属性,parent属性用于相同属性以及值的复用。
对于parent继承,会有以下几种情况:
(1)如果父bean有class属性,而子bean没有class属性,那么子bean就是和父bean同一个class类型,相当于创建两个相同的对象。
(2)如果父bean有class属性,而子bean也有class属性,那么允许它们是不同的类型,但是子bean必须含有父bean中定义的所有的注入方式。
(3)如果父bean没有class属性,那么子bean必须定义class属性
(4)这里的父bean和子bean 以及“继承”,并不是Java中的继承关系,仅仅是复用了注入方式,精简了代码。(他这个parent不是什么继承关系,就是复用代码。)
(5)如果子bean和父bean中注入对相同依赖同时注入的值的话,那么可能会相互覆盖对方的值。这根据依赖注入的先后顺序:父bean的构造器注入->子bean的构造器注入->父bean的setter注入->子bean的setter注入,排序在后面的对相同依赖的注入值将会覆盖之前注入的值!
idfer引用校验值
< idfer >标签是<.construtor-arg >,< property >以及某些集合的子标签。
< idfer >标签用于将一个bean的id或者name的字符串值(并不是引用),传递给< construct-arg >,< property >这些集合。
这个标签用的比较少。
内部Bean
< bean >标签内部可以使用< construct-arg >,< property >以及某些集合标签,表示依赖注入。
在< construct-arg >,< property >这些集合也会可以使用<.bean >子标签,表示一个内部bean。(原因很简单,如果我们注入的是一个对象,并且我们不想要通过ref引用其他已存在的bean,那么只有定义自己的内部的bean。)
和“外部”bean区别,内部bean不需要定义id或者name属性,因为这个对象一个外部bean对象。
就算指定了,容器也不会使用这些值作为bean的名字,我们也不能通过IoC容器获取。
容器在创建时也会忽略内部bean的scope作用域属性(后面会讲),因为内部 bean 始终是匿名的,并且始终使用外 bean 创建。无法独立访问内部bean,也无法将它们注入其他外部bean中。
/*** 内部bean** @author lx*/
public class InnerBean {private InnerBeanInner innerBeanInner1;private InnerBeanInner innerBeanInner2;public void setInnerBeanInner1(InnerBeanInner innerBeanInner1) {this.innerBeanInner1 = innerBeanInner1;}public void setInnerBeanInner2(InnerBeanInner innerBeanInner2) {this.innerBeanInner2 = innerBeanInner2;}@Overridepublic String toString() {return "InnerBean{" +"innerBeanInner1=" + innerBeanInner1 +", innerBeanInner2=" + innerBeanInner2 +'}';}public static class InnerBeanInner {private String property1;private int property2;public void setProperty1(String property1) {this.property1 = property1;}public void setProperty2(int property2) {this.property2 = property2;}@Overridepublic String toString() {return "InnerBeanInner{" +"property1='" + property1 + '\'' +", property2=" + property2 +'}';}}
}
<!--内部bean-->
<bean id="innerBean" class="com.spring.core.InnerBean"><property name="innerBeanInner1"><!--内部bean 不需要指定id或者name--><bean class="com.spring.core.InnerBean.InnerBeanInner"><property name="property1" value="aaa"/><property name="property2" value="111"/></bean></property><property name="innerBeanInner2"><!--内部bean 指定id或者name也没用,不能通过容器获取到--><bean id="innerBeanInner" class="com.spring.core.InnerBean.InnerBeanInner"><property name="property1" value="bbb"/><property name="property2" value="222"/></bean></property>
</bean>
集中注入
注入方式
Spring提供了详细的集合注入方式,< list >,< set >,<.map >,< array >标签分别注入对应的Java的list,set,map,array的接口。
因为集合的元素既可以是基本类型也可以是对象甚至集合,因此集合注入非常灵活。另外集合注入支持泛型转。
/*** 集合注入** @author lx*/
public class CollectionDI {//集合属性注入private List list;private Set set;private Map map;private Properties properties;private Object[] array;public CollectionDI(List list, Set set, Map map, Properties properties, Object[] array) {this.list = list;this.set = set;this.map = map;this.properties = properties;this.array = array;}static class CollectionInner {private String property1;private int property2;public void setProperty1(String property1) {this.property1 = property1;}public void setProperty2(int property2) {this.property2 = property2;}@Overridepublic String toString() {return "CollectionInner{" +"property1='" + property1 + '\'' +", property2=" + property2 +'}';}}@Overridepublic String toString() {return "CollectionDI{" +"\n" + "list=" + list +"\n" + ", set=" + set +"\n" + ", map=" + map +"\n" + ", properties=" + properties +"\n" + ", array=" + Arrays.toString(array) +'}';}
}
<!--Collection注入-->
<bean id="collectionInner" class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="refs"/><property name="property2" value="111"/>
</bean><bean id="collectionDI" class="com.spring.core.CollectionDI"><!--list只有一个元素时,可以使用value属性赋值或者ref引用就行了--><constructor-arg name="list"><!--list标签表示list集合,用于定义多个元素--><list><!--value标签用于定义字面量的值作为集合元素--><value>111</value><!--也可以使用Bean标签定义一个bean(对象)作为集合元素--><bean class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="list"/><property name="property2" value="1"/></bean><!--也可以引用外部bean--><ref bean="collectionInner"/><value>null</value><!--当然集合元素也可以定义集合--></list></constructor-arg><!--set只有一个元素时,可以使用value属性赋值或者ref引用就行了--><constructor-arg name="set"><!--set标签表示set集合,用于定义多个元素--><set><!--value标签用于定义字面量的值作为集合元素--><value>111</value><!--也可以使用Bean标签定义一个bean(对象)作为集合元素--><bean class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="set"/><property name="property2" value="2"/></bean><!--也可以引用外部bean--><ref bean="collectionInner"/><value>null</value><!--也可以使用idref,仅作为字符串--><idref bean="collectionInner"/><!--当然集合元素也可以定义集合--></set></constructor-arg><constructor-arg name="map"><!--map标签表示map集合--><map><!--map标签中首先需要定义entry标签,表示一个键值对--><entry key="key" value="value"/><!--key和value都可以使用标签--><entry key-ref="collectionInner" value-ref="collectionInner"/><!--key和value都可以引用外部bean--><entry><!--key可以使用内部bean,或者集合等等--><key><bean class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="mapkey"/><property name="property2" value="3"/></bean></key><!--注意value标签只能是注入字面量值,如果想要对象类型的value,那么直接使用Bean标签就行了--><bean class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="mapvalue"/><property name="property2" value="3"/></bean><!--value也可以是集合等等类型,但是只能有一个大标签--><!--<map>--><!-- <entry key="innermap" value="innermap"/>--><!-- <entry key="inner2map" value="inner2map"/>--><!--</map>--><!--也可以使用idref,仅作为字符串--><!--<idref bean="collectionInner"/>--></entry></map></constructor-arg><constructor-arg name="properties"><!--props标签表示properties集合--><props><!--props标签中首先需要定义prop标签,表示一个String键值对--><prop key="111">111</prop><prop key="111">222</prop><prop key="222">222</prop><prop key="null">null</prop></props><!--实际上也可以放map,但是要求String类型的key和value--><!--<map>--><!-- <entry key="key" value="value"/>--><!--</map>--></constructor-arg><!--数组只有一个元素时,可以使用value属性赋值或者ref引用就行了--><constructor-arg name="array"><!--array标签表示array数组,用于定义多个元素--><array><!--value标签用于定义字面量的值作为集合元素--><value>111</value><!--也可以使用Bean标签定义一个bean(对象)作为集合元素--><bean class="com.spring.core.CollectionDI.CollectionInner"><property name="property1" value="array"/><property name="property2" value="4"/></bean><!--也可以引用外部bean--><ref bean="collectionInner"/><value>null</value><!--也可以使用idref,仅作为字符串--><idref bean="collectionInner"/><!--当然集合元素也可以定义为集合--></array></constructor-arg>
</bean>
集合的继承与合并
Spring容器还支持集合的继承和合并。我们可以定义父< list >、< map >、< set >、< props >、< array >集合bean,并且支持子< list >、< map >、< set >、< props >、< array >集合bean从父集合bean继承值,当然子集合bean也可以重写值。
null注入
Spring将默认字符串为空的视为空字符串,就算value的字面量设置为null,但实际也是变成一个“null”字符串而不是null,真的想设置null,可以使用< null >标签,这个标签能设置null值。
p-namespace
用的不多,就是允许你使< bean >元素的属性而不是嵌套的< property >子标签来描述依赖,实际上是简化setter方式注入的配置。
c-namespace
和p-namespace一样用的不多,Spring 3.1继续引入了c-namespace快捷注入,允许你使< bean >元素的属性而不是嵌套的< constructor-arg >子标签来描述依赖,实际上是简化构造器方式注入的配置。
name复合属性注入
用的不多
depends-on
这个可以和刘Java我认为有不同的地方,这个注解挺有用处的,尤其是使他对应的注解dependOn。
适用场景就是Bean之间有依赖关系,A依赖B先实例化。
dependOn属性可以显示强制在初始化,可以在使用了这个元素的Bean在这个Bean之前先初始化一个或者多个Bean。depends-on的值是bean的id或者name,用到多个bean时可以用逗号分号来作为分隔符(“,” “;”)按照这个顺序来初始化bean。
depends-on可以指定初始化bean的顺序,也可以指定依赖bean的销毁的顺序,最先初始化的bean是最后一个被销毁的。
无论是指定初始化顺序还是销毁顺序,都要求设置该属性的bean的作用范围是singleton,否则depends-on属性无效。(必须是要单例,depends-on属性才起作用)
<!--depends-on 如果不是scope=singleton(默认),那么depends-on属性无效-->
<bean class="com.spring.core.DependsOnDI.DependsOnDIA" depends-on="dependsOnDIC dependsOnDIB" id="dependsOnDIA" destroy-method="destroy" init-method="init"/>
<bean class="com.spring.core.DependsOnDI.DependsOnDIB" id="dependsOnDIB" destroy-method="destroy" init-method="init"/>
<bean class="com.spring.core.DependsOnDI.DependsOnDIC" id="dependsOnDIC" destroy-method="destroy" init-method="init"/>
lazy-init延迟初始化
ApplicationContext在默认情况下,初始化创建所有定义好的singleton的bean以及他们的依赖项。(这样设置,可在初始化就能发现创建bean的问题,而不是getBean方法才出问题。)
< bean >的lazy-init属性可以设置bean为延迟初始化,使用了这个属性的话,bean不会一开始被容器初始化,而是在使用时用再初始化。
这时候会有一个问题,如果一个bean A是延迟初始化的,另一个bean B是非延迟初始化的,B依赖A,那容器初始化B怎么办?实际上容器初始化B时会把这个延迟加载的Bean A也给初始化。
这个lazy-init属性默认是false,但有一个问题就是这个你要把这个scope属性设置“singleton”,才可以生效。如果你设置的scope是prototype,哪怕你设置的lazy-init是false也会延迟初始化,因为prototype是getBean就要生成一个实例。
(scope作用域,singleton是全局,容器启动生成唯一单例,容器会管理实例的销毁,容器销毁就会销毁。
prototype原型模式,对比singleton单例模式,他是每一次getBean将会创建一个实例,但是容器不会管理销毁这些实例。)
< beans >也可以使用这个lazy-init属性
XML依赖自动注入
这个“自动注入”和我们之前的DI“依赖注入”的那种自动,不一样不是通过依赖的方式来自动依赖注入。
“自动注入”,这个你可以理解为,你配置一种特殊的配置文件的“省略写法”,这个文件上的bean会被主动的去自动注入。容器根据这个文件自动去注入,这里和之前的配置文件相比,内容很精简,里面的关系是让容器自己去解析,不需要我们指定特别多属性,也就是不让我们手动写配置文件。
自动注入的优点如下:
(1)自动注入,可以显著减少手动指定属性或构造参数的需要
(2)自动注入,可以自动手动更新对象的配置(如果你想添加一个新的依赖项,无需修改文件就自动装配该依赖项。)
使用基于XML的配置元数据时,可以通过设置bean标签的autwire属性自动设置自动注入方式。
(1)no: 默认值,不启动自动装配,bean必须引用ref定义元素,配置多一些,这样就可以让系统结构和bean之间关系更清晰。
(2)byName:通过属性名字的方式查找bean依赖的对象并为其注入,使用这个注入方式,IoC容器会在配置文件中找id/name值包含对应的name的bean。setter方式为注入方式,这个自动setter和之前说的手动setter不一样(静态工厂方法,工厂方法,或者XML直接set方法),这里有限制就是,这个自动装配的bean必须包含这个名字(毕竟是byName)。
(3)byType:通过属性的类型来查找bean来依赖注入,设置byType依赖注入IoC容器来查找对应的class属性来注入这个bean,也是setter方法注入的方式。
(4)constructor:同byType一样,通过类型来查找依赖对象,但是和byType不一样的是,就是constructor是通过构造器来自动注入,而不是byType的setter方法注入。但是有多个同类型的bean,constructor会报错。
XML自动注入的方式
自动注入节省配置文件的编写方式
对于byName注入,没有该name的依赖就不会注入
对于byType注入,容器有多个同类型bean的,找不到合适的依赖,也会报错
对于constructor注入,容器有多个同类型的bean,找不到合适的依赖,会报错。
使用自动注入之后,不容易明确之间的依赖关系。
实际他们的依赖规则非常复杂,同样是多个同类型的bean,现在constructor会根据参数名进一步匹配,setter注入时不会根据参数名匹配的。
对于这些问题,解决的方式有:
(1)最常见的是放弃自动注入,多配置一些代码,保证不会出问题。以下是手动注入和自动注入做一个对比:
xml 手动注入
<bean id="userService" class="com.example.UserService"> <property name="orderService" ref="orderService"/> </bean>
xml自动注入autowire
xml <bean id="userService" class="com.example.UserService" autowire="byType"/>
(2)< bean >的autowire-candicate属性设置为false,该bean自动注入候选bean中排除。
(3)< bean >primary属性设置为true,该bean主动设置候选bean。
(4)使用注解方式注入
这个就是XML的autowire的自动注入,节省了属性,但是还是有问题会,中间的依赖关系不清晰,同样这个对应的autowire注解方式也要考虑这些。
选择自动注入候选bean
bean标签有一个default-autowire-candidate(默认的自动注入候选)属性,用于统一筛选当前容器的bean主动注入候选bean,default-autowire-candidate的值可以模式匹配。
将bean标签的属性autowire-candidate设置为false(默认为true)也会将这个bean排除候选bean。
将< bean >标签的primary属性设置为true(默认为fasle),将该bean指定为主要候选对象,一般指定一个就行了,可以指定多个。
方法注入
IoC容器中的bean之间依赖关系通过属性确定,这样可能由于bean的声明周期不同而造成某些问题,比如某个bean a是单例只会在容器启动的时候创建一次,bean a 依赖bean b,但是这时候会要求这个bean不能是单例,希望每访问一次bean需要注入一个新的bean的实例。
查找方法注入
是指IoC容器重新编写配置文件的指定方法,注入方法的返回结果将返回容器另一个命名的bean。
原理也简单,解析<.bean >标签,具有< lookup-method >标签的利用cglib的动态代理技术,生成该类的动态子类。该代理类将会代理< lookup-method >的name属性的指定方法,最终返回bean属性指定名称的bean对象,这个返回是向容器要对象。那么如果这个bean对象是prototype类型,必然每一次生成一个新的对象并返回,如果这个bean对象是singleton类型,必然每一次返回同一个对象。
<bean class="com.spring.core.LookupMethodIn.LookupMethodInA"
id="lookupMethodInA"><!--普通setter注入--><property name="lookupMethodInB" ref="lookupMethodInB"/><!--查找方法注入 name表示要被动态替换的方法名,bean表示容器中的一个bean的名字,没有过i嗯都会从容器返回该bean的实例--><lookup-method name="createLookupMethodInC" bean="lookupMethodInC"/>
</bean>
<!--需要被注入的bean-->
<bean class="com.spring.core.LookupMethodIn.LookupMethodInB" id="lookupMethodInB" scope="prototype"/>
<bean class="com.spring.core.LookupMethodIn.LookupMethodInC" id="lookupMethodInC" scope="prototype"/>
这个要注意一点是,用lookup-method因为有覆盖方法的要求实现依赖注入逻辑(或者注解@lookup)通过cglib来完成反射,生成代理类。
除此之外Spring是通过反射来完成(反射构造器来创建实例,但是不会像cglib生成代理类。)
任意方法替换
任意方法替换(Arbitrary Method Replacement):可以实现方法主体和返回结果的替换,相当于运行时使用一个方法替换另一个方法。
scope作用域
作用域分类
<.bean >标签scope属性用于定义该bean的作用域,这是一个非常强大的属性。
所谓作用域,实际应该是“用来声明容器中的对象应该处于限定的场景,或者说对象的特定存活时间,即对象在对象进入其相应的scope之前生成并装配这些对象,这些对象不再处于这些scope的限定之后,容器经常会销毁这些对象。”
Spring 5.x 支持6个scope作用域(这6个作用域,后面的4个是指web开发的ApplicationContext中可用。)
1 singleton
(1)默认值,单例。
单个bean的定义绑定到单个bean的实例,每个实例仅保存单个对象实例,随后的请求和对应单个名为bean的请求都返回缓存的对象。
(2)应用加载时,创建容器时,对象就被创建,只要容器在,对象一直活着。并且这个对象不会再被创建,直到销毁容器时,对象被销毁了。
2 prototype
(1)原型,单个bean的定义绑定到多个bean的实例(对比前面的singleton),具有prototype作用域的bean,被注入到一个要初始化的bean中。或者getBean获取这个bean的时候,都会获得一个新的实例。
通常来说,这个有状态的bean时用的是prototype作用域的bean,使用的singletion的作用域的bean是无状态的。
(有状态和无状态的bean,对应指的是对象的可变数据,无状态的对应的是bean是线程安全的,有状态线程不完全)
(2)Spring不负责prototype作用域的bean完整生命周期,容器实例化,配置,以及其他对象生成的对象交给客户端,就不再记录这个实例。
3 request
单个bean定义绑定到单个http请求生命周期,每一次http请求会产生一个新的bean,这个bean在当前http请求是有效的。
4 session
单个bean定义绑定到这个session的生命周期,每个独立的session产生一个新的bean,比上一个request的存活时间长一点。
5 application
单个bean定义绑定到ServletContext生命周期(一个web应用可以有多个IoC容器)
6 websocket
单个bean绑定到websocket周期
实际后面4个根web应用的作用域,实际在项目中都没有人怎么用,主要还是用singleton。
Singletion依赖prototype
当一个prototype的bean是一个singleton bean的依赖时,ApplicationContext在创建singleton是会立即创建一个bean,如果希望singleton bean在运行时重复获取prototype bean的新实例。则不能将prototype bean通过传统方式(构造器或者setter)注入到singleton bean中,因为当Spring容器实例化singleton bean并解析和注入其依赖项时,该注入只发生一次。如果在运行时需要多个原型bean的新实例,应该使用前面讲的方法注入。
bean的回调扩展
Spring框架提供了很多bean相关的回调接口,可以使用很多功能:
(1)bean的生命周期回调
生命周期接口是与容器对bean对生命周期的管理进行的交互接口,这样的接口两个:初始化之后和销毁之前。
对于自己创建的bean,即容器不能管理的bean的生命周期接口无效,比如prototype类型的bean,他创建之后他的生命周期不归容器来管理,所以只能调用初始化调回接口。(但是销毁调回接口就用不上了)
与bean的生命周期Spring管理,最原始的方式时Spring的InitializingBean和DisposiableBean接口。前者对应的是afterPropertiseSet方法,后者destory方法,来做到这个bean的初始化和销毁的一些操作。
JSR-250标准注解接口,对于Spring来说对应标准注解的@PostConstruct和@PreDestory,来作为bean的创建初始化回调和销毁回调,这个是最佳实践。
统一默认回调:
bean的回调总结:
new 容器的时候,refresh方法销毁已经存在的bean,生成新的bean
close方法,是销毁已存在bean。
Spring 2.5开始三种方式周期回调:
1 initalizingBean接口和DisposeableBean接口
2 自定义的init()和destory()方法,使用XML配置的init-method和destory- method属性,自定义init和destroy方法。
3 使用标准的注解,@PostConstruct 和 @PreDestroy注解来完成整个Bean周期的回调
如果同时配置了这么多生命周期回调的方法,那他的执行顺序是怎么样的?
(1)第一个是标准注解的@PostConstruct和@PreDestroy
(2)Spring自己的生命周期的接口
(3)XML定义的自定义的生命周期方法
容器状态回调
ApplicationContext容器有自己的状态,Spring提供一些接口,可以注册监听不同的状态变化,并调用不同的回调方法。
Lifecycle回调,一般用于一些后台组件的活动的开启和关闭,ApplicationCOntCOntext收到一个start或者stop信号,将这些信号传递给Lifecycle实现的接口。
非web容器应用的优雅关闭的容器
上面很多都是ApplicaionContext容器,也就是web应用的比较多,如果不是web应用怎么办?怎么去做生命周期管理?
web应用容器的关闭会随着JVM的结束而结束,不是web容器怎么优雅关闭。可以使用钩子方法,在JVM里面注册一个shutdownHook到JVM中,JVM关闭也就会安全的把容器关闭掉,让资源释放。
@Test
public void shutdownHook() {System.out.println("new容器的代码内部实际上会调用一次refresh操作,因此会自动启动");ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("DI.xml");//添加回调方法,在JVM关闭时会调用容器close方法的逻辑,如果不加那么容器并不会正常关闭//ac.registerShutdownHook();System.out.println("start调用");ac.start();
}
public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.refresh();context.registerShutdownHook(); // 手动注册
}
(别说我项目中还真见过,就是redission的,如果连接不上就会调用shutdownHook方法来关闭容器)
总结
这个XML是比较老的工程,所以用的少,但是有很多东西可以通用,为后面的部分打基础。