文章目录
- 一、 Spring Bean的配置
- 1. Bean的基础配置
- 2. Bean的别名配置
- 3. Bean的范围配置
- 4. Bean的延迟加载
- 5. Bean的初始化和销毁方法配置
- 6. Bean的实例化配置
- 7. Bean的依赖注入配置
- 8. Spring的其他配置标签
一、 Spring Bean的配置
Spring开发中主要是对Bean的配置,Bean的常用配置一览如下:
1. Bean的基础配置
例如:配置UserServiceImpl由Spring容器负责管理
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userService,值是UserServiceImpl对象,可
以根据beanName获取Bean实例
applicationContext.getBean("userService");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean("com.itheima.service.impl.UserServiceImpl");
2. Bean的别名配置
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl"/>
此时多个名称都可以获得UserDaoImpl实例对象
applicationContext.getBean("userService");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");
id与name同时定义时,id会转为beanName存到容器中,别名会存到aliasMap中
如果不配id,只配name,则容器中会存第一个别名作为beanName
<bean name="aaa,bbb" class="com.itheima.dao.impl.UserDaoImpl"/>
3. Bean的范围配置
默认情况下,单纯的Spring环境Bean的作用范围有两个:singleton和prototype
⚫ singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
⚫ prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。当scope设置为singleton时,获得两次对象打印结果是一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"/>
Object userDao = applicationContext.getBean("userDao");
Object userDao2 = applicationContext.getBean("userDao");
System.out.println(userDao); //com.itheima.dao.impl.UserDaoImpl@631330c
System.out.println(userDao2); //com.itheima.dao.impl.UserDaoImpl@631330c
通过断点调试,观察可以发现单例池中存在 userDao 实例
当scope设置为prototype时,获得两次对象打印结果是不一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"/>
Object userDao = applicationContext.getBean("userDao");
Object userDao2 = applicationContext.getBean("userDao");
System.out.println(userDao); //com.itheima.dao.impl.UserDaoImpl@4d50efb8
System.out.println(userDao2); //com.itheima.dao.impl.UserDaoImpl@7e2d773b
通过断点调试,观察可以发现单例池中不存在 userDao 实例,但是 userDao的信息已经被存储到beanDefinitionMap中了
当在SpringMVC环境下,Bean的作用范围有4个:singleton、prototype、request(创建的bean存到request域中)、session(创建的bean存到session域中)
4. Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的。
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
5. Bean的初始化和销毁方法配置
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称需要配置
<bean id="userService" class="com.itheima.dao.impl.UserServiceImpl" init-method="init" destroy-method="destroy"/>
public class UserServiceImpl implements UserService {
public UserServiceImpl() { System.out.println("UserServiceImpl实例化"); }
public void init(){ System.out.println("初始化方法..."); }
public void destroy(){ System.out.println("销毁方法..."); }
}
控制台只输出了实例化、初始化方法,没有执行销毁方法是因为程序执行完后,容器直接关闭了,没来得及执行销毁方法
如果想要执行销毁方法,需要手动显示关闭容器,使用ClassPathXmlApplicationContext的close方法关闭
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService");
System.out.println(userService);
applicationContext.close();
扩展:除此之外,我们还可以通过实现 InitializingBean 接口,完成一些Bean的初始化操作,如下:
public class UserServiceImpl implements UserService, InitializingBean {
public UserServiceImpl() {System.out.println("UserServiceImpl实例化");}
public void setUserDao(UserDao userDao){
System.out.println("属性设置完毕...");
this.userDao = userDao;
}
// 执行时机早于init-method配置的方法
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet执行");
}
public void init(){System.out.println("初始化方法...");}
public void destroy(){System.out.println("销毁方法...");}
}
6. Bean的实例化配置
Spring的实例化方式主要有如下两种:
⚫ 构造方式实例化:底层通过构造方法对Bean进行实例化
⚫ 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的< bean>几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean
// 有参构造方法
public UserDaoImpl(String name, int age){
// 有参构造方法创建bean
...
}
有参构造在实例化Bean时,需要参数的注入,通过< constructor-arg>标签,嵌入在< bean>标签内部提供构造参数,如下:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<constructor-arg name="name" value="haohao"/>
<constructor-arg name="age" value="18"/>
</bean>
工厂方式实例化Bean,又分为如下三种:
⚫ 静态工厂方法实例化Bean
⚫ 实例工厂方法实例化Bean
⚫ 实现FactoryBean规范延迟实例化Bean
工厂方法实例化Bean的好处是在创建bean时还可以执行其它逻辑,很灵活静态工厂方法实例化Bean,其实就是定义一个工厂类,提供一个静态方法用于生产Bean实例,再将该工厂类及其静态方法配置给Spring即可。
// 工厂类
public class UserDaoFactoryBean {
// 静态工厂方法
public static UserDao getUserDao(String name){
// 可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
PS:< constructor-arg>标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过< constructor-arg>标签完成,例如上面通过静态工厂方法实例化Bean所传递的参数也是要通过< constructor-arg>进行传递的
在Spring框架中,使用静态工厂方式创建Bean的优点如下:
1.这种方式还可以通过在静态工厂方法中添加一些逻辑来控制对象的创建,比如检查是否需要从缓存中获取对象,从而避免重复创建对象。
2.避免对象创建的复杂性:有时,创建对象可能是一项非常复杂的任务,可能需要读取配置文件、建立数据库连接或执行其他操作。使用静态工厂方法可以将这些复杂性封装在一个方法中,以便对客户端代码隐藏它们。此外,如果需要在创建Bean时进行某些操作(如初始化或清理),也可以在静态工厂方法中执行它们。
3.可以在不修改代码的情况下更改Bean的实现:使用静态工厂方法创建Bean可以使得Bean的实现和客户端代码解耦,从而可以在不修改客户端代码的情况下更改Bean的实现。这种方式可以通过将实现细节封装在静态工厂方法中来实现,而不需要将这些细节暴露给客户端代码。
4.易于使用:使用静态工厂方法创建Bean是一种简单的方式,因为它只涉及到一个方法。客户端代码只需要调用该方法,并传入任何必需的参数,就可以获取所需的Bean对象。实例工厂方法,也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,再用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,再配置目标Bean
// 工厂类
public class UserDaoFactoryBean2 {
// 非静态工厂方法
public UserDao getUserDao(String name){
// 可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.itheima.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDao" factory-bean="userDaoFactoryBean2" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
通过断点观察单例池singletonObjects,发现单例池中既有工厂Bean实例,也有目标Bean实例,且都是在Spring容器创建时,就完成了Bean的实例化
上面不管是静态工厂方式还是非静态工厂方式,都是自定义的工厂方法,Spring提供了FactoryBean的接口规范,FactoryBean接口定义如下:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = “factoryBeanObjectType”;
T getObject() throws Exception; // 获得实例对象方法
Class<?> getObjectType(); // 获得实例对象类型方法
default boolean isSingleton() {
return true;
}
}
定义工厂实现FactoryBean
public class MyFactoryBean3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
配置FactoryBean交由Spring管理即可
<!--MyFactoryBean3实现了FactoryBean<UserDao>接口,所以不需要再配置工厂方法,spring容器会找到对应的工厂方法getObject()去创建bean-->
<!--注意:此时单例池singletonObjects中存放的是工厂对象,beanName是userDao3,对象是MyFactoryBean3-->
<bean id="userDao3" class="com.itheima.factory.MyFactoryBean3"/>
通过Spring容器根据beanName可以正常获得UserDaoImpl
ApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
Object userDao3 = applicationContext.getBean("userDao3");
System.out.println(userDao3);
通过断点观察发现Spring容器创建时,FactoryBean被实例化了,并存储到了单例池singletonObjects中,但是getObject() 方法尚未被执行,UserDaoImpl也没被实例化,当首次用到UserDaoImpl时,才调用getObject() ,此工厂方式产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中,并且后期每次使用到userDao都从该缓存池中返回同一个userDao实例。
7. Bean的依赖注入配置
Bean的依赖注入的方式有两种:
其中,ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value 用于注入普通属性值。
依赖注入的数据类型有如下三种:
⚫ 普通数据类型,例如:String、int、boolean等,通过value属性指定。
⚫ 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
⚫ 集合数据类型,例如:List、Map、Properties等。注入 List< T> 集合 – 泛型是普通数据
public class UserServiceImpl implements UserService {
private List<String> stringList;
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="stringList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
</bean>
注入 List< T> 集合 – 泛型是引用数据
public class UserServiceImpl implements UserService {
private List<UserDao> userDaoList;
public void setUserDaoList(List<UserDao> userDaoList) {
this.userDaoList = userDaoList;
}
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="stringList">
<list>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
</list>
</property>
</bean>
也可以直接引用容器中存在的Bean
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userDao2" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--配置UserService-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="objList">
<list>
<ref bean="userDao"></ref>
<ref bean="userDao2"></ref>
<ref bean="userDao3"></ref>
</list>
</property>
</bean>
注入 Set< T> 集合
注入 Map<K,V> 集合
注入 Properties 键值对
扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在< bean> 标签中使用 autowire 属性去配置自动注入方式,属性值有两个:
⚫ byName:通过属性名自动装配,即去匹配 setXxx 与 id=“xxx”(name=“xxx”)是否一致;
⚫ byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
<!--UserServiceImpl中注入了UserDao,但是bean标签中没有通过<property name="userDao" ref="userDao"/>手动方式注入UserDao-->
<!--而是通过autowire="byName"方式自动装配的,UserServiceImpl中有setUserDao方法,所以会根据"userDao"去xml中匹配id="userDao"的bean进行依赖注入-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
}
有多个相同类型的bean时,通过byType自动装配会报错
8. Spring的其他配置标签
Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签
⚫ 默认标签:就是不用额外导入其他命名空间约束的标签,例如 < bean> 标签
⚫ 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 < context:property-placeholder/> 标签Spring的默认标签用到的是Spring的默认命名空间
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns:ns是namespace的缩写-->
<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">
</beans>
该命名空间约束下的默认标签如下:
< beans>标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
</beans>
<!-- 配置开发环境下,需要加载的Bean实例 -->
<beans profile="dev">
</beans>
可以使用以下两种方式指定被激活的环境:
⚫ 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
⚫ 使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,“test”)< import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务模块进行拆分,拆分后,最终通过< import>标签导入到一个主配置文件中,项目加载主配置文件就连同< import> 导入的文件一并加载了
<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
< alias> 标签是为某个Bean添加别名,与在< bean> 标签上使用name属性添加别名的方式一样,我们为UserServiceImpl指定四个别名:aaa、bbb、xxx、yyy
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userService" alias="yyy"/>
断点调试,在beanFactory中维护着一个名为aliasMap的Map<String,String>集合,存储别名和beanName之间的映射关系
Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring xml扩展点方式之一。
<!--默认标签-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--自定义标签-->
<context:property-placeholder/>
<mvc:annotation-driven/>
<dubbo:application name="application"/>