Spring Boot基础用法

Posted tuacy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot基础用法相关的知识,希望对你有一定的参考价值。

       Spring启动过程中会找出IOC容器里面特定类型的Bean,之后自动调用这些类型(一般是接口类)里面的方法。这种特性对我们非常有用,我们只需要实现这些特定类型的Bean并覆盖其方法,在方法里面加入我们自定义的一些逻辑。Spring就会在恰当的时机调用我们定义的这些类里面的方法。让我们可以做一些特别的逻辑。接下来我们就对这些特定类型的类做一个简单的收集。

一 实现Aware接口的类

       Spring中有很多继承Aware类的接口。比如BeanFactoryAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等.这些接口一般都有一个共同的回调方法setXXX()方法来设置对应的Bean,让我们实现Aware接口的Bean中获取到每种Aware接口对应的Bean。

Aware:翻译过来是知道的,已感知的,意识到的,所以这些接口从字面意思应该是能感知到Aware对应的Bean。

Aware接口Aware对应的Beran备注
ApplicationContextAwareApplicationContextSpring上下文
ApplicationEventPublisherAwareApplicationEventPublisher用于发布事件
BeanNameAware组件名字组件在IOC容器里面的名字
BeanFactoryAwareBeanFactory负责生产和管理Bean的工厂
BeanClassLoaderAwareBeanClassLoader让Bean知道它是由哪一类装载器负责装载的
ImportAware用来处理自定义注解的,比如将注解里面的某些属性值赋值给其他Bean
EnvironmentAwareEnvironment可以获取到系统的环境变量信息
EmbeddedValueResolverAwareStringValueResolver可以获取Spring加载properties文件的属性值
LoadTimeWeaverAwareLoadTimeWeaver可获取LoadTimeWeaver实例,用于在加载时处理类定义
MessageSourceAwareMessageSource国际化处理相关
NotificationPublisherAwareNotificationPublisherJMX通知
ResourceLoaderAwareResourceLoader可获取Spring中配置的加载程序(ResourceLoader),用于对资源进行访问;可用于访问类l类路径或文件资源
ServletConfigAwareServletConfigweb开发过程中获取ServletConfig和ServletContext信息
ServletContextAwareServletContextweb开发过程中获取ServletConfig和ServletContext信息

1.1 ApplicationContextAware

       ApplicationContextAware用于获取ApplicationContext上下文的情况(仅仅适用于当前运行的代码和已启动的Spring代码处于同一个Spring上下文,否则获取到的ApplicationContext是空的)。

       关于ApplicationContextAware的使用我们见到最多的一个应用场景是。想在一个普通的类里面(非Bean)里面获取到IOC容器里面的某个组件。这个时候我们一般会自定义一个类实现ApplicationContextAware接口(Spring启动过程中会找到这个类,然后会自动执行setApplicationContext()方法)得到ApplicationContext对象。当然了在这个自定义的类里面我们会把ApplicationContext对象设置成static的。之后在普通的类里面通过ApplicationContext我们就可以获取Spring IOC容器里面所有的Bean。具体实例如下所示。

/**
 * 通过ApplicationContextAware实现,在bean实例化后,经过Aware扫描时,
 * 发现实现了ApplicationContextAware接口,就会调用setApplicationContext方法注入ApplicationContext对象,
 * 这也是非常经典的一种获取上下文的方法。
 */
@Service
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware 

    private static ApplicationContext applicationContext = null;

    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) 
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    

    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) 
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    

    /**
     * 清除SpringContextHolder中的ApplicationContext为Null.
     */
    public static void clearHolder() 
        applicationContext = null;
    

    /**
     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
        SpringContextHolder.applicationContext = applicationContext;
    

    /**
     * 检查ApplicationContext不为空.
     */
    private static void assertContextInjected() 
        Preconditions.checkArgument(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");
    

也非常推荐大家在每个应用里面都可以尝试去建立这么一个这样的类备用。

1.2 ApplicationEventPublisherAware

       通过实现ApplicationEventPublisherAware接口,我们可以获取到ApplicationEventPublisher对象。ApplicationEventPublisher可以用来发布事件。我们也可以通过@Autowired把ApplicationEventPublisher对象注入进来。

@Service
public class LoadConfigFileService implements ApplicationEventPublisherAware 

    private ApplicationEventPublisher applicationEventPublisher;


    /**
     * 我们模拟一个这样的场景,比如,我们Spring启动的时候需要加载两部分的配置
     * 1. 配置文件里面的配置
     * 2. 数据库里面的配置
     * 但是这两部分的配置我们又是在两个Service里面实现的.
     */
    public boolean loadConfigFIle() 

        // TODO:加载配置文件里面的配置

        // 发布事件
        applicationEventPublisher.publishEvent(new ConfigFIleLoadSuccessEvent());

        return true;
    

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) 
        this.applicationEventPublisher = applicationEventPublisher;

    

