Java八股文面试笔记整理(四)

简介: Java八股文面试笔记整理(四)

4-3.Spring事务失效

要求
  • 掌握事务失效的八种场景
1. 抛出检查异常导致事务不能正确回滚
@Service
public class Service1 {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 默认只会回滚非检查异常
  • 解法:配置 rollbackFor 属性
  • @Transactional(rollbackFor = Exception.class)
2. 业务方法内自己 try-catch 异常导致事务不能正确回滚
@Service
public class Service2 {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount)  {
        try {
            int fromBalance = accountMapper.findBalanceBy(from);
            if (fromBalance - amount >= 0) {
                accountMapper.update(from, -1 * amount);
                new FileInputStream("aaa");
                accountMapper.update(to, amount);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
  • 解法1:异常原样抛出
  • 在 catch 块添加 throw new RuntimeException(e);
  • 解法2:手动设置 TransactionStatus.setRollbackOnly()
  • 在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
3. aop 切面顺序导致导致事务不能正确回滚
@Service
public class Service3 {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            new FileInputStream("aaa");
            accountMapper.update(to, amount);
        }
    }
}
@Aspect
public class MyAspect {
    @Around("execution(* transfer(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        LoggerUtils.get().debug("log:{}", pjp.getTarget());
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…
  • 解法1、2:同情况2 中的解法:1、2
  • 解法3:调整切面顺序,在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1) (不推荐)
4. 非 public 方法导致的事务失效
@Service
public class Service4 {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional
    void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}
  • 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
  • 解法1:改为 public 方法
  • 解法2:添加 bean 配置如下(不推荐)
@Bean
public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}
5. 父子容器导致的事务失效
package day04.tx.app.service;
// ...
@Service
public class Service5 {
    @Autowired
    private AccountMapper accountMapper;
    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        int fromBalance = accountMapper.findBalanceBy(from);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
}

控制器类

package day04.tx.app.controller;
// ...
@Controller
public class AccountController {
    @Autowired
    public Service5 service;
    public void transfer(int from, int to, int amount) throws FileNotFoundException {
        service.transfer(from, to, amount);
    }
}

App 配置类

@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
    // ... 有事务相关配置
}
Web 配置类
less
复制代码
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
    // ... 无事务配置
}

现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效

  • 原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
  • 解法1:各扫描各的,不要图简便
  • 解法2:不要用父子容器,所有 bean 放在同一容器
6. 调用本类方法导致传播行为失效
@Service
public class Service6 {
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        bar();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}
  • 原因:本类方法调用不经过代理,因此无法增强
  • 解法1:依赖注入自己(代理)来调用
  • 解法2:通过 AopContext 拿到代理对象,来调用
  • 解法3:通过 CTW,LTW 实现功能增强

解法1

@Service
public class Service6 {
  @Autowired
  private Service6 proxy; // 本质上是一种循环依赖
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
    System.out.println(proxy.getClass());
    proxy.bar();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

解法2,还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)

@Service
public class Service6 {
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void foo() throws FileNotFoundException {
        LoggerUtils.get().debug("foo");
        ((Service6) AopContext.currentProxy()).bar();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bar() throws FileNotFoundException {
        LoggerUtils.get().debug("bar");
    }
}

7. @Transactional 没有保证原子行为

@Service
public class Service7 {
    private static final Logger logger = LoggerFactory.getLogger(Service7.class);
    @Autowired
    private AccountMapper accountMapper;
    @Transactional(rollbackFor = Exception.class)
    public void transfer(int from, int to, int amount) {
        int fromBalance = accountMapper.findBalanceBy(from);
        logger.debug("更新前查询余额为: {}", fromBalance);
        if (fromBalance - amount >= 0) {
            accountMapper.update(from, -1 * amount);
            accountMapper.update(to, amount);
        }
    }
    public int findBalance(int accountNo) {
        return accountMapper.findBalanceBy(accountNo);
    }
}

上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况

