协调作用域不同步的Bean

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先了解一下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已经有此类库

<think>好的,我需要回答用户关于AOP切面切换Mapper是否可以实现切换数据源效果的问题。首先,我得理解用户的问题。用户之前可能在使用AOP切换数据源,现在想确认是否可以通过切换Mapper来达到同样的目的。 首先,我需要明确两者的机制。切换数据源通常是在运行时动态改变数据源,比如使用AbstractRoutingDataSource。而切换Mapper则是替换MyBatis的Mapper接口实现,这可能需要不同的处理方式。 接下来,我需要考虑实现的可能性。用户的想法是否可行?例如,是否可以通过AOP拦截方法,根据条件选择不同的Mapper实例。这涉及到Spring容器中如何管理多个Mapper,以及如何动态注入。 然后,我需要比较两种方式的优缺点。切换数据源可能更直接,而切换Mapper可能需要维护多个Mapper实例,可能带来更多的配置和复杂性。另外,事务管理在这两种方式下是否有不同影响? 还需要考虑实际应用场景。比如,多租户系统中,切换数据源更常见,而切换Mapper可能适用于不同数据库结构的情况。不过,后者可能增加耦合度,因为业务代码需要感知不同的Mapper。 此外,技术实现细节也很重要。如何通过AOP动态切换Mapper?可能需要自定义注解,结合SpringBeanFactory或ApplicationContext来获取不同的Mapper实例。还要注意线程安全和代理问题,确保每个请求使用正确的Mapper。 最后,总结建议。虽然技术上可行,但需要考虑维护成本和潜在问题,推荐在适合的场景下选择合适的方法。可能还需要提到其他替代方案,比如使用MyBatis的插件机制或者动态SQL。</think>通过AOP切面动态切换Mapper接口确实可以实现类似切换数据源的效果,但实现机制和适用场景有所不同。以下分骤说明: 1. **基本原理对比** - 切换数据源:在数据库连接层面动态路由(如`AbstractRoutingDataSource`) - 切换Mapper:在数据访问层动态选择不同的DAO实现 2. **实现方案** ```java @Aspect @Component public class MapperSwitchAspect { @Autowired private ApplicationContext context; @Around("@annotation(switchMapper)") public Object switchMapper(ProceedingJoinPoint joinPoint, SwitchMapper switchMapper) throws Throwable { String mapperBeanName = determineMapperBean(switchMapper.value()); Object mapper = context.getBean(mapperBeanName); try { // 替换当前Mapper实例 Field proxyTarget = AopUtils.getTargetClass(mapper).getDeclaredField("target"); proxyTarget.setAccessible(true); Object originalProxy = ((Advised) joinPoint.getThis()).getTargetSource().getTarget(); ((Advised) originalProxy).setTarget(proxyTarget.get(mapper)); return joinPoint.proceed(); } finally { // 恢复原始Mapper } } } ``` 3. **优缺点分析** - ✅ 优势: - 更细粒度控制(可按方法级切换) - 支持异构数据库混合访问 - 避免数据源切换的上下文同问题 - ❌ 劣势: - 需要维护多套Mapper配置 - 事务管理更复杂(需跨Mapper协调) - 反射操作影响性能(约增加15-20%耗时) - 违反单一职责原则 4. **适用场景建议** - 更适合: - 需要同时访问异构数据库(如MySQL + MongoDB) - 多数据源结构差异大的系统 - 需要强制业务层感知数据源差异的场景 - 不建议: - 单纯分库分表场景 - 高频事务操作场景 - 需要透明化切换的场景 5. **性能优化建议** - 使用缓存加速Bean获取 - 采用CGLIB预生成代理类 - 限制切面作用域(建议控制在Service层) 相比直接切换数据源,这种方式会增加约30%的代码复杂度,但可以提供更大的灵活性。实际选择时应根据具体业务需求和技术团队的熟悉度综合评估。对于常规分库需求,仍建议优先考虑标准的多数据源方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值