1.3 BeanNameAware

       BeanNameAware接口的作用就是让实现这个接口的Bean知道自己在Spring IOC容器里的名字。而且听官方的意思是这个接口更多的使用在spring的框架代码中,实际开发环境应该不建议使用,因为spring认为bean的名字与bean的联系并不是很深。

1.4 BeanFactoryAware

       BeanFactoryAware用于获取BeanFactory(负责生产和管理Bean的工厂)。拿到BeanFactory之后,我们就可以对Spring IOC容器做各种操作了,比如从容器里面获取Bean,判断对应的Bean是否在容器里面等等。

1.5 BeanClassLoaderAware

       BeanClassLoaderAware的作用是让受管Bean本身知道它是由哪一类装载器负责装载的。

1.6 ImportAware

       ImportAware的作用是用来处理自定义注解的,比如将注解的某些属性值赋值给其他bean的属性。有一点要注意的就是ImportAware也是要配合@Import()注解一起使用。

       我们通过一个简单的实例来说明,通过ImportAware来修改某个Bean的属性。把ChangeAttribute注解上的值设置给BeanImportAware组件的name属性。

/**
 * 注意这个类上的两个注解的使用
 * 1. @Import(BeanImportAware.class),BeanImportAware类实现了ImportAware接口
 * 2. @ChangeAttribute是我们自定义的一个注解,用来带参数的。会在BeanImportAware类里面去获取这个主句
 */
@Configuration
@Import(BeanImportAware.class)
@ChangeAttribute(value = "tuacy")
public class ImportAwareConfig 

@Configuration
public class BeanImportAware implements ImportAware 

    private String name;

    public String getName() 
        return name;
    

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

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) 

        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(ChangeAttribute.class.getName()));
        if (annoAttrs != null) 
            // 获取到ChangeAttribute注解里面value对应的值
            name = (String) annoAttrs.get("value");
        

    

1.7 EnvironmentAware

       实现EnvironmentAware接口可以获取到系统的环境变量信息。更加具体的信息可以去瞧一瞧Spring里面关于Environment的使用。我们也可以通过@Autowired把Environment注入进来。

1.8 EmbeddedValueResolverAware

       实现该接口用于获取接口获取StringValueResolver对象。StringValueResolver可以获取Spring读取properties文件里面的内容。和@Value注解读取配置文件内容的作用类似。所以我们也可以用@Value注解来替代EmbeddedValueResolverAware的使用。

1.9 LoadTimeWeaverAware

       组件实现该接口用于获取LoadTimeWeaver对象,用于在加载时处理类定义。也可以通过@Autowired注入LoadTimeWeaver来替代LoadTimeWeaverAware接口的使用。关于LoadTimeWeaver的具体使用我还没找到。

1.10 MessageSourceAware

       MessageSourceAware主要用于获取MessageSource,用来处理国际化相关。我们一般会直接在组件类名通过@Autowired注入MessageSource来替代MessageSourceAware接口的使用。更加具体的用法可以去看下MessageSource的使用。

1.11 NotificationPublisherAware

       组件实现该接口用于获取JMX通知发布者NotificationPublisher对象。拿到NotificationPublisher对象之后就可以在Spring里面发布消息了。也可以通过@Autowired注入NotificationPublisher对象。详细的用法可以去看看NotificationPublisher的使用。

1.12 ResourceLoaderAware

       组件实现该接口用于获取Spring中配置的加载程序ResourceLoader对象,用于对资源进行访问。另一种方式是通过@Autowired注入ResourceLoader对象。具体可以去看看ResourceLoader的使用。

1.13 ServletConfigAware

       web开发过程中实现该接口用于获取ServletConfig对象。获取ServletConfig和ServletContext信息。我们也可以通过@Autowired注入ServletConfig对象。

1.14 ServletContextAware

       web开发过程中组件实现给接口用于获取ServletContext对象。

二 Spring IOC扩展点

       Spring Boot里面Bean的生成,有一个特定的顺序: 声明(定义) --> 注册 --> 创建(实例化Instantiation) --> 初始化(Initialization)。

  • 声明: 声明一个Bean。简单来说就是做一个标记,让Spring知道他是一个Bean。比如我们经常见到的@Component、@Service、@Controller、@Repository、@Bean这些注解都是在声明定义一个Bean。
  • 注册: Spring Boot通过读取配置文件获取各个Bean的声明(定义)信息,并且对这些信息进行注册的过程。所有的Bean注册在BeanDefinitioin中。(其实就是根据各个Bean的声明,把每个声明的Bean转换成BeanDefinitioin的一个过程)
  • 创建(实例化–Instantiation): Bean的实例化则指的是Spring Boot通过Bean的注册信息(BeanDefinitionin)对各个Bean进行实例化的过程。
  • 初始化(Initialization): 在Bean创建之后,有些Bean可能设置了创建之后需要执行的方法。比如@PostConstruct注解,或者@Bean注解里面的initMethod属性。我们把这一部分操作当做初始化。

       Spring Boot对IOC的扩展点,就是让我们可以侵入到Bean生成过程的各个阶段。加入一些我们自定义的逻辑。

