Spring Bean 生命周期

问题

  • spring 如何产生Bean
  • @Configuration注解的作用是什么,Spring是如何解析加了@Configuration注解的类?
  • Spring在什么时候对@ComponentScan、@ComponentScans注解进行了解析?
  • Spring什么时候解析了@Import注解,如何解析的?
  • Spring什么时候解析了@Bean注解?

如何产生bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yanwen.springTest;

import com.yanwen.springTest.service.MemberService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

@ComponentScan("com.yanwen.springTest")
@Configuration
public class AppConfig {
public static void main(String[] args){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(annotationConfigApplicationContext.getBean(MemberService.class));
}
}

package com.yanwen.springTest.service;

import org.springframework.stereotype.Service;

@Service
public class MemberService {
}

如何在spring源码中新建model及创建上面的代码
关于ApplicationContext
spring实例化过程.png
以上代码:

  • 从一个Java的配置类中加载Spring应用上下文(AnnotationConfigApplicationContext)。
  • 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。
  • 对于单实例(singleton)的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean时直接从IoC容器缓存中获取Bean。

启动过程分析

记住,一定要在电脑中打开源码,不然纯看是很累的。
spring源码注释
分支是springcode

Spring bean 默认是单例
为了弄清楚Bean是怎么来的,花费了大把功夫,现在要把Bean Definition的加载、解析、处理、注册到bean工厂的过程记下来

  • AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    实例化AnnotationConfigApplicationContext(Class<?>… componentClasses
    • this()
    • register(componentClasses);
    • refresh();
      • prepareRefresh();
        刷新前的预处理;
      • ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        获取BeanFactory;默认实现是DefaultListableBeanFactory,在创建容器的时候创建的
      • prepareBeanFactory(beanFactory);
        BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器,BeanPostProcessor和XXXAware自动装配等)
      • postProcessBeanFactory(beanFactory);
        BeanFactory准备工作完成后进行的后置处理工作
      • invokeBeanFactoryPostProcessors(beanFactory);
        执行BeanFactoryPostProcessor的方法;
      • registerBeanPostProcessors(beanFactory);
        注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
      • initMessageSource();
        初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
      • initApplicationEventMulticaster();
        初始化事件派发器
      • onRefresh();
        子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
      • registerListeners();
        注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean,这些监听器是注册到ApplicationEventMulticaster中的
      • finishBeanFactoryInitialization(beanFactory);
        初始化所有剩下的非懒加载的单例bean
      • finishRefresh();
        完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)

AnnotationConfigApplicationContext构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

private final AnnotatedBeanDefinitionReader reader;

private final ClassPathBeanDefinitionScanner scanner;

   /**
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
   这句代码调用以下方法
根据参数类型可以知道,其实可以传入多个annotatedClasses,但是这种情况出现的比较少
 */
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    //详细看AnnotationConfigApplicationContext 无参构造
this();
register(componentClasses);
refresh();
}
   
  public AnnotationConfigApplicationContext() {
////调用父类GenericApplicationContext无参构造函数,初始化一个BeanFactory: DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory()
//在IOC容器中初始化一个 注解bean读取器AnnotatedBeanDefinitionReader
this.reader = new AnnotatedBeanDefinitionReader(this);
//在IOC容器中初始化一个 按类路径扫描注解bean的 扫描器
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

}
  • 调用无参构造函数,会先调用父类GenericApplicationContext的构造函数
    • 父类的构造函数里面就是初始化DefaultListableBeanFactory,并且赋值给beanFactory
      • 本类的构造函数里面,初始化了一个读取器:AnnotatedBeanDefinitionReader read,一个扫描器ClassPathBeanDefinitionScanner scanner
    • scanner的用处不是很大,它仅仅是在我们外部手动调用 .scan 等方法才有用,常规方式是不会用到scanner对象的

GenericApplicationContext 构造方法

1
2
3
4
5
6
7
8
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
public GenericApplicationContext() {
   //初始化一个BeanFactory
this.beanFactory = new DefaultListableBeanFactory();
}

}

DefaultListableBeanFactory 构造方法

默认实现了ListableBeanFactory和BeanDefinitionRegistry接口,基于bean definition对象,是一个成熟的bean factroy。

最典型的应用是:在访问bean前,先注册所有的definition(可能从bean definition配置文件中)。使用预先建立的bean定义元数据对象,从本地的bean definition表中查询bean definition因而将不会花费太多成本。

DefaultListableBeanFactory既可以作为一个单独的beanFactory,也可以作为自定义beanFactory的父类。
DefaultListableBeanFactory类图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
       //存储BeanDefinition对象
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

  /** 存储 spring bean 单例对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Create a new DefaultListableBeanFactory.
*/
public DefaultListableBeanFactory() {
super();
}
}
AbstractAutowireCapableBeanFactory构造方法
1
2
3
4
5
6
7
8
public AbstractAutowireCapableBeanFactory() {

super();
//自动装配时忽略指定接口或类的依赖注入
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
AbstractBeanFactory
1
2
3
4
5
/**
* Create a new AbstractBeanFactory.
*/
public AbstractBeanFactory() {
}

//初始化一个Bean读取器AnnotatedBeanDefinitionReader

1
this.reader = new AnnotatedBeanDefinitionReader(this);

AnnotatedBeanDefinitionReader构造方法

1
2
3
4
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
//这里的BeanDefinitionRegistry当然就是AnnotationConfigApplicationContext的实例了
this(registry, getOrCreateEnvironment(registry));
}

AnnotatedBeanDefinitionReader 方法

1
2
3
4
5
6
7
8
public AnnotatedBeanDefinitionReader (BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
//让我们把目光移动到这个方法的最后一行,进入registerAnnotationConfigProcessors方法:
this.registry 是    AnnotationConfigApplicationContext的实例了  AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

AnnotationConfigUtils.registerAnnotationConfigProcessors

1
2
3
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
registerAnnotationConfigProcessors(registry, null);
}

在看registerAnnotationConfigProcessors方法

  • 这里会一连串注册好几个Bean,在这其中最重要的一个Bean(没有之一)就是BeanDefinitionRegistryPostProcessor Bean。
    * ConfigurationClassPostProcessor实现BeanDefinitionRegistryPostProcessor接口,
    * BeanDefinitionRegistryPostProcessor接口又扩展了BeanFactoryPostProcessor接口,
    * BeanFactoryPostProcessor是Spring的扩展点之一,
    *  ConfigurationClassPostProcessor是Spring极为重要的一个类,必须牢牢的记住上面所说的这个类和它的继承关系。
    * 除了注册了ConfigurationClassPostProcessor,还注册了其他Bean,其他Bean也都实现了其他接口,比如BeanPostProcessor等。
    * BeanPostProcessor接口也是Spring的扩展点之一。
    

执行完方法
beanDefs 有5个

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • EventListenerMethodProcessor
  • DefaultEventListenerFactory
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    // this.registry 是    AnnotationConfigApplicationContext的实例了   
    source=null
    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {
              //这里返回AnnotationConfigApplicationContext对象中DefaultListableBeanFactory类实例
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    BeanDefinition if (beanFactory != null) {

    if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
    //用来支持Spring的Ordered类、@Order注解和@Priority注解。
    beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
    }

    if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
    }
    }

    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

    /**
    * 注册解析我们配置类的后置处理器ConfigurationClassPostProcessor
    * org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    */
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    //如果不存在(当然这里肯定是不存在的),就通过RootBeanDefinition的构造方法获得ConfigurationClassPostProcessor的BeanDefinition,RootBeanDefinition是BeanDefinition的子类
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    /**
    * 注册处理@Autowired 注解的处理器AutowiredAnnotationBeanPostProcessor
    *
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    */
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    /**
    * 注册处理JSR规范的注解处理器CommonAnnotationBeanPostProcessor
    * org.springframework.context.annotation.internalCommonAnnotationProcessor
    */
    // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    /**
    * 处理jpa注解的处理器org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
    */
    // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition();
    try {
    def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
    AnnotationConfigUtils.class.getClassLoader()));
    }
    catch (ClassNotFoundException ex) {
    throw new IllegalStateException(
    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
    }
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    /**
    * 处理监听方法的注解解析器EventListenerMethodProcessor
    */
    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    }
    /**
    * 注册事件监听器工厂
    */
    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
    }

    return beanDefs;
ConfigurationClassPostProcessor

ConfigurationClassPostProcessor类图

* ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。
* ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,而 BeanDefinitionRegistryPostProcessor 接口继承了 BeanFactoryPostProcessor 接口,所以 ConfigurationClassPostProcessor 中需要重写 postProcessBeanDefinitionRegistry() 方法和 postProcessBeanFactory() 方法。而ConfigurationClassPostProcessor类的作用就是通过这两个方法去实现的。
* ConfigurationClassPostProcessor这个类是Spring内置的一个BeanFactory后置处理器,是在this()方法中将其添加到BeanDefinitionMap中的
* [postProcessBeanDefinitionRegistry](#PostProcessorRegistrationDelegate-invokeBeanFactoryPostProcessors方法中)调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  registry是DefaultListableBeanFactory对象
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);

