Spring基础(1):两个概念-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

最近看了点Spring的源码于是来稍微扯一扯希望能帮一部分培训班出身的朋友撕开一道口子透透气。

广义上的Spring指的是Spring整个项目包含SpringBoot、SpringCloud、SpringFramework、SpringData等等

本系列文章只讨论狭义上的Spring也就是SpringFrameWork。

主要内容

  • 盲点
  • Spring说万物皆可定义
  • 默默付出的后置处理器
  • 利用后置处理器返回代理对象

盲点

如果你恰好非科班转行且从未独立看过源码那么你很可能至今都不曾注意某两个概念。

你以为我会说IOC和AOPNO。

看到这里一部分读者心里一惊卧槽说的啥玩意Spring不就IOC和AOP吗这两个都不说你这篇文章为啥能写这么长

不错我就是这么长。其实我要讲的是

  • BeanDefinition
  • BeanPostProcessor

大部分人一听到“请你谈谈对Spring的理解”就会下意识地搬出IOC和AOP两座大山赶紧糊弄过去。大概是这样的

IOC

所谓的控制反转。通俗地讲就是把原本需要程序员自己创建和维护的一大堆bean统统交由Spring管理。

也就是说Spring将我们从盘根错节的依赖关系中解放了。当前对象如果需要依赖另一个对象只要打一个@Autowired注解Spring就会自动帮你安装上。

AOP

所谓的面向切面编程。通俗地讲它一般被用来解决一些系统交叉业务的织入比如日志啦、事务啥的。打个比方UserService的method1可能要打印日志BrandService的method2可能也需要。亦即一个交叉业务就是要切入系统的一个方面。具体用代码展示就是

AOP图一这个切面可以是日志也可以是事务

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

AOP图二

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

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

InvocationHandler介于代理对象和目标对象中间作用有两个1.衔接调用链 2.存放增强代码

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

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

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

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

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

@Autowired
private UserService userService;

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

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

实际上Spring依赖注入的对象并不一定是我们自己写的类的实例也可能是userServiceImpl的代理对象。下面分别演示这两种情况

  • 注入userServiceImpl对象

注入的是UserServiceImpl类型

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

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

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

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

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

动态代理嘛

上面说了InvocationHandler作用有两个1.衔接调用链 2.存放增强代码。

用动态代理在InvocationHandler的invoke()中开启关闭事务即可完成事务控制。

看到这里我仿佛听到有一部分兄弟默默说了句卧槽原来是这样...

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


Spring说万物皆可定义

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

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

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

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

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

其实两者并没有矛盾。

BeanDefinition的实现类很多这里仅以AbstractBeanDefinition为例它实现了BeanDefinition

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

  • 单例吗
  • 是否需要延迟加载
  • 需要调用哪个初始化方法/销毁方法
在容器内部这些bean定义被表示为BeanDefinition对象包含以下元数据

1.包限定的类名通常定义bean的实际实现类。
2.Bean行为配置它声明Bean在容器中的行为(范围、生命周期回调等等)。
3.Bean依赖对其他Bean的引用。
4.对当前Bean的一些设置例如池的大小限制或在管理连接池的bean中使用的连接数。
——Spring官方文档

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

  • Spring首先会扫描解析指定位置的所有的类得到Resources可以理解为读取.Class文件
  • 然后依照TypeFilter和@Conditional注解决定是否将这个类解析为BeanDefinition
  • 稍后再把一个个BeanDefinition取出实例化成Bean

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


默默付出的后置处理器

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

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

为什么Spring要返回代理对象因为我们压根就没在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接口说明它其实也是一个容器。但是同为容器与BeanFactory不同的是ApplicationContext主要用来包含各种各样的组件而不是存bean

ApplicationContext的部分组件示意图包括Bean工厂

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

后置处理器其实可以分好多种属于Spring的扩展点之一

前三个BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是后置处理器这里篇幅有限暂且先只介绍一下BeanPostProcessor。

BeanFactoryPostProcessor是处理BeanFactory的所以存在ApplicationContext中。而BeanPostProcessor是处理Bean的所以存在BeanFactory中请务必注意

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

@Autowired
ApplicationContext annotationConfigApplicationContext;

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

我们可以让Bean实现ApplicationContextAware接口

实现ApplicationContextAware接口并实现setApplicationContext()方法用成员变量去接收形参applicationContext

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

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

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

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

什么意思呢

也就是说虽然表面上在我们只要让Bean实现一个接口就能完成ApplicationContext组件的注入看起来很简单但是背地里Spring做了很多事情。Spring会在框架的某一处搞个for循环遍历当前容器中所有的BeanPostProcessor其中就包括一个叫ApplicationContextAwareProcessor的后置处理器它的作用是处理实现了ApplicationContextAware接口的Bean。

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

Spring Bean的生命周期创建过程必然经过BeanPostProcessor

要扩展的类Bean是不确定的但是处理扩展类的流程循环BeanPostProcessor是写死的。因为一个程序再怎么高度可扩展总有一个要定下来吧。也就是说在这个Bean实例化的某一紧要处必然要经过很多BeanPostProcessor。但是BeanPostProcessor也不是谁都处理有时也会做判断。比如

if (bean instanceof Aware) {
    if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.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方式即当前配置类相当于一个applicationContext.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

使用步骤

  1. 实现BeanPostProcessor
  2. @Component加入Spring容器
@Component
public class MyAspectJAutoProxyCreator implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        final Object obj = bean;
        //如果当前经过BeanPostProcessors的Bean是Calculator类型我们就返回它的代理对象
        if (bean instanceof Calculator) {
           Object proxyObj = Proxy.newProxyInstance(
                    this.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("开始计算....");
                            Object result = method.invoke(obj, args);
                            System.out.println("结束计算...");
                            return result;
                        }
                    }
            );
           return proxyObj;
        }
        //否则返回本身
        return obj;
    }
}

测试类

public class TestPostProcessor {
    public static void main(String[] args) {

        System.out.println("容器启动成功");
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        //打印当前容器所有BeanDefinition
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }

        System.out.println("============");
        
        //取出Calculator类型的实例调用add方法
        Calculator calculator = (Calculator) applicationContext.getBean(Calculator.class);
        calculator.add(1, 2);
}

先把MyAspectJAutoProxyCreator的@Component注释掉此时Spring中没有我们自定义的后置处理器那么返回的就是CalculatorImpl

把@Component加上此时MyAspectJAutoProxyCreator加入到Spring的BeanPostProcessors中会拦截到CalculatorImpl并返回代理对象

代理对象的add()方法被增强前后打印日志


本文是Spring源码系列的第一篇仅仅是介绍了两个重要概念BeanDefinition和BeanPostProcessor。更详细的内容 比如Bean的生命周期流程及其它后置处理器的介绍以后有机会再慢慢更新。通过本文大家只要有朦胧的Spring Bean生命周期的概念以及知道BeanDefinition和BeanPostProcessor即可。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring