写点什么

我这么回答对 Spring 的理解,面试官狂问我什么时候入职?

作者:Java高工P7
  • 2021 年 11 月 12 日
  • 本文字数:3555 字

    阅读完需:约 12 分钟

这个切面,可以是日志,也可以是事务


交叉业务的编程问题即为面向切面编程。AOP 的目标就是使交叉业务模块化。做法是将切面代码移动到原始方法的周围:



原先不用 AOP 时(图一),交叉业务的代码直接硬编码在方法内部的前后,而 AOP 则是把交叉业务写在方法调用前后。那么,为什么 AOP 不把代码也写在方法内部的前后呢?两点原因:


  • 首先,这与 AOP 的底层实现方式有关:动态代理其实就是代理对象调用目标对象的同名方法,并在调用前后加增强代码。



  • 其次,这两种最终运行效果是一样的,所以没什么好纠结的。


而所谓的模块化,我个人的理解是将切面代码做成一个可管理的状态。比如日志打印,不再是直接硬编码在方法中的零散语句,而是做成一个切面类,通过通知方法去执行切面代码。


我相信大部分培训班出来的朋友也就言尽于此,讲完上面内容就准备收拾打卡下班了。


怎么说呢,IOC 按上面的解释,虽然很浅,但也马马虎虎吧。然而 AOP,很多人对它的认识是非常片面的...


这样吧,我问你一个问题,现在我自己写了一个 UserController,以及 UserServiceImplimplements UserService,并且在 UserController 中注入 Service 层对象:


@Autowired


private UserService userService;


那么,这个 userService 一定是我们写的 UserServiceImpl 的实例吗?


如果你听不懂我要问什么,说明你对 Spring 的 AOP 理解还是太少了。


实际上,Spring 依赖注入的对象并不一定是我们自己写的类的实例,也可能是 userService


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


Impl 的代理对象。下面分别演示这两种情况:


  • 注入 userServiceImpl 对象



注入的是 UserServiceImpl 类型


  • 注入 userServiceImpl 的代理对象(CGLib 动态代理)



注入的是 CGLib 动态代理生成的 userServiceImpl 的代理对象


为什么两次注入的对象不同?


因为第二次我给 UserServiceImpl 加了 @Transactional 注解。



此时 Spring 读取到这个注解,便知道我们要使用事务。而我们编写的 UserService 类中并没有包含任何事务相关的代码。如果给你,你会怎么做?动态代理嘛!


看到这里,我仿佛听到有一部分兄弟默默说了句:我 C...


但是,上面对 IOC 和 AOP 的理解,也仅仅是应用级别,是一个面。仅仅了解到这个程度,对 Spring 的了解还是非常扁平的,不够立体。


Spring 说,万物皆可定义


==============


上帝说,要有光。于是特斯拉搞出了交流电。


Java 说,万物皆对象。但是 Spring 另外搞了 BeanDefinition...


什么 BeanDefinition 呢?其实它是 bean 定义的一个顶级接口:



正如 BeanDefinition 的类注释所言:一个 BeanDefinition 是用来描述一个 bean 实例的


哎呀卧 C,啥玩意啊。描述一个 bean 实例?我咋想起了 Class 类呢


其实,两者并没有矛盾。



Class 只是描述了一个类有哪些字段、方法,但是无法描述如何实例化这个 bean!如果说,Class 类描述了一块猪肉,那么 BeanDefinition 就是描述如何做红烧肉:


  • 单例吗?

  • 是否需要延迟加载?

  • 需要调用哪个初始化方法/销毁方法?


大部分初学者以为 Spring 解析<bean/>或者 @Bean 后,就直接搞了一个 bean 存到一个大 Map 中,其实并不是。


  • Spring 首先会扫描解析指定位置的所有的类得到 Resources(可以理解为.Class 文件)

  • 然后依照 TypeFilter 和 @Conditional 注解决定是否将这个类解析为 BeanDefinition

  • 稍后再把一个个 BeanDefinition 取出实例化成 Bean


就好比什么呢?你从海里钓了一条鱼,但是你还没想好清蒸还是红烧,那就干脆先晒成鱼干吧。一条咸鱼,其实蕴藏着无限可能,因为它可能会翻身!


默默付出的后置处理器


==========


接下来,我们讨论一下咸鱼如何翻身。


最典型的例子就是 AOP。上面 AOP 的例子中我说过了,如果不加 @Transactional,那么 Controller 层注入的就是普通的 userServiceImpl,而加了以后返回的实际是代理对象。


为什么要返回代理对象?因为我们压根就没在 UserServiceImpl 中写任何 commit 或者 rollback 等事务相关的代码,但是此时此刻代理对象却能完成事务操作。毫无疑问,这个代理对象已经被 Spring 加了佐料。


那么 Spring 是何时何地加佐料的呢?说来话长。


