zoukankan      html  css  js  c++  java
  • Spring(一)——读源码前的理解

    iwehdio的博客园:https://www.cnblogs.com/iwehdio/

    学习自:

    1、基础

    BeanDefinition和BeanPostProcessor

    • BeanDefinition:

      • BeanDefinition其实是bean定义的一个顶级接口:一个BeanDefinition是用来描述一个bean实例的。

        image-20210130094543238

      • 功能其实就类似于用Class类描述一个类:BeanDefinition的实现类很多,这里以AbstractBeanDefinition为例,它实现了BeanDefinition。

        image-20210130094823443

      • Class只是描述了一个类有哪些字段、方法,但是无法描述如何实例化这个bean。BeanDefinition就完成了这些工作,比如:

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

        • 包限定的类名:通常,定义bean的实际实现类。
        • Bean行为配置:它声明Bean在容器中的行为(范围、生命周期回调,等等)。
        • Bean依赖:对其他Bean的引用。
        • 对当前Bean的一些设置:例如,池的大小限制或在管理连接池的bean中使用的连接数。
      • Spring解析<bean/>或者@Bean后,其实并不是就直接搞了一个bean存到一个大Map中。

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

      • 后置处理器处理的就是,如何将BeanDefinition取出实例化成Bean。

        • 比如,如果不加@Transactional,那么Controller层注入的就是普通的userServiceImpl,而加了注解以后返回的实际是代理对象。
      • 大部分人把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);
        

        image-20210130095750353

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

      • 拿ApplicationContext来讲,它也实现了BeanFactory接口,说明它其实也是一个容器。但是同为容器,与BeanFactory不同的是,ApplicationContext主要用来包含各种各样的组件,而不是存bean:

        image-20210130095931641

      • 后置处理器其实可以分好多种,属于Spring的扩展点之一:前三个BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是后置处理器。

        image-20210130095946261

      • BeanFactoryPostProcessor是处理BeanFactory的,所以存在ApplicationContext中。而BeanPostProcessor是处理Bean的,所以存在BeanFactory中。

        image-20210130100109680

      • 在普通Bean中如何注入ApplicationContext实例?

        • 除了利用Spring本身的IOC容器自动注入以外,还可以让Bean实现ApplicationContextAware接口:实现ApplicationContextAware接口,并实现setApplicationContext()方法,用成员变量去接收形参applicationContext。

        image-20210130100524537

        • 后期,Spring会调用setApplicationContext()方法传入ApplicationContext实例。
        • 此时此刻一个类实现ApplicationContextAware接口,有两层含义:
          • 作为后置处理器的判断依据,只有你实现了该接口我才处理你
          • 提供被后置处理器调用的方法

        image-20210130100903337

      • 自定义后置处理器,返回代理对象。

        @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;
            }
        }
        

    IOC和DI

    • IOC与DI:

      • IOC理解成一种思想,而DI是实现该思想的一种具体方式。Spring被称为IOC容器,它实现IOC的方式除了DI(Dependency Inject,依赖注入),其实还有DL(Dependency Look,依赖查找)。
      • 按照Spring官方文档的说法,Spring的容器配置方式可以分为3种:
        • Schema-based Container Configuration(XML配置)
          • 全部<bean>标签,但也可以配置自动装配
        • Annotation-based Container Configuration(注解)
          • XML+注解:XML+<context:component-scan>+@Component
          • JavaConfig+注解:@Configuration+@ComponentScan+@Component
        • Java-based Container Configuration(@Configuration配置类)
          • @Configuration+@Bean
      • 对于配置而言<bean>这个标签,其实承载着两个作用:
        • 定义bean,告诉Spring哪个Bean需要交给它管理(放入容器)
        • 维护bean与bean之间的依赖关系
      • 指定自动装配,可以使得bean标签的职责单一化:只表示放入容器。
        • byName/byType对应setter方法注入。
        • constructor对应构造方法注入。
      • 降低耦合度,可以直接用@Component表示将bean放入容器。
        • 使用<context:component-scan>隐式地启用了<context:annotation-config>的功能,让Spring具备解析@Component等注解的功能。
      • @Component,同时还需要指定自动装配:@Autowired@Resource@Inject
      • @Autowired默认采用byType模式自动装配,如果找到多个同类型的,会根据名字匹配。都不匹配,则会报错。
    • 如何把对象交给Spring管理:

      • 首先明确Spring Bean和Java Object的概念:

        • Bean指的是交给Spring管理、且在Spring中经历完整生命周期(创建、赋值、各种后置处理)的Java对象。
        • Object指的是我们自己new的、且没有加入Spring容器的Java对象。

        image-20210130110440542

      • 把Bean交给Spring管理的3种方式:

        • <bean>
        • @Component
        • @Configuration+@Bean

    2、Bean容器

    • 从ApplicationContext和BeanFactory开始:

      • 这两个都是接口,而且ApplicationContext继承了BeanFactory。

        image-20210130112129592

      • 看看BeanFactory:可以注意到的是,其中只有getXxx方法,而没有SetXxx方法。这是因为,BeanFactory接口内部并没有定义字段去存储bean,仅仅是提供了方法规范。

        image-20210130112238666

      • 真正存bean的工厂,DefaultSingletonBeanRegistry(默认的单例bean注册表)是专门来管理单例bean的。

        image-20210130112611100

    • 容器在哪?

      • 最重要的三个成员变量:
        • singletonObjects(真正存放单例bean的Map)
        • earlySingletonObjects
        • singletonFactories

      image-20210130112800193

      • 作为专门管理单例bean的工厂,它如何保证单例?

        image-20210130112907303

        • DefaultSingletonBeanRegistry搞了一个Set<String> singletonsCurrentlyInCreation,专门来存放正在创建的单例bean的名字(注意,只是名字而不是bean,因为bean还在创建中)。
        • 一个单例bean在创建前,先往singletonsCurrentlyInCreation存自己的name,其他bean在创建时,会先来这里确认有无同名bean

        image-20210130113010716

    • 如何存取?

      image-20210130113248884

      • 但是,DefaultSingletonBeanRegistry没有getBean()方法,因为它压根就没实现BeanFactory!!

      • 但是,AnnotationConfigApplicationContext#getBean()方法,经过层层调用最终还是找到了DefaultSingletonBeanRegistry的getSingleton()。

      • 实际上,getBean()方法获取到单例并非是通过继承的方式,而是通过聚合的方式。

        • 具体的是在GenericApplicationContext中,一个DefaultListableBeanFactory字段。

          image-20210130114020129

      • AnnotationConfigApplicationContext#getBean()整个过程应该是这样的:

        image-20210130114040646

      • DefaultListableBeanFactory作为中介,继承了DefaultSingletonBeanRegistry得以访问单例池,实现了BeanFactory对外提供getBean方法。

        image-20210130114208468

      • DefaultListableBeanFactory是ApplicationContext与SingletonBeanRegistry的中介:

        image-20210130114340184

        image-20210130114500347

    • ApplicationContext与BeanFactory:

      image-20210130114956146

      • 除了BeanFactory,ApplicationContext还继承了很多乱七八糟的接口来扩展自己的功能,比如:
        • ResourcePatternResolver接口,继承自ResourceLoader,我们常说的包扫描就是它负责的
        • ApplicationEventPublisher接口,关系着Spring的监听机制
        • EnvironmentCapable接口,提供了获取环境变量的方法。环境变量意味着什么?关系着@PropertySource、@Value、@Profile等注解
      • 官方文档对ApplicationContext的解释:
        • Theorg.springframework.beansorg.springframework.context包是Spring Framework的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。
        • ApplicationContext是BeanFactory的子接口,扩展了以下几点:
          • 更容易与Spring的AOP特性集成
          • 消息资源处理(用于国际化)
          • 事件发布
          • 应用程序层特定的上下文,如web应用程序中使用的WebApplicationContext。
      • 简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整超集。

    3、IOC源码的前置知识

    • id和name:每个 Bean 在 Spring 容器中都有一个唯一的名字(beanName)和 0 个或多个别名(aliases)。

      我们从 Spring 容器中获取 Bean 的时候,可以根据 beanName,也可以通过别名。

      beanFactory.getBean("beanName or alias");
      
      <bean id="messageService" name="m1, m2, m3" class="com.javadoop.example.MessageServiceImpl">
      

      以上配置的结果就是:beanName 为 messageService,别名有 3 个,分别为 m1、m2、m3。

      <bean name="m1, m2, m3" class="com.javadoop.example.MessageServiceImpl" />
      

      以上配置的结果就是:beanName 为 m1,别名有 2 个,分别为 m2、m3。

      <bean class="com.javadoop.example.MessageServiceImpl">
      

      beanName 为:com.javadoop.example.MessageServiceImpl#0,

      别名 1 个,为: com.javadoop.example.MessageServiceImpl

      <bean id="messageService" class="com.javadoop.example.MessageServiceImpl">
      

      以上配置的结果就是:beanName 为 messageService,没有别名。

    • FactoryBean:

      • 是 Spring 中的特殊接口,代表一类特殊的 Bean。
      • 适用于 Bean 的创建过程比较复杂的场景,比如数据库连接池的创建。
      public interface FactoryBean<T> {
          T getObject() throws Exception;
          Class<T> getObjectType();
          boolean isSingleton();
      }
      
      • FactoryBean类型获取到的是参数类型T,可以直接将FactoryBean注入类型为T的属性。比如ref="factorybean"
    • 初始化Bean的回调:共有四种方案。

      • <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
        
      • public class AnotherExampleBean implements InitializingBean {
        
            public void afterPropertiesSet() {
                // do some initialization work
            }
        }
        
      • @Bean(initMethod = "init")
        public Foo foo() {
            return new Foo();
        }
        
      • @PostConstruct
        public void init() {
        
        }
        
    • 销毁Bean的回调,也是对应的。

      • <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
        
      • public class AnotherExampleBean implements DisposableBean {
        
            public void destroy() {
                // do some destruction work (like releasing pooled connections)
            }
        }
        
      • @Bean(destroyMethod = "cleanup")
        public Bar bar() {
            return new Bar();
        }
        
      • @PreDestroy
        public void cleanup() {
        
        }
        
    • Bean的继承:

      • 涉及到的就是 <bean parent="" /> 中的 parent 属性。这里的继承和 java 语法中的继承没有任何关系,不过思路是相通的。child bean 会继承 parent bean 的所有配置,也可以覆盖一些配置,当然也可以新增额外的配置。

      • Spring 中提供了继承自 AbstractBeanDefinition 的 ChildBeanDefinition 来表示 child bean。

      • 例如:

        <bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean">
            <property name="name" value="parent"/>
            <property name="age" value="1"/>
        </bean>
        
        <bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean"
                parent="inheritedTestBean" init-method="initialize">
        
            <property name="name" value="override"/>
        </bean>
        
      • parent bean 设置了 abstract="true" 所以它不会被实例化,child bean 继承了 parent bean 的两个属性,但是对 name 属性进行了覆写。

      • child bean 会继承 scope、构造器参数值、属性值、init-method、destroy-method 等等。

      • 当然,我不是说 parent bean 中的 abstract = true 在这里是必须的,只是说如果加上了以后 Spring 在实例化 singleton beans 的时候会忽略这个 bean。

    • 方法注入:

      • 一般来说,我们的应用中大多数的 Bean 都是 singleton 的。singleton 依赖 singleton,或者 prototype 依赖 prototype 都很好解决,直接设置属性依赖就可以了。
      • 但是,如果是 singleton 依赖 prototype 呢?这个时候不能用属性依赖,因为如果用属性依赖的话,我们每次其实拿到的还是第一次初始化时候的 bean。
      • 一种解决方案就是不要用属性依赖,每次获取依赖的 bean 的时候从 BeanFactory 中取。
      • 另一种解决方案就是这里要介绍的通过使用 Lookup method。
      public abstract class CommandManager {
      
          public Object process(Object commandState) {
              MyCommand command = createCommand();
              command.setState(commandState);
              return command.execute();
          }
      
          @Lookup("myCommand")
          protected abstract Command createCommand();
      }
      
    • replaced-method:

    • 它的功能,就是替换掉 bean 中的一些方法。

    public class MyValueCalculator {
    
        public String computeValue(String input) {
            // some real code...
        }
    
        // some other methods...
    }
    
    • 方法覆写,注意要实现 MethodReplacer 接口:
    public class ReplacementComputeValue implements org.springframework.beans.factory.support.MethodReplacer {
    
        public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
            // get the input value, work with it, and return a computed result
            String input = (String) args[0];
            ...
            return ...;
        }
    }
    
    • 配置:
    	<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
        <!-- 定义 computeValue 这个方法要被替换掉 -->
        <replaced-method name="computeValue" replacer="replacementComputeValue">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>
    
    <bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
    

    4、AOP

    • 掌握AOP所需的重要概念:

      • 通知方法
      • 目标对象
      • 切面
      • 切点表达式
    • 对于动态代理而言,通知方法就是增强部分(比如打印日志),目标对象就是执行被代理对象的方法。

    • 单纯的动态代理并没有所谓“切面”和“切点表达式”的概念,这两个概念是Spring AOP对动态代理的扩展。

      • 所谓的切面,其实就是把所有通知方法集中起来,都放在一个类中,这个类叫做切面类。

      image-20210130120701314

      • 所谓的切入点表达式,其实就是制定目标对象和通知方法之间的对应规则。即,那些被代理对象需要那些增强,进而让Spring完成自动代理。
      • 通过切入点表达式,使得获得代理从硬编码方式改为了表达式方式。
      • 总而言之,切面类是为了统一管理通知方法,切点表达式定制哪些对象需要代理,以及如何代理(用哪个通知方法增强)。

    iwehdio的博客园:https://www.cnblogs.com/iwehdio/
    来源与结束于否定之否定。
  • 相关阅读:
    一行code实现ADO.NET查询结果映射至实体对象。
    傻瓜式使用AutoFac
    Asp.Net MVC中捕捉错误路由并设置默认Not Found页面。
    asp.net MVC中实现调取web api
    JavaScript_11_验证
    JavaScript_10_错误
    JavaScript_9_循环
    JavaScript_8_比较,条件语句
    JavaScript_7_运算符
    JavaScript_6_函数
  • 原文地址:https://www.cnblogs.com/iwehdio/p/14348670.html
Copyright © 2011-2022 走看看