IOC扩展接口解释
BeanFactoryPostProcessor是针对于BeanFactory的扩展点,可以在BeanFactory初始化之后做一些操,这个时候Bean已经注册了,但是Bean还未创建前调用,一般在这里做修改Bean属性处理
BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口,即Spring会在调用BeanFactoryPostProcessor之前调用他。这个时候,所有的BeanDefinition已经被加载了。一般在这里增加一些Spirng扫描不到的Bean,比如第三方的Bean
InstantiationAwareBeanPostProcessor也是BeanPostProcessor的子接口,让我们可以在Bean创建的前后加入自定义的逻辑
SmartInstantiationAwareBeanPostProcessor智能实例化Bean后置处理器(继承InstantiationAwareBeanPostProcessor)。这个相当于InstantiationAwareBeanPostProcessor的扩展版本,增加了一个对Bean类型预测的回调
BeanPostProcessor让我们可以在Bean的初始化前后加入自定义的逻辑。这个时候Bean已经创建好了
InitializingBean在Bean所有的属性都被赋值后调用,属性会在Bean初始化的时候赋值
MergedBeanDefinitionPostProcessor在合并处理Bean定义的时候的回调
DestructionAwareBeanPostProcessor让我们可以在Bean销毁前加入自定义的逻辑

2.1 BeanFactoryPostProcessor

@FunctionalInterface
public interface BeanFactoryPostProcessor 

    /**
     * 这个方法被调用的时候, 所有的Bean已经被注册了,但是Bean还没有被创建出来。我们一般在这个里面修改一些Bean的属性
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;


      BeanFactoryPostProcessor是针对BeanFactory的扩展点,是BeanFactory的后处理类。BeanFactoryPostProcessor提供了个postProcessBeanFactory()方法,这个方法被调用的时候,所有的Bean已经被注册了(所有的Bean都已经转换成BeanDefinitioin了),但是要记住这个时候Bean还没有被创建。也就是说,通过它我们可以在初始化任何Bean之前,做各种操作,甚至读取并修改BeanDefinition(Bean定义的元数据)。特别强调一点千万不要在这个方法里面获取Bean。因为我曾经在这个方法里面获取Bean,虽然我们能获取到Bean但是会导致@PostConstruct失效

BeanFactory,我们可以稍微解释下。BeanFactory的地位相当高,它是各种Bean的工厂,它里面提供了一系列的getBean()方法。常用的ApplicationContext就继承了它。

      我们用一个简单的实例来看下BeanFactoryPostProcessor的使用。找到helloFactoryPostProcessorService名字对应的Bean的定义BeanDefinition,然后修改他的属性。

/**
 * 使用BeanFactoryPostProcessor实现一个简单的功能,
 * 找到helloFactoryPostProcessorService对应的bean修改desc属性对应的值
 */
@Component
public class CustomizeBeanFactoryPostProcessor implements BeanFactoryPostProcessor 


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
        AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition("helloFactoryPostProcessorService");
        MutablePropertyValues pv = abstractBeanDefinition.getPropertyValues();
        pv.addPropertyValue("desc", "hello BeanFactoryPostProcessor");
        abstractBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
    

2.2 BeanDefinitionRegistryPostProcessor

/**
 * 继承BeanFactoryPostProcessor,BeanFactoryPostProcessor能做的BeanDefinitionRegistryPostProcessor也能做
 */
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor 

    /**
     * 这个方法被调用的时候, 所有的BeanDefinition已经被加载了(定义,注册好了), 但是所有的Bean还没被创建
     * 在这个方法里面我们可以通过BeanDefinitionRegistry加入第三方的Bean
     */
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;


       BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor接口。BeanFactoryPostProcessor能做的事情BeanDefinitionRegistryPostProcessor也能做。额外BeanDefinitionRegistryPostProcessor多了一个方法postProcessBeanDefinitionRegistry(),这个方法会在Bean注册的时候执行(在Bean被定义但还没被创建的时候执行)。

       BeanDefinitionRegistryPostProcessor的使用场景。我们可以通过BeanDefinitionRegistryPostProcessor把第三方Bean添加到IOC容器里面去。比如如下的实例我们就添加了一个Bean到IOC容器里面去了。