大部分人把 Spring 比作容器,其实潜意识里是将 Spring 完全等同于一个 Map 了。其实,真正存单例对象的 map,只是 Spring 中很小很小的一部分,仅仅是 BeanFactory 的一个字段,我更习惯称它为“单例池”。


/** Cache of singleton objects: bean name --> bean instance */


private final Map<String, Object> singletonObjects =new ConcurrentHashMap<String, Object>(256);



这里的 ApplicationContext 和 BeanFactory 是接口,实际上都有各自的子类。比如注解驱动开发时,Spring 中最关键的就是


AnnotationConfigApplicationContext 和 DefaultListableBeanFactory。


所以,很多人把 Spring 理解成一个大 Map,还是太浅了。还是太浅了。就拿 ApplicationContext 来讲,它也实现了 BeanFactory 接口,但是作为容器,其实它是用来包含各种各样的组件的,而不是存 bean:



那么,Spring 是如何给咸鱼加佐料(事务代码的织入)的呢?关键就在于后置处理器。


后置处理器其实可以分好多种,属于 Spring 的扩展点之一。我从最高赞那边抄了一幅图,请不要告诉它:



引用:https://www.zhihu.com/question/48427693/answer/691483076


上面 BeanFactory、


BeanDefinitionRegistryPostProcessor、BeanPostProcessor 都算是后置处理器,这里篇幅有限,只介绍一下 BeanPostProcessor。



BeanFactoryPostProcessor 是用来干预 BeanFactory 创建的,而 BeanPostProcessor 是用来干预 Bean 的实例化。不知道大家有没有试过在普通 Bean 中注入 ApplicationContext 实例?你第一时间想到的是:


@Autowired


ApplicationContext annotationConfigApplicationContext;


除了利用 Spring 本身的 IOC 容器自动注入以外,你还有别的办法吗?


我们可以让 Bean 实现 ApplicationContextAware 接口:



后期,Spring 会调用 setApplicationContext()方法传入 ApplicationContext 实例。


Spring 官方文档:一般来说,您应该避免使用它,因为它将代码耦合到 Spring 中,并且不遵循控制反转样式。


这是我认为 Spring 最牛逼的地方:代码具有高度的可扩展性,甚至你自己都懵逼,为什么实现了一个接口,这个方法就被莫名其妙调用,还传进了一个对象...


这其实就是后置处理器的工作!


什么意思呢?


就是说啊,明面上我们看得见的地方只要实现一个接口,但是背地里 Spring 在自己框架的某一处搞了个 for 循环,遍历所有的 BeanPostProcessor,其中就包括处理实现了 ApplicationContextAware 接口的 bean 的后置处理器:


ApplicationContextAwareProcessor。


上面这句话有点绕,大家停下来多想几遍。



也就是说,要扩展的类是不确定的,但是处理扩展类的流程是写死的。总有一个要定下来吧。也就是说,在这个 Bean 实例化的某一紧要处,必然要经过很多 BeanPostProcessor。但是,BeanPostProcessor 也不是谁都处理,有时也会做判断。比如:


if (bean instanceof Aware) {


if (bean instanceof EnvironmentAware) {


if (bean instanceof EnvironmentAware) {getEnvironment());


}


if (bean instanceof EmbeddedValueResolverAware) {


((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);


}


if (bean instanceof ResourceLoaderAware) {


((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);


}


if (bean instanceof ApplicationEventPublisherAware) {


((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);


}


if (bean instanceof MessageSourceAware) {


((MessageSourceAware) bean).setMessageSource(this.applicationContext);


}


if (bean instanceof ApplicationContextAware) {


((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);


}


}


所以,此时此刻一个类实现 ApplicationContextAware 接口,有两层含义:


  • 作为后置处理器的判断依据,只有你实现了该接口我才处理你

  • 提供被后置处理器调用的方法



利用后置处理器返回代理对象


=============


大致了解 Spring Bean 的创建流程后,接下来我们尝试着用 BeanPostProcessor 返回当前 Bean 的代理对象。


pom.xml


<dependencies>


<dependency>


<groupId>org.springframework</groupId>


<artifactId>spring-context</artifactId>


<version>4.3.12.RELEASE</version>


</dependency>


</dependencies>


AppConfig


@Configuration //JavaConfig 方式,即当前配置类相当于一个 applicationConotext.xml 文件


@ComponentScan //默认扫描当前配置类(AppConfig)所在包及其子包


public class AppConfig {


}


Calculator


public interface Calculator {


public void add(int a, int b);


}


CalCulatorImpl


@Component


public class CalculatorImpl implements Calculator {


public void add(int a, int b) {


System.out.println(a+b);


}


}


后置处理器 MyAspectJAutoProxyCreator


使用步骤:

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
我这么回答对Spring的理解,面试官狂问我什么时候入职?