面试官:讲一下spring的ioc和aop
IOC
ioc:让spring帮我们
- 创建对象(工厂模式)
- 并管理对象之间的依赖关系。(DI:依赖注入)
ioc可以理解为实现了依赖注入的工厂模式。
任何交给spring管理的对象都可以叫spring bean。
而java bean则是符合一定规范的java类:
- 字段都是私有的。
- 为所有字段提供setter、getter方法。
- 提供一个无参构造函数。
- 可序列化。
POJO(普通java对象):PO(持久对象)、DTO、VO都是POJO
- 有一些私有属性以及对应的setter、getter
- 没有业务逻辑
循环依赖:
【原型bean不支持循环依赖】,会直接抛异常(BeanCurrentlyInCreationException )。如果不抛异常,会一直循环创建,直到stackoverflow或oom。
【单例bean基于构造器注入,也不支持循环依赖。】因为基于构造器注入,意味着当前实例还没创建出来,别人就要依赖。
只有,基于属性注入的单例bean,才支持循环依赖。
Spring使用三级缓存,通过提前暴露实例化中的bean来解决循环依赖问题。
过程如果忘记了,看这个图解:https://juejin.cn/post/6844904122160775176
A和B循环依赖:(getSingleton、doCreateBean、populateBean、addSingleton)
-
走A的get流程(三级缓存中都没get到)
先实例化A,并把A的工厂添加到第三级缓存,填充A发现依赖B。 -
走B的get流程(三级缓存中都没get到)
先实例化B,并把B的工厂添加到第三级缓存,填充B发现依赖A。 -
又走A的get流程
这时候在第三级缓存get到了。从A的工厂获取A的实例,放到第二级缓存中,根据beanName删除三级缓存,并返回A的实例。 -
回到B的get流程,B拿到A的实例并完成创建,然后把自己放到第一级缓存中,根据beanName删除二三级缓存,并返回B的实例。
-
回到A的get流程,A拿到B的实例并完成创建,然后把自己放到第一级缓存中,根据beanName删除二三级缓存,并返回A的实例(这时候A已经完全创建好了)。
spring提供两种容器:
- BeanFactory
- ApplicationContext(不仅提供了BeanFactory的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。)
BeanFactory是一个Bean工厂,使用简单工厂模式,是Spring ioc容器顶级接口,可以理解为含有Bean集合的工厂类,作用是管理Bean,包括实例化、定位、配置对象及建立这些对象间的依赖。
BeanFactory实例化后并不会自动实例化Bean,只有当Bean被使用时才实例化与装配依赖关系,【属于延迟加载,适合多例模式。】
FactoryBean是一个工厂Bean,使用了工厂方法模式,作用是生产其他Bean实例,可以通过实现该接口,提供一个工厂方法来自定义实例化Bean的逻辑。FactoryBean接口由BeanFactory中配置的对象实现,这些对象本身就是用于创建对象的工厂,如果一个Bean实现了这个接口,那么它就是创建对象的工厂Bean,而不是Bean实例本身。
ApplicationConext是BeanFactory的子接口,扩展了BeanFactory的功能,提供了支持国际化的文本消息,统一的资源文件读取方式,事件传播以及应用层的特别配置等。容器会在初始化时对配置的Bean进行预实例化,Bean的依赖注入在容器初始化时就已经完成,【属于立即加载,适合单例模式】,一般推荐使用。
spring bean的作用域:
- singleton(默认)
- prototype
- request(只在WebApplicationContext中有)
- session(只在WebApplicationContext中有)
- global session(只在WebApplicationContext中有)
WebApplicationContext:ApplicationContext的子类,专门给web应用使用的。
无状态bean使用singleton,有状态bean使用prototype。
spring会管理singleton bean从创建到销毁的整个生命周期。
而对于prototype bean,spring只负责创建,不负责销毁。
单例bean生命周期的理解记忆,可以分为两部分:
- 生命周期的【概要流程】
- 生命周期的【扩展点】
概要流程:
- 创建实例(构造函数)
- 属性注入(setter)
- 初始化(init-method,通过XML配置)
- 销毁(destroy-method,通过XML配置)
BeanFactory中bean的生命周期:
bean生命周期.png
processor对所有的bean都有效。
AOP就是借助BeanPostProcessor#postProcessAfterInitialization实现的。
在postProcessAfterInitialization方法里对对象进行判断,看它需不需要织入切面逻辑,如果需要,那就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终放入容器的,自然就是代理对象了。
@PostConstruct、@PreDestroy都是JDK定义的注解。
被@PostConstruct修饰的注解在BeanPostProcessor#postProcessBeforeInitialization之后,InitializingBean#afterPropertiesSet之前,执行。
如果注入的属性为null,你会从哪几个方向去排查?
- 想要注入的那个bean是否配置或加了注解。
- 扫描路径是否正确。
- 有一种低级的错误,自己用new实例化spring bean,那获取属性肯定NPE。
- @Autowired是否加到了static字段上。
- 是不是在spring注入前使用属性,比如在无参构造函数里使用。
AOP
aop:面向切面编程,把oop代码之间的冗余逻辑拿出来放到切面当中,在需要执行时,通过动态代理,在不改变oop源码的基础上对方法进行功能增强。
- 当切面bean是一个切口时,spring使用jdk的动态代理。
- 否则使用cglib的动态代理。
JDK动态代理:基于反射生成【实现接口】的代理类。
CGLib动态代理:基于ASM(开源的Java字节码编辑库),通过修改被代理类的字节码来【生成子类】作为代理类。
常用场景包括权限认证、自动缓存、错误处理、日志、事务等。
aop的常用注解:
@Aspect:声明被注解的类是一个切面Bean。
@Pointcut、@After、@Before、@Around
一般流程:
- 通过@Pointcut定义切点(正则,配置包路径)
- @After、@Before、@Around为匹配指定切点的方法进行功能增强。