协调作用域不同步的Bean

本文详细解析了Spring框架中Bean的六种作用域,重点对比了singleton和prototype的区别。并通过实例演示了singleton作用域的Bean依赖prototype作用域的Bean时可能遇到的问题及解决方案,包括使用lookup方法注入。
先了解一下Bean的作用域

spring提供了6种作用域:singleton、prototype、request、session、applicaton、websocket
我们常用的singleton和prototype
singleton是默认值,singleton作用域的Bean只生成一个实例
prototype:通过容器的getBean()方法获取实例时,将会产生不同的实例
例如:
beans.xml

<?xml version="1.0" encoding="UTF-8"?>
   <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="Son" class="configrelax.Son" scope="prototype"/>
    <bean id="man" class="configrelax.man"/>
     </beans>

configrelax.Son.java

package configrelax;
public class Son {
public int age;
public int getAge() {
 return this.age;
}
public void setAge(int age) {
 this.age = age;
}
}

configrelax.man.java

package configrelax;
public class man {
public Son son;
public Son getSon() {
 return son;
}
public void setSon(Son son) {
 this.son = son;
}
}

maintest.java

package configrelax;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class maintest {
 public static void main(String[] args)
 { 
 ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
  System.out.println(cn.getBean("Son",Son.class));
  System.out.println(cn.getBean("Son",Son.class));
  System.out.println(cn.getBean("man",man.class));
  System.out.println(cn.getBean("man",man.class));
 }
}

运行结果
configrelax.Son@56235b8e
configrelax.Son@3632be31
configrelax.man@5abca1e0
configrelax.man@5abca1e0
显然singleton作用域的实例只有一个,prototype则会创建多个实例

那问题就来了

首先我们知道bean实例化的时机是:
Spring什么时候实例化bean,首先要分2种情况
第一:如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该 Bean的时候实例化
第二:如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
(1):如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使用该Bean的时候,直接从这个缓存中取
(2):如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
(3):如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
对于singleton作用域的Bean依赖prototype作用域的Bean时
对于singleton 作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建spring容器时实例化所有的singletonBean,于此同时,所依赖的prototype Bean也一起被实例化,并将prototype Bean注入singleton Bean中。这就导致无论何时通过singleton Bean去访问prototype Bean,得到的永远是最初的prototype Bean,这就相当于prototype也变成了singleton,显然不合适

解决方法

1、放弃依赖注入。每次需要prototype Bean实例时,主动向容器请求新的实例(可用不可取)
2、利用方法注入
先看下lookup方法注入的执行过程再分析注意事项
为了使用lookup方法注入,大致分为两步
1、将调用者Bean的实现类定义为抽象类,并定义一个抽象方法获取被依赖的Bean,spring容器会自行重写容器中的Bean的抽象类的抽象方法
2、<bean…/>中添加<lookup-method…/>说明去实现哪个抽象方法,有两个属性
1)name:spring去实现的方法
2)bean:实现该方法的返回值
实现一个人每次带着不同的狗去打猎
configrelax.Person.java

package configrelax;
public interface Person
{
 public void hunt();
 }

congigrelax.Chinese.java

package configrelax;
public abstract class Chinese implements Person {
 public abstract Dog getDog();
 public void hunt()
 {
  System.out.println("猎人带着"+getDog()+"去打猎");
  System.out.println(getDog().run());
 }
}

configrelax.Dog.java

package configrelax;
public interface Dog {
public String run();
}

configrelax.gunDog.java

package configrelax;
 public class gunDog implements Dog
 {
  private String name;
  public void setName(String name)
  {
   this.name = name;
  }
  public String getName()
  {
   return name;
  }
  public String run()
  {
   return "狗去追";
  }
 }

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
   <beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="gunDog" class="configrelax.gunDog" scope="prototype"/>
    <bean id="Chinese" class="configrelax.Chinese">
     <lookup-method name="getDog" bean="gunDog"/>
     </bean>
     </beans>

maintest.java

package configrelax;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class maintest {
 public static void main(String[] args)
 { 
 ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
 Person per1=cn.getBean("Chinese",Person.class);
 Person per2=cn.getBean("Chinese",Person.class);
 per1.hunt();
 per2.hunt();
 }

猎人带着configrelax.gunDog@6950e31去打猎
狗去追
猎人带着configrelax.gunDog@b7dd107去打猎
狗去追

spring容器自行负责重写getDog()

public Dog getDog()
{
 ApplicationContext cn=new ClassPathXmlApplicationContext("beans.xml");
 return cn.getBean("gunDog");
}

spring会采用运行时动态增强的方式实现<lookuo-method…/>指定的抽象方法
如果目标抽象类如上Chinese类实现过接口,spring会使用JDK动态代理实现该抽象类,并实现抽象方法
如果目标抽象类如上Chinese类没有实现过接口,spring会使用cglib实现该抽象类,并实现抽象方法,spring5.0已经有此类库

### Spring AOP 中 Bean 创建失败的原因分析 在 Spring 应用程序中,`BeanCreationException` 是一种常见的异常,尤其是在涉及复杂依赖关系或代理机制的情况下。以下是关于 `Spring AOP` 初始化时可能出现的 `BeanCreationException` 的原因及其解决方案。 #### 1. 循环依赖问题 当两个或多个 Bean 形成相互依赖的关系时,可能会引发循环依赖问题。虽然 Spring 容器可以解决部分类型的循环依赖(通常是单例 Bean),但在某些情况下仍然会抛出 `BeanCreationException`[^2]。 例如,在 AOP 场景下,如果目标类和切面之间存在复杂的依赖链,则可能导致无法正常完成实例化过程。 #### 解决方案: - **调整设计模式**:重新评估并优化应用程序的设计结构,减少不必要的双向依赖。 - **延迟注入**:通过使用 `@Lazy` 注解实现懒加载来推迟 Bean 实例化的时机,从而打破循环引用链条。 - **修改作用域**:将其中一个 Bean 设置为原型或其他非单例范围,避免因共享状态而导致冲突。 #### 2. FactoryBean 和 FactoryBean 接口混淆 在配置文件或者 Java 配置类中定义了一个自定义的工厂 Bean (`factoryBean`) ,但是它可能未正确遵循 Spring 提供的标准接口 `org.springframework.beans.factory.FactoryBean<T>` 。这种不一致会导致容器解析阶段出现问题,并最终触发错误提示 [^1]. #### 处理办法: 确保任何实现了 `FactoryBean` 接口的对象都按照官方文档说明的方式编写代码;特别是要注意返回值类型以及 getObject(), getObjectType() 方法的行为一致性。 #### 3. 单例 Bean 加载顺序不当 由于 Spring IOC 容器采用了一种特定策略——提前暴露尚未完全初始化完毕的对象给其他组件作为占位符 (early reference),所以在 doGetBean 过程当中遇到 null 值时就会尝试调用带有 ObjectFactory 参数版本的 getSingleton 函数继续构建流程 [^4]. 如果在此期间发生意外情况比如资源不足或者其他外部因素干扰,则同样会造成整个上下文启动失败的结果。 #### 调整建议: 仔细审查项目中的所有关键路径上的服务提供者是否具备足够的健壮性和容错能力;必要时候可以通过增加额外的日志记录帮助定位具体位置所在。 #### 4. 分布式锁竞争条件下的潜在风险 假如业务逻辑涉及到分布式事务控制并且采用了类似于 Redisson 这样的工具库来进行加解锁操作的话,那么还需要考虑到并发环境下可能存在的时间窗口漏洞所带来的隐患 [^5]: ```java RLock redissonLock = redisson.getLock(REDIS_LOCK); if (!redissonLock.tryLock()) { throw new RuntimeException("Failed to acquire lock"); } try { // 执行核心任务... } finally { redissonLock.unlock(); } ``` 上述片段展示了如何安全地请求锁定而不阻塞线程执行流的同时也提供了基本防护措施防止死锁现象的发生。 --- ### 总结 针对 spring aop 导致的 bean creation exception 可能由多种不同层面的因素引起,包括但不限于循环依赖、工厂bean定义失误、单例bean加载次序混乱或者是分布式的同协调机制失效等等。因此需要依据实际应用场景逐一排查根本诱因并对症施治。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值