最新Spring最全面试题

Spring bean的生命周期

参考链接Spring框架(六)——bean的生命周期

用例图(引用自https://blog.csdn.net/jc_hook/article/details/123658314)
在这里插入图片描述

  1. 调用 Bean 构造方法或工厂方法实例化 Bean
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用
  6. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了
  10. 如果在 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁
类型 描述
Bean自身的方法 包括了Bean本身调用的方法和通过配置文件中的init-methoddestroy-method指定的方法
Bean级生命周期接口方法 包括了BeanNameAwareBeanFactoryAwareInitializingBeanDiposableBean这些接口的方法
容器级生命周期接口方法 包括了InstantiationAwareBeanPostProcessorBeanPostProcessor 这两个接口实现的方法,一般称它们的实现类为“后处理器”。
工厂后处理器接口方法 括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法

IOC

IOC,即 Inversion of Control,控制反转。
IOC 容器根据配置文件来创建对象,在对象的生命周期内,在不同时期根据不同配置进行对象的创建和改造。
对象的创建都由 IOC 容器来控制之后,对象之间就不会有很明确的依赖关系,使得非常容易设计出松耦合的程序。
控制反转,是面向对象编程的一种设计原则,可以用来降低计算机代码之间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给他。也可以说依赖被注入到对象中。

通俗的讲,就是把对象创建和对象之间的调用过程,都交给 spring 进行管理,这个过程叫做IOC

使用IOC目的:降低耦合度
IOC底层原理
xml解析、工厂模式、反射

IOC思想基于IOC容器来完成,IOC容器底层就是对象工厂

Spring提供IOC容器实现两种方式:(两个接口)
//加载spring配置文件
BeanFactory context = new ClassPathXmlApplicationContext(“bean.xml”);

BeanFactory
IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用

加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象

ApplicationContext
BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。

加载配置文件时候就会把配置文件对象进行创建

DI

DI,Dependency Injection,依赖注入。

容器在运行的时候,可以找到被依赖的对象,然后将其注入,通过这样的方式,使得各对象之间的关系可由运行期决定,而不用在编码时候明确。

AOP

AOP,Aspect Oriented Programming,面向切面编程。

将一些通用的逻辑集中实现,然后通过 AOP 进行逻辑的切入,减少了零散的碎片化代码,提高了系统的可维护性。

通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,然后再调用真正的方法实现。
代理大体上可以分为:动态代理和静态代理。

动态代理,即在运行时将切面的逻辑进去,按照上面的逻辑就是你实现 A 类,然后定义要代理的切入点和切面的实现,程序会自动在运行时生成类似上面的代理类。
主要实现原理就是:

  1. 首先通过实现一个 InvocationHandler 接口得到一个切面类。
  2. 然后利用 Proxy 糅合目标类的类加载器、接口和切面类得到一个代理类。
  3. 代理类的逻辑就是执行切入逻辑,把所有接口方法的调用转发到 InvocationHandler 的 invoke() 方法上,然后根据反射调用目标类的方法。

静态代理,在编译时或者类加载时进行切面的织入,典型的 AspectJ 就是静态代理。

  • CGLIB 是基于ASM 字节码生成工具,它是通过继承的方式来实现代理类,所以要注意 final 方法,这种方法无法被继承。
  • 通过字节码技术动态拼接成一个子类,在其中织入切面的逻辑。

Spring AOP 的动态代理实现分别是:JDK 动态代理与 CGLIB。

默认的实现是 JDK 动态代理。

SpringBoot 2.x 版本已经默 CGLIB。
如果要修改 SpringBoot 使用 JDK 动态代理,那么设置 spring.aop.proxy-target-class=false
JDK 动态代理要求接口,所以没有接口的话会有报错,并且让 CGLIB 作为默认也没什么副作用,特别是 CGLIB 已经被重新打包为 Spring 的一部分了,所以就默认 CGLIB 。

Spring流程(IOC下的Bean的生命周期,循环依赖,构造函数)

  1. 启动ApplicationContext
    两个重要的子类:
    AnnotationConfigApplicationContext(用的最多)
    ClassPathXmlApplicationContext
  2. 初始化AnnotationBeanDefinitionReader
    a.读取spring内部的初始的 beanFactoryPostProcess 和 其他的几种 beanPostProcess(AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry))
    1. AnnotationAwareOrderComparator:解析@Order进行排序
    2. ContextAnnotationAutowireCandidateResolver
    3. ConfigurationClassPostProcessor:解析加了@Configuration、@ComponentScan、@ComponentScans、@Import等注解(最重要的类)
    4. AutowiredAnnotationBeanPostProcessor:解析@Autowired
    5. RequiredAnnotationBeanPostProcessor:解析@Required
    6. CommonAnnotationBeanPostProcessor:负责解析@Resource、@WebServiceRef、@EJB
    7. EventListenerMethodProcessor:找到@EventListener
    8. DefaultEventListenerFactory:解析@EventListener
    b. 在ConfigurationClassPostProcessor类中有主要是为了解析加了@Configuration、@ComponentScan、@ComponentScans、@Import等注解,在这里面他有一个细节,就是加了@Configuration里面,他会把当前类标注成full类,就会产生一个aop的动态代理去加载当前类,没有的话就把当前类标注成lite类,也就是普通类处理。
  3. 初始化ClassPathBeanDefinitionScanner
    a. 程序员能够在外部调用doScan(), 或者 继承该类可以重写scan规则用来动态扫描注解,需要注册到容器。
    b. spring内部是自己重新new 新的对象来扫描。
  4. 执行register()方法,一般来说就是注册我们的配置类
    a. 先把此实体类型转换为一个BeanDefinition
  5. 执行refresh(),先初始化比如BeanFactory这类基础的容器。
    a. 执行invokeBeanFactoryPostProcessors(),主要的作用是扫描包和parse (类->beanDefinition)
    1. 执行BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry register)
    作用:主要是扫描包找到合格的类,解析类
    i. 先执行程序员通过 context.add的
    ii. 再执行spring内部的和程序员通过注解注册的 并且特殊的比如 实现了PriorityOrdered,Order
    iii. 最后再执行其他的 BeanDefinitionRegistryPostProcessor
    2. 再执行BeanFactoryPostProcessor接口 方法postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)**
    作用:1. 和子接口一样 扫描包找到合格的类,解析类
    2. 为@Configuration的类做代理
    i. 先执行子接口中的方法
    ii. 再执行程序员通过 context.add添加的
    iii. 再执行spring内部和程序员通过注解注册的 并且特殊的比如 PriorityOrdered,Order
    iv. 最后执行其他的 BeanFactoryPostProcessor
    他们在spring中唯一的实现类是ConfigurationClassPostProcessor
    将类变成beanDefinition的流程:
    1. 从BeanDefinitionRegistry中获取所有的bd
    2. 判断是否该bd是否被解析过,主要根据bd中是否有full或者lite属性。
    3. 将未解的bd去,循环解析bd
    a. 先处理内部类
    b. 处理@PropertrySource 环境配置
    c. 处理@ComponentScan
    解析带有ComponentScan,会调用ClassPathBeanDefinitionScanner,根据包路径,和匹配规则扫描出合格类。
    d. 处理@Import
    i. 先处理 ImportSelect,执行selectImports(), 事务的初始化和aop的代理类型,是否传递代理 就是在这里做的。
    ii. 然后处理 ImportBeanDefinitionRegistrar接口,会放到该bd的一个Map中,循环map统一去执行实现方法registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
    iii. 最后处理普通的类,同样会递归去解析该bd
    e. 处理@ImportResource
    f. 处理@Bean
    g.处理接口bd
    4. 然后将所有的合格的类,转换成bd,注册到beanDefinitionRegistry。
    b. 然后会注册beanPostProcessor,国际化等等,不是很重要
    c. 比较重要的,也是将bd变成bean的方法 finishBeanFactoryInitialization(),实例化非延迟的单例(循环依赖)
    d. 一般来说首先getBeanDefinition之前,都要合并bd。
    1)第一次getSingleton,从单例池拿是否存在,单例的第一次一般是不存在,并且会判断是否在正在创建bean的set集合中。
    singletonObjects 一级缓存,完整的bean
    singletonFactories 二级缓存,存的是代理bean工厂
    earlySingletonObjects 三级缓存,一般是是半成品的bean
    a. 如果存在,直接返回
    b. 如果不存在,并且不在正在创建bean的set集合中,直接返回null
    c. 如果不存在,并且在正在创建bean的set集合中。从三级缓存拿。
    i. 存在,直接三级缓存拿。
    ii. 不存在,通过二级缓存,代理的bean工厂拿,获得该bean,然后将得到bean放到三级缓存中,移出二级缓存。(原因是生产bean工厂周期比较长的。)
    2)第二次getSingleton
    a. 首先将beanName放到正在创建bean的set集合中,表示正在创建该bean
    b. 然后会调用二级缓存去获取bean,lambda延迟机制,就会调用表达式中,也就是createBean,这时候是正在获取代理bean工厂会走一个完整的bean 的生命周期。
    c. 然后从bean工厂获取bean。
    1. 构造函数:第一次 BeanPostProcessor,是否需要代理bean。如果代理bean直接返回,不会走下面的流程。

    2. 第二次BeanPostProcessor,推断构造函数
      a. 首先推断构造函数数组
      i. 没提供构造函数=设置构造函数数组为null
      ii. 一个默认的构造函数
      设置构造函数数组为null
      iii. 一个不是默认的构造函数
      =设置构造函数数组为该构造函数
      iv. 一个构造方法并且加了@Autowired设置构造函数数组为该构造函数
      v. 多个模糊构造函数
      ==设置构造函数数组为null
      vi. 多个构造函数,有唯一加了@Autowired
      设置构造函数数组为该构造函数
      vii. 多个构造函数,多个@Autowired(required为false)=设置构造函数数组为多个@Autowired
      viii. 提供多个构造函数,多个@Autowired(required为true)
      = 抛异常
      b. 如果推断构造数组不为null 或者,自动注入类型为构造函数,或者设置了构造函数的属性(xml方式)等,还有一种传参数金来
      i. 推断构造函数,
      1. 只有个构造函数,最终被确定的构造函数,
      2. 有多个构造函数
      a. 优先修饰符最开放的,public>protected>Default>private
      b. 修饰符一样找属性最多的
      ii. 推断参数,
      1. 首先找出所有候选的参数类型,实例化属性
      2. 然后类型是接口,那么判断是否开启宽松构造
      a. 未开启报错。
      b. 开启了,判断子类的差值(spring有个算法),默认差值是-1024。
      c. 差值低的为该参数,一样的丢到模糊集合中,随机取出。

      c. 构造函数数组为null,直接通过无参实例化构造函数。

    3. 第三次BeanPostProcessor ,缓存了注入元素的信息
      injectionMetadataCache: key: beanName或者类名 value:为解析出的属性(包括方法)集合 InjectionMetadata。
      InjectionMetadata:可以存放method 和 属性。类中有字段判断是否是属性 isField。
      checkedInitMethods: 存放 @PostConstruct 。
      checkedDestroyMethods:存放 @PreDestroy。
      a. AutowiredAnnotationBeanPostProcessor 主要解析加了 @Autowired 和 @Value 方法和属性。
      b. CommonAnnotationBeanPostProcessor 主要解析加了 @Resource属性。
      c. InitDestroyAnnotationBeanPostProcessor 主要解析加了 @PostConstruct 和 @PreDestroy方法
      d. 还有很多

    4. 第四次 BeanPostProcessor,生产代理工厂,作用是可以解决循环依赖
      a. 先判断是否允许循环依赖,可通过api修改属性,或者直接改源代码。
      b. 然后判断当前bean是否是正在创建的bean
      c. 调用populateBean 主要作用,注入属性。

    5. 第五次BeanPostProcessor,控制是否需要属性注入,目前没什么作用。
      再注入缓存的属性之前,先通过 自动注入模型
      a. byType byName,找到setter,注入。体现了@Autowired不是自动注入,而是手动注入。

    6. 第六次 BeanPostProcessor ,完成注解的属性填充** @Autowired @Resource
      a. 注入之前还是会再找一下是否有其他需要注入的属性和方法。
      b. 属性的调用属性注入方法,函数调用函数的注入方法。
      i. 通过属性的类型,从BeanDefinitionMap中找属性名称(接口则找找这个接口的子类),
      ii. 然后判断我们当前需要注入的属性是不是这几个类型,得到候选的类型。
      iii. 当有多个类型,再通过属性名称去推断出唯一候选的属性名。如果找到多个候选的属性名,抛异常。
      iv. 只有唯一的属性名,通过类名去获取类型。
      v. 最终通过找到唯一匹配的beanName和类型去注入。当没有找到匹配的名称和类型,就会抛异常。
      c. 在注入的时候,有循环依赖的时候,会去先去实例化该属性。

    7. 第七次BeanPostProcessor ,处理实现各种aware接口的重写方法 + 生命周期回调 执行@PostConstruct方法
      执行 实现InitializingBean接口的,重写方法,和 xml 中的 init-method="xxx"方法。

    8. 第八次BeanPostProcessor ,做aop代理
      a. 判断是否需要做代理
      i. 找出所有的候选切面,比如 加了 @Aspect的类 , 事务的切面
      ii. 做匹配逻辑,比如根据切面的连接点表达式 或者 类中方法是否加了@Transaction去 判断当前类是否匹配出,合适的切面集合。
      iii. 然后对匹配出的切面集合,做排序。
      iv. 能匹配上说明就做代理
      b. 哪种代理(默认用JDK动态代理)
      i. 当代理工厂设置ProxyTargetClass为 true,则为CGLIB代理。
      ii. 当目标对象为类,则也用为CGLIB代理。
      iii. 只有proxyTarget为 false,并且为目标对象为接口,则用JDK动态代理
      c. 执行代理invokeHandler(这里主要是JDK的代理,invoke方法)
      i. 首先会进行普通方法的判断比如hashcode eques等等,没有就给代理类创建。不是很重要
      ii. 然后判断是否需要将代理传递下去,就是绑定到 ThreadLocal中(在事务中,这个特别的重要)
      iii. 获取执行链,也就是这个目标对象的通知集合。(也就是所有过滤器链,实现了MethodIntercept。)
      iv. 执行过滤器执行链,类似于火炬传递。(事务的methodInterceptor也在这里会被调用)
      1. 判断通知是否执行完,没有执行完去,按顺序执行通知。
      2. 依次调用对应的通知,最终都会去回调到proceed()方法。
      3. 最终执行完代理方法,就会调用本身的方法。比较特殊的是around是在通知里,执行被代理的目标方法。

什么是 Spring 三级缓存

所谓三级缓存,其实就是org.springframework.beans.factory包下DefaultSingletonBeanRegistry类中的三个成员属性:
// Spring一级缓存:用来保存实例化、初始化都完成的对象
// Key:beanName
// Value: Bean实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// Spring二级缓存:用来保存实例化完成,但是未初始化完成的对象
// Key:beanName
// Value: Bean实例
// 和一级缓存一样也是保存BeanName和创建bean实例之间的关系,
// 与singletonObjects不同之处在于,当一个单例bean被放在里面后,
// 那么bean还在创建过程中,就可以通过getBean方法获取到了,其目的是用来循环检测引用!(后面会分析)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// Spring三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
// Key:beanName
// Value: Bean的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
除了三级缓存是一个HashMap,其他两个都是ConcurrentHashMap

Spring之所以引入三级缓存,目的就是为了解决循环依赖问题

什么是 Spring循环依赖问题?如何解决?

首先,Spring 解决循环依赖有两个前提条件:

Setter方式注入造成的循环依赖(构造器方式注入不可以)
必须是单例
本质上解决循环依赖的问题就是依靠三级缓存,通过三级缓存提前拿到未初始化的对象。

比如A对象依赖B对象,B对象又依赖A对象
A 对象的创建过程:

创建对象A,实例化的时候把A对象工厂放入三级缓存

A 注入属性时,发现依赖 B,转而去实例化 B

同样创建对象 B,注入属性时发现依赖 A,一次从一级到三级缓存查询 A,从三级缓存通过对象工厂拿到 A,把 A 放入二级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成,把 B 放入一级缓存。

接着继续创建 A,顺利从一级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除二级缓存中的 A,同时把 A 放入一级缓存。

最后,一级缓存中保存着实例化、初始化都完成的A、B 对象。

从上面步骤的分析可以看出,三级缓存解决循环依赖是通过把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作(因为构造器注入实例化和初始是一起进行的)。因此构造器方式注入的话是无法解决循环依赖问题的。
解决循环依赖为什么必须要要三级缓存?二级不行吗?

答案:不可以!

使用三级缓存而非二级缓存并不是因为只有三级缓存才能解决循环引用问题,其实二级缓存同样也能很好解决循环引用问题。

使用三级而非二级缓存并非出于IOC的考虑,而是出于AOP的考虑,即若使用二级缓存,在AOP情形下,往二级缓存中放一个普通的Bean对象,BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。

一句话总结就是,在 AOP 代理增强 Bean 后,会对早期对象造成覆盖,如果多线程情况下可能造成取到的对象不一致~

AOP

源码底层的实现是动态代理
动态代理有cglib和jdk实现
1、JDK动态代理通过反射机制实现:
通过实现InvocationHandlet接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理:
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程,底层是ASM实现
3、两者对比:
JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现(被代理类不能被final关键字所修饰,)。
4、使用注意:
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类,那么Spring会强制使用CGLib来实现动态代理

SpringMVC 父子容器

Spring 容器在启动的时候,不会有 SpringMVC 这个概念,只会扫描文件然后创建一个 context ,此时就是父容器。

然后发现是 web 服务需要生成 DispatcherServlet ,此时就会调用 DispatcherServlet#init,这个方法里面最会生成一个新的 context,并把之前的 context 置为自己的 Parent。

这样就有了父子之分,这样指责就更加清晰,子容器就负责 web 部分,父容器则是通用的一些 bean。

也正是有了父子之分,如果有些人没把 controller 扫包的配置写在 spring-servlet.xml ,而写到了 service.xml 里,那就会把 controller 添加到父容器里,这样子容器里面就找不到了,请求就 404 了。
子容器可以用父容器的 Bean,父容器不能用子容器的 Bean。

说说Spring 里用到了哪些设计模式?

单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。
工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。
代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。

SpringMVC核心组成

1)前端控制器DispatcherServlet 由框架提供作用:接收请求,处理响应结果
2)处理器映射器HandlerMapping由框架提供
作用:根据请求URL,找到对应的Handler
3)处理器适配器HandlerAdapter由框架提供
作用:调用处理器(Handler|Controller)的方法
4)处理器Handler又名Controller,后端处理器
作用:接收用户请求数据,调用业务方法处理请求
5)视图解析器ViewResolver由框架提供
作用:视图解析,把逻辑视图名称解析成真正的物理视图
支持多种视图技术:JSTLView,FreeMarker…
6)视图View,程序员开发
作用:将数据展现给用户

Springmvc执行流程?

① 首先,用户发送 HTTP 请求给 SpringMVC 前端控制器 DispatcherServlet。
② DispatcherServlet 收到请求后,调用HandlerMapping 处理器映射器,根据请求 URL 去定位到具体的处理器 Handler,并将该处理器对象返回给 DispatcherServlet 。
③ 接下来,DispatcherServlet 调用 HandlerAdapter 处理器适配器,通过处理器适配器调用对应的 Handler 处理器处理请求,并向前端控制器返回一个 ModelAndView 对象。
④ 然后,DispatcherServlet 将 ModelAndView 对象交给 ViewResoler 视图解析器去处理,并返回指定的视图 View 给前端控制器。
⑤ DispatcherServlet 对 View 进行渲染(即将模型数据填充至视图中)。View 是一个接口, 它的实现类支持不同的视图类型(JSP,FreeMarker,Thymleaf 等)。
⑥ DispatcherServlet 将页面响应给用户。

springmvc如何解决循环依赖的问题

当使用构造器方式初始化一个bean,而且此时多个Bean之间有循环依赖的情况,spring容器就会抛出异常!
解决办法:初始化bean的时候(注意此时的bean必须是单例,否则不能提前暴露一个创建中的bean)使用set方法进行注入属性,此时bean对象会先执行构造器实例化,接着将实例化后的bean放入一个map中,并提供引用。当需要通过set方式设置bean的属性的时候,spring容器就会从map中取出被实例化的bean。比如A对象需要set注入B对象,那么从Map中取出B对象即可。以此类推,不会出现循环依赖的异常。

spring如何解决循环依赖的问题

总结一波
拿bean的时候先从singletonObjects(一级缓存)中获取
如果获取不到,并且对象正在创建中,就从earlySingletonObjects(二级缓存)中获取
如果还是获取不到就从singletonFactories(三级缓存)中获取,然后将获取到的对象放到earlySingletonObjects(二级缓存)中,并且将bean对应的singletonFactories(三级缓存)清除
bean初始化完毕,放到singletonObjects(一级缓存)中,将bean对应的earlySingletonObjects(二级缓存)清除

SpringBoot的自动装箱

1、@SpringBootApplication=>
2、@EnableAutoConfiguration=>
3、@Import(AutoConfigurationImportSelector.class)=>调用getCandidateConfigurations()方法,里面有个读取Meta-info/spring.factories

Starter自动装配

1.编写一个带有@Configuration注解的类,如果按条件加载可以加上@ConditionalOnClass或@ConditionalOnBean注解
2.在classpath下创建META-INF/spring.factories文件,并在spring.factories中添加
org.springframework.boot.autoconfigure.EnableAutoConfiguretion =
上面定义类的全类名

spring MVC boot 常用到的注解及作用

@ResponseBody

@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。
注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。

@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】。
注意:在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。

ResponseEntity

1、ResponseEntity继承了HttpEntity,可以添加HttpStatus状态码的HttpEntity的扩展类。被用于RestTemplate和Controller层方法
2、ResponseEntity可以定义返回的HttpStatus(状态码)和HttpHeaders(消息头:请求头和响应头)
3. ResponseEntity的优先级高于@ResponseBody。在不是ResponseEntity的情况下才去检查有没有@ResponseBody注解。如果响应类型是ResponseEntity可以不写@ResponseBody注解,写了也没有关系。简单的说
@ResponseBody可以直接返回Json结果,
@ResponseEntity不仅可以返回json结果,还可以定义返回的HttpHeaders和HttpStatus

public ResponseEntity<List<Category>> queryCategoriesByPid(@RequestParam(value = "pid",defaultValue = "0") Long pid){
        if(pid == null || pid.longValue()<0){
            // 响应400,相当于ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
            return ResponseEntity.badRequest().build();

        }
        //ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
       // ResponseEntity.notFound().build();
        // ResponseEntity.ok(null);

        List<Category> categoryList = this.categoryService.queryCategoriesByPid(pid);
        if(CollectionUtils.isEmpty(categoryList)){
            // 响应404
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(categoryList);
    }

@ModelAttribute

用法1:用在入参
运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用;

用在方法的入参上依次做如下操作:

从隐含对象中获取隐含的模型数据
将请求参数绑定到隐含对象中
将隐含对象传入到入参
将入参绑定到Model
用法2:用在方法上
被@ModelAttribute注释的方法会在此controller的每个方法执行前被执行 ,如果有返回值,则自动将该返回值加入到ModelMap中。因此对于一个controller映射多个URL的用法来说,要谨慎使用。我们编写控制器代码时,会将保存方法独立成一个控制器也是如此。

使用@ModelAttribute注解的方法和被@RequestMapping注解的处理方法由很多相似之处:

1
都可以通过入参接收前台提交的数据,而且对入参绑定的设置都是一样的。
入参绑定的数据如果没有设置可为空,不能接收空数据,否则会报错。
都可以将数据放入model中,而且对于一次请求,model是共享的,所以在处理方法中的model中存放了@ModelAttribute注解的方法中存放的数据。

1.@ModelAttribute注释void返回值的方法

@Controller
public class HelloModelController {
    
    @ModelAttribute 
    public void populateModel(@RequestParam String abc, Model model) {  
       model.addAttribute("attributeName", abc);  
    }  
 
    @RequestMapping(value = "/helloWorld")  
    public String helloWorld() {  
       return "helloWorld.jsp";  
    }  
 
}

在这个代码中,访问控制器方法helloWorld时,会首先调用populateModel方法,将页面参数abc(/helloWorld.ht?abc=text)放到model的attributeName属性中,在视图中可以直接访问。

@RequestParam

application/x-www-form-urlencoded是以表格的形式请求,而application/json则将数据序列化后才进行传递,如果使用了@RequestParam会在Content里面查找对应的数据,结果因为传递的数据已经被序列化所以不能找到,所以当要使用@RequestParam注解时候应当使用application/x-www-form-urlencoded,而如果想要使用application/json则应当使用@RequestBody获取被序列化的参数
@RequestParam

① 支持POST和GET请求。
② 只支持Content-Type:为application/x-www-form-urlencoded编码的内容。Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)

@RequestBody

① 不支持GET请求。
② 必须要在请求头中申明content-Type(如application/json)springMvc通过HandlerAdapter配置的HttpMessageConverters解析httpEntity的数据,并绑定到相应的bean上。

@Value和@Bean

Springboot中使用@Configruation和@Bean一起将Bean注册到ioc容器中,而@Value常用于将yml配置文件中的配置信息注入到类的成员变量中。当@Configruation、@Bean和@Value出现在同一个类中时,@Bean会比@Value先执行,这会导致当@Bean注解的方法中用到@Value注解的成员变量时,无法注入(null)的情况。例如在为Feign创建配置类,以实现Feign的权限验证时,需要将yml文件中的用户名和密码注入到配置类的成员变量中,@Bean注解方法则依赖于用户名和密码产生Bean的实例。

@Value和@Bean注解不在同一文件下时,@Value先执行

在同一文件下时,@Bean先执行

@PropertySource

@Configuration

@PropertySource("classpath:test.properties") //注意文件格式的指定
public class ElConfig {
    @Value("I Love You!") //1 注入普通字符串
    private String normal;

    @Value("#{systemProperties['os.name']}") //2 注入操作系统属性
    private String osName;

    @Value("#{ T(java.lang.Math).random() * 100.0 }") //3 注入表达式结果
    private double randomNumber;

    @Value("#{demoService.another}") //4  注入其他Bean的属性
    private String fromAnother;

    @Value("classpath:test.txt") //5  注入了文件资源
    private Resource testFile;

    @Value("http://www.baidu.com") //6   注入网页资源
    private Resource testUrl;

    @Value("${book.name}") //7   注入classpath:test.properties中资源项,注意美元符号$
    private String bookName;

    @Autowired
    private Environment environment; //7 属性也可以从environment中获取。

    @Bean //7
    public static PropertySourcesPlaceholderConfigurer propertyConfigure() {
        return new PropertySourcesPlaceholderConfigurer();
    }



    public void outputResource() {
        try {
            System.out.println(normal);
            System.out.println(osName);
            System.out.println(randomNumber);
            System.out.println(fromAnother);

            System.out.println(IOUtils.toString(testFile.getInputStream()));
            System.out.println(IOUtils.toString(testUrl.getInputStream()));
            System.out.println(bookName);
            System.out.println(environment.getProperty("book.author"));
            System.out.println(environment.getProperty("book.school"));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

@ImportResource

@ImportResource注解用于导入Spring的配置文件,让配置文件里面的内容生效;(就是以前写的springmvc.xml、applicationContext.xml)

Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;

想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上。

注意!这个注解是放在主入口函数的类上,而不是测试类上

该注解标注在主配置类上,用于加载我们自己手写的spring相关的配置文件

package com.yangzhenxu.firstspringboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@ImportResource(locations = "classpath:applicationContext.xml")
@SpringBootApplication
@RestController
public class FirstSpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(FirstSpringbootApplication.class, args);
    }


}


猜你喜欢

转载自blog.csdn.net/liuerchong/article/details/123921149