Spring Core常见错误及解决方案

news2024/9/20 12:16:23

Spring Core常见错误及解决方案

一些Spring Core错误及解决方案,出自极客时间傅健老师《Spring编程常见错误50例》

https://time.geekbang.org/column/intro/100077001

Bean定义

隐式扫描不到Bean的定义

image-20240821091058169

如果我们定义这样的目录结构,实际上访问对应接口时会找不到。

原因是@SpringBootApplication注解里的@ComponentScan注解中的basePackages属性指定了应用启动时扫描的Bean目录,如果不显式指定,默认扫描主启动类下的包,因此扫描不到HelloWorldController了,导致Bean失效。

解决方案

通过@ComponentScans/@ComponentScan添加需要扫描的包路径,注意如果添加这个注解后主启动类所在包就不会再被扫描!

定义的 Bean 缺少隐式依赖

image-20240821091848633

如果我们的Bean中有某个属性值,Bean在启动时需要通过构造器构造,但是找不到这个属性的Bean,启动有时候就会报错:Parameter 0 of constructor in * required a bean of type 'java.lang.String' that could not be found.

解决方案

image-20240821091931742

定义一个这样的Bean,供构造器使用

注意:显式定义构造器,会自动发生根据构造器参数寻找对应bean的过程。可以给参数添加@Autowired((required = false)注解。

原型 Bean 被固定

image-20240821092233169

image-20240821092152064

当一个属性成员 serviceImpl 声明为 @Autowired 后,那么在创建HelloWorldController这个 Bean 时,会先使用构造器反射出实例,然后来装配各个标记为 @Autowired 的属性成员,这个装配的执行只发生了一次,所以后续就固定起来了,它并不会因为 ServiceImpl 标记了 SCOPE_PROTOTYPE 而改变。

解决方案

image-20240821092433145

每次从Context中取。

依赖注入

过多赠予,无所适从

错误为:required a single bean, but * were found

直接翻译为需要一个bean,但是却提供了多个

public interface DataService {
	void deleteStudent(int id);
}

@Repository
@Slf4j
public class OracleDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by oracle");
    }
}

@Repository
@Slf4j
public class MysqlDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by mysql");
    }
}

这时我们在使用下面这个代码运行时就会发生错误:

@Autowired
private DataService dataService;

当程序在装配dataService时候,发现有MysqlDataService和OracleDataService可以选择,并且决策不出优先级(@Primary注解),因此无法正常运行。

解决方案

  1. 给其中一个Bean加上@Primary注解,让其优先级更高
  2. 不要使用private DataService dataService,而是将命名改为bean的名字private DataService oracleDataService
  3. @Autowired配合@Qualifier("oracleDataService")使用
显式引用 Bean 时首字母忽略大小写

我们使用@Autowired配合@Qualifier("oracleDataService")

@Autowired()
@Qualifier("oracleDataService")
private DataService dataService;

发现程序可以正常运行了,但是如果改为下面这样

@Autowired()
@Qualifier("OracleDataService")
private DataService dataService;

会报错Unsatisfied dependency expressed through field 'dataService'.......

原因是找不到可以注入的Bean

但并不是所有的Bean都默认首字母小写的,Spring有自己的转换规则

BeanNameGenerator#generateBeanName 即用来产生 Bean 的名字

默认实现是:如果一个类名是以两个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写

为了避免此类隐式规则推荐在定义Bean时就手动指定命名

@Repository("SQLiteDataService")
@Slf4j
public class SQLiteDataService implements DataService {
//省略实现
}
引用内部类的 Bean 遗忘类名
@RestController
public class StudentController {
	@Repository
	public static class InnerClassDataService implements DataService{
        @Override
        public void deleteStudent(int id) {
        //空实现
        }
    }
}

这时候我们要注入InnerClassDataService时,要按照以下格式:

@Autowired
@Qualifier("studentController.InnerClassDataService")
private DataService innerClassDataService;
@Vlaue注解没有注入预期的值

假如我们在配置文件 application.properties 配置了这样的属性:

username=admin
password=pass

在程序里这样注入:

@RestController
@Slf4j
public class ValueTestController {
    @Value("${username}")
    private String username;
    @Value("${password}")
    private String password;
    @RequestMapping(path = "user", method = RequestMethod.GET)
    public String getUser(){
        return username + ":" + password;
    };
}

输出时发现username的值并不正确

原因是@Value在查询值的过程中,并不只查application.properties文件,包括系统环境变量systemEnvironment,系统参数 systemProperties中的同名配置也可能会被注入。

错乱注入集合
@Bean
public Student student1(){
	return createStudent(1, "xie");
}
@Bean
public Student student2(){
	return createStudent(2, "fang");
}

private Student createStudent(int id, String name) {
    Student student = new Student();
    student.setId(id);
    student.setName(name);
    return student;
}

image-20240821100040008

这样我们就可以完成List<Student> students的注入,不过这样比较麻烦,还可以用下面的方式:

image-20240821100152327

如果两种方式并存会发生什么呢,假如把第一种方式叫做收集方式,第二种方式叫做直接装配方式,程序运行的结果其实是后面的注入方式根本没有生效,只返回了收集方式的数据。

原因是:当Spring在通过收集方式找目标对象时,只要不为空就直接返回,不再进行直接装配,因此只返回了收集方式的数据。也就是说这两种装配集合的方式是不会都执行的。

解决方案

统一注入方式,只采用其中一种。

生命周期

构造器内空指针异常

image-20240821101202188

本意是想要在初始化类时执行一次检查,此时lightService已经被自动装配好,然后进行一次检查,但实际执行时其实会报空指针错误。

原因是对于Bean的生命周期来讲,先创建实例(构造器),再装配内部@Autowired的属性,最后执行后置处理函数,创建实例先于装配属性执行,因此执行时会出现空指针错误。

解决方式

  1. 构造器注入
@Component
public class LightMgrService {
    
    private LightService lightService;
    
    public LightMgrService(LightService lightService) {
        this.lightService = lightService;
        lightService.check();
    }
    
}

原因同第二个问题,在构造器中的变量,Spring创建时会去查找对应的Bean,因此完成初始化

  1. 添加 init 方法,并且使用 @PostConstruct 注解进行修饰:
@Component
public class LightMgrService {
    @Autowired
    private LightService lightService;
    @PostConstruct
    public void init() {
    	lightService.check();
    }
}
  1. 实现 InitializingBean 接口,在其 afterPropertiesSet() 方法中执行初始化代码:
@Component
public class LightMgrService implements InitializingBean {
    @Autowired
    private LightService lightService;
    @Override
    public void afterPropertiesSet() throws Exception {
    	lightService.check();
    }
}

后面两种解决方案都与Bean生命周期中的后置函数有关,在装配完属性,初始化时执行。

意外触发 shutdown 方法

这里主要是通过@Bean注解修饰的Bean,在Spring容器关闭时候会意外执行shutdown或者close方法

原因是@Bean注解修饰时会String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;destroyMethod属性一个默认值,此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或者 close 的方法,如果有,此方法会被 Spring 记录下来,并在容器被销毁时自动执行。

解决方式

  1. 不要定义这样具有特殊意义的方法名
  2. 定义Bean时手动指定destroyMethod

image-20240821102716298

注意这里是只有@Bean修饰的Bean,@Service等不会出现这样的情况

AOP

this 调用的当前类方法无法被拦截

这个可以参考我的这篇文章:https://blog.csdn.net/qq_56517253/article/details/140553254?spm=1001.2014.3001.5501

原因是this调用时候只是一个普通对象,而不是一个增强对象,因此没有被切面拦截或者事务未生效。

解决方案

  1. 在类的内部注入自己(注意循环依赖)

image-20240821103417075

  1. 从上下文中中取得当前Bean实例
直接访问被拦截类的属性抛空指针异常

image-20240821103917983

image-20240821103947340

在定义切面进行拦截AdminUserService后,访问其adminUser属性会报空指针错误,原因是 Spring 使用 CGLIB 生成 Proxy时生成的代理对象不会初始化内部属性

解决方案

  1. 添加方法获取user对象,当代理类方法被调用,会被 Spring 拦截,从而进入intercept,并在此方法中获取被 代理的原始对象。而在原始对象中,类属性是被实例化过且存在的。因此代理类是可以通 过方法拦截获取被代理对象实例的属性。

image-20240821104819259

  1. 修改启动参数 spring.objenesis.ignore为true
错乱混合不同类型的增强

image-20240821105150433

这里实际上是针对同一个方法定义了两个增强,一个是统计方法耗时,一个是校验权限,但是最终统计耗时的结果把权限校验也算进去了。引申出的问题是当同一个切面(Aspect)中同时包含多个不同类型的增强时(Around、Before、After、AfterReturning、AfterThrowing 等),它们的执行是有顺序的。那么顺序如何确定?

这里直接写结论:spring5.3版本时,最终的排序结果依次是 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class

错乱混合同类型增强

如果对同一个方法做两个before增强,那么执行的顺序是怎样呢?

直接说结论:当同一个切面(Aspect)中同时包含多个增强时,首先会根据类型(@Around、@Before…)进行比较,接着会根据切面方法名进行比较并排序。

这些案例的出现原因和解决方式都离不开源码的解读,傅健老师牛逼!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2075645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图书馆客流统计系统实时精准统计,管理者能及时了解馆内人数情况

在信息化时代&#xff0c;图书馆作为知识的宝库&#xff0c;其日常管理和服务水平直接影响着读者的体验和满意度。为了更好地管理图书馆的空间资源&#xff0c;并为读者提供更优质的阅读环境&#xff0c;许多图书馆开始引入先进的客流统计系统。 一、客流统计系统统计精准 1. 高…

线下参会报名丨智源数据与行业应用 Workshop 第二期

目前&#xff0c;大模型在数据基建和行业落地仍存在不少挑战。北京智源人工智能研究院深耕数据工具研发与数据平台建设&#xff0c;并持续推动模型与垂直场景的应用&#xff0c;旨在通过举办“数据与行业应用系列Workshop”活动&#xff0c;广泛链接生态伙伴&#xff0c;共同探…

NC设计LFU缓存结构

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 一个缓存结构…

基于pygame的雷电战机小游戏

import pygame import sys import random# 初始化 Pygame pygame.init()# 设置窗口尺寸 WIDTH, HEIGHT 800, 600 screen pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("雷电战机")# 设置颜色 BLACK (0, 0, 0) RED (255, 0, 0) GREEN (…

波束管理简介

波束管理简介 3GPP中&#xff0c;波束管理被定义为一组物理层或接入网高层的过程&#xff0c;用于获取和维护一组用于上行和下行传输或接收的gNB和UE波束。波束管理大致由&#xff1a;波束扫描&#xff0c;波束测量&#xff0c;波束判决&#xff0c;上报和指示&#xff0c;波束…

点亮星星的世界:当小孩得了自闭症该怎么办

在这个丰富多彩的世界里&#xff0c;每一个孩子都是独一无二的天使。然而&#xff0c;有一些孩子却仿佛被困在了自己的小小世界中&#xff0c;难以与外界正常交流和互动。他们是自闭症儿童&#xff0c;他们的世界充满了挑战和困惑&#xff0c;也让家长们陷入了深深的担忧和焦虑…

深入解析并发与并行:如何利用住宅代理加速网页抓取

引言 什么是并发执行&#xff1f;基本单位是什么&#xff1f; 什么是并行执行&#xff1f;和并发执行的区别是什么&#xff1f; 导致网页抓取缓慢的原因有哪些&#xff1f; 使用python进行网页抓取的并发性 并行性如何加速网页抓取 总结 引言 在当今数据驱动的世界中&am…

泛微Ecology9建模问题及解决办法记录(一)(按钮变灰、批量修改、导出、显示转换等)

1、为管理员和普通用户分别设置显示模版,其中普通用户的显示模版,“编辑‘’按钮置灰,不允许使用的方法 在用户显示模版页面中插入E9代码块如下: <script type="text/javascript"> /* * 请在下面编写JS代码 */ ModeForm.controlBtnDisabled(true); </…

“我王多鱼投了!”疯狂烧钱的AI大模型公司如何赚钱?

AI大模型到底有多烧钱&#xff1f; 或许最有发言权的就是OpenAI了&#xff0c;2022年其亏损大约翻了一番&#xff0c;达到约5.4亿美元。据悉&#xff0c;Altman曾私下建议&#xff0c;OpenAI可能会在未来几年尝试筹集多达1000亿美元的资金&#xff0c;以实现其开发足够先进的通…

Thinkphp6 反序列化漏洞分析