@Component
public class CustomBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor 

    /**
     * 这个方法被调用的时候, 所有的BeanDefinition已经被加载了, 但是所有的Bean还没被创建
     * 定义 --> 实例化 --> 初始化
     * 在Bean被定义但还没被实例化的时候执行。
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException 
        // 创建一个Bean然后添加到BeanDefinitionRegistry里面去
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CustomBean.class).addConstructorArgValue("tuacy").addConstructorArgValue(18);
        //设置属性值
        builder.addPropertyValue("age", 28);
        //设置可通过@Autowire注解引用
        builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);
        //注册到BeanDefinitionRegistry
        registry.registerBeanDefinition("customBean", builder.getBeanDefinition());
    
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    

2.3 InstantiationAwareBeanPostProcessor

 * InstantiationAwareBeanPostProcessor继承BeanPostProcessor
 */
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor 

    /**
     * Bean实例化前调用的
     * - 如果postProcessBeforeInstantiation方法返回了Object是null;那么就直接返回,调用doCreateBean方法();
     * - 如果postProcessBeforeInstantiation返回不为null;说明修改了bean对象;
     * 然后这个时候就立马执行postProcessAfterInitialization方法
     * (注意这个是初始化之后的方法,也就是通过这个方法实例化了之后,直接执行初始化之后的方法;中间的实例化之后 和 初始化之前都不执行);
     */
    @Nullable
    default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException 
        return null;
    

    /**
     * 实例化Bean之后调用
     * 返回值要注意,因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());
     * 如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true,postProcessPropertyValues就会被执行
     */
    default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException 
        return true;
    

    /**
     * 调用时机为postProcessAfterInstantiation执行之后并返回true,
     * 返回的PropertyValues将作用于给定bean属性赋值.
     * spring 5.1之后出现以替换@Deprecated标注的postProcessPropertyValues
     */
    @Nullable
    default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
            throws BeansException 

        return null;
    

    /**
     * 已经被标注@Deprecated,后续将会被postProcessProperties取代
     */
    @Deprecated
    @Nullable
    default PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException 

        return pvs;
    


       Spring会在Bean实例化前后会调用InstantiationAwareBeanPostProcessor里面相关的方法。下面我们对InstantiationAwareBeanPostProcessor里面的每个方法做一个简单的介绍:

  • InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理自定义逻辑,包括实例化对象的前后过程以及实例的属性设置。
  • postProcessBeforeInstantiation()方法是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如返回一个代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有BeanPostProcessor#postProcessAfterInitialization()方法会调用,其它方法不再调用。
  • postProcessAfterInstantiation()方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues()方法;如果该方法返回false,那么postProcessPropertyValues()就会被忽略不执行;如果返回true,postProcessPropertyValues()就会被执行。
  • postProcessPropertyValues()方法对属性值进行修改(这个时候属性值还未被设置,但是我们可以修改原本该设置进去的属性值)。注意如果postProcessAfterInstantiation()方法返回false,该方法可能不会被调用。
  • 父接口BeanPostProcessor的2个方法postProcessBeforeInitialization()和postProcessAfterInitialization()都是在目标对象被实例化之后,并且属性也被设置之后调用的。

2.4 SmartInstantiationAwareBeanPostProcessor

/**
 * SmartInstantiationAwareBeanPostProcessor继承InstantiationAwareBeanPostProcessor
 */
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor 

    /**
     * 预测Bean的类型,返回第一个预测成功的Class类型,如果不能预测返回null
     */
    @Nullable
    default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException 
        return null;
    

    /**
     * 选择合适的构造器,比如目标对象有多个构造器,在这里可以进行一些定制化,选择合适的构造器
     * beanClass参数表示目标实例的类型,beanName是目标实例在Spring容器中的name
     * 返回值是个构造器数组,如果返回null,会执行下一个PostProcessor的determineCandidateConstructors方法;否则选取该PostProcessor选择的构造器
     */
    @Nullable
    default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
            throws BeansException 

        return null;
    

    /**
     * 获得提前暴露的bean引用。主要用于解决循环引用的问题,只有单例对象才会调用此方法
     */
    default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException 
        return bean;
    


       智能实例化Bean后置处理器(继承InstantiationAwareBeanPostProcessor)。这个相当于InstantiationAwareBeanPostProcessor的扩展版本,增加了一个对Bean类型预测的回调,这个接口主要是Spring框架内部用的,在实际中我们一般还是用InstantiationAwareBeanPostProcessor来实现我们一些自定义逻辑。

2.5 BeanPostProcessor

