SpringMVC拓展

### 原生SpringMVC有如下缺陷

  1. 参数的JSON反序列化只支持@RequestBody注解,这意味着不能在controller方法中写多个参数,如下代码是不对的
    public Map test(@RequestBody User user, @RequestBody Role role)
  2. 如下代码,User和Role类都有id字段。假如请求方式是默认的x-www-form-urlencoded格式,那么请求test方法的http请求实体将类似于id=1234&userName=uuuu&roleName=rrrrr,但实际上我们希望传两个id且代表不同的意思。另外,如果User中有School类的成员,School类中有schoolName字段,那么请求参数仅会变成id=1234&userName=uuuu&roleName=rrrrr&&schoolName=ssss。可见这种参数绑定方式有歧义而且没有属性的层级概念
    ```
    public class User {
    private int id;
    private String userName;
    ...
    }

    public class Role {
    private int id;
    private String roleName;
    ...
    }

    // controller的方法
    public Map test(User user, Role role)
    ```
  3. JSON参数绑定不能支持Map参数。如下代码中的userMap将会被SpringMVC初始化成BindingAwareModelMap,而不是前端传过来的JSON对象
    public Map test(@RequestParam Map<String, User> userMap)
  4. 参数校验不支持原始类型、原始类型的包装类。如下代码的校验不会起作用,原因是SpringMVC是对方法参数遍历,分别对每一个参数用适配的参数解析器把request中的parameter转换成参数,在参数解析器中只能知道当前的参数类型和HttpRequest,无法得到test方法的第N个参数带有什么注解
    public Map test(@Valid @Range(min=1, max=10) Integer limit)

### 对上述缺陷的想法

  1. 可以用增加一个参数解析器去解析@JsonParam注解,请求格式第一层是x-www-form-urlencode,第二层是JSON格式,如下例子(未进行encode)使得请求参数不具有歧义、且有属性有层次
    user={"id": 123, "userName": "abc"}&role={"id": 456, "roleName": "admin"}
  2. SpringMVC默认使用RequestMappingHandlerAdapter去初始化24个参数解析器,把我们拓展的自定义参数解析器排在后面,并且不允许修改顺序。这就导致了MapMethodProcessor比我们的解析器先处理Map参数,返回了ModelMap。因此不能通过增加参数解析器去处理无法传入Map参数的问题。
  3. Spring在初始化一个bean后,在放入BeanFactory前,首先会让BeanPostProcessor对bean进行后处理,同时RequestMappingHandlerAdapter可以通过setArgumentResolvers方法覆盖默认的参数解析器,因此我们可以新建一个后置Bean处理器去处理RequestMappingHandlerAdapter,首先通过getArgumentResolvers拿到参数解析器列表,然后在列表的最前面加上我们自定义的参数解析器,使得我们的参数解析器比MapMethodProcessor要先处理,这样就能解析转换Map参数
  4. JSR-303规范中定义了两个接口,一个是javax.validation.Validator,一个是javax.validation.executable.ExecutableValidator,其中Validator能对一个实体bean进行校验,ExecutableValidator能够对一个方法中的所有带有@Valid注解的参数进行校验,SpringMVC的参数解析器使用的是Validator。为了解决缺陷4,可以使用切面对Controller的方法进行处理,在调用方法之前,先用ExecutableValidator对方法参数进行处理。注意:如果使用Tomcat容器环境,则需要把切面定义在SpringMVC的配置文件中,而不是配置Spring的配置文件中,它们的上下文是不同的,Spring读不到SpringMVC的bean。Spring boot没有这个问题。

### 实现代码

json参数注解:

package com.baidu.waimai.springext;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonParam {
    String value();

    boolean required() default true;

    String defaultValue() default "";
}

@JsonParam的参数解析器,参考自org.springframework.web.method.annotation.ModelAttributeMethodProcessor,使用FastJson来做参数转换,做了实体bean的参数校验:

package com.baidu.waimai.springext;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.ServletRequest;
import java.lang.annotation.Annotation;
import java.util.Map;

public class JsonArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        JsonParam jsonParam = parameter.getParameterAnnotation(JsonParam.class);
        return jsonParam != null;
    }

    protected Object resolveName(String name, MethodParameter methodParameter, NativeWebRequest nativeWebRequest)
            throws Exception {
        String[] paramValues1 = nativeWebRequest.getParameterValues(name);
        if (paramValues1 != null) {

            return paramValues1.length == 1
                    ? JSON.parseObject(
                        paramValues1[0], methodParameter.getNestedGenericParameterType(), Feature.OrderedField)
                    : paramValues1;
        }
        return null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        JsonParam jsonParam = parameter.getParameterAnnotation(JsonParam.class);
        Object target = resolveName(jsonParam.value(), parameter, webRequest);

        WebDataBinder binder = binderFactory.createBinder(webRequest, target, jsonParam.value());
        if (binder.getTarget() != null) {
            bindRequestParameters(binder, webRequest);
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }

    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
        servletBinder.bind(servletRequest);
    }

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
        Annotation[] annotations = methodParam.getParameterAnnotations();
        for (Annotation ann : annotations) {
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
            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;
            }
        }
    }

    protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
        int i = methodParam.getParameterIndex();
        Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
        boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
        return !hasBindingResult;
    }
}