processConfigBeanDefinitions(registry);
}
  • processConfigBeanDefinitions 方法

    • 从Beanfactory找出含有Configuration 或Component或ComponentScan或Import或ImportResource类 目前只有appconfig类符合
    • ConfigurationClassParser.parse()
      解析appconfig(AnnotatedGenericBeanDefinition 定义)

      • 1 处理内部类

        • 2 处理@PropertySources注解:进行一些配置信息的解析

        • 3 处理@ComponentScan注解:使用ComponentScanAnnotationParser扫描basePackage下的需要解析的类(@SpringBootApplication注解也包括了@ComponentScan注解,只不过basePackages是空的,空的话会去获取当前@Configuration修饰的类所在的包),并注册到BeanFactory中(这个时候bean并没有进行实例化,而是进行了注册。具体的实例化在finishBeanFactoryInitialization方法中执行)。对于扫描出来的类,递归解析

        • 4 处理@Import注解:先递归找出所有的注解,然后再过滤出只有@Import注解的类,得到@Import注解的值。比如查找@SpringBootApplication注解的@Import注解数据的话,首先发现@SpringBootApplication不是一个@Import注解,然后递归调用修饰了@SpringBootApplication的注解,发现有个@EnableAutoConfiguration注解,再次递归发现被@Import(EnableAutoConfigurationImportSelector.class)修饰,还有@AutoConfigurationPackage注解修饰,再次递归@AutoConfigurationPackage注解,发现被@Import(AutoConfigurationPackages.Registrar.class)注解修饰,所以@SpringBootApplication注解对应的@Import注解有2个,分别是@Import(AutoConfigurationPackages.Registrar.class)和@Import(EnableAutoConfigurationImportSelector.class)。找出所有的@Import注解之后,开始处理逻辑:

          • 遍历这些@Import注解内部的属性类集合

          • 如果这个类是个ImportSelector接口的实现类,实例化这个ImportSelector,如果这个类也是DeferredImportSelector接口的实现类,那么加入ConfigurationClassParser的deferredImportSelectors属性中让第7步处理。否则调用ImportSelector的selectImports方法得到需要Import的类,然后对这些类递归做@Import注解的处理

          • 如果这个类是ImportBeanDefinitionRegistrar接口的实现类,设置到配置类的importBeanDefinitionRegistrars属性中

          • 其它情况下把这个类入队到ConfigurationClassParser的importStack(队列)属性中,然后把这个类当成是@Configuration注解修饰的类递归重头开始解析这个类

    • 5 处理@ImportResource注解:获取@ImportResource注解的locations属性,得到资源文件的地址信息。然后遍历这些资源文件并把它们添加到配置类的importedResources属性中

    • 6 处理@Bean注解:获取被@Bean注解修饰的方法,然后添加到配置类的beanMethods属性中

    • 7 处理DeferredImportSelector:处理第3步@Import注解产生的DeferredImportSelector,进行selectImports方法的调用找出需要import的类,然后再调用第3步相同的处理逻辑处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
     public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    /* 获得所有的BeanDefinition的Name,放入candidateNames数组
    目前6个:
    ConfigurationClassPostProcessor
    AutowiredAnnotationBeanPostProcessor
    CommonAnnotationBeanPostProcessor
    EventListenerMethodProcessor
    DefaultEventListenerFactory
    appconfig
    */
    String[] candidateNames = registry.getBeanDefinitionNames();
    //循环candidateNames数组
    for (String beanName : candidateNames) {
    //根据beanName获得BeanDefinition
    BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    /* 内部有两个标记位来标记是否已经处理过了
    这里会引发一连串知识盲点
    当我们注册配置类的时候,可以不加Configuration注解,
    直接使用Component ComponentScan Import ImportResource注解,称之为Lite配置类
    如果加了Configuration注解,就称之为Full配置类
    如果我们注册了Lite配置类,我们getBean这个配置类,会发现它就是原本的那个配置类
    如果我们注册了Full配置类,我们getBean这个配置类,会发现它已经不是原本那个配置类了,而是已经被cgilb代理的类了
    写一个A类,其中有一个构造方法,打印出“你好”
    再写一个配置类,里面有两个bean注解的方法
    其中一个方法new了A 类,并且返回A的对象,把此方法称之为getA
    第二个方法又调用了getA方法
    如果配置类是Lite配置类,会发现打印了两次“你好”,也就是说A类被new了两次
    如果配置类是Full配置类,会发现只打印了一次“你好”,也就是说A类只被new了一次,因为这个类被cgilb代理了,方法已经被改写
    */
    if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
    if (logger.isDebugEnabled()) {
    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
    }
    }
    /* 判断是否为配置类(有两种情况 一种是传统意义上的配置类,一种是普通的bean),
    在这个方法内部,会做判断,这个配置类是Full配置类,还是Lite配置类,并且做上标记
    满足条件,加入到configCandidates */
    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
    configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    }
    }
    // 如果没有配置类,直接返回
    // Return immediately if no @Configuration classes were found
    if (configCandidates.isEmpty()) {
    return;
    }
    //处理排序
    // Sort by previously determined @Order value, if applicable
    configCandidates.sort((bd1, bd2) -> {
    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
    return Integer.compare(i1, i2);
    });

    // Detect any custom bean name generation strategy supplied through the enclosing application context
    SingletonBeanRegistry sbr = null;
    // DefaultListableBeanFactory最终会实现SingletonBeanRegistry接口,所以可以进入到这个if
    if (registry instanceof SingletonBeanRegistry) {
    sbr = (SingletonBeanRegistry) registry;
    if (!this.localBeanNameGeneratorSet) {
    //spring中可以修改默认的bean命名方式,这里就是看用户有没有自定义bean命名方式,虽然一般没有人会这么做
    BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
    AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
    if (generator != null) {
    this.componentScanBeanNameGenerator = generator;
    this.importBeanNameGenerator = generator;
    }
    }
    }

    if (this.environment == null) {
    this.environment = new StandardEnvironment();
    }

    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
    this.metadataReaderFactory, this.problemReporter, this.environment,
    this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
    //解析配置类(传统意义上的配置类或者是普通bean,核心来了) candidates 仅appConfig类
    parser.parse(candidates);
    parser.validate();

    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);

    // Read the model and create bean definitions based on its content
    if (this.reader == null) {
    this.reader = new ConfigurationClassBeanDefinitionReader(
    registry, this.sourceExtractor, this.resourceLoader, this.environment,
    this.importBeanNameGenerator, parser.getImportRegistry());
    }
    //直到这一步才把Import的类,@Bean @ImportRosource 转换成BeanDefinition
    this.reader.loadBeanDefinitions(configClasses);
    //把configClasses加入到alreadyParsed,代表
    alreadyParsed.addAll(configClasses);

    candidates.clear();
    //获得注册器里面BeanDefinition的数量 和 candidateNames进行比较
    //如果大于的话,说明有新的BeanDefinition注册进来了
    if (registry.getBeanDefinitionCount() > candidateNames.length) {
    String[] newCandidateNames = registry.getBeanDefinitionNames();
    Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
    Set<String> alreadyParsedClasses = new HashSet<>();
    //循环alreadyParsed。把类名加入到alreadyParsedClasses
    for (ConfigurationClass configurationClass : alreadyParsed) {
    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
    }
    for (String candidateName : newCandidateNames) {
    if (!oldCandidateNames.contains(candidateName)) {
    BeanDefinition bd = registry.getBeanDefinition(candidateName);
    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
    !alreadyParsedClasses.contains(bd.getBeanClassName())) {
    candidates.add(new BeanDefinitionHolder(bd, candidateName));
    }
    }
    }
    candidateNames = newCandidateNames;
    }
    }
    while (!candidates.isEmpty());

    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
    sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
    // Clear cache in externally provided MetadataReaderFactory; this is a no-op
    // for a shared cache since it'll be cleared by the ApplicationContext.
    ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
    }
    • ConfigurationClassParser.parse方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
         public void parse(Set<BeanDefinitionHolder> configCandidates) {
      //循环传进来的配置类
      for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
      //如果获得BeanDefinition是AnnotatedBeanDefinition的实例
      if (bd instanceof AnnotatedBeanDefinition) {
      parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
      else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
      parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
      }
      else {
      parse(bd.getBeanClassName(), holder.getBeanName());
      }
      }
      catch (BeanDefinitionStoreException ex) {
      throw ex;
      }
      catch (Throwable ex) {
      throw new BeanDefinitionStoreException(
      "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
      }
      //执行DeferredImportSelector
      this.deferredImportSelectorHandler.process();
      }
      protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
      processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
      }


      protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
      //判断是否需要跳过
      if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
      }

      ConfigurationClass existingClass = this.configurationClasses.get(configClass);
      if (existingClass != null) {
      if (configClass.isImported()) {
      if (existingClass.isImported()) {
      existingClass.mergeImportedBy(configClass);
      }
      // Otherwise ignore new imported config class; existing non-imported class overrides it.
      return;
      }
      else {
      // Explicit bean definition found, probably replacing an import.
      // Let's remove the old one and go with the new one.
      this.configurationClasses.remove(configClass);
      this.knownSuperclasses.values().removeIf(configClass::equals);
      }
      }

      // Recursively process the configuration class and its superclass hierarchy.
      SourceClass sourceClass = asSourceClass(configClass, filter);
      do {
      sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
      }
      while (sourceClass != null);

      this.configurationClasses.put(configClass, configClass);
      }



      /**
      * Apply processing and build a complete {@link ConfigurationClass} by reading the
      * annotations, members and methods from the source class. This method can be called
      * multiple times as relevant sources are discovered.
      * @param configClass the configuration class being build
      * @param sourceClass a source class
      * @return the superclass, or {@code null} if none found or previously processed
      */
      @Nullable
      protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {


      if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      // Recursively process any member (nested) classes first
      //递归处理内部类,一般不会写内部类
      processMemberClasses(configClass, sourceClass, filter);
      }

      // Process any @PropertySource annotations
      //处理@PropertySource注解,@PropertySource注解用来加载properties文件
      for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
      sourceClass.getMetadata(), PropertySources.class,
      org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
      processPropertySource(propertySource);
      }
      else {
      logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
      "]. Reason: Environment must implement ConfigurableEnvironment");
      }
      }

      // Process any @ComponentScan annotations
      //获得ComponentScan注解具体的内容,ComponentScan注解除了最常用的basePackage之外,还有includeFilters,excludeFilters等
      Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
      sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
      //如果没有打上ComponentScan,或者被@Condition条件跳过,就不再进入这个if
      if (!componentScans.isEmpty() &&
      !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      //循环处理componentScans
      for (AnnotationAttributes componentScan : componentScans) {
      // The config class is annotated with @ComponentScan -> perform the scan immediately
      //componentScan就是@ComponentScan上的具体内容,sourceClass.getMetadata().getClassName()就是配置类的名称
      Set<BeanDefinitionHolder> scannedBeanDefinitions =
      this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
      // Check the set of scanned definitions for any further config classes and parse recursively if needed
      for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
      BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      if (bdCand == null) {
      bdCand = holder.getBeanDefinition();
      }
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
      //递归调用,因为可能组件类有被@Bean标记的方法,或者组件类本身也有ComponentScan等注解
      parse(bdCand.getBeanClassName(), holder.getBeanName());
      }
      }
      }
      }

      //处理@Import注解
      //@Import注解是spring中很重要的一个注解,Springboot大量应用这个注解
      //@Import三种类,一种是Import普通类,一种是Import ImportSelector,还有一种是Import ImportBeanDefinitionRegistrar
      //getImports(sourceClass)是获得import的内容,返回的是一个set
      // Process any @Import annotations
      processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

      // Process any @ImportResource annotations
      //处理@ImportResource注解
      AnnotationAttributes importResource =
      AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
      if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
      String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
      configClass.addImportedResource(resolvedResource, readerClass);
      }
      }

      //处理@Bean的方法,可以看到获得了带有@Bean的方法后,不是马上转换成BeanDefinition,而是先用一个set接收
      // Process individual @Bean methods
      Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
      for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
      }

      // Process default methods on interfaces
      processInterfaces(configClass, sourceClass);

      // Process superclass, if any
      if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (superclass != null && !superclass.startsWith("java") &&
      !this.knownSuperclasses.containsKey(superclass)) {
      this.knownSuperclasses.put(superclass, configClass);
      // Superclass found, return its annotation metadata and recurse
      return sourceClass.getSuperClass();
      }
      }

      // No superclass -> processing is complete
      return null;
      }
AutowiredAnnotationBeanPostProcessor

AutowiredAnnotationBeanPostProcessor类图

 * AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有@Autowired 注释时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。  
*  关于作用后续内容会说到
CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor图

* CommonAnnotationBeanPostProcessor类在spring中是一个极其重要的类,它负责解析@Resource、@WebServiceRef、@EJB三个注解。这三个注解都是定义在javax.*包下的注解,属于java中的注解
* 关于作用后续内容会说到
PersistenceAnnotationBeanPostProcessor

PersistenceAnnotationBeanPostProcessor图

 * PersistenceAnnotationBeanPostProcessor是Spring提供的用于处理注解@PersistenceUnit和@PersistenceContext的BeanPostProcessor。用于注入相应的JPA资源:EntityManagerFactory和EntityManager (或者它们的子类变量)。
* 关于作用后续内容会说到
EventListenerMethodProcessor
![EventListenerMethodProcessor图](Spring-Bean-生命周期/EventListenerMethodProcessor.png)
 * EventListenerMethodProcessor 是 Spring 事件机制中非常重要的一个组件。它管理了一组EventListenerFactory组件,用来将应用中每个使用@EventListener注解定义的事件监听方法变成一个ApplicationListener实例注册到容器。换句话讲,框架开发者,或者应用开发者使用注解@EventListener定义的事件处理方法,如果没有EventListenerMethodProcessor的发现和注册,是不会被容器看到和使用的。
* 关于作用后续内容会说到
DefaultEventListenerFactory
![DefaultEventListenerFactory图](Spring-Bean-生命周期/DefaultEventListenerFactory.png)
 * 监听器工厂
* 关于作用后续内容会说到

registerPostProcessor 方法

registerPostProcessor方法内部就是注册Bean,

1
2
3
4
5
6
7
8
9
10
private static BeanDefinitionHolder registerPostProcessor(
BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
//为BeanDefinition设置了一个Role,ROLE_INFRASTRUCTURE代表这是spring内部的,并非用户定义的
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//BeanDefinitionRegistry是接口,实现类:AnnotationConfigApplicationContext 实现方法位于:GenericApplicationContext
//方法实现this.beanFactory.registerBeanDefinition(beanName, beanDefinition);

registry.registerBeanDefinition(beanName, definition);
return new BeanDefinitionHolder(definition, beanName);
}

  • RootBeanDefinition 是BeanDefinition接口子类
    BeanDefinition是什么,顾名思义,它是用来描述Bean的,
    • 里面存放着关于Bean的一系列信息,比如Bean的作用域,Bean所对应的Class,
    • 是否懒加载,是否Primary等等,这个BeanDefinition也相当重要,

RootBeanDefinition类图

至此,实例化AnnotatedBeanDefinitionReader reader分析完毕。

初始化扫描器

this.scanner = new ClassPathBeanDefinitionScanner(this);

register(componentClasses);

  • 把传入的类进行注册,这里有两个情况,
  • 传入传统的配置类
  • 传入bean(虽然一般没有人会这么做
  • 看到后面会知道spring把传统的带上@Configuration的配置类称之为FULL配置类,不带@Configuration的称之为Lite配置类
  • 但是我们这里先把带上@Configuration的配置类称之为传统配置类,不带的称之为普通bean
1
2
3
4
5
6
7
@Override
//componentClasses= AppConfig.class
public void register(Class<?>...componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
      //AnnotatedBeanDefinitionReader 类对象
this.reader.register(componentClasses);
}

AnnotatedBeanDefinitionReader 类register 方法

1
2
3
4
5
6
//componentClasses= AppConfig.class
public void register (Class<?>... componentClasses) {
for (Class<?> componentClass : componentClasses) {
registerBean(componentClass);
}
}

AnnotatedBeanDefinitionReader 类 registerBean方法

public void registerBean(Class<?> beanClass) {
    doRegisterBean(beanClass, null, null, null, null);
}

AnnotatedBeanDefinitionReader的方法doRegisterBean

  • 1 通过AnnotatedGenericBeanDefinition的构造方法,
    获得配置类的BeanDefinition,这里是不是似曾相似,
    在注册ConfigurationClassPostProcessor类的时候,
    也是通过构造方法去获得BeanDefinition的,只不过当时是通过RootBeanDefinition去获得,
    现在是通过AnnotatedGenericBeanDefinition去获得。
  • 2 判断需不需要跳过注册,Spring中有一个@Condition注解,如果不满足条件,就会跳过这个类的注册。
  • 3 然后是解析作用域,如果没有设置的话,默认为单例。
  • 4 获得BeanName。
  • 5 解析通用注解,填充到AnnotatedGenericBeanDefinition,解析的注解为Lazy,Primary,DependsOn,Role,Description。
  • 6 限定符处理,不是特指@Qualifier注解,也有可能是Primary,或者是Lazy,或者是其他(理论上是任何注解,这里没有判断注解的有效性)。
  • 7 把AnnotatedGenericBeanDefinition数据结构和beanName封装到一个对象中(这个不是很重要,可以简单的理解为方便传参)。
  • 8 注册,最终会调用DefaultListableBeanFactory中的registerBeanDefinition方法去注册:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//beanClass= AppConfig.class
其它参数都是null
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
//AnnotatedGenericBeanDefinition可以理解为一种数据结构,是用来描述Bean的,这里的作用就是把传入的标记了注解的类
//转为AnnotatedGenericBeanDefinition数据结构,里面有一个getMetadata方法,可以拿到类上的注解
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
//判断是否需要跳过注解,spring中有一个@Condition注解,当不满足条件,这个bean就不会被解析
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}

abd.setInstanceSupplier(supplier);
//解析bean的作用域,如果没有设置的话,默认为单例
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
//获得beanName
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
//解析通用注解,填充到AnnotatedGenericBeanDefinition,解析的注解为Lazy,Primary,DependsOn,Role,Description
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
//限定符处理,不是特指@Qualifier注解,也有可能是Primary,或者是Lazy,或者是其他(理论上是任何注解,这里没有判断注解的有效性),如果我们在外面,以类似这种
//AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);常规方式去初始化spring,
//qualifiers永远都是空的,包括上面的name和instanceSupplier都是同样的道理
//但是spring提供了其他方式去注册bean,就可能会传入了
if (qualifiers != null) {
//可以传入qualifier数组,所以需要循环处理
for (Class<? extends Annotation> qualifier : qualifiers) {

//Primary注解优先
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
//Lazy注解
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else { //其他,AnnotatedGenericBeanDefinition有个Map<String,AutowireCandidateQualifier>属性,直接push进去
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
//这个方法用处不大,就是把AnnotatedGenericBeanDefinition数据结构和beanName封装到一个对象中
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
//注册,最终会调用DefaultListableBeanFactory中的registerBeanDefinition方法去注册,
//DefaultListableBeanFactory维护着一系列信息,比如beanDefinitionNames,beanDefinitionMap
//beanDefinitionNames是一个List<String>,用来保存beanName
//beanDefinitionMap是一个Map,用来保存beanName和beanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
  • AnnotatedGenericBeanDefinition
    类注解初始化
    AnnotatedGenericBeanDefinition类图

refresh

Spring容器创建之后,会调用它的refresh方法刷新Spring应用的上下文。

prepareRefresh();

//刷新预处理,和主流程关系不大,就是保存了容器的启动时间,启动标志等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
protected void prepareRefresh() {
// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);

if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}

// 空方法
initPropertySources();

// 验证环境信息里一些必须存在的属性
getEnvironment().validateRequiredProperties();

// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}

// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

获取AnnotationConfigApplicationContext 中
DefaultListableBeanFactory对象,DefaultListableBeanFactory是ConfigurableListableBeanFactory子类

prepareBeanFactory(beanFactory);

配置这个工厂的标准环境,比如context的类加载器和post-processors后处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 设置了一个类加载器
* 设置了bean表达式解析器
* 添加了属性编辑器的支持
* 添加了一个后置处理器:ApplicationContextAwareProcessor,此后置处理器实现了BeanPostProcessor接口
* 设置了一些忽略自动装配的接口
* 设置了一些允许自动装配的接口,并且进行了赋值操作
* 在容器中还没有XX的bean的时候,帮我们注册beanName为XX的singleton bean
*/
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
//设置类加载器
beanFactory.setBeanClassLoader(getClassLoader());
//设置bean表达式解析器
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
//属性编辑器支持
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

//添加一个后置处理器:ApplicationContextAwareProcessor,此后置处理处理器实现了BeanPostProcessor接口
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
//以下接口,忽略自动装配
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

//以下接口,允许自动装配,第一个参数是自动装配的类型,,第二个字段是自动装配的值
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);

//添加一个后置处理器:ApplicationListenerDetector,此后置处理器实现了BeanPostProcessor接口
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}

//如果没有注册过bean名称为XXX,spring就自己创建一个名称为XXX的singleton bean
// 注册环境变量
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}

  • ApplicationContextAwareProcessor
    ApplicationContextAwareProcessor是一个Spring内部工具,它实现了接口BeanPostProcessor,用于向实现了如下某种Aware接口的bean设置ApplicationContext中相应的属性:

    • EnvironmentAware
    • EmbeddedValueResolverAware
    • ResourceLoaderAware
    • ApplicationEventPublisherAware
    • MessageSourceAware
    • ApplicationContextAware
      ApplicationContextAwareProcessor自己会被应用程序上下文自动注册到bean容器,不需要应用开发人员操心
      后续内容会分析作用
  • ApplicationListenerDetector

    • 1、在Bean初始化完成之后:如果Bean是单例的则并且bean instanceof ApplicationListener。加入到this.applicationListeners中。

    • 2、在Bean销毁之前搞事情: 如果Bean是一个ApplicationListener,则会从ApplicationEventMulticaster(事件广播器)中提前删除了
      后续内容会分析作用

  • LoadTimeWeaverAwareProcessor
    增加对 AspectJ 的支持
    后续说到aop会详细说

    postProcessBeanFactory(beanFactory);

    空方法

    invokeBeanFactoryPostProcessors(beanFactory);

    在Spring容器中找出实现了BeanFactoryPostProcessor接口的processor并执行。Spring容器会委托给PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    //spring允许我们手动添加BeanFactoryPostProcessor
    //即:annotationConfigApplicationContext.addBeanFactoryPostProcessor(XXX);
    //getBeanFactoryPostProcessors() 在外部可以手动添加一个后置处理器,而不是交给Spring去扫描
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
    // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
    beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
    beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
    }
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法