public interface BeanPostProcessor 

    /**
     * Bean创建(实例化)完成,初始化之前调用
     * 注意需要把Bean返回回去
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 
        return bean;
    

    /**
     * Bean创建(实例化)完成,初始化之后调用
     * 注意需要把Bean返回回去
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
        return bean;
    


       BeanPostProcessor可以让我们在Bean创建完成之后,初始化的前后做一些处理。日经常可以拓展该接口对Bean初始化做定制化处理。

2.6 InitializingBean

public interface InitializingBean 

    /**
     * 所有属性被赋值之后调用.
     * 结合BeanPostProcessor来说就是在postProcessBeforeInitialization()和postProcessAfterInitialization()方法之间执行
     */
    void afterPropertiesSet() throws Exception;


       InitializingBean接口只有一个方法:afterPropertiesSet(),当一个Bean实现InitializingBean接口,afterPropertiesSet()方法在所有的属性都被赋值后调用。属性被赋值是在初始化的时候做的,与BeanPostProcessor结合来看,afterPropertiesSet()方法将在postProcessBeforeInitialization()和postProcessAfterInitialization()之间被调用执行。

2.7 MergedBeanDefinitionPostProcessor

/**
 * MergedBeanDefinitionPostProcessor继承BeanPostProcessor
 */
public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor 

    /**
     * 在bean实例化完毕后调用 可以用来修改merged BeanDefinition的一些properties 或者用来给后续回调中缓存一些meta信息使用
     * 执行Bean定义的合并
     * 且在实例化完Bean之后执行
     */
    void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

    /**
     * 已重置指定名称的bean定义的通知,
     * 并且此后处理器应清除受影响Bean的任何元数据
     */
    default void resetBeanDefinition(String beanName) 
    


       合并Bean定义后置处理器(继承BeanPostProcessor)。MergedBeanDefinitionPostProcessor这个接口,这个接口对@Autowired和@Value的支持起到了至关重要的作用。当某个bean在实例化的时候就会调到所有的实现了MergedBeanDefinitionPostProcessor接口的实例。其中就有一个非常关键的类:AutowiredAnnotationBeanPostProcessor。大家可以去瞧下AutowiredAnnotationBeanPostProcessor类的具体实现逻辑。

2.8 DestructionAwareBeanPostProcessor

public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor 

    /**
     * 实现销毁对象的逻辑
     */
    void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

    /**
     * 判断是否需要处理这个对象的销毁
     */
    default boolean requiresDestruction(Object bean) 
        return true;
    



       处理对象销毁的前置回调。具体实现我们可以参考ApplicationListenerDetector里面实现逻辑。这个类是用来注册ApplicationListener实例的,而如果销毁一个对象,不解除这里的引用会导致无法进行回收,因此在销毁对象时,会判断如果是ApplicationListener要执行从监听器列表中移除掉。


       稍微总结下IOC各个类的调用顺序。

  1. BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()
  2. BeanFactoryPostProcessor.postProcessBeanFactory()
  3. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()该方法在创建对象之前会先掉用,如果有返回实例则直接使用不会去走下面创建对象的逻辑,并在之后执行BeanPostProcessor.postProcessAfterInitialization()。
  4. SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors()如果需要的话,会在实例化对象之前执行。
  5. MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition()在对象实例化完毕 初始化之前执行。
  6. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()在bean创建完毕实例化之前执行。
  7. InstantiationAwareBeanPostProcessor.postProcessPropertyValues()在bean的property属性注入完毕 向bean中设置属性之前执行。
  8. BeanPostProcessor.postProcessBeforeInitialization()在Bean初始化(自定义init或者是实现了InitializingBean.afterPropertiesSet())之前执行。
  9. BeanPostProcessor.postProcessAfterInitialization()在bean初始化(自定义init或者是实现了InitializingBean.afterPropertiesSet())之后执行。
  10. DestructionAwareBeanPostProcessor.postProcessBeforeDestruction()会在销毁对象前执行。

三 需要配合@Import使用的扩展类

3.1 ImportBeanDefinitionRegistrar

/**
 * ImportBeanDefinitionRegistrar,我们一般会实现ImportBeanDefinitionRegistrar类,然后配合一个自定义的注解一起使用。而且在注解类上@Import我们的这个实现类。
 * 通过自定义注解的配置,拿到注解的一些元数据。然后在ImportBeanDefinitionRegistrar的实现类里面做相应的逻辑处理,比如把自定义注解标记的类添加到Spring IOC容器里面去。
 */
public interface ImportBeanDefinitionRegistrar 

    /**
     * 根据注解的给定注释元数据,根据需要注册bean定义
     * @param importingClassMetadata 可以拿到@Import的这个class的Annotation Metadata
     * @param registry BeanDefinitionRegistry 就可以拿到目前所有注册的BeanDefinition,然后可以对这些BeanDefinition进行额外的修改或增强。
     */
    void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);


      ImportBeanDefinitionRegistrar,常规手段是实现ImportBeanDefinitionRegistrar类,然后配合自定义的注解一起使用。而且在注解类上@Import我们的这个实现类。通过自定义注解的配置,拿到注解的一些元数据(这个就相当于是参数了)。然后在ImportBeanDefinitionRegistrar的实现类里面做相应的逻辑处理,比如把自定义注解标记的类添加到Spring IOC容器里面去。

