Spring Class Global Method

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

这里写图片描述

我们可以看到在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方法命名最好特别一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值