先介绍两个接口:

  • BeanFactoryPostProcessor:用来修改Spring容器中已经存在的bean的定义,使用ConfigurableListableBeanFactory对bean进行处理
  • BeanDefinitionRegistryPostProcessor:继承BeanFactoryPostProcessor,作用跟BeanFactoryPostProcessor一样,只不过是使用BeanDefinitionRegistry对bean进行处理

过程:

  • beanFactory是DefaultListableBeanFactory,是BeanDefinitionRegistry的实现类,所以肯定满足if

    • 1 定义了一个Set(processedBeans),装载BeanName,后面会根据这个Set,来判断后置处理器是否被执行过了。
    • 2 定义了两个List,

      • 一个是regularPostProcessors,用来装载BeanFactoryPostProcessor
      • 一个是registryProcessors用来装载BeanDefinitionRegistryPostProcessor,其中BeanDefinitionRegistryPostProcessor扩展了BeanFactoryPostProcessor;BeanDefinitionRegistryPostProcessor有两个方法,一个是独有的postProcessBeanDefinitionRegistry方法,一个是父类的postProcessBeanFactory方法。
    • 3 循环传进来的beanFactoryPostProcessors,上面已经解释过了,一般情况下,这里永远都是空的,只有手动add beanFactoryPostProcessor,这里才会有数据。我们假设beanFactoryPostProcessors有数据,进入循环,判断postProcessor是不是BeanDefinitionRegistryPostProcessor,因为BeanDefinitionRegistryPostProcessor扩展了BeanFactoryPostProcessor,所以这里先要判断是不是BeanDefinitionRegistryPostProcessor,是的话,执行postProcessBeanDefinitionRegistry方法,然后把对象装到registryProcessors里面去,不是的话,就装到regularPostProcessors。

    • 4 定义了一个临时变量:currentRegistryProcessors,用来装载BeanDefinitionRegistryPostProcessor。

    • 5 getBeanNamesForType,顾名思义,是根据类型查到BeanNames,这里有一点需要注意,就是去哪里找,点开这个方法的话,就知道是循环beanDefinitionNames去找,这个方法以后也会经常看到。这里传了BeanDefinitionRegistryPostProcessor.class,就是找到类型为BeanDefinitionRegistryPostProcessor的后置处理器,并且赋值给postProcessorNames。一般情况下,只会找到一个,就是org.springframework.context.annotation.internalConfigurationAnnotationProcessor,也就是ConfigurationAnnotationProcessor。这里有一个问题,为什么我自己写了个类,实现了BeanDefinitionRegistryPostProcessor接口,也打上了@Component注解,但是这里没有获得,因为直到这一步,Spring还没有完成扫描,扫描是在ConfigurationClassPostProcessor类中完成的,也就是下面第一个invokeBeanDefinitionRegistryPostProcessors方法。

    • 6 循环postProcessorNames,其实也就是org.springframework.context.annotation.internalConfigurationAnnotationProcessor,

      • 判断此后置处理器是否实现了PriorityOrdered接口(ConfigurationAnnotationProcessor也实现了PriorityOrdered接口),如果实现了,把它添加到currentRegistryProcessors这个临时变量中,再放入processedBeans,代表这个后置处理已经被处理过了(当然现在还没有处理,但是马上就要处理了)
    • 7 进行排序,PriorityOrdered是一个排序接口,如果实现了它,就说明此后置处理器是有顺序的,所以需要排序。当然目前这里只有一个后置处理器,就是ConfigurationClassPostProcessor。
    • 8 把currentRegistryProcessors合并到registryProcessors,为什么需要合并?因为一开始spring只会执行BeanDefinitionRegistryPostProcessor独有的方法,而不会执行BeanDefinitionRegistryPostProcessor父类的方法,即BeanFactoryProcessor接口中的方法,所以需要把这些后置处理器放入一个集合中,后续统一执行BeanFactoryProcessor接口中的方法。当然目前这里只有一个后置处理器,就是ConfigurationClassPostProcessor。
    • 9 可以理解为执行currentRegistryProcessors中的ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法,这就是Spring设计思想的体现了,在这里体现的就是其中的热插拔,插件化开发的思想。Spring中很多东西都是交给插件去处理的,这个后置处理器就相当于一个插件,如果不想用了,直接不添加就是了。这个方法特别重要,我们后面会详细说来。
    • 10 清空currentRegistryProcessors,因为currentRegistryProcessors是一个临时变量,已经完成了目前的使命,所以需要清空,当然后面还会用到。
    • 11 再次根据BeanDefinitionRegistryPostProcessor获得BeanName,然后进行循环,看这个后置处理器是否被执行过了,如果没有被执行过,也实现了Ordered接口的话,把此后置处理器推送到currentRegistryProcessors和processedBeans中。这里就可以获得我们定义的,并且打上@Component注解的后置处理器了,因为Spring已经完成了扫描,但是这里需要注意的是,由于ConfigurationClassPostProcessor在上面已经被执行过了,所以虽然可以通过getBeanNamesForType获得,但是并不会加入到currentRegistryProcessors和processedBeans。
    • 12 处理排序。
    • 13 合并Processors,合并的理由和上面是一样的。
    • 14 执行我们自定义的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。
    • 15 清空临时变量。
    • 16 在上面的方法中,仅仅是执行了实现了Ordered接口的BeanDefinitionRegistryPostProcessor,这里是执行没有实现Ordered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。
    • 17 regularPostProcessors装载BeanFactoryPostProcessor,执行BeanFactoryPostProcessor的postProcessBeanFactory方法
    • 18 regularPostProcessors一般情况下,是不会有数据的,只有在外面手动添加BeanFactoryPostProcessor,才会有数据
    • 19 查找实现了BeanFactoryPostProcessor的后置处理器,并且执行后置处理器中的方法。和上面的逻辑差不多,不再详细说明。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      //beanFactory=DefaultListableBeanFactory
      //beanFactoryPostProcessors 没有添加 所以size=0
      public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

      // Invoke BeanDefinitionRegistryPostProcessors first, if any.
      Set<String> processedBeans = new HashSet<>();
      //beanFactory是DefaultListableBeanFactory,是BeanDefinitionRegistry的实现类,所以肯定满足if
      if (beanFactory instanceof BeanDefinitionRegistry) {
      BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
      //regularPostProcessors 用来存放BeanFactoryPostProcessor,
      List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
      //registryProcessors 用来存放BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor扩展了BeanFactoryPostProcessor
      List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
      /*
      循环传进来的beanFactoryPostProcessors,正常情况下,beanFactoryPostProcessors肯定没有数据
      因为beanFactoryPostProcessors是获得手动添加的,而不是spring扫描的
      只有手动调用annotationConfigApplicationContext.addBeanFactoryPostProcessor(XXX)才会有数据
      */

      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
      /* 判断postProcessor是不是BeanDefinitionRegistryPostProcessor,因为BeanDefinitionRegistryPostProcessor
      扩展了BeanFactoryPostProcessor,所以这里先要判断是不是BeanDefinitionRegistryPostProcessor
      是的话,直接执行postProcessBeanDefinitionRegistry方法,然后把对象装到registryProcessors里面去

      */
      if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
      BeanDefinitionRegistryPostProcessor registryProcessor =
      (BeanDefinitionRegistryPostProcessor) postProcessor;
      registryProcessor.postProcessBeanDefinitionRegistry(registry);
      registryProcessors.add(registryProcessor);
      }
      else {
      regularPostProcessors.add(postProcessor);
      }
      }

      // Do not initialize FactoryBeans here: We need to leave all regular beans
      // uninitialized to let the bean factory post-processors apply to them!
      // Separate between BeanDefinitionRegistryPostProcessors that implement
      // PriorityOrdered, Ordered, and the rest.

      /* 一个临时变量,用来装载BeanDefinitionRegistryPostProcessor
      BeanDefinitionRegistry继承了PostProcessorBeanFactoryPostProcessor
      */
      List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

      // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
      /*
      获得实现BeanDefinitionRegistryPostProcessor接口的类的BeanName:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
      对象是:ConfigurationClassPostProcessor
      */
      String[] postProcessorNames =
      beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
      if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
      /*
      获得ConfigurationClassPostProcessor类,并且放到currentRegistryProcessors
      ConfigurationClassPostProcessor是很重要的一个类,它实现了BeanDefinitionRegistryPostProcessor接口
      BeanDefinitionRegistryPostProcessor接口又实现了BeanFactoryPostProcessor接口
      ConfigurationClassPostProcessor是极其重要的类
      里面执行了扫描Bean,Import,ImportResouce等各种操作
      用来处理配置类(有两种情况 一种是传统意义上的配置类,一种是普通的bean)的各种逻辑
      */
      currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
      //把name放到processedBeans,后续会根据这个集合来判断处理器是否已经被执行过了
      processedBeans.add(ppName);
      }
      }
      //处理排序
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      /* 合并Processors,为什么要合并,因为registryProcessors是装载BeanDefinitionRegistryPostProcessor的
      一开始的时候,spring只会执行BeanDefinitionRegistryPostProcessor独有的方法
      而不会执行BeanDefinitionRegistryPostProcessor父类的方法,即BeanFactoryProcessor的方法
      所以这里需要把处理器放入一个集合中,后续统一执行父类的方法
      */
      registryProcessors.addAll(currentRegistryProcessors);
      /*可以理解为执行ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
      Spring热插播的体现,像ConfigurationClassPostProcessor就相当于一个组件,Spring很多事情就是交给组件去管理
      如果不想用这个组件,直接把注册组件的那一步去掉就可以
      */
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      //因为currentRegistryProcessors是一个临时变量,所以需要清除
      currentRegistryProcessors.clear();

      // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
      // 再次根据BeanDefinitionRegistryPostProcessor获得BeanName,看这个BeanName是否已经被执行过了,有没有实现Ordered接口
      // 如果没有被执行过,也实现了Ordered接口的话,把对象推送到currentRegistryProcessors,名称推送到processedBeans
      // 如果没有实现Ordered接口的话,这里不把数据加到currentRegistryProcessors,processedBeans中,后续再做处理
      // 这里才可以获得我们定义的实现了BeanDefinitionRegistryPostProcessor的Bean
      postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
      if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
      currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
      processedBeans.add(ppName);
      }
      }
      //处理排序
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      //合并Processors
      registryProcessors.addAll(currentRegistryProcessors);
      //执行我们自定义的BeanDefinitionRegistryPostProcessor
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      //清空临时变量
      currentRegistryProcessors.clear();

      // 上面的代码是执行了实现了Ordered接口的BeanDefinitionRegistryPostProcessor,
      // 下面的代码就是执行没有实现Ordered接口的BeanDefinitionRegistryPostProcessor
      // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
      boolean reiterate = true;
      while (reiterate) {
      reiterate = false;
      postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
      for (String ppName : postProcessorNames) {
      if (!processedBeans.contains(ppName)) {
      currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
      processedBeans.add(ppName);
      reiterate = true;
      }
      }
      sortPostProcessors(currentRegistryProcessors, beanFactory);
      registryProcessors.addAll(currentRegistryProcessors);
      invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
      currentRegistryProcessors.clear();
      }

      // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
      //registryProcessors集合装载BeanDefinitionRegistryPostProcessor
      //上面的代码是执行子类独有的方法,这里需要再把父类的方法也执行一次
      invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
      //regularPostProcessors装载BeanFactoryPostProcessor,执行BeanFactoryPostProcessor的方法
      //但是regularPostProcessors一般情况下,是不会有数据的,只有在外面手动添加BeanFactoryPostProcessor,才会有数据
      invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
      }

      else {
      // Invoke factory processors registered with the context instance.
      invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
      }

      // Do not initialize FactoryBeans here: We need to leave all regular beans
      // uninitialized to let the bean factory post-processors apply to them!
      //找到BeanFactoryPostProcessor实现类的BeanName数组
      String[] postProcessorNames =
      beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

      // Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
      // Ordered, and the rest.
      List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
      List<String> orderedPostProcessorNames = new ArrayList<>();
      List<String> nonOrderedPostProcessorNames = new ArrayList<>();
      //循环BeanName数组
      for (String ppName : postProcessorNames) {
      //如果这个Bean被执行过了,跳过
      if (processedBeans.contains(ppName)) {
      // skip - already processed in first phase above
      }
      //如果实现了PriorityOrdered接口,加入到priorityOrderedPostProcessors
      else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
      priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
      }
      //如果实现了Ordered接口,加入到orderedPostProcessorNames
      else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
      orderedPostProcessorNames.add(ppName);
      }
      else {
      //如果既没有实现PriorityOrdered,也没有实现Ordered。加入到nonOrderedPostProcessorNames
      nonOrderedPostProcessorNames.add(ppName);
      }
      }

      //排序处理priorityOrderedPostProcessors,即实现了PriorityOrdered接口的BeanFactoryPostProcessor
      // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
      sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
      //执行priorityOrderedPostProcessors
      invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

      //执行实现了Ordered接口的BeanFactoryPostProcessor
      // Next, invoke the BeanFactoryPostProcessors that implement Ordered.
      List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
      for (String postProcessorName : orderedPostProcessorNames) {
      orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
      }
      sortPostProcessors(orderedPostProcessors, beanFactory);
      invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

      // 执行既没有实现PriorityOrdered接口,也没有实现Ordered接口的BeanFactoryPostProcessor
      // Finally, invoke all other BeanFactoryPostProcessors.
      List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
      for (String postProcessorName : nonOrderedPostProcessorNames) {
      nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
      }
      invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

      // Clear cached merged bean definitions since the post-processors might have
      // modified the original metadata, e.g. replacing placeholders in values...
      beanFactory.clearMetadataCache();
      }

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry

registerBeanPostProcessors(beanFactory);

initMessageSource();

initApplicationEventMulticaster();

onRefresh();

registerListeners();

