一、Spring是什么?

Spring 是一款开源的轻量级 Java 开发框架 目的:提高开发效率、提高系统可维护性。

Spring目前有如下模块

Spring相关面试题_后端

了解下各个模块的功能

  • 1、Spring Test
    提供单元测试和集成测试功能、提供对JUnit、TestNG、Mockito、PowerMock等常用的测试框架的支持。
  • 2、Core Container
    Spring的核心模块、提供 IoC 依赖注入功能的支持。Spring 其他功能基本都需要依赖这个模块。

spring-core :核心工具类。
spring-beans(重点) :提供对Spring bean 的创建、配置、管理等功能的支持。
spring-context :提供对国际化、事件传播、资源加载等功能的支持。
spring-expression :提供对表达式语言(Spring Expression Language) SpEL 的支持。

  • 3、AOP (重点)
    spring-aspects :集成AspectJ。
    spring-aop :提供面向切面编程的实现。
    spring-instrument :提供了为 JVM 添加代理(agent)的功能。
  • 4、Messaging
    spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。
  • 5、Data Access/Integration
    spring-jdbc :提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库。
    spring-tx :提供对事务的支持。
    spring-orm : 提供对 Hibernate、JPA 、iBatis、MyBatis 等 ORM 框架的支持。
    spring-oxm :提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
    spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。
  • 6、Spring Web
    spring-web :对 Web 功能的实现提供一些最基础的支持。
    spring-webmvc : 提供对 Spring MVC 的实现。
    spring-websocket : 提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
    spring-webflux :提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。

二、对Spring IoC的理解?

IoC 直译为控制反转 是一种设计思想 Spring的IoC 目的是解决 Java开发领域对象的创建以及管理的问题
控制:指对象的创建权
反转:指将对象的创建权交给Spring容器、让容器代理程序员来创建和管理对象

好处: 降低开发难度、降低对象之间耦合度、增加项目的可维护性,开发人员只需要配置好配置文件或者使用注解即可快速获取对象,不再需要手动使用new关键字创建对象。

三、IoC 和 DI的区别?

IoC是一种设计思想,是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。
依赖注入(Dependency Injection,简称 DI)是IoC最常见的实现方式

四、对Spring AOP的理解?

AOP:Aspect oriented programming 直译 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。

  • 切 :指的是横切逻辑(在多个纵向(顺序) 流程中出现的相同子流程代码,我们称之为横切逻辑代码),
    原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
  • 面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念

横切逻辑代码存在的问题:
代码重复问题 ,横切逻辑代码和业务代码混杂在一起,代码臃肿,难维护。
AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离。

AOP的目的:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

五、Spring AOP 的实现?

Spring AOP 是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,
对于没有实现接口的对象, 使用 Cglib 生成一个被代理对象的子类来作为代理

六、什么是静态代理、动态代理,二者区别? ​​参考​​

代理指的是代理模式,就是在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。

静态代理

静态代理也就是在程序运行前就已经存在代理类的字节码文件,即代理类需要在程序运行之前创建好。
优点:能够在不改变被代理类代码的前提下 增强被代理类的功能
缺点:需要为每个被代理对象 创建对应的代理类,被代理类的方法修改后 代理类方法也要相应修改,可维护性差。且被代理类必须实现接口。

代理类 = 与被代理类实现相同接口 + 增强代码 + 目标实现类

静态代理实现步骤:
①、写一个代理类,让它与目标类实现同一个接口 例如下图中的AProxy

Spring相关面试题_动态代理_02

②、代理类里面维护一个目标实现类实例,调用代理类的方法时传入目标类的实例,调用代理类方法时使用目标类实例调用目标类原方法,
只不过在前后加了一些其他逻辑代码。也就是说后面客户端不需要直接调用目标实现类,只需要调用代理类即可,这样就间接调用了对应方法。

Spring相关面试题_后端_03

③、在所有 new 目标类的地方都替换为 new 代理类,并将目标类作为构造方法参数传入;所有使用目标类调用的地方全部都替换为代理类调用。

静态代理的代码示例:

public class Test {

public static void main(String[] args) {

A a = new A();
a.eat();

// 使用BProxy代理A类 在不改变A类代码的前提下实现eat方法的增强
System.out.println("====使用BProxy代理A类 在不改变A类代码的前提下实现eat方法的增强");
BProxy bProxy = new BProxy(a);
bProxy.eat();
}


}


// 目标接口AInterface
interface AInterface {
// 吃饭
void eat();
}

// 目标类A
class A implements AInterface {

@Override
public void eat() {
// 吃饭方法的具体实现
System.out.println("开始吃饭");
}
}


// 代理类B
class BProxy implements AInterface {
// 定义目标类对象
private A a;

public BProxy(A a) {
// 通过构造方法初始化目标对象
this.a = a;
}

@Override
public void eat() {
// 增强吃饭方法
System.out.println("吃饭前洗手");
// 调用目标类的吃饭方法
a.eat();
System.out.println("吃饭后运动");
}
}

动态代理

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。
可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

通过代理类 Class 对象就可以生成代理对象 , Class对象包含了一个类的所有信息,如:构造方法、成员方法、成员属性等。
如果我们不写代理类,似乎无法获得代理类 Class 对象,但稍稍动一动脑:代理类和目标类实现的是同一组接口,是不是可以通过接口间接获得代理类 Class 对象。
代理类和目标类实现了同一组接口,这就说明他们大体结构都是一致的,这样我们对代理对象的操作都可以转移到目标对象身上,代理对象只需要专注于增强代码的实现。

涉及到一个对象在JVM中的创建过程 参考

动态代理相对于静态代理最大的区别就是不需要事先写好代理类,一般在程序的运行过程中动态产生代理类对象。

七、动态代理的实现方式 JDK、Cglib、AspectJ ?

JDK动态代理

JDK 原生提供了动态代理的实现,主要是通过java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler这两个类配合使用。
Proxy类有个静态方法,传入类加载器和一组接口就可以返回代理 Class 对象。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
这个方法的作用简单来说就是,会将你传入一组接口类的结构信息"拷贝"到一个新的 Class 对象中,新的 Class对象带有构造器是可以创建对象的。
一句话总结:Proxy.getProxyClass() 这个静态方法的本质是以 Class 造 Class。
拿到了 Class 对象,就可以使用反射创建实例对象了

Spring相关面试题_后端_04

总结一下流程:
(1)通过 Proxy.getProxyClass() 方法获取代理类 Class 对象;
(2)通过反射 aClazz.getConstructor() 获取构造器对象;
(3)定义InvocationHandler类并实例化,当然也可以直接使用匿名内部类;
(4)通过反射 constructor.newInstance() 创建代理类对象;
(5)调用代理方法;

​​JDK动态代理为什么只能代理有接口的类​​

代码示例:

import java.lang.reflect.*;

public class Test1 {

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

Class<?> aClazz = Proxy.getProxyClass(AInterface.class.getClassLoader(),AInterface.class);
Constructor<?> constructor = aClazz.getConstructor(InvocationHandler.class);
// 下面是Lambda表达式的简化写法 类似匿名内部类写法
AInterface aProxy =(AInterface)constructor.newInstance((InvocationHandler) (proxy, method, args1) ->
{
System.out.println("吃饭前洗手");
method.invoke(new A(),args1);
System.out.println("吃饭后运动");
return null;
});
aProxy.eat();


// 或者直接使用 Proxy.newProxyInstance方法创建 代理对象
AInterface aProxy1 = (AInterface)Proxy.newProxyInstance(AInterface.class.getClassLoader(), A.class.getInterfaces(), (proxy, method, args1) ->
{
System.out.println("吃饭前洗手");
method.invoke(new A(), args1);
System.out.println("吃饭后运动");
return null;
});
aProxy1.eat();

}
}


// 目标接口AInterface
interface AInterface {
// 吃饭
void eat();
}

// 目标类A
class A implements AInterface {

@Override
public void eat() {
// 吃饭方法的具体实现
System.out.println("开始吃饭");
}
}

Cglib 动态代理

利用ASM框架,将目标对象类生成的class文件加载进来,通过修改其字节码生成代理子类

