理解并手写Spring MVC框架

一、前言

Spring框架是大多Java程序员的必修课程,而SpringMVC是里面的重头戏,它大大的简化了Servlet的繁琐操作,让开发人员得以用更多的时间去处理业务。

SpringMVC是一款经典的三层架构模式,M为Model(模型),V为View(视图),C为Controlle(控制器)。MVC是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

二、SpringMVC流程

流程简述:

(1)用户通过浏览器或其他途径发起请求。

(2)请求经过前端控制器DispatcherServlet,并根据请求的URI匹配到对应的处理映射器。

(3)处理映射器返回HandlerExecutionChain实体给DispatcherServlet,内部封装了拦截器HandlerInterceptor、Object类型的具体处理器handler。

(4)系统启动时,初始化了HandlerAdapter的List的集合,此步骤循环List集合,调用HandlerAdapter的support方法,找到handler支持的HandlerAdapter处理适配器。

(5)由HandlerAdapter处理参数并调用处理器的方法。

(6)返回结果是ModelAndView,即视图。其内部封装了view、model、status。

(7)将处理得到的ModelAndView返回给DispatcherServlet。

(8)将ModelAndView交由专业的视图解析器进行解析,得到页面位置。

(9)返回结果给DispatcherServlet。

(10)将数据渲染到页面。

(11)响应请求。

源码核心部分概览

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}
  • initMultipartResolver(context)

初始化文件上传解析功能,将普通HttpServletRequest包装成MutipartHttpServletRequest,此类可通过getFile获取到文件集合。

  • initLocaleResolver(context)

初始化与当地区域有关的设置,如视图解析器与国际化资源的配置。

  • initThemeResolver(context)

初始化主题解析,如消息国际话得配置,每个主题对应 一个properties文件。

  • initHandlerMappings(context)

初始化HandlerMappings,用来获取对应的handler与interceptor。

  • initHandlerAdapters(context)

初始化处理适配器,用来执行具体方法。

  • initHandlerExceptionResolvers(context)

对异常情况进行处理。

  • initRequestToViewNameTranslator(context)

从请求中获取视图名称。

  • initViewResolvers(context)

初始化视图解析器,将ModelAndView渲染成页面。

  • initFlashMapManager(context)

主要用处在于传递重定向参数。

三、手写Spring MVC

项目目录

源码地址:

https://github.com/hanguilin/custom-springmvc

环境配置:

maven项目中引入Servlet需要的jar包

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>

其他jar包(本人写代码用到的一些包)

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

注解类:

建立SpringMVC中常用注解@Controller、@RequstMapping、@Autowired、@Qualifier、@Service、@RequestParam的替代注解。区别为在自己的注解中给名字添加了Custom前缀。各个注解的区别在于注解地方,如类、方法、字段上不同。

CustomController.java

package com.hgl.mvc.annotation;

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