finishBeanFactoryInitialization(beanFactory);

finishRefresh();

循环依赖

Spring体系结构

官网:

https://spring.io/

Spring简介

Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。

Spring优点:

Spring的优点:

  • (1)方便解耦,简化开发

Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。

  • (2)AOP编程的支持

Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。

  • (3)声明式事务的支持

只需要通过配置就可以完成对事务的管理,而无需手动编程。

  • (4)方便程序的测试

Spring对Junit4支持,可以通过注解方便的测试Spring程序。

*(5)方便集成各种优秀框架

Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。

*(6)降低JavaEE API的使用难度

Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

spring 项目

在官网点击project 菜单 就见到spring很多项目
spring project

springFramework

这是一个Spring的基础框架,提供了Spring 的核心功能,比如依赖注入、事务管理、面向方面编程等等
体系结构
spring project
Spring的核心容器是其他模块建立的基础,有Spring-core、Spring-beans、Spring-context、Spring-context-support和Spring-expression(String表达式语言)等模块组成。

  • Spring-core模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
  • Spring-beans模块:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • Spring-context模块:建立在Core和Beans模块的基础之上,提供一个框架式的对象访问方式,是访问定义和配置的任何对象的媒介。ApplicationContext接口是Context模块的焦点。
  • Spring-context-support模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
  • Spring-expression模块:提供了强大的表达式语言去支持运行时查询和操作对象图。这是对JSP2.1规范中规定的统一表达式语言(Unified EL)的扩展。该语言支持设置和获取属性值、属性分配、方法调用、访问数组、集合和索引器的内容、逻辑和算术运算、变量命名以及从Spring的IOC容器中以名称检索对象。它还支持列表投影、选择以及常用的列表聚合。
  • Spring-aop模块:提供了一个符合AOP要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
  • Spring-aspects模块:提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
  • Spring-jdbc模块:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析。
  • Spring-orm模块:为流行的对象关系映射(Object-Relational Mapping)API提供集成层,包括JPA和Hibernate。使用Spring-orm模块可以将这些O/R映射框架与Spring提供的所有其他功能结合使用,例如声明式事务管理功能。
  • Spring-oxm模块:提供了一个支持对象/XML映射的抽象层实现,例如JAXB、Castor、JiBX和XStream。
  • Spring-jms模块(Java Messaging Service):指Java消息传递服务,包含用于生产和使用消息的功能。自Spring4.1以后,提供了与Spring-messaging模块的集成。
  • Spring-tx模块(事务模块):支持用于实现特殊接口和所有POJO(普通Java对象)类的编程和声明式事务管理
  • Spring-web模块:提供了基本的Web开发集成功能,例如多文件上传功能、使用Servlet监听器初始化一个IOC容器以及Web应用上下文。
  • Spring-webmvc模块:也称为Web-Servlet模块,包含用于web应用程序的Spring MVC和REST Web Services实现。Spring MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。
  • Spring-websocket模块:Spring4.0以后新增的模块,它提供了WebSocket和SocketJS的实现。
  • Portlet模块:类似于Servlet模块的功能,提供了Portlet环境下的MVC实现
  • Spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

spring boot

我们原来运行Spring程序,需要在Maven或者Gradle中添加一些Spring的依赖,然后再通过这些构建工具提供的服务器来运行程序。使用Spring Boot,则可以免去这些繁复的工作。Spring Boot提供了一系列功能可以自动搜索、配置Spring程序。Spring Boot会将项目打包为一个可执行的jar文件,内部包含有tomcat这样的服务器,让我们可以直接以命令行的方式运行Spring程序。Spring Boot官方介绍说,它可以让你尽可能快的运行起Spring程序

Springcloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

  • Spring Cloud Netflix
      是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。
  • Spring Cloud Config
      将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件
  • Spring Cloud Bus
      分布式消息队列,是对Kafka, MQ的封装
  • Spring Cloud Security
      对Spring Security的封装,并能配合Netflix使用
  • Spring Cloud Zookeeper
      对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用
  • Spring Cloud Eureka
    Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。
    Spring Cloud对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发自己的分布式系统基础设施,使用Spring Cloud一站式解决方案能在从容应对业务发展的同时大大减少开发成本。同时,随着近几年微服务架构和Docker容器概念的火爆,也会让Spring Cloud在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在五花八门的分布式解决方案中提供了标准化的、全站式的技术方案,意义可能会堪比当年Servlet规范的诞生,有效推进服务端软件系统技术水平的进步。

    Spring Data

    这是Spring关于处理数据的框架,其中包含了多个模块,可以让我们使用JPA操作数据、在Redis等非SQL数据库上存取数据等很多功能

    Spring Security

    Spring的安全框架,支持在程序中设置安全权限,限制未授权的用户访问某些页面,也提供了一些加密功能方便地加密数据

    Spring Session

    提供了一个Session的实现,帮助我们管理用户会话。

    Spring Integration

    这个框架用来将Spring和其他框架、协议、服务集成起来,这些服务包括但不限于控制总线、FTP服务器、Web服务,社交服务、套接字、消息队列、邮件系统……Spring Integration提供了一些适配器,可以方便的和这些服务进行集成。

    Spring AMQP

    用于开发AMQP的解决方案

springFramework源码--IOC 容器

Spring framework是一个Spring的基础框架,提供了Spring 的核心功能,比如依赖注入、事务管理、面向方面编程等等

IOC容器( Inversion of Control )是Spring Framework 最核心部分,它管理着Spring应用中bean的创建、 配置和管理,也叫依赖注入(dependency injection )

什么是IOC容器

没有IOC容器

按照传统的做法,每个对象负责管理与自己相互协作的对象
(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代 码。

我们假定一个在线书店,通过BookService获取书籍:

1
2
3
4
5
6
7
8
9
10
11
public class BookService {
private HikariConfig config = new HikariConfig();
private DataSource dataSource = new HikariDataSource(config);

public Book getBook(long bookId) {
try (Connection conn = dataSource.getConnection()) {
...
return book;
}
}
}

  • 这种方式使用new的方式创建对象,
  • 若测试BookService,是复杂的,因为必须要在真实的数据库环境下执行。

  • 从上面的例子可以看出,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

有IOC容器

核心问题是:

  • 谁负责创建组件?
  • 谁负责根据依赖关系组装组件?
  • 销毁时,如何按依赖顺序正确销毁?
    解决这一问题的核心方案就是IoC。

传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:

在创建BookService的过程中,又创建了DataSource组件。这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。

在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制,
例如,BookService自己并不会创建DataSource,而是等待外部通过setDataSource()方法来注入一个DataSource:

1
2
3
4
5
6
public class BookService { 
private DataSource dataSource;
   public BookService(DataSource dataSource){
this.DataSource=dataSource;
}
}

不直接new一个DataSource,而是注入一个DataSource,这个小小的改动虽然简单,却带来了一系列好处:

  • BookService不再关心如何创建DataSource,因此,不必编写读取数据库配置之类的代码;
  • 测试BookService更容易,因为注入的是DataSource,可以使用内存数据库,而不是真实的MySQL配置。

因此,IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系.
以下这种通过在JavaConfig中进行显式配置bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public Class JavaBean{

@Bean
public HikariConfig hikariConfig(){
return new HikariConfig();
}
@Bean
public DataSource dataSource(HikariConfig config){
return new HikariDataSource(config);
}
@Bean
public BookService bookService(DataSource dataSource){
return new BookService(dataSource);
}

}

在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。

依赖注入的方式

  • 通过构造方法注入
1
2
3
4
5
6
7
public class BookService { 
private DataSource dataSource;
@Autowired
   public BookService(DataSource dataSource){
this.DataSource=dataSource;
}
}
  • 通过方法注入
1
2
3
4
5
6
7
public class BookService { 
private DataSource dataSource;
@Autowired
   public void setDataSource(DataSource dataSource){
this.DataSource=dataSource;
}
}

如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出
一个异常。为了避免异常的出现,你可以将@Autowired的
required属性设置为false:

1
2
3
4
5
6
7
public class BookService { 
private DataSource dataSource;
     @Autowired(required=false)
   public void setDataSource(DataSource dataSource){
this.DataSource=dataSource;
}
}

将required属性设置为false时,Spring会尝试执行自动装配,但
是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状
态。但是,把required属性设置为false时,你需要谨慎对待。如
果在你的代码中没有进行null检查的话,这个处于未装配状态的属性
有可能会出现NullPointerException。

如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,
表明没有明确指定要选择哪个bean进行自动装配

  • 属性注入
1
2
3
4
public class BookService { 
 @Autowired
private DataSource dataSource;
}

Spring容器是

  • 从概念上讲:Spring 容器是 Spring 框架的核心,是用来管理对象的。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。
  • 从具象化讲:在java项目中,我们使用实现了org.springframework.context.ApplicationContext接口的实现类。
  • 从代码上讲:一个Spring容器就是某个实现了ApplicationContext接口的类的实例。也就是说,从代码层面,Spring容器其实就是一个

BeanFactory

BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。

BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。

BeanFactory 是初始化 Bean 和调用它们生命周期方法的“吃苦耐劳者”。注意,BeanFactory 只能管理单例(Singleton)Bean 的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean 实例被创建之后便被传给了客户端,容器失去了对它们的引用。
DefaultListableBeanFactory类图:
DefaultListableBeanFactory类图

  • 1、BeanFactory作为一个主接口不继承任何接口,暂且称为一级接口。
    是Spring bean容器的根接口,提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean 别名的方法 。它最主要的方法就是getBean(String beanName)。
  • 2、有3个子接口继承了它,进行功能上的增强。这3个子接口称为二级接口。
    • HierarchicalBeanFactory:提供父容器的访问功能  
    • ListableBeanFactory:提供了批量获取Bean的方法  
    • AutowireCapableBeanFactory:在BeanFactory基础上实现对已存在实例的管理
  • 3、ConfigurableBeanFactory可以被称为三级接口,对二级接口HierarchicalBeanFactory进行了再次增强,它还继承了另一个外来的接口SingletonBeanRegistry
    主要单例bean的注册,生成实例,以及统计单例bean

  • 4、ConfigurableListableBeanFactory是一个更强大的接口,继承了上述的所有接口,无所不包,称为四级接口。 (这4级接口是BeanFactory的基本接口体系。继续,下面是继承关系的2个抽象类和2个实现类:)
    继承了上述的所有接口,增加了其他功能:比如类加载器,类型转化,属性编辑器,BeanPostProcessor,作用域,bean定义,处理bean依赖关系, bean如何销毁…

  • 5、AbstractBeanFactory作为一个抽象类,实现了三级接口ConfigurableBeanFactory大部分功能。

  • 6、AbstractAutowireCapableBeanFactory同样是抽象类,继承自AbstractBeanFactory,并额外实现了二级接口AutowireCapableBeanFactory

  • 7、DefaultListableBeanFactory继承自AbstractAutowireCapableBeanFactory,实现了最强大的四级接口ConfigurableListableBeanFactory,并实现了一个外来接口BeanDefinitionRegistry,它并非抽象类。

    实现了ConfigurableListableBeanFactory,实现上述BeanFactory所有功能。它还可以注册BeanDefinition
    BeanFactory的源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    https://docs.spring.io/spring/docs/5.1.18.BUILD-SNAPSHOT/javadoc-api/

    package org.springframework.beans.factory;

    import org.springframework.beans.BeansException;
    import org.springframework.core.ResolvableType;
    import org.springframework.lang.Nullable;

    public interface BeanFactory {

    /**
    * 用来引用一个实例,或把它和工厂产生的Bean区分开,就是说,如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
    */
    String FACTORY_BEAN_PREFIX = "&";
    /*
    * 五个不同形式的getBean方法,获取实例
    */
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    //2个不同形式 返回指定bean的提供程序,以允许按需延迟检索实例,包括可用性和唯一性选项。
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    /**
    * bean 是否存在
    */
    boolean containsBean(String name);

    // 是否为单实例
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    // 是否为原型(多实例)
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    // 2个不同形式 名称、类型是否匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    // 2个不同形式 获取类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    @Nullable
    Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

    // 根据实例的名字获取实例的别名
    String[] getAliases(String name);

    }

具体:
  1、5个获取实例的方法。getBean的重载方法。
  2、7个判断的方法。判断是否存在,是否为单例、原型,名称类型是否匹配。
  3、1个获取类型的方法、一个获取别名的方法。根据名称获取类型、根据名称获取别名。一目了然!
总结:
  这13个方法,很明显,这是一个典型的工厂模式的工厂接口。
AnnotationConfigApplicationContext 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
从一个或多个
基于Java的配置类中加载Spring应用上下文

package com.yanwen.springTest;

import com.yanwen.springTest.service.MemberService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

@ComponentScan("com.yanwen.springTest")
@Configuration
public class AppConfig {
public static void main(String[] args){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(annotationConfigApplicationContext.getBean(MemberService.class));
}
}

package com.yanwen.springTest.service;

import org.springframework.stereotype.Service;

@Service
public class MemberService {
}

  1. 从一个或多个基于Java的配置类中加载Spring应用上下文(AnnotationConfigApplicationContext)。
  2. 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。
  3. 对于单实例(singleton)的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean时直接从IoC容器缓存中获取Bean。

ApplicationContext

如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。

BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。而ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:

  • (1)MessageSource, 提供国际化的消息访问
  • (2)资源访问,如URL和文件
  • (3)事件传播特性,即支持aop特性
  • (4)载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。
AnnotationConfigApplicationContext类图

ApplicationContext常用实现类 作用

  • AnnotationConfigApplicationContext 从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。
  • ClassPathXmlApplicationContext 从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。
  • FileSystemXmlApplicationContext 从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。
  • AnnotationConfigWebApplicationContext 专门为web应用准备的,适用于注解方式。
  • XmlWebApplicationContext 从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

三种装配机制:

  • 在XMl中进行显示配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配
    • 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
    • 自动装配(autowiring):Spring自动满足bean之间的依赖。

(使用的优先性: 3>2>1)尽可能地使用自动配置的机制,显示配置越少越好。当必须使用显示配置bean的时候(如:有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用类型安全比XML更加强大的JavaConfig。最后只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才使用XML。
代码示例:

通过xml文件将配置加载到IOC容器中

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--若没写id,则默认为com.test.Man#0,#0为一个计数形式-->
<bean id="man" class="com.test.Man"></bean>
</beans>

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
//加载项目中的spring配置文件到容器
//ApplicationContext context = new ClassPathXmlApplicationContext("resouces/applicationContext.xml");
//加载系统盘中的配置文件到容器
ApplicationContext context = new FileSystemXmlApplicationContext("E:/Spring/applicationContext.xml");
//从容器中获取对象实例
Man man = context.getBean(Man.class);
man.driveCar();
}
}

通过java注解的方式将配置加载到IOC容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//同xml一样描述bean以及bean之间的依赖关系
@Configuration
public class ManConfig {
@Bean
public Man man() {
return new Man(car());
}
@Bean
public Car car() {
return new QQCar();
}
}
public class Test {
public static void main(String[] args) {
//从java注解的配置中加载配置到容器
ApplicationContext context = new AnnotationConfigApplicationContext(ManConfig.class);
//从容器中获取对象实例
Man man = context.getBean(Man.class);
man.driveCar();
}
}

