前言
最近的一项工作内容是将旧系统较为原生的框架升级到Spring boot 2.7.x
,模块的变化见下图。
因为旧系统的代码最近一次更新在十年前,而且当时水平有限,所以难免有些历史遗留问题。
就比如前一阵遇到的特殊bug:画面中的下拉选,选项总是会重复,旧系统是没有问题的。经过不断的debug才定位到是Service
中的私有ArrayList<T>
类型的变量每次进行查询操作都会add一次,即使关闭网页,再登录系统也会保留。
而完成这一切仅仅是因为@Autowried
注解的功能,之前的原始的**Bean是不具备一直存在的。
一. 认识自动注入
从Java SE到Java EE,我们的方式变得固定起来,遵循着MVC模式。起初是最原始的Servlet,但是每一次的手写映射方法以及不能复用的对象使的编程变得极为不痛快。
代码语言:java复制// 一段映射逻辑
if (cmd = "a") {
// a 方法
} else if (cmd = "b"){
// b 方法
} else ...
等经历过这个过程后,开始学习陪伴很久的框架Spring
,正如名字一样,他给开发者带来了赛博春天。伴着Spring一起来的就是我那个时代的天团SSM(Spring,Spring MVC,MyBatis)。
虽说省去了繁多的配置和映射等问题,但还是存在较多的,默认的配置。导致实战开发的时候,都是将一堆配置复制过来,改一点,生动的称之为“祖传配置文件”。
后来到了Spring boot
阶段,初始化工程的任务一下子减轻了很多。甚至有些时候开箱即用,几分钟内就可以完成一个简单的API开发。这样简化的一切归功于内部的各种**AutoConfig
。
其中就包含我们今天讨论的注解@Autowired
。
1.1 怎样注入
Spring 为了应对不同技术场景,支持三种注入方式,继承自简单接口BeanDefinitionReader
BeanDefinitionReader // 简单解析接口
├─GroovyBeanDefinitionReader // 全局注解解析
├─PropertiesBeanDefinitionReader // properties、yaml等键值对配置文件解析
└─XmlBeanDefinitionReader // xml文件解析
1.2 Autowired
学习之初,在单体环境中,我们用来代替new。将需要使用的对象定义且并不实例化。
比如Service的实现类里面:
代码语言:java复制@Autowired
private UserMapper userMapper;
这样加上注解以后,Spring便会在启动的时候,自动扫描并注册到容器里。
以下源码解释
代码语言:java复制// 作用目标:构造器、方法、形参、属性/字段、注解接口
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
* 声明是否是必要的。默认为true
*/
boolean required() default true;
}
作用目标没什么可说的,这里解释一下参数。
默认设置为true
,如果在启动过程中找不到实例,则抛出如下异常
Field xxxService in xxController required a bean of type 'xxxService' that could not be found.
如果想在这时候启动,可以设置为false
@Autowired(required=false)
当然,这并不推荐。如果你想找出错误,请尝试:
- 找到该类,观察有没有注入到容器的注解
- 看一下配置信息,检查有无包扫描位置相关
- 注意单词拼写
二. 注入流程
- 扫描
Spring在启动时会扫描所有
Bean
的定义,寻找使用了@Autowired
注解的目标对象。这里的关键类就是ClassPathBeanDefinitionScanner
。 - 匹配
接下来需要给对应的对象实例化了,用到了
AutowireCapableBeanFactory
的resolveDependency
。如果我们编码有误,会出现,多个匹配一个的情况。如果是Autowired,则会按照名字排序。这时可以考虑下其他的按名字注入的注解,例如@Primary
,@Qualifier
等。当然原生的@Resource
也可以。事实上,某些IDE中是推荐后者的。 - 注入 到这一步,常考虑的是单例与多例。而Autowired并不保证单例或是多例,这个主动权交给Bean的定义方。用注解的方式可以使用如下,默认单例。
@Scope(value = "prototype")
- 检查 如果没有找到实例,则会抛出异常。由参数控制。
解释前言的问题
因为没有额外的任何配置,这个单例的List
是会一直存在的,所以每次点击会添加一次数据。在前期流量不大的情况下,可以使用此特性记录访问次数。