Spring经常出现的报错原因,看完保证你技术涨一层!

在我们开发中经常会遇到很多关于Spring启动报错的问题,我司在整合几十个老系统的时候也经常出现这样的问题,而这样的场景在大一点的公司会经常出现,比如给你个任务,你整合下几个遗留系统,有几十个甚至几百个jar包,而这些jar包不是你写的,是程序员们迭代出来的,但是没人敢动啊~~所以可能程序员们这辈子都素未谋面,但是都做了自己的开发,每个人都有每个人开发规范,甚至一批程序员是老程序员,在Spring2.0刚问世的时候,注解也刚诞生,多数使用的还是配置文件的方式,而迭代后的另一批程序员是Spring3.x或者4.x年代,那个时候注解的已经开始盛行,整合一起,就会各种报错,所以本文带你真正剖析下Spring产生这些报错,和出现报错的原因!!

在介绍报错之前,我先搭建一个环境,以Springboot环境为例,版本你们随便,这个和Springboot没有什么很大的关系,本质是Springboot的自动装配和场景驱动器已经帮我们完成Spring的jar包整合,报错还是和Spring有关系!!

  • IDEA IntelliJ:版本随便
  • Springboot:版本随便

过程我就不带着大家去搭建了,随便创建好一个项目就行了。结果的样子是这样的

Spring经常出现的报错原因,看完保证你技术涨一层!_第1张图片

然后我们就开始说问题,对于报错我整理了两个类型,请耐心的看完,对号入座哈!

1、对于Bean实例化的报错

场景一、5年前的一个程序员写了一个UserController,在他定义的包路径下,5年后你开发了一个UserController,在你的包路径下,结果启动的时候,发现两个类都创建了userController这个beanName? (为了演示报错,规范性先忽略!)

我模拟两个UserController,一个是zhangsan的包下,5年前开发的,一个是chenxin,现在开发的,都叫做UserController。

Spring经常出现的报错原因,看完保证你技术涨一层!_第2张图片

启动的时候,哦豁,报错了,我们看看报错原因:

为了方便查看,我截取了重要信息,手机也很方便看。

可以看到抛出的异常是BeanDefinitionStoreException,英语好点的可以翻译下,是Bean定义存储异常,很明显像是Bean要创建出来,但是有异常,异常信息接着看:

没能转换这个配置类,也就是这个Springboot的启动类BeannameErrorApplication,不知道配置类的话读下我前面讲的Spring最后章节,然后下一行是内嵌的ConflictBeanDefinitionException,叫做bean定义时的冲突异常,了解Spring的应该知道Spring会把

对象扫描做成bean对象,并给个id,这个id就是Bean的名字,id默认是这个类的首单词首字母小写,所以你想想,很自然Spring为zhangsan和chenxin这个包下的UserController这个类,都会生成id=userController这个名字,但是不好意思,当扫描第一个

UserController的时候,做成了userController,扫描第二个的时候,发现存在了有个叫做userController,但是却发现不是同一个类生成的bean对象,是来自不同的类,你说Spring还会给你生成吗?

org.springframework.beans.factory.BeanDefinitionStoreException: 
Failed to parse configuration class [com.chenxin.spring.BeannameErrorApplication]; 
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException:
 Annotation-specified bean name 'userController' for bean class [com.chenxin.spring.zhangsan.UserController] conflicts with existing, 
non-compatible bean definition of same name and class [com.chenxin.spring.chenxin.UserController]

这个问题其实有不少的解决方案:

  • 作为5年后的程序员,你可以换个名字吧,不叫UserController就好了,这是一种解决方案
  • 但是你想想,Spring其实早就考虑到这点,你们素未谋面,你怎么知道我之前会写一个UserController,再说了,我是个开发,我写UserController本来就是规范的,我不能不改名字吗?

当然可以了,那Spring给我们做了扩展点:你需要自己写个类,实现AnnotationBeanNameGenerator,重写里面的这个方法,直接返回beanClassName,这个时候,就等于修改了Spring默认创建对象赋值beanName的方式,不再是首字母首单词小写