隐式的bean发现机制和自动装配

1
2
3
4
5
6
7
8
9
10
11
/**
* 这是一个游戏光盘的实现
*/
//这个简单的注解表明该类回作为组件类,并告知Spring要为这个创建bean。
@Component
public class GameDisc implements Disc{
@Override
public void play() {
System.out.println("我是马里奥游戏光盘。");
}
}

不过,组件扫描默认是不启用的。我们还需要显示配置一下Spring,从而命令它去寻找@Component注解的类,并为其创建bean。

1
2
3
4
@Configuration
@ComponentScan
public class DiscConfig {
}

我们在DiscConfig上加了一个@ComponentScan注解表示在Spring中开启了组件扫描,默认扫描与配置类相同的包,就可以扫描到这个GameDisc的Bean了。这就是Spring的自动装配机制。

由于ApplicationContext会预先初始化所有的Singleton Bean,于是在系统创建前期会有较大的系统开销,但一旦ApplicationContext初始化完成,程序后面获取Singleton Bean实例时候将有较好的性能。也可以为bean设置lazy-init属性为true,即Spring容器将不会预先初始化该bean。

常用注解

@Configuration

定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

@ComponentScan

该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 context:component-scan

  • 指定包扫描,主要扫描该包下@Controller @Service @Respsitory @Component四个注解

    1
    @ComponentScan(value="com.songzixian")
  • 指定排除要扫描的包

    1
    @ComponentScan(value="com.songzixian",excludeFilters ={@ComponentScan.Filter (type= FilterType.ANNOTATION,classes ={Component.class,Repository.class,Controller.class,Service.class})} )
  • 指定要扫描的包
    useDefaultFilters = false默认是true,需要改为false该类才生效

    1
    @ComponentScan(value="com.songzixian",includeFilters ={@ComponentScan.Filter (type= FilterType.ANNOTATION,classes ={Component.class,Repository.class,Controller.class,Service.class})},useDefaultFilters = false)

@Bean

注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中

@Autowired

注解对自动装配何时何处被实现提供了更多细粒度的控制

@Qualifier

自动装配,如果容器中某个bean有多个实例,想要获取指定的的实例,可以用@Qualifier

@scope

用来配置 spring bean 的作用域,它标识 bean 的作用域。默认值是单例

  • 1、singleton:单例模式,全局有且仅有一个实例

  • 2、prototype:原型模式,每次获取Bean的时候会有一个新的实例

  • 3、request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效

  • 4、session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效

  • 5、global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。

@Lazy

容器启动不创建对象,调用的时候创建对象

@Conditional({WindowConditional.class})

WindowConditional 实现condition接口,返回true

@PostConstruct

初始化 注解在初始化的方法上面

@PreDestroy

销毁 在容器销毁bean之前调用

@Controller

标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.

@Service

用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中

@Resource

@Resource的作用相当于@Autowired
只不过@Autowired按byType自动注入,
而@Resource默认按 byName自动注入
@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略

@Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注

@RestController

Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。

@RequestMapping

类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。

@RequestParam

用于将请求参数区数据映射到功能处理方法的参数上

1
2
3
4
5
@RequestMapping(value="", method=RequestMethod.POST)
public String postUser(@RequestParam(value="phoneNum", required=true) String phoneNum ) String userName) {
userService.create(phoneNum, userName);
return "success";
}

@Repository

用于标注数据访问组件,即DAO组件

@Required

适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。

import

,@Import通过快速导入的方式实现把实例加入spring的IOC容器中

  • 1、直接填class数组方式

    1
    2
    3
    4
      @Import({ 类名.class , 类名.class... })
    public class TestDemo {

    }

    @PathVariable

    映射 URL 绑定的占位符
    通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过

@PathVariable(“xxx”) 绑定到操作方法的入参中

1
2
3
4
@GetMapping("/users/{username}")
public String userProfile(@PathVariable("username") String user) {
return String.format("user %s", user);
}

@PathVariable和@RequestParam的区别就在于:@RequestParam用来获得静态的URL请求参数;@PathVariable用来获得动态的URL请求入参

@ResponseBody

的作用其实是将java对象转为json格式的数据。

1
2
3
4
5
6
7
 @RequestMapping("/login.do")
@ResponseBody
public Object login(String name, String password, HttpSession session) {
user = userService.checkLogin(name, password);
session.setAttribute("user", user);
return new JsonResult(user);
}

@RequestBody

是作用在形参列表上,用于将前台发送过来固定格式的数据【xml格式 或者 json等】封装为对应的 JavaBean 对象,
封装时使用到的一个对象是系统默认配置的 HttpMessageConverter进行解析,然后封装到形参上。
如上面的登录后台代码可以改为:

1
2
3
4
5
6
7
@RequestMapping("/login.do")
@ResponseBody
public Object login(@RequestBody User loginUuser, HttpSession session) {
user = userService.checkLogin(loginUser);
session.setAttribute("user", user);
return new JsonResult(user);
}

@PropertySource

目的是加载指定的属性文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@PropertySource(value = {"classpath:people.properties"},ignoreResourceNotFound = false,encoding = "UTF-8",name = "people.properties")
public class PeopleProperties {

private String name;

private int age;
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;


}

people.properties内容如下:

1
2
3
age=10
name=xiaohua
history=beijing

##ConfigurationProperties
是类级别的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@PropertySource(value = {"classpath:people.properties"},ignoreResourceNotFound = false,encoding = "UTF-8",name = "people.properties")
@ConfigurationProperties(prefix = "female",ignoreUnknownFields=true,ignoreInvalidFields=true)
public class PeopleProperties {

private String name;

private int age;
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;

}

在PeopleProperties类中增加了ConfigurationProperties注解,并且指明了属性的前缀为female。这样Springboot在处理的时候,会去扫描当前类中的所有字段并进行属性的查找以及组装。比如我们配置的prefix = “female”,PeopleProperties类中有一个name字段,则female字段需要匹配的属性是prefix+字段=female.name。

@ConfigurationProperties(prefix = “female”,ignoreUnknownFields=true,ignoreInvalidFields=true)

  • ignoreUnknownFields:忽略未知的字段。

  • ignoreInvalidFields:是否忽略验证失败的字段。这个怎么理解呢?比如我们在配置文件中配置了一个字符串类型的变量,类中的字段是int类型,那肯定会报错的。如果出现这种情况我们可以容忍,则需要配置该属性值为true。该参数值默认为false。
    注意使用该注解,bean一定有Set与Get方法,否则取不出对应的属性值。
    people.properties内容如下:

    1
    2
    3
    female.age=10
    female.name=xiaohua
    female.history=beijing

@Value

通过@Value将外部配置文件的值动态注入到Bean中。配置文件主要有两类:

  • application.properties。application.properties在spring boot启动时默认加载此文件
    自定义属性文件。自定义属性文件通过@PropertySource加载。* @PropertySource可以同时加载多个文件,也可以加载单个文件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key启作用。加载文件的路径也可以配置变量,如下文的${anotherfile.configinject},此值定义在第一个属性文件config.properties
    1
    2
    @Value("${app.name}")
    private String appName; // 这里的值来自application.properties,spring boot启动时默认加载此文件

无侵入容器

在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:

  • 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;
  • 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率。

官方文档:

https://docs.spring.io/spring/docs/5.0.18.RELEASE/spring-framework-reference/core.html#spring-core

springFramework源码编译

安装环境配套

Gradle 5.6.2 +java version “1.8.0_251”+idea+spring framework5.1.x

配置gradle环境

配置gradle环境变量 新建GRADLE_HOME,path中增加%GRADLE_HOME%/bin;即可
gradle
gradle
配置完成以后win+r,输入cmd进入dos界面,键入命令gradle -v,若如下图正确输出版本信息即可
gradle
配置gradle默认的本地仓库,gradle和maven类似,工作时也需要一个本地仓库,管理工程jar包,可做如下配置,新增GRADLE_USER_HOME
gradle

Git 下载代码

Gi地址:https://github.com/spring-projects/spring-framework.git
gradle
下载完成切换分支5.1.x
gradle
在import-into-idea.md 你编译spring work源码之前你需要spring-core和spring-oxm
gradle

gradle 添加aliyun maven仓库(根目录的build.gradle)

1
2
3
4
5
6
7
在文件中找到以下()
repositories {
maven { url "http://maven.aliyun.com/nexus/content/groups/public/" }
mavenCentral()
maven { url "https://repo.spring.io/libs-spring-framework-build" }
maven { url "https://repo.spring.io/snapshot" } // Reactor Dysprosium snapshots
}

注意

注意几点:

  • a,因为其他项目需要依赖spring-core和spring-orm,所以我们导入后需要先编译这两个包 (在cmd 命令下执行gradle build -x test ),需要
    等候一定时间(我这边花了1小时时间),保证网络稳定
    gradle
    gradle

成功截图: 显示Build successful
gradle

  • b,spring-aspects需要依赖三方jar(精通aop的应该知道spring-aop和aspectj的关系,在使用注解方式实现spring-aop的时候,需要导入aspectj的jar,有兴趣的可以去查阅下官方文档aop相关文档),这里编译可能会报错,不影响我们编译使用,我们先不处理

  • c,使用工具编译时,可能会出现内存溢出情况,这里我们编译时需要增加相关参数

    1
             -XX:MaxPermSize=2048m -Xmx2048m -XX:MaxHeapSize=2048m
  • d) 以上工作完成之后,我们可以开始导入编辑编译配置了,如下图(需要注意的地方已在图中标注)

idea使用导入springframework项目

gradle
点击 import project
gradle
使用gradle导入项目
gradle
最后点击finish (需要从gradle仓库下载所需依赖包 这个过程可能比较久 决定因素是 网络环境及机器环境 )

导入成功:
gradle

常见问题:

  • 导入项目的时候还需要下载gradle 软件
    Setting–》gradle 选择gradle软件目录

gradle

  • 可能由于网络原因构建失败 在gradle窗口选择spring项目右击 菜单选择refreshGradle dependencies

gradle

新建Model

gradle
选择Gradle项目 next

gradle
输入项目名称
gradle
确认项目存储路径 最后点击finish
gradle
新建完成:
gradle
编辑build.gradle
compile(project(“:spring-context”))
gradle

新增AppConfig.java和MemberService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yanwen.springTest;

import com.yanwen.springTest.service.MemberService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

@ComponentScan("com.yanwen.springTest")
@Configuration
public class AppConfig {
public static void main(String[] args){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(annotationConfigApplicationContext.getBean(MemberService.class));
}
}

package com.yanwen.springTest.service;

import org.springframework.stereotype.Service;

@Service
public class MemberService {
}

运行Appconfig main方法

gradle

tomcat 三种部署方式

一Tomcat自动部署:

只要将一个Web应用的WebContent级的AppName直接扔进%Tomcat_Home%\webapps文件夹下,系统会把该web应用直接部署到Tomcat中
因为:

二增加自定义的Web部署文件

我们需要在%Tomcat_Home%\conf路径下新建一个文件夹catalina——再在其中新建一个localhost文件夹——最后再新建一个XML文件,即增加两层目录并新增XML文件:%Tomcat_Home%\conf\Catalina\localhost\web应用配置文件.xml ,该文件就是部署Web应用的配置文件。例如,我们新建一个%Tomcat_Home%\conf\Catalina\localhost\XXX.xml, 该文件的内容如下:

1
<Context path="/XXX" reloadable="true" docBase="D:\workspace\WebApp\AppName" workDir="D:\workspace\WebApp\work"/>

手动修改%Tomcat_Home%\conf\server.xml文件来部署web应用

打开%Tomcat_Home%\conf\server.xml文件并在其中增加以下元素:

1
2

<Context docBase="D:\workspace\WebApp\AppName" path="/XXX" debug="0" reloadable="false" />

总结:第一种和第二种:
tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用

RabbitMq

思考问题:为什么要使用消息队列

一个系统需要调用多个系统或者模块,互相之间调用很复杂,维护起来很麻烦。

情景1–未使用队列前

logo
如果现在新增e系统,就需要a系统的开发改系统;
如果现在D不需要A系统调用接口,就需要A系统的开发改系统
……

消息队列优点–解耦

logo
哪个系统需要数据,自己去MQ里消费数据

情景2-未使用队列

logo
这样,系统调用太多接口,影响用户体验

消息队列优点–异步

logo

思考:引入消息队列会存在哪些问题

消息队列存在问题:

  • 1 系统可用性降低

系统引入的外部依赖越多,越容易挂掉,本来你就是A系统调用BCD三个系统的接口就好,ABCD四个系统好哈的,没啥问题,
现在改为MQ,若MQ挂了怎么办

  • 系统复杂性提高

例如:现在使用了MQ,你需要考虑消息是否重复消费? 怎么处理消息丢失情况?问题大一堆,很痛苦吧

  • 系统一致性问题

使用MQ后,BD系统写库成功,结果c系统写库失败,现在怎么办?

RabbitMq可靠性–镜像集群模式

logo

数据丢失怎么办

logo
生产者

  • 1 设置channel 设置成confirm的模式
  • 2 发送一个消息
  • 3 RabbitMq如果接收到了这条消息的话,
    就会回调你系统里一个接口,通知消息已经收到了;
    如果接收消息失败,也通知你消息接收失败,
    此时候你可以进行重推

RabbitMq:

  • rabbitmq:持久化磁盘(queue 持久化,发送消息,deliveryMode=2)

消费者:

  • auto ack机制,消费到了一条消息

数据丢失处理方案图

logo

关于重复消费

解决方案:每个消息都有一个唯一id,如果已经处理过无需再次处理

其他问题

  • 1 )如果Mq数据挤压怎么办
    解决方案:
    第一步修复consumer故障

    第二步 临时部署多一些consumer应用进行消费

  • 2)rabbitmq设置了消息过期时间,导致数据丢失了,怎么办
    解决方案:生产者消息重发
  • 3)mq数据挤压,磁盘满了怎么办?
    解决方案:临时写一个程序把消费一个放弃一个;在进行重推消息

SpringSession源码(二)

如何使用spring session
上一篇文章中介绍了Spring-Session的核心原理,Filter,Session,Repository等等,传送门:SpringSession源码

这篇继上一篇的原理逐渐深入Spring-Session中的事件机制原理的探索。众所周知,Servlet规范中有对HttpSession的事件的处理,如:HttpSessionEvent/HttpSessionIdListener/HttpSessionListener,可以查看Package javax.servlet

在Spring-Session中也有相应的Session事件机制实现,包括Session创建/过期/删除事件。
本文主要从以下方面探索Spring-Session中事件机制

  • Session事件的抽象
  • 事件的触发机制

    Session事件的抽象

    先来看下Session事件抽象UML类图,整体掌握事件之间的依赖关系。
    Session事件的抽象
    Session Event最顶层是ApplicationEvent,即Spring上下文事件对象。由此可以看出Spring-Session的事件机制是基于Spring上下文事件实现。

抽象的AbstractSessionEvent事件对象提供了获取Session(这里的是指Spring Session的对象)和SessionId。

基于事件的类型,分类为:

  • Session创建事件
  • Session删除事件
  • Session过期事件
    事件对象只是对事件本身的抽象,描述事件的属性,如:

