在Spring中想要更简单的存储和读取对象的核心是使用注解,也就是我们接下来要学的Spring中相关注解。
之前我们存储Bean时,需要在自己添加的配置文件中添加一行bean才行:
而现在我们只需要一个注解就可以替代之前要写的一行配置的繁琐了。
目录
1.前置工作:配置扫描路径
对于Spring来说,如果需要扫描所有的项目路径,对于性能影响非常大。所以我们配置一个要储存对象的扫描包路径。只有被配置的包下的所有类,并且添加了注释,才能够正确的识别并保存到Spring中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.spring.demo"></content:component-scan>
</beans>
base-package就是扫描路径。需要保证添加的注释都要在这个路径下面。
2.添加注释
想要将对象储存在Spring中,有两种注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean
顾名思义,类注解就是注解在类上,方法注解同理。
2.1 类注解
当把UserController用Controller注释后,通过之前读取对象的方式来获取到对象。
可以发现,确实是和之前把具体的id和class存到配置文件一样的效果。而通过注释的方式更加方便简单。
并且,其他的另外四个注解的效果都是一样的,都可以通过读取对象的方式拿到Spring中存取的对象。
那么为什么要有这么多类注解?
最主要的原因方便阅读,望文生义,可以直接了解当前类的用途。
- @Controller:业务逻辑层
- @Servie:服务层
- @Repository:持久层
- @Configuration:配置层
类似于之前学习Servlet时的service、DAO、API这样的分层。
并且@Controller、@Service、@Repository、@Configuration都是@Component的子类。
2.2 Bean 命名规则
通常我们使用的bean都是大驼峰命名,而读取的时候首字母小写就可以获取到bean:
但是如果首字母和第二个字母都是大写时,就不能正常获取到bean了:
具体的Spring关于bean的命名规则究竟是什么样的呢?
在IDEA中搜索AnnotationBeanNameGenerator可以找到buildDefaultBeanName方法:
它使用的是Introspector中的decapitalize方法,这是JDK中的方法,源码:
这个就是具体的命名规则了:如果第一个字母和第二个字母都为大写,Bean为原类名。如果是大驼峰,就把第一个字母小写。
2.3 方法注解
类注解是添加到某个类上的,而方法注解是放到某个方法上的。
对于一个User类,我们添加一个获取user这样一个方法,并且添加类注解:
但是并不能得到我们想要的结果,这是为什么?
方法注解要配合类注解使用。这是Spring为了性能而规定的。
通过这样的方式就可以拿到user1。
同时还可以给Bean重命名,不过重命名后通过方法名就拿不到对象了,只能通过重命名的来拿到对象。
获取Bean对象(对象装配)
获取bean对象也叫做对象装配,就是把某个对象取出来放到某个类中,也叫做对象注入。
有三种方式:属性注入、Setter注入、构造方法注入。通过@Autowired来注入。@Autowired来自于Spring。
属性注入
假设我们现在有两个类,需要把Service类注入到Controller类中。
通过@Autowired就可以在不new对象的情况下,直接获得注入的对象了。
但是有很多缺点:
- 功能性问题:无法注入一个不可变的对象(final 修饰的对象);
- 通用性问题:只能适应于 IoC 容器;
- 设计原则问题:更容易违背单一设计原则。
Setter注入
在Controller中需要获取到Service,那么就在Controller中设置一个Setter方法,再用Autowired注入,这样就可以拿到Service。
优缺点:
- 复合单一设计原则
- 不能注入final修饰的对象
- 注入的对象可能会被修改
构造方法注入
注入方法注入是直接在类的构造方法中实现注入,并且是Spring官方推荐使用的方式:
并且如果只有一个构造方法,那么@Autowired注释可以省略。
构造方法注入相比于前两种注入方法,它可以注入final修饰的对象,并且也只会执行一次,不会被修改。无论是IoC还是非IoC都是可以用的。
@Resource
@Resource是另一种注入关键字,来自于JDK。只能用于属性注入和Setter注入中。
相比于@Autowired来说,@Resource支持更多的参数设置,例如name设置,根据名称获取Bean。
很典型的例子:如果出现多个Bean,返回同一对象类型时会报错。
报错的原因是,出现了非唯一的Bean对象。
此时可以通过@Resource来定义name的值:
@Qualifier
解决同一个类型的Bean对象,除了通过@Resource定义name,还可以使用@Qualifier来定义:
Lombok
Lombok是一个非常好用的插件,能够简化代码。这是简化之前的:
使用Lombok简化后:
Lombok可以把繁琐的getter和setter方法简化成注释。
要使用Lombok,需要添加依赖和插件:
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
Spring Bean作用域
Spring容器在初始化一个Bean的实例时,同时会指定该实例的作用域。默认情况下,Bean的作用域单例作用域。
singleton 单例作用域
当一个bean的作用域为singleton,所有对bean的读取都是访问的同一个实例(对象)。
好处就是可以获得更好的性能,因为只有一份bean。
但是坏处就是如果不同的用户修改了,那么后来的用户拿到的值已经是前一个用户修改过的值。
prototype 原型作用域(多例作用域)
和singleton完全不同,可以看做每次读取bean对象都是对原型bean的一份拷贝。所有的读取的bean都是新的一份bean实例。
request 请求作用域
request适用于Spring MVC 和 Spring Web。
每次HTTP请求,都会创建一个Bean实例。
session 会话作用域
session适用于Spring MVC。
一个HtttpSession共享一个Bean实例。
application 全局作用域
application适用于Spring MVC。
一个http servlet context中共享一个bean。
websocket
网络长连接,只适用于Spring WebSocket项目。
配置方式
使用@Scope标签可以声明Bean的作用域,比如设置Bean的作用域:
两种方式都可以,一种是直接设置值,另一种使用的枚举设置。
Spring执行流程和生命周期
执行流程
第一步:启动容器
第二步:加载配置文件spring_config.xml,完成初始化
第三步:扫描路径底下的Spring注释:@Controller、@Service、@Component、@Repository,并且注册Bean对象到容器中。
第四步:获取Bean对象(装配Bean)
生命周期
生命周期指的就是一个对象从创建到销毁的整个过程。
Bean的生命周期分为五个部分:
1.实例化Bean,为Bean分配内存空间
2.设置属性,也就是Bean的注入和装配
3.Bean初始化,在这里实现了很多种方式,如Aware方法,初始化前置、初始化、初始化后置方法和自己指定的init-method方法。
4.使用Bean
5.销毁Bean
关于生命周期这里有一定难度,可以参考其他的文章~