• Spring自定义类扫描器 ClassPathScanningCandidateComponentProvider Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method


    Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

    Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

    (1) 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
    (2) 通过 <bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
    (3) 在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。 

    具体参考:Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

    项目中有个需求 读取xml文件,然后 对xml文件进行解析,比如如果是 Gender=0/1的话,分别代表男女。

    所以需要在构造函数之后,初始化bean之前进行过滤解析

    xml文件:

     <interface name="网约车乘客基本信息(CKJB)" message="baseInfoPassenger">
            <field name="interfaceType" valExpr="BASIC" desc="接口类型标识" serialize="false"/>
            <field name="command" valExpr="CKJB" serialize="false" desc="接口操作命令"/>
            <field name="symbol" valExpr="PassengerPhone"
                   desc="唯一标识 公司标识(见2.6)加平台自编号(将根据唯一标识执行操作标识对应操作)"/>
            <field name="companyId" valExpr="MEITUANDACHE" desc="公司标识,与交通部一致。 见2.6"/>
            <field name="registerDate" valExpr="${RegisterDate}" must="false" dataType="long"
                   desc="注册时间 乘客在平台的注册日期YYYYMMDD"/>
            <field name="passengerPhone" valExpr="${PassengerPhone}" desc="乘客电话 "/>
            <field name="passengerSex" valExpr="M_genderConvertToNumber(PassengerGender)" dataType="int"
                   desc="乘客性别 见JT/T 697.7-2014中,与平台发送交通部一致。"/>
            <field name="state" valExpr="${State}" dataType="int" desc="状态 0:有效 1:失效"/>
            <field name="flag" valExpr="${Flag}" dataType="int" desc="操作标识 1:新增 2:更新 3:删除"/>
            <field name="updateTime" valExpr="${UpdateTime}" dataType="long"
                   desc="更新时间 网约车平台完成数据更新的时间,格式YYYYMMDDHHMMSS"/>
        </interface>

    注意里面的方法:valExpr="M_genderConvertToNumber(PassengerGender)"

    可以继承 InitializingBean 这个接口,然后重写方法:

    
    
    @Component
    public class CityRepositoryImpl implements CityRepository, InitializingBean {

    /**
    * 模板方法的扫描路径
    */
    private static final String TEMPLATE_METHOD_SCAN_LOCATION = "com.sankuai";

    /**
    * 模板方法
    */
    private static final Map<String, TemplateMethod> TEMPLATE_METHOD_MAP = new HashMap<>();

    @Override
    public void afterPropertiesSet() { synchronized (CityRepositoryImpl.class) { if (TEMPLATE_METHOD_MAP.size() == 0) { loadTemplateMethod(); } } }
    }

    其实方法:afterPropertiesSet 就是设置属性,initializingBean 具体参考:Spring生命周期 Constructor > @PostConstruct > InitializingBean > init-method

    然后在这方法里面实现了一个自己定义的类扫描类,只要是继承TempleMethod.class 就 扫描出来

      /**
         * 加载模板方法(基于spring的扫描器)
         */
        private void loadTemplateMethod() {
            ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
            scanner.addIncludeFilter(new AssignableTypeFilter(TemplateMethod.class));
            for (BeanDefinition beanDefinition : scanner.findCandidateComponents(TEMPLATE_METHOD_SCAN_LOCATION)) {
                try {
                    TemplateMethod templateMethod = (TemplateMethod) Class.forName(beanDefinition.getBeanClassName()).newInstance();
    
                    String name = templateMethod.getMethodName();
                    if (name == null || name.length() == 0) {
                        throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(String.format("模板方法:%s 名称不能为空!", beanDefinition.getBeanClassName()));
                    }
    
                    /* 模板方法追加前缀标识 */
                    String realName = "M_" + name;
                    if (TEMPLATE_METHOD_MAP.containsKey(realName)) {
                        throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(String.format("模板方法:%s重复定义", name));
                    }
                    TEMPLATE_METHOD_MAP.put(realName, templateMethod);
                } catch (QcsFactException e) {
                    throw e;
                } catch (Exception e) {
                    throw QcsFactErrorEnum.TEMPLATE_METHOD_ERROR.formException(e, String.format("模板方法:%s加载失败", beanDefinition.getBeanClassName()));
                }
            }
    
        }

    然后方法:

    public class GenderTempleMethod {
        /**
         * 字符型转数字
         * 例如
         * 男 convert to 1
         * 女 convert to 2
         */
        public static final class GenderConvertNumber implements TemplateMethod {
            @Override
            public String getMethodName() {
                return "genderConvertToNumber";
            }
    
            @Override
            public Object exec(List list) {
                String val = list.get(0).toString();
                Integer result = Gender.UNKNOWN.getValue();
    
                for (Gender gender : Gender.values()) {
                    if (gender.getName().equals(val)) {
                        result = gender.getValue();
                        break;
                    }
                }
                return result;
            }
        }

    Gender类:

    public enum Gender {
        /**
         * 男性
         */
        MALE("", 1),
        /**
         * 女性
         */
        FEMALE("", 2),
        /**
         * 未说明
         */
        UNKNOWN("未知", 0),
        /**
         * 未解释
         */
        OTHER("其他", 9);
    
        private String name;
        private Integer value;
    
        Gender(String name, Integer value) {
            this.name = name;
            this.value = value;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getValue() {
            return value;
        }
    
        public void setValue(Integer value) {
            this.value = value;
        }
    }

    具体的spring 自定义扫描器的实现参考:

    在我们刚开始接触Spring的时候,要定义bean的话需要在xml中编写,比如:

    <bean id="myBean" class="your.pkg.YourClass"/>


    后来发现如果bean比较多,会需要写很多的bean标签,太麻烦了。于是出现了一个component-scan注解。这个注解直接指定包名就可以,它会去扫描这个包下所有的class,然后判断是否解析:

    <context:component-scan base-package="your.pkg"/>


    再后来,由于注解Annotation的流行,出现了@ComponentScan注解,作用跟component-scan标签一样,跟@Configuration注解配合使用:

    @ComponentScan(basePackages = {"your.pkg", "other.pkg"})
    public class Application { ... }


    不论是component-scan标签,还是@ComponentScan注解。它们扫描或解析的bean只能是Spring内部所定义的,比如@Component、@Service、@Controller或@Repository。如果有一些自定义的注解,比如@Consumer、这个注解修饰的类是不会被扫描到的。这个时候我们就得自定义扫描器完成这个操作。
     
    Spring内置的扫描器
     
    component-scan标签底层使用ClassPathBeanDefinitionScanner这个类完成扫描工作的。@ComponentScan注解配合@Configuration注解使用,底层使用ComponentScanAnnotationParser解析器完成解析工作。

    ComponentScanAnnotationParser解析器内部使用了ClassPathBeanDefinitionScanner扫描器,ClassPathBeanDefinitionScanner扫描器内部的处理过程整理如下:

    1. 遍历basePackages,根据每个basePackage找出这个包下的所有的class。比如basePackage为your/pkg,会找出your.pkg包下所有的class。找出之后封装成Resource接口集合,这个Resource接口是Spring对资源的封装,有FileSystemResource、ClassPathResource、UrlResource实现等
    2. 遍历找到的Resource集合,通过includeFilters和excludeFilters判断是否解析。这里的includeFilters和excludeFilters是TypeFilter接口类型的集合,是ClassPathBeanDefinitionScanner内部的属性。TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤。includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤
    3. 如果没有被过滤。把Resource封装成ScannedGenericBeanDefinition添加到BeanDefinition结果集中
    4. 返回最后的BeanDefinition结果集
     
    TypeFilter接口的定义:
     

    public interface TypeFilter {
    boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
    throws IOException;
    }


    TypeFilter接口目前有AnnotationTypeFilter实现类(类是否有注解修饰)、RegexPatternTypeFilter(类名是否满足正则表达式)等。

    ClassPathBeanDefinitionScanner继承ClassPathScanningCandidateComponentProvider类。

    ClassPathScanningCandidateComponentProvider内部的构造函数提供了一个useDefaultFilters参数:
     

    public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
    this(useDefaultFilters, new StandardEnvironment());
    }


    useDefaultFilters这个参数表示是否使用默认的TypeFilter,如果设置为true,会添加默认的TypeFilter:

    protected void registerDefaultFilters() {
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
    this.includeFilters.add(new AnnotationTypeFilter(
    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
    logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }
    try {
    this.includeFilters.add(new AnnotationTypeFilter(
    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
    logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    // JSR-330 API not available - simply skip.
    }
    }


    我们看到这里includeFilters加上了AnnotationTypeFilter,并且对应的注解是@Component。@Service、@Controller或@Repository注解它们内部都是被@Component注解所修饰的,所以它们也会被识别。
     
    自定义扫描功能
     
    一般情况下,我们要自定义扫描功能的话,可以直接使用ClassPathScanningCandidateComponentProvider完成,加上一些自定义的TypeFilter即可。或者写个自定义扫描器继承ClassPathScanningCandidateComponentProvider,并在内部添加自定义的TypeFilter。后者相当于对前者的封装。

    我们就以一个简单的例子说明一下自定义扫描的实现,直接使用ClassPathScanningCandidateComponentProvider。

    项目结构如下:
    ./
    └── spring
    └── study
    └── componentprovider
    ├── annotation
    │   └── Consumer.java
    ├── bean
    │   ├── ConsumerWithComponentAnnotation.java
    │   ├── ConsumerWithConsumerAnnotation.java
    │   ├── ConsumerWithInterface.java
    │   ├── ConsumerWithNothing.java
    │   └── ProducerWithInterface.java
    └── interfaze
       ├── IConsumer.java
       └── IProducer.java
    我们直接使用ClassPathScanningCandidateComponentProvider扫描spring.study.componentprovider.bean包下的class:

    ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默认的TypeFilter
    provider.addIncludeFilter(new AnnotationTypeFilter(Consumer.class));
    provider.addIncludeFilter(new AssignableTypeFilter(IConsumer.class));
    Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("spring.study.componentprovider.bean");


    这里扫描出来的类只有2个,分别是ConsumerWithConsumerAnnotation(被@Consumer注解修饰)和ConsumerWithInterface(实现了IConsumer接口)。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing没实现任何借口,没使用任何注解,ProducerWithInterface实现了IProducer接口;所以这3个类不会被识别。

    如果我们要自定义ComponentProvider,继承ClassPathScanningCandidateComponentProvider类即可。

    RepositoryComponentProvider这个类是SpringData模块提供的,继承自ClassPathScanningCandidateComponentProvider,主要是为了识别SpringData相关的类。

    它内部定义了一些自定义TypeFilter,比如InterfaceTypeFilter(识别接口的TypeFilter,目标比较是个接口,而不是实现类)、AllTypeFilter(保存存储TypeList集合,这个集合内部所有的TypeFilter必须全部满足条件才能被识别)等。

    参考:Spring自定义类扫描器

  • 相关阅读:
    mysql 安装命令
    MySQL——修改root密码的4种方法(以windows为例)
    正则表达式(一):php常用的正则匹配
    nginx+php在调试过程中临时关闭缓存
    (总结)Nginx配置文件nginx.conf中文详解
    理解Linux系统/etc/init.d目录和/etc/rc.local脚本
    关于mongodb ,redis,memcache之间见不乱理还乱的关系和作用
    angularjs factory,service,provider 自定义服务的不同
    使用loopback创建nodejs框架
    采用express创建nodejs服务器
  • 原文地址:https://www.cnblogs.com/aspirant/p/10649202.html
Copyright © 2020-2023  润新知