获取事件产生的源:getSource获取事件产生源
获取相应事件特性:getSession/getSessoinId获取时间关联的Session
下面再深入探索以上的Session事件是如何触发,从事件源到事件监听器的链路分析事件流转过程。

二.事件的触发机制

介绍Session Event事件基于Spring的ApplicationEvent实现。先简单认识spring上下文事件机制:
Session事件的抽象

  • ApplicationEventPublisher实现用于发布Spring上下文事件ApplicationEvent
  • ApplicationListener实现用于监听Spring上下文事件ApplicationEvent
  • ApplicationEvent抽象上下文事件

那么在Spring-Session中必然包含事件发布者ApplicationEventPublisher发布Session事件和ApplicationListener监听Session事件。

可以看出ApplicationEventPublisher发布一个事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@FunctionalInterface
public interface ApplicationEventPublisher {

/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an application event. Events may be framework events
* (such as RequestHandledEvent) or application-specific events.
* @param event the event to publish
* @see org.springframework.web.context.support.RequestHandledEvent
*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}

/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an event.
* <p>If the specified {@code event} is not an {@link ApplicationEvent},
* it is wrapped in a {@link PayloadApplicationEvent}.
* @param event the event to publish
* @since 4.2
* @see PayloadApplicationEvent
*/
void publishEvent(Object event);

}

ApplicationListener用于监听相应的事件:

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);

}

Session事件的流程实现如下:
Session事件的抽象
RedisMessageListenerContainer初始化时会启动一个线程SubscriptionTask进行订阅redis信息

上图展示了Spring-Session事件流程图,事件源来自于Redis键空间通知,在spring-data-redis项目中抽象MessageListener监听Redis事件源,然后将其传播至spring应用上下文发布者,由发布者发布事件。在spring上下文中的监听器Listener即可监听到Session事件。

因为两者是Spring框架提供的对Spring的ApplicationEvent的支持。Session Event基于ApplicationEvent实现,必然也有其相应发布者和监听器的的实现。

Spring-Session中的RedisSession的SessionRepository是RedisOperationSessionRepository。所有关于RedisSession的管理操作都是由其实现,所以Session的产生源是RedisOperationSessionRepository。

在RedisOperationSessionRepository中持有ApplicationEventPublisher对象用于发布Session事件。

1
2
3
4
5
6
7
8
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
}
@Override
public void publishEvent(Object event) {
}
};

但是该ApplicationEventPublisher是空实现,实际实现是在应用启动时由Spring-Session自动配置。在spring-session-data-redis模块中RedisHttpSessionConfiguration中有关于创建RedisOperationSessionRepository Bean时将调用set方法将ApplicationEventPublisher配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
SchedulingConfigurer {

private ApplicationEventPublisher applicationEventPublisher;

@Bean
public RedisOperationsSessionRepository sessionRepository() {
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
redisTemplate);
// 注入依赖
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
return sessionRepository;
}

// 注入上下文中的ApplicationEventPublisher Bean
@Autowired
public void setApplicationEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

}

在进行自动配置时,将上下文中的ApplicationEventPublisher的注入,实际上即ApplicationContext对象。
对于ApplicationListener是由应用开发者自行实现,注册成Bean即可。当有Session Event发布时,即可监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* session事件监听器
*
* @author huaijin
*/
@Component
public class SessionEventListener implements ApplicationListener<SessionDeletedEvent> {

private static final String CURRENT_USER = "currentUser";

@Override
public void onApplicationEvent(SessionDeletedEvent event) {
Session session = event.getSession();
UserVo userVo = session.getAttribute(CURRENT_USER);
System.out.println("Current session's user:" + userVo.toString());
}
}

以上部分探索了Session事件的发布者和监听者,但是核心事件的触发发布则是由Redis的键空间通知机制触发,当有Session创建/删除/过期时,Redis键空间会通知Spring-Session应用。

RedisOperationsSessionRepository实现spring-data-redis中的MessageListener接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Listener of messages published in Redis.
*
* @author Costin Leau
* @author Christoph Strobl
*/
public interface MessageListener {

/**
* Callback for processing received objects through Redis.
*
* @param message message must not be {@literal null}.
* @param pattern pattern matching the channel (if specified) - can be {@literal null}.
*/
void onMessage(Message message, @Nullable byte[] pattern);
}

该监听器即用来监听redis发布的消息。RedisOperationsSessionRepositorys实现了该Redis键空间消息通知监听器接口,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class RedisOperationsSessionRepository implements
FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
MessageListener {

@Override
@SuppressWarnings("unchecked")
public void onMessage(Message message, byte[] pattern) {
// 获取该消息发布的redis通道channel
byte[] messageChannel = message.getChannel();
// 获取消息体内容
byte[] messageBody = message.getBody();

String channel = new String(messageChannel);

// 如果是由Session创建通道发布的消息,则是Session创建事件
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
// 从消息体中载入Session
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
// 发布创建事件
handleCreated(loaded, channel);
return;
}

// 如果消息体不是以过期键前缀,直接返回。因为spring-session在redis中的key命名规则:
// "${namespace}:sessions:expires:${sessionId}",如:
// session.example:sessions:expires:a5236a19-7325-4783-b1f0-db9d4442db9a
// 所以判断过期或者删除的键是否为spring-session的过期键。如果不是,可能是应用中其他的键的操作,所以直接return
String body = new String(messageBody);
if (!body.startsWith(getExpiredKeyPrefix())) {
return;
}

// 根据channel判断键空间的事件类型del或者expire时间
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
// Redis键空间消息通知内容即操作的键,spring-session键中命名规则:
// "${namespace}:sessions:expires:${sessionId}",以下是根据规则解析sessionId
String sessionId = body.substring(beginIndex, endIndex);

// 根据sessionId加载session
RedisSession session = getSession(sessionId, true);

if (session == null) {
logger.warn("Unable to publish SessionDestroyedEvent for session "
+ sessionId);
return;
}

if (logger.isDebugEnabled()) {
logger.debug("Publishing SessionDestroyedEvent for session " + sessionId);
}

cleanupPrincipalIndex(session);

// 发布Session delete事件
if (isDeleted) {
handleDeleted(session);
}
else {
// 否则发布Session expire事件
handleExpired(session);
}
}
}
}

下续再深入每种事件产生的前世今生。

Session创建事件的触发

Session事件的抽象

  • 由RedisOperationSessionRepository向Redis指定通道${namespace}:event:created:${sessionId}发布一个message
  • MessageListener的实现RedisOperationSessionRepository监听到Redis指定通道${namespace}:event:created:${sessionId}的消息
  • 将其传播至ApplicationEventPublisher
  • ApplicationEventPublisher发布SessionCreateEvent
  • ApplicationListener监听SessionCreateEvent,执行相应逻辑

RedisOperationSessionRepository中保存一个Session时,判断Session是否新创建。
如果新创建,则向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void save(RedisSession session) {
session.saveDelta();
// 判断是否为新创建的session
if (session.isNew()) {
// 获取redis指定的channel:${namespace}:event:created:${sessionId},
// 如:session.example:event:created:82sdd-4123-o244-ps123
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
// 向该通道发布session数据
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
// 设置session为非新创建
session.setNew(false);
}
}

该save方法的调用是由HttpServletResponse提交时——即返回客户端响应调用,上篇文章已经详解,这里不再赘述。关于RedisOperationSessionRepository实现MessageListener上述已经介绍,这里同样不再赘述。

Session删除事件的触发

删除事件中使用到了Redis KeySpace Notification,建议先了解该技术。

Session事件的抽象

  • 由RedisOperationSessionRepository删除Redis键空间中的指定Session的过期键,Redis键空间会向__keyevent@*:del的channel发布删除事件消息
  • MessageListener的实现RedisOperationSessionRepository监听到Redis指定通道__keyevent@*:del的消息
  • 将其传播至ApplicationEventPublisher
  • ApplicationEventPublisher发布SessionDeleteEvent
  • ApplicationListener监听SessionDeleteEvent,执行相应逻辑
    当调用HttpSession的invalidate方法让Session失效时,即会调用RedisOperationSessionRepository的deleteById方法删除Session的过期键。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * Allows creating an HttpSession from a Session instance.
    *
    * @author Rob Winch
    * @since 1.0
    */
    private final class HttpSessionWrapper extends HttpSessionAdapter<S> {
    HttpSessionWrapper(S session, ServletContext servletContext) {
    super(session, servletContext);
    }

    @Override
    public void invalidate() {
    super.invalidate();
    SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
    setCurrentSession(null);
    clearRequestedSessionCache();
    // 调用删除方法
    SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
    }
    }

上篇中介绍了包装Spring Session为HttpSession,这里不再赘述。这里重点分析deleteById内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void deleteById(String sessionId) {
// 如果session为空则返回
RedisSession session = getSession(sessionId, true);
if (session == null) {
return;
}

cleanupPrincipalIndex(session);
this.expirationPolicy.onDelete(session);
// 获取session的过期键
String expireKey = getExpiredKey(session.getId());
// 删除过期键,redis键空间产生del事件消息,被MessageListener即
// RedisOperationSessionRepository监听
this.sessionRedisOperations.delete(expireKey);
session.setMaxInactiveInterval(Duration.ZERO);
save(session);
}

后续流程同SessionCreateEvent流程。

Session失效事件的触发

Session的过期事件流程比较特殊,因为Redis的键空间通知的特殊性,Redis键空间通知不能保证过期键的通知的及时性。
Session事件的抽象

  • RedisOperationsSessionRepository中有个定时任务方法每整分运行访问整分Session过期键集合中的过期sessionId,如:spring:session:expirations:1439245080000。触发Redis键空间会向__keyevent@*:expired的channel发布过期事件消息
  • MessageListener的实现RedisOperationSessionRepository监听到Redis指定通道__keyevent@*:expired的消息
  • 将其传播至ApplicationEventPublisher
  • ApplicationEventPublisher发布SessionDeleteEvent
  • ApplicationListener监听SessionDeleteEvent,执行相应逻辑
1
2
3
4
@Scheduled(cron = "0 * * * * *")
public void cleanupExpiredSessions() {
this.expirationPolicy.cleanExpiredSessions();
}

定时任务每整分运行,执行cleanExpiredSessions方法。expirationPolicy是RedisSessionExpirationPolicy实例,是RedisSession过期策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void cleanExpiredSessions() {
// 获取当前时间戳
long now = System.currentTimeMillis();
// 时间滚动至整分,去掉秒和毫秒部分
long prevMin = roundDownMinute(now);
if (logger.isDebugEnabled()) {
logger.debug("Cleaning up sessions expiring at " + new Date(prevMin));
}
// 根据整分时间获取过期键集合,如:spring:session:expirations:1439245080000
String expirationKey = getExpirationKey(prevMin);
// 获取所有的所有的过期session
Set<Object> sessionsToExpire = this.redis.boundSetOps(expirationKey).members();
// 删除过期Session键集合
this.redis.delete(expirationKey);
// touch访问所有已经过期的session,触发Redis键空间通知消息
for (Object session : sessionsToExpire) {
String sessionKey = getSessionKey((String) session);
touch(sessionKey);
}
}

将时间戳滚动至整分

1
2
3
4
5
6
7
8
static long roundDownMinute(long timeInMs) {
Calendar date = Calendar.getInstance();
date.setTimeInMillis(timeInMs);
// 清理时间错的秒位和毫秒位
date.clear(Calendar.SECOND);
date.clear(Calendar.MILLISECOND);
return date.getTimeInMillis();
}

获取过期Session的集合

1
2
3
4
5
6
7
8
String getExpirationKey(long expires) {
return this.redisSession.getExpirationsKey(expires);
}

// 如:spring:session:expirations:1439245080000
String getExpirationsKey(long expiration) {
return this.keyPrefix + "expirations:" + expiration;
}

调用Redis的Exists命令,访问过期Session键,触发Redis键空间消息

1
2
3
4
5
6
7
8
9
10
/**
* By trying to access the session we only trigger a deletion if it the TTL is
* expired. This is done to handle
* https://github.com/spring-projects/spring-session/issues/93
*
* @param key the key
*/
private void touch(String key) {
this.redis.hasKey(key);
}

总结
至此Spring-Session的Session事件通知模块就已经很清晰:

  • Redis键空间Session事件源:Session创建通道/Session删除通道/Session过期通道
  • Spring-Session中的RedisOperationsSessionRepository消息监听器监听Redis的事件类型
  • RedisOperationsSessionRepository负责将其传播至ApplicationEventPublisher
  • ApplicationEventPublisher将其包装成ApplicationEvent类型的Session Event发布
  • ApplicationListener监听Session Event,处理相应逻辑

SpringSession源码

可参考spring 引入 session代码

再详细阅读源码之前先来看张图,介绍下spring-session中的核心模块以及之间的交互。
springSesion模块

spring-session分为以下核心模块:

  • SessionRepositoryFilter:Servlet规范中Filter的实现,用来切换HttpSession至Spring Session,包装HttpServletRequest和HttpServletResponse
  • HttpServerletRequest/HttpServletResponse/HttpSessionWrapper包装器:包装原有的HttpServletRequest、HttpServletResponse和Spring Session,实现切换Session和透明继承HttpSession的关键之所在
  • Session:Spring Session模块
  • SessionRepository:管理Spring Session的模块
  • HttpSessionStrategy:映射HttpRequst和HttpResponse到Session的策略

    1. SessionRepositoryFilter

    SessionRepositoryFilter是一个Filter过滤器,符合Servlet的规范定义,用来修改包装请求和响应。这里负责包装切换HttpSession至Spring Session的请求和响应。
    SpringHttpSessionConfiguration 类定义SessionRepositoryFilter Bean,而在spring项目DelegatingFilterProxy代理装配SessionRepositoryFilter可参考spring 引入 session代码
    而spring boot项目为何自动装配SessionRepositoryFilter 待补充)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Override
    protected void doFilterInternal(HttpServletRequest request,
    HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    // 设置SessionRepository至Request的属性中
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
    // 包装原始HttpServletRequest至SessionRepositoryRequestWrapper
    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
    request, response, this.servletContext);
    // 包装原始HttpServletResponse响应至SessionRepositoryResponseWrapper
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
    wrappedRequest, response);
    // 设置当前请求的HttpSessionStrategy策略
    HttpServletRequest strategyRequest = this.httpSessionStrategy
    .wrapRequest(wrappedRequest, wrappedResponse);
    // 设置当前响应的HttpSessionStrategy策略
    HttpServletResponse strategyResponse = this.httpSessionStrategy
    .wrapResponse(wrappedRequest, wrappedResponse);
    try {
    filterChain.doFilter(strategyRequest, strategyResponse);
    }
    finally {
    // 提交session
    wrappedRequest.commitSession();
    }
    }

以上是SessionRepositoryFilter的核心操作,每个HttpRequest进入,都会被该Filter包装成切换Session的请求很响应对象。

SessionRepositoryRequestWrapper

对于developers获取HttpSession的api

1
2
HttpServletRequest request = ...;
HttpSession session = request.getSession(true);

在spring session中request的实际类型SessionRepositoryRequestWrapper。调用SessionRepositoryRequestWrapper的getSession方法会触发创建spring session,而非web容器的HttpSession。

