Spring Class Global Method

来源:互联网 发布:数据精灵稳定版源 编辑:程序博客网 时间:2024/06/05 05:30

今天在遇到一个有意思的问题,那就是我们可以在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)@Documentedpublic @interface GlobalInitMethod {    String value() default "";}

2、全局销毁方法注解

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

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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当中。

@Componentpublic 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启动服务

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

6、测试类

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

@Componentpublic class GlobalMethodBean {    public void initMethod(){        System.out.println("init");    }}

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

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