【JavaGuide面试总结】Spring篇·上
- 1.谈谈自己对于 Spring IoC 的了解
- 2.什么是 Spring Bean?
- 3.将一个类声明为 Bean 的注解有哪些?
- 4.注入 Bean 的注解有哪些?
- 5.@Component 和 @Bean 的区别是什么?
- 6.@Autowired 和 @Resource 的区别是什么?
- 7.Bean 的作用域有哪些?
- 8.单例 Bean 的线程安全问题了解吗?
- 9.Bean 的生命周期了解么?
1.谈谈自己对于 Spring IoC 的了解
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。
为什么叫控制反转?
- 控制 :指的是对象创建(实例化、管理)的权力
- 反转 :控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象🕊️
2.什么是 Spring Bean?
Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
例如:如下是一个bean实例:
<!-- 使用Spring来创建对象,Spring中被创建的对象都被称为Bean -->
<bean id="user" class="com.klza.pojo.User">
<property name="id" value="1"/>
<property name="username" value="dahezhiquan"/>
<property name="password" value="admin123"/>
</bean>
3.将一个类声明为 Bean 的注解有哪些?
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
4.注入 Bean 的注解有哪些?
Spring 内置的 @Autowired
以及 JDK 内置的 @Resource
和 @Inject
都可以用于注入 Bean。
Annotaion | Package | Source |
---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-330 |
5.@Component 和 @Bean 的区别是什么?
@Component
注解作用于类,而@Bean
注解作用于方法(使用@Configuration
注解标注的注解配置类中的方法)
例如:
@Configuration
public class DaheConfig {
@Bean
public Xiaoqian getXiaoqian() {
return new Xiaoqian();
}
}
-
@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。 -
@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现(我们无法修改第三方类库的代码,为它加上@Component
注解)
6.@Autowired 和 @Resource 的区别是什么?
Autowired
属于 Spring 内置的注解,默认的注入方式为byType
(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)
这会有什么问题呢? 当一个接口存在多个实现类的话,byType
这种方式就无法正确注入对象了(我们只能注入接口,而不能直接注入接口的实现类),因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式需要变为 byName
(根据名称进行匹配),这个名称通常就是类名(首字母小写)
举个例子,HelloService
接口有两个实现类: HelloService01Impl
和 HelloService02Impl
,且它们都已经被 Spring 容器所管理。
那么这种方式就会报错,因为Spring无法确定到底要注入那个实现类:
@Autowired
private HelloService helloService ;
正确的解决方法是指定具体的实现类:
@Autowired
private HelloService helloService01Impl;
或者通过@Qualifier
注解指定注入的实现类:
@Qualifier("helloService02Impl")
@Autowired
private HelloService helloService;
我们还是建议通过
@Qualifier
注解来显式指定名称而不是依赖变量的名称🦢
@Resource
属于 JDK 提供的注解,默认注入方式为 byName
。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType
。
@Resource
有两个比较重要且日常开发常用的属性:name
(名称)、type
(类型)。
如果仅指定 name
属性则注入方式为byName
,如果仅指定type
属性则注入方式为byType
,如果同时指定name
和type
属性(不建议这么做)则注入方式为byType
+byName
。
报错,byName 和 byType 都无法匹配到 bean:
@Resource
private HelloService helloService;
正确注入的方法:
@Resource
private HelloService helloService01Impl;
正确注入,推荐的写法:
@Resource(name = "helloService01Impl")
private HelloService helloService;
简单总结一下:
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。
7.Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()
两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置 bean 的作用域呢?
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
如果你使组件扫描来发现和声明 Bean,那么你可以在 Bean 的类上使用 @Scope
注解:
@Service
@Scope(value = ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class HelloService01Impl implements HelloService{
@Override
public void hey() {
System.out.println("我是一号我是一号");
}
}
8.单例 Bean 的线程安全问题了解吗?
单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。
9.Bean 的生命周期了解么?
大体来看,一个Bean的生命周期实例化 Instantiation,属性赋值 Populate,初始化 Initialization,销毁 Destruction四个阶段🦚
我们讲到了Bean容器四个阶段,会有一些容器级的方法,进行前置和后置的处理,比如InstantiationAwareBeanPostProcessor
、BeanPostProcessor
接口方法。这些方法独立于Bean之外,并且会注册到Spring容器中,在Spring容器创建Bean的时候,进行一些处理。
Bean的生命周期图示:
- 实例化:第 1 步,实例化一个 Bean 对象
- 属性赋值:第 2 步,为 Bean 设置相关属性和依赖
- 初始化:初始化的阶段的步骤比较多,5、6步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean就可以被使用了
- 销毁:第8~10步,第8步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁Bean 时再执行相应的方法
用文字描述一下这个过程:
- Bean容器在配置文件中找到Person Bean的定义,这个可以说是妈妈怀上了。
- Bean容器使用Java 反射API创建Bean的实例,孩子出生了。
- Person声明了属性,它们会被设置,相当于注册身份证号和姓名。如果属性本身是Bean,则将对其进行解析和设置(依赖注入)
- Person类实现了
BeanNameAware
接口,通过传递Bean的名称来调用setBeanName()
方法,相当于起个学名。 - Person类实现了
BeanFactoryAware
接口,通过传递BeanFactory对象的实例来调用setBeanFactory()
方法,就像是选了一个学校。 - PersonBean实现了
BeanPostProcessor
接口,在初始化之前调用用postProcessBeforeInitialization()
方法,相当于入学报名。 - PersonBean类实现了
InitializingBean
接口,在设置了配置文件中定义的所有Bean属性后,调用afterPropertiesSet()
方法,就像是入学登记。 - 配置文件中的Bean定义包含
init-method
属性,该属性的值将解析为Person类中的方法名称,初始化的时候会调用这个方法,成长不是走个流程,还需要自己不断努力。 - Bean Factory对象如果附加了Bean 后置处理器,就会调用
postProcessAfterInitialization()
方法,毕业了,总得拿个证。 - Person类实现了
DisposableBean
接口,则当Application不再需要Bean引用时,将调用destroy()
方法,简单说,就是人挂了。 - 配置文件中的Person Bean定义包含
destroy-method
属性,所以会调用Person类中的相应方法定义,相当于选好地儿,埋了。