Spring Ioc和 DI 之 bean 创建与销毁(二)
Bean 的命名
1、如果<bean>定义了id属性,那么属性的值则会作为bean名称
2、若没有指定id属性,则会查找name属性,如果定义了name属性,则将使用name
属性中定义的第一个名称(之所以为第一个名称,是因为可以再name属性中定义多个
名称)。
3、若既没有指定id属性,也没有指定name属性,则spring使用该bean的类名作为名
称,当然前提是没有其它bean使用相同的类名,如果声明了多个没有id或名称的相同
类型的bean,那么spring在ApplicationContext初始化期间,在注入时抛出异常。
注意:最好自己定义bean的名称,这样spring更改了默认行为,也会继续工作
示例:
beanName-beans.xml 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="string1" class="java.lang.String"></bean>
<bean id="string2" class="java.lang.String"></bean>
<bean class="java.lang.String"></bean>
<bean class="java.lang.String"></bean>
</beans>
测试:
package com.zombie.ioc.test;
import com.zombie.ioc.domain.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
import java.util.Map;
public class BeanNameTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beanName-beans.xml");
Map<String, String> beansOfType = ctx.getBeansOfType(String.class);
beansOfType.entrySet().stream().forEach(bean -> System.out.println("the beanName : "+bean.getKey()+" the bean is : "+bean.getValue()));
ctx.close();
}
}
输出:
the beanName : string1
the beanName : string2
the beanName : java.lang.String#0
the beanName : java.lang.String#1
最后这两个为spring提供给未在配置中明确指定 String类型ID,Spring将确保在整
个ApplicationContext中id为唯一的
Spring允许一个bean拥有多个bean名称,可以通过<bean>标记的name属性指定以
空格、逗号或分号分隔的名称列表实现多个名称 ,也可以使用<alias>标记名称定
义名称。
<bean id="stringId" name="stringName0 StringName1,stringName2;
stringName3" class="java.lang.String">
</bean>
byName 模式:当使用该模式进行自动装配时,Spring会尝试将每个属性连接到同
名的bean,因此,如果目标bean具有名为 foo 的属性且ApplicationContext中定义
了foo bean,则foo bean 将被分配给目标 bean 的 foo 属性。
byType 模式:当使用该模式自动装配时,Spring通过在ApplicationContext中自
动使用相同类型的bean来尝试连接目标bean模式的每个属性。
构造函数模式:该模式与byType模式在功能上相同,只不过使用的是构造函数而
不是setter来执行注入。Spring试图匹配构造函数中最大数量的参数,所以,如果
bean有两个构造函数,一个接受一个String,另一个接受一个String和一个Integer,
那么Spring将使用带有两个参数的构造函数。
默认模式:Spring将自动在构造函数模式和byType模式之间进行选择,如果bean
有一个默认的(无参数)构造函数,那么Spring使用byType模式,否则就使用构造函
数模式。
无:这是默认设置
Bean 的创建
在bean 的 <bean> 标记的init-method 属性中指定初始化的方法
package com.zombie.ioc.domain;
public class Grade {
private String subject ;
private float grade;
public void initProperties(){
this.setGrade(89.0f);
this.setSubject("math");
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public float getGrade() {
return grade;
}
public void setGrade(float grade) {
this.grade = grade;
}
}
xml配置,并指定init-method方法,该方法会在实例化后回调
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="false">
<bean id="grade" class="com.zombie.ioc.domain.Grade"
init-method="initProperties">
<property name="grade" value="35.0f"/>
<property name="subject" value="math"/>
</bean>
</beans>
default-lazy-init="false" 该注解用于告诉Spring,在第一次请求该bean时进行
实例化
测试:
public class InitBeanTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-beans.xml");
Grade grade = (Grade)ctx.getBean("grade");
System.out.println(grade.getSubject()+"==="+grade.getGrade());
}
}
输出:
math===89.0
只需要bean实现该接口的afterPropertiesSet
public class Grade implements InitializingBean {
private String subject ;
private float grade;
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public float getGrade() {
return grade;
}
public void setGrade(float grade) {
this.grade = grade;
}
@Override
public void afterPropertiesSet() throws Exception {
this.setGrade(93);
this.setSubject("chinese");
}
}
xml配置如上;
测试:
public class InitBeanTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-beans.xml");
Grade grade = (Grade)ctx.getBean("grade");
System.out.println(grade.getSubject()+"==="+grade.getGrade());
}
}
输出:
chinese===93.0
若同时实现该接口,也指定了init-method则最后调用afterProperties接口方法
使用JSR-250 @PostConstruct
注解
public class Grade {
private String subject ;
private float grade;
@PostConstruct
private void initPostProperties(){
System.out.println("调用指定的PostConstruct方法开始");
this.setGrade(100.0f);
this.setSubject("english");
}
..
}
xml配置需要增加注解扫描
<context:annotation-config/>
<bean id="grade" class="com.zombie.ioc.domain.Grade" >
</bean>
测试:
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-beans.xml");
Grade grade = (Grade)ctx.getBean("grade");
System.out.println(grade.getSubject()+"==="+grade.getGrade());
}
输出:
调用指定的PostConstruct方法开始
english===100.0
1、使用初始化方法,可以使应用程序与Spring分离,但必须要记住初始化方法
的bean配置相应的初始化方法
2、如果要使用InitializingBean接口,则可以一次性为bean类的所有指定初始化
回调,但该种方式会与Spring耦合
3、如果使用注解,需要将注解应用于方法,并确保IOC容器支持JSR-250
若不考虑移值,推荐使用InitializingBean接口,当使用init-method或
@PostConstruct配置初始化时,声明带有不同访问权限的初始化方法,该方法应
该在bean创建时由Spring IOC调用一次,而随后的调用将导致意外的结果或失败,
可以设置方法为私有化来禁止外部调用,spring ioc能够通过反射来调用它,但代
码中的其它任何调用都不被允许
调用指定的PostConstruct方法开始
调用指定的afterPropertiesSet方法开始
调用指定的init-method方法开始
bean的创建主要步骤为:
1、首先调用构造函数创建bean
2、注入依赖项(调用setter)
3、bean已经存在且提供了依赖项预初始化的BeanPostProcessor基础结构bean
将被查询,以查看是否想从创建的 bean 中调用东西,它们在创建后执行bean
修改操作。@PostConstruct 注解由CmmonAnnotationPostProcessor注册,
所以该bean将调用使用了@PostConstruct注解的方法,该方法在bean创建后,
在类被投入使用之前且在bean的实际初始化之前(即在afterPropertiesSet()
和init-method之前)执行。
4、InitializingBean的afterPropertiesSet()方法在注入依赖项后立即执行
5、最后执行init-method属性,这是因为它是bean的实际初始化方法
Bean
的销毁
只需要在bean的<bean> 标记的destroy-method 属性指定该方法的名称即可,
Spring 在销毁bean的单例实例之前会调用该方法,对于那些有原型作用域的
bean,Spring不会调用此方法。
实现 DisposableBean
接口
使用JSR-250 @PreDestroy
注解
bean销毁的解析顺序
若在同一个bean实例上使用所有的机制进行bean销毁,则Spring会先调用
@PreDestroy注解的方法,然后调用DisposableBean.destroy(),最后调用
Xml定义的destory()方法。
在Spring中销毁函数的缺点:不会自动触发,需要记住在应用程序关闭之前
调用AbstractApplicationContext.destroy()。java允许创建一个关闭钩子
(shutdown hook) ,它是在应用程序关闭之前执行的一个线程,这是调用
AbstractApplicationContext#registerShutdownHook(),该种方式也可以实
现同样的销毁bean
让 Spring 感知 bean
package com.zombie.ioc.domain;
import org.springframework.beans.factory.BeanNameAware;
public class SingerName implements BeanNameAware {
private String name;
@Override
public void setBeanName(String name) {
this.name = name;
}
public void sing() {
System.out.println("Singer "+ name +" -sing()");
}
}
可以通过该种方式,注入bean的名称,从而根据名称做一些处理
使用 ApplicationContextAware 接口
package com.zombie.ioc.domain;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
public class AppWare implements ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
if (ctx instanceof GenericApplicationContext) {
((GenericApplicationContext) ctx).registerShutdownHook();
}
}
}
通过该接口,bean可以获取对配置它们的ApplicationContext实例的引用,创建
该接口的主要原因:是为了bean在应用程序中访问Spring的ApplicationContex,
使用getBean()可以获取其它bean,但应避免使用该做法,并使用依赖注入为bean
提供协作者,否则会导致与Spring耦合;也可以注册销毁bean的钩子