  • 原因:事务的原子性仅涵盖 insert、update、delete、select … for update 语句,select 方法并不阻塞

image.png

  • 如上图所示,红色线程和蓝色线程的查询都发生在扣减之前,都以为自己有足够的余额做扣减

8. @Transactional 方法导致的 synchronized 失效

针对上面的问题,能否在方法上加 synchronized 锁来解决呢?

java

复制代码

@ServicepublicclassService7 {

    privatestaticfinalLoggerlogger= LoggerFactory.getLogger(Service7.class);    @Autowired    private AccountMapper accountMapper;    @Transactional(rollbackFor = Exception.class)    publicsynchronizedvoidtransfer(int from, int to, int amount) {        intfromBalance= accountMapper.findBalanceBy(from);        logger.debug("更新前查询余额为: {}", fromBalance);        if (fromBalance - amount >= 0) {            accountMapper.update(from, -1 * amount);            accountMapper.update(to, amount);        }    }    publicintfindBalance(int accountNo) {        return accountMapper.findBalanceBy(accountNo);    }}

答案是不行,原因如下:

  • synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内
  • 可以参考下图发现,蓝色线程的查询只要在红色线程提交之前执行,那么依然会查询到有 1000 足够余额来转账

image.png

  • 解法1:synchronized 范围应扩大至代理方法调用
  • 解法2:使用 select … for update 替换 select

4-4.Spring MVC执行过程

要求
  • 掌握 Spring MVC 的执行流程
  • 了解 Spring MVC 的重要组件的作用
概要

我把整个流程分成三个阶段

  • 准备阶段
  • 匹配阶段
  • 执行阶段
准备阶段
  1. 在 Web 容器第一次用到 DispatcherServlet 的时候,会创建其对象并执行 init 方法
  2. init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
  3. refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等
  4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用

image.png

匹配阶段
  1. 用户发送的请求统一到达前端控制器 DispatcherServlet
  2. DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器
    ① HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。其中能识别 @RequestMapping 的优先级最高
    ② 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息
    ③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好

image.png

  1. 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回

image.png

  1. 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用

image.png

调用阶段
  1. 执行拦截器 preHandle

image.png

  1. 由 HandlerAdapter 调用 HandlerMethod
    ① 调用前处理不同类型的参数
    ② 调用后处理不同类型的返回值

image.png

  1. 第 2 步没有异常
    ① 返回 ModelAndView
    ② 执行拦截器 postHandle 方法
    ③ 解析视图,得到 View 对象,进行视图渲染

image.png

  1. 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程

image.png

