zoukankan      html  css  js  c++  java
  • 如何写好一个 Spring 组件

    背景

    Spring 框架提供了许多接口,可以使用这些接口来定制化 bean ,而非简单的 getter/setter 或者构造器注入。细翻 Spring Cloud Netflix、Spring Cloud Alibaba 等这些构建在 Spring Framework 的成熟框架源码,你会发现大量的扩展 bean 例如

    • Eureka 健康检查
    package org.springframework.cloud.netflix.eureka;
    
    public class EurekaHealthCheckHandler implements InitializingBean {}
    
    • Seata Feign 配置
    package com.alibaba.cloud.seata.feign;
    
    public class SeataContextBeanPostProcessor implements BeanPostProcessor {}
    

    代码示例

    • DemoBean
    @Slf4j
    public class DemoBean implements InitializingBean {
    
    	public DemoBean() {
    		log.info("--> instantiate ");
    	}
    
    	@PostConstruct
    	public void postConstruct() {
    		log.info("--> @PostConstruct ");
    	}
    
    	@Override
    	public void afterPropertiesSet() throws Exception {
    		log.info("--> InitializingBean.afterPropertiesSet ");
    	}
    
    	public void initMethod() {
    		log.info("--> custom initMehotd");
    	}
    }
    
    • DemoBeanPostProcessor
    @Configuration
    public class DemoBeanPostProcessor implements BeanPostProcessor {
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		if ("demoBean".equals(beanName)){
    			log.info("--> BeanPostProcessor.postProcessBeforeInitialization ");
    		}
    		return bean;
    	}
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		if ("demoBean".equals(beanName)){
    			log.info("--> BeanPostProcessor.postProcessAfterInitialization ");
    		}
    		return bean;
    	}
    }
    
    • DemoConfig
    @Configuration
    public class DemoConfig {
    
    	@Bean(initMethod = "initMethod")
    	public DemoBean demoBean() {
    		return new DemoBean();
    	}
    }
    

    运行输出日志

    • 整个 bean 的创建过程日志输出如下和文首图片横线以上 bean 创建周期一致
    DemoBean             : --> instantiate
    DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization
    DemoBean             : --> @PostConstruct
    DemoBean             : --> InitializingBean.afterPropertiesSet
    DemoBean             : --> custom initMehotd
    DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization
    

    执行过程核心源码

    • AbstractAutowireCapableBeanFactory.initializeBean
    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    
      // 执行BeanPostProcessor.postProcessBeforeInitialization
      Object wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
          ...
          // 执行用户自定义初始化and JSR 250 定义的方法
      invokeInitMethods(beanName, wrappedBean, mbd);
          ...
      // 执行执行BeanPostProcessor.postProcessAfterInitialization
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    
      return wrappedBean;
    }
    
    • 下文就详细说明一下每个 bean 的扩展周期的最佳使用场景

    BeanPostProcessor

    BeanPostProcessor 是一个可以自定义实现回调方法接口,来实现自己的实例化逻辑、依赖解决逻辑等,如果想要在 Spring 完成对象实例化、配置、初始化之后实现自己的业务逻辑,可以通过扩展实现一个或多个 BeanPostProcessor 处理。

    • 多用于适配器模式,可以在实现同一接口 bean 创建前后进行包装转换
    // seata 上下文转换,将其他类型 wrap 成 SeataFeignContext
    public class SeataContextBeanPostProcessor implements BeanPostProcessor {
    
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName){
    		if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
    			return new SeataFeignContext(getSeataFeignObjectWrapper(),
    					(FeignContext) bean);
    		}
    		return bean;
    	}
    }
    
    • 自定义 注解查找扩展
    net.dreamlu.mica.redisson.stream.RStreamListenerDetector 查找自定义 @RStreamListener 实现 基于 Redisson 的 pub/sub
    
    public class RStreamListenerDetector implements BeanPostProcessor {
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		Class<?> userClass = ClassUtils.getUserClass(bean);
    		ReflectionUtils.doWithMethods(userClass, method -> {
    			RStreamListener listener = AnnotationUtils.findAnnotation(method, RStreamListener.class);
    			.... do something
    		}, ReflectionUtils.USER_DECLARED_METHODS);
    		return bean;
    	}
    }
    
    

    PostConstruct

    JavaEE5 引入了@PostConstruct 作用于 Servlet 生命周期的注解,实现 Bean 初始化之前的自定义操作。

    • 只能有一个非静态方法使用此注解
    • 被注解的方法不能有返回值和方法参数
    • 被注解的方法不得抛出异常

    这里需要注意的 这个注解不是 Spring 定义的,而是属于 JavaEE JSR-250 规范定义的注解,当你在使用 Java11 的时候要手动引入相关 jar(因为 Java11 移除了)

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
    </dependency>
    

    使用场景: 在之前的版本,我们可以在启动后通过 @PostConstruct 注解的方法执行初始化数据。但由于 Java 高版本已经移除相关 API ,我们不推荐使用此 注解,可以通过 Spring 相关 Event 回调事件处理

    @PostConstruct 注解的方法在项目启动的时候执行这个方法,也可以理解为在 spring 容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。

    InitializingBean

    InitializingBean 接口方法会在 容器初始化(getter/setter/构造器)完成 bean 的属性注入后执行。

    应用场景: 动态修改容器注入的 Bean 参数

    • 正常用户配置参数注入到 bean
    security:
      oauth2:
          ignore-urls:
            - '/ws/**'
    
    @ConfigurationProperties(prefix = "security.oauth2")
    public class PermitAllUrlProperties {
    	@Getter
    	@Setter
    	private List<String> ignoreUrls = new ArrayList<>();
    }
    
    • 我们发现此时用户配置并不完整,还有一些通用不需要用户维护,可通过实现 InitializingBean 接口回调扩展
    @ConfigurationProperties(prefix = "security.oauth2.ignore")
    public class PermitAllUrlProperties implements InitializingBean {
    
    	@Getter
    	@Setter
    	private List<String> urls = new ArrayList<>();
    
    	@Override
    	public void afterPropertiesSet() {
    		urls.add("/common/*");
    	}
    }
    

    initMethod

    上文 @PostConstruct 已经不推荐大家使用,可以使用 Bean(initMethod = 'initMehotd') 替代,相关的限制如上。

    @Bean(initMethod = "initMethod")
    public DemoBean demoBean() {
      return new DemoBean();
    }
    
    public void initMethod() {
      log.info("--> custom initMehotd");
    }
    

    总结

    项目推荐: Spring Cloud 、Spring Security OAuth2的RBAC权限管理系统 欢迎关注

  • 相关阅读:
    UINavigationController的简单使用
    UIApplication
    NSOperation开启线程情况分析
    NSOperation & NSOperationQueue
    GCD 多线程 Ios 补充
    GCD中不同队列 配合 不同函数的 情况分析
    GCD详解
    is,as,sizeof,typeof,GetType
    人人必知的10个jQuery小技巧
    移动设备wap手机网页html5通过特殊链接:打电话,发短信,发邮件详细教程
  • 原文地址:https://www.cnblogs.com/leng-leng/p/13064031.html
Copyright © 2011-2022 走看看