今天在开发过程中,测试环境报了一个错误,就是通过spring容器获取某个类获取不到,报NoSuchBeanDefinitionException错误,伪代码如下:
public interface RepayMessageService {
void notifyRepayResult(String message);
}
public abstract class AbstractRepayMessageHelper implements RepayMessageService {
}
@Service
public class IQiYiNotifyRepayMessageHelper extends AbstractRepayMessageHelper{
@Override
public void notifyRepayResult(String message) {
System.out.println("===================");
}
}
@Service("repayProcessMessageStrategy")
public class RepayProcessMessageStrategy {
@Autowired
private ApplicationContext applicationContext;
public RepayMessageService getService(String assetProductCode){
RepayMessageServiceEnum repayMessageServiceEnum = RepayMessageServiceEnum.getProductInstance(assetProductCode);
if(null == repayMessageServiceEnum){
return null;
}
return (RepayMessageService) applicationContext.getBean(repayMessageServiceEnum.getServiceImpl());
}
@Getter
@AllArgsConstructor
private enum RepayMessageServiceEnum {
AQY_LOANS("AQY_LOANS", "iQiYiNotifyRepayMessageHelper"),
AQY_LOANS2("AQY_LOANS2", "iqiYiNotifyRepayMessageHelper2");
//.............
private String assetProductCode;
private String serviceImpl;
public static RepayMessageServiceEnum getProductInstance(String assetProductCode){
for(RepayMessageServiceEnum record : values()){
if(record.assetProductCode.equals(assetProductCode)){
return record;
}
}
return null;
}
}
}
public class Test3 extends BaseTest{
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RepayProcessMessageStrategy repayProcessMessageStrategy;
@Test
public void test() {
//打印spring容器的bean观察
for(String bean:applicationContext.getBeanDefinitionNames()){
if(bean.toString().contains("NotifyRepayMessageHelper")){
System.out.println("spring容器加载的bean:"+bean);
}
}
//如果类名为IQiYiNotifyRepayMessageHelper,则使用applicationContext.getBean("iQiYiNotifyRepayMessageHelper")会报错
try{
RepayMessageService repayMessageService1 = repayProcessMessageStrategy.getService("AQY_LOANS");
System.out.println(repayMessageService1);
}catch (Exception e){
e.printStackTrace();
}
//解决方法1:改为applicationContext.getBean("IQiYiNotifyRepayMessageHelper")
System.out.println("=============1"+applicationContext.getBean("IQiYiNotifyRepayMessageHelper"));
//解决方法2:将类名改为第二个字母小写IqiYiNotifyRepayMessageHelper,此时使用applicationContext.getBean("iqiYiNotifyRepayMessageHelper")
RepayMessageService repayMessageService = repayProcessMessageStrategy.getService("AQY_LOANS2");
System.out.println("=============2"+repayMessageService);
//解决方法3:使用@Service("iQiYiNotifyRepayMessageHelper3")
System.out.println("=============3"+applicationContext.getBean("iQiYiNotifyRepayMessageHelper3"));
}
经看源码得知:原因是取某个类名的时候第一个和第二个字母都为大写所致。当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致
源码如下:
public class AnnotationBeanNameGenerator implements BeanNameGenerator {
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return buildDefaultBeanName(definition);
}
protected String buildDefaultBeanName(BeanDefinition definition) {
String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
return Introspector.decapitalize(shortClassName);
}
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
其实还有一种解决方法,我们可以看到代码中的策略模式其实还是有缺陷的,比如,如果我要则更加多个实现类,iqiYiNotifyRepayMessageHelper3、iqiYiNotifyRepayMessageHelper4,那么在策略类中的枚举类中还是需要加上这两行代码:这显然不符合开闭原则
AQY_LOANS3("AQY_LOANS3", "iqiYiNotifyRepayMessageHelper3"),
AQY_LOANS4("AQY_LOANS4", "iqiYiNotifyRepayMessageHelper4");
可以改为如下代码,这样每次新建类后,不需要改动此策略类的代码,只需要新建对应的实现类即可;
private static Map<String,RepayMessageService> serviceHashMap = new HashMap<>();
/**
* 每次新建一个实现类则调用此方法将类注册到serviceHashMap中
* @param code
* @param service
*/
public static void registerImpl(String code,RepayMessageService service){
serviceHashMap.put(code,service);
}
/**
* 调用此方法获取code对应的类
* @param code
* @return
*/
public RepayMessageService getService2(String code){
return serviceHashMap.get(code);
}