SessionRepositoryRequestWrapper用来包装原始的HttpServletRequest实现HttpSession切换至Spring Session。是透明Spring Session透明集成HttpSession的关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper {

private final String CURRENT_SESSION_ATTR = HttpServletRequestWrapper.class
.getName();

// 当前请求sessionId有效
private Boolean requestedSessionIdValid;
// 当前请求sessionId无效
private boolean requestedSessionInvalidated;
private final HttpServletResponse response;
private final ServletContext servletContext;

private SessionRepositoryRequestWrapper(HttpServletRequest request,
HttpServletResponse response, ServletContext servletContext) {
// 调用HttpServletRequestWrapper构造方法,实现包装
super(request);
this.response = response;
this.servletContext = servletContext;
}
}

SessionRepositoryRequestWrapper继承Servlet规范中定义的包装器HttpServletRequestWrapper。HttpServletRequestWrapper是Servlet规范api提供的用于扩展HttpServletRequest的扩张点——即装饰器模式,可以通过重写一些api达到功能点的增强和自定义。

HttpServletRequestWrapper中持有一个HttpServletRequest对象,然后实现HttpServletRequest接口的所有方法,所有方法实现中都是调用持有的HttpServletRequest对象的相应的方法。继承HttpServletRequestWrapper 可以对其重写。SessionRepositoryRequestWrapper继承HttpServletRequestWrapper,在构造方法中将原有的HttpServletRequest通过调用super完成对HttpServletRequestWrapper中持有的HttpServletRequest初始化赋值,然后重写和session相关的方法。这样就保证SessionRepositoryRequestWrapper的其他方法调用都是使用原有的HttpServletRequest的数据,只有session相关的是重写的逻辑。

这里的设计是否很精妙!一切都多亏与Servlet规范设计的的巧妙啊!

1
2
3
4
@Override
public HttpSessionWrapper getSession() {
return getSession(true);
}

重写HttpServletRequest的getSession()方法,调用有参数getSession(arg)方法,默认为true,表示当前reques没有session时创建session。继续看下有参数getSession(arg)的重写逻辑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Override
public HttpSessionWrapper getSession(boolean create) {
// 从当前请求的attribute中获取session,如果有直接返回
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}

// 获取当前request的sessionId,这里使用了HttpSessionStrategy
// 决定怎样将Request映射至Session,默认使用Cookie策略,即从cookies中解析sessionId
String requestedSessionId = getRequestedSessionId();
// 请求的如果sessionId存在且当前request的attribute中的没有session失效属性
// 则根据sessionId获取spring session
if (requestedSessionId != null
&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {
S session = getSession(requestedSessionId);
// 如果spring session不为空,则将spring session包装成HttpSession并
// 设置到当前Request的attribute中,防止同一个request getsession时频繁的到存储器
//中获取session,提高性能
if (session != null) {
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(session, getServletContext());
currentSession.setNew(false);
setCurrentSession(currentSession);
return currentSession;
}
// 如果根据sessionId,没有获取到session,则设置当前request属性,此sessionId无效
// 同一个请求中获取session,直接返回无效
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
}
// 判断是否创建session
if (!create) {
return null;
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException(
"For debugging purposes only (not an error)"));
}
// 根据sessionRepository创建spring session
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
// 设置session的最新访问时间
session.setLastAccessedTime(System.currentTimeMillis());
// 包装成HttpSession透明化集成
currentSession = new HttpSessionWrapper(session, getServletContext());
// 设置session至Requset的attribute中,提高同一个request访问session的性能
setCurrentSession(currentSession);
return currentSession;
}

再来看下spring session的持久化。上述SessionRepositoryFilter在包装HttpServletRequest后,执行FilterChain中使用finally保证请求的Session始终session会被提交,此提交操作中将sesionId设置到response的head中并将session持久化至存储器中。

持久化只持久spring session,并不是将spring session包装后的HttpSession持久化,因为HttpSession不过是包装器,持久化没有意义。

再来看下包装的响应SessionRepositoryResponseWrapper。

SessionRepositoryResponseWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Allows ensuring that the session is saved if the response is committed.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryResponseWrapper
extends OnCommittedResponseWrapper {
private final SessionRepositoryRequestWrapper request;
/**
* Create a new {@link SessionRepositoryResponseWrapper}.
* @param request the request to be wrapped
* @param response the response to be wrapped
*/
SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
HttpServletResponse response) {
super(response);
if (request == null) {
throw new IllegalArgumentException("request cannot be null");
}
this.request = request;
}
@Override
protected void onResponseCommitted() {
this.request.commitSession();
}
}

上面的注释已经非常详细,这里不再赘述。这里只讲述为什么需要包装原始的响应。从注释上可以看出包装响应时为了:确保如果响应被提交session能够被保存。

这里我有点疑惑:在上述的SessionRepositoryFilter.doFilterInternal方法中不是已经request.commitSession()了吗,FilterChain执行完或者异常后都会执行Finally中的request.commitSession。为什么这里仍然需要包装响应,为了确保session能够保存,包装器中的onResponseCommitted方法可以看出也是做了一次request.commitSession()

原因:一旦response执行flushBuffer方法,迫使Response中在Buffer中任何数据都会被返回至client端。这个方法自动提交响应中的status code和head。那么如果不包装请求,监听flushBuffer事件在提交response前,将session写入response和持久化session,将导致作者说的无法追踪session。(来源网上)

SessionRepositoryResponseWrapper继承父类OnCommittedResponseWrapper,其中flushBuffer方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
* before calling the superclass <code>flushBuffer()</code>.
* @throws IOException if an input or output exception occurred
*/
@Override
public void flushBuffer() throws IOException {
doOnResponseCommitted();
super.flushBuffer();
}


/**
* Calls <code>onResponseCommmitted()</code> with the current contents as long as
* {@link #disableOnResponseCommitted()} was not invoked.
*/
private void doOnResponseCommitted() {
if (!this.disableOnCommitted) {
onResponseCommitted();
disableOnResponseCommitted();
}
}

重写HttpServletResponse方法,监听response commit,当发生response commit时,可以在commit之前写session至response中并持久化session

再看SessionRepository之前,先来看下spring session中的session接口。

Session接口

spring-session和tomcat中的Session的实现模式上有很大不同,tomcat中直接对HttpSession接口进行实现,而spring-session中则抽象出单独的Session层接口,让后再使用适配器模式将Session适配层Servlet规范中的HttpSession。spring-sesion中关于session的实现和适配整个UML类图如下:

springSesion模块

Session是spring-session对session的抽象,主要是为了鉴定用户,为Http请求和响应提供上下文过程,该Session可以被HttpSession、WebSocket Session,非WebSession等使用。定义了Session的基本行为:

  • getId:获取sessionId
  • setAttribute:设置session属性
  • getAttribte:获取session属性

ExipringSession:提供Session额外的过期特性。定义了以下关于过期的行为:

  • setLastAccessedTime:设置最近Session会话过程中最近的访问时间
  • getLastAccessedTime:获取最近的访问时间
  • setMaxInactiveIntervalInSeconds:设置Session的最大闲置时间
  • getMaxInactiveIntervalInSeconds:获取最大闲置时间
  • isExpired:判断Session是否过期

MapSession:基于java.util.Map的ExpiringSession的实现

RedisSession:基于MapSession和Redis的ExpiringSession实现,提供Session的持久化能力

先来看下MapSession的代码源码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class MapSession implements ExpiringSession, Serializable {
/**
* Default {@link #setMaxInactiveIntervalInSeconds(int)} (30 minutes).
*/
public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800;

private String id;
private Map<String, Object> sessionAttrs = new HashMap<String, Object>();
private long creationTime = System.currentTimeMillis();
private long lastAccessedTime = this.creationTime;

/**
* Defaults to 30 minutes.
*/
private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;

MapSession中持有HashMap类型的变量sessionAtts用于存储Session设置属性,比如调用的setAttribute方法的k-v就存储在该HashMap中。这个和tomcat内部实现HttpSession的方式类似,tomcat中使用了ConcurrentHashMap存储。

其中lastAccessedTime用于记录最近的一次访问时间,maxInactiveInterval用于记录Session的最大闲置时间(过期时间-针对没有Request活跃的情况下的最大时间,即相对于最近一次访问后的最大闲置时间)。

1
2
3
4
5
6
7
8
public void setAttribute(String attributeName, Object attributeValue) {
if (attributeValue == null) {
removeAttribute(attributeName);
}
else {
this.sessionAttrs.put(attributeName, attributeValue);
}
}

setAttribute方法极其简单,null时就移除attributeName,否则put存储。

重点熟悉RedisSession如何实现Session的行为:setAttribute、persistence等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
* basis for its mapping. It keeps track of any attributes that have changed. When
* {@link org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession#saveDelta()}
* is invoked all the attributes that have been changed will be persisted.
*
* @author Rob Winch
* @since 1.0
*/
final class RedisSession implements ExpiringSession {
private final MapSession cached;
private Long originalLastAccessTime;
private Map<String, Object> delta = new HashMap<String, Object>();
private boolean isNew;
private String originalPrincipalName;

首先看javadocs,对于阅读源码,学会看javadocs非常重要!

基于MapSession的基本映射实现的Session,能够追踪发生变化的所有属性,当调用saveDelta方法后,变化的属性将被持久化!

在RedisSession中有两个非常重要的成员属性:

  • cached:实际上是一个MapSession实例,用于做本地缓存,每次在getAttribute时无需从Redis中获取,主要为了improve性能
  • delta:用于跟踪变化数据,做持久化
    再来看下RedisSession中最为重要的行为saveDelta——持久化Session至Redis中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /**
    * Saves any attributes that have been changed and updates the expiration of this
    * session.
    */
    private void saveDelta() {
    // 如果delta为空,则Session中没有任何数据需要存储
    if (this.delta.isEmpty()) {
    return;
    }
    String sessionId = getId();
    // 使用spring data redis将delta中的数据保存至Redis中
    getSessionBoundHashOperations(sessionId).putAll(this.delta);
    String principalSessionKey = getSessionAttrNameKey(
    FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
    String securityPrincipalSessionKey = getSessionAttrNameKey(
    SPRING_SECURITY_CONTEXT);
    if (this.delta.containsKey(principalSessionKey)
    || this.delta.containsKey(securityPrincipalSessionKey)) {
    if (this.originalPrincipalName != null) {
    String originalPrincipalRedisKey = getPrincipalKey(
    this.originalPrincipalName);
    RedisOperationsSessionRepository.this.sessionRedisOperations
    .boundSetOps(originalPrincipalRedisKey).remove(sessionId);
    }
    String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
    this.originalPrincipalName = principal;
    if (principal != null) {
    String principalRedisKey = getPrincipalKey(principal);
    RedisOperationsSessionRepository.this.sessionRedisOperations
    .boundSetOps(principalRedisKey).add(sessionId);
    }
    }
    // 清空delta,代表没有任何需要持久化的数据。同时保证
    //SessionRepositoryFilter和SessionRepositoryResponseWrapper的onResponseCommitted
    //只会持久化一次Session至Redis中,解决前面提到的疑问
    this.delta = new HashMap<String, Object>(this.delta.size());
    // 更新过期时间,滚动至下一个过期时间间隔的时刻
    Long originalExpiration = this.originalLastAccessTime == null ? null
    : this.originalLastAccessTime + TimeUnit.SECONDS
    .toMillis(getMaxInactiveIntervalInSeconds());
    RedisOperationsSessionRepository.this.expirationPolicy
    .onExpirationUpdated(originalExpiration, this);
    }

从javadoc中可以看出,saveDelta用于存储Session的属性:

  • 保存Session中的属性数据至Redis中
  • 清空delta中数据,防止重复提交Session中的数据
  • 更新过期时间至下一个过期时间间隔的时刻

再看下RedisSession中的其他行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 设置session的存活时间,即最大过期时间。先保存至本地缓存,然后再保存至delta
public void setMaxInactiveIntervalInSeconds(int interval) {
this.cached.setMaxInactiveIntervalInSeconds(interval);
this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());
flushImmediateIfNecessary();
}

// 直接从本地缓存获取过期时间
public int getMaxInactiveIntervalInSeconds() {
return this.cached.getMaxInactiveIntervalInSeconds();
}

// 直接从本地缓存中获取Session中的属性
@SuppressWarnings("unchecked")
public Object getAttribute(String attributeName) {
return this.cached.getAttribute(attributeName);
}

// 保存Session属性至本地缓存和delta中
public void setAttribute(String attributeName, Object attributeValue) {
this.cached.setAttribute(attributeName, attributeValue);
this.delta.put(getSessionAttrNameKey(attributeName), attributeValue);
flushImmediateIfNecessary();
}

除了MapSession和RedisSession还有JdbcSession、MongoExpiringSession,感兴趣的读者可以自行阅读。

下面看SessionRepository的逻辑。SessionRepository是spring session中用于管理spring session的核心组件。

SessionRepository

javadoc中描述SessionRepository为管理spring-session的接口实例。抽象出:

1
2
3
4
S createSession();
void save(S session);
S getSession(String id);
void delete(String id);

创建、保存、获取、删除Session的接口行为。根据Session的不同,分为很多种Session操作仓库。
springSesion模块

这里重点介绍下RedisOperationsSessionRepository。在详细介绍其之前,了解下RedisOperationsSessionRepository的数据存储细节。

当创建一个RedisSession,然后存储在Redis中时,RedisSession的存储细节如下:

spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe
spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
spring:session:expirations:1439245080000

Redis会为每个RedisSession存储三个k-v。

  • 第一个k-v用来存储Session的详细信息,包括Session的过期时间间隔、最近的访问时间、attributes等等。这个k的过期时间为Session的最大过期时间 + 5分钟。如果默认的最大过期时间为30分钟,则这个k的过期时间为35分钟
  • 第二个k-v用来表示Session在Redis中的过期,这个k-v不存储任何有用数据,只是表示Session过期而设置。这个k在Redis中的过期时间即为Session的过期时间间隔
  • 第三个k-v存储这个Session的id,是一个Set类型的Redis数据结构。这个k中的最后的1439245080000值是一个时间戳,根据这个Session过期时刻滚动至下一分钟而计算得出。
    简单描述下,为什么RedisSession的存储用到了三个Key,而非一个Redis过期Key。
    对于Session的实现,需要支持HttpSessionEvent,即Session创建、过期、销毁等事件。当应用用监听器设置监听相应事件,Session发生上述行为时,监听器能够做出相应的处理。
    Redis的强大之处在于支持KeySpace Notifiction——键空间通知。即可以监视某个key的变化,如删除、更新、过期。

但是Redis中带有过期的key有两种方式:

  • 当访问时发现其过期
  • Redis后台逐步查找过期键
    当访问时发现其过期,会产生过期事件,但是无法保证key的过期时间抵达后立即生成过期事件。
    spring-session为了能够及时的产生Session的过期时的过期事件,所以增加了:

spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
spring:session:expirations:1439245080000

spring-session中有个定时任务,每个整分钟都会查询相应的spring:session:expirations:整分钟的时间戳中的过期SessionId,然后再访问一次这个SessionId,即spring:session:sessions:expires:SessionId,以便能够让Redis及时的产生key过期事件——即Session过期事件。

接下来再看下RedisOperationsSessionRepository中的具体实现原理

createSession方法:

1
2
3
4
5
6
7
8
9
public RedisSession createSession() {
// new一个RedisSession实例
RedisSession redisSession = new RedisSession();
// 如果设置的最大过期时间不为空,则设置RedisSession的过期时间
if (this.defaultMaxInactiveInterval != null) {
redisSession.setMaxInactiveIntervalInSeconds(this.defaultMaxInactiveInterval);
}
return redisSession;
}

再来看下RedisSession的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Creates a new instance ensuring to mark all of the new attributes to be
* persisted in the next save operation.
*/
RedisSession() {
// 设置本地缓存为MapSession
this(new MapSession());
// 设置Session的基本属性
this.delta.put(CREATION_TIME_ATTR, getCreationTime());
this.delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds());
this.delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime());
// 标记Session的是否为新创建
this.isNew = true;
// 持久化
flushImmediateIfNecessary();
}

save方法:

1
2
3
4
5
6
7
8
9
10
public void save(RedisSession session) {
// 调用RedisSession的saveDelta持久化Session
session.saveDelta();
// 如果Session为新创建,则发布一个Session创建的事件
if (session.isNew()) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.setNew(false);
}
}

