尽管spring中声明bean的形式多种多样,但spring容器创建bean主要有两种方式:构造器创建bean和工厂模式创建bean。
参考官方文档相关小节 - 实例化bean
参考练习examples/spring-03-create-bean
构造器创建bean
调用无参构造
在我们通过xml声明bean时,如果我们没有指定构造器参数,则默认会使用无参构造来创建bean。
<bean id="myBook" class="com.xiaoma.spring.example.reading.Book" />
如果我们为上面的bean声明提供这样的Book
类,容器启动会报错:
package com.xiaoma.spring.example.reading;
import ...
@Data
//@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class Book {
private String name;
private String type;
// public Book() {
// log.info("调用了Book的无参构造");
// }
}
因为无参的构造器我们没有显式的定义
Error creating bean with name 'myBook' defined in class path resource [application-context.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.xiaoma.spring.example.reading.Book]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.xiaoma.spring.example.reading.Book.<init>()
...
调用有参构造
我们可以在<bean>
标签中指定<constructor-arg>
来让spring根据我们指定的实参,找到并调用合适的构造器来完成bean的创建。比如:
<bean id="funnyBook" class="com.xiaoma.spring.example.reading.Book">
<constructor-arg name="name" value="金瓶梅" />
<constructor-arg name="type" value="古典文学" />
</bean>
<bean id="dabao" class="com.xiaoma.spring.example.reading.Child">
<constructor-arg name="name" value="大宝" />
<!-- 通过ref属性引用其他bean -->
<constructor-arg name="book" ref="funnyBook" />
</bean>
spring在使用构造器创建bean时,如果构造器参数涉及到bean的引用,会先尝试获取相应的bean作为构造器实参来完成bean创建的成员变量(bean依赖)初始化工作,关于从spring容器获取bean的细节这里我们暂时不作讨论。
创建静态内部类
Author
是Book
的静态内部类
public class Book {
...
static class Author {
public Author() {
log.info("调用了静态内部类Author的无参构造");
}
}
}
在外部手动new
一个静态内部类的实例,我们会使用.
的形式:
Book.Author author = new Book.Author();
而在xml中声明内部类时采用$
的形式:
<bean id="author" class="com.xiaoma.spring.example.reading.Book$Author" />
工厂模式创建bean
包括了用静态工厂、实例工厂(工厂bean)来创建bean。这种方式可以确保创建一个bean之前可以先做一些资源的初始化获取工作,在工厂中封装bean构建的细节,也可以对构建步骤执行日志输出以方便调试、排查问题。
静态工厂
package com.xiaoma.spring.example.reading.factory;
import ...
@Slf4j
public class BookStaticFactory {
public static Book createBook(String name, String type) {
log.info("调用静态工厂来创建对象,name: {}, type: {}", name, type);
return new Book(name, type);
}
}
<bean id="boringBook" class="com.xiaoma.spring.example.reading.factory.BookStaticFactory" factory-method="createBook">
<constructor-arg name="name" value="Spring入门" />
<constructor-arg name="type" value="IT" />
</bean>
实例工厂
package com.xiaoma.spring.example.reading.factory;
import ...
@Slf4j
public class BookFactory {
public Book create(String name, String type) {
log.info("调用实例工厂来创建对象,name: {}, type: {}", name, type);
return new Book(name, type);
}
}
<bean id="bookFactory" class="com.xiaoma.spring.example.reading.factory.BookFactory" />
<bean id="funnyBook" factory-bean="bookFactory" factory-method="create">
<constructor-arg name="name" value="金瓶梅" />
<constructor-arg name="type" value="古典文学" />
</bean>
实例工厂创建bean还有一种简化形式,继承AbstractFactoryBean
:
package com.xiaoma.spring.example.reading.factory;
import ...
@Slf4j
@Setter
public class BookFactoryBean extends AbstractFactoryBean<Book> {
private String name;
private String type;
@Override
public Class<?> getObjectType() {
return Book.class;
}
@Override
protected Book createInstance() throws Exception {
log.info("调用FactoryBean来创建对象,name: {}, type: {}", name, type);
return new Book(name, type);
}
}
<bean id="funnyBook" class="com.xiaoma.spring.example.reading.factory.BookFactoryBean">
<property name="name" value="葵花宝典" />
<property name="type" value="武功秘籍" />
</bean>
注意获取FactoryBean创建出来的bean和获取FactoryBean本身的区别:
Book funnyBook = context.getBean("funnyBook", Book.class); BookFactoryBean bookFactoryBean = context.getBean("&funnyBook", BookFactoryBean.class);
轻松一刻
有意思的是在从spring容器获取
FactoryBean
时,前面可以指定任意多的&
符号。