3.1.1 借助ImportBeanDefinitionRegistrar实现Bean的注入

      把指定包下(通过BeanIocScan注解指定)添加了BeanIoc注解的类,添加到IOC容器里面去。

BeanIocScan注解,注意@Import(BeanIocScannerRegister.class)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(BeanIocScannerRegister.class)
public @interface BeanIocScan 
    String[] basePackages() default "";

BeanIoc注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface BeanIoc 


BeanIocScannerRegister实现

public class BeanIocScannerRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware 

    private final static String PACKAGE_NAME_KEY = "basePackages";

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) 
        //1. 从BeanIocScan主机获取到元数据,指定搜索路径
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(BeanIocScan.class.getName()));
        if (annoAttrs == null || annoAttrs.isEmpty()) 
            return;
        
        String[] basePackages = (String[]) annoAttrs.get(PACKAGE_NAME_KEY);
        // 2. 找到指定路径下所有添加了BeanIoc注解的类,添加到IOC容器里面去
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(BeanIoc.class));
        scanner.scan(basePackages);
    

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) 
        this.resourceLoader = resourceLoader;
    

      最后使用的时候,在启动类上添加@BeanIocScan(basePackages = “com.tuacy.study.springboot.hook.importBeanDefinitionRegistrar.beanioc”)就OK。

3.1.2 借助ImportBeanDefinitionRegistrar以及ApplicationRunner实现Spring Boot启动完成之后,启动我们自定义的一些逻辑

      我们实现这样的一个小功能,在Spring Boot启动完成之后.我们需要启动添加了RunStart注解,并且实现了IRunStart接口的类.我们主要分两步来实现:

  • 通过ImportBeanDefinitionRegistrar,找到指定包下面添加了RunStart注解,并且实现了IRunStart接口的类.找到的这些类我们会保存在RunStartManager单例里面.
  • 通过ApplicationRunner,启动找到的这些类.

RunStartScan注解指定搜索路径,注意@Import(RunStartScannerRegister.class)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RunStartScannerRegister.class)
public @interface RunStartScan 
    String[] basePackages() default "";

ImportBeanDefinitionRegistrar的使用,拿到RunStartScan注解拿到指定搜索路径.然后去该路径下找到所有添加了RunStart注解,并且实现了IRunStart接口的类.保存到单例类里面

public class RunStartScannerRegister implements ImportBeanDefinitionRegistrar 

    private final static String PACKAGE_NAME_KEY = "basePackages";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) 
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RunStartScan.class.getName()));
        if (annoAttrs == null || annoAttrs.isEmpty()) 
            return;
        

        String[] basePackages = (String[]) annoAttrs.get(PACKAGE_NAME_KEY);
        RunStartManager.INSTANCE.autoStartScan(basePackages);
    

ApplicationRunner启动我们找到的添加了RunStart注解,并且实现了IRunStart接口的类.

@Component
public class ApplicationRunnerManager implements ApplicationRunner 
    @Override
    public void run(ApplicationArguments args) throws Exception 
        RunStartManager.INSTANCE.autoStartInvoke();
    

3.2 ImportSelector

public interface ImportSelector 

    /**
     * 用于指定需要注册为bean的Class名称
     * 当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。
     *
     * 通过其参数AnnotationMetadata importingClassMetadata可以获取到@Import标注的Class的各种信息,
     * 包括其Class名称,实现的接口名称、父类名称、添加的其它注解等信息,通过这些额外的信息可以辅助我们选择需要定义为Spring bean的Class名称
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);


      ImportSelector使用方法和ImportBeanDefinitionRegistrar类似,也是通过@Import来引入生效的。ImportSelector主要作用是收集需要导入的配置类,如果该接口的实现类同时实现EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports方法之前先调用上述接口中对应的方法,如果需要在所有的@Configuration处理完在导入时可以实现DeferredImportSelector接口。

      一个简单的例子,比如如下类,会把所有实现了HelloService接口的类放到IOC容器里面去。

ImportSelector一定要配合@Import使用

@Configuration
@ComponentScan("com.tuacy.study.springboot.hook.importSelector")
@Import(DynamicSelectImport.class)
public class DynamicSelectConfigure 

具体的实现类,首先拿到@ComponentScan指定的路径,在指定的路径下搜索实现了HelloService接口的类,最终实现了HelloService接口的类会被添加到IOC容器里面去。

public class DynamicSelectImport implements ImportSelector 
    /**
     * DynamicSelectImport需要配合@Import()注解使用
     * <p>
     * 通过其参数AnnotationMetadata importingClassMetadata可以获取到@Import标注的Class的各种信息,
     * 包括其Class名称,实现的接口名称、父类名称、添加的其它注解等信息,通过这些额外的信息可以辅助我们选择需要定义为Spring bean的Class名称
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) 
        String[] basePackages = null;
        // @Import注解对应的类上的ComponentScan注解
        if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) 
            Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
            basePackages = (String[]) annotationAttributes.get("basePackages");
        
        if (basePackages == null || basePackages.length == 0) 
            //ComponentScan的basePackages默认为空数组
            String basePackage = null;
            try 
                // @Import注解对应的类的包名
                basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
             catch (ClassNotFoundException e) 
                e.printStackTrace();
            
            basePackages = new String[] basePackage;
        
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<String> classes = new HashSet<>();
        for (String basePackage : basePackages) 
            scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
        
        return classes.toArray(new String[0]);
    

四 SmartLifecycle

/**
 * 在使用Spring开发时,我们都知道,所有bean都交给Spring容器来统一管理,其中包括没一个bean的加载和初始化。
 * 有时候我们需要在Spring加载和初始化所有bean后,接着执行一些任务或者启动需要的异步服务,这样我们可以使用 SmartLifecycle 来做到
 */