/**
 * 控制层注解
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface CustomController {

    String value() default "";
}

CustomRequestMapping.java

package com.hgl.mvc.annotation;

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

/**
 * 请求路径注解
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface CustomRequestMapping {

    String value();
}

CustomAutowired.java

package com.hgl.mvc.annotation;

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

/**
 * 自动注入
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface CustomAutowired {

    String value() default "";
}

CustomQualifer.java

package com.hgl.mvc.annotation;

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

/**
 * 按名称自动注入
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface CustomQualifer {

    String value();
}

image.gif

CustomService.java

package com.hgl.mvc.annotation;

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

/**
 * 业务层注解
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface CustomService {

    String value() default "";
}

CustomRequestParam.java

package com.hgl.mvc.annotation;

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

/**
 * 参数注解
 * @author guilin
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.PARAMETER)
public @interface CustomRequestParam {

    String value() default "";
}

配置文件

在classpath下建立spring-mvc.properties,指定扫描包路径

spring.scanner.base.package=com.hgl

核心配置类

SpringMVC核心配置类DispatcherServlet.java,替代类为CustomDispatcherServlet.java

package com.hgl.mvc.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hgl.mvc.annotation.CustomController;
import com.hgl.mvc.annotation.CustomQualifer;
import com.hgl.mvc.annotation.CustomRequestMapping;
import com.hgl.mvc.annotation.CustomService;
import com.hgl.mvc.resolver.ArgumentResolver;

public class CustomDispatcherServlet extends HttpServlet{

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);

    private static final long serialVersionUID = 1L;

    private Properties contextConfig = new Properties();

    // 所有扫描类
    private List<String> classes = new ArrayList<String>();

    // 存放bean的容器ioc
    private Map<String, Object> context = new HashMap<String, Object>();

    // 存放参数解析器
    private Map<String, ArgumentResolver> argumentResolverMap = new HashMap<String, ArgumentResolver>();

    // 根据请求url找到具体的处理器
    private List<CustomHandlerMapping> handlerMapping = new ArrayList<CustomHandlerMapping>();

    private List<CustomHandlerAdapter> handlerAdapter = new ArrayList<CustomHandlerAdapter>();

    public CustomDispatcherServlet() {
        LOGGER.info("CustomDispatcherServlet()...");
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 加载配置文件
        initConfig(config.getInitParameter("spring-mvc"));
        // 扫描类
        initBaseScanPackage(contextConfig.getProperty("spring.scanner.base.package"));
        // 生成bean实例,注入ioc
        initContext();
        // 初始化参数解析器
        initArgumentResolver();
        // 为controller层中service对象注入实例
        initInstance();
        // 建立URI与处理器的映射
        initHandlerMapping();
        // 处理器适配器
        initHandlerAdapter();
    }

    private void initConfig(String initParameter) {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(initParameter);
        try {
            contextConfig.load(in);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            if(in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    LOGGER.error(e.getMessage(), e);
                }
            }
        }
    }

    private void initBaseScanPackage(String basePackage) {
        URL resource = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
        String packagePath = resource.getFile();
        File packageFile = new File(packagePath);
        String[] listFiles = packageFile.list();
        for (String filepPath : listFiles) {
            File file = new File(packagePath + filepPath);
            if(file.isDirectory()) {
                initBaseScanPackage(basePackage + "." + filepPath);
            }else {
                classes.add(basePackage + "." + file.getName());
            }
        }
    }

    private void initContext() {
        if(classes.isEmpty()) {
            LOGGER.error("do scan failed.");
            return;
        }
        for (String className : classes) {
            String classPath = className.substring(0, className.lastIndexOf(".class"));
            try {
                Class<?> clazz = Class.forName(classPath);
                String simpleName = clazz.getSimpleName();
                if(clazz.isAnnotationPresent(CustomController.class)) {
                    CustomController controller = clazz.getAnnotation(CustomController.class);
                    String key = controller.value();
                    if(StringUtils.isBlank(key)) {
                        key = toLowerCaseFirstOne(simpleName);
                    }
                    Object instance = clazz.newInstance();
                    context.put(key, instance);
                } else if(clazz.isAnnotationPresent(CustomService.class)) {
                    CustomService service = clazz.getAnnotation(CustomService.class);
                    String key = service.value();
                    if(StringUtils.isBlank(key)) {
                        key = toLowerCaseFirstOne(simpleName);
                    }
                    Object instance = clazz.newInstance();
                    context.put(key, instance);
                } else {
                    continue;
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
    }

    private void initArgumentResolver() {
        if(context.isEmpty()) {
            return;
        }
        for(Map.Entry<String, Object> entry : context.entrySet()) {
            Object bean = entry.getValue();
            // 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
            if(ArgumentResolver.class.isAssignableFrom(bean.getClass())) {
                argumentResolverMap.put(entry.getKey(), (ArgumentResolver) bean);
            }
        }
    }

    private void initInstance() {
        if(context.isEmpty()) {
            LOGGER.error("no bean is instanced.");
            return;
        }
        for(Map.Entry<String, Object> entry : context.entrySet()) {
            Object bean = entry.getValue();
            Class<? extends Object> clazz = bean.getClass();
            if(clazz.isAnnotationPresent(CustomController.class)) {
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field field : declaredFields) {
                    if(field.isAnnotationPresent(CustomQualifer.class)) {
                        CustomQualifer qualifer = field.getAnnotation(CustomQualifer.class);
                        String beanName = qualifer.value();
                        Object value = context.get(beanName);
                        try {
                            if(!field.isAccessible()) {
                                field.setAccessible(true);
                            }
                            field.set(bean, value);
                        } catch (IllegalArgumentException | IllegalAccessException e) {
                            LOGGER.error(e.getMessage(), e);
                        }
                    }
                }
            }
        }
    }

    private void initHandlerMapping() {
        if(context.isEmpty()) {
            LOGGER.error("no bean is instanced.");
            return;
        }
        for(Map.Entry<String, Object> entry : context.entrySet()) {
            Object bean = entry.getValue();
            Class<? extends Object> clazz = bean.getClass();
            if(clazz.isAnnotationPresent(CustomController.class)) {
                String classRequestMappingVal = "";
                if(clazz.isAnnotationPresent(CustomRequestMapping.class)) {
                    CustomRequestMapping classRequestMapping = clazz.getAnnotation(CustomRequestMapping.class);
                    classRequestMappingVal = classRequestMapping.value();
                }
                Method[] declaredMethods = clazz.getDeclaredMethods();
                List<String> uris = new ArrayList<String>();
                for (Method method : declaredMethods) {
                    String methodRequestMappingVal = "";
                    if(method.isAnnotationPresent(CustomRequestMapping.class)) {
                        CustomRequestMapping methodRequestMapping = method.getAnnotation(CustomRequestMapping.class);
                        methodRequestMappingVal = classRequestMappingVal + methodRequestMapping.value();
                    }
                    if(StringUtils.isNotBlank(methodRequestMappingVal)) {
                        if(uris.contains(methodRequestMappingVal)) {
                            throw new RuntimeException("Duplicate mapping for " + methodRequestMappingVal);
                        }
                        handlerMapping.add(new CustomHandlerMapping(bean, method, Pattern.compile(methodRequestMappingVal)));
                        uris.add(methodRequestMappingVal);
                    }
                }
                uris = null;
            }
        }
    }

    private void initHandlerAdapter() {
        if(context.isEmpty()) {
            LOGGER.error("no bean is instanced.");
            return;
        }
        for(Map.Entry<String, Object> entry : context.entrySet()) {
            Object bean = entry.getValue();
            // 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口
            if(CustomHandlerAdapter.class.isAssignableFrom(bean.getClass())) {
                handlerAdapter.add((CustomHandlerAdapter) bean);
            }
        }
    }

    private String toLowerCaseFirstOne(String s){
        if(Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        CustomHandlerMapping handler = getHandler(request);
        CustomHandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        CustomModelAndView modelAndView = handlerAdapter.handle(request, response, handler, argumentResolverMap);
    }

    private CustomHandlerAdapter getHandlerAdapter(CustomHandlerMapping handler) {
        for (CustomHandlerAdapter customHandlerAdapter : handlerAdapter) {
            if(customHandlerAdapter.support(handler)) {
                return customHandlerAdapter;
            }
        }
        throw new RuntimeException("There is no handlerAdapter for " + handler);
    }

    private CustomHandlerMapping getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String path = requestURI.replaceAll(request.getContextPath(), "");
        for (CustomHandlerMapping handler : handlerMapping) {
            Pattern pattern = handler.getPattern();
            Matcher matcher = pattern.matcher(path);
            if(matcher.matches()) {
                return handler;
            }
        }
        throw new RuntimeException("There is no mapping for " + path);
    }

}

处理映射器

CustomHandlerMapping.java

package com.hgl.mvc.servlet;

import java.lang.reflect.Method;
import java.util.regex.Pattern;

/**
 * 自定义handlerMapping
 * 
 * @author guilin
 *
 */