而是类的全路径名!!

@Component
public class BeanNameConfig extends AnnotationBeanNameGenerator {
    @Override
    protected String buildDefaultBeanName(BeanDefinition definition) {

        return definition.getBeanClassName();
    }
}

有的小伙子该喷我了,不信我们打印看看是不是,再写个类拿到Spring工厂后循环打印beanNames试试看

 这个类是可以打印你的项目初始化的时候Spring帮你创建的所有beanName,这个可以帮助你以后排查一些重大问题的思路,学费了没?

@Component
public class ApplicationAwareFor implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {

            System.out.println(">>>>>>>>>>>>>" + beanDefinitionName);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext =   applicationContext;
    }
}

项目的结构是这样的:

 Spring经常出现的报错原因,看完保证你技术涨一层!_第3张图片

光写了BeanNameConfig不够,你要在启动类上,@SpringbootApplication这个注解扫描的时候,加上个扫描时候生成的BeanName规则,就是你上面写的BeanNameConfig

@SpringBootApplication(nameGenerator = BeanNameConfig.class)
public class BeannameErrorApplication {

    public static void main(String[] args) {
        SpringApplication.run(BeannameErrorApplication.class, args);
    }

}

启动我们看看,很明显打印出来了很多beanName,注意看下标红的,这些都是Spring根据我们的生成BeanName的规则去帮我们创建的BeanName。现在两个UserController的beanName是不一样的,当然已经可以创建成功了!!项目也正常启动了。

Spring经常出现的报错原因,看完保证你技术涨一层!_第4张图片

现在我已经解决了创建Bean的时候冲突的问题了;这个是模拟一个包中的某个路径的类,和你写的类,名字一样,创建的时候冲突的问题。来看场景二

场景二、zhangsan程序员早5年前写了个底层jar包,里面是很老的xml配置方式,里面写了一个UserController,也在他的包路径下,然后并不是通过配置类来创建对象的,而是通过ApplicationContext.xml创建对象的,我模拟下

新建立了一个maven项目,下一步到这里

Spring经常出现的报错原因,看完保证你技术涨一层!_第5张图片

然后直接下一步创建出来这个项目,applicationContext.xml是这样的




    

整个zhangsan项目工厂这样的

 

pom的依赖是这样的

 
        
            org.springframework
            spring-context
            5.1.10.RELEASE
        

        
            org.springframework
            spring-core
            5.1.10.RELEASE
        
    

我此时打了个jar发布到私服,我这模拟下,就install到本地了哈

重点来了,我写的比较清楚,希望你们可以认真看!!!此时我要做个事情,我要先把自己的项目zhangsan包下的UserController先完整的注释掉。并且修改自己项目的启动类上扫描bean的规则,我改成初始化的规则,不用BeanNameConfig,并且我还要扫

描jar包里面的applicationContext.xml,为什么呢?还是那句话,你一定会接触到老项目,而你要做的是整合遗留系统和jar包,不是要重新写,而这个Spring我觉得真正的核心就自这个上面!!

所以看下截图我干了什么,只干这两个事情

Spring经常出现的报错原因,看完保证你技术涨一层!_第6张图片

以及注释

Spring经常出现的报错原因,看完保证你技术涨一层!_第7张图片

这个时候我们启动看下,哦豁又报错,我再截个核心的报错

这个还是Bean定义存储异常,和场景一的异常一样,但是有不一样的地方,接着翻译

不被期望的异常,发生在解析xml的标签,内嵌的bean定义又冲突了,原因是什么呢?因为他jar包的配置文件applicationContext.xml已经做了扫描这个工作,而他的扫描是默认Spring的生成BeanName规则,也就是userController,而我们主启动类扫描的

还原也是默认Spring的方式,冲突的原因就和场景一是一样的。底层jar包帮你扫jar的,你自己扫你项目的。生成bean的规则一样,所以冲突。