参数校验切面:

package com.baidu.waimai.springext;

import com.baidu.waimai.validator.ParameterBindingResult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;


@Aspect
@Component
public class ControllerParameterValidateAspect {

    @Autowired
    private Validator validator; // 需要注入ExtendValidator

    @Pointcut("execution(public * com.baidu.waimai.controller.*.*(..))")
    private void anyControllerMethod() {}//定义一个切入点

    @Around("anyControllerMethod()")
    public Object validateParameters(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        // 只有MethodSignature才能获取到方法,才能获取参数列表中的注解
        if (validator != null && validator instanceof ExecutableValidator &&
            joinPoint.getSignature() instanceof MethodSignature) {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

            ParameterBindingResult errors = new ParameterBindingResult(
                joinPoint.getTarget(), method, args, joinPoint.getTarget().toString());

            new ExtendSpringValidatorAdapter(validator)
                .validateParameters(errors, joinPoint.getTarget(), method, args);

            if (errors.hasErrors()) {
                throw new BindException(errors);
            }
        }

        return joinPoint.proceed(args);
    }

    private static class ExtendSpringValidatorAdapter extends SpringValidatorAdapter {
        public ExtendSpringValidatorAdapter(Validator targetValidator) {
            super(targetValidator);
        }

        public <T> void validateParameters(Errors errors, T obj, Method method, Object[] params, Class... classes) {
            ExecutableValidator validator = (ExecutableValidator)unwrap(Validator.class);
            Set<ConstraintViolation<Object>> constraintViolations =
                validator.validateParameters(obj, method, params, classes);
            processConstraintViolations(constraintViolations, errors);
        }
    }
}

拓展校验器,让LocalValidatorFactoryBean拥有ExecutableValidator的功能:

package com.baidu.waimai.springext;

import org.springframework.validation.Errors;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.ConstraintViolation;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;


public class ExtendValidator extends LocalValidatorFactoryBean implements ExecutableValidator {

    @Override
    protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
        super.processConstraintViolations(violations, errors);
    }

    @Override
    public void validate(Object target, Errors errors, Object... validationHints) {
        if (target instanceof Collection) {
            validateCollectionHints(((Collection) target), errors, validationHints);
        } else {
            super.validate(target, errors, validationHints);
        }
    }

    @Override
    public <T> Set<ConstraintViolation<T>> validateParameters(T t, Method method, Object[] objects, Class<?>... classes) {
        if (getValidator() instanceof ExecutableValidator) {
            return ((ExecutableValidator) getValidator()).validateParameters(t, method, objects, classes);
        }
        return Collections.emptySet();
    }

    @Override
    public <T> Set<ConstraintViolation<T>> validateReturnValue(T t, Method method, Object o, Class<?>... classes) {
        if (getValidator() instanceof ExecutableValidator) {
            return ((ExecutableValidator) getValidator())
                .validateReturnValue(t, method, o, classes);
        }
        return Collections.emptySet();
    }

    @Override
    public <T> Set<ConstraintViolation<T>> validateConstructorParameters(Constructor<? extends T> constructor, Object[] objects, Class<?>... classes) {
        if (getValidator() instanceof ExecutableValidator) {
            return ((ExecutableValidator) getValidator())
                .validateConstructorParameters(constructor, objects, classes);
        }
        return Collections.emptySet();
    }

    @Override
    public <T> Set<ConstraintViolation<T>> validateConstructorReturnValue(Constructor<? extends T> constructor, T t, Class<?>... classes) {
        if (getValidator() instanceof ExecutableValidator) {
            return ((ExecutableValidator) getValidator())
                .validateConstructorReturnValue(constructor, t, classes);
        }
        return Collections.emptySet();
    }

    private void validateCollectionHints(Collection collection, Errors errors, Object... validationHints) {
        if (collection != null) {
            for (Object item : collection) {
                validate(item, errors, validationHints);
            }
        }
    }
}

增加一个参数绑定结果:

package com.baidu.waimai.springext;

import org.springframework.validation.AbstractBindingResult;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterBindingResult extends AbstractBindingResult {
    private Object target;
    private Method method;
    private Object[] args;

    public ParameterBindingResult(Object target, Method method, Object[] args, String targetName) {
        super(targetName);
        this.target = target;
        this.method = method;
        this.args = args;
    }

    @Override
    public Object getTarget() {
        return target;
    }

    @Override
    protected Object getActualFieldValue(String field) {
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = method.getParameters()[i];
            if (parameter.getName().equals(field)) {
                return args[i];
            }
        }
        return null;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }
}

后置bean处理器:

package com.baidu.waimai.springext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.LinkedList;
import java.util.List;