  1. 最后都会执行拦截器的 afterCompletion 方法
  2. 如果控制器方法标注了 @ResponseBody 注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染

4-5.Spring注解

要求
  • 掌握 Spring 常见注解
事务注解
  • @EnableTransactionManagement,会额外加载 4 个 bean
  • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类
  • TransactionAttributeSource 用来解析事务属性
  • TransactionInterceptor 事务拦截器
  • TransactionalEventListenerFactory 事务监听器工厂
  • @Transactional
核心
  • @Order
切面
  • @EnableAspectJAutoProxy
  • 会加载 AnnotationAwareAspectJAutoProxyCreator,它是一个 bean 后处理器,用来创建代理
  • 如果没有配置 @EnableAspectJAutoProxy,又需要用到代理(如事务)则会使用 InfrastructureAdvisorAutoProxyCreator 这个 bean 后处理器
组件扫描与配置类
  • @Component
  • @Controller
  • @Service
  • @Repository
  • @ComponentScan
  • @Conditional
  • @Configuration
  • 配置类其实相当于一个工厂, 标注 @Bean 注解的方法相当于工厂方法
  • @Bean 不支持方法重载, 如果有多个重载方法, 仅有一个能入选为工厂方法
  • @Configuration 默认会为标注的类生成代理, 其目的是保证 @Bean 方法相互调用时, 仍然能保证其单例特性
  • @Configuration 中如果含有 BeanFactory 后处理器, 则实例工厂方法会导致 MyConfig 提前创建, 造成其依赖注入失败,解决方法是改用静态工厂方法或直接为 @Bean 的方法参数依赖注入或将BeanFactory处理器用其他可行注释进行代替,针对 Mapper 扫描可以改用注解方式
  • @Bean
  • @Import
  • 四种用法
  1. 引入单个 bean
  2. 引入一个配置类
  3. 通过 Selector 引入多个类
  4. 通过 beanDefinition 注册器
  • 解析规则
  • 同一配置类中, @Import 先解析 @Bean 后解析
  • 同名定义, 默认后面解析的会覆盖前面解析的
  • 不允许覆盖的情况下, 如何能够让 MyConfig(主配置类) 的配置优先? (虽然覆盖方式能解决)
  • 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析 @Bean, 再 Import
  • @Lazy
  • 加在类上,表示此类延迟实例化、初始化
  • 加在方法参数上,此参数会以代理方式注入
  • @PropertySource
依赖注入
  • @Autowired
  • @Qualifier
  • @Value
mvc mapping
  • @RequestMapping,可以派生多个注解如 @GetMapping 等
mvc rest
  • @RequestBody
  • @ResponseBody,组合 @Controller => @RestController
  • @ResponseStatus
mvc 统一处理
  • @ControllerAdvice,组合 @ResponseBody => @RestControllerAdvice
  • @ExceptionHandler
mvc 参数
  • @PathVariable
mvc ajax
  • @CrossOrigin
boot auto
  • @SpringBootApplication
  • @EnableAutoConfiguration
  • @SpringBootConfiguration
boot condition
  • @ConditionalOnClass,classpath 下存在某个 class 时,条件才成立
  • @ConditionalOnMissingBean,beanFactory 内不存在某个 bean 时,条件才成立
  • @ConditionalOnProperty,配置文件中存在某个 property(键、值)时,条件才成立
boot properties
  • @ConfigurationProperties,会将当前 bean 的属性与配置文件中的键值进行绑定
  • @EnableConfigurationProperties,会添加两个较为重要的 bean
  • ConfigurationPropertiesBindingPostProcessor,bean 后处理器,在 bean 初始化前调用下面的 binder
  • ConfigurationPropertiesBinder,真正执行绑定操作

4-6.SpringBoot自动配置原理

要求
  • 掌握 SpringBoot 自动配置原理
自动配置原理

@SpringBootConfiguration 是一个组合注解,由 @ComponentScan、@EnableAutoConfiguration 和 @SpringBootConfiguration 组成

  1. @SpringBootConfiguration 与普通 @Configuration 相比,唯一区别是前者要求整个 app 中只出现一次
  2. @ComponentScan
  • excludeFilters - 用来在组件扫描时进行排除,也会排除自动配置类
  1. @EnableAutoConfiguration 也是一个组合注解,由下面注解组成
  • @AutoConfigurationPackage – 用来记住扫描的起始包
  • @Import(AutoConfigurationImportSelector.class) 用来加载 META-INF/spring.factories 中的自动配置类

优点:

  • 分离主从配置,缓解主从配置间的强耦合
  • 执行优先级比较低,保证其他配置加载完后再进行从属配置的加载
为什么不使用 @Import 直接引入自动配置类

有两个原因:

  1. 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
  2. 直接用 @Import(自动配置类.class),引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置

因此,采用了 @Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 去读取 META-INF/spring.factories 中的自动配置类,实现了弱耦合。
  • 另外 AutoConfigurationImportSelector.class 实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析

4-7.Spring中设计模式

要求
  • 掌握 Spring 中常见的设计模式
1. Spring 中的 Singleton

区分 singleton pattern 与 Spring 中的 singleton bean