org.springframework.beans.factory.BeanDefinitionStoreException: 
Unexpected exception parsing XML document from URL [jar:file:/E:/Maven/Repositories/com/chenxin/zhangsan-jar/1.0-SNAPSHOT/zhangsan-jar-1.0-SNAPSHOT.jar!/applicationContext.xml]; 
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: 
Annotation-specified bean name 'userController' for bean class [com.chenxin.zhangsan.UserController] conflicts with existing, 
non-compatible bean definition of same name and class [com.chenxin.spring.chenxin.UserController]

解决方案就是场景一的,我这个时候用我的规则!

Spring经常出现的报错原因,看完保证你技术涨一层!_第8张图片

看下启动的时候的日志:

Spring经常出现的报错原因,看完保证你技术涨一层!_第9张图片

但是这个时候我扩展下,我把zhangsan-jar里面的这个扫描的标签注释掉,然后再install到本地试试效果

Spring经常出现的报错原因,看完保证你技术涨一层!_第10张图片

尝试启动你会发现只扫了本项目的UserController,没有扫底层zhangsan-jar的UserController,是为什么?

Spring经常出现的报错原因,看完保证你技术涨一层!_第11张图片

很简单我这里特地挖了一个坑,你看我的项目的包路径默认扫的是com.chenxin.spring包及其子包下的所有类,而zhangsan-jar的UserController是在com.chenxin.zhangsan这个包下的,所以我们怎么做?

是不是只需要让我的项目可以帮忙扫描到com.chenxin这个包,那么当前包,及其子包下的类都会被扫到对吧,所以需要在启动类上的@SpringbootApplication加这个属性

scanBasePackages = {"com.chenxin"}

Spring经常出现的报错原因,看完保证你技术涨一层!_第12张图片

那么运行下:可以看到底层jar包,在去除applicationContext的component-scan这个标签后,是可以被扫到的也做成了bean,加了规则,不再冲突。

Spring经常出现的报错原因,看完保证你技术涨一层!_第13张图片

那这个时候我再扩展下,当前我已经把zhangsan-jar的applicationContext中component-scan标签去掉了,但是我能不能在我主启动类上加个@ComponentScan?并指明扫包路径?

我们试试,现在主启动类是这样的,@SpringbootApplication负责生成beanName的名字,而@ComponentScan负责扫包

Spring经常出现的报错原因,看完保证你技术涨一层!_第14张图片

结果是冲突!!!报错如下,很明显,又是创建bean的时候冲突!

org.springframework.beans.factory.BeanDefinitionStoreException: 
Failed to parse configuration class [com.chenxin.spring.BeannameErrorApplication]; 
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: 
Annotation-specified bean name 'userController' for bean class [com.chenxin.zhangsan.UserController] conflicts with existing, 
non-compatible bean definition of same name and class [com.chenxin.spring.chenxin.UserController]

我解释下原因,如果知道SpringbootApplication这个注解的,应该知道里面有个注解叫做@ComponentScan,而如果你自己手动定义了@ComponentScan这个注解在启动类上,则Springboot默认不会使用自带的ComponentScan的扫描方式,而是以你

启动类上显示明确的@ComponentScan的路径,同样,属性nameGenerator这个属性,也是跟着@ComponentScan走的,不信我们看看注解源码

这是@ComponentScan

Spring经常出现的报错原因,看完保证你技术涨一层!_第15张图片

这是@SpringbootApplication,是AliasFor跟着@ComponentScan走的,所以这个可以很清晰的解释了为什么我@SpringbootApplication有生成beanName的规则,也用@ComponentScan覆盖了默认的扫包规则,为什么还是不生效呢?对吧

Spring经常出现的报错原因,看完保证你技术涨一层!_第16张图片

所以你想扫到的话,你得这么写!这样的话,你自定义的,解决beanName生成冲突的规则BeanNameConfig才生效嘛!!!

Spring经常出现的报错原因,看完保证你技术涨一层!_第17张图片

那么目前常见的生成Bean冲突的场景,我先补充到这,后续有的话我再继续。

