FactoryBean使用及真实应用场景
一、FactoryBean是什么?
FactoryBean是spring提供的一个接口,可以通过它创建自定义对象工厂,将其注入spring容器后,会将其本身和其所要创建的bean一同注入容器。他和工厂模式中的工厂本质上来没有太大的区别,只不过一个是由spring 容器管理,一个是使用者自己管理。FactoryBean中有三个方法如下:
public interface FactoryBean<T> {
/**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
* @since 5.2
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
返回我们所要创建的bean
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* <p>As with a {@link BeanFactory}, this allows support for both the
* Singleton and Prototype design pattern.
* <p>If this FactoryBean is not fully initialized yet at the time of
* the call (for example because it is involved in a circular reference),
* throw a corresponding {@link FactoryBeanNotInitializedException}.
* <p>As of Spring 2.0, FactoryBeans are allowed to return {@code null}
* objects. The factory will consider this as normal value to be used; it
* will not throw a FactoryBeanNotInitializedException in this case anymore.
* FactoryBean implementations are encouraged to throw
* FactoryBeanNotInitializedException themselves now, as appropriate.
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
@Nullable
T getObject() throws Exception;
/**
返回我们要创建bean的class类型
* Return the type of object that this FactoryBean creates,
* or {@code null} if not known in advance.
* <p>This allows one to check for specific types of beans without
* instantiating objects, for example on autowiring.
* <p>In the case of implementations that are creating a singleton object,
* this method should try to avoid singleton creation as far as possible;
* it should rather estimate the type in advance.
* For prototypes, returning a meaningful type here is advisable too.
* <p>This method can be called <i>before</i> this FactoryBean has
* been fully initialized. It must not rely on state created during
* initialization; of course, it can still use such state if available.
* <p><b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
* {@code null} here. Therefore, it is highly recommended to implement
* this method properly, using the current state of the FactoryBean.
* @return the type of object that this FactoryBean creates,
* or {@code null} if not known at the time of the call
* @see ListableBeanFactory#getBeansOfType
*/
@Nullable
Class<?> getObjectType();
/**
是否为单例
* Is the object managed by this factory a singleton? That is,
* will {@link #getObject()} always return the same object
* (a reference that can be cached)?
* <p><b>NOTE:</b> If a FactoryBean indicates to hold a singleton object,
* the object returned from {@code getObject()} might get cached
* by the owning BeanFactory. Hence, do not return {@code true}
* unless the FactoryBean always exposes the same reference.
* <p>The singleton status of the FactoryBean itself will generally
* be provided by the owning BeanFactory; usually, it has to be
* defined as singleton there.
* <p><b>NOTE:</b> This method returning {@code false} does not
* necessarily indicate that returned objects are independent instances.
* An implementation of the extended {@link SmartFactoryBean} interface
* may explicitly indicate independent instances through its
* {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean}
* implementations which do not implement this extended interface are
* simply assumed to always return independent instances if the
* {@code isSingleton()} implementation returns {@code false}.
* <p>The default implementation returns {@code true}, since a
* {@code FactoryBean} typically manages a singleton instance.
* @return whether the exposed object is a singleton
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
default boolean isSingleton() {
return true;
}
}
二、使用
在使用FactoryBean的时候,大多数是于spring提供的其他接口一起使用
1.创建我们需要的bean及其builder
代码如下(示例):
@Data
public class Customize {
private String id;
private String name;
private int age;
private String address;
private String phone;
}
public class CustomizeBuilder {
private Customize customize;
public CustomizeBuilder builder() {
customize = new Customize();
return this;
}
public CustomizeBuilder id(String id) {
customize.setId(id);
return this;
}
public CustomizeBuilder name(String name) {
customize.setName(name);
return this;
}
public CustomizeBuilder age(int age) {
customize.setAge(age);
return this;
}
public CustomizeBuilder address(String address) {
customize.setAddress(address);
return this;
}
public CustomizeBuilder phone(String phone) {
customize.setPhone(phone);
return this;
}
public Customize build() {
return customize;
}
}
2.创建FactoryBean的实例
实现了InitializingBean 的 afterPropertiesSet()方法,该方法将在实现类的属性加载完毕后调用。 代码如下(示例):
@Component
public class CustomizeFactoryBean implements FactoryBean<Customize> , InitializingBean {
private Customize customize;
@Override
public Customize getObject() throws Exception {
return customize;
}
@Override
public Class<?> getObjectType() {
return Customize.class;
}
@Override
public void afterPropertiesSet() throws Exception {
if (customize == null){
customize= new CustomizeBuilder().builder()
.id("1")
.name("sun")
.age(18)
.address("beijing")
.phone("123456789")
.build();
}
}
}
之后我们在测试类中进行测试
@SpringBootTest
class SpringBeanApplicationTests {
@Autowired
private Customize customize;
@Autowired
private CustomizeFactoryBean customizeFactoryBean;
@Test
public void contextLoads() throws Exception {
System.out.println(customizeFactoryBean);
System.out.println(customizeFactoryBean.getObject());
System.out.println(customize);
}
}
输出: com.agp.springbean.factorybean.CustomizeFactoryBean@5a592c70 Customize(id=1, name=sun, age=18, address=beijing, phone=123456789) Customize(id=1, name=sun, age=18, address=beijing, phone=123456789)
对springboot进行调试得到注入FactoryBean工厂得到的默认对象就是其要创建的对象,可以通过&+factoryBeanName得到真实的bean工厂
整合mybatis
上述我们虽然通过@Component 将工厂注入到spring容器中,但是他真正的用法却不是这样。因为想要注册一个bean我们可以通过很多种方式去完成。这种方法明显是比较麻烦的,官方也不推荐使用spring相关注解注入。下面来看看mybatis-spring是如何将spring整合mybatis的。
mybatis是如何运行的?
mybatis的核心组件是 SqlSession,我们所有的sql操作都要使用这个接口完成。sqlSession又是由SqlSessionFactory构建的。通过build方法创建出了SqlSessionFactory,即得到SqlSession。具体解析过程这里不做介绍
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 传入配置文件,创建一个XMLConfigBuilder类
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 分两步:
// 1、解析配置文件,得到配置文件对应的Configuration对象
// 2、根据Configuration对象,获得一个DefaultSqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
}
}
}
由上述代码可知,只要我们拥有了SqlSessionFactory就拥有了mybatis的所有能力,剩下的就是怎么把SqlSessionFactory集成到spring中 在mybatis-spring中有这样的一个Factorybean
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent>{
private SqlSessionFactory sqlSessionFactory;
....
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* {@inheritDoc}
*/
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
}
通过了buildSqlSessionFactory()来创建了SqlSessionFactory(),具体细节不做介绍。相信到了这里在结合我们在使用spring结合mybatis时的配置,大家都应该能了解如何使用了,通过xml或者javaConfig的方式就SqlSessionFactoryBean加入到spring容器中将第三方的mybatis 集成到容器中,然后使用mybatis了
总结
FactoryBean主要是用来在spring初始化的过程中创建bean的工厂,个人理解它更像一个插件的接口。可以将第三方的类优雅的加载到spring容器中。可能还有其他功能有待开发
转载自:https://juejin.cn/post/7372914724512940059