public class CustomHandlerMapping{

    /**
     * controller对应的bean
     */
    private Object controller;

    /**
     * 具体处理方法
     */
    private Method method;

    /**
     * 用来验证是否是当前url对应的处理方法
     */
    private Pattern pattern;

    public CustomHandlerMapping(Object controller, Method method, Pattern pattern) {
        super();
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }
}

处理适配器

CustomHandlerAdapter.java,作为接口,由实现类实现。

package com.hgl.mvc.servlet;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hgl.mvc.resolver.ArgumentResolver;

/**
 * @author guilin
 * 自定义适配器
 *
 */
public interface CustomHandlerAdapter {

    /**
     * 是否支持处理
     * 是则调用本类handle方法
     * 
     * @param handler 处理器
     * @return boolean
     */
    public boolean support(Object handler);

    /**
     * 具体处理方法
     * 
     * @param request
     * @param response
     * @param handler
     * @param argumentResolverMap
     * @return CustomModelAndView
     */
    public CustomModelAndView handle(HttpServletRequest request, HttpServletResponse response, CustomHandlerMapping handler,
            Map<String, ArgumentResolver> argumentResolverMap);

}

实现类CustomSimpleHandlerAdapter.java

package com.hgl.mvc.servlet;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hgl.mvc.annotation.CustomService;
import com.hgl.mvc.resolver.ArgumentResolver;