interface SmartLifecycle extends Lifecycle, Phased 


    /**
     * 根据该方法的返回值决定是否执行start方法。<br/>
     * 返回true时start方法会被自动执行,返回false则不会。
     */
    default boolean isAutoStartup() 
        return true;
    

    /**
     * 1. 只有该方法返回false时,start方法才会被执行。<br/>
     * 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
     */
    @Override
    default boolean isRunning() 
        return false;
    

    /**
     * 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
     * 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
     * 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
     */
    @Override
    default void start() 

    

    /**
     * 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。
     * 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。
     * 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
     * 如果我们使用的是SmartLifecycle改方法忽视掉
     */
    @Override
    default void stop() 

    

    /**
     * SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
     */
    default void stop(Runnable callback) 
        stop();
        callback.run();
    

    /**
     * 如果工程中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。<br/>
     * 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
     */
    @Override
    default int getPhase() 
        return DEFAULT_PHASE;
    


      在使用Spring开发时,我们都知道,所有Bean都交给Spring容器来统一管理。有时候我们需要在Spring加载和初始化所有Bean后,接着执行一些任务或者启动需要的异步服务,这种情况下我们可以使用SmartLifecycle来实现。

@Component
public class CustomizeLifeCycle implements SmartLifecycle 

    private boolean isRunning = false;

    /**
     * 根据该方法的返回值决定是否执行start方法。
     * 返回true时start方法会被自动执行,返回false则不会。
     */
    @Override
    public boolean isAutoStartup() 
        System.out.println("start");
        // 默认为false
        return true;
    

    /**
     * 1. 只有该方法返回false时,start方法才会被执行。<br/>
     * 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
     */
    @Override
    public boolean isRunning() 
        System.out.println("isRunning");
        // 默认返回false
        return isRunning;
    

    /**
     * 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
     * 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
     * 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
     */
    @Override
    public void start() 
        System.out.println("start");
        // 执行完其他业务后,可以修改 isRunning = true
        isRunning = true;
    

    /**
     * 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
     * 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
     * 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
     */
    @Override
    public void stop() 
        System.out.println("stop");
        isRunning = false;
    

    /**
     * SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
     */
    @Override
    public void stop(Runnable callback) 
        System.out.println("stop(Runnable)");

        // 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
        // 否则在你程序退出时,Spring的DefaultLifecycleProcessor会认为你这个TestSmartLifecycle没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
        callback.run();

        isRunning = false;
    
    

    /**
     * 如果工程中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。<br/>
     * 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
     */
    @Override
    public int getPhase() 
        return 0;
    

五 ApplicationListener

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener 

    /**
     * 处理应用程序事件。
     */
    void onApplicationEvent(E event);


      ApplicationListener是SpringBoot的监听器。为Bean和Bean之间的消息通信提供了支持。当Bean处理完一个事件之后,希望另一个Bean能够知道并做相应的处理。这时另一个Bean监听当前Bean所发送的事件,就需要用到ApplicationListener。

      Spring事件分为事件发布者(EventPublisher)、事件监听者(EventListener),还包括一个事件广播者(这个是Spring实现相关)。

      Spring事件分为两类:一个是Spring自带的一些事件,另一个是我们自定义的事件.

5.1 Spring自带的事件

Spring一些常用事件解释
ApplicationStartedEventspring boot启动开始时执行的事件
ApplicationEnvironmentPreparedEventspring boot 对应Enviroment已经准备完毕,但此时上下文context还没有创建
ApplicationPreparedEventspring boot上下文context创建完成,但此时spring中的bean是没有完全加载完成的
ApplicationReadyEvent上下文已经准备完毕的时候触发
ApplicationFailedEventspring boot启动异常时执行事件
ContextRefreshedEvent当ApplicationContext被初始化或刷新的时候会触发

5.2 自定义事件

      自定义Spring事件需要继承ApplicationEvent。