  • 根据单例模式的目的 Ensure a class only has one instance, and provide a global point of access to it
  • 显然 Spring 中的 singleton bean 并非实现了单例模式,singleton bean 只能保证每个容器内,相同 id 的 bean 单实例
  • 当然 Spring 中也用到了单例模式,例如
  • org.springframework.transaction.TransactionDefinition#withDefaults
  • org.springframework.aop.TruePointcut#INSTANCE
  • org.springframework.aop.interceptor.ExposeInvocationInterceptor#ADVISOR
  • org.springframework.core.annotation.AnnotationAwareOrderComparator#INSTANCE
  • org.springframework.core.OrderComparator#INSTANCE
2. Spring 中的 Builder

定义 Separate the construction of a complex object from its representation so that the same construction process can create different representations

它的主要亮点有三处:

  1. 较为灵活的构建产品对象
  2. 在不执行最后 build 方法前,产品对象都不可用
  3. 构建过程采用链式调用,看起来比较爽

Spring 中体现 Builder 模式的地方:

  • org.springframework.beans.factory.support.BeanDefinitionBuilder
  • org.springframework.web.util.UriComponentsBuilder
  • org.springframework.http.ResponseEntity.HeadersBuilder
  • org.springframework.http.ResponseEntity.BodyBuilder
3. Spring 中的 Factory Method

定义 Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses

根据上面的定义,Spring 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法,它隐藏了 bean (产品)的创建过程和具体实现

Spring 中其它工厂:

  • org.springframework.beans.factory.FactoryBean
  • @Bean 标注的静态方法及实例方法
  • ObjectFactory 及 ObjectProvider

前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题

4. Spring 中的 Adapter

定义 Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces

典型的实现有两处:

  • org.springframework.web.servlet.HandlerAdapter – 因为控制器实现有各种各样,比如有
  • 大家熟悉的 @RequestMapping 标注的控制器实现
  • 传统的基于 Controller 接口(不是 @Controller注解啊)的实现
  • 较新的基于 RouterFunction 接口的实现
  • 它们的处理方法都不一样,为了统一调用,必须适配为 HandlerAdapter 接口
  • org.springframework.beans.factory.support.DisposableBeanAdapter – 因为销毁方法多种多样,因此都要适配为 DisposableBean 来统一调用销毁方法
5. Spring 中的 Composite

定义 Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly

典型实现有:

  • org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
  • org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
  • org.springframework.web.servlet.view.ViewResolverComposite

composite 对象的作用是,将分散的调用集中起来,统一调用入口,它的特征是,与具体干活的实现实现同一个接口,当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成

6. Spring 中的 Decorator

定义 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality

典型实现:

  • org.springframework.web.util.ContentCachingRequestWrapper
7. Spring 中的 Proxy

定义 Provide a surrogate or placeholder for another object to control access to it

装饰器模式注重的是功能增强,避免子类继承方式进行功能扩展,而代理模式更注重控制目标的访问

典型实现:

  • org.springframework.aop.framework.JdkDynamicAopProxy
  • org.springframework.aop.framework.ObjenesisCglibAopProxy
8. Spring 中的 Chain of Responsibility

定义 Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it

典型实现:

  • org.springframework.web.servlet.HandlerInterceptor
9. Spring 中的 Observer

定义 Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically

典型实现:

  • org.springframework.context.ApplicationListener
  • org.springframework.context.event.ApplicationEventMulticaster
  • org.springframework.context.ApplicationEvent
10. Spring 中的 Strategy

定义 Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it

典型实现:

  • org.springframework.beans.factory.support.InstantiationStrategy
  • org.springframework.core.annotation.MergedAnnotations.SearchStrategy
  • org.springframework.boot.autoconfigure.condition.SearchStrategy
11. Spring 中的 Template Method

定义 Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure

典型实现:

