1. 程式人生 > 實用技巧 >java 設計模式責任鏈模式應用(二)

java 設計模式責任鏈模式應用(二)

責任鏈模式

概念就不必細說,該模式主要特點就是像連結式流一樣,一步一步執行。

可以應用的例子,比如用來驗證使用者資訊,使用者登入後,判斷是否使用者等級,使用者禁用,使用者資訊等內容。

不止可以用來驗證使用者資訊,還有很多驗證,打算做個框架jar包,來體現在spring boot上。

核心關鍵點:

java 無法反射直接根據一個介面獲取所有的實現類,需要指定掃描包的路徑才可以,所以類似mybatis plus需要scan註解填入basePackage。

需要借用reflections工具包來反射獲取掃描包,需要結合guava一起用,版本要對應上。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>21.0</version>
</dependency>

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.11</version>
</dependency>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qt</groupId>
    <artifactId>duty-pattern-app</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>21.0</version>
        </dependency>

        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.11</version>
        </dependency>
    </dependencies>


</project>

定義一個介面IValidator,主要操作驗證。

public interface IValidator {
    /**
     * 驗證
     * @param req
     * @param validator
     * @param arg
     * @param <T>
     * @param <E>
     */
    <T extends BaseValidatorDto, E extends IValidator> void doValidate(T req, IValidator validator, Class<E> arg);
}

再定義一個IBaseValidator繼承IValidator,來新增其他功能,如是否可以驗證,順序。

public interface IBaseValidator extends IValidator {
    /**
     * 順序
     * @return
     */
    int getOrder();

    /**
     * 是否驗證
     * @return
     */
    boolean isValidate();
}

關鍵類:ValidatorChain

它的作用就是先獲取所有的執行器,然後依次用來分配到哪個物件需要執行。

下面的程式碼中init初始化中根據Reflections工具獲取介面所有實現的類,然後根據介面來分類存入map。

@Data
public class ValidatorScannerConfigurer {
    private String basePackage;
}

public final class ValidatorChain implements IValidator {
    @Autowired(required = false)
    private List<IBaseValidator> validatorList;

    private Map<Class<? extends IBaseValidator>, List<IBaseValidator>> validatorMap;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired(required = false)
    private ValidatorScannerConfigurer scannerConfigurer;

    @PostConstruct
    public void init() {
        if (!CollectionUtils.isEmpty(validatorList)) {
            validatorList = validatorList.stream().filter(x -> x.isValidate()).sorted(Comparator.comparing(IBaseValidator::getOrder)).collect(Collectors.toList());
        }
        validatorMap = new HashMap<>();
        String[] packages = Optional.ofNullable(scannerConfigurer)
                .map(x -> Optional.ofNullable(scannerConfigurer.getBasePackage())
                        .map(m -> m.split(",")).orElse(null)).orElse(null);
        if (packages == null || packages.length == 0) {
            return;
        }
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .forPackages(packages) // 指定掃描包路徑
                .addScanners(new SubTypesScanner()) // 新增子類掃描工具
                .addScanners(new FieldAnnotationsScanner()) // 新增 屬性註解掃描工具
                .addScanners(new MethodAnnotationsScanner()) // 新增 方法註解掃描工具
                .addScanners(new MethodParameterScanner()) // 新增方法引數掃描工具
        );
        Set<Class<? extends IBaseValidator>> subValidators = reflections.getSubTypesOf(IBaseValidator.class);
        //獲取介面的型別
        subValidators.stream().filter(x -> x.isInterface()).collect(Collectors.toSet()).forEach(x -> {
            Map<String, ? extends IBaseValidator> beansOfType = applicationContext.getBeansOfType(x);
            List<IBaseValidator> validators = beansOfType.entrySet().stream().map(m -> m.getValue()).collect(Collectors.toList());
            validatorMap.put(x, validators);
        });

    }

    /**
     * 驗證
     *
     * @param req
     * @param validator
     * @param arg
     */
    @Override
    public <T extends BaseValidatorDto, E extends IValidator> void doValidate(T req, IValidator validator, Class<E> arg) {
        List<IBaseValidator> baseValidators = validatorMap.get(arg);
        if (req.getIndex() == baseValidators.size()) {
            return;
        }
        System.out.println("當前位置:" + req.getIndex() + "... 對應型別:" + arg.getName());
        IBaseValidator baseValidator = baseValidators.get(req.getIndex());
        req.setIndex(req.getIndex() + 1);
        baseValidator.doValidate(req, validator, arg);
    }
}

