美文网首页JavaWeb 知识点renren-fast项目分析其它开源框架
人人快速开发平台 renren-fast 源码分析(二)异常处理

人人快速开发平台 renren-fast 源码分析(二)异常处理

作者: beetlebum | 来源:发表于2018-09-07 15:11 被阅读325次

    异常处理

    在上一篇文章中,我们找到了RRExceptionHandler

    /**
     * 异常处理器
     * 
     * @author chenshun
     * @email sunlightcs@gmail.com
     * @date 2016年10月27日 下午10:16:19
     */
    @RestControllerAdvice
    public class RRExceptionHandler {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        /**
         * 处理自定义异常
         */
        @ExceptionHandler(RRException.class)
        public R handleRRException(RRException e){
            R r = new R();
            r.put("code", e.getCode());
            r.put("msg", e.getMessage());
    
            return r;
        }
    
        @ExceptionHandler(NoHandlerFoundException.class)
        public R handlerNoFoundException(Exception e) {
            logger.error(e.getMessage(), e);
            return R.error(404, "路径不存在,请检查路径是否正确");
        }
    
        @ExceptionHandler(DuplicateKeyException.class)
        public R handleDuplicateKeyException(DuplicateKeyException e){
            logger.error(e.getMessage(), e);
            return R.error("数据库中已存在该记录");
        }
    
        @ExceptionHandler(AuthorizationException.class)
        public R handleAuthorizationException(AuthorizationException e){
            logger.error(e.getMessage(), e);
            return R.error("没有权限,请联系管理员授权");
        }
    
        @ExceptionHandler(Exception.class)
        public R handleException(Exception e){
            logger.error(e.getMessage(), e);
            return R.error();
        }
    }
    

    该类用@RestControllerAdvice修饰,这个注解修饰的类,里面的方法作用于@ResponseBody修饰的方法返回结果。从handleRRException(RRException e)方法看得出,系统中的业务逻辑异常都是直接往上抛,最后由RRExceptionHandler处理并且返回对应的提示信息。
    查看R类可以发现,该项目 RESTful 接口返回结果都是一个统一格式的 map 转成的 json

    /**
     * 返回数据
     * 
     * @author chenshun
     * @email sunlightcs@gmail.com
     * @date 2016年10月27日 下午9:59:27
     */
    public class R extends HashMap<String, Object> {
        private static final long serialVersionUID = 1L;
        
        public R() {
            put("code", 0);
            put("msg", "success");
        }
        
        public static R error() {
            return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
        }
        
        public static R error(String msg) {
            return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
        }
        
        public static R error(int code, String msg) {
            R r = new R();
            r.put("code", code);
            r.put("msg", msg);
            return r;
        }
    
        public static R ok(String msg) {
            R r = new R();
            r.put("msg", msg);
            return r;
        }
        
        public static R ok(Map<String, Object> map) {
            R r = new R();
            r.putAll(map);
            return r;
        }
        
        public static R ok() {
            return new R();
        }
    
        public R put(String key, Object value) {
            super.put(key, value);
            return this;
        }
    }
    

    校验

    项目中使用的是 Hibernate-validator 做输入的校验
    以登录表单为例
    LoginForm.java

    /**
     * 登录表单
     *
     * @author Mark sunlightcs@gmail.com
     * @since 3.1.0 2018-01-25
     */
    @ApiModel(value = "登录表单")
    public class LoginForm {
        @ApiModelProperty(value = "手机号")
        @NotBlank(message="手机号不能为空")
        private String mobile;
    
        @ApiModelProperty(value = "密码")
        @NotBlank(message="密码不能为空")
        private String password;
    
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    AppLoginController.java

    /**
     * APP登录授权
     *
     * @author chenshun
     * @email sunlightcs@gmail.com
     * @date 2017-03-23 15:31
     */
    @RestController
    @RequestMapping("/app")
    @Api("APP登录接口")
    public class AppLoginController {
        @Autowired
        private UserService userService;
        @Autowired
        private JwtUtils jwtUtils;
    
        /**
         * 登录
         */
        @PostMapping("login")
        @ApiOperation("登录")
        public R login(@RequestBody LoginForm form){
            //表单校验
            ValidatorUtils.validateEntity(form);
    
            //用户登录
            long userId = userService.login(form);
    
            //生成token
            String token = jwtUtils.generateToken(userId);
    
            Map<String, Object> map = new HashMap<>();
            map.put("token", token);
            map.put("expire", jwtUtils.getExpire());
    
            return R.ok(map);
        }
    
    }
    

    ValidatorUtils.java

    /**
     * hibernate-validator校验工具类
     *
     * 参考文档:http://docs.jboss.org/hibernate/validator/5.4/reference/en-US/html_single/
     *
     * @author chenshun
     * @email sunlightcs@gmail.com
     * @date 2017-03-15 10:50
     */
    public class ValidatorUtils {
        private static Validator validator;
    
        static {
            validator = Validation.buildDefaultValidatorFactory().getValidator();
        }
    
        /**
         * 校验对象
         * @param object        待校验对象
         * @param groups        待校验的组
         * @throws RRException  校验不通过,则报RRException异常
         */
        public static void validateEntity(Object object, Class<?>... groups)
                throws RRException {
            Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
            if (!constraintViolations.isEmpty()) {
                StringBuilder msg = new StringBuilder();
                for(ConstraintViolation<Object> constraint:  constraintViolations){
                    msg.append(constraint.getMessage()).append("<br>");
                }
                throw new RRException(msg.toString());
            }
        }
    }
    

    参照 hibernate-validator文档 ,得知,在AppLoginControllerlogin 方法中,调用ValidatorUtils.validateEntity(form);通过 LoginForm声明的属性对应的@NotBlank注解,来校验输入是否符合约束。若不符合,根据 validator 返回的message拼接成异常信息,然后抛出 RRException,提示对应信息,这就连接上异常处理功能了。

    异常处理和校验,如果只是看怎么使用,为什么要这么用的话是挺简单的。项目中使用@RestControllerAdvice作为异常处理的方式,并且用 hibernate-validator 作为校验工具,可以减少大量的输入校验和异常捕获的代码(当然有些时候还是需要自己在代码中捕获不希望抛出的异常的)。

    但是只看怎么用实在是太没有意思了。因此我们来试着读一下 validator 的源码~

    首先我们找到 Validator 的实现类 ValidatorImpl,找到validate(T object, Class<?>... groups)方法

        @Override
        public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
            Contracts.assertNotNull( object, MESSAGES.validatedObjectMustNotBeNull() );
            sanityCheckGroups( groups );
    
            ValidationContext<T> validationContext = getValidationContextBuilder().forValidate( object );
    
            if ( !validationContext.getRootBeanMetaData().hasConstraints() ) {
                return Collections.emptySet();
            }
    
            ValidationOrder validationOrder = determineGroupValidationOrder( groups );
            ValueContext<?, Object> valueContext = ValueContext.getLocalExecutionContext(
                    validatorScopedContext.getParameterNameProvider(),
                    object,
                    validationContext.getRootBeanMetaData(),
                    PathImpl.createRootPath()
            );
    
            return validateInContext( validationContext, valueContext, validationOrder );
        }
    

    根据官网文档指出,groups 不传的话应该是对所有属性进行校验,否则只对对应 groups 的属性进行校验。那我们就来看看它是怎么做到的.
    在这个方法中主要做了四件事:

    1. 根据 object 参数获得 ValidationContext<T>
    2. 根据 groups 参数获得ValidationOrder
    3. 根据 object, validationContext 等获得ValueContext<?, Object>
    4. 调用validateInContext( validationContext, valueContext, validationOrder );

    这个方法的主干就完成了,接下来一个个方法看看里面都做了什么

    根据 object 参数获得 ValidationContext<T>

    查看ValidationContext<T>源码,类的注释上有这么一段话

    /**
     * Context object keeping track of all required data for a validation call.
     *
     * We use this object to collect all failing constraints, but also to have access to resources like
     * constraint validator factory, message interpolator, traversable resolver, etc.
     */
    

    也就说,这个类是用来做 validation call 的时候传入的,并且会把调用时产生的所有需要的数据都追踪。这个类是通过ValidationContextBuilder.forValidate(T rootBean)创建的。看看都是怎么创建的吧。

    public <T> ValidationContext<T> forValidate(T rootBean) {
                @SuppressWarnings("unchecked")
                Class<T> rootBeanClass = (Class<T>) rootBean.getClass();
                return new ValidationContext<>(
                        ValidationOperation.BEAN_VALIDATION,
                        constraintValidatorManager,
                        constraintValidatorFactory,
                        validatorScopedContext,
                        traversableResolver,
                        constraintValidatorInitializationContext,
                        rootBean,
                        rootBeanClass,
                        beanMetaDataManager.getBeanMetaData( rootBeanClass ),
                        null, //executable
                        null, //executable parameters
                        null, //executable return value
                        null //executable metadata
                );
            }
    

    已知传入的参数有待校验的 bean,这个 bean 所属的 class,还有一堆杂七杂八的参数,先放着。目前已知这个类是用来存放所有校验时的数据的就行了。

    根据 groups 参数获得 ValidationOrder

    跟踪 determineGroupValidationOrder( groups ) 会找到getValidationOrder(Collection<Class<?>> groups)

    /**
         * Generates a order of groups and sequences for the specified validation groups.
         *
         * @param groups the groups specified at the validation call
         *
         * @return an instance of {@code ValidationOrder} defining the order in which validation has to occur
         */
        public ValidationOrder getValidationOrder(Collection<Class<?>> groups) {
            if ( groups == null || groups.size() == 0 ) {
                throw LOG.getAtLeastOneGroupHasToBeSpecifiedException();
            }
    
            // HV-621 - if we deal with the Default group we return the default ValidationOrder. No need to
            // process Default as other groups which saves several reflection calls (HF)
            if ( groups.size() == 1 && groups.contains( Default.class ) ) {
                return ValidationOrder.DEFAULT_GROUP;
            }
    
            for ( Class<?> clazz : groups ) {
                if ( !clazz.isInterface() ) {
                    throw LOG.getGroupHasToBeAnInterfaceException( clazz );
                }
            }
    
            DefaultValidationOrder validationOrder = new DefaultValidationOrder();
            for ( Class<?> clazz : groups ) {
                if ( Default.class.equals( clazz ) ) { // HV-621
                    validationOrder.insertGroup( Group.DEFAULT_GROUP );
                }
                else if ( isGroupSequence( clazz ) ) {
                    insertSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), true, validationOrder );
                }
                else {
                    Group group = new Group( clazz );
                    validationOrder.insertGroup( group );
                    insertInheritedGroups( clazz, validationOrder );
                }
            }
    
            return validationOrder;
        }
    

    然后我们看看默认的 ValidationOrder

    class DefaultGroupValidationOrder implements ValidationOrder {
    
            private final List<Group> defaultGroups;
    
            private DefaultGroupValidationOrder() {
                defaultGroups = Collections.singletonList( Group.DEFAULT_GROUP );
            }
    
            @Override
            public Iterator<Group> getGroupIterator() {
                return defaultGroups.iterator();
            }
    
            @Override
            public Iterator<Sequence> getSequenceIterator() {
                return Collections.<Sequence>emptyIterator();
            }
    
            @Override
            public void assertDefaultGroupSequenceIsExpandable(List<Class<?>> defaultGroupSequence) throws GroupDefinitionException {
            }
        }
    

    可以看出ValidationOrder是用来存放校验的顺序的。里面可以取得一个校验迭代器,估计后面校验的时候是根据这个迭代器来比较属性的 group 的,这其实看类名就可以猜到了。

    根据 object, validationContext 等获得 ValueContext<?, Object>

    我们看看ValueContext<?, Object>是个什么东西

    /**
     * An instance of this class is used to collect all the relevant information for validating a single class, property or
     * method invocation.
     */
    

    根据类注释可知这个类是用来存放待校验的 property 和 method invocation 的。也就是说这个类是校验时用来遍历的。

    那么这三个类是做什么的都知道了,我们看看校验逻辑

    调用validateInContext( validationContext, valueContext, validationOrder );

    /**
         * Validates the given object using the available context information.
         * @param validationContext the global validation context
         * @param valueContext the current validation context
         * @param validationOrder Contains the information which and in which order groups have to be executed
         *
         * @param <T> The root bean type
         *
         * @return Set of constraint violations or the empty set if there were no violations.
         */
        private <T, U> Set<ConstraintViolation<T>> validateInContext(ValidationContext<T> validationContext, ValueContext<U, Object> valueContext,
                ValidationOrder validationOrder) {
            if ( valueContext.getCurrentBean() == null ) {
                return Collections.emptySet();
            }
    
            BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
            if ( beanMetaData.defaultGroupSequenceIsRedefined() ) {
                validationOrder.assertDefaultGroupSequenceIsExpandable( beanMetaData.getDefaultGroupSequence( valueContext.getCurrentBean() ) );
            }
    
            // process first single groups. For these we can optimise object traversal by first running all validations on the current bean
            // before traversing the object.
            Iterator<Group> groupIterator = validationOrder.getGroupIterator();
            while ( groupIterator.hasNext() ) {
                Group group = groupIterator.next();
                valueContext.setCurrentGroup( group.getDefiningClass() );
                validateConstraintsForCurrentGroup( validationContext, valueContext );
                if ( shouldFailFast( validationContext ) ) {
                    return validationContext.getFailingConstraints();
                }
            }
            groupIterator = validationOrder.getGroupIterator();
            while ( groupIterator.hasNext() ) {
                Group group = groupIterator.next();
                valueContext.setCurrentGroup( group.getDefiningClass() );
                validateCascadedConstraints( validationContext, valueContext );
                if ( shouldFailFast( validationContext ) ) {
                    return validationContext.getFailingConstraints();
                }
            }
    
            // now we process sequences. For sequences I have to traverse the object graph since I have to stop processing when an error occurs.
            Iterator<Sequence> sequenceIterator = validationOrder.getSequenceIterator();
            while ( sequenceIterator.hasNext() ) {
                Sequence sequence = sequenceIterator.next();
                for ( GroupWithInheritance groupOfGroups : sequence ) {
                    int numberOfViolations = validationContext.getFailingConstraints().size();
    
                    for ( Group group : groupOfGroups ) {
                        valueContext.setCurrentGroup( group.getDefiningClass() );
    
                        validateConstraintsForCurrentGroup( validationContext, valueContext );
                        if ( shouldFailFast( validationContext ) ) {
                            return validationContext.getFailingConstraints();
                        }
    
                        validateCascadedConstraints( validationContext, valueContext );
                        if ( shouldFailFast( validationContext ) ) {
                            return validationContext.getFailingConstraints();
                        }
                    }
                    if ( validationContext.getFailingConstraints().size() > numberOfViolations ) {
                        break;
                    }
                }
            }
            return validationContext.getFailingConstraints();
        }
    

    这个方法主要调用了validateConstraintsForCurrentGroup( validationContext, valueContext );,那么继续看源码

    private void validateConstraintsForCurrentGroup(ValidationContext<?> validationContext, ValueContext<?, Object> valueContext) {
            // we are not validating the default group there is nothing special to consider. If we are validating the default
            // group sequence we have to consider that a class in the hierarchy could redefine the default group sequence.
            if ( !valueContext.validatingDefault() ) {
                validateConstraintsForNonDefaultGroup( validationContext, valueContext );
            }
            else {
                validateConstraintsForDefaultGroup( validationContext, valueContext );
            }
        }
    

    看看默认 group 的校验

    private <U> void validateConstraintsForDefaultGroup(ValidationContext<?> validationContext, ValueContext<U, Object> valueContext) {
            final BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
            final Map<Class<?>, Class<?>> validatedInterfaces = new HashMap<>();
    
            // evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions
            for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
                BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
                boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.defaultGroupSequenceIsRedefined();
    
                // if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
                if ( defaultGroupSequenceIsRedefined ) {
                    Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );
                    Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getMetaConstraints();
    
                    while ( defaultGroupSequence.hasNext() ) {
                        for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
                            boolean validationSuccessful = true;
    
                            for ( Group defaultSequenceMember : groupOfGroups ) {
                                validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz,
                                        metaConstraints, defaultSequenceMember );
                            }
                            if ( !validationSuccessful ) {
                                break;
                            }
                        }
                    }
                }
                // fast path in case the default group sequence hasn't been redefined
                else {
                    Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
                    validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
                            Group.DEFAULT_GROUP );
                }
    
                validationContext.markCurrentBeanAsProcessed( valueContext );
    
                // all constraints in the hierarchy has been validated, stop validation.
                if ( defaultGroupSequenceIsRedefined ) {
                    break;
                }
            }
        }
    

    这下应该就一目了然了,如果这个 class 被重新定义成 default group 那么遍历所有的这个类的层次结构。然后获取到这个类相关的所有MetaConstraint,传给下一个方法validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints, defaultSequenceMember );。看MetaConstraint这个类可知,它是对应的一种约束,比如 Email, String等等。

    private <U> boolean validateConstraintsForSingleDefaultGroupElement(ValidationContext<?> validationContext, ValueContext<U, Object> valueContext, final Map<Class<?>, Class<?>> validatedInterfaces,
                Class<? super U> clazz, Set<MetaConstraint<?>> metaConstraints, Group defaultSequenceMember) {
            boolean validationSuccessful = true;
    
            valueContext.setCurrentGroup( defaultSequenceMember.getDefiningClass() );
    
            for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
                // HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
                // time. An interface can define more than one constraint, we have to check the class we are validating.
                final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
                if ( declaringClass.isInterface() ) {
                    Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
                    if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
                        continue;
                    }
                    validatedInterfaces.put( declaringClass, clazz );
                }
    
                boolean tmp = validateMetaConstraint( validationContext, valueContext, valueContext.getCurrentBean(), metaConstraint );
                if ( shouldFailFast( validationContext ) ) {
                    return false;
                }
    
                validationSuccessful = validationSuccessful && tmp;
            }
            return validationSuccessful;
        }
    

    validateConstraintsForSingleDefaultGroupElement()方法,了解方法主要是遍历metaConstraints,每个metaConstraint都对这个 group 进行一次校验。

    往下读得知,它先是逐步创建约束对应的 constraintTree ,每个 constraintTree 用一个工厂类创建了 ConstraintValidator<A, ?> ,然后调用 isValid()方法,完成了校验。最后如果校验失败了,将不符合约束的地方写到了ValidationContext<T>中。每个constraintTree会校验目标对象的所有指定约束的属性。比如@Email

    protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
                ValueContext<?, ?> valueContext,
                ConstraintValidatorContextImpl constraintValidatorContext,
                ConstraintValidator<A, V> validator) {
            boolean isValid;
            try {
                @SuppressWarnings("unchecked")
                V validatedValue = (V) valueContext.getCurrentValidatedValue();
                isValid = validator.isValid( validatedValue, constraintValidatorContext );
            }
            catch (RuntimeException e) {
                if ( e instanceof ConstraintDeclarationException ) {
                    throw e;
                }
                throw LOG.getExceptionDuringIsValidCallException( e );
            }
            if ( !isValid ) {
                //We do not add these violations yet, since we don't know how they are
                //going to influence the final boolean evaluation
                return executionContext.createConstraintViolations(
                        valueContext, constraintValidatorContext
                );
            }
            return Collections.emptySet();
        }
    

    总结一下校验方法做了这些事:

    1. 如果传 groups ,就根据 groups 获取 group sequence,否则就是获取 default group sequence,也就是所有属性都要遍历都要校验。
    2. 每个 group 都进行一次 metaConstraints 的遍历,metaConstraint 对应一种约束,比如 Email 类型,或者 String 类型等等
    3. metaConstraint 拿到 constraintTree,然后创建 Validator,进行校验,如果校验不通过写到 ValidationContext 中
    4. 从 ValidationContext 中可以获取到校验不通过的所有信息

    大概的校验机制就是这些了。

    总结

    读源码经常会被一层又一层的调用难倒,以我浅薄的经验,读源码首先要明确要读的是什么模块什么功能,带着目的性读源码,然后多从方法名和变量名去理解含义,多看类和接口的注释。逐步看懂细节再看整体,这样比较能对整个功能模块有全面的了解。

    相关文章

      网友评论

        本文标题:人人快速开发平台 renren-fast 源码分析(二)异常处理

        本文链接:https://www.haomeiwen.com/subject/nlmqgftx.html