本文来自无问社区&#xff0c;更多实战内容可前往查看http://wwlib.cn/index.php/artread/artid/10431.html 版本&#xff1a;Thinkphp6&PHP7.3.4 TP 环境搭建利用 composer 命令进行&#xff0c;同时本次分析在 windows 环境下进行 composer create-project topthink/t…

佰朔资本:上市有什么好处?为什么有公司不愿上市?

公司上市或许带来的长处有&#xff1a; 1、融资途径拓宽。上市为公司供给了更广大的融资途径&#xff0c;通过发行股票公司可以敏捷筹集到大量资金&#xff0c;用于扩大生产规模、研制新产品、拓宽商场等。与银行贷款、发行债券等传统融资方法比较&#xff0c;上市融资不仅本钱…

【目标检测】YOLOV1

You Only Look Once: Unified, Real-Time Object Detection 1、核心思想 将整张图片作为网络的输入&#xff0c;直接在输出层对 BBox 的位置和类别进行回归。 Resize image&#xff1a;将输入图片resize到448x448。Run ConvNet&#xff1a;使用CNN提取特征&#xff0c;FC层输出…

Python神经网络在基因组学中的应用

在基因组学研究中&#xff0c;神经网络作为一种强大的工具&#xff0c;被广泛应用于基因型和表型之间的关联分析。通过构建神经网络模型&#xff0c;我们可以更好地理解遗传变异是如何影响个体的性状表现。本文将详细介绍如何使用Python实现这一过程&#xff0c;利用已知群体的…

一文读懂大语言模型:基础概念篇

在当今AI时代&#xff0c;大语言模型正以前所未有的速度重塑我们的世界。作为NLP领域的明星&#xff0c;它们不仅理解语言&#xff0c;更创造语言&#xff0c;开启了智能交互的新纪元。 本文将介绍着重介绍大模型的概念&#xff0c;帮助大家简单了解其技术原理、发展历程&#…

C++学习笔记----5、重用之设计(二)---- 为最优化重用结构化你的代码(1)

一定要在所有的层面在设计的一开始就考虑重用&#xff0c;也就是说&#xff0c;从一个独立的函数到一个类&#xff0c;直到整个库与框架。我们以后就把这些统称为部件。下面的策略会帮助你正确地组织代码。记住所有的这些策略关注的是你的代码的的通用目的。设计可重用代码第二…

2024上半年营业收入同比增长17%,一图看懂亚信安全2024半年报

2024上半年营业收入同比增长17%&#xff0c;贯彻健康经营&#xff0c;业绩企稳向好&#xff01; 云网安筑基 AI智绘未来 | 一图看懂亚信安全2024半年报

SAP DRC 交易与报表合规化

交易与报表合规化- SAP Document and Reporting Compliance(DRC) SAP DRC通过嵌入到端到端业务流程的源系统中的国家和地区特定内容&#xff0c;保证全球处理合规&#xff1b;利用统一的解决方案管理全球电子发票和法定报表&#xff0c;从而优化合规性&#xff0c;提高效率并提…

无线麦克风什么牌子最好,无线直播麦克风十大名牌推荐

​在数字化飞速发展的今天&#xff0c;无线领夹麦克风已经成为自媒体创作者、直播主播和专业录音师的重要工具。它们不仅小巧便携、操作简便&#xff0c;还具有出色的录音质量&#xff0c;极大地提升了音频录制的效率和质量。无论是户外探险的Vlog拍摄&#xff0c;还是室内直播…

基于Linux系统和ncurses库的贪吃蛇小游戏

目录 前言 一、地图&#xff0c;蛇身&#xff0c;食物设计 二、蛇和食物的初始化 食物 蛇 三、添加和删除蛇身节点 四、main函数和蛇运行方向线程 五、地图刷新线程 最终源码 前言 ncurses库是什么我并没有深入了解&#xff0c;本文的重点也不是ncurses的使用&#xff…

BUUCTF PWN wp--pwn1_sctf_2016

第一步 checksec&#xff0c;并检查该题的保护机制&#xff0c;32位 Arch: i386-32-little 这表示程序的架构是32位的i386&#xff08;即x86&#xff09;&#xff0c;并且使用小端序&#xff08;little-endian&#xff09;存储方式。这意味着程序是为32位系统设计的。RELRO: Pa…