为ioc容器中的bean正确赋值的各种方式
1、setter方式注入bean
导入的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!--这里为测试方便我就直接用了lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
bean配置
<bean id="user01" class="com.springleaning.entity.User">
<property name="uid" value="01"/>
<property name="uname" value="张三"/>
<property name="pwd" value="zs123"/>
<property name="age" value="20"/>
</bean>
test01
private ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
@Test
public void test01() {
//直接使用ioc容器实例的getBean方法获取id为useer01的bean
User user = (User) ioc.getBean("user01");
//下面这种,如果容器中有多个类型是User的bean,则会出错
User user2 = (User) ioc.getBean(User.class);
System.out.println(user+"\n"+user2);
}
结果
User(uid=1, uname=张三, pwd=zs123, age=20)
User(uid=1, uname=张三, pwd=zs123, age=20)
2、使用构造器
2.1 指定name属性
bean配置
<bean id="user02" class="com.springleaning.entity.User">
<constructor-arg name="uid" value="02"/>
<constructor-arg name="uname" value="李四"/>
<constructor-arg name="pwd" value="ls123"/>
<constructor-arg name="age" value="19"/>
</bean>
test02
@Test
public void test02() {
User user = (User) ioc.getBean("user02");
System.out.println(user);
}
结果
User(uid=2, uname=李四, pwd=ls123, age=19)
同样能够正确为bean赋值
2.2 不指定name,直接赋value值
bean配置
<bean id="user03" class="com.springleaning.entity.User">
<constructor-arg value="03"/>
<constructor-arg value="王五"/>
<constructor-arg value="ww123"/>
<constructor-arg value="18"/>
</bean>
test03
@Test
public void test03() {
User user = (User) ioc.getBean("user03");
System.out.println(user);
}
结果
User(uid=3, uname=王五, pwd=ww123, age=18)
这仍是没问题的,但是要注意一点,这种不指定name的方式需要按照构造器形参的顺序来进行赋值,否则会错
2.3 不指定name,但是指定index索引
bean配置
<!--这里我故意把顺序打乱,使用指定正确索引来帮助bean正确赋值-->
<bean id="user04" class="com.springleaning.entity.User">
<constructor-arg value="赵七" index="1"/>
<constructor-arg value="04" index="0"/>
<constructor-arg value="21" index="3"/>
<constructor-arg value="zq123" index="2"/>
</bean>
test04
@Test
public void test04() {
User user = (User) ioc.getBean("user04");
System.out.println(user);
}
结果
User(uid=4, uname=赵七, pwd=zq123, age=21)
可以看到这种方式也能正确赋值
3、通过P名称空间来为bean赋值
P名称空间是用来防止标签重复的,在beans标签引入头部文件
xmlns:p="http://www.springframework.org/schema/p"
在引入P名称空间头部文件后,新创建一个bean
可以看到在打出p的时候提示就已经出来了,这就意味着不用在bean标签里面写property标签了
bean配置
<bean id="user05" class="com.springleaning.entity.User" p:uid="05"
p:uname="老六" p:pwd="ll123" p:age="22"/>
结果
User(uid=5, uname=老六, pwd=ll123, age=22)
可以看到这也能正确的赋值
4、内外部bean
xml称可扩展的标记语言,但始终在xml文件里编写的东西还是文本内容,但是却能把参数自动转换对应的类型
但如果,我一个类里面还有其他类的对象属性呢,比如
4.1 给属性赋null值
首先我在User实体类中给了uname一个初始值
bean配置
<bean id="user01" class="com.springleaning.entity.User">
<property name="uname" value="null"/>
</bean>
test01
@Test
public void test01() {
User user = (User) ioc.getBean("user01");
System.out.println(user.getUname()==null);
}
结果
false
可以看到并不是真正为null,用value方式其实都是赋的字符串,但null不是字符串
那么下面这种才是正确的赋null值的方法
bean配置
<bean id="user02" class="com.springleaning.entity.User">
<property name="uname">
<null/>
</property>
</bean>
test01
@Test
public void test01() {
User user = (User) ioc.getBean("user02");
System.out.println(user.getUname()==null);
}
结果
true
赋null值的方法仅此一种,在需要赋null值的property标签内加标签就ok
4.2 引用外部bean
bean配置
<bean id="user02" class="com.springleaning.entity.User">
<property name="uname">
<null/>
</property>
<property name="car" ref="car01"/>
<property name="book" ref="book01"/>
</bean>
<bean id="car01" class="com.springleaning.entity.Car">
<property name="cname" value="宝马"/>
<property name="coler" value="黑色"/>
<property name="price" value="500000"/>
</bean>
<bean id="book01" class="com.springleaning.entity.Book">
<property name="bname" value="鲁宾逊漂流记"/>
<property name="btype" value="故事"/>
<property name="bprice" value="35"/>
</bean>
test03
@Test
public void test03() {
User user = (User) ioc.getBean("user02");
System.out.println(user.getCar() + "\n" + user.getBook());
}
结果
Car(cname=宝马, coler=黑色, price=500000.0) Book(bname=鲁宾逊漂流记, btype=故事, bprice=35.0)
引用外部bean成功
要注意一点的是,引用外部bean只能引用beans根标签下的bean,否则会报错提示找不到该bean
4.3 引用内部bean
通过这张图,你知道了什么?没错,内部bean类型必须父级property的name属性的类型一样
来做个测试
bean配置
<property name="car">
<bean class="com.springleaning.entity.Car">
<property name="cname" value="奥迪"/>
<property name="coler" value="黑色"/>
<property name="price" value="600000"/>
</bean>
</property>
test02
@Test
public void test02() {
User user = (User) ioc.getBean("user02");
System.out.println(user.getCar() + "\n" + user.getBook());
}
结果
Car(cname=奥迪, coler=黑色, price=600000.0)
没问题,这里还需要注意一点,内部bean是不需要写id或者别名的,因为只能在这个property里面使用,写了id没任何意义,而且每个bean也引用不了其他bean里的内部bean。
4.4 给List赋值
bean配置
<bean id="user03" class="com.springleaning.entity.User">
<property name="list">
<list>
<bean class="com.springleaning.entity.Book"
p:bname="三国演义" p:btype="故事书" p:bprice="50"/>
<bean class="com.springleaning.entity.Book"
p:bname="java" p:btype="编程" p:bprice="78"/>
<bean class="com.springleaning.entity.Book"
p:bname="c++" p:btype="编程" p:bprice="68"/>
<bean class="com.springleaning.entity.Book"
p:bname="mysql" p:btype="编程" p:bprice="60"/>
</list>
</property>
</bean>
test03
@Test
public void test03() {
User user = (User) ioc.getBean("user03");
System.out.println(user.getCar() + "\n" + user.getBook());
}
结果
[Book(bname=三国演义, btype=故事书, bprice=50.0), Book(bname=java, btype=编程, bprice=78.0), Book(bname=c++, btype=编程, bprice=68.0), Book(bname=mysql, btype=编程, bprice=60.0)]t
好没问题,同样list里的bean不需要写id或者别名,因为也是内部bean。
4.5 给Map赋值
bean配置
<bean id="user04" class="com.springleaning.entity.User">
<property name="map">
<map>
<entry key="key01" value="哈哈"/>
<entry key="key02" value="嘿嘿"/>
<entry key="car" value-ref="car01"/>
<entry key="book" value-ref="book01"/>
</map>
</property>
</bean>
test04
@Test
public void test04() {
User user = (User) ioc.getBean("user04");
System.out.println("全部:"+user.getMap()+"\ncar:"+user.getMap().get("car"));
}
结果
全部:{key01=哈哈, key02=嘿嘿, car=Car(cname=宝马, coler=黑色, price=500000.0), book=Book(bname=鲁宾逊漂流记, btype=故事, bprice=35.0)} car:Car(cname=宝马, coler=黑色, price=500000.0)
ok这也是没问题的
4.6 给Properties赋值
bean配置
<bean id="user05" class="com.springleaning.entity.User">
<property name="properties">
<props >
<prop key="p1">配置1</prop>
<prop key="p2">配置2</prop>
<prop key="p3">配置3</prop>
</props>
</property>
</bean>
test05
@Test
public void test05() {
User user = (User) ioc.getBean("user05");
System.out.println(user.getProperties());
}
结果
{p1=配置1, p2=配置2, p3=配置3}
这个也是没问题的
4.7 级联属性
什么叫级联属性呢,最简单的例子就是,一个List,List对象装进去的是Book类的对象,这个book对象就是List对象的属性,属性中的属性,就是级联属性,这个我不就不多说了,参考下拉框级联选择城市
4.8 配置继承
bean配置
<bean id="user06" parent="user05"/>
test06
@Test
public void test06() {
User user = (User) ioc.getBean("user06");
System.out.println(user.getProperties());
}
结果
{p1=配置1, p2=配置2, p3=配置3}
使用parent指定继承的bean的id,继承了并不表示两个bean就是同一个bean了,只是配置信息被继承了而已
4.9 抽象bean
抽象bean是只能当做模板被继承,而不用直接获取这个抽象bean的信息
bean配置
<bean id="user07" class="com.springleaning.entity.User" abstract="true">
<property name="map">
<map>
<entry key="key01" value="哈哈"/>
<entry key="key02" value="嘿嘿"/>
<entry key="car" value-ref="car01"/>
<entry key="book" value-ref="book01"/>
</map>
</property>
</bean>
test07
@Test
public void test07() {
User user = (User) ioc.getBean("user07");
System.out.println(user.getMap());
}
结果
直接抛出异常org.springframework.beans.factory.BeanIsAbstractException: Error creating bean with name 'user07': Bean definition is abstract
意思是这个bean是抽象的,不能创建
4.9 继承抽象bean
抽象bean是只能当做模板被继承,而不用直接获取这个抽象bean的信息
bean配置
<bean id="user08" parent="user07"/>
test08
@Test
public void test08() {
User user = (User) ioc.getBean("user08");
System.out.println(user.getMap());
}
结果
{key01=哈哈, key02=嘿嘿, car=Car(cname=宝马, coler=黑色, price=500000.0), book=Book(bname=鲁宾逊漂流记, btype=故事, bprice=35.0)}
继承抽象bean后继承了它的配置信息,没毛病
5、bean的依赖
首先做个测试,看看b一个ioc容器内bean的创建的先后顺序
抽象bean是只能当做模板被继承,而不用直接获取这个抽象bean的信息
bean配置
<bean id="user" class="com.springleaning.entity.User"/>
<bean id="car" class="com.springleaning.entity.Car"/>
<bean id="book" class="com.springleaning.entity.Book"/>
test01
private ApplicationContext ioc= new ClassPathXmlApplicationContext("ioc3.xml");
@Test
public void test01() {
System.out.println("ioc容器启动完毕。。。");
}
结果
User被创建了。。。 Car被创建了。。。 Book被创建了。。。 ioc容器启动完毕。。。
然后我把bean的位置换一下,在测试
bean配置
<bean id="user" class="com.springleaning.entity.User"/>
<bean id="car" class="com.springleaning.entity.Car"/>
<bean id="book" class="com.springleaning.entity.Book"/>
结果
Book被创建了。。。 Car被创建了。。。 User被创建了。。。 ioc容器启动完毕。。。
可以看到,一个ioc容器内bean的创建顺序是按照配置的先后顺序来创建的,而且所有的bean都是在ioc容器启动之前就已经创建好了。
那有没有什么方法在不改变配置顺序的情况下改变bean的创建顺序呢,答案是肯定是有的
那就是bean的依赖,这是什么个情况呢,那就来做个测试
bean配置
<bean id="book" class="com.springleaning.entity.Book" depends-on="car"/>
<bean id="car" class="com.springleaning.entity.Car"/>
<bean id="user" class="com.springleaning.entity.User"/>
结果
Car被创建了。。。 Book被创建了。。。 User被创建了。。。 ioc容器启动完毕。。。
可以看到已经不是按照配置顺序来创建bean了,Car是第一个创建
我给第一个bean加上了 depends-on=“car” 这就表示,id为book的bean依赖于id为car这个bean,
所谓依赖,就是只能等被依赖的bean创建好之后,这个依赖于它的bean才能创建,所以能看到如上的结果
6、bean的作用域,单实例,多实例
首先bean的作用域是什么?简单来说其实就是单实例和多实例,单实例上面的案例以及接触过了,就是同一个类型的实体类的实例只有一个,也就是说只会创建一次,而且是在ioc容器创建之前就已经创建好了。这个单实例就不多讲了。
那么什么是多实例呢?我们先来做个小测试
bean配置
<bean id="book" class="com.springleaning.entity.Book"/>
<bean id="car" class="com.springleaning.entity.Car"/>
<!--prototype:多实例-->
<!--singleton:单实例-->
<bean id="user" class="com.springleaning.entity.User" scope="prototype"/>
test01
@Test
public void test01() {
Book book= (Book) ioc.getBean("book");
Book book2= (Book) ioc.getBean("book");
System.out.println("ioc容器启动完毕。。。");
}
结果
Book被创建了。。。 Car被创建了。。。 ioc容器启动完毕。。。
可以看到,我创建了两个book对象,但是构造器却只调用了一次,这就是单实例。
第三个bean并没有创建,因为我加了scope=“prototype” 也就是这它改成了多实例的。
那多实例的并是什么时候创建呢?很简单再来做个测试
test01
@Test
public void test01() {
User user= (User) ioc.getBean("user");
User user2= (User) ioc.getBean("user");
System.out.println("ioc容器启动完毕。。。");
}
结果
Book被创建了。。。 Car被创建了。。。 User被创建了。。。 User被创建了。。。 ioc容器启动完毕。。。
可以看到,多实例bean每创建一个对象就调用一次构造器,但是上面那个例子就已经知道了,ioc启动完成了,并没有多实例bean被创建 ,因为多实例bean是使用bean的时候才去创建
7、工厂模式
什么是工厂模式?
其实就是一个类,这个类专门帮我们去创建对象,这个类就是工厂
而工厂又分为两种:
静态工厂:就是静态类,本身不需要被创建,可以直接通过类名点出方法,工厂类.工厂方法名();
实例工厂:就是本身需要创建的类,先new实例再通过实例去调用工厂方法。
那么静态工厂和实例工厂具体怎么实现呢?以下分别做个案例
7.1 静态工厂
AirPlane类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AirPlane {
private String jzName;
private String fjsName;
private String fdj;
private String yc;
private Integer zks;
}
StaticFactroy(静态工厂类)
public class StaticFactroy {
public static AirPlane getAirPlane() {
return new AirPlane();
}
}
bean配置
<bean id="airplane" class="com.springleaning.entity.StaticFactroy"
factory-method="getAirPlane">
<property name="fdj" value="太行"/>
<property name="fjsName" value="lmz"/>
<property name="jzName" value="xhh"/>
<property name="yc" value="50m"/>
<property name="zks" value="150"/>
</bean>
test01
@Test
public void test01() {
AirPlane airplane = (AirPlane) ioc.getBean("airplane");
System.out.println(airplane);
}
结果
Book被创建了。。。 Car被创建了。。。 AirPlane(jzName=xhh, fjsName=lmz, fdj=太行, yc=50m, zks=150)
7.2 实例工厂
InstanseFactroy(静态工厂类)
public class InstanseFactroy {
public AirPlane getAirPlane() {
return new AirPlane();
}
}
bean配置
<bean id="instanseFactroy" class="com.springleaning.entity.InstanseFactroy"/>
<bean id="airplane2" factory-bean="instanseFactroy" factory-method="getAirPlane">
<property name="fdj" value="太行"/>
<property name="fjsName" value="lmz"/>
<property name="jzName" value="xhh"/>
<property name="yc" value="50m"/>
<property name="zks" value="150"/>
</bean>
test02
@Test
public void test02() {
AirPlane airplane = (AirPlane) ioc.getBean("airplane2");
System.out.println(airplane);
}
结果
Book被创建了。。。 Car被创建了。。。 AirPlane(jzName=xhh, fjsName=lmz, fdj=太行, yc=50m, zks=150)
好了,两种工厂模式就这样了
类的区别
静态的工厂方法加了static,实例的工厂方法没有加,就这么简单
xml配置的区别
静态工厂:
配置bean需要class指定静态工的全类名以及指定一个factory-method工厂方法。
实例工厂:
先配置一个实例工厂的bean,这个bean指定实例工厂的全类名,实则就是为了创建出实例工厂的一个实例。
再配置一个用来获取信息的bean,这个bean不用指定class类型,但是需要指定factory-bean=这个实例工厂 bean的id,然后指定一个factory-method工厂方法即可
7.3 FactoryBean
FactoryBean是一个Spring规定的一个接口,只要实现了这个接口的类,Spring都识别为这是一个工厂
做个小测试
BookFactoryBean
public class BookFactoryBean implements FactoryBean<Book> {
/**
* @return 返回创建的对象
*/
@Override
public Book getObject() throws Exception {
System.out.println("Book由工厂创建完成...");
return new Book();
}
/**
* @return 返回对象的类型
*/
@Override
public Class<?> getObjectType() {
return Book.class;
}
/**
* isSingleton:是不是单例?
* @return true:是单例,false:不是单例
*/
@Override
public boolean isSingleton() {
return false;
}
}
bean配置
<bean id="bookFactoryBean" class="com.springleaning.entity.BookFactoryBean">
</bean>
test03
@Test
public void test03() {
Object bean=ioc.getBean("bookFactoryBean");
System.out.println(bean);
System.out.println("bean的类型:"+bean.getClass());
}
结果
Book由工厂创建完成... Book(bname=null, btype=null, bprice=null) bean的类型:class com.springleaning.entity.Book
那么这个FactoryBean工厂是在何时创建bean的实例呢?
/**
* isSingleton:是不是单例?
* @return true:是单例,false:不是单例
*/
@Override
public boolean isSingleton() {
return false;
}
这个方法也知道了设置是不是单例的方法,刚才我们设置的false就是代表要创建的bean不是单实例,所以就是多实例。前面知道了多实例是在使用bean的时候才创建。所以我们这次把测试类中的获取对象去掉看看。
test04
@Test
public void test04() {
System.out.println("ioc容器启动完成。。。'");
}
结果
ioc容器启动完成。。。
可以看到并没有创建,所以证明这个设置为false的确是创建多实例的bean,那么如果设置为true单实例呢,
是否跟之前一样会在ioc启动时候就创建好对象呢,来试试
/**
* isSingleton:是不是单例?
* @return true:是单例,false:不是单例
*/
@Override
public boolean isSingleton() {
//改为true
return true;
}
test05
@Test
public void test04() {
System.out.println("ioc容器启动完成。。。'");
}
结果
ioc容器启动完成。。。
没有创建!!是不是有点惊讶,和之前的xml配置bean的设置单多实例不一样。这个FactoryBean不管是创建单实例还是多实例都是在使用bean的时候才创建的。
8、bean的生命周期
单实例bean:
容器启动的时候创建,容器关闭的时候销毁bean
多实例bean,
使用时候创建bean
我们为为bean自定义一些生命周期方法,spring在创建和销毁的时候会调用这些方法,自定义初始化方法和销毁方法:且这些方法不能带参数,但是可以抛出异常。
7.3 FactoryBean
FactoryBean是一个Spring规定的一个接口,只要实现了这个接口的类,Spring都识别为这是一个工厂
做个小测试
Book
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("serial")
public class Book {
private String bname;
private String btype;
private Double bprice;
public void myInit(){
System.out.println("这是Book初始化方法");
}
public void myDestroy(){
System.out.println("这是Book销毁方法");
}
}
bean配置
<bean id="book" class="com.entity.Book" init-method="myInit" destroy-method="myDestroy">
test03
private ConfigurableApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test() {
ioc.close();
}
结果
这是Book初始化方法
这是Book销毁方法
不知道大家有没有注意到,我上面用的不是ApplicationContext去接收的,那是因为它没有销毁ioc容器的方法,
在ConfigurableApplicationContext类以上就开始有这个方法了。所以用ApplicationContext去接收ioc的实例是只能调用到启动方法的。
ioc容器的bean默认都是单实例的bean,随容器产生,随容器销毁
那么多实例bean在ioc容器销毁的时候会随之销毁吗?来试试
bean配置
<bean id="book" class="com.entity.Book" init-method="myInit" destroy-method="myDestroy" scope="prototype">
test03
private ConfigurableApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test() {
Book book = (Book) ioc.getBean("book");
ioc.close();
}
结果
这是Book初始化方法z
可以看到销毁的方法并没有被调用,所以多实例bean是在使用bean时候创建,并且ioc容器销毁的时候多实例bean不会销毁。
9、bean的后置处理器
BeanPostProcessor是一个接口,只有两个方法:
postProcessBeforeInitialization();这个方法会在初始化之前调用
postProcessAfterInitialization():这个方法会在初始化之后调用
为了验证方法执行顺序,我实现了这个接口,重写了这两个方法
bean配置
<bean id="book" class="com.entity.Book" init-method="myInit" destroy- method="myDestroy"/>
<bean id="myBeanPostprocessor" class="com.entity.MyBeanPostprocessor"/>
MyBeanPostprocessor
public class MyBeanPostprocessor implements BeanPostProcessor {
/**
* 初始化之前调用
* @param bean 将要初始化的bean
* @param beanName 将要初始化的bean的名称
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean即将初始化。。。");
return bean;
}
/**
* 初始化方法之后调用
* @param bean 初始化之后的bean
* @param beanName 初始化之后的bean名称
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean初始化已完成。。。");
return bean;
}
}
结果
bean即将初始化。。。 这是Book初始化方法 bean初始化已完成。。。 这是Book销毁方法
可以看到的确是这样的,无论自己有没有去写这个bean后置处理器,都会默认有个bean后置处理器在工作。