单接口多个实现类调用指定类失效问题排查

背景描述

最近在做数据库大表拆分接ES的优化需求,针对某个大表数据逻辑接口IPoolProductService,增加策略实现类PoolProductProxyService,用于控制访问走老逻辑PoolProductService还是新逻辑PoolProductInfoService

数据逻辑接口IPoolProductService

public interface IPoolProductService {
    ProductDTO query(Long id);
}

老逻辑实现类PoolProductService

@Service
public class PoolProductService implements IPoolProductService {
    @Override
    public ProductDTO query(Long id) {
        System.out.println("I AM oldService");
        return null;
    }
}

策略类PoolProductProxyService

@Service
@Primary
public class PoolProductProxyService implements IPoolProductService {
    @Resource
    private PoolProductService oldService;
    @Resource
    private PoolProductInfoService newService;

    @Override
    public ProductDTO query(Long id) {
        System.out.println("I AM proxyService");

        if (oldRead) {
            return oldService.query(id);
        }
        if (newRead) {
            return newService.query(id);
        }
        return null;
    }
}

新逻辑实现类PoolProductInfoService

@Service
public class PoolProductInfoService implements IPoolProductService {
    @Override
    public ProductDTO query(Long id) {
        System.out.println("I AM newService");
        return null;
    }
}

调用类PoolProductBiz

@Service
public class PoolProductBiz {
    @Resource
    private IPoolProductService poolProductService;

    private ProductDTO query(Long id) {
       return poolProductService.query(id);
    }
}

为减小对业务逻辑调用的代码改动,在策略类PoolProductProxyService上加注解@Primary,希望业务代码调用优先调PoolProductProxyService,经过策略分发走新老逻辑。

但在测试时,发现加了@Primary注解的类PoolProductProxyService未被优先调用,仍走老逻辑,注解没有生效。

原因分析

Spring启动时,发现注入相同类型的bean有多个,会报异常NoUniqueBeanDefinitionException,可以通过加注解@Primary 赋予某个bean更高优先级或@Priority 决定优先注入的bean等。

此处采用加@Primary 注解,但@Primary 注解不能解决调用指定实现类的问题。

在注入类时使用@Resource,其默认按照名称进行装配,找不到bean,会通过byType寻找bean。而@Primary是指在根据type寻找时,优先取加了该注解的实现类。

在上述示例中,调用类PoolProductBiz通过@Resource注入接口IPoolProductService,name为poolProductService,所以会优先根据poolProductService寻找对应的实现类,即老逻辑实现类,走不到策略类,@Primary注解未生效。

解决方案

针对一个接口多个实现类,想调用指定实现类可以有下述几种改进方案:

  1. 将@Resource注入的别名改为和实现类PoolProductService不同的,此时通过byName找不到实现类,会通过byType寻找,之后会首先取加了@Primary 注解的实现类PoolProductProxyService
@Service
public class PoolProductBiz {
    @Resource
    private IPoolProductService poolProductServiceImpl;

    public ProductDTO query(Long id) {
       return poolProductServiceImpl.query(id);
    }
}
  1. 将@Resource换成@Autowired,通过byType寻找实现类,此时会通过byType寻找,之后会首先取加了@Primary 注解的实现类PoolProductProxyService
@Service
public class PoolProductBiz {
    @Autowired
    private IPoolProductService poolProductService;

    public ProductDTO query(Long id) {
       return poolProductService.query(id);
    }
}
  1. 将@Resource换成@Autowired,并使用@Qualifier指定实现类。
@Service
public class PoolProductBiz {
    @Autowired
    @Qualifier("PoolProductProxyService")
    private IPoolProductService poolProductService;
}

@Service
@Repository(value = "PoolProductProxyService")
public class PoolProductProxyService implements IPoolProductService {
    @Resource
    private PoolProductService oldService;
    @Resource
    private PoolProductInfoService newService;
}

当然还有其他解决方案,在本case中,采用方案1
因有大量业务代码通过以下方式注入(如下代码块),考虑代码重构成本和业务影响大小,将老逻辑实现类PoolProductService改名为PoolProductServiceImpl,在策略类PoolProductProxyService上加@Primary 注解,解决一个接口多个实现类调用指定类失效的问题。

    @Resource
    private IPoolProductService poolProductService;

另外,也可以简单粗暴直接在业务代码中注入策略类PoolProductProxyService,但这种业务改动兼容成本太高,后面切换新老逻辑读写,下掉老逻辑和策略分发类也不方便。

其次业务代码中,若没有注入接口,直接注入老逻辑实现类,改动回归成本也会非常高。

你可能感兴趣的:(Spring,spring,java,后端)