欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Class Global Method

程序员文章站 2024-03-23 21:32:34
...

今天在遇到一个有意思的问题,那就是我们可以在Spring的xml配置文件里面可以定义bean的全局的init和destroy方法。如下图所示:

Spring Class Global Method

我们可以看到在beans这个标签里面可以定义一些bean的全局属性,其中包括default-init-method与default-destroy-method方法:

  • init-method:Spring容器初始化bean的时候会调用的方法
  • destroy-method : Spring容器销毁的时候,bean会调用的方法

有一个小伙伴就问Spring可不可以使用在类里面来实现这一功能呢?如果大家用过Spring 3.0里面的新特性,就是使用Class来定义Bean,就是使用@Configuration和@Bean注解。具体可以参看之前的blog – Spring Bean Type。它的实现是基于Spring容器的扩展BeanFactoryPostProcessor, 它是可以修改Spring BeanDefinition配置的元数据.关于Spring IOC的扩展可以参看 – Spring Container Extension。关于Spring IOC的过程我总结如下:

Resource –> BeanDefinition –> BeanWrapper –> Object

  1. 配置各种资源文件,包括xml, 注解(@Component及其继承注解@Service,@Controller等),Class(使用@Configuration和@Bean),Properties/Yml文件(Spring boot应用很多),Spring把它抽象为Resource接口。
  2. Spring把这些资源文件解析成BeanDefinition,也就是Bean的定义。里面包括在资源配置文件里面配置的bean的各种定义。里面最重要的两个方法是:getConstructorArgumentValues()通过构造器依赖注入,getPropertyValues()通过setter方法注入
  3. Spring 把BeanDefinition通过构造器初始化bean,并通过new BeanWrapperImpl(beanInstance)把这个对象包装成BeanWrapper。然后通过BeanWrapper进行依赖注入。关于BeanWrapper可以参看 – Spring IOC BeanWrapper.然后通过BeanWrapper的getWrappedInstance()获取到需要的对象,完成整个IOC的过程。

我可以看到Spring暴露给使用者的核心概念是Bean,而Spring IOC过程当中,框架里面的核心概念是BeanDefinition。那么我们需要全局的给Bean添加init-method和destroy-method。我们只需要修改BeanDefinition就好了。如果大家看过上面的 Spring Container Extension就知道,我们可以使用BeanFactoryPostProcessor这个接口。下面就是我基于注解实现全局初始化与全局销毁方法:以Spring boot为测试类。

总体思路:定义全局初始化与全局销毁的方法注解,这个注解需要与@Component注解配合使用。

1、全局初始化方法注解

定义全局初始化方法注解 – GlobalInitMethod,value是你定义的全局初始化方法名称。

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

    String value() default "";

}

2、全局销毁方法注解

定义全局销毁方法注解 – GlobalDestroyMethod ,value是你定义的全局销毁方法名称。

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

    String value() default "";

}

3、全局初始化方法与销毁方法配置

通过@Configration把@GlobalInitMethod@GlobalDestroyMethod加载到Spring IOC容器当中,当然你也可以使用@Component注解。但是使用@Configration更符合场景,因为这个是Spring的xml配置class化。虽然Spring支持@Component注解。

@Configuration
@GlobalInitMethod(value = "initMethod")
@GlobalDestroyMethod(value = "destroyMethod")
public class GlobalInitDestroyMethodConfig {

}

4、全局初始化方法注解解析类

把之前的Class配置解析,找到默认配置的全局初始化与销毁方法,并设置到Spring IOC容器的每一个BeanDefinition当中。

@Component
public class GlobalInitDestroyMethodPostProcessor implements BeanDefinitionRegistryPostProcessor {

    private static final String GLOBAL_INIT_METHOD = GlobalInitMethod.class.getName();

    private static final String GLOBAL_DESTROY_METHOD = GlobalDestroyMethod.class.getName();

    private static final String GLOBAL_METHOD_NAME = "value";

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String[] beanDefinitionNames = registry.getBeanDefinitionNames();
        if(beanDefinitionNames == null || beanDefinitionNames.length == 0) {
            return;
        }

