Spring经典问题收录

问题集合

Spring题目合集

https://blog.csdn.net/a745233700/article/details/80959716

对Spring的认识与理解

Spring采取了以下4种关键策略:

  1. 基于POJO的轻量级和最小侵入性编程;
  2. 通过依赖注入和面向接口实现松耦合;
  3. 基于切面和惯例进行声明式编程;
  4. 通过切面和模板减少样板式代码。

Spring的优缺点是什么?

优点:

  1. AOP + IOC带来的好处:IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。方便解耦,简化开发,高内聚低耦合;反转控制,专注于业务代码
  2. 集成了各种框架,避免大量模板式代码
  3. 各种核心模块,简化开发。比如SpringSecurity、Spring MVC
  4. 只需要通过配置就可以完成对事务的管理,而无需手动编程。
  5. 声明式事务的支持
  6. Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  7. 外观facade模式:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
  8. Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。

缺点
Spring明明一个很轻量级的框架,却给人感觉大而全
Spring依赖反射,反射影响性能
使用门槛升高,入门Spring需要较长时间

Spring IOC的理解

简介

(1)IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。

(2)最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

Spring IoC 的实现机制

Spring 中的 IoC 的实现原理就是工厂模式加反射机制。

控制反转(IoC)有什么作用/优势

  1. 让Spring为我们管理对象的创建和依赖关系的维护。托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
  2. 解耦,由容器去维护具体的对象
  3. 依赖注入把应用的代码量降到最低。
  4. 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
  5. 最小的代价和最小的侵入性使松散耦合得以实现。
  6. IOC容器支持加载服务时的饿汉式初始化和懒加载。

Spring 的 IoC 设计支持功能

依赖注入
依赖检查
自动装配
支持集合
指定初始化方法和销毁方法
支持回调某些方法(但是需要实现 Spring 接口,略有侵入)

对AOP的理解

简介

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

实现原理

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  1. JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;

  2. 如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。
    CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
在这里插入图片描述

多个bean同时满足注入条件,怎么选择;

参考资料:Spring实战3.3章节
总的来说,Spring对多个bean符合注入条件时的处理有以下几种:

1、如果满足的bean列表中存在bean有@Primary注解,将注入该bean。
如果同时有两个及以上的bean有@Primary注解,则会抛出NoUniqueBeanDefinitionException异常 
2、如果带有javax.annotation.Priority注解,将以注解中value值中最小(优先级最高)的bean注入。
如果有两个bean的优先级相同且都是最高,则会抛出NoUniqueBeanDefinitionException异常
3、如果需要的注入的属性的名称与多个bean中的某个bean的name相同,则注入该bean
4、使用@Qulical注解进行声明选取同类型bean中的某一个
5、上面条件都不满足,但是如果需要的注入的field上的注解为@Autowired(required=false)则返回null,不会抛出异常

Spring事务传播行为详解

参考:https://segmentfault.com/a/1190000013341344
通过实例验证了几种事务的传播
在这里插入图片描述

Spring容器初始化

https://blog.csdn.net/a745233700/article/details/113761271
Spring的启动流程可以归纳为三个步骤:

1、初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中
2、将配置类的BeanDefinition注册到容器中
3、调用refresh()方法刷新容器

第一步初始化:
spring容器的初始化时,通过this()调用了无参构造函数,主要做了以下三个事情:

(1)实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
(2)实例化BeanDefinitionReader注解配置读取器,向容器内添加内置组件,用于对特定注解(如@Service、@Repository)的类进行读取转化成  BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等).
其中最主要的组件便是 ConfigurationClassPostProcessor 和 AutowiredAnnotationBeanPostProcessor ,前者是一个 beanFactory 后置处理器,用来完成 bean 的扫描与注入工作,后者是一个 bean 后置处理器,用来完成 @AutoWired 自动注入。
(3)实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象

先准备好各种解析BD的原始工具,然后开始去导入各种BD

第二步:导入各种配置中的BD到容器
这里有各种扫描的操作,@Scan、@Import + 自定义BeanDefinition的注册类等

