浅谈对Spring的理解

本文主要针对Spring Framework做一些简单的理解

前言

基本上我们一谈及对Spring的理解,很多人第一时间想到的就是IOC和AOP。但是,本文将从其他角度出发,先说一说

  • BeanDefinition
  • BeanPostProcessor

说到IOC和AOP,那我们简单回顾一下:

1. IOC

所谓的控制反转。通俗地讲,就是把原本需要程序员自己创建和维护的一大堆bean统统交由Spring管理。
在这里插入图片描述
也就是说,Spring将我们从盘根错节的依赖关系中解放了。当前对象如果需要依赖另一个对象,只要打一个@Autowired注解,Spring就会自动帮你安装上。
在这里插入图片描述

2. AOP

专业术语:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗解释:

砧板上有一捆面,一刀切下去,刀面就是切面,每一条面(方法)都经过刀面(切面Aspect),面切断处就是切点(Pointcut)。
小区都有保安室,每个居民无论“进”还是“出”都要经过保安室,这个时候保安可以进行一些检查等操作。保安室就是切面。
编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

在实际的开发场景中,我们通常都要对方法进行异常拦截、权限控制、日志记录等操作,如果按部就班的将这些操作全部写在方法内部,那么有100个方法就得写100分重复代码。
在这里插入图片描述
如果将这些操作抽取出来,注入到接口调用的某个地方(切点)
在这里插入图片描述
这样接口只需要关心具体的功能业务,而不需要关注其他非该接口关注的逻辑或处理。

红框处,就是面向切面编程。

相关概念:
这里还是先给出一个比较专业的概念定义:

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组joint point,这些joint point或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice将要发生的地方。
  • Advice(增强):Advice 定义了在Pointcut里面定义的程序点具体要做的操作,它通过before、after和around来区别是在每个 joint point之前、之后还是代替执行的代码。
  • Target(目标对象):织入Advice的目标对象。
  • Weaving(织入):将Aspect和其他对象连接起来, 并创建Advice object 的过程。

在这里插入图片描述

BeanDefinition

什么是BeanDefinition呢?其实它是bean定义的一个顶级接口,用来描述一个bean实例的。

在这里插入图片描述
既然是描述一个bean实例,那和Class类有什么区别呢?
其实两者并没有矛盾。
在这里插入图片描述
Class只是描述了一个类有哪些字段、方法,但是无法描述如何实例化这个bean!如果说,Class类描述了一块猪肉,那么BeanDefinition就是描述如何做红烧肉:

  • 是否单例?
  • 是否需要延迟加载?
  • 需要调用哪个初始化方法/销毁方法?

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

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

就好比你钓了一条鱼,你还没想好清蒸还是红烧,那就干脆先晒成鱼干。

后置处理器

鱼干如何再做成清蒸/红烧鱼?最典型的例子就是AOP

先说一个例子,写一个UserController,以及UserServiceImpl implements UserService,并且在UserController中注入Service层对象:

@Autowired
private UserService userService;

这时userService一定是我们写的UserServiceImpl的实例吗?

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

下面分别演示这两种情况:

  1. 注入userServiceImpl对象
    在这里插入图片描述
  2. 注入userServiceImpl的代理对象(CGLib动态代理)
    在这里插入图片描述
    为什么两次注入的对象不同?
    因为第二次我给UserServiceImpl类上加了@Transactional 注解。
    此时Spring读取到这个注解,便知道我们要使用事务。而我们编写的UserService类中并没有包含任何事务相关的代码。如果给你,你会怎么做?动态代理嘛!

上面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的扩展点之一。
在这里插入图片描述
上面BeanFactory、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是后置处理器,这里只介绍一下BeanPostProcessor。

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

@Autowired
ApplicationContext applicationContext;

除了利用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) {
        ((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的代理对象。

  1. pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.12.RELEASE</version>
    </dependency>
</dependencies>
  1. AppConfig
@Configuration //JavaConfig方式,即当前配置类相当于一个applicationConotext.xml文件
@ComponentScan //默认扫描当前配置类(AppConfig)所在包及其子包
public class AppConfig {

}
  1. Calculator
public interface Calculator {
    public void add(int a, int b);
}
  1. CalCulatorImpl
@Component
public class CalculatorImpl implements Calculator {
    public void add(int a, int b) {
        System.out.println(a+b);
    }
}
  1. 后置处理器MyAspectJAutoProxyCreator

使用步骤:

i. 实现BeanPostProcessor
ii. 添加@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,并返回代理对象:
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值