Spring面试题
参考内容:
- JavaGuide - Spring常见问题总结
- JavaGuide - Spring事务总结
- JavaGuide - Spring中都用到了哪些设计模式?
- 9 种设计模式在 Spring 中的运用,一定要非常熟练!
- SpringBoot+Spring常用注解总结
- Spring IOC 容器源码分析
什么是Spring框架?(谈谈你对Spring的理解?)
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
数据访问 :事务,DAO支持,JDBC,ORM,编组XML。
Web支持 : Spring MVC和Spring WebFlux Web框架。
集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言 :Kotlin,Groovy,动态语言。
Spring跟SpringMVC的区别?
1、Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。 Spring也提供了很多基础功能(事务管理、持久化框架集成等等)
2、Spring MVC就是一个MVC模式的WEB开发框架,是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring。Spring是一个通用解决方案, 最大的用处就是通过Ioc/AOP解耦, 降低软件复杂性。
3、Spring可以说是一个管理bean的容器,也可以说是包括很多开源项目的总称,spring mvc是其中一个开源项目。
Spring流程?
什么是IOC?什么是AOP?
IOC
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
推荐阅读:https://www.zhihu.com/question/23277575/answer/169698662
Spring IoC的初始化过程:
IoC源码阅读
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制、异常处理等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
IOC注入的方式?
- 构造器依赖注入:构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数,每个参数代表注入的对象。
- Setter方法依赖注入:首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。
- 注解注入:基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入。
可以同时使用两种方式的依赖注入,最好的选择是使用构造器参数实现强制依赖注入,使用setter方法实现可选的依赖关系。
请解释 Spring Bean 的自动装配?
no:这是Spring框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在bean定义中用标签明确的设置依赖关系。
byName:该选项可以根据bean名称设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
byType:该选项可以根据bean类型设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的类型自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
constructor:造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
autodetect:该模式自动探测使用构造器自动装配或者byType自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。
如何开启基于注解的自动装配?说一下这些注解?
@Autowired(required = false)
当不能确定 Spring 容器中一定拥有某个类的 Bean 时,可以在需要自动注入该类 Bean 的地方可以使用 @Autowired(required = false),这等于告诉 Spring:在找不到匹配 Bean 时也不报错。
@Autowired
@Qualifier("office")
private Office office; //xml配置文件中存在office和office2两个Bean
在 Spring 容器中配置了两个类型为 Office 类型的 Bean,当对 Boss 的 office 成员变量进行自动注入时,Spring 容器将无法确定到底要用哪一个 Bean,因此异常发生了。Spring 允许我们通过 @Qualifier 注释指定注入 Bean 的名称,这样歧义就消除了。
@Qualifier("office")
中的 office 是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。
@Autowired 可以对成员变量、方法以及构造函数进行注释,而 @Qualifier 的标注对象是成员变量、方法入参、构造函数入参。
正是由于注释对象的不同,所以 Spring 不将 @Autowired 和 @Qualifier 统一成一个注释类。 @Autowired是根据类型注入,@Qualifier是根据名称注入
以上说的Bean 的名称是指bean中的id
构造方法注入和设值注入有什么区别?
- 在设值注入方法支持大部分的依赖注入,如果我们仅需要注入int、string和long型的变量,我们不要用设值的方法注入。对于基本类型,如果我们没有注入的话,可以为基本类型设置默认值。在构造方法注入不支持大部分的依赖注入,因为在调用构造方法中必须传入正确的构造参数,否则的话为报错。
- 设值注入不会重写构造方法的值。如果我们对同一个变量同时使用了构造方法注入又使用了设置方法注入的话,那么构造方法将不能覆盖由设值方法注入的值。很明显,因为构造方法尽在对象被创建时调用。
- 在使用设值注入时有可能还不能保证某种依赖是否已经被注入,也就是说这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注入则不允许生成依赖关系不完整的对象。
- 在设值注入时如果对象A和对象B互相依赖,在创建对象A时Spring会抛出ObjectCurrentlyInCreationException异常,因为在B对象被创建之前A对象是不能被创建的,反之亦然。所以Spring用设值注入的方法解决了循环依赖的问题,因对象的设值方法是在对象被创建之前被调用的。
1、构造器注入适用具有强依赖和不变性的依赖; 2、Setter注入适用于具有可选性和可变性的依赖注入; 3、Field注入,尽量少使用,如果需要则使用@Resource进行替代,以降低耦合性。
使用@Autowired注解自动装配的过程是怎样的?
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />
。 在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false
Spring AOP和AspectJ AOP有什么区别?
Spring AOP 基于动态代理实现,属于运行时增强。 AspectJ 则属于编译时增强,主要有3种方式:
- 编译时织入:指的是增强的代码和源代码我们都有,直接使用 AspectJ 编译器编译就行了,编译之后生成一个新的类,他也会作为一个正常的 Java 类装载到JVM。
- 编译后织入:指的是代码已经被编译成 class 文件或者已经打成 jar 包,这时候要增强的话,就是编译后织入,比如你依赖了第三方的类库,又想对他增强的话,就可以通过这种方式。
- 加载时织入:指的是在 JVM 加载类的时候进行织入。
总结下来的话,就是 Spring AOP 只能在运行时织入,不需要单独编译,性能相比 AspectJ 编译织入的方式慢,而 AspectJ 只支持编译前后和类加载时织入,性能更好,功能更加强大。
Spring AOP
基于动态代理方式实现;AspectJ
基于静态代理方式实现。Spring AOP
仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。
Spring AOP的实现原理?
实现AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行。
- 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring AOP 的实现原理:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice和回调目标对象的方法所组成, 并将该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但AOP代理中的方法与目标对象的方法存在差异,AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法。
Spring AOP使用动态代理技术在运行期织入增强代码。使用两种代理机制:
基于JDK的动态代理(JDK本身只提供接口的代理)、基于CGlib的动态代理。
JDK的动态代理:
- JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
- 其代理对象必须是某个接口的实现, 它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理.只能实现接口的类生成代理,而不能针对类
CGLib的动态代理:
- CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑.它运行期间生成的代理对象是目标类的扩展子类.所以无法通知final、private的方法,因为它们不能被覆写。是针对类实现代理,主要是为指定的类生成一个子类,覆盖其中方法。
- 在spring中默认情况下使用JDK动态代理实现AOP,如果proxy-target-class设置为true或者使用了优化策略那么会使用CGLIB来创建动态代理。
Spring AOP在这两种方式的实现上基本一样。以JDK代理为例,会使用JdkDynamicAopProxy来创建代理,在invoke()方法首先需要织入到当前类的增强器封装到拦截器链中,然后递归的调用这些拦截器完成功能的织入.最终返回代理对象。
AOP的常用术语及其含义?
面向切面编程(AOP):允许程序员模块化横向业务逻辑,或定义核心部分的功能,例如日志管理和事务管理。
切面(Aspect) :AOP的核心就是切面,它将多个类的通用行为封装为可重用的模块。该模块含有一组API提供 cross-cutting功能。例如,日志模块称为日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
通知(Advice):通知表示在方法执行前后需要执行的动作。实际上它是Spring AOP框架在程序执行过程中触发的一些代码。Spring切面可以执行一下五种类型的通知:
- before(前置通知):在一个方法之前执行的通知。
- after(最终通知):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出。
- after-returning(后置通知):在某连接点正常完成后执行的通知。
- after-throwing(异常通知):在方法抛出异常退出时执行的通知。
- around(环绕通知):在方法调用前后触发的通知。
连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。
切入点(Pointcut):切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
引入(Introduction):引入允许我们在已有的类上添加新的方法或属性。
目标对象(Target Object):被一个或者多个切面所通知的对象。它通常是一个代理对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
代理:代理是将通知应用到目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。有以下几种代理:
- BeanNameAutoProxyCreator:bean名称自动代理创建器DefaultAdvisorAutoProxyCreator:默认通知者自动代理创建器Metadata autoproxying:元数据自动代理
织入(Weaving):将切面和其他应用类型或对象连接起来创建一个通知对象的过程。织入可以在编译、加载或运行时完成。
项目中如何进行AOP配置?
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class AspectDemo {
// 访问下方的方法时会触发
@Pointcut("execution(* com.hpt.demo01.controller.Hello.*(..))")
public void test() {
}
@Before("test()")
public void doBefore() {
System.out.println("before!");
}
@After("test()")
public void doAfter() {
System.out.println("after!");
}
@AfterThrowing(pointcut="test()", throwing="exception")
public void doAfterThrow() {
System.out.println("Error!!!");
}
@AfterReturning("test()")
public void doAfterRe() {
System.out.println("AfterReturning!");
}
@Around("test()")
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("doAround111!");
joinPoint.proceed();
System.out.println("doAround222!");
}
}
访问指定方法,console结果如下:
doAround111!
before!
hello~~~~~~~~
AfterReturning!
after!
doAround222!
JDK动态代理和CGLIB代理有什么区别?
JDK 动态代理主要是针对类实现了某个接口,AOP 则会使用 JDK 动态代理。他基于反射的机制实现,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。 而如果某个类没有实现接口,AOP 则会使用 CGLIB 代理。他的底层原理是基于 asm 第三方框架,通过修改字节码生成成成一个子类,然后重写父类的方法,实现对代码的增强。
动态代理跟静态代理的区别?
静态代理:是由程序员创建或特定工具自动生成源代码,在对其进行编译。在程序运行之前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
反射的实现原理?
JAVA反射机制是在运行状态中: 对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
原理:Java在编译之后会生成一个class文件,反射通过字节码文件找到其类中的方法和属性等。
Spring自动注入类型?
@Autowired注解——由spring提供
@Qualifier (与@Autowired搭配按名称,如@Qualifier("iUserDao"))
@Resource注解——由JSR-250提供
@Inject注解——由JSR-330提供
BeanFactory和FactoryBean的区别?
都是用来创建对象的。
BeanFactory 是 Bean 的工厂, ApplicationContext 的父类,IOC 容器的核心,负责生产和管理 Bean 对象。
FactoryBean 是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作。
使用BeanFactory的时候必须遵循完整的创建过程,这个过程是由Spring来管理控制的。而使用FactoryBean 只需要调用getObject就可以返回具体的对象,整个对象的创建过程是由用户自己来控制的,更加灵活。
BeanFactory和ApplicationContext有什么区别?
ApplicationContext接口继承BeanFactory接口,Spring核心工厂是BeanFactory,BeanFactory采取延迟加载,第一次getBean时才会初始化Bean,ApplicationContext是会在加载配置文件时初始化Bean。
ApplicationContext是对BeanFactory扩展,它可以进行国际化处理、事件传递和bean自动装配以及各种不同应用层的Context实现开发中基本都在使用ApplicationContext,web项目使用WebApplicationContext,很少用到BeanFactory。
两者都是通过xml配置文件加载bean,ApplicationContext
和BeanFacotry
相比,提供了更多的扩展功能,但其主要区别在于后者是延迟加载,如果Bean的某一个属性没有注入,BeanFacotry
加载后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用ApplicationContext。 BeanFactroy
采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean
时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring
的配置问题。而ApplicationContext
则相反,它是在容器启动时,一次性创建了所有的Bean
。这样,在容器启动时,我们就可以发现Spring
中存在的配置错误。 BeanFactory
和ApplicationContext
都支持BeanPostProcessor
、BeanFactoryPostProcessor
的使用,但两者之间的区别是:BeanFactory
需要手动注册,而ApplicationContext
则是自动注册。
Bean是什么?
Bean 在代码层面上可以简单认为是 BeanDefinition 的实例,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中。 BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。
BeanDefinition是一个接口。在接口中,默认只提供 sington 和 prototype 两种范围。request, session, globalSession, application, websocket 这几种属于基于 web 的扩展。
Bean的加载过程?
- 实例化;
- 设置属性值;
- 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
- 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;
- 如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext
- 调用BeanPostProcessor的预先初始化方法;
- 调用InitializingBean的afterPropertiesSet()方法;
- 调用定制init-method方法;
- 调用BeanPostProcessor的后初始化方法;
Bean的生命周期?
SpringBean 生命周期简单概括为4个阶段:
- 实例化,创建一个Bean对象
- 填充属性,为属性赋值
- 初始化
- 如果实现了
xxxAware
接口,通过不同类型的Aware接口拿到Spring容器的资源 - 如果实现了BeanPostProcessor接口,则会回调该接口的
postProcessBeforeInitialzation
和postProcessAfterInitialization
方法 - 如果配置了
init-method
方法,则会执行init-method
配置的方法
- 销毁
- 容器关闭后,如果Bean实现了
DisposableBean
接口,则会回调该接口的destroy
方法 - 如果配置了
destroy-method
方法,则会执行destroy-method
配置的方法
另外一种描述:
下面的内容整理自:https://yemengying.com/2016/07/14/spring-bean-life-cycle/open in new window ,除了这篇文章,再推荐一篇很不错的文章 :https://www.cnblogs.com/zrtqsk/p/3735273.htmlopen in new window 。
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 属性,执行指定的方法。
Bean的作用范围?
singleton(默认):在Spring IOC容器中仅存在一个Bean实例,Bean以单实例的方式存在。
prototype: 每次请求都会创建一个新的 bean 实例。
request:每次HTTP请求都会创建一个新的Bean。该作用域仅适用于WebApplicationContext环境。
session:一个HTTP Session定义一个Bean。该作用域仅适用于WebApplicationContext环境。
global-Session:同一个全局HTTP Session定义一个Bean。该作用域同样仅适用于WebApplicationContext环境。Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。
Spring的单例实现原理
Spring框架对单例的支持是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap对象,如果配置文件中的配置信息不要求使用单例,Spring会采用新建实例的方式返回对象实例。
@Component 和 @Bean 的区别是什么?
@Component
注解作用于类,而@Bean
注解作用于方法。@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现。
@Bean
注解使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
下面这个例子是通过 @Component
无法实现的。
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
将一个类声明为 bean 的注解有哪些?
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
bean 标签中可以定义哪些属性?
Property | Introduce |
---|---|
class | 类的全限定名 |
name | 可指定 id、name(用逗号、分号、空格分隔) |
scope | 作用域 |
constructor arguments | 指定构造参数 |
properties | 设置属性的值 |
autowiring mode | no(默认值)、byName、byType、 constructor |
lazy-initialization mode | 是否懒加载(如果被非懒加载的bean依赖了那么其实也就不能懒加载了) |
initialization method | bean 属性设置完成后,会调用这个方法 |
destruction method | bean 销毁后的回调方法 |
除了上面举例出来的这些,还有 factory-bean
、factory-method
、lookup-method
、replaced-method
、meta
、qualifier
不同作用域的Bean相互依赖,有什么问题?如何处理?
Bean循环依赖问题?如何解决?
高频面试题:Spring 如何解决循环依赖?Spring循环依赖三级缓存是否可以减少为二级缓存?
首先,Spring 解决循环依赖有两个前提条件:
- 不全是构造器方式的循环依赖
- 必须是单例
基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。
第一级缓存:用来保存实例化、初始化都完成的对象
第二级缓存:用来保存实例化完成,但是未初始化完成的对象
第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
假设一个简单的循环依赖场景,A、B互相依赖。
A对象的创建过程:
- 创建对象A,实例化的时候把A对象工厂放入三级缓存
- A注入属性时,发现依赖B,转而去实例化B
- 同样创建对象B,注入属性时发现依赖A,依次从一级到三级缓存查询A,从三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入一级缓存。
- 接着继续创建A,顺利从一级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入一级缓存
- 最后,一级缓存中保存着实例化、初始化都完成的A、B对象
因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。
为什么要三级缓存?二级不行吗?
不可以,主要是为了生成代理对象。
因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。
使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。
假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象,BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。
Spring用到了哪些设计模式?
第一种:简单工厂
又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
第二种:工厂方法(Factory Method)
通常由应用程序直接使用new创建新的对象,为了将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。一般情况下,应用程序有自己的工厂对象来创建bean.如果将应用程序自己的工厂对象交给Spring管理,那么Spring管理的就不是普通的bean,而是工厂Bean。
第三种:单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。 spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的java对象。
第四种:适配器(Adapter)
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
第五种:包装器(Decorator)
在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢? 首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。 spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。
第六种:代理(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。 spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。
第七种:观察者(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
第八种:策略(Strategy)
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
第九种:模板方法(Template Method)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method
使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 Template Method
模式一般是需要继承的。这里想要探讨另一种对Template Method
的理解。spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。
Spring事务的实现方式有几种?
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
实现方式:
- 编程式事务管理,在代码中调用 commit()、rollback()等事务管理相关的方法
- 基于 TransactionProxyFactoryBean 的声明式事务管理
- 基于注解 @Transactional 的声明式事务管理
- 基于Aspectj AOP配置事务
Spring事务中的隔离级别有哪几种?
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。 |
ISOLATION_READ_COMMITTED | (Oracle 默认级别)允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。 |
ISOLATION_REPEATABLE_READ | (MYSQL默认级别)对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 |
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
不可重复读 :是指在一个事务内,多次读同一数据。
幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
配置参考:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Spring事务的传播行为?
- Propagation.REQUIRED(required):支持当前事务,如果当前有事务, 那么加入事务, 如果当前没有事务则新建一个(默认情况)
- Propagation.REQUIRES_NEW (requires_new) :支持当前事务,如果当前有事务,则挂起当前事务,然后新创建一个事务,如果当前没有事务,则自己创建一个事务。
- Propagation.MANDATORY (mandatory) :支持当前事务,如果当前没有事务,则抛出异常。(当前必须有事务)
- Propagation.SUPPORTS (supports) :如果当前有事务则加入,如果没有则不用事务。
- Propagation.NESTED (nested 嵌套事务) :如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行(和required一样)。嵌套的事务使用保存点作为回滚点,当内部事务回滚时不会影响外部事物的提交;但是外部回滚会把内部事务一起回滚回去。(这个和新建一个事务的区别)
- Propagation.NOT_SUPPORTED(not_supported) : 以非事务方式执行操作,如果当前存在事务就把当前事务挂起,执行完后恢复事务(忽略当前事务);
- PROPAGATION_NEVER (never) :以非事务方式执行,如果当前存在事务,则抛出异常。(当前必须不能有事务)
配置参考:@Transactional(propagation=Propagation.REQUIRED)
Spring事务失效的场景?
@Transactional(rollbackFor=Exception.class注解了解吗?
如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚。
Spring框架中的单例Beans是线程安全的么?
不是。因为当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。 但是,一般情况下,我们常用的 Controller
、Service
、Dao
这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。 常见的有 2 种解决办法:
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。 - 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。
spring应用销毁是在哪里?
Spring有哪些重要的扩展点?
- 0很棒
- 0不错
- 0一般
- 0???