总结一下流程:
1、引入 CGLIB 依赖
2、定义一个被代理类
3、定义一个拦截器并实现接口 MethodInterceptor 重写intercept方法
4、代理工厂类
5、通过代理对象调用方法

注意引入cglib的同时还要引入asm

Spring相关面试题_后端_05

代码示例:

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibTest {

public static void main(String[] args) {
A a = (A)ProxyFactory.createProxy(new AProxy(),A.class);
a.eat();
}

}

// 目标类A
class A {
public void eat() {
// 吃饭方法的具体实现
System.out.println("开始吃饭");
}
}

// 代理工厂类
class ProxyFactory{

// 创建代理对象方法
public static Object createProxy(Callback callback, Class<?> clazz){
// 构造增强器
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(clazz);
// 设置回调
enhancer.setCallback(callback);
// 创建代理对象
Object proxy = enhancer.create();
return proxy;
}
}

// 拦截器
class AProxy implements MethodInterceptor{
// 增强方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("饭前洗手");
proxy.invokeSuper(obj,args);
System.out.println("饭后运动");
return null;
}
}

AspectJ动态代理

AspectJ动态代理的使用参考

JDK动态代理和Cglib动态代理的区别

JDK 动态代理基于接口,CGLIB 动态代理基于类。因为 JDK 动态代理生成的代理类需要继承 java.lang.reflect.Proxy,所以,只能基于接口;
CGLIB 动态代理是根据目标类创建目标类的子类,所以,目标类类不能被 final 修饰
JDK 和 CGLIB 动态代理都是在运行期生成字节码。而 JDK 是直接写 Class 字节码;而 CGLIB 使用 ASM 框架写 Class 字节码
(不鼓励直接使用ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉)
JDK 通过反射调用方法,CGLIB 通过 FastClass 机制直接调用方法。所以,CGLIB 执行的效率较高
JDK 动态代理是利用反射机制生成一个实现代理接口的类(这个类看不见摸不着,在 jvm 内存中有这个类),在调用具体方法前调用 InvokeHandler来处理。核心是实现 InvocationHandler接口,使用 invoke()方法进行面向切面的处理,调用相应的通知;CGLIB 动态代理是利用 asm 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。核心是实现 MethodInterceptor 接口,使用 intercept() 方法进行面向切面的处理,调用相应的通知。

​​FastClass机制参考这篇文章​​

八、多个切面的执行顺序如何控制?

1、通常使用@Order 注解直接定义切面顺序

// 值越小优先级越高  如果不设置值 默认为 Integer.MAX_VALUE

@Order(1)
@Component
@Aspect
public class AAA {
void doSomething(){
}
}

2、实现Ordered 接口重写 getOrder 方法。

@Component
@Aspect
public class AAA implements Ordered{
void doSomething(){
}

@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}

九、Spring如何实现统一的异常处理?

推荐使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice + @ExceptionHandler 这两个注解。

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

// 处理自定义异常
@ExceptionHandler(AppException.class)
public ResponseEntity<?> handleAppException(BaseException ex) {
//......
}

// 处理 全部异常
@ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(Exception ex) {
//......
}
}

@ControllerAdvice 和 @RestControllerAdvice 的区别
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
即使用@RestControllerAdvice注解 方法自动返回json数据,每个方法无需再添加@ResponseBody注解
类似的注解还有 @Controller 和 @RestController

十、Spring用到了哪些常见的设计模式? (内容来自JavaGuide)

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 装饰器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