我们自定义一个事件CustomerEvent,继承ApplicationEvent

public class CustomerEvent extends ApplicationEvent 

    /**
     * 事件内容
     */
    private String content;

    public CustomerEvent(Object source, String content) 
        super(source);
        this.content = content;
    

    public String getContent() 
        return content;
    

在Bean里面发送事件,先拿到ApplicationContext对象,在把消息发送出去.

        applicationContext.publishEvent(new CustomerEvent(this, "CommandLineRunner"));

接受事件,两种方式:一种实现ApplicationListener接口,另一种是使用@EventListener注解监听事件.

Bean实现ApplicationListener接口,接听到我们发送过来的CustomerEvent事件

@Component
public class CustomerApplicationListener implements ApplicationListener<CustomerEvent> 

    @Override
    public void onApplicationEvent(CustomerEvent event) 
        System.out.println(event.getSource());
    

Bean里面通过@EventListener注解监听到我们发送过来的CustomerEvent事件

@Component
public class AnnotationListener 

    /**
     * 通过@EventListener组件监听到我们发送的CustomerEvent事件
     */
    @EventListener
    public void customerEvent(CustomerEvent event) 
        System.out.println("收到是消息:" + event.getSource());
    


六 CommandLineRunner、ApplicationRunner

       在使用SpringBoot构建项目时,我们通常有一些预先数据的加载(比如:读取配置文件信息,数据库连接,删除临时文件,清除缓存信息)。Spring Boot中我们可以通过CommandLineRunner、ApplicationRunner两个接口实现这一要求,我们需要时,只需实现该接口就行。如果存在多个加载的数据,我们也可以使用@Order注解或者在实现类上实现Ordered来标识来排序(数字越小优先级越高,越先执行)。它们的执行时机是容器启动完成的时候。

       他两唯一的不同点在于ApllicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口的run方法的参数为String数组。其实对我们使用来说这两个接口间没有很大的区别,咱们随便用一个就行,如果想要更详细地获取命令行参数,那就使用ApplicationRunner接口。

ApplicationRunner优先于CommandLineRunner执行。

七 ClassPathBeanDefinitionScanner

      ClassPathBeanDefinitionScanner(类扫描器分析)作用就是将指定包下的类通过一定规则过滤后将Class信息包装成BeanDefinition的形式注册到IOC容器中。

      举一个例子,比如如下的代码配合ImportBeanDefinitionRegistrar的使用,我们会去找添加了BeanIoc注解的类,并且他找到的类添加到IOC容器里面去。

public class BeanIocScannerRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware 

    private final static String PACKAGE_NAME_KEY = "basePackages";

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) 
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(BeanIocScan.class.getName()));
        if (annoAttrs == null || annoAttrs.isEmpty()) 
            return;
        
        // 搜索路径
        String[] basePackages = (String[]) annoAttrs.get(PACKAGE_NAME_KEY);
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(BeanIoc.class));
        scanner.scan(basePackages);
    

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) 
        this.resourceLoader = resourceLoader;
    

更加详细的使用可以看看上文关于ImportBeanDefinitionRegistrar的使用

八 ClassPathScanningCandidateComponentProvider

      ClassPathScanningCandidateComponentProvider是ClassPathBeanDefinitionScanner的基类,ClassPathScanningCandidateComponentProvider本身主要作用是包扫描,就是根据指定的规则扫描到指定的类.

ClassPathScanningCandidateComponentProvider和ClassPathBeanDefinitionScanner的区别在于ClassPathBeanDefinitionScanner会在根据指定的规则扫描到类之后,把扫描到的类添加到IOC容器里面去.

      一个简单的例子,比如搜索指定包(com.tuacy.study.springboot.hook.importSelector)下,实现了HelloService接口的类。

    /**
     * 搜索指定包(com.tuacy.study.springboot.hook.importSelector)下,实现了HelloService接口的类
     */
    @Test
    public void test() 
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        TypeFilter helloServiceFilter = new AssignableTypeFilter(HelloService.class);
        scanner.addIncludeFilter(helloServiceFilter);
        Set<BeanDefinition> classes = scanner.findCandidateComponents("com.tuacy.study.springboot.hook.importSelector");
        if (!classes.isEmpty()) 
            classes.forEach(beanDefinition -> System.out.println(beanDefinition.getBeanClassName()));
        

    

      关于Spring Boot基础用法相关的一些类,我们就介绍到这里.在实际开发中,这些类也特别有用.文章中涉及到所有实例可以在 https://github.com/tuacy/java-study springboot文件夹下面找到.

以上是关于Spring Boot基础用法的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 中,如何干掉 if else!

修改Spring Boot默认的上下文

spring boot上下文路径写在哪

Spring Boot 中修改端口和上下文路径

以编程方式重新启动 Spring Boot 应用程序/刷新 Spring 上下文

spring boot 的 netty 上下文路径