@Component
public class RequestMappingHandlerAdapterPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
            RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
            List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();
            List<HandlerMethodArgumentResolver> newArgumentResolvers =
                processHandlerMethodArgumentResolvers(argumentResolvers);
            if (newArgumentResolvers != null) {
                adapter.setArgumentResolvers(newArgumentResolvers);
            }
        }
        return bean;
    }

    protected List<HandlerMethodArgumentResolver> processHandlerMethodArgumentResolvers(
            List<HandlerMethodArgumentResolver> argumentResolvers) {
        List<HandlerMethodArgumentResolver> newArgumentResolvers = new LinkedList<>(argumentResolvers);

        newArgumentResolvers.add(0, new JsonArgumentResolver()); // 关键,插入在前面,使得带有@JsonParam的参数被优先处理
        return newArgumentResolvers;
    }
}

异常处理器:

package com.baidu.waimai.springext;

import org.springframework.core.annotation.Order;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;


@Order(1) // 排序要在SpringMvc默认异常处理器的前面
public class BindExceptionHandler implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest req, HttpServletResponse resp, Object o, Exception e) {
        if (e instanceof BindException) {
            return resolveBindException(e);
            // 在Controller的方法加上切面时抛出的异常是UndeclaredThrowableException
            // 需要判断里面的是不是BindException
        } else if (e instanceof UndeclaredThrowableException) {
            Throwable undeclaredThrowable = ((UndeclaredThrowableException) e).getUndeclaredThrowable();
            if (undeclaredThrowable instanceof BindException) {
                return resolveBindException(undeclaredThrowable);
            }
        }
        return null;
    }

    private ModelAndView resolveBindException(Throwable e) {
        BindException be = (BindException) e;
        List<ObjectError> errors = be.getBindingResult().getAllErrors();
        ObjectError oe = errors.get(0);
        ModelAndView mav = new ModelAndView();
        BindExceptionJsonView view = new BindExceptionJsonView();
        mav.setView(view);
        mav.addObject("Exception", e);
        mav.addObject("ObjectError", oe);
        return mav;
    }
}

绑定异常的ModelAndView:

package com.baidu.waimai.springext;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baidu.waimai.util.ResponseUtil;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.view.AbstractView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;


public class BindExceptionJsonView extends AbstractView {

    @Override
    protected void renderMergedOutputModel(
            Map<String, Object> context, HttpServletRequest req, HttpServletResponse resp) throws Exception {

        String OBJECT_ERROR = "ObjectError";

        resp.setContentType(ResponseUtil.JSON_CONTENT_TYPE);
        PrintWriter out = resp.getWriter();
        Map map = new LinkedHashMap();
        ObjectError oe = (ObjectError) context.get(OBJECT_ERROR);

        map.put(ResponseUtil.STATUS, 0);
        map.put(ResponseUtil.MSG, "参数校验错误");
        map.put(OBJECT_ERROR, oe);
        out.print(JSON.toJSONString(map, SerializerFeature.PrettyFormat, SerializerFeature.SortField));
    }
}

转载于:https://www.cnblogs.com/lanhj/p/6405258.html

狂神springmvc代码指的是由李仁密老师在其视频教程中讲解的一套完整的Spring MVC项目代码,该代码涵盖了Spring MVC框架的各个功能模块,以及常用的Web开发技术和工具,具有实用性和参考价值。下面从代码结构、特点、使用和拓展等方面进行详述。 代码结构: 狂神springmvc代码采用Maven作为项目管理工具,以标准的MVC架构模式设计,目录结构清晰,分为java和resources两个目录。 Java目录下分为controller、dao、entity、service和util等包,各自对应了Spring MVC框架中Controller、Dao、Entity、Service和常用的工具类,这些包下又分别包含了相应的类和接口。Resources目录下主要是Spring MVC框架的配置文件,包含了web.xml、spring.xml、mybatis.xml等等,其中spring.xml是整个应用程序的核心配置文件,配置了包括数据源、transaction manager、各种bean和拦截器在内的所有Spring MVC组件。 特点: 1. 视频教程详细:狂神springmvc代码是通过李仁密老师的视频教程带领学生逐步完成的,在代码编写方面考虑较为全面和严谨。 2. 适用性强:狂神springmvc代码包含了Spring MVC框架的基本功能和高级特性,支持Restful结构,便于实现独立的业务模块。 3. 良好的开放性:代码分层清晰,各层之间耦合度低,便于后期的升级和扩展。 4. 数据持久化采用mybatis:狂神springmvc代码采用了mybatis框架作为持久化层,支持动态sql语句和对象映射,具有较好的可维护性和可扩展性。 5. 模块化设计:狂神springmvc代码实现了模块化设计,便于分组和管理,结构紧凑。 使用: 使用狂神springmvc代码需要使用Maven作为项目管理工具,以及Java、Spring、mybatis等开发框架,需要对这些工具和框架有一定的了解和掌握。在代码使用过程中,可以根据实际的需求进行自定义修改和调整,便于实现个性化的业务需求。 拓展: 狂神springmvc代码为学习者提供了一种全面学习Spring MVC框架的方式,开发者可以根据自己的需求对代码进行维护和拓展。如果需要调整和扩展功能,可以在原代码基础上进行二次开发,例如增加更多的业务模块、改进用户体验、优化性能等等。总而言之,狂神springmvc代码是一套具有实战价值和参考价值的完整的Spring MVC项目代码,适合需要学习或使用Spring MVC框架的开发人员。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值