十一、Spring事务? (https://javaguide.cn/system-design/framework/spring/spring-transaction.html)

①、Spring管理事务的方式

编程式事务:在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用。
声明式事务: 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

②、Spring 事务中哪几种事务传播行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

正确的事务传播行为可能的值如下:
1.TransactionDefinition.PROPAGATION_REQUIRED (Spring事务默认的传播方式就是这个)
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。
如果当前方法存在事务,则被调用的方法加入该事务;如果当前方法没有事务,则被调用方法创建一个新的事务。

下面解释下@Transactional(propagation = Propagation.REQUIRED)传播机制可能出现的情况
情况一、方法A如果未开启事务 在方法A内调用方法B(加了注解@Transactional(propagation = Propagation.REQUIRED)) ,
此时方法B会开启自己的事务 ,如果方法A内又调用方法C(也加了注解@Transactional(propagation = Propagation.REQUIRED)) ,
此时方法C也会开启自己的事务 并且方法B和方法C开启的事务相互独立,互不影响。

如果A方法出现异常, B、C方法依然能够正常提交。
B方法出现异常,B方法回滚,C方法正常提交,A方法正常提交。
同理C方法出现异常,C方法回滚、B方法正常提交,A方法正常提交。

情况二、方法A如果开启事务(加了注解@Transactional(propagation = Propagation.REQUIRED)) ,
在方法A内调用方法B(加了注解@Transactional(propagation = Propagation.REQUIRED)) ,
方法A内又调用方法C(也加了注解@Transactional(propagation = Propagation.REQUIRED)) ,
此时方法A、B、C 都开启了事务 ( 都使用了 注解@Transactional(propagation = Propagation.REQUIRED) )
这种情况B、C 不会开启新的事务 会直接运行在 A方法的事务中

如果有任何一个方法异常三个方法都回滚,(前提异常正常抛出)
如果A方法异常且捕获异常不抛出,则方法A、B、C都正常提交

情况二有个特殊情况:
如果方法B出现异常,在方法A中捕获方法B的异常且不抛出,此时A、B、C方法均回滚
会报这个错Transaction rolled back because it has been marked as rollback-only
因为在情况二中A、B两个方法是用同一个事务,在B方法执行的时候出现异常这个事务就标记为rollback-only ,
然后A方法捕获异常后没有继续抛出而是继续使用该事务,然后又执行事务提交的操作,所以最后会抛异常。然后整个A、B、C方法均回滚。

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,
且开启的事务相互独立,互不干扰。 如果外部方法有事务则执行到内部方法时外部方法事务被挂起,内部方法会新建事务,
直至该内部方法执行结束,恢复外部方法事务执行。两者之间事务存在隔离性。

情况一、
方法A未开启事务、方法B开启事务(@Transactional(propagation = Propagation.REQUIRES_NEW))
方法B出现异常 会回滚

情况二、
方法A开启事务、方法B开启事务(@Transactional(propagation = Propagation.REQUIRES_NEW))
方法A出现异常(在方法B执行之后)、方法B正常提交
方法B出现异常,方法A回滚、方法B回滚

情况二也有个特殊情况:
方法A开启事务、方法B开启事务(@Transactional(propagation = Propagation.REQUIRES_NEW))
方法B出现异常,方法A中捕获方法B的异常不抛出
此时方法B回滚、方法A正常提交

因为方法B传播机制是REQUIRES_NEW 即使A开启了事务 方法B也会开启一个新的事务
且执行到方法B时 方法A的事务被挂起 方法B抛异常 回滚方法B
此时再回到A事务 由于方法B抛出的异常在A方法中被捕获未抛出 所以方法A正常提交

3.TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

NESTED适用场景:
A方法调用B方法,B方法执行的是插入或者更新数据库操作,继续执行A方法需要拿到B方法插入或者更新到数据库的数据,
且后续如果A方法继续执行出现异常 整个A、B方法都回滚的情况。

需要注意的点:
注意每层事务可以通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 独立设置是否回滚。
这种方法设置的回滚,当前层的回滚不会影响上层的回滚,但上层的回滚会影响下层的回滚。(这点对于NESTED和REQUIRES_NEW是一致的)
如果是通过抛出异常导致的回滚,这个会触发整个事务的回滚(Sping的事务处理中异常的回滚处理和手动回滚走的不同的处理逻辑)。
如果下层回滚,最上层不回滚,则最终事务仍是作为正常提交成功的事务,仍会触发事务提交成功后的事件,并不会触发事务回滚的事件。
即最终事务的状态由最顶层的事务决定。

4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
这个使用的很少。即PROPAGATION_MANDATORY方法 必须在事务中运行。

这个没啥特殊情况,很容易理解。

5.TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

这个也没啥特殊情况,很容易理解。

6.TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。

这个也没啥特殊情况,很容易理解。

7.TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。

这个也没啥特殊情况,很容易理解。

4、5、6、7很少用 重点理解1、2、3

④、哪些情况会导致Spring事务失效?

1.数据库引擎不支持事务(例如MySQL的MyISAM引擎)
2.类没有被 Spring 管理
3.方法不是 public
4.自身调用问题,方法A未开启事务,直接使用方法名调用本类中的方法B,B开启了事务 方法B出现异常 不会回滚
(这个比较有意思 因为自身调用实际上没有使用Spring创建的代理对象调用事务方法 所以事务自然就不生效)
自身调用问题让事务生效的方式:
方式一、自己注入自己 在本类中注入自己类的实例 使用注入的实例调用自身的方法 而不是默认的this, 或者把方法挪走放到别的Service里面
方式二、启动类上面加注解@EnableAspectJAutoProxy(exposeProxy = true),然后获取代理对象调用自身方法
例如:在AService类中获取Spring代理对象 调用自身的methodA方法 ((AService)AopContext.currentProxy()).methodA();

5.数据源没有配置事务管理器
6.事务传播机制设置不支持事务(例如PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED)
7.异常被吃了
8.异常类型错误(一般都设置 rollbackFor = Exception.class)

⑤、Spring 事务中的隔离级别有哪几种?

TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别
Oracle 默认采用的 READ_COMMITTED 隔离级别.

TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,
可能会导致脏读、幻读或不可重复读

TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,
可以阻止脏读和不可重复读,但幻读仍有可能发生。

TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,
这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、 不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

@Transactional(rollbackFor = Exception.class)
Exception 分为运行时异常 RuntimeException 和非运行时异常。
当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,
同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

在 @Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,
加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

例如 方法A(只加了@Transactional) 当方法A抛出了 IOException的时候 方法A并不会回滚 因为IOException不属于RuntimeException

十二、对Spring Bean的理解?

Spring Bean 代指的就是那些被 IoC 容器所管理的对象。

将一个类声明为 Bean 的注解有哪些?

@Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
@Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
@Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

@Component 和 @Bean 的区别是什么?

@Component 注解作用于类,而@Bean注解作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中
(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。

@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。
比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

注入 Bean 的注解有哪些?

一般使用下面两个注解:
@Autowired(来自org.springframework.bean.factory Spring 2.5+)
@Resource (来自 javax.annotation Java JSR-250)

@Autowired 和 @Resource 的区别?
@Autowired是Spring内置注解, 默认的注入方式为byType(根据类型进行匹配),
也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。

当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,
默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。
例如:
AInterface接口 实现类有 A1 , A2

@Autowired
AInterface a; //会报错 因为根据类型AInterface 有两个实现类A1 , A2 并且采用变量的名称a (首字母转大写后) 找不到具体的实现类A

@Autowired
AInterface a1; //正确 采用默认的名称a1 找到具体的实现类A1 注入A1的实例

@Autowired
@Qualifier(value = “a1”)
AInterface a; //正确 采用指定的名称a1 找到具体的实现类A1 注入A1的实例

@Resource属于 JDK 提供的注解
默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType(如果有多个实现类 那么仅通过byType就会报错)。

例如:
AInterface接口 实现类有 A1 , A2

@Resource
AInterface a; //会报错 因为默认采用变量的名称a (首字母转大写后) 找不到具体的实现类A,通过byType 有两个A1,A2也会报错

@Resource
AInterface a1; //正确 采用默认的名称a1 找到具体的实现类A1 注入A1的实例

@Resource(value = “a1”)
AInterface a; //正确 采用指定的名称a1 找到具体的实现类A1 注入A1的实例

推荐使用@Resource方式

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。

配置方式 推荐使用注解方式:
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

单例 Bean 的线程安全问题了解吗?

无状态(没有实例变量)的单例Bean (比如 Dao、Service),是线程安全的。
如果单例Bean中定义了可变参数的成员变量 则多线程情况下操作同一个对象可能出现资源竞争

如果需要自定义变量 同时存在多线程并发情况,则建议在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中

十三、Bean 的生命周期了解么?

Bean 容器找到配置文件中 Spring Bean 的定义。
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
如果涉及到一些属性值 利用 set()方法设置一些属性值。
如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

图示如下:

Spring相关面试题_java_06