/**
 * 对处理适配器的实现
 * 
 * @author guilin
 *
 */
@CustomService("customSimpleHandlerAdapter")
public class CustomSimpleHandlerAdapter implements CustomHandlerAdapter{

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);

    @Override
    public CustomModelAndView handle(HttpServletRequest request, HttpServletResponse response, CustomHandlerMapping handler,
            Map<String, ArgumentResolver> argumentResolverMap) {
        Method method = handler.getMethod();
        Object controller = handler.getController();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] args = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterClass = parameterTypes[i];
            for (Map.Entry<String, ArgumentResolver> entry : argumentResolverMap.entrySet()) {
                ArgumentResolver argumentResolver = entry.getValue();
                if(argumentResolver.support(parameterClass, i, method)) {
                    Object resolver = argumentResolver.argumentResolver(request, response, parameterClass, i, method);
                    args[i] = resolver;
                    break;
                }
            }
        }
        try {
            method.invoke(controller, args);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return new CustomModelAndView();
    }

    @Override
    public boolean support(Object handler) {
        // 暂定实现为true
        return true;
    }

}

参数解析器

ArgumentResolver.java,作为上层接口,具体由实现类实现。

package com.hgl.mvc.resolver;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 参数解析器
 * @author guilin
 *
 */
public interface ArgumentResolver {

    public boolean support(Class<?> type, int paramIndex, Method method);

    public Object argumentResolver(HttpServletRequest request, HttpServletResponse response,
            Class<?> type, int paramIndex, Method method);
}

HttpServletRequestArgumentResolver.java,用于解析HttpServletRequest参数

package com.hgl.mvc.resolver;

import java.lang.reflect.Method;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hgl.mvc.annotation.CustomService;

/**
 * HttpServletRequest参数解析器
 * @author guilin
 *
 */
@CustomService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {

    @Override
    public boolean support(Class<?> type, int paramIndex, Method method) {
        return ServletRequest.class.isAssignableFrom(type);
    }

    @Override
    public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
            int paramIndex, Method method) {
        return request;
    }

}

HttpServletResponseArgumentResolver.java,用于解析HttpServletResponse参数

package com.hgl.mvc.resolver;

import java.lang.reflect.Method;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hgl.mvc.annotation.CustomService;

/**
 * HttpServletResponse参数解析器
 * @author guilin
 *
 */
@CustomService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {

    @Override
    public boolean support(Class<?> type, int paramIndex, Method method) {
        return ServletResponse.class.isAssignableFrom(type);
    }

    @Override
    public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
            int paramIndex, Method method) {
        return response;
    }

}

RequestParamArgumentResolver.java,用于解析CustomRequestParam定义的参数

package com.hgl.mvc.resolver;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hgl.mvc.annotation.CustomRequestParam;
import com.hgl.mvc.annotation.CustomService;

/**
 * RequestParam参数解析器
 * @author guilin
 *
 */
@CustomService("requestParamArgumentResolver")
public class RequestParamArgumentResolver implements ArgumentResolver {

    @Override
    public boolean support(Class<?> type, int paramIndex, Method method) {
        Annotation[][] annotations = method.getParameterAnnotations();
        Annotation[] currentField = annotations[paramIndex];
        for (Annotation annotation : currentField) {
            if(CustomRequestParam.class.isAssignableFrom(annotation.getClass())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type,
            int paramIndex, Method method) {
        Annotation[][] annotations = method.getParameterAnnotations();
        Annotation[] currentField = annotations[paramIndex];
        for (Annotation annotation : currentField) {
            if(CustomRequestParam.class.isAssignableFrom(annotation.getClass())) {
                CustomRequestParam requestParam = (CustomRequestParam) annotation;
                String parameterName = requestParam.value();
                String parameterVal = request.getParameter(parameterName);
                return parameterVal;
            }
        }
        return null;
    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355