ValidatorScannerConfigurer的設定

之前說了,掃描包需要指定包名,所以我參照mybatis plus的模式來實現自定義註解。關鍵點@Import來實現手動將ValidatorScannerConfigurer注入到容器中。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(ValidatorScannerRegistrar.class)
public @interface ValidatorScan {
    String[] value() default {};

    String[] basePackages() default {};
}

注入容器中有3種方式,可參照https://blog.csdn.net/tuoni123/article/details/80213050

  • 直接注入
  • 實現 ImportBeanDefinitionRegistrar 介面 注入
  • 實現 ImportSelector 注入

對比功能實用,我選擇用ImportBeanDefinitionRegistrar來實現。registerBeanDefinition方法用來注入容器

public class ValidatorScannerRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
     * registered here, due to lifecycle constraints related to {@code @Configuration}
     * class processing.
     *
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry               current bean definition registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes validatorScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(ValidatorScan.class.getName()));
        if (validatorScanAttrs != null) {
            this.registerBeanDefinitions(importingClassMetadata, validatorScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
        }
    }

    void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ValidatorScannerConfigurer.class);
        List<String> basePackages = new ArrayList();
        basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

    private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
        return importingClassMetadata.getClassName() + "#" + ValidatorScannerRegistrar.class.getSimpleName() + "#" + index;
    }
}

配置依賴注入

@Configuration
public class ValidatorBeanConfig {

    @Bean
    public ValidatorChain validatorChain() {
        return new ValidatorChain();
    }

    @Bean
    public IValidatorService validatorService() {
        return new ValidatorServiceImpl();
    }
}

客戶端暴露使用介面

public interface IValidatorService {
    /**
     * 驗證
     *
     * @param req
     * @param arg
     * @param <T>
     * @param <E>
     */
    <T extends BaseValidatorDto, E extends IValidator> void doValidate(T req, Class<E> arg);
}

實現方式如下,其中設定index為0,防止被修改

public final class ValidatorServiceImpl implements IValidatorService {
    @Qualifier("validatorChain")
    @Autowired
    private IValidator validator;

    /**
     * 驗證
     *
     * @param req
     * @param arg
     */
    @Override
    public <T extends BaseValidatorDto, E extends IValidator> void doValidate(T req, Class<E> arg) {
        req.setIndex(0);
        validator.doValidate(req, validator, arg);
    }
}

使用方法

1.新增介面繼承IBaseValidator

public interface ILoginValidator extends IBaseValidator {
}

2.實現類

@Service
public class LoginNameValidator implements ILoginValidator {

    @PostConstruct
    public void init(){
        System.out.println("LoginNameValidator init");
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public boolean isValidate() {
        return true;
    }

    @Override
    public <T extends BaseValidatorDto, E> void doValidate(T req, IValidator validator, Class<E> arg) {
        System.out.println("login name validator");
        validator.doValidate(req, validator, arg);
    }
}

新增掃描包

@Configuration
@ValidatorScan({"com.validator"})
public class ValidatorConfig {
}

客戶端呼叫IValidatorService

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private IValidatorService validatorService;

    @Override
    public void loginAction() {
        LoginValidatorDto dto = new LoginValidatorDto();
        dto.setUserName("wangsio");
        validatorService.doValidate(dto, ILoginValidator.class);
    }
}