不是线程安全的
Spring bean并没有可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。
Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决
当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性) ,则必须考虑线程同步问题。
如何处理 Spring 单例有状态的Bean 的线程安全问题?
默认情况下,只有public修饰的方法才能被Spring的事务管理器拦截,非public方法会被忽略。
@Transactional是基于动态代理实现的,当创建bean实例时,Spring框架会扫描带有@Transactional注解的方法,并通过AOP技术生成代理对象并对事务进行管理。动态代理只能代理public方法。
如果在@Transactional注解标记的方法中,出现了异常但是该异常被捕获并处理了,并没有抛出异常,那么这个事务就会正常提交,而不是回滚。
Spring 默认只会回滚非检查异常(不指定rollbackFor参数的前提下)
非检查异常:在编译时不需要强制处理的异常。这些异常一般是由程序逻辑错误或者系统内部错误导致的
检查异常:在编译时需要显式处理的异常。通常表示程序运行中可能出现的外部错误或不正常情况,通常表示程序运行中可能出现的外部错误或不正常情况
解决办法:
配置rollbackFor属性:@Transactional(rollbackFor=Exception.class)
在Spring Boot项目中,需要在配置类上添加 @EnableTransactionManagement
注解来启用事务注解支持。
@Transactional的实现原理在于以该注解为切面坐标,在扫描动态生成的代理类中以此坐标来在其前后分别设置事务开启和事务提交操作的增强,那么这些的前提是在于,你的操作环境是在于Spring动态代理生成的代理类中,而不是this对应的本类
当一个Bean需要被注入其它Bean时,Spring会创建一个代理对象来代替真正的Bean对象。这个代理对象会拦截目标Bean对象的方法调用,并在方法调用前后进行一些操作,比如实现事务管理、缓存处理等。这样,通过代理对象的调用,就可以实现一些横切关注点的统一处理。
解决方法
Spring 生命周期全过程大致分为五个阶段:
创建前准备阶段
创建实例阶段
依赖注入阶段
被实例化的 Bean 存在依赖其他 Bean 对象的情况,则需要对这些依赖 bean 进行对象注入
同时,在这个阶段会触发一些扩展的调用,比如常见的扩展类:BeanPostProcessors(用来实现 bean 初始化前后的扩展回调)、InitializingBean(这个类有一个 afterPropertiesSet(),这个在工作中也比较常见)、BeanFactoryAware 等等。
@PostConstruct注解用于标记一个方法,该方法在类实例化后被调用,在依赖注入完成之后执行。它的作用是在对象创建后执行一些初始化操作——>[解决静态方法中调用注入Bean对象]
容器缓存阶段
销毁实例阶段
destory-method
属性,会在这个阶段被调用。并非所有的Bean都会被声明为需要代理,只有被特定的AOP切面所切入的Bean才会生成AOP代理对象。其他非代理的Bean仍然会作为原始对象被实例化和注入。因此,虽然Spring加载的对象确实大部分是通过代理方式生成的AOP代理对象,但仍然存在一部分对象是原始对象。
假设我们有两个Bean,分别是UserService和OrderService。其中,UserService需要被AOP代理,以实现事务管理的功能,而OrderService不需要被代理。
在Spring的配置中,我们可以通过对UserService添加@Transactional注解来标明该Bean需要生成AOP代理对象。而对于OrderService,则不需要做任何特殊的标记,它会作为原始对象被实例化和注入。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void addUser(User user) {
userDao.addUser(user);
}
// other methods...
}
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
public void addOrder(Order order) {
orderDao.addOrder(order);
}
// other methods...
}
在上述代码中,UserService被标记为@Service注解,并且其中的addUser方法被标记为@Transactional注解,表示该方法需要被事务管理。因此,Spring会为UserService生成一个AOP代理对象,并在Bean初始化时将该代理对象注入到其他需要UserService依赖的Bean中。
而OrderService则没有任何特殊的注解标记,因此Spring会将其作为原始对象进行实例化和注入。
一级缓存: 缓存完全实例化且属性赋值完成的 Bean ,可以直接使用
二级缓存: 缓存早期的bean对象(生命周期还没走完),只实例加载了,没有依赖注入,懒加载机制
三级缓存: 缓存的是ObjectFactory,存储的是代理的Bean的工厂对象,而不是代理的Bean本身。
当我们调用getBean( )方法的时候,先去一级缓存找目标Bean,找不到就去二级缓存找,如果二级缓存也没有,意味着这Bean还没有被实例化,Spring就会去实例化该目标Bean,将其丢到二级缓存中,然后对该Bean标记是否具有循环依赖,如果没有循环依赖,则不动它,如果具有循环依赖,则等待下一次轮询进行再赋值,也就是解析@Autowired 注解。等@Autowired 注解赋值完成后,会将目标 Bean 存入到一级缓存。
可以说Spring的Bean是一种懒加载的机制。在Spring容器初始化时,并不会立即对所有的Bean进行属性的赋值,而是在需要使用Bean时才会进行属性的填充和赋值。
当调用getBean()方法获取Bean实例时,如果Bean已经在一级缓存中,则直接返回已经完成属性填充的Bean对象;如果Bean在二级缓存中,则检查是否存在循环依赖,如果不存在循环依赖,则创建一个代理对象返回,等待后续的属性填充;如果Bean还未实例化,则会进行实例化并放入二级缓存中,等待后续的属性填充和解析@Autowired注解的过程。
循环依赖的可能是
AOP
代理之后的对象,而不是原始对象!如果不存在代理对象,二级缓存就可以解决循环依赖性的问题,但是当存在代理对象的时候,二级缓存则无法完全解决循环依赖,需要引入三级缓存
当使用 CGLIB 代理时,代理对象无法被缓存。这就意味着在二级缓存中,我们无法获取到代理对象。(如果不存在代理对象的时候可以借助早期Bean来实现提前曝光,但是如果该循环的对象被AOP这些修饰了,它被注入到其他类中的实例一定得是一个代理类实例)
如果需要被代理的类出现了循环依赖的情况,Spring必须在创建Bean时就创建代理对象,才能保证循环依赖的解决。这就违背了Spring的设计原则。因为在循环依赖的情况下,如果Bean之间的依赖没有完成,Bean就无法完成创建。如果此时再去为它们创建代理,可能会出现问题。
因此,为了解决循环依赖的问题,使用第三级缓存是比较合适的做法,如果发现循环对象需要被代理的,就将该对象通过JDK代理 / Cglib代理调用FactoryBean的getObject( )创建一个代理工厂对象,并将其该类的 BeanDefinition 信息一起存储到三级缓存中,在循环依赖并需要使用代理对象的情况下,就可以从三级缓存中获取所有必要的信息来创建代理对象来进行一个类似于曝光的操作。让该需要被代理的类所依赖的类创建出来,丢到一级缓存中,这个半成品代理对象再从一级缓存中拿到其依赖的类变得完整,最后将这代理对象放到一级缓存中。删除二级缓存的半成品代理对象
虽然它一样是基于早期的不完整的 Bean 实例来创建的,但是Spring出手维护了下,所以这个代理对象是可以被拿来当作曝光用的
要知道,三级缓存中存储的都是代理对象的工厂对象,每次通过这个工厂对象的getObject方法获取到的代理对象都是不一样的噢,也就是不是单例的啦。
多例 Bean 通过 setter 注入的情况,不能解决循环依赖问题
构造器注入的 Bean 的情况,不能解决循环依赖问题
本质上是因为构造器注入需要将对象的实例化和依赖注入两个步骤绑定为一个原子步骤,这样子就不能通过曝光来实现破局
解决办法: 加上延迟加载注解@Lazy,其效果是什么时候需要该对象才进行Bean对象创建
Spring 为了解决这个问题,定义了 7 种事务传播行为。
主要从你和我双方有没有的角度去回答
理论上来说,常规的生命周期只有两种:
但是在基于 Spring 框架下的 Web 应用里面,增加了一个会话纬度来控制 Bean 的生命周期,主要有三个选择
在Spring 5.0前的默认方案是懒汉式的单例,在首次被调用时才进行实例化,并且只实例化一次,后续的调用都返回同一个实例。从 Spring 5.0 开始,Spring 的单例 bean 默认采用的是饿汉式的实现,即在容器启动时就实例化并初始化单例 bean,而不是延迟加载
/*
Spring5.0前的懒汉式实现,主要采用的是双重检查锁定的方案
*/
public class SingletonBean {
// 声明静态的 SingletonBean 实例
private static volatile SingletonBean instance;
// 私有化构造函数,防止外部实例化
private SingletonBean() {}
// 获取 SingletonBean 实例的静态方法
public static SingletonBean getInstance() {
// 双重检查锁定,确保只有实例不存在时才进行实例化
if (instance == null) {
synchronized (SingletonBean.class) {
if (instance == null) {
instance = new SingletonBean();
}
}
}
return instance;
}
// 随便一个方法来确认单例成功创建了
public void doSomething() {
System.out.println("SingletonBean.doSomething() method is called.");
}
}
/*
在 Spring 5.0 或更新版本中,可以通过 @Configuration 注解配合 @Bean 注解来实现饿汉式创建Bean
*/
@Configuration
public class SingletonBean {
// 声明静态的 SingletonBean 实例
private static SingletonBean instance = new SingletonBean();
// 私有化构造函数,防止外部实例化
private SingletonBean() {}
// 提供静态方法获取 SingletonBean 实例
public static SingletonBean getInstance() {
return instance;
}
// 随便一个方法来确认单例成功创建了
public void doSomething() {
System.out.println("SingletonBean.doSomething() method is called.");
}
@Bean
public SingletonBean createSingletonBean() {
return getInstance();
}
}
在Spring中最核心的就是Ioc容器,它保存了所有需要对外提供的Bean的实例。Spring对外暴露的ApplicationContext作为IoC容器最重要的接口,它也实现了 BeanFactory
接口。
BeanFactory
用于访问Spring bean容器的根接口。这是Spring bean容器的基本客户端视图。原来是获取Spring Bean的接口,也就是IoC容器。我们更常用的ApplicationContext就是一个BeanFactory。我们通过bean的名称或者类型都可以从BeanFactory来获取bean
FactoryBean
是一个特殊的Bean,这个Bean可以返回创建Bean的工厂。
当我们想要从BeanFactory中获取创建Bean的Factory时,可以在beanName前面加上 & 符号就可以获得Bean对应的Factory。
FactoryBean在IOC容器的基础上,给Bean的实现加上了一个简单工厂模式和装饰模式。Bean的代理对象就是FactoryBean实现的
功能用途
返回值
总结:
二者都是用来创建对象的
当使用BeanFactory的时候必须遵循完整的创建过程,这个过程是由Spring来管理控制的
而使用FactoryBean只需要调用getObject就可以返回具体的对象,整个对象的创建过程是由用户自己来控制的,更加灵活.
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
功能上的区别
加载方式的区别。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
创建方式的区别。
注册方式的区别。
IOC 的全称是 Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。
应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,不需要在一个类中去实例化另一个类对象,这样设计的好处是降低了程序里面对象与对象之间的耦合性。
Spring IOC 的工作流程大致可以分为两个阶段。
首先,一个IoC容器应创建一个工厂(DefaultListableBeanFactory),可以使我们读取的资源文件可以存放。
然后根据程序中定义的 XML 或者注解等 Bean 的声明方式,通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC容器 (注意:这里不是直接就是将该Bean的实例注册加载到容器中,而是Bean的BeanDefinition ) ,最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化
BeanDefinition 是 Spring 容器中用来描述和定义一个 Bean 的元数据对象。它包含了关于 Bean 的一些重要信息,如 Bean 的类名、作用域、依赖关系、初始化方法、销毁方法等等。BeanDefinition 本质上是一个配置对象,用于告诉 Spring 容器如何创建和管理这个 Bean。
总结:BeanDefinition 描述了一个 Bean 应该具有的属性和行为,而 Bean 实例则是这个描述的具体实现。
对于设置lazy-init 属性为false的Bean,在容器初始化后就会进行实例化存储到Bean容器中
然后进入到第二个阶段,这个阶段会做两个事情
通过反射针对没有设置 lazy-init 属性 (默认为true,懒加载) 的单例 bean 进行初始化。执行实例化之前的一些准备(初始化啊、事件处理器、注册组件等);
在 Spring 5.0后,默认情况下,单例作用域的 Bean 是在容器启动时就会被实例化和初始化的。但是如果你将单例 Bean 的
lazy-init
属性设置为true
,那么该 Bean 将会在第一次被使用时才会被实例化和初始化。
实例化Bean
检查和解析 Bean 之间的依赖关系,完成 Bean 的依赖注入。
@Autowired 是 Spring 提供的一个注解,默认是根据类型来实现Bean 的依赖注入
@Autowired 注解里面有一个 required 属性默认值是 true,表示强制要求 bean 实例的注入,在应用启动的时候,如果 IOC 容器里面不存在对应类型的 Bean,就会报错。当然,如果不希望自动注入,可以把这个属性设置成 false
如果在 Spring IOC 容器里面存在多个相同类型的 Bean 实例。由于@Autowired 注解是根据类型来注入 Bean 实例的,所以 Spring 启动的时候,会提示一个错误,大概意思原本只能注入一个单实例 Bean但是在 IOC 容器里面却发现有多个,导致注入失败。
解决方法如下:
@Primary
@Qualifier
@Resource 是 JDK 提供的注解,只是 Spring 在实现上提供了这个注解的功能支持。
@Resource 可以支持ByName 和 ByType 两种注入方式,具体采用哪个取决于属性 name
和 type
如果两个属性都没配置,就先根据定义的属性名字去匹配,如果没匹配成功,再根据类型匹配。两个都没匹配到,就报错
@Component
public class ExampleService {
private Dependency dependency;
public ExampleService(Dependency dependency) {
this.dependency = dependency;
}
// ...
}
在创建ExampleService
时,就会自动将一个Dependency
实例注入到ExampleService
中
优点:
缺点:
final
修饰的变量表示它的值一旦被初始化后就不能再被修改。而在变量注入的情况下,Spring 框架在运行时会动态地将依赖注入到变量中,这意味着变量的引用可能会在实例化后的任意时间点进行修改。@Component
public class ExampleService {
private Dependency dependency;
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
// ...
}
优点:
缺点:
这种方式要求目标Bean实现特定的接口,并通过接口方法来设置依赖项。
public interface Dependency {
void doSomething();
}
@Component
public class ExampleDependency implements Dependency {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
@Component
public class ExampleService {
private Dependency dependency;
@Autowired
public ExampleService(Dependency dependency) {
this.dependency = dependency;
}
public void performAction() {
// 使用依赖接口的方法
dependency.doSomething();
}
}
ExampleDependency
实现了Dependency
接口,并被标记为@Component
,成为一个由Spring管理的Bean。在ExampleService
中,通过构造函数注入Dependency
接口的实例。当调用performAction()
方法时,会使用注入进来的Dependency
实例执行相应的操作
在 Spring 中,创建 Bean 实例都是从 getBean()方法开始的,在实例创建之后,Spring 容器将根据 AOP 的配置去匹配目标类的类名,看目标类的类名是否满足切面规则。如果满足满足切面规则,就会调用 ProxyFactory
创建代理 Bean并缓存到 IoC 容器中。
根据目标对象的自动选择不同的代理策略 (JDK动态代理 / Cglib动态代理) ,如果目标类实现了接口,Spring会默认选择 JDK Proxy,如果目标类没有实现接口,Spring 会默认选择 Cglib Proxy,当然,我们也可以通过配置强制使用 Cglib Proxy
当用户调用目标对象的某个方法时,将会被一个叫做AopProxy 的对象拦截,然后按顺序执行符合所有 AOP 拦截规则的拦截器链。
Spring AOP 拦截器链中的每个元素被命名为 MethodInterceptor,其实就是切面配置中的 Advice 通知。这个回调通知可以简单地理解为是新生成的代理 Bean 中的方法。也就是我们常说的被织入的代码片段,这些被织入的代码片段会在这个阶段执行
MethodInterceptor 接口也有一个 invoke()方法,在 MethodInterceptor 的 invoke()方法中会触发对目标对象方法的调用,也就是反射调用目标对象的方法。 (动态代理的本质)
JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。
Cglib的工作原理主要分为两个步骤:
ASM库
来修改字节码,生成一个新的类作为代理类。代理类是被代理类的子类,并且会重写被代理类中的非final方法,将代理逻辑插入到这些方法中。这些重写方法会在调用时首先执行代理逻辑,然后再调用原始的被代理类方法。CGLIB缺点:
单例模式: 在Spring中定义的bean默认是单例模式。
工厂模式:Spring使用工厂模式通过BeanFactory、ApplicationContext创建Bean对象。
代理模式:Spring AOP功能的实现是通过代理模式中的动态代理实现的。
策略模式:Spring中资源访问接口Resource的设计是一种典型的策略模式。Resource接口是所有资源访问类所实现的接口,Resource 接口就代表资源访问策略,但具体采用哪种策略实现,Resource 接口并不理会。客户端程序只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样客户端程序可以在不同的资源访问策略之间自由切换。
适配器模式:Spring AOP的增强或通知使用到了适配器模式。
环绕通知是最强大的一种通知类型,它同时包含了前置通知和后置通知的功能,并且可以完全控制方法的执行。
在实现环绕通知时,需要使用到适配器模式。具体来说,需要创建一个继承自 Spring 的 MethodInterceptor 接口的类,该类中实现 invoke 方法,该方法中包含了环绕通知的实现代码。
为了将 MethodInterceptor 接口的类与目标对象绑定起来,在 Spring AOP 中使用了适配器模式,具体来说,适配器模式将 MethodInterceptor 接口的方法调用转换为了目标对象的方法调用,从而实现了通知的功能。
装饰器模式:Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源,项目需要连接多个数据库,这种模式让我们可以根据客户需求切换不同的数据源。
模板模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,就是用到了模板模式。