我们还是先回到一开始的情况,有两个UserController的前提+BeanNameConfig,zhangsan-jar的依赖我们不引入,启动后是这样的,读者保证环境一致的前提!

Spring经常出现的报错原因,看完保证你技术涨一层!_第18张图片

2、对于Bean注入时候的报错

上面讲了我们bean的生成冲突问题,本节开始讲下注入的时候产生的问题

场景一:基于上面结尾的环境,我这边新建了一个UserService类和实现,在service包下

Spring经常出现的报错原因,看完保证你技术涨一层!_第19张图片

然后此时我在chenxin包下的UserController中注入service,有没有问题?启动后很明显,正常

Spring经常出现的报错原因,看完保证你技术涨一层!_第20张图片

但是我这个时候再建立一个UserService实现类叫做UserServiceImpl2,这个时候再注入,怎么说?启动怎么样,哦吼报错了,这个错误很常见吧

意思是chenxin下的UserController注入UserService的时候,因为有两个多实现类,注入的时候,我无法分清楚,你想让我注入的是哪个实现类?因为这是由同一个接口,生成的两个代理类,分别是两个service实现的代理类!!!记住,这里是同一个

接口!!这里要记住,后面我要详细对比的。那么解决的方式有很多种。

Spring经常出现的报错原因,看完保证你技术涨一层!_第21张图片

解决一:我直接手动@Autowired注入实现类UserServiceImpl,而不是接口,但是这样违反了规范,违反了设计准则,只是临时的解决而已!

解决二:在多个实现类上,其中一个做成你Controller要优先注入的实现类,比如UserController在注入UserService的时候,我想优先注入UserServiceImpl,那么我就要在这个类上加个@Primary,表示被优先注入

Spring经常出现的报错原因,看完保证你技术涨一层!_第22张图片

这样可以临时解决,默认注入优先你标注的实现类,但是有特殊的需求,我要是想注入其他的UserService的实现类呢比如UserServiceImpl2,那么这个@Primary显然就不是那么靠谱了

于是解决三:手动标注@Qualifier的value值,通过byName的方式注入,实际上看过我之前的博客知道,

我写了@Autowired+@Qualifier 就等于  @Resource(name="")(地址:https://blog.csdn.net/qq_31821733/article/details/115560566?spm=1001.2014.3001.5501),所以我这么干,我把@Primary去掉,在注入的地方这么写,手动写beanName,

那这个就比较灵活了!

Spring经常出现的报错原因,看完保证你技术涨一层!_第23张图片

这样的话,注入冲突的场景一我们就可以解决了!

来看场景二:

我们先把UserServiceImpl2这个类注释掉,只保留一个实现类UserServiceImpl,UserController注入的地方我也修改成最初的方式

Spring经常出现的报错原因,看完保证你技术涨一层!_第24张图片

这个时候我多加一个包service2,也写个UserService和UserServiceImpl

Spring经常出现的报错原因,看完保证你技术涨一层!_第25张图片

目前就成了在两个不同的包下,都有两个一毛一样的UserService及其实现类UserServiceImpl,好,我现在要干个事情,就是直接启动,看看什么样子

很明显,可以启动,这个zhangsan下的UserController也可以注入UserService,以后人说了不冲突吗?当然不冲突,我在UserController里面,注入的是指定的类啊!!!!是service包下的UserService,不是service1下的UserService

而刚刚说了,@Autowired注入的特点是byType注入,是指当前类,及其子类,实现类,都已经限定了类了,当然可以注入,所以这个是没问题的!!

Spring经常出现的报错原因,看完保证你技术涨一层!_第26张图片

那后面有场景我会继续补充到里面来,希望你们喜欢!!!

 

所以目前看来,能出现异常的情况,一定是你要对Spring基础的考察,和对很多注解的认识,有时候自己手动写写,模拟下场景,会解决你很多开

发一辈子遇到常见问题,网上的解决方案各有各的,但是万变不离其中,看会的别忘点赞三连+收藏,我会持续更新下去这些实战出来的报错点。

感兴趣可以跟着做做!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Spring基础+源码分析篇,java)