目录

八股打卡七

八股打卡(七)

Spring 循环依赖

什么是循环依赖

多个Bean之间相互依赖,形成闭环。

如何解决循环依赖

Spring循环依赖的理论依据是基于Java的引用传递。当我们获取对象的引用时,field可以延后设置,但是构造器必须在引用之前。

Spring生成一个Bean需要以下过程:

  1. createBeanInstance实例化:使用构造器来创建Bean实例;

  2. populateBean填充属性:涉及多个Bean的引用;

  3. initializeBean初始化:调用spring xml中的init方法。

    发生循环依赖一般是在第一二步,构造器循环依赖和field循环依赖。

    解决Spring单例的循环依赖问题,采用三级缓存。

  4. 在singletonObjects一级缓存中获取;

  5. 如果获取不到,在earlySingletonObjects二级缓存中获取;

  6. 如果获取不到,那么在singletonFactories三级缓存中调用getOebct方法获取;

  7. 从三级缓存获取到后,将它从singletonFactories中删除,放入earlySingletonObjects中。

举个栗子

Bean A的setter方法或field依赖了Bean B的实例对象,Bean B的setter方法或field依赖了Bean A的实例对象。

  • Bean A通过构造器完成初始化第一步,被提前暴露到singletonFactories中。在第二步时,发现需要调用get(B),那么去寻找B,发现B还没被创建,那么就先创建B。
  • Bean B同样通过构造器完成初始化第一步,在第二步时,发现需要调用get(A),那么去寻找A。
  • 在singletonObjects中寻找A,找不到;去earlySingletonObjects中寻找A,找不到,去singletonFactories中寻找,找到了半成品A,继续完成初始化第二三步,B完成了初始化,被放到singletonObjects中。
  • 返回A,A从singletonObjects中获取到B,顺利完成初始化第二三步,A完成了初始化。
  • 由于B中有A的对象引用,那么B也完整地完成了初始化。

什么循环依赖无法解决

  1. 构造器依赖:三级缓存的前提是通过构造器生成了半成品的对象。
  2. prototype bean:Spring只会在需要的时候创建它,实例化它,不会缓存它,也就是不能提前暴露一个创建中的bean。

JDK动态代理 vs. cglib动态代理

  1. 基于接口&基于类

    jdk动态代理基于接口,被代理类需要实现一个或多个接口。

    cglib动态代理基于类,通过生成目标类的子类实现代理,通过继承实现代理类,不能代理final类和final方法。

  2. 性能:

    jdk动态代理通过反射调用来实现代理,创建代理的开销较小。但是频繁调用比cglib耗时。

    cglib动态代理通过字节码生成来实现代理,创建代理的开销较大。但是实际调用性能较好,没有反射调用的开销。

  3. 适用场景:

    jdk动态代理:接口驱动编程。

    cglib动态代理:没有实现接口的类。

Java中的反射机制

Java中的反射机制核心是运行时动态加载类并获取类的详细信息。本质是JVM获取class对象后,通过class对象反编译获得类的详细信息。

Java是一个先编译后运行的语言。程序中的对象类型在编译时期就确定下来了,有些类之前用不到,就没有加载到JVM中,通过反射能够动态创建对象调用属性。在编译时JVM不需要直到运行的对象是谁。

Spring中的设计模式

  1. 工厂设计模式:Spring通过工厂设计模式的BeanFactory和ApplicationContex来创建Bean。
  2. 单例模式:Spring中的Bean默认是单例模式。
  3. 代理设计模式:Spring AOP功能的实现。
  4. 模板方法模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类/
  5. 包装器设计模式:项目中连接多个数据库,不同用户根据需求访问不同的数据库。
  6. 观察者设计模式:Spring的事件驱动模型。
  7. 适配器设计模式:Spring MVC中controller。

Spring MVC执行流程

  1. 用户发送请求到前端控制器DispatcherServlet。

  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。

  3. HandlerMapping根据请求找到具体的处理器,生成处理器对象和处理器拦截器返回给DispatcherServlet。

  4. DispatcherServlet调用HandlerAdapter处理器适配器。

  5. HandlerAdapter经过适配调用具体的控制器Controller,也即后端控制器。

  6. Controller执行,返回ModelAndView。

  7. HandlerAdapter将ModelAndView返回给DispatcherServlet。

  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器。

  9. ViewResolver解析生成View。

    10.DispatcherServlet根据View渲染视图,返回响应给用户。

SpringBoot Starter的作用

SpringBoot Starter 是为了简化和加速项目的配置和依赖管理。

  1. SpringBoot Starter是预配置的模块,封装了特定功能的配置和依赖项。引入Starter依赖后,开发人员无需手动配置相关依赖和参数。常见的Starter有Spring-Boot-Starter-Web应用于Web应用、Spring-Boot-Starter-data-jpa用于数据访问,引入这些Starter后,Spring会自动配置所需的组件和Bean。
  2. Starter管理相关功能的依赖项,包括其他Starter和第三方库,能够确保它们良好协作,避免版本冲突和依赖问题。
  3. Starter的设计使得应用程序能够通过引入不同的Starter来完成模块化的开发。每个Starter专注一个功能领域,包括数据库访问、消息队列和web应用等。
  4. 开发者能够自定义Starter,用于共享和重用特定功能的配置和依赖项。

Java 8新特性

  1. lambda表达式:简洁的语言编写匿名函数。
  2. StreamAPI:使用声明式方法对集合操作,包括过滤、映射、排序、归约。
  3. 函数式接口:Consumer, Predicate, Function。
  4. 新的日期时间API:java.Time,解决了java.util.Date和java.util.Calendar的问题。
  5. 方法引用:方法引用通过方法名引用,而不是执行它。