getSession方法:

1
2
3
4
5
// 根据SessionId获取Session,这里的false代表的参数
// 指:如果Session已经过期,是否仍然获取返回
public RedisSession getSession(String id) {
return getSession(id, false);
}

在有些情况下,Session过期,仍然需要能够获取到Session。这里先来看下getSession(String id, boolean allowExpired):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

private RedisSession getSession(String id, boolean allowExpired) {
// 根据SessionId,从Redis获取到持久化的Session信息
Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
// 如果Redis中没有,则返回null
if (entries.isEmpty()) {
return null;
}
// 根据Session信息,加载创建一个MapSession对象
MapSession loaded = loadSession(id, entries);
// 判断是否允许过期获取和Session是否过期
if (!allowExpired && loaded.isExpired()) {
return null;
}
// 根据MapSession new一个信息的RedisSession,此时isNew为false
RedisSession result = new RedisSession(loaded);
// 设置最新的访问时间
result.originalLastAccessTime = loaded.getLastAccessedTime();
return result;
}

这里需要注意的是loaded.isExpired()和loadSession。loaded.isExpired判断Session是否过期,如果过期返回null:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean isExpired() {
// 根据当前时间判断是否过期
return isExpired(System.currentTimeMillis());
}
boolean isExpired(long now) {
// 如果maxInactiveInterval小于0,表示Session永不过期
if (this.maxInactiveInterval < 0) {
return false;
}
// 最大过期时间单位转换为毫秒
// 当前时间减去Session的最大有效期间隔以获取理论上有效的上一次访问时间
// 然后在与实际的上一次访问时间进行比较
// 如果大于,表示理论上的时间已经在实际的访问时间之后,那么表示Session已经过期
return now - TimeUnit.SECONDS
.toMillis(this.maxInactiveInterval) >= this.lastAccessedTime;
}

loadSession中,将Redis中存储的Session信息转换为MapSession对象,以便从Session中获取属性时能够从内存直接获取提高性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private MapSession loadSession(String id, Map<Object, Object> entries) {
MapSession loaded = new MapSession(id);
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
String key = (String) entry.getKey();
if (CREATION_TIME_ATTR.equals(key)) {
loaded.setCreationTime((Long) entry.getValue());
}
else if (MAX_INACTIVE_ATTR.equals(key)) {
loaded.setMaxInactiveIntervalInSeconds((Integer) entry.getValue());
}
else if (LAST_ACCESSED_ATTR.equals(key)) {
loaded.setLastAccessedTime((Long) entry.getValue());
}
else if (key.startsWith(SESSION_ATTR_PREFIX)) {
loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()),
entry.getValue());
}
}
return loaded;
}

至此,可以看出spring-session中request.getSession(false)的过期实现原理。

delete方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void delete(String sessionId) {
// 获取Session
RedisSession session = getSession(sessionId, true);
if (session == null) {
return;
}
cleanupPrincipalIndex(session);
// 从过期集合中移除sessionId
this.expirationPolicy.onDelete(session);
String expireKey = getExpiredKey(session.getId());
// 删除session的过期键
this.sessionRedisOperations.delete(expireKey);
// 设置session过期
session.setMaxInactiveIntervalInSeconds(0);
save(session);
}

至此RedisOperationsSessionRepository的核心原理就介绍完毕。但是RedisOperationsSessionRepository中还包括关于Session事件的处理和清理Session的定时任务。这部分内容在后述的SessionEvent部分介绍。

HttpSessionStrategy

从javadoc中可以看出,HttpSessionStrategy是建立Request/Response和Session之间的映射关系的策略。
该策略接口中定义一套策略行为:

1
2
3
4
5
6
7
// 根据请求获取SessionId,即建立请求至Session的映射关系
String getRequestedSessionId(HttpServletRequest request);
// 对于新创建的Session,通知客户端
void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response);
// 对于session无效,通知客户端
void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);

如下UML类图:
springSesion模块

这里主要介绍CookieHttpSessionStrategy,这个也是默认的策略,可以查看spring-session中类SpringHttpSessionConfiguration,在注册SessionRepositoryFilter Bean时默认采用CookieHttpSessionStrategy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
sessionRepositoryFilter.setHttpSessionStrategy(
(MultiHttpSessionStrategy) this.httpSessionStrategy);
}
else {
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
}
return sessionRepositoryFilter;
}

下面来分析CookieHttpSessionStrategy的原理。该策略使用Cookie来映射Request/Response至Session。即request/requset的head中cookie存储SessionId,当请求至web服务器,可以解析请求head中的cookie,然后获取sessionId,根据sessionId获取spring-session。当创建新的session或者session过期,将相应的sessionId写入response的set-cookie或者从respose中移除sessionId。

getRequestedSessionId方法

1
2
3
4
5
6
7
8
public String getRequestedSessionId(HttpServletRequest request) {
// 获取当前请求的sessionId:session别名和sessionId映射
Map<String, String> sessionIds = getSessionIds(request);
// 获取当前请求的Session别名
String sessionAlias = getCurrentSessionAlias(request);
// 获取相应别名的sessionId
return sessionIds.get(sessionAlias);
}

接下来看下具体获取SessionIds的具体过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public String getRequestedSessionId(HttpServletRequest request) {
// 获取当前请求的sessionId:session别名和sessionId映射
Map<String, String> sessionIds = getSessionIds(request);
// 获取当前请求的Session别名
String sessionAlias = getCurrentSessionAlias(request);
// 获取相应别名的sessionId
return sessionIds.get(sessionAlias);
}


public Map<String, String> getSessionIds(HttpServletRequest request) {
// 解析request中的cookie值
List<String> cookieValues = this.cookieSerializer.readCookieValues(request);
// 获取sessionId
String sessionCookieValue = cookieValues.isEmpty() ? ""
: cookieValues.iterator().next();
Map<String, String> result = new LinkedHashMap<String, String>();
// 根据分词器对sessionId进行分割,因为spring-session支持多session。默认情况只有一个session
StringTokenizer tokens = new StringTokenizer(sessionCookieValue, this.deserializationDelimiter);
// 如果只有一个session,则设置默认别名为0
if (tokens.countTokens() == 1) {
result.put(DEFAULT_ALIAS, tokens.nextToken());
return result;
}
// 如果有多个session,则建立别名和sessionId的映射
while (tokens.hasMoreTokens()) {
String alias = tokens.nextToken();
if (!tokens.hasMoreTokens()) {
break;
}
String id = tokens.nextToken();
result.put(alias, id);
}
return result;
}


public List<String> readCookieValues(HttpServletRequest request) {
// 获取request的cookie
Cookie[] cookies = request.getCookies();
List<String> matchingCookieValues = new ArrayList<String>();
if (cookies != null) {
for (Cookie cookie : cookies) {
// 如果是以SESSION开头,则表示是SessionId,毕竟cookie不只有sessionId,还有可能存储其他内容
if (this.cookieName.equals(cookie.getName())) {
// 决策是否需要base64 decode
String sessionId = this.useBase64Encoding
? base64Decode(cookie.getValue()) : cookie.getValue();
if (sessionId == null) {
continue;
}
if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
sessionId = sessionId.substring(0,
sessionId.length() - this.jvmRoute.length());
}
// 存入list中
matchingCookieValues.add(sessionId);
}
}
}
return matchingCookieValues;
}

再来看下获取当前request对应的Session的别名方法getCurrentSessionAlias

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String getCurrentSessionAlias(HttpServletRequest request) {
// 如果session参数为空,则返回默认session别名
if (this.sessionParam == null) {
return DEFAULT_ALIAS;
}
// 从request中获取session别名,如果为空则返回默认别名
String u = request.getParameter(this.sessionParam);
if (u == null) {
return DEFAULT_ALIAS;
}
if (!ALIAS_PATTERN.matcher(u).matches()) {
return DEFAULT_ALIAS;
}
return u;
}

spring-session为了支持多session,才弄出多个session别名。当时一般应用场景都是一个session,都是默认的session别名0。

上述获取sessionId和别名映射关系中,也是默认别名0。这里返回别名0,所以返回当前请求对应的sessionId。

onNewSession方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void onNewSession(Session session, HttpServletRequest request,
HttpServletResponse response) {
// 从当前request中获取已经写入Cookie的sessionId集合
Set<String> sessionIdsWritten = getSessionIdsWritten(request);
// 判断是否包含,如果包含,表示该sessionId已经写入过cookie中,则直接返回
if (sessionIdsWritten.contains(session.getId())) {
return;
}
// 如果没有写入,则加入集合,后续再写入
sessionIdsWritten.add(session.getId());
Map<String, String> sessionIds = getSessionIds(request);
String sessionAlias = getCurrentSessionAlias(request);
sessionIds.put(sessionAlias, session.getId());
// 获取cookieValue
String cookieValue = createSessionCookieValue(sessionIds);
//将cookieValue写入Cookie中
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}

sessionIdsWritten主要是用来记录已经写入Cookie的SessionId,防止SessionId重复写入Cookie中。

onInvalidateSession方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public void onInvalidateSession(HttpServletRequest request,
HttpServletResponse response) {
// 从当前request中获取sessionId和别名映射
Map<String, String> sessionIds = getSessionIds(request);
// 获取别名
String requestedAlias = getCurrentSessionAlias(request);
// 移除sessionId
sessionIds.remove(requestedAlias);
String cookieValue = createSessionCookieValue(sessionIds);
// 写入移除后的sessionId
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
}

继续看下具体的写入writeCookieValue原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public void writeCookieValue(CookieValue cookieValue) {
// 获取request/respose和cookie值
HttpServletRequest request = cookieValue.getRequest();
HttpServletResponse response = cookieValue.getResponse();
String requestedCookieValue = cookieValue.getCookieValue();
String actualCookieValue = this.jvmRoute == null ? requestedCookieValue
: requestedCookieValue + this.jvmRoute;
// 构造servlet规范中的Cookie对象,注意这里cookieName为:SESSION,表示为Session,
// 上述的从Cookie中读取SessionId,也是使用该cookieName
Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
? base64Encode(actualCookieValue) : actualCookieValue);
// 设置cookie的属性:secure、path、domain、httpOnly
sessionCookie.setSecure(isSecureCookie(request));
sessionCookie.setPath(getCookiePath(request));
String domainName = getDomainName(request);
if (domainName != null) {
sessionCookie.setDomain(domainName);
}
if (this.useHttpOnlyCookie) {
sessionCookie.setHttpOnly(true);
}
// 如果cookie值为空,则失效
if ("".equals(requestedCookieValue)) {
sessionCookie.setMaxAge(0);
}
else {
sessionCookie.setMaxAge(this.cookieMaxAge);
}
// 写入cookie到response中
response.addCookie(sessionCookie);
}

至此,CookieHttpSessionStrategy介绍结束。

由于篇幅过长,关于spring-session event和RedisOperationSessionRepository清理session并且产生过期事件的部分后续文章介绍。

总结

spring-session提供集群环境下HttpSession的透明集成。spring-session的优势在于开箱即用,具有较强的设计模式。且支持多种持久化方式,其中RedisSession较为成熟,与spring-data-redis整合,可谓威力无穷。

nutch 坑

每次运行nutch都会在/tmp/hadoop-aaws/mapred/staging产生一个临时目录,若不定时清理,很容易inode(在同一个路径下,一级子目录的个数是有限制的)

统计当前文件夹下目录的个数:

1
ls -l |grep "^d"|wc -l

统计当前文件夹下文件的个数:

1
ls -l |grep "^-"|wc -l

统计当前文件夹下文件的个数,包括子文件夹里的 :

1
ls -lR|grep "^-"|wc -l

统计文件夹下目录的个数,包括子文件夹里的:

1
ls -lR|grep "^d"|wc -l

apacheHttpd日志说明

日志目录

  • 1,首先,你要确定你的apache工作路径:ps -aux
  • 2,其次,你可查看httpd.conf,看是否有特定的设置,一般logs文件夹与conf文件夹在同一个父目录下;
  • 3,访问日志为access_log,错误日志为error_log;
  • 4,如果上面的尝试都失败,那你再在/var/log/messages看看

access_log 访问日志

access_log为访问日志,记录所有对apache服务器进行请求的访问,它的位置和内容由CustomLog指令控制,LogFormat指令可以用来简化该日志的内容和格式

error_log 错误日志

error_log为错误日志,记录下任何错误的处理请求,它的位置和内容由ErrorLog指令控制,通常服务器出现什么错误,首先对它进行查阅,是一个最重要的日志文件。

看一条典型的access_log的日志记录:
61.155.149.20 - - [13/Jan/2017:15:42:47 +0800] “GET /category/db/ HTTP/1.1” 200 23225

1).61.155.149.20
这是一个请求到apache服务器的客户端ip,默认的情况下,第一项信息只是远程主机的ip地址,但我们如果需要apache查出主机的名字,可以将 HostnameLookups设置为on,不推荐使用,会大大降低网站速度。

2). -
这一项是空白,使用”-“来代替,用于记录浏览者的标识,对于大多数浏览器,这项都是空。

3). -
也为空,记录浏览者进行身份验证时提供的名字,大多数这项也为空。

4). [13/Jan/2017:15:42:47 +0800]
第四项是记录请求的时间,格式为[day/month/year:hour:minute:second zone],最后的+0800表示服务器所处的时区为东八区

5). “GET /category/db/ HTTP/1.1”
这一项最有用,首先,它告诉我们的服务器收到的是一个GET请求,其次,是客户端请求的资源路径,第三,客户端使用的协议时HTTP/1.1,整个格式为”%m %U%q %H”,即”请求方法/访问路径/协议”

6). 200
这是一个状态码,由服务器端发送回客户端,它告诉我们客户端的请求是否成功,或者是重定向,或者是碰到了什么样的错误,这项值为200,表示服务器已经成 功的响应了客户端的请求,一般来说,这项值以2开头的表示请求成功,以3开头的表示重定向,以4开头的标示客户端存在某些的错误,以5开头的标示服务器端 存在某些错误。

7).23225
这项表示服务器向客户端发送了多少的字节,在日志分析统计的时侯,把这些字节加起来就可以得知服务器在某点时间内总的发送数据量是多少

|