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
- 配置各种资源文件,包括xml, 注解(@Component及其继承注解@Service,@Controller等),Class(使用@Configuration和@Bean),Properties/Yml文件(Spring boot应用很多),Spring把它抽象为Resource接口。
- Spring把这些资源文件解析成BeanDefinition,也就是Bean的定义。里面包括在资源配置文件里面配置的bean的各种定义。里面最重要的两个方法是:
getConstructorArgumentValues()
通过构造器依赖注入,getPropertyValues()
通过setter方法注入 - 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方法命名最好特别一点。
推荐阅读
-
Spring Class Global Method
-
基于Spring,MyBatis的Interface及Class混用 博客分类: Coding springmybatis
-
基于Spring,MyBatis的Interface及Class混用 博客分类: Coding springmybatis
-
spring boot单元测试类的使用.Loading class `com.mysql.jdbc.Driver'. This is deprecated.
-
Absent Code attribute in method that is not native or abstract in class file jav
-
Meta-Programming in Ruby: 动态生成class,并添加attribute和method。 博客分类: Ruby RoR Ruby
-
JSON: property "xxx" has no getter method in class "..." 博客分类: j2se jsonBeanJava.netthread
-
JSON: property "xxx" has no getter method in class "..." 博客分类: j2se jsonBeanJava.netthread
-
spring boot 异常: Cannot load driver class: com.mysql.jdbc.Driver
-
Error creating bean with name 'sqlSessionFactory' defined in class path resource [config/spring/applicationContext.xml]: Invocation of init me