背景
@Qualifier注解的使用场景主要是在Spring容器中存在多个相同的RequiredType时,通过beanName的方式指定依赖的是哪个一个bean实例。
之所以有动态修改@Qualifier注解的value值,是因为我们把多个微服务应用通过Maven依赖的方式整合成了一个单体应用,但是不同的服务中定义了相同的类名导致Spring启动时提示beanName重复。
通过实现AnnotationBeanNameGenerator的方式自定义了beanName的生成方式,取代了Spring默认的类名作为beanName的方式,这个具体过程不表,搜索引擎能搜到很多具体的实现代码。
这样引发了另外一个问题,有些开发同学在定义依赖bean的时候使用@Qualifier注释指定了beanName,因为修改了beanName默认生成方式导致无法找到指定的bean,启动又报错了。
解决方案有两种:
- 方案一:适合在开发阶段时使用,对代码有一定的侵入性,有条件还是比较推荐这种实现方式的;
- 方案二:通过重载Spring的DefaultListableBeanFactory类,实现无代码入侵的方式动态修改@Qualifier注解指定的beanName。
方案一
使用@Resource注解替换@Qualifier注解,需要注入的beanName可以通过配置项的方式动态指定,实现代码如下:
public class DemoService {
// 通过配置beanName参数指定需要注入的bean实例
@Resource(name="${beanName}")
IService iService;
// todo
}
方案二
看了下@Qualifier注解核心实现逻辑(SpringBoot2.0版本)在org.springframework.beans.factory.support.DefaultListableBeanFactory类的
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor)方法中,具体干了如下几件事:
- 通过requiredType找到所有匹配的bean;
- 通过@Qualifier注解的value找到对应的bean并放入result集合返回;
动态修改的思路:
- 拿到所有匹配的beanName数组后,加一个自定义的重写@Qualifier注解value值的方法;
- 判断当前处理的类是否是业务层代码,需要过滤掉Spring底层和中间件的类,这个可以按照自己的技术架构特点进行判断;
- 获取@Qualifier注解原始value值,并根据自己的beanName规则生成新的beanName;
- 判断原始的beanName不在匹配的beanName数组中,并且根据自己的beanName规则生成新的beanName在匹配的beanName数组中,说明该@Qualifier注解的value值需要进行重写;
- 通过反射的方式动态修改@Qualifier注解的value值,这里大家可以搜索下Java注解实现原理,是通过AnnotationInvocationHandler生成的一个代理类,所有的注解属性存储在AnnotationInvocationHandler类的memberValues属性Map集合中。
实现代码如下:
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
// 自定义的动态修改@Qualifier注解值
rewriteQualifierValue(candidateNames, requiredType, descriptor);
// .......
return result;
}
private void rewriteQualifierValue(String[] candidateNames, Class<?> requiredType, DependencyDescriptor descriptor) {
// 尝试获取@Qualifier注解
Qualifier qualifier = descriptor.getAnnotation(Qualifier.class);
// 尝试获取依赖类子产品代码
String serviceName = PackageUtils.getServiceName(requiredType.getName());
// 必要性判断
if (Objects.nonNull(qualifier) && StringUtils.hasText(qualifier.value()) && StringUtils.hasText(serviceName)) {
// 通过子产品代码生成有依赖对象beanName
// {@link com.xxx.dgdp.config.PackageBeanNameGenerator}
String dependencyWithService = new StringBuilder(serviceName).append(StringUtils.capitalize(qualifier.value())).toString();
// ICO容器中没有@Qualifier中定义的依赖对象
// ICO容器中存在代子产品的依赖对象
if (!ArrayUtils.contains(candidateNames, qualifier.value())
&& ArrayUtils.contains(candidateNames, dependencyWithService)) {
// 获取代理实例
InvocationHandler annotationInvocationHandler = Proxy.getInvocationHandler(qualifier);
try {
// 获取AnnotationInvocationHandler的memberValues属性
Field memberValues = annotationInvocationHandler.getClass().getDeclaredField("memberValues");
memberValues.setAccessible(true);
// 修改value值
Map<String, Object> values = (Map<String, Object>) memberValues.get(annotationInvocationHandler);
values.put("value", dependencyWithService);
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.error("重写@Qualifier指定依赖beanName时,未找到注解AnnotationInvocationHandler对象memberValues属性");
}
}
}
}