        GlobalMethodDefinition globalMethodDefinition = new GlobalMethodDefinition();
        // 查找 全局初始化方法与全局销毁方法配置
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            if(beanDefinition instanceof ScannedGenericBeanDefinition) {
                ScannedGenericBeanDefinition scannedBeanDefinition = ScannedGenericBeanDefinition.class.cast(beanDefinition);
                String globalInitMethodName = extractGlobalMethod(scannedBeanDefinition, GLOBAL_INIT_METHOD);
                if(!StringUtils.isEmpty(globalInitMethodName)) {
                    globalMethodDefinition.setInitMethodName(globalInitMethodName);
                }
                String globalDestroyMethodName = extractGlobalMethod(scannedBeanDefinition, GLOBAL_DESTROY_METHOD);
                if(!StringUtils.isEmpty(globalDestroyMethodName)) {
                    globalMethodDefinition.setDestroyMethodName(globalDestroyMethodName);
                }
            }
            if(globalMethodDefinition.isHasGlobalInitMethod() && globalMethodDefinition.isHasGlobalDestroyMethod()) {
                break;
            }
        }

        // 为BeanDefinition配置全局初始化和销毁方法
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            if(beanDefinition instanceof AbstractBeanDefinition) {
                AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) beanDefinition;
                if(globalMethodDefinition.isHasGlobalInitMethod()) {
                    if(StringUtils.isEmpty(abstractBeanDefinition.getInitMethodName())){
                        abstractBeanDefinition.setInitMethodName(globalMethodDefinition.getInitMethodName());
                        abstractBeanDefinition.setEnforceInitMethod(false);
                    }
                }
                if(globalMethodDefinition.isHasGlobalDestroyMethod()) {
                    if(StringUtils.isEmpty(abstractBeanDefinition.getDestroyMethodName())) {
                        abstractBeanDefinition.setDestroyMethodName(globalMethodDefinition.getDestroyMethodName());
                        abstractBeanDefinition.setEnforceDestroyMethod(false);
                    }
                }
            }
        }

    }

    private String extractGlobalMethod(ScannedGenericBeanDefinition scannedBeanDefinition, String annotationName) {
        AnnotationMetadata metadata = scannedBeanDefinition.getMetadata();
        MultiValueMap<String, Object> allInitAnnotationAttributes = metadata.getAllAnnotationAttributes(annotationName);
        if(allInitAnnotationAttributes != null && allInitAnnotationAttributes.containsKey(GLOBAL_METHOD_NAME)) {
            List<Object> methodValue = allInitAnnotationAttributes.get(GLOBAL_METHOD_NAME);
            String value = (String) methodValue.get(0);
            if(StringUtils.hasText(value)) {
                return value;
            }
        }
        return null;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // do nothing
    }

    private class GlobalMethodDefinition {

        private String initMethodName;
        private String destroyMethodName;
        private boolean hasGlobalInitMethod;
        private boolean hasGlobalDestroyMethod;

        public String getInitMethodName() {
            return initMethodName;
        }

        public void setInitMethodName(String initMethodName) {
            this.initMethodName = initMethodName;
            this.hasGlobalInitMethod = true;
        }

        public String getDestroyMethodName() {
            return destroyMethodName;
        }

        public void setDestroyMethodName(String destroyMethodName) {
            this.destroyMethodName = destroyMethodName;
            this.hasGlobalDestroyMethod = true;
        }

        public boolean isHasGlobalInitMethod() {
            return hasGlobalInitMethod;
        }

        public boolean isHasGlobalDestroyMethod() {
            return hasGlobalDestroyMethod;
        }
    }

}

5、Spring boot启动类

通过Spring boot启动服务

@SpringBootApplication
public class Bootstrap {

    public static void main(String[] args) {
        SpringApplication.run(Bootstrap.class, args);
    }

}

6、测试类

通过@Component定义一个bean,并写一个Class配置的全局初始化方法并为测试。

@Component
public class GlobalMethodBean {

    public void initMethod(){
        System.out.println("init");
    }

}

可以看到在Spring容器启动的时候会调用到GlobalMethodBean#initMethod.

有一个小小的不足之处,初始化方法命名最好不要使用init(),我在测试的时候DispatcherServlet调用Servlet的init方法会报错,因为它还依赖于其它类。所以init方法命名最好特别一点。