Spring Validation最佳实践及其实现原理,参数校验没那么简单!

(点击上方公众号,可快速关注)


之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation。本文会详细介绍Spring Validation各种场景下的最佳实践及其实现原理,死磕到底!


项目源码:https://github.com/chentianming11/spring-validation


简单使用


Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。


Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们以spring-boot项目为例,介绍Spring Validation的使用。


引入依赖


如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖:

<dependency>
    <groupId>org.hibernategroupId>
    <artifactId>hibernate-validatorartifactId>
    <version>6.0.1.Finalversion>
dependency>


对于web服务来说,为防止非法参数对业务造成影响,在Controller层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:


  • POST、PUT请求,使用requestBody传递参数;

  • GET请求,使用requestParam/PathVariable传递参数。


  • 下面我们简单介绍下requestBody和requestParam/PathVariable的参数校验实战!


    requestBody参数校验


    POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用DTO对象进行接收。只要给DTO对象加上@Validated注解就能实现自动参数校验。比如,有一个保存User的接口,要求userName长度是2-10,account和password字段长度是6-20。


    如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求。


    DTO表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的。在spring-web项目中可以表示用于接收请求参数的Bean对象。


    在DTO字段上声明约束注解


    @Data
    public class UserDTO {

        private Long userId;

        @NotNull
        @Length(min = 2, max = 10)
        private String userName;

        @NotNull
        @Length(min = 6, max = 20)
        private String account;

        @NotNull
        @Length(min = 6, max = 20)
        private String password;
    }


    在方法参数上声明校验注解


    @PostMapping("/save")
    public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
        // 校验通过,才会执行业务逻辑处理
        return Result.ok();
    }


    这种情况下,使用@Valid和@Validated都可以。


    requestParam/PathVariable参数校验


    GET请求一般会使用requestParam/PathVariable传参。如果参数比较多(比如超过6个),还是推荐使用DTO对象接收。


    否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解(如@Min等)。如果校验失败,会抛出ConstraintViolationException异常。


    代码示例如下:


    @RequestMapping("/api/user")
    @RestController
    @Validated
    public class UserController {
        // 路径变量
        @GetMapping("{userId}")
        public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
            // 校验通过,才会执行业务逻辑处理
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId(userId);
            userDTO.setAccount("11111111111111111");
            userDTO.setUserName("xixi");
            userDTO.setAccount("11111111111111111");
            return Result.ok(userDTO);
        }

        // 查询参数
        @GetMapping("getByAccount")
        public Result getByAccount(@Length(min = 6, max = 20) @NotNull String  account) {
            // 校验通过,才会执行业务逻辑处理
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId(10000000000000003L);
            userDTO.setAccount(account);
            userDTO.setUserName("xixi");
            userDTO.setAccount("11111111111111111");
            return Result.ok(userDTO);
        }
    }


    统一异常处理


    前面说过,如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。


    在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。


    比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。


    @RestControllerAdvice
    public class CommonExceptionHandler {

        @ExceptionHandler({MethodArgumentNotValidException.class})
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
            BindingResult bindingResult = ex.getBindingResult();
            StringBuilder sb = new StringBuilder("校验失败:");
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
            }
            String msg = sb.toString();
           return Result.fail(BusinessCode.参数校验失败, msg);
        }

        @ExceptionHandler({ConstraintViolationException.class})
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        public Result handleConstraintViolationException(ConstraintViolationException ex) {
            return Result.fail(BusinessCode.参数校验失败, ex.getMessage());
        }
    }


    进阶使用


    分组校验


    在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。


    还是上面的例子,比如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。这个时候使用分组校验的代码示例如下:


    约束注解上声明适用的分组信息groups


    @Data
    public class UserDTO {

        @Min(value = 10000000000000000L, groups = Update.class)
        private Long userId;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 2, max = 10, groups = {Save.class, Update.class})
        private String userName;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 6, max = 20, groups = {Save.class, Update.class})
        private String account;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 6, max = 20, groups = {Save.class, Update.class})
        private String password;

        /**
         * 保存的时候校验分组
         */
        public interface Save {
        }

        /**
         * 更新的时候校验分组
         */
        public interface Update {
        }
    }


    @Validated注解上指定校验分组

    @PostMapping("/save")
    public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
        // 校验通过,才会执行业务逻辑处理
        return Result.ok();
    }

    @PostMapping("/update")
    public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
        // 校验通过,才会执行业务逻辑处理
        return Result.ok();
    }


    嵌套校验


    前面的示例中,DTO类里面的字段都是基本数据类型和String类型。


    但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。


    比如,上面保存User信息的时候同时还带有Job信息。需要注意的是,此时DTO类的对应字段必须标记@Valid注解。


    @Data
    public class UserDTO {

        @Min(value = 10000000000000000L, groups = Update.class)
        private Long userId;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 2, max = 10, groups = {Save.class, Update.class})
        private String userName;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 6, max = 20, groups = {Save.class, Update.class})
        private String account;

        @NotNull(groups = {Save.class, Update.class})
        @Length(min = 6, max = 20, groups = {Save.class, Update.class})
        private String password;

        @NotNull(groups = {Save.class, Update.class})
        @Valid
        private Job job;

        @Data
        public static class Job {

            @Min(value = 1, groups = Update.class)
            private Long jobId;

            @NotNull(groups = {Save.class, Update.class})
            @Length(min = 2, max = 10, groups = {Save.class, Update.class})
            private String jobName;

            @NotNull(groups = {Save.class, Update.class})
            @Length(min = 2, max = 10, groups = {Save.class, Update.class})
            private String position;
        }

        /**
         * 保存的时候校验分组
         */
        public interface Save {
        }

        /**
         * 更新的时候校验分组
         */
        public interface Update {
        }
    }


    嵌套校验可以结合分组校验一起使用。还有就是嵌套集合校验会对集合里面的每一项都进行校验,例如List 字段会对这个list里面的每一个Job对象都进行校验。


    集合校验


    如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:


    包装List类型,并声明@Valid注解


    public class ValidationList<E> implements List<E> {

        @Delegate // @Delegate是lombok注解
        @Valid // 一定要加@Valid注解
        public List  list =  new ArrayList<>();

         // 一定要记得重写toString方法
         @Override
         public String toString() {
             return list.toString();
        }
    }


    @Delegate注解受lombok版本限制,1.18.6以上版本可支持。如果校验不通过,会抛出NotReadablePropertyException,同样可以使用统一异常进行处理。


    比如,我们需要一次性保存多个User对象,Controller层的方法可以这么写:


    @PostMapping("/saveList")
    public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList  userList)  {
        // 校验通过,才会执行业务逻辑处理
        return Result.ok();
    }


    自定义校验


    业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。


    自定义spring validation非常简单,假设我们自定义加密id(由数字或者a-f的字母组成,32-256长度)校验,主要分为两步:


    自定义约束注解


    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = {EncryptIdValidator.class})
    public @interface EncryptId {

        // 默认错误消息
        String message() default "加密id格式错误";

        // 分组
        Class[] groups() default {};

        // 负载
        Class[] payload() default {};
    }


    实现ConstraintValidator接口编写约束校验器


    public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {

        private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            // 不为null才进行校验
            if (value != null) {
                Matcher matcher = PATTERN.matcher(value);
                return matcher.find();
            }
            return true;
        }
    }


    这样我们就可以使用@EncryptId进行参数校验了!


    编程式校验


    上面的示例都是基于注解来实现自动校验的,在某些情况下,我们可能希望以编程方式调用验证。这个时候可以注入

    javax.validation.Validator对象,然后再调用其api。


    @Autowired
    private javax.validation.Validator globalValidator;

    // 编程式校验
    @PostMapping("/saveWithCodingValidate")
    public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
        Set<constraintviolation > validate = globalValidator.validate(userDTO, UserDTO.Save.class);
        // 如果校验通过,validate为空;否则,validate包含未校验通过项
        if (validate.isEmpty()) {
            // 校验通过,才会执行业务逻辑处理

        } else {
            for (ConstraintViolation  userDTOConstraintViolation : validate) {
                 // 校验失败,做其它逻辑
                System.out.println(userDTOConstraintViolation);
            }
        }
         return Result.ok();
    }
    </constraintviolation


    快速失败(Fail Fast)


    Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启Fali Fast模式,一旦校验失败就立即返回。


    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }


    @Valid和@Validated区别


    实现原理


    requestBody参数校验实现原理


    在spring-mvc中,RequestResponseBodyMethodProcessor是用于解析@RequestBody标注的参数以及处理@ResponseBody标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法resolveArgument()中:


    public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
        @Override
        public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

            parameter = parameter.nestedIfOptional();
            //将请求数据封装到DTO对象中
            Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
            String name = Conventions.getVariableNameForParameter(parameter);

            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
                if (arg != null) {
                    // 执行数据校验
                    validateIfApplicable(binder, parameter);
                    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                    }
                }
                if (mavContainer != null) {
                    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
                }
            }
            return adaptArgumentIfNecessary(arg, parameter);
        }
    }


    可以看到,resolveArgument()调用了validateIfApplicable()进行参数校验。


    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        // 获取参数注解,比如@RequestBody、@Valid、@Validated
        Annotation[] annotations = parameter.getParameterAnnotations();
        for (Annotation ann : annotations) {
            // 先尝试获取@Validated注解
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
            //如果直接标注了@Validated,那么直接开启校验。
            //如果没有,那么判断参数前是否有Valid起头的注解。
            if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
                Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
                Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
                //执行校验
                binder.validate(validationHints);
                break;
            }
        }
    }


    看到这里,大家应该能明白为什么这种场景下@Validated、@Valid两个注解可以混用。我们接下来继续看WebDataBinder.validate()实现。


    @Override
    public void validate(Object target, Errors errors, Object... validationHints) {
        if (this.targetValidator != null) {
            processConstraintViolations(
                //此处调用Hibernate Validator执行真正的校验
                this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
        }
    }


    最终发现底层最终还是调用了Hibernate Validator进行真正的校验处理。


    方法级别的参数校验实现原理


    上面提到的将参数一个个平铺到方法参数中,然后在每个参数前面声明约束注解的校验方式,就是方法级别的参数校验。


    实际上,这种方式可用于任何Spring Bean的方法上,比如Controller/Service等。其底层实现原理就是AOP,具体来说是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法织入增强。


    public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {
        @Override
        public void afterPropertiesSet() {
            //为所有`@Validated`标注的Bean创建切面
            Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
            //创建Advisor进行增强
            this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
        }

        //创建Advice,本质就是一个方法拦截器
        protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
            return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
        }
    }


    接着看一下MethodValidationInterceptor:


    public class MethodValidationInterceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            //无需增强的方法,直接跳过
            if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
                return invocation.proceed();
            }
            //获取分组信息
            Class[] groups = determineValidationGroups(invocation);
            ExecutableValidator execVal = this.validator.forExecutables();
            Method methodToValidate = invocation.getMethod();
            Set<constraintviolation> result;
            try {
                //方法入参校验,最终还是委托给Hibernate Validator来校验
                result = execVal.validateParameters(
                    invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
            }
            catch (IllegalArgumentException ex) {
                ...
            }
            //有异常直接抛出
            if (!result.isEmpty()) {
                throw new ConstraintViolationException(result);
            }
            //真正的方法调用
            Object returnValue = invocation.proceed();
            //对返回值做校验,最终还是委托给Hibernate Validator来校验
            result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
            //有异常直接抛出
            if (!result.isEmpty()) {
                throw new ConstraintViolationException(result);
            }
            return returnValue;
        }
    }
    </constraintviolation


    实际上,不管是requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装。


    推荐程序员必备微信号 

    程序员内参
    微信号:

    progrmer0001


    推荐理由:
    在这里,我们分享程序员相关技术,职场生活,行业热点资讯。不定期还会分享IT趣文和趣图。这里属于我们程序员自己的生活,工作和娱乐空间。
     ▼长按下方↓↓↓二维码识别关注

    收外国男人的钱骗中国妹子的炮?天朝竟有这样一帮「女权组织」 2018-03-19 INSIGHT视界 From 酷玩实验室 微信号:coollabs 其实我读书的时候 也曾经想过做一个女权主义者 但是后来发生了一些事情 让我选择了放弃 简单来说是这么一个事情:我发现 女权对于一些中国人来说是信仰 但是对另一些中国人来说是生意 所谓的“伪女权”“女权癌” 大概就是这么回事 尽管早就有这样的思想准备 但让我没想到的是 这两天,知乎上曝光了一件大事 还是让我三观震碎 我没想到,这些“伪女权” 竟然已经形成了黑色产业链 让人细思恐极—— 国内竟然有一群人 打着“女权主义”的名号 从事着组织卖淫的事情 在中国女生不知情的情况下 把她们卖给外国男人!事情是这样的:根据知乎用户伊利丹·怒风的爆料 他在知乎和一个伪女权主义者 吵了起来 一开始,他可能以为这只是一个 脑子比较轴的伪女权主义者 所以两人就吵了一通 本来,他以为就是撕个逼而已 没想到的是 这个伪女权主义者 可不是什么好惹的主 这个自称为“玛丽女王”的人 竟然在半个月中 持续不断地骚扰他 而最夸张的是 玛丽女王声称 自己有能力 让伊利丹的QQ号 在5天之内被封掉 到这里为止 伊利丹一直以为 他不过是碰到了一个杠精 但是万万没想到 5天之后 他的QQ号竟然真的被永久封禁了!说真的,这就有点吓人了 这个不起眼的玛丽女王 竟然还能操控别人的QQ账号被封?难不成,她真的背后有人?伊利丹这才意识到 自己好像惹到了一个组织 他去扒了扒这个玛丽女王的QQ空间 这才发现 自己简直捅出一个马蜂窝:这个人平时干的 竟然是把中国女生 卖给外国男人的皮肉生意!真的,我本来以为 我是一个见过不少套路的人 但没想到 这一套操作 真的是惊为天人 简单来说是这样的 首先,玛丽女王自称是“女权主义者” 但是实际上她的言论 宣传的却是 中国男人配不上中国女人 她甚至恶意辱骂中国男人 恨不得中国男人全部死光 连自己的爸爸都不放过 但是,这么做对她有什么好处呢?很简单 骂完中国男人以后 接下来她就说—— 既然中国男人这么差劲 那就找外国男人吧!于是,她就经常发布外国男人的介绍 看起来是一个热心的媒婆 还在各种QQ和微信群里 散播此类信息 但是看到这里 我们不难发现有点问题 看看其中这些不堪入目的措辞 这并不是普通的介绍男友啊!这简直是在拉皮条啊!果然,伊利丹发现 玛丽女王真的在 拉皮条的过程中 收外国男人的钱!下面是聊天记录实锤:而且,请注意—— 在这个过程中 她会收外国男人的钱 但是钱不给中国女生 却落到了她自己的腰包 于是一个诡异的情况出现了:中国妹子 并不知道收钱这回事 还以为是正常交友 而外国男人 却都交了钱 很可能认为自己是在买春!额,也就是说 在中国女孩不知情的情况下 她们被“卖”给了外国男人 而好处费 却全都进了玛丽女王的腰包... 我真的是没见过这种操作 这说轻了是骗炮 说重了,已经可以算是卖淫了吧?我想请熟悉刑法的朋友们看看 这个玛丽女王 至少应该算是个 介绍组织卖淫罪吧?而且,从伊利丹曝光的资料看来 这个组织规模不小 玛丽女王甚至把外国男生的信息 建了一个完整的表格 有详细的个人资料、照片 可以说 是一条非常完整的产业链 那如果按照这样操作 外国男人都是来嫖的 中国女生却不知道 还以为是要跟他们谈恋爱 那双方难道不会穿帮吗?恩,在这方面 玛丽女王早有对策 根据知乎一位 从事过这个产业的匿名用户提供的信息 针对这种情况 玛丽女王们 还会手把手地教外国男人 怎么快速摆脱女生的纠缠 怎么调教中国女生 怎么让女生觉得自己很可爱 可以说 各种套路一应俱全 甚至还可以开发票!看到这里 她们背后的产业就非常清楚了 这个玛丽女王 她根本就不是什么女权主义者 而是打着女权主义的口号 贩卖中国女生的人贩子 一方面 她们通过辱骂中国男人 吸引对外国男人感兴趣的中国女生 另一方面 她们向外国男人收钱 然后把中国女生卖给他们!图片来源:知乎@渭水徐工 而可怜的中国妹子们 还以为自己是在 追求男女平权 其实,不过是沦为了 这些老鸨的赚钱工具 伊利丹把这整个事情 写出来以后 在知乎、微博引起了巨大的关注 关于其中提到的 伊利丹的QQ被永久封禁的问题 腾讯经过核查 目前也有了结果:经调查,是玛丽女王利用伪造证据 恶意举报了伊利丹的QQ号 目前,腾讯已经将伊利丹的QQ解封 同时封禁了玛丽女王等人的 两个QQ账号 警方也就此事立案侦查了 相信很快就会有结果 这个事情算是告一段落了 但是在我看来 却有一件事让我无法释怀:为什么“女权主义”竟然会和 辱骂中国男性等同起来?为什么“和外国男人交友” 竟然还能演变成 一个免费的陪睡组织?我想,这个玛丽女王 也许只是一个 发现了恶性赚钱模式的生意人 但是在这背后隐藏的 其实是一个很深的问题:为什么有不少中国女人 越来越看不上中国男人 甚至觉得嫁给外国男人 是一种时尚?这里面的原因可能非常复杂 我这里先提供一个思路 供大家讨论:我发现 现在中国很多大型的女权组织 背后都有着西方势力的影子 她们打着女权的名号 为自己谋取暴利 为西方国家从事破坏活动 而那些真正为女性平权而奔走的人 却得不到应有的帮助 我之所以这样说 并不是信口开河 而是有充足的证据 有一个非常有名的民间女权组织 叫做“女权之声” 它一再声称 自己只是一个自发的民间组织 致力于促进男女平等的 它所有的微博账号、微信账号 全部都是由一个 叫做妇女传媒监测网络的创办的 而这个妇女传媒监测网络 有这么多媒体产品 那它的钱都是哪里来的呢?从她们介绍的合作组织里 我们可以清楚地找到 她们的资助者—— 竟然有西方的福特基金会 有人也许会问 收了西方的钱怎么了?中国的组织不能收西方的钱吗?然而,她们不只是收了西方的钱而已 女权之声组织里 有一个人叫做郑楚然 她除了女权运动之外 没有任何其他工作 表面上,是一个全职的女权工作者 在2015年的时候 她还因为寻衅滋事 被警察拘留过30多天 甚至在她被拘留的时候 希拉里还借题发挥 指责中国侵犯人权、压制民主 一个中国的小小民间组织的首领 在互联网上的粉丝还没有我多 竟然能得到希拉里这个级别的关注?我真的是惊掉了下巴 这样看来 我离希拉里也不是很远了??而不止是希拉里 这样一个明明思想上毫无建树的人 却被西方媒体BBC评为了 全球百大思想家 图:郑楚然在王宝强事件中发表的言论 除此以外 更让人匪夷所思的 是她们平时就喜欢攻击政府 甚至于,她们还会试图分裂我们国家 比如,女权之声这个组织里 著名的女权斗士洪理达 就曾经转发著名的港独媒体 Hong Kong Free Press的言论 甚至曾公开发表过 支持藏独、港独、台独的言论 她也经常和郑楚然混在一起 我很想不通 如果她们真的只是单纯的女权主义者 为何要发表分裂国家的言论?为何要支持藏独、港独、台独?我只能说,这大概就叫 拿人家的手短,吃人家的嘴软吧 以前,我在接触中国的女权组织时 我就觉得很奇怪 她们都喜欢声称 自己是不盈利的非政府组织 但是她们无论是宣传 还是组织各类活动 都需要大量的钱 如果她们真的不盈利 那这些钱都是哪里来的呢?而这些外国的金主 他们也更加不可能是什么慈善组织 大发善心来给中国人投钱 每一分投出去的钱 一定都是要有回报的 那么,他们的回报是什么呢?他们给中国的“女权组织”投钱 能得到什么利益呢?联想到中国网络上 如火如荼的对中国男人的讨伐 我只能说,细思恐极 我绝不是危言耸听 因为我们就看不远的邻国日本 近些年来日本对于西方的崇拜 可谓深入骨髓 已经到了崇洋媚外的程度 而这其中 当然也包括对白人男性的崇拜 甚至在2016年一个瑞士白人 发了一个视频,赤裸裸的说 “在东京,只要你是白人, 做什么都可以” 视频里面他在日本便利店 随意的亲吻不认识的收银员女孩 在酒吧把不认识的日本女孩 按向自己的裤裆 而日本女孩回应的却是谄媚的笑容 我想,并不会有那么多中国人 真正被西方伪女权主义控制 但是,我们要警惕的是 别在你自己都没有察觉的时候 被别有用心的人洗了脑 更有甚者 别在你自己都不知道的情况下 被别人卖给了外国男人 还去帮他数钱 本文系授权发布,From 酷玩实验室,微信号:coollabs,欢迎分享到朋友圈,未经许可不得转载,INSIGHT视界 诚意推荐 Forwarded from Official Account 酷玩实验室 酷玩实验室 Learn More Scan QR Code via WeChat to follow Official Account 采集文章采集样式近似文章查看封面