第三步:refresh
结合Bean的生命周期,做各种事情,预处理,初始化BeanFactory,添加各种BD MAP、组建BeanFactory,调用BeanFactoryPostProcessor,调用各种BeanPostProcessor方法等,做国际化、事件分发器等设置。

常用的三种注入方式

https://blog.csdn.net/qq594913801/article/details/80512451
常用的注入方式主要有三种:构造方法注入,set方法参数注入,接口注入。
帖子中讲解了:
@Autowired:支持构造方法、方法、接口注入的几个实例
@Resource注解可以标注在字段或属性的setter方法上,默认按名称装配,当找不到与名称匹配的bean才会按类型装配。名称可以通过@Resource的name属性指定,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。

Spring的BeanPostProcessor

https://www.cnblogs.com/frankltf/p/11449033.html
BeanPostProcessor也称为Bean后置处理器,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法:postProcessBeforeInitialization,postProcessAfterInitialization。
BeanPostProcessor的执行是在容器的刷新过程中:AbstractApplicationContext.refresh()。执行时会获取所有的beanpostprocessor,并在bean生命周期的初始化阶段前以及初始化阶段后执行这两个方法。

几种BeanPostProcessor:
0,支持自定义自己的beanPostProcessor接口,刷新容器中的bean
Spring自带的几个beanpostprocessor
1,bean生命周期的回调方法aware背后,实际上就是各种BeanPostProcessor。
2,InitDestroyAnnotationBeanPostProcessor
InitDestroyAnnotationBeanPostProcessor后置处理器是用来处理自定义的初始化方法和销毁方法。实际上就是解析Bean属性完成自定义注入初始化与销毁的方法
Spring中提供了3种自定义初始化和销毁方法:1.通过@Bean指定init-method和destroy-method属性;2.Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑); 3.@PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法@PreDestroy:在容器销毁bean之前通知我们进行清理工作

Spring的@Configuration原理:

https://blog.csdn.net/weixin_43843104/article/details/109771918

  1. @Configuration注解的Bean,在BeanDefinition加载注册到IOC容器之后,进行postProcessBeanFactory处理时会进行CGLIB动态代理
  2. @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean等直接注解的类的BeanDefinition,是在ConfigurationClassParser#parse()中直接进行加载注册
  3. 通过ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()开始将@Configuration注解类内部@Import、@Bean进行BeanDefinition的加载注册

ConfigurationClassPostProcessor进行调用,实际上也是种BeanPostProcessor

SpringMVC拦截器项目实例

log中的属性填充,配合Log4j2进行,一写日志二写数据库
设置切入点,这里设置的是URL
其次设置切入时的方法:进入方法前的处理,进入后的处理,出了方法后的处理等

注解原理

注解本质是一个继承了Annotation的特殊接口,其具体实现类是JDK动态代理生成的代理类。我们通过反射获取注解时,返回的也是Java运行时生成的动态代理对象。
通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法,该方法会从memberValues这个Map中查询出对应的值,而memberValues的来源是Java常量池。

SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决?

答:是单例模式。在多线程访问的时候有线程安全问题,解决方案是在控制器里面不能写可变状态量,让controller尽可能保持无状态。如果需要使用这些可变状态,可以使用ThreadLocal机制解决,为每个线程单独生成一份变量副本,独立操作,互不影响。

参数获取

怎样在方法里面得到Request,或者Session?
答:直接在方法的形参中声明request,SpringMvc就自动把request对象传入。

如果想在拦截的方法里面得到从前台传入的参数,怎么得到?
答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。

如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象?
答:直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。

BeanFactory和ApplicationContext

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

依赖关系

BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

  1. 继承MessageSource,因此支持国际化。
  2. 统一的资源文件访问方式。
  3. 提供在监听器中注册bean的事件。
  4. 同时加载多个配置文件。
  5. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

加载方式的区别

  1. BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  2. ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

ApplicationContext通常的实现是什么?

FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。

WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

Spring框架中的单例bean是线程安全的吗?

不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
有状态就是有数据存储功能。
无状态就是不会保存数据。

猜你喜欢

转载自blog.csdn.net/weixin_38370441/article/details/115137557