🌟个人主页:时间会证明一切.
目录
- BeanFactory和FactroyBean的关系?
- BeanFactory
- FactoryBean
- Spring 中的 Bean 是线程安全的吗?
- 有状态的Bean如何解决线程安全问题
- Spring 中的 Bean 作用域有哪些?
- 作用域与循环依赖
- 自定义作用域
BeanFactory和FactroyBean的关系?
FactoryBean和BeanFactory是Spring中的两个重要的概念。先看一下他们的类定义:
FactoryBean:
package org.springframework.beans.factory;public interface FactoryBean<T> {T getObject() throws Exception;Class<?> getObjectType();boolean isSingleton();
}
BeanFactory:
package org.springframework.beans.factory;public interface BeanFactory {Object getBean(String name) throws BeansException;<T> T getBean(String name, Class<T> requiredType) throws BeansException;Object getBean(String name, Object... args) throws BeansException;<T> T getBean(Class<T> requiredType) throws BeansException;<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;boolean containsBean(String name);boolean isSingleton(String name) throws NoSuchBeanDefinitionException;boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// ...
}
至少从代码上来看,这两个东西都是接口(interface),然后都是在org.springframework.beans.factory包下面的。
网上有很多概念的解释,说明他俩的区别,但是很多人还是看不懂,下面是的解释我结合了具体的case,帮助大家更好地理解他们的作用,理解了各自的作用,那么区别自然也就理解了。
BeanFactory
BeanFactory比较常用,名字也比较容易理解,就是Bean工厂,他是整个Spring IoC容器的一部分,负责管理Bean的创建和生命周期。
其中提供了一系列方法,可以让我们获取到具体的Bean实例。你可能没有直接用过BeanFactory,但是你肯定用过或者见过:
applicationContext.getBean(requiredType);applicationContext.getBean(name);
以上就是我们经常用的,在Spring的上下文中通过bean名称或者类型获取bean的方式,而这里的ApplicationContext,其实就是一种BeanFactory。这里面调用的getBean方法,就是上面我们在BeanFactory中看到的方法。
所以,BeanFactory是Spring IoC容器的一个接口,用来获取Bean以及管理Bean的依赖注入和生命周期。
FactoryBean
FactoryBean是一个接口,用于定义一个工厂Bean,它可以产生某种类型的对象。当在Spring配置文件中定义一个Bean时,如果这个Bean实现了FactoryBean接口,那么Spring容器不直接返回这个Bean实例,而是返回FactoryBean#getObject()方法所返回的对象。
是不是还是听不懂?
那我给你举个具体的例子你就知道了。
Dubbo用过吧(没用过?那可能理解起来比较吃力,因为FactoryBean确实是在很多框架中用到的比较多,比如Kafka、dubbo等各种框架中都会用他来和Spring做集成)。
当我们想要在Dubbo中定义一个远程的提供者提供的的Bean的时候,可以用@DubboReference或者dubbo:reference
而这两种定义方式的最终实现都是一个Dubbo中的ReferenceBean ,它负责创建并管理远程服务代理对象。而这个ReferenceBean就是一个FactoryBean的实现。
public class ReferenceBean<T> implements FactoryBean<T>,ApplicationContextAware, BeanClassLoaderAware, BeanNameAware, InitializingBean, DisposableBean {}
ReferenceBean的主要作用是创建并配置Dubbo服务的代理对象。这些代理对象允许客户端像调用本地方法一样调用远程服务。创建Dubbo服务代理通常涉及复杂的配置和初始化过程,包括网络通信设置、序列化配置等。通过ReferenceBean将这些复杂性封装起来,对于使用者来说,只需要通过简单的Spring配置即可使用服务。
ReferenceBean 实现了 FactoryBean 接口并实现了getObject方法。在getObject()方法中,ReferenceBean会给要调用的服务创建一个动态代理对象。这个代理对象负责与远程服务进行通信,封装了网络调用的细节,使得远程方法调用对于开发者来说是透明的。
通过 FactoryBean 实现,ReferenceBean 还可以延迟创建代理对象直到真正需要时,这样可以提升启动速度并减少资源消耗。此外,它还可以实现更复杂的加载策略和优化。
通过实现 FactoryBean,ReferenceBean 能够很好地与Spring框架集成。这意味着它可以利用Spring的依赖注入,生命周期管理等特性,并且能够被Spring容器所管理。
所以,FactoryBean通常用于创建很复杂的对象,比如需要通过某种特定的创建过程才能得到的对象。例如,创建与JNDI资源的连接或与代理对象的创建。就如我们的Dubbo中的ReferenceBean。
Spring 中的 Bean 是线程安全的吗?
Spring的Bean是否线程安全,这个要取决于他的作用域。Spring的Bean有多种作用域,其中用的比较多的就是Singleton和Prototype。
默认情况下,Spring Bean 是单例的(Singleton)。这意味着在整个 Spring 容器中只存在一个 Bean 实例。如果将 Bean 的作用域设置为原型的(Prototype) ,那么每次从容器中获取 Bean 时都会创建一个新的实例。
对于Prototype这种作用域的Bean,他的Bean 实例不会被多个线程共享,所以不存在线程安全的问题。
但是对于Singleton的Bean,就可能存在线程安全问题了,但是也不绝对,要看这个Bean中是否有共享变量。
如以下Bean:
@Service
public class CounterService {private int count = 0;public int increment() {return ++count;}
}
默认情况下,Spring Bean 是单例的,count字段是一个共享变量,那么如果多个线程同时调用 increment 方法,可能导致计数器的值不正确。那么这段代码就不是线程安全的。
我们通常把上面这种Bean叫做有状态的Bean,有状态的Bean就是非线程安全的,我们需要自己来考虑他的线程安全性问题。
那如果一个Singleton的Bean中是无状态的,即没有成员变量,或者成员变量只读不写,那么他就是个线程安全的。
@Service
public class CounterService {public int increment(int a) {return ++a;}
}
所以,总结一下就是:
Prototype的Bean是线程安全的,无状态的Singleton的Bean是线程安全的。有状态的Singleton的Bean是非线程安全的。
有状态的Bean如何解决线程安全问题
想要让一个有状态的Bean变得线程安全,有以下几个做法:
1、修改作用域为Prototype,这样的Bean就可以避免线程安全问题。
@Scope("prototype")
@Service
public class CounterService {private int count = 0;// ...
}
但是需要注意,Prototype的bean,每次从容器中请求一个 Prototype Bean 时,都会创建一个新的实例。这可能导致性能开销,特别是在需要频繁创建对象的情况下。 而且,每个 Prototype Bean 的实例都需要占用一定的内存,可能会导致内存资源的消耗较大。
2、加锁
想要实现线程安全,有一个有效的办法就是加锁,在并发修改共享变量的地方加锁:
@Service
public class CounterService {private int count = 0;public synchronized int increment() {return ++count;}
}
但是加锁的话会影响并发,降低系统的吞吐量,所以使用的时候需要谨慎,不建议用这个方案。
3、使用并发工具类
可以使用并发包中提供的工具类,如原子类,线程安全的集合等。
import java.util.concurrent.atomic.AtomicInteger;public class CounterService {private AtomicInteger count = new AtomicInteger(0);public int increment() {return count.incrementAndGet();}
}
建议使用这种,既能保证线程安全,又有比较好的性能。
Spring 中的 Bean 作用域有哪些?
所谓作用域,其实就是说这个东西哪个范围内可以被使用。如我们定义类的成员变量的时候使用的public、private等这些也是作用域的概念。
Spring的Bean的作用域,描述的就是这个Bean在哪个范围内可以被使用。不同的作用域决定了了 Bean 的创建、管理和销毁的方式。
常见的作用域有Singleton、Prototype、Request、Session、Application这五种。我们在代码中,可以在定义一个Bean的时候,通过@Scope 注解来指定他的作用域:
@Service
@Scope("prototype")
public class HollisTestService{}
这五种作用域的解释如下:
- 单例(Singleton):
- 默认作用域。
- 对于每个 Spring IoC 容器,只创建一个 Bean 实例。
- 适用于全局共享的状态。
- 原型(Prototype):
- 每次请求都会创建一个新的 Bean 实例。
- 适用于所有状态都是非共享的情况。
- 请求(Request):
- 仅在 Web 应用程序中有效。
- 每个 HTTP 请求都会创建一个新的 Bean 实例。
- 用于请求级别的数据存储和处理。
- 会话(Session):
- 仅在 Web 应用程序中有效。
- 每个 HTTP 会话都会创建一个新的 Bean 实例。
- 适用于会话级别的数据存储和处理。
- 应用(Application):
- 仅在 Web 应用程序中有效。
- 在 ServletContext 的生命周期内,只创建一个 Bean 实例。
- 适用于全应用程序级别的共享数据。
- Websocket:
- 仅在 Web 应用程序中有效。
- 在 Websocket 的生命周期内,只创建一个 Bean 实例。
- 适用于websocket级别的共享数据。
一般来说我们都是使用Singleton的作用域,有的时候也会用Prototype,其他几个用得不多。
以下两张图是Spring官方给的关于singleton和prototype的区别。其实就是会创建一个Bean还是多个Bean的区别:
作用域与循环依赖
Spring在解决循环依赖时,只解决了单例作用域的,别的作用域没有解决:
自定义作用域
除了Spring官方提供的这些作用域以外,我们还可以自定义我们自己的作用域,Spring提供了这方面的支持。
要自定义一个 Spring 的作用域,需要实现 org.springframework.beans.factory.config.Scope
接口。这个接口要求实现几个关键方法来管理 Bean 的生命周期。
public interface Scope {Object get(String name, ObjectFactory<?> objectFactory);@NullableObject remove(String name);void registerDestructionCallback(String name, Runnable callback);@NullableObject resolveContextualObject(String key);@NullableString getConversationId();
}
接下来,我们需要实现接口的方法,例如 get(创建或检索 Bean 实例)、remove(销毁 Bean 实例)等。
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;public class MyCustomScope implements Scope {@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {// 实现获取 Bean 的逻辑return objectFactory.getObject();}@Overridepublic Object remove(String name) {// 实现移除 Bean 的逻辑return null;}@Overridepublic void registerDestructionCallback(String name, Runnable callback) {// 注册 Bean 销毁时的回调}@Overridepublic Object resolveContextualObject(String key) {// 用于解析相关上下文数据return null;}@Overridepublic String getConversationId() {// 返回当前会话的 IDreturn null;}
}
接下来,我们需要 Spring 配置中注册这个自定义的作用域。这可以通过 ConfigurableBeanFactory.registerScope
方法实现。
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfig {@Beanpublic MyCustomScope myCustomScope(ConfigurableBeanFactory beanFactory) {MyCustomScope scope = new MyCustomScope();beanFactory.registerScope("myCustomScope", scope);return scope;}
}
在 Bean 定义中使用自定义的作用域的名称。Spring 容器将会根据你的自定义逻辑来创建和管理这些 Bean。
@Component
@Scope("myCustomScope")
public class MyScopedBean {// ...
}