Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
目录
一、前言
- 本篇博文是对“”一文的内容补充(十、关于Bean配置的更多内容和细节)。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着过一遍才有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!
二、Bean配置信息重用
1.简介 :
所谓“Bean配置信息重用”,指的是在通过XML配置Bean时,若你想配置一个Bean与之前的某个Bean属性值相同,可以使用 parent="bean_id" 实现,表示当前bean对象的属性值从“id = bean_id”的bean对象中来。
2.实例 :
首先,我们在beans.xml中配置一个对象,用以充当parent,代码如下 :
然后,另新配置一个Bean,并使用 parent="stu10" 重用上面这个Bean的属性值,代码如下 :
接着,仍是在测试类StudentBeanByXML中,新定义一个单元测试方法,测试Bean配置信息重用是否成功。
beanConfigReuse()方法代码如下 :
运行结果 :
此外,我们还可以直接通过 abstract="true" 定义一个抽象Bean,抽象Bean本身不能被实例化,也就无法获取;但是可以通过parent属性配置一个同抽象类属性值相同的Bean对象,代码如下 :
在测试类中获取bean并打印bean的信息,代码如下 : (仍在beanConfigReuse()方法中)
运行结果 :
三、关于Bean的创建顺序
1.简介 :
Spring的IOC容器,默认是按照Bean配置时的顺序创建Bean对象。
而如果我们在前面配置的Bean对象中用到了 depends-on="bean_id" 即前面配置的Bean对象依赖了后面配置的Bean对象, IOC容器就会先去创建依赖的Bean对象,即使它是在后面配置的。
2.实例 :
首先,我们来建两个JavaBean类用于演示,在它们的无参构造中打印出提示信息,以便测试。
Commodity类代码如下 :
Member类代码如下 :
然后,我们先在beans.xml中按照Member --> Commodity的顺序配置两个Bean对象,即我们认为Member对象应该在Commodity对象之前被创建。代码如下 :
接着,在测试类StudentBeanByXML中新定义一个单元测试方法,beanCreateOrder()方法代码如下 :
运行结果 :
可以看到,Bean对象的确是依据配置时的顺序被创建的。
然后,我们利用 depends-on 额外配置两个Bean,要求先配置Commodity对象,且Commodity对象依赖于Member对象,代码如下 : (将刚才配置的两个Bean对象注释掉)
再次在beanCreateOrder()方法中获取Bean,代码如下 :
运行结果 :
可以看到,明明我们在beans.xml中先配置的是Commodity对象,但由于depends-on属性导致Commodity对象依赖于Member对象,因此最终是Member对象首先被创建。
PS:补充一点,对于ref引用——ref属性并不会影响Bean配置的顺序,因为IOC容器对Bean对象的创建是以一个整体来执行的,先完成Bean对象的创建,再进行ref引用的关联。。
四、关于Bean的单例和多例
1.简介 :
(1) 在Spring的IOC容器中,默认是按照单例形式来创建对象的,即配置一个Bean对象后,IOC容器只会创建一个该Bean的实例,后续使用的是同一个实例。若希望IOC容器按照多例形式来创建Bean对象,可以通过在配置Bean时加入 scope = "prototype"[/ˈproʊtətaɪp/,原型] 属性来指定。
(2) 配置的Bean默认scope="singleton"(即使你没有手动写出),若该Bean没有进行任何其他配置,默认会在容器启动时创建该Bean实例,并放入到单例池beanFactory-->singletonObjects中;若配置Bean时指定了scope = "prototype",则Bean实例被创建的时机会被推迟,该Bean只有在getBean(...)时才会被创建。(可通过无参构造打印提示信息进行验证)
(3) 实际上,Bean实例的创建时机,还取决于Bean的lazy-init (懒加载)属性;默认lazy-init="false",即默认Bean实例都是在容器启动时就创建(以空间换时间)。对于 scope = "prototype" 的情况,不管 lazy-init="true 还是 lazy-init="false",Bean实例都是只有在getBean(...)时才会被创建。而对于 scope="singleton" 的情况,若在单例形式的同时,希望Bean是在getBean(...)被调用时创建,则可以指定懒加载lazy-init="true"。
2.实例 :
以我们刚才创建的Member类为JavaBean,首先我们注释掉Member类中重写的toString()方法,其目的是可以打印出对象的哈希码值,如下图所示 :
然后,我们在beans.xml中配置一个Member对象(将之前配置的Member对象注释掉,防止无参构造打印出的提示信息造成干扰),代码如下 :
接着,我们在StudentBeanByXML测试类中另定义一个单元测试方法,在测试方法中多次获取 id=member03 的Bean对象,打印对象并比较它们的哈希码值(不同对象的哈希码值一般不同),beanScope()方法代码如下 :
运行结果 :
由对象的哈希码值我们可以得知,尽管我们获取了三次Bean对象,打印出的却始终是同一个对象,即是单例的。
接下来,我们在方才配置的Member对象上添加 scope = "prototype" 属性,如下图所示 :
再次运行beanScope()测试方法,结果如下 :
可以看到,三次获取到的Bean对象是三个不同的对象,且由Member类无参构造打印出的提示信息我们亦可得知,配置的Member类型的Bean实例被创建了三次。
注意,假如我们此时将getBean(...)的代码注释掉,如下图所示 :
此时,若再次运行beanScope()方法,会发现没有Bean被创建(没有无参构造打印出的提示信息),如下图所示 :
原因我们上面也说了——对于 scope = "prototype"(多例形式) 的情况,不管 lazy-init="true 还是 lazy-init="false",Bean实例都是只有在getBean(...)时才会被创建。现在我们把getBean(...)方法注释掉了,当然也就没有Bean实例被创建了。
那么,如果我们在getBean(...)方法被注释掉的基础上,又将多例改为单例呢?如下图所示 :
这时候我们再运行beanScope()方法,会发现有无参构造打印的提示信息了,如下 :
原因我们上文也提到了——配置的Bean,默认scope="singleton"(即使你没有手动写出),若该Bean没有进行任何其他配置,默认会在容器启动时创建该Bean实例,并放入到单例池beanFactory-->singletonObjects中。
当然,如果这时候我们希望在单例形式下实现懒加载,可以在 scope="singleton" 的基础上,配置懒加载lazy-init="true",这样即使此时scope="singleton",也不会有无参构造打印出提示信息了(因为getBean被注释了,不会有Bean实例被创建),如下图所示 :
这时候我们再运行beanScope()测试方法,会发现再一次“一无所获”,如下图所示 :
五、关于Bean的生命周期
1.简介 :
Spring中,Bean的生命周期是指Bean从创建到初始化再到销毁的过程,这个过程由IOC容器管理,主要包括以下几个过程——
①JVM完成创建Bean对象,执行构造器。
②执行相关setter方法(Bean的属性注入)。
③调用初始化Bean的方法。(需要在beans.xml中配置Bean时,通过 init-method="method_name" 属性来指定,初始化bean的方法在setter方法后执行,具体执行时机由IOC容器来控制)
④使用Bean对象。
⑤调用销毁Bean的方法(需要在beans.xml中配置Bean时,通过 destroy-method="method_name" 属性来指定,销毁bean的方法在IOC容器关闭的时候被执行。)。
2.实例 :
首先,我们来定义一个用于演示的JavaBean,在该JavaBean中自定义初始化Bean的方法和销毁Bean的方法。
Task类代码如下 :
然后,在beans.xml文件中配置一个Task类的Bean对象,代码如下 :
接着,仍然是在StudentBeanByXML测试类中定义一个单元测试方法,输出获取到的Bean对象,查看无参构造,setter方法,以及自定义的初始化Bean和销毁Bean的方法有没有打印出提示信息。beanLifeCycle()方法代码如下 :
运行结果 :
六、Bean配置后置处理器
1.简介 :
(1) 在Spring的IOC容器中,可以配置bean的后置处理器,后置处理器的本质其实是一个实现了BeanPostProcessor接口的Java类。
(2) 后置处理器通常会实现BeanPostProcessor接口中的两个方法,这两个方法允许该后置处理器在Bean的初始化方法(即配置Bean时init-method属性所指定的初始化方法)调用前和调用后进行相应的业务处理(即程序员可以在这两个方法中处理自己的代码)。其实,我们可以直接“见名知意”,这两个方法分别为postProcessBeforeInitialization(...) 和 postProcessAfterInitialization(...)。
(3) 当我们在XML文件中配置了后置处理器,后置处理器对象会作用于当前IOC容器中所有创建的Bean对象(体现了AOP[面向切面编程]);“对IOC容器中所有创建的Bean对象的统一处理”可以应用于日志处理、权限校验、安全验证,事务管理等业务场景。
2.实例 :
就以上文中定义的Task类为JavaBean类(我们之前在该类中业已定义了初始化Bean的方法和销毁Bean的方法),我们还需要定义一个后置处理器,CyanBeanPostProcessor类代码如下 :
为了更好的演示Bean的后置处理器,up在src目录下新建了一个beans_EX.xml配置文件。在beans_EX.xml配置文件中,我们需要配置一个Task类的bean对象,同时要配置后置处理器类型的bean对象,代码如下 :
最后,仍然是在StudentBeanByXML测试类中新定义一个单元测试方法,测试后置处理器实现的两个方法是否被调用。
beanPostProcessor()方法代码如下 :
运行结果 :
可以看到,仍然满足上文中Bean的生命周期的五个过程;并且在此基础上,初始化Bean的方法执行前调用了postProcessBeforeInitialization(...)方法,而初始化Bean的方法执行后调用了postProcessAfterInitialization(...)方法。
七、通过.properties文件为Bean注入属性
1.简介 :
在XML中通过命名空间<context:property-placeholder location="classpath:__.properties"/>指定一个.properties文件,在该属性文件中为Bean的属性赋值。其中,location属性表示文件所在的位置(注意location属性的格式)。
在配置Bean时,以"${key}"的形式引用属性文件中定义的属性值。
2.实例 :
首先我们需要在src目录下建立一个自定义的properties文件。up以cyan.properties为例,如下所示 :
以上文“关于Bean的创建顺序”中定义的Commodity类为JavaBean类,在cyan.properties属性文件中为Bean属性赋值:
继续回到我们的beans.xml中进行配置,将上文 “Bean的生命周期” 中配置的Task类Bean对象注释掉,防止打印出的信息造成干扰。在beans.xml中配置一个Commodity类型的Bean对象,并指定相应的属性文件,代码如下 :
在测试类中新定义一个单元测试方法,测试为Bean注入属性是否成功。
injectBeanByProperties()方法代码如下 :
运行结果 :
八、关于Bean的自动装配
1.简介 :
前面我们在讲 “通过ref引用实现Bean的相互引用” 时提到,在Spring中,可以通过ref引用实现“Web层调用Service层,Service层调用DAO层”的分层设计思想,如下图所示 :(回顾)
而“Bean的自动装配”,其实就是以另一种更快捷的方式来达到这个效果。
Bean的自动装配,需要在配置Bean时通过autowire属性来实现。
Δautowire属性又主要有两种常用的属性值——byType 和 byName。
①byType,指通过类型给Bean对象的属性完成赋值,即根据属性的类型在IOC容器中匹配有没有相同的类型的Bean对象(要求当前IOC容器中有且至多有一个对应类型的Bean对象),若匹配成功则自动装配。
②byName,指通过setter方法的名称给Bean对象的属性完成赋值,即根据setXxx方法的xxx (字面意思),去IOC容器中寻找"id=xxx"的Bean对象,若匹配成功则自动装配。
2.实例 :
先来演示autowire="byType"的情况。
基于"Web层调用Service层,Service层调用DAO层"的思想,我们先来定义三个演示类OrderServlet, OrderServiceImpl, OrderDAOImpl。
OrderDAOImpl类代码如下 :
OrderServiceImpl类代码如下 :
OrderServlet类代码如下 :
接着,在beans.xml中分别配置OrderDAOImpl,OrderServiceImpl,OrderServlet类的Bean对象,代码如下 :
最后,在测试类中定义单元测试方法,beanAutoAssemble()方法代码如下 :
运行结果 :
再来演示autowire="byName"的情况。
假设我们直接将原先配置的OrderServlet和OrderServiceImpl的autowire改为byName,如下图所示 :
由于setOrderService方法和setOrderDAO对应的xxx分别为"orderService"和"orderDAO",因此会匹配IOC容器中 id=orderService 和 id=orderDAO的Bean对象,而我们此时只有id=orderDAO01 和 id=orderService01的Bean对象,因此匹配失败,可以猜到测试方法会报错空指针异常(不能正常调用getOrderDAO()方法),我们尝试运行,结果如下 :
果然如此,此时,解决方法有两个——①修改配置的Bean对象的id为setter方法对应的xxx;②修改setter方法的名称,使之与已配置的Bean的id匹配。这里up以解决方法①为例,如下图所示 ;
这时,我们再次运行beanAutoAssemble()方法,结果如下 :
果然如此。
九、关于Spring-EL表达式
1.简介 :
(1) Spring Expression Language,简称SpEL,指Spring表达式语言,支持运行时查询并操作对象,类似于EL表达式,SpEL根据JavaBean的getXxx()、setXxx()方法定义的属性访问对象。
(2) SpEL使用#{...}作为定界符,所有在{}中的字符都将被认为是SpEL表达式。
2.实例 :
首先建立一个新的JavaBean,以Cat类为例,Cat类代码如下:
在beans.xml中通过SpEL配置Cat类Bean对象,代码如下 :
最后,仍然是在测试类中定义一个单元测试方法,测试id=cat02的Bean对象的属性是否注入成功。
testSpringEL()方法代码如下 :
运行结果 :
十、总结
🆗,以上就是“关于Bean配置的更多内容和细节”的全部内容了,虽然本篇博文是只是对
“”一文的内容补充,但由于“Spring——基于XML配置和管理Bean”本身内容较多,所以这篇补充性质的文章篇幅也不短,希望大家勤动手,自己写一写,感谢阅读!。
System.out.println("END-----------------------------);
- 点赞
- 收藏
- 关注作者
评论(0)