zoukankan      html  css  js  c++  java
  • 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自定义类扫描器

  • 相关阅读:
    【模板】Sparse-Table
    UVa 11235 Frequent values
    【模板】树状数组
    UVa 1428 Ping pong
    数学技巧
    UVa 11300 Spreading the Wealth
    UVa 11729 Commando War
    UVa 11292 Dragon of Loowater
    POJ 3627 Bookshelf
    POJ 1056 IMMEDIATE DECODABILITY
  • 原文地址:https://www.cnblogs.com/aspirant/p/10649202.html
Copyright © 2011-2022 走看看