  • 大部分以 Template 命名的类,如 JdbcTemplate,TransactionTemplate
  • 很多以 Abstract 命名的类,如 AbstractApplicationContext

4-8.Spring创建代理

要点
  • 要完全理解循环依赖,需要理解代理对象的创建时机
  • 掌握ProxyFactory创建代理的过程,理解Advisor,Advice,Pointcut和Aspect
  • 掌握Annotation Aware AspectJ AutoProxy Creator(自动代理后处理器)筛选Advisor合格者,创建代理的过程
总结
  • 最基本的切面是Advisor,一个Aspect切面对应一道多个Advisor
  • 最基本的Advice是MethodInterceptor,其他Advice最终都将适配为MethodInterceptor
  • 创建代理的方式
  • 实现了用户自定义接口,采用jdk动态代理
  • 没有实现用户自定义接口,采用cglb代理
  • 设置了setProxyTargetClass(true),统一采用cglb代理
  • 切面、切点、通知等不会被代理
  • Annotation Aware AspectJ AutoProxy Creator(自动代理后处理器)调用时机:创建阶段、依赖注入阶段、初始化阶段

4-9.SpringBean循环依赖

要求
  • 掌握单例 set 方式循环依赖的原理
  • 掌握其它循环依赖的解决方法
循环依赖的产生
  • 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱

image.png

  • set 方法(包括成员变量)的循环依赖如图所示
  • 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
  • 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
  • a 的顺序,及 b 的顺序都能得到保障

image.png

  • 构造方法的循环依赖如图所示,显然无法用前面的方法解决

image.png

构造循环依赖的解决
  • 思路1
  • a 注入 b 的代理对象,这样能够保证 a 的流程走通
  • 后续需要用到 b 的真实对象时,可以通过代理间接访问

image.png

  • 思路2
  • a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
  • 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问

image.png

  • 示例1:用 @Lazy 为构造方法参数生成代理
public class App60_1 {
    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;
        public A(@Lazy B b) {
            log.debug("A(B b) {}", b.getClass());
            this.b = b;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;
        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println();
    }
}
  • 示例2:用 ObjectProvider 延迟依赖对象的创建
public class App60_2 {
    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private ObjectProvider<B> b;
        public A(ObjectProvider<B> b) {
            log.debug("A({})", b);
            this.b = b;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;
        public B(A a) {
            log.debug("B({})", a);
            this.a = a;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println(context.getBean(A.class).b.getObject());
        System.out.println(context.getBean(B.class));
    }
}
  • 示例3:用 @Scope 产生代理
public class App60_3 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
        scanner.scan("com.itheima.app60.sub");
        context.refresh();
        System.out.println();
    }
}
@Component
class A {
    private static final Logger log = LoggerFactory.getLogger("A");
    private B b;
    public A(B b) {
        log.debug("A(B b) {}", b.getClass());
        this.b = b;
    }
    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}


@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
    private static final Logger log = LoggerFactory.getLogger("B");
    private A a;
    public B(A a) {
        log.debug("B({})", a);
        this.a = a;
    }
    @PostConstruct
    public void init() {
        log.debug("init()");
    }
}
  • 示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖


<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
public class App60_4 {
    static class A {
        private static final Logger log = LoggerFactory.getLogger("A");
        private Provider<B> b;
        public A(Provider<B> b) {
            log.debug("A({}})", b);
            this.b = b;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    static class B {
        private static final Logger log = LoggerFactory.getLogger("B");
        private A a;
        public B(A a) {
            log.debug("B({}})", a);
            this.a = a;
        }
        @PostConstruct
        public void init() {
            log.debug("init()");
        }
    }
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a", A.class);
        context.registerBean("b", B.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
        System.out.println(context.getBean(A.class).b.get());
        System.out.println(context.getBean(B.class));
    }
}
解决 set 循环依赖的原理
一级缓存

image.png

作用是保证单例对象仅被创建一次

  • 第一次走 getBean("a") 流程后,最后会将成品 a 放入 singletonObjects 一级缓存
  • 后续再走 getBean("a") 流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建
一级缓存与循环依赖

image.png

一级缓存无法解决循环依赖问题,分析如下

  • 无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走 getBean("a")
  • 当 a 的实例对象创建,接下来执行 a.setB() 时,需要走 getBean("b") 流程,红色箭头 1
  • 当 b 的实例对象创建,接下来执行 b.setA() 时,又回到了 getBean("a") 的流程,红色箭头 2
  • 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环
二级缓存

image.png

解决思路如下:

  • 再增加一个 singletonFactories 缓存(二级缓存,Spring中称为三级缓存)

解决循环依赖

  • 在依赖注入前,即 a.setB() 以及 b.setA() 将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存
  • 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程

对于上面的图

  • a = new A() 执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)
  • 接下来执行 a.setB(),走入 getBean("b") 流程,红色箭头 3
  • 这回再执行到 b.setA() 时,需要一个 a 对象,从二级缓存中取出临时存储在二级缓存中的a对象
  • factories.get() 在 singletonFactories 缓存中就可以找到,红色箭头 4 和 5
  • b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6
二级缓存与创建代理

image.png

二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下

  • spring 默认要求,在 a.init 完成之后才能创建代理 pa = proxy(a)
  • 由于 a 的代理创建时机靠后,在执行 factories.put(a) 向 singletonFactories 中放入的还是原始对象
  • 接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象

当对代理对象进行方法增强时,取出的代理对象均为原始对象,无法满足由jdk创建的代理对象,故二级缓存无法正确处理需要由代理创建的场景

三级缓存

image.png

简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:

  • 图中 factories.put(fa) 放入的既不是原始对象,也不是代理对象而是工厂对象 fa
  • 当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a
  • 假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5
  • 这回 b.setA() 注入的就是代理对象,保证了正确性,红色箭头 7
  • 还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6
  • a.init 完成后,无需二次创建代理,从earlySingletonObjects 缓存中找出代理对象a,并放入最后的单例池中,蓝色箭头 9

当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可

总结
  • 单例set方法(包括成员变量)循环依赖,Spring会利用三级缓存解决,无需额外配置
  • 一级缓存存放成品对象(singletonObjects)
  • 二级缓存存放发生了循环依赖时的产品对象

由三级缓存中通过bean Factorys处理完后存放的对象,可能是原始的bean对象,也有可能是通过处理后的代理对象

  • 三级缓存存放工厂对象,发生循环依赖时,会调用工厂获取产品
  • Spring期望在初始化时创建代理,但如果发生循环依赖,会由工厂提前创建代理,后续初始化时就不必重复创建代理
  • 二级缓存的意义在于,如果提前创建了代理对象,在最后的阶段只需要从二级缓存中获取所需的代理对象,作为最终结果即可
  • 构造方法及多例循环依赖解决方法
  • @Lazy(通过创建代理对象)
  • @Scope(通过创建代理对象)
  • ObjectFactory 或 Object Provider(通过工厂方式推迟注入对象获取)
  • Provider接口(通过工厂方式推迟注入对象获取)
目录
相关文章
|
1天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
1天前
|
Java
[Java 面试题] ArrayList篇
[Java 面试题] ArrayList篇
|
1天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
12 1
|
1天前
|
设计模式 搜索推荐 Java
面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?
面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?
9 0
|
1天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
7 0
|
1天前
|
Java 编译器
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
6 0
|
6天前
|
XML 缓存 Java
Java大厂面试题
Java大厂面试题
18 0
|
6天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
11 0
|
27天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
50 1
|
2月前
|
存储 关系型数据库 MySQL
2024年Java秋招面试必看的 | MySQL调优面试题
随着系统用户量的不断增加,MySQL 索引的重要性不言而喻,对于后端工程师,只有在了解索引及其优化的规则,并应用于实际工作中后,才能不断的提升系统性能,开发出高性能、高并发和高可用的系统。 今天小编首先会跟大家分享一下MySQL 索引中的各种概念,然后介绍优化索引的若干条规则,最后利用这些规则,针对面试中常考的知识点,做详细的实例分析。
247 0
2024年Java秋招面试必看的 | MySQL调优面试题