前言

SpringMVC简介

SpringMVC 是一个类似于 Struts2 表现层的框架,属于 SpringFramework 的后续产品。

学习SpringMVC的原因

SpringMVC 与 Struts2 的区别
对比项目 SpringMVC Struts2 优势
国内市场情况 有大量用户,一般新项目启动都会选用 SpringMVC。 有部分老用户,老项目组,由于习惯了,一直在使用。 国内情况,SpringMVC 的使用率已经超过了 Struts2。
框架入口 基于 Servlet。 基于 Filter。 本质上没有太大优势,只是配置方式不同。
框架设计思想 控制器基于方法级别的拦截,处理器设计为单实例。 控制器基于类级别的拦截,处理器设计为多实例。 由于设计本身原因,造成了 Struts2 通常只能设计为多实例模式,相比于 SpringMVC 设计为单实例模式,Struts2 会消耗更多内存。
参数传递 参数通过方法入参传递。 参数通过类的成员变量传递。 Struts2 通过成员变量传递参数,导致了参数的线程不安全,有可能引发并发问题。
与 Spring 整合 与 Spring 属于同一家公司开发,可与 Spring 无缝整合。 需要整合包。 SpringMVC 可以与 Spring 更轻松的整合。

SpringMVC架构图

使用

入门程序

1、创建 maven web 工程,引入如下依赖:

<?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>

    <groupId>com.zze.springmvc</groupId>
    <artifactId>springmvc_test1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--使用 tomcat7:run-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

pom.xml

2、创建 SpringMVC 核心配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置控制器扫描-->
    <context:component-scan base-package="com.zze.springmvc.web.controller"/>

    <mvc:annotation-driven/>
</beans>

springmvc.xml

3、配置 SpringMVC 核心监听 Servlet 并加载核心配置文件:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--加载 SpringMVC 核心配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--注意:这里使用 / ,如果使用 /* 会出现找不到视图的问题-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

WEB-INF/web.xml

4、创建测试用的 pojo:

package com.zze.springmvc.pojo;

import java.util.Date;

public class User {
    private Integer id;
    private String name;
    private Date birthday;
    private String address;

    public User(Integer id, String name, Date birthday, String address) {
        this.id = id;
        this.name = name;
        this.birthday = birthday;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

com.zze.springmvc.pojo.User

5、创建视图:

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<head>
    <title>list</title>
</head>
<body>

<table border="1">
    <thead>
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Birthday</th>
        <th>Address</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td><fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/></td>
            <td>${user.address}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

WEB-INF/jsp/userlist.jsp

6、创建控制器:

package com.zze.springmvc.web.controller;

import com.zze.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

@Controller
public class TestController {
    @RequestMapping("list")
    public ModelAndView list() throws ParseException {
        List<User> userList = new ArrayList<>();
        userList.add(new User(1, "张三", new SimpleDateFormat("yyyy-MM-dd").parse("1997-1-12"), "湖南"));
        userList.add(new User(2, "李四", new SimpleDateFormat("yyyy-MM-dd").parse("1995-5-23"), "湖北"));
        userList.add(new User(3, "王五", new SimpleDateFormat("yyyy-MM-dd").parse("1993-2-23"), "常德"));
        userList.add(new User(4, "赵六", new SimpleDateFormat("yyyy-MM-dd").parse("1998-5-6"), "北京"));

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("list", userList);
        modelAndView.setViewName("/WEB-INF/jsp/userlist.jsp");
        return modelAndView;
    }
}

com.zze.springmvc.web.controller.TestController

7、测试:

浏览器:

请求 'localhost:8080/list'

配置相关

处理器适配器和映射器

Spring MVC 有许多默认配置,如下:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
# 处理器映射器
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
# 处理器适配器
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

spring-webmvc-4.2.4.RELEASE.jar!/org/springframework/web/servlet/DispatcherServlet.properties

查看处理器映射器和处理器适配器类可以发现这两个类是已过期的,如下:

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;

import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;

/**
 * Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
 * interface that maps handlers based on HTTP paths expressed through the
 * {@link RequestMapping} annotation at the type or method level.
 *
 * <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
 * on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
 * DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
 * explicitly, since custom HandlerMapping beans replace the default mapping strategies.
 * Defining a DefaultAnnotationHandlerMapping also allows for registering custom
 * interceptors:
 *
 * <pre class="code">
 * &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
 *   &lt;property name="interceptors"&gt;
 *     ...
 *   &lt;/property&gt;
 * &lt;/bean&gt;</pre>
 *
 * Annotated controllers are usually marked with the {@link Controller} stereotype
 * at the type level. This is not strictly necessary when {@link RequestMapping} is
 * applied at the type level (since such a handler usually implements the
 * {@link org.springframework.web.servlet.mvc.Controller} interface). However,
 * {@link Controller} is required for detecting {@link RequestMapping} annotations
 * at the method level if {@link RequestMapping} is not present at the type level.
 *
 * <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
 * expressed at the class level (if any). HTTP paths need to uniquely map onto
 * specific handler beans, with any given HTTP path only allowed to be mapped
 * onto one specific handler bean (not spread across multiple handler beans).
 * It is strongly recommended to co-locate related handler methods into the same bean.
 *
 * <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
 * annotated handler methods, as mapped by this HandlerMapping. For
 * {@link RequestMapping} at the type level, specific HandlerAdapters such as
 * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
 *
 * @author Juergen Hoeller
 * @author Arjen Poutsma
 * @since 2.5
 * @see RequestMapping
 * @see AnnotationMethodHandlerAdapter
 * @deprecated as of Spring 3.2, in favor of
 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping RequestMappingHandlerMapping}
 */
@Deprecated
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {

    static final String USE_DEFAULT_SUFFIX_PATTERN = DefaultAnnotationHandlerMapping.class.getName() + ".useDefaultSuffixPattern";

    private boolean useDefaultSuffixPattern = true;

    private final Map<Class<?>, RequestMapping> cachedMappings = new HashMap<Class<?>, RequestMapping>();

    /**
     * Set whether to register paths using the default suffix pattern as well:
     * i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
     * <p>Default is "true". Turn this convention off if you intend to interpret
     * your {@code @RequestMapping} paths strictly.
     * <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be
     * transformed using the default suffix pattern in any case.
     */
    public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
        this.useDefaultSuffixPattern = useDefaultSuffixPattern;
    }

    /**
     * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
     * annotation on the handler class and on any of its methods.
     */
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        ApplicationContext context = getApplicationContext();
        Class<?> handlerType = context.getType(beanName);
        RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
        if (mapping != null) {
            // @RequestMapping found at type level
            this.cachedMappings.put(handlerType, mapping);
            Set<String> urls = new LinkedHashSet<String>();
            String[] typeLevelPatterns = mapping.value();
            if (typeLevelPatterns.length > 0) {
                // @RequestMapping specifies paths at type level
                String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
                for (String typeLevelPattern : typeLevelPatterns) {
                    if (!typeLevelPattern.startsWith("/")) {
                        typeLevelPattern = "/" + typeLevelPattern;
                    }
                    boolean hasEmptyMethodLevelMappings = false;
                    for (String methodLevelPattern : methodLevelPatterns) {
                        if (methodLevelPattern == null) {
                            hasEmptyMethodLevelMappings = true;
                        }
                        else {
                            String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
                            addUrlsForPath(urls, combinedPattern);
                        }
                    }
                    if (hasEmptyMethodLevelMappings ||
                            org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
                        addUrlsForPath(urls, typeLevelPattern);
                    }
                }
                return StringUtils.toStringArray(urls);
            }
            else {
                // actual paths specified by @RequestMapping at method level
                return determineUrlsForHandlerMethods(handlerType, false);
            }
        }
        else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
            // @RequestMapping to be introspected at method level
            return determineUrlsForHandlerMethods(handlerType, false);
        }
        else {
            return null;
        }
    }

    /**
     * Derive URL mappings from the handler's method-level mappings.
     * @param handlerType the handler type to introspect
     * @param hasTypeLevelMapping whether the method-level mappings are nested
     * within a type-level mapping
     * @return the array of mapped URLs
     */
    protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) {
        String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
        if (subclassResult != null) {
            return subclassResult;
        }

        final Set<String> urls = new LinkedHashSet<String>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
        handlerTypes.add(handlerType);
        handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
        for (Class<?> currentHandlerType : handlerTypes) {
            ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) {
                    RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
                    if (mapping != null) {
                        String[] mappedPatterns = mapping.value();
                        if (mappedPatterns.length > 0) {
                            for (String mappedPattern : mappedPatterns) {
                                if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
                                    mappedPattern = "/" + mappedPattern;
                                }
                                addUrlsForPath(urls, mappedPattern);
                            }
                        }
                        else if (hasTypeLevelMapping) {
                            // empty method-level RequestMapping
                            urls.add(null);
                        }
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
        return StringUtils.toStringArray(urls);
    }

    /**
     * Derive URL mappings from the handler's method-level mappings.
     * @param handlerType the handler type to introspect
     * @return the array of mapped URLs
     */
    protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) {
        return null;
    }

    /**
     * Add URLs and/or URL patterns for the given path.
     * @param urls the Set of URLs for the current bean
     * @param path the currently introspected path
     */
    protected void addUrlsForPath(Set<String> urls, String path) {
        urls.add(path);
        if (this.useDefaultSuffixPattern && path.indexOf('.') == -1 && !path.endsWith("/")) {
            urls.add(path + ".*");
            urls.add(path + "/");
        }
    }

    /**
     * Validate the given annotated handler against the current request.
     * @see #validateMapping
     */
    @Override
    protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
        RequestMapping mapping = this.cachedMappings.get(handler.getClass());
        if (mapping == null) {
            mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class);
        }
        if (mapping != null) {
            validateMapping(mapping, request);
        }
        request.setAttribute(USE_DEFAULT_SUFFIX_PATTERN, this.useDefaultSuffixPattern);
    }

    /**
     * Validate the given type-level mapping metadata against the current request,
     * checking HTTP request method and parameter conditions.
     * @param mapping the mapping metadata to validate
     * @param request current HTTP request
     * @throws Exception if validation failed
     */
    protected void validateMapping(RequestMapping mapping, HttpServletRequest request) throws Exception {
        RequestMethod[] mappedMethods = mapping.method();
        if (!ServletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) {
            String[] supportedMethods = new String[mappedMethods.length];
            for (int i = 0; i < mappedMethods.length; i++) {
                supportedMethods[i] = mappedMethods[i].name();
            }
            throw new HttpRequestMethodNotSupportedException(request.getMethod(), supportedMethods);
        }

        String[] mappedParams = mapping.params();
        if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) {
            throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap());
        }

        String[] mappedHeaders = mapping.headers();
        if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
            throw new ServletRequestBindingException("Header conditions \"" +
                    StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
                    "\" not met for actual request");
        }
    }

    @Override
    protected boolean supportsTypeLevelMappings() {
        return true;
    }
}

org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping:处理器映射器

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc.annotation;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.transform.Source;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver;
import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;

/**
 * Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface
 * that maps handler methods based on HTTP paths, HTTP methods, and request parameters
 * expressed through the {@link RequestMapping} annotation.
 *
 * <p>Supports request parameter binding through the {@link RequestParam} annotation.
 * Also supports the {@link ModelAttribute} annotation for exposing model attribute
 * values to the view, as well as {@link InitBinder} for binder initialization methods
 * and {@link SessionAttributes} for automatic session management of specific attributes.
 *
 * <p>This adapter can be customized through various bean properties.
 * A common use case is to apply shared binder initialization logic through
 * a custom {@link #setWebBindingInitializer WebBindingInitializer}.
 *
 * @author Juergen Hoeller
 * @author Arjen Poutsma
 * @author Sam Brannen
 * @since 2.5
 * @see #setPathMatcher
 * @see #setMethodNameResolver
 * @see #setWebBindingInitializer
 * @see #setSessionAttributeStore
 * @deprecated as of Spring 3.2, in favor of
 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter}
 */
@Deprecated
public class AnnotationMethodHandlerAdapter extends WebContentGenerator
        implements HandlerAdapter, Ordered, BeanFactoryAware {

    /**
     * Log category to use when no mapped handler is found for a request.
     * @see #pageNotFoundLogger
     */
    public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";

    /**
     * Additional logger to use when no mapped handler is found for a request.
     * @see #PAGE_NOT_FOUND_LOG_CATEGORY
     */
    protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);

    private UrlPathHelper urlPathHelper = new UrlPathHelper();

    private PathMatcher pathMatcher = new AntPathMatcher();

    private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver();

    private WebBindingInitializer webBindingInitializer;

    private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

    private int cacheSecondsForSessionAttributeHandlers = 0;

    private boolean synchronizeOnSession = false;

    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private WebArgumentResolver[] customArgumentResolvers;

    private ModelAndViewResolver[] customModelAndViewResolvers;

    private HttpMessageConverter<?>[] messageConverters;

    private int order = Ordered.LOWEST_PRECEDENCE;

    private ConfigurableBeanFactory beanFactory;

    private BeanExpressionContext expressionContext;

    private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
            new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>(64);

    private final Map<Class<?>, Boolean> sessionAnnotatedClassesCache = new ConcurrentHashMap<Class<?>, Boolean>(64);

    public AnnotationMethodHandlerAdapter() {
        // no restriction of HTTP methods by default
        super(false);

        // See SPR-7316
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);
        this.messageConverters = new HttpMessageConverter<?>[] {
            new ByteArrayHttpMessageConverter(), stringHttpMessageConverter,
            new SourceHttpMessageConverter<Source>(),
            new org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter() };
    }

    /**
     * Set if URL lookup should always use the full path within the current servlet
     * context. Else, the path within the current servlet mapping is used if applicable
     * (that is, in the case of a ".../*" servlet mapping in web.xml).
     * <p>Default is "false".
     * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
     */
    public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
        this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
    }

    /**
     * Set if context path and request URI should be URL-decoded. Both are returned
     * <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
     * <p>Uses either the request encoding or the default encoding according
     * to the Servlet spec (ISO-8859-1).
     * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
     */
    public void setUrlDecode(boolean urlDecode) {
        this.urlPathHelper.setUrlDecode(urlDecode);
    }

    /**
     * Set the UrlPathHelper to use for resolution of lookup paths.
     * <p>Use this to override the default UrlPathHelper with a custom subclass,
     * or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
     */
    public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
        Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
        this.urlPathHelper = urlPathHelper;
    }

    /**
     * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns.
     * <p>Default is {@link org.springframework.util.AntPathMatcher}.
     */
    public void setPathMatcher(PathMatcher pathMatcher) {
        Assert.notNull(pathMatcher, "PathMatcher must not be null");
        this.pathMatcher = pathMatcher;
    }

    /**
     * Set the MethodNameResolver to use for resolving default handler methods
     * (carrying an empty {@code @RequestMapping} annotation).
     * <p>Will only kick in when the handler method cannot be resolved uniquely
     * through the annotation metadata already.
     */
    public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
        this.methodNameResolver = methodNameResolver;
    }

    /**
     * Specify a WebBindingInitializer which will apply pre-configured
     * configuration to every DataBinder that this controller uses.
     */
    public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
        this.webBindingInitializer = webBindingInitializer;
    }

    /**
     * Specify the strategy to store session attributes with.
     * <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
     * storing session attributes in the HttpSession, using the same attribute name as in the model.
     */
    public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
        Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
        this.sessionAttributeStore = sessionAttributeStore;
    }

    /**
     * Cache content produced by {@code @SessionAttributes} annotated handlers
     * for the given number of seconds. Default is 0, preventing caching completely.
     * <p>In contrast to the "cacheSeconds" property which will apply to all general handlers
     * (but not to {@code @SessionAttributes} annotated handlers), this setting will
     * apply to {@code @SessionAttributes} annotated handlers only.
     * @see #setCacheSeconds
     * @see org.springframework.web.bind.annotation.SessionAttributes
     */
    public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
        this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
    }

    /**
     * Set if controller execution should be synchronized on the session,
     * to serialize parallel invocations from the same client.
     * <p>More specifically, the execution of the {@code handleRequestInternal}
     * method will get synchronized if this flag is "true". The best available
     * session mutex will be used for the synchronization; ideally, this will
     * be a mutex exposed by HttpSessionMutexListener.
     * <p>The session mutex is guaranteed to be the same object during
     * the entire lifetime of the session, available under the key defined
     * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a
     * safe reference to synchronize on for locking on the current session.
     * <p>In many cases, the HttpSession reference itself is a safe mutex
     * as well, since it will always be the same object reference for the
     * same active logical session. However, this is not guaranteed across
     * different servlet containers; the only 100% safe way is a session mutex.
     * @see org.springframework.web.util.HttpSessionMutexListener
     * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
     */
    public void setSynchronizeOnSession(boolean synchronizeOnSession) {
        this.synchronizeOnSession = synchronizeOnSession;
    }

    /**
     * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
     * (e.g. for default attribute names).
     * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
     */
    public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
        this.parameterNameDiscoverer = parameterNameDiscoverer;
    }

    /**
     * Set a custom WebArgumentResolvers to use for special method parameter types.
     * <p>Such a custom WebArgumentResolver will kick in first, having a chance to resolve
     * an argument value before the standard argument handling kicks in.
     */
    public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
        this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
    }

    /**
     * Set one or more custom WebArgumentResolvers to use for special method parameter types.
     * <p>Any such custom WebArgumentResolver will kick in first, having a chance to resolve
     * an argument value before the standard argument handling kicks in.
     */
    public void setCustomArgumentResolvers(WebArgumentResolver... argumentResolvers) {
        this.customArgumentResolvers = argumentResolvers;
    }

    /**
     * Set a custom ModelAndViewResolvers to use for special method return types.
     * <p>Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
     * a return value before the standard ModelAndView handling kicks in.
     */
    public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
        this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
    }

    /**
     * Set one or more custom ModelAndViewResolvers to use for special method return types.
     * <p>Any such custom ModelAndViewResolver will kick in first, having a chance to resolve
     * a return value before the standard ModelAndView handling kicks in.
     */
    public void setCustomModelAndViewResolvers(ModelAndViewResolver... customModelAndViewResolvers) {
        this.customModelAndViewResolvers = customModelAndViewResolvers;
    }

    /**
     * Set the message body converters to use.
     * <p>These converters are used to convert from and to HTTP requests and responses.
     */
    public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
        this.messageConverters = messageConverters;
    }

    /**
     * Return the message body converters that this adapter has been configured with.
     */
    public HttpMessageConverter<?>[] getMessageConverters() {
        return messageConverters;
    }

    /**
     * Specify the order value for this HandlerAdapter bean.
     * <p>Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered.
     * @see org.springframework.core.Ordered#getOrder()
     */
    public void setOrder(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (beanFactory instanceof ConfigurableBeanFactory) {
            this.beanFactory = (ConfigurableBeanFactory) beanFactory;
            this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
        }
    }

    @Override
    public boolean supports(Object handler) {
        return getMethodResolver(handler).hasHandlerMethods();
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        Class<?> clazz = ClassUtils.getUserClass(handler);
        Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
        if (annotatedWithSessionAttributes == null) {
            annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
            this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
        }

        if (annotatedWithSessionAttributes) {
            checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
        }
        else {
            checkAndPrepare(request, response, true);
        }

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return invokeHandlerMethod(request, response, handler);
                }
            }
        }

        return invokeHandlerMethod(request, response, handler);
    }

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
        Method handlerMethod = methodResolver.resolveHandlerMethod(request);
        ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ExtendedModelMap implicitModel = new BindingAwareModelMap();

        Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
        ModelAndView mav =
                methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
        methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
        return mav;
    }

    /**
     * This method always returns -1 since an annotated controller can have many methods,
     * each requiring separate lastModified calculations. Instead, an
     * {@link RequestMapping}-annotated method can calculate the lastModified value, call
     * {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}
     * to check it, and return {@code null} if that returns {@code true}.
     * @see org.springframework.web.context.request.WebRequest#checkNotModified(long)
     */
    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        return -1;
    }

    /**
     * Build a HandlerMethodResolver for the given handler type.
     */
    private ServletHandlerMethodResolver getMethodResolver(Object handler) {
        Class<?> handlerClass = ClassUtils.getUserClass(handler);
        ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
        if (resolver == null) {
            synchronized (this.methodResolverCache) {
                resolver = this.methodResolverCache.get(handlerClass);
                if (resolver == null) {
                    resolver = new ServletHandlerMethodResolver(handlerClass);
                    this.methodResolverCache.put(handlerClass, resolver);
                }
            }
        }
        return resolver;
    }

    /**
     * Template method for creating a new ServletRequestDataBinder instance.
     * <p>The default implementation creates a standard ServletRequestDataBinder.
     * This can be overridden for custom ServletRequestDataBinder subclasses.
     * @param request current HTTP request
     * @param target the target object to bind onto (or {@code null}
     * if the binder is just used to convert a plain parameter value)
     * @param objectName the objectName of the target object
     * @return the ServletRequestDataBinder instance to use
     * @throws Exception in case of invalid state or arguments
     * @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
     * @see ServletRequestDataBinder#convertIfNecessary(Object, Class, org.springframework.core.MethodParameter)
     */
    protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
        return new ServletRequestDataBinder(target, objectName);
    }

    /**
     * Template method for creating a new HttpInputMessage instance.
     * <p>The default implementation creates a standard {@link ServletServerHttpRequest}.
     * This can be overridden for custom {@code HttpInputMessage} implementations
     * @param servletRequest current HTTP request
     * @return the HttpInputMessage instance to use
     * @throws Exception in case of errors
     */
    protected HttpInputMessage createHttpInputMessage(HttpServletRequest servletRequest) throws Exception {
        return new ServletServerHttpRequest(servletRequest);
    }

    /**
     * Template method for creating a new HttpOutputMessage instance.
     * <p>The default implementation creates a standard {@link ServletServerHttpResponse}.
     * This can be overridden for custom {@code HttpOutputMessage} implementations
     * @param servletResponse current HTTP response
     * @return the HttpInputMessage instance to use
     * @throws Exception in case of errors
     */
    protected HttpOutputMessage createHttpOutputMessage(HttpServletResponse servletResponse) throws Exception {
        return new ServletServerHttpResponse(servletResponse);
    }

    /**
     * Servlet-specific subclass of {@link HandlerMethodResolver}.
     */
    private class ServletHandlerMethodResolver extends HandlerMethodResolver {

        private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();

        private ServletHandlerMethodResolver(Class<?> handlerType) {
            init(handlerType);
        }

        @Override
        protected boolean isHandlerMethod(Method method) {
            if (this.mappings.containsKey(method)) {
                return true;
            }
            RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (mapping != null) {
                String[] patterns = mapping.value();
                RequestMethod[] methods = new RequestMethod[0];
                String[] params = new String[0];
                String[] headers = new String[0];
                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) {
                    methods = mapping.method();
                }
                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
                    params = mapping.params();
                }
                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
                    headers = mapping.headers();
                }
                RequestMappingInfo mappingInfo = new RequestMappingInfo(patterns, methods, params, headers);
                this.mappings.put(method, mappingInfo);
                return true;
            }
            return false;
        }

        public Method resolveHandlerMethod(HttpServletRequest request) throws ServletException {
            String lookupPath = urlPathHelper.getLookupPathForRequest(request);
            Comparator<String> pathComparator = pathMatcher.getPatternComparator(lookupPath);
            Map<RequestSpecificMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestSpecificMappingInfo, Method>();
            Set<String> allowedMethods = new LinkedHashSet<String>(7);
            String resolvedMethodName = null;
            for (Method handlerMethod : getHandlerMethods()) {
                RequestSpecificMappingInfo mappingInfo = new RequestSpecificMappingInfo(this.mappings.get(handlerMethod));
                boolean match = false;
                if (mappingInfo.hasPatterns()) {
                    for (String pattern : mappingInfo.getPatterns()) {
                        if (!hasTypeLevelMapping() && !pattern.startsWith("/")) {
                            pattern = "/" + pattern;
                        }
                        String combinedPattern = getCombinedPattern(pattern, lookupPath, request);
                        if (combinedPattern != null) {
                            if (mappingInfo.matches(request)) {
                                match = true;
                                mappingInfo.addMatchedPattern(combinedPattern);
                            }
                            else {
                                if (!mappingInfo.matchesRequestMethod(request)) {
                                    allowedMethods.addAll(mappingInfo.methodNames());
                                }
                                break;
                            }
                        }
                    }
                    mappingInfo.sortMatchedPatterns(pathComparator);
                }
                else if (useTypeLevelMapping(request)) {
                    String[] typeLevelPatterns = getTypeLevelMapping().value();
                    for (String typeLevelPattern : typeLevelPatterns) {
                        if (!typeLevelPattern.startsWith("/")) {
                            typeLevelPattern = "/" + typeLevelPattern;
                        }
                        boolean useSuffixPattern = useSuffixPattern(request);
                        if (getMatchingPattern(typeLevelPattern, lookupPath, useSuffixPattern) != null) {
                            if (mappingInfo.matches(request)) {
                                match = true;
                                mappingInfo.addMatchedPattern(typeLevelPattern);
                            }
                            else {
                                if (!mappingInfo.matchesRequestMethod(request)) {
                                    allowedMethods.addAll(mappingInfo.methodNames());
                                }
                                break;
                            }
                        }
                    }
                    mappingInfo.sortMatchedPatterns(pathComparator);
                }
                else {
                    // No paths specified: parameter match sufficient.
                    match = mappingInfo.matches(request);
                    if (match && mappingInfo.getMethodCount() == 0 && mappingInfo.getParamCount() == 0 &&
                            resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
                        match = false;
                    }
                    else {
                        if (!mappingInfo.matchesRequestMethod(request)) {
                            allowedMethods.addAll(mappingInfo.methodNames());
                        }
                    }
                }
                if (match) {
                    Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
                    if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
                        if (methodNameResolver != null && !mappingInfo.hasPatterns()) {
                            if (!oldMappedMethod.getName().equals(handlerMethod.getName())) {
                                if (resolvedMethodName == null) {
                                    resolvedMethodName = methodNameResolver.getHandlerMethodName(request);
                                }
                                if (!resolvedMethodName.equals(oldMappedMethod.getName())) {
                                    oldMappedMethod = null;
                                }
                                if (!resolvedMethodName.equals(handlerMethod.getName())) {
                                    if (oldMappedMethod != null) {
                                        targetHandlerMethods.put(mappingInfo, oldMappedMethod);
                                        oldMappedMethod = null;
                                    }
                                    else {
                                        targetHandlerMethods.remove(mappingInfo);
                                    }
                                }
                            }
                        }
                        if (oldMappedMethod != null) {
                            throw new IllegalStateException(
                                    "Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" +
                                    oldMappedMethod + ", " + handlerMethod +
                                    "}. If you intend to handle the same path in multiple methods, then factor " +
                                    "them out into a dedicated handler class with that path mapped at the type level!");
                        }
                    }
                }
            }
            if (!targetHandlerMethods.isEmpty()) {
                List<RequestSpecificMappingInfo> matches = new ArrayList<RequestSpecificMappingInfo>(targetHandlerMethods.keySet());
                RequestSpecificMappingInfoComparator requestMappingInfoComparator =
                        new RequestSpecificMappingInfoComparator(pathComparator, request);
                Collections.sort(matches, requestMappingInfoComparator);
                RequestSpecificMappingInfo bestMappingMatch = matches.get(0);
                String bestMatchedPath = bestMappingMatch.bestMatchedPattern();
                if (bestMatchedPath != null) {
                    extractHandlerMethodUriTemplates(bestMatchedPath, lookupPath, request);
                }
                return targetHandlerMethods.get(bestMappingMatch);
            }
            else {
                if (!allowedMethods.isEmpty()) {
                    throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
                }
                throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(), request.getParameterMap());
            }
        }

        private boolean useTypeLevelMapping(HttpServletRequest request) {
            if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) {
                return false;
            }
            Object value = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
            return (value != null) ? (Boolean) value : Boolean.TRUE;
        }

        private boolean useSuffixPattern(HttpServletRequest request) {
            Object value = request.getAttribute(DefaultAnnotationHandlerMapping.USE_DEFAULT_SUFFIX_PATTERN);
            return (value != null) ? (Boolean) value : Boolean.TRUE;
        }

        /**
         * Determines the combined pattern for the given methodLevelPattern and path.
         * <p>Uses the following algorithm:
         * <ol>
         * <li>If there is a type-level mapping with path information, it is {@linkplain
         * PathMatcher#combine(String, String) combined} with the method-level pattern.</li>
         * <li>If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern}
         * in the request, it is combined with the method-level pattern.</li>
         * <li>Otherwise, the method-level pattern is returned.</li>
         * </ol>
         */
        private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
            boolean useSuffixPattern = useSuffixPattern(request);
            if (useTypeLevelMapping(request)) {
                String[] typeLevelPatterns = getTypeLevelMapping().value();
                for (String typeLevelPattern : typeLevelPatterns) {
                    if (!typeLevelPattern.startsWith("/")) {
                        typeLevelPattern = "/" + typeLevelPattern;
                    }
                    String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
                    String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
                    if (matchingPattern != null) {
                        return matchingPattern;
                    }
                }
                return null;
            }
            String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
            if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
                String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
                String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
                if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) {
                    return matchingPattern;
                }
            }
            return getMatchingPattern(methodLevelPattern, lookupPath, useSuffixPattern);
        }

        private String getMatchingPattern(String pattern, String lookupPath, boolean useSuffixPattern) {
            if (pattern.equals(lookupPath)) {
                return pattern;
            }
            boolean hasSuffix = pattern.indexOf('.') != -1;
            if (useSuffixPattern && !hasSuffix) {
                String patternWithSuffix = pattern + ".*";
                if (pathMatcher.match(patternWithSuffix, lookupPath)) {
                    return patternWithSuffix;
                }
            }
            if (pathMatcher.match(pattern, lookupPath)) {
                return pattern;
            }
            boolean endsWithSlash = pattern.endsWith("/");
            if (useSuffixPattern && !endsWithSlash) {
                String patternWithSlash = pattern + "/";
                if (pathMatcher.match(patternWithSlash, lookupPath)) {
                    return patternWithSlash;
                }
            }
            return null;
        }

        @SuppressWarnings("unchecked")
        private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, HttpServletRequest request) {
            Map<String, String> variables =
                    (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            int patternVariableCount = StringUtils.countOccurrencesOf(mappedPattern, "{");
            if ((variables == null || patternVariableCount != variables.size()) && pathMatcher.match(mappedPattern, lookupPath)) {
                variables = pathMatcher.extractUriTemplateVariables(mappedPattern, lookupPath);
                Map<String, String> decodedVariables = urlPathHelper.decodePathVariables(request, variables);
                request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedVariables);
            }
        }
    }

    /**
     * Servlet-specific subclass of {@link HandlerMethodInvoker}.
     */
    private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {

        private boolean responseArgumentUsed = false;

        private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
            super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
                    customArgumentResolvers, messageConverters);
        }

        @Override
        protected void raiseMissingParameterException(String paramName, Class<?> paramType) throws Exception {
            throw new MissingServletRequestParameterException(paramName, paramType.getSimpleName());
        }

        @Override
        protected void raiseSessionRequiredException(String message) throws Exception {
            throw new HttpSessionRequiredException(message);
        }

        @Override
        protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
                throws Exception {

            return AnnotationMethodHandlerAdapter.this.createBinder(
                    webRequest.getNativeRequest(HttpServletRequest.class), target, objectName);
        }

        @Override
        protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception {
            ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
            servletBinder.bind(webRequest.getNativeRequest(ServletRequest.class));
        }

        @Override
        protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            return AnnotationMethodHandlerAdapter.this.createHttpInputMessage(servletRequest);
        }

        @Override
        protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception {
            HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
            return AnnotationMethodHandlerAdapter.this.createHttpOutputMessage(servletResponse);
        }

        @Override
        protected Object resolveDefaultValue(String value) {
            if (beanFactory == null) {
                return value;
            }
            String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
            BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
            if (exprResolver == null) {
                return value;
            }
            return exprResolver.evaluate(placeholdersResolved, expressionContext);
        }

        @Override
        protected Object resolveCookieValue(String cookieName, Class<?> paramType, NativeWebRequest webRequest)
                throws Exception {

            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
            if (Cookie.class.isAssignableFrom(paramType)) {
                return cookieValue;
            }
            else if (cookieValue != null) {
                return urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
            }
            else {
                return null;
            }
        }

        @Override
        @SuppressWarnings({"unchecked"})
        protected String resolvePathVariable(String pathVarName, Class<?> paramType, NativeWebRequest webRequest)
                throws Exception {

            HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
            Map<String, String> uriTemplateVariables =
                    (Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) {
                throw new IllegalStateException(
                        "Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
            }
            return uriTemplateVariables.get(pathVarName);
        }

        @Override
        protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

            if (ServletRequest.class.isAssignableFrom(parameterType) ||
                    MultipartRequest.class.isAssignableFrom(parameterType)) {
                Object nativeRequest = webRequest.getNativeRequest(parameterType);
                if (nativeRequest == null) {
                    throw new IllegalStateException(
                            "Current request is not of type [" + parameterType.getName() + "]: " + request);
                }
                return nativeRequest;
            }
            else if (ServletResponse.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                Object nativeResponse = webRequest.getNativeResponse(parameterType);
                if (nativeResponse == null) {
                    throw new IllegalStateException(
                            "Current response is not of type [" + parameterType.getName() + "]: " + response);
                }
                return nativeResponse;
            }
            else if (HttpSession.class.isAssignableFrom(parameterType)) {
                return request.getSession();
            }
            else if (Principal.class.isAssignableFrom(parameterType)) {
                return request.getUserPrincipal();
            }
            else if (Locale.class == parameterType) {
                return RequestContextUtils.getLocale(request);
            }
            else if (InputStream.class.isAssignableFrom(parameterType)) {
                return request.getInputStream();
            }
            else if (Reader.class.isAssignableFrom(parameterType)) {
                return request.getReader();
            }
            else if (OutputStream.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                return response.getOutputStream();
            }
            else if (Writer.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                return response.getWriter();
            }
            return super.resolveStandardArgument(parameterType, webRequest);
        }

        @SuppressWarnings("unchecked")
        public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue,
                ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {

            ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class);
            if (responseStatus != null) {
                HttpStatus statusCode = responseStatus.code();
                String reason = responseStatus.reason();
                if (!StringUtils.hasText(reason)) {
                    webRequest.getResponse().setStatus(statusCode.value());
                }
                else {
                    webRequest.getResponse().sendError(statusCode.value(), reason);
                }

                // to be picked up by the RedirectView
                webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, statusCode);

                this.responseArgumentUsed = true;
            }

            // Invoke custom resolvers if present...
            if (customModelAndViewResolvers != null) {
                for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
                    ModelAndView mav = mavResolver.resolveModelAndView(
                            handlerMethod, handlerType, returnValue, implicitModel, webRequest);
                    if (mav != ModelAndViewResolver.UNRESOLVED) {
                        return mav;
                    }
                }
            }

            if (returnValue instanceof HttpEntity) {
                handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
                return null;
            }
            else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
                handleResponseBody(returnValue, webRequest);
                return null;
            }
            else if (returnValue instanceof ModelAndView) {
                ModelAndView mav = (ModelAndView) returnValue;
                mav.getModelMap().mergeAttributes(implicitModel);
                return mav;
            }
            else if (returnValue instanceof Model) {
                return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
            }
            else if (returnValue instanceof View) {
                return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
            }
            else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {
                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
                return new ModelAndView().addAllObjects(implicitModel);
            }
            else if (returnValue instanceof Map) {
                return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map<String, ?>) returnValue);
            }
            else if (returnValue instanceof String) {
                return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
            }
            else if (returnValue == null) {
                // Either returned null or was 'void' return.
                if (this.responseArgumentUsed || webRequest.isNotModified()) {
                    return null;
                }
                else {
                    // Assuming view name translation...
                    return new ModelAndView().addAllObjects(implicitModel);
                }
            }
            else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
                // Assume a single model attribute...
                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
                return new ModelAndView().addAllObjects(implicitModel);
            }
            else {
                throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
            }
        }

        private void handleResponseBody(Object returnValue, ServletWebRequest webRequest) throws Exception {
            if (returnValue == null) {
                return;
            }
            HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
            HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
            writeWithMessageConverters(returnValue, inputMessage, outputMessage);
        }

        private void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception {
            if (responseEntity == null) {
                return;
            }
            HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
            HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
            if (responseEntity instanceof ResponseEntity && outputMessage instanceof ServerHttpResponse) {
                ((ServerHttpResponse) outputMessage).setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
            }
            HttpHeaders entityHeaders = responseEntity.getHeaders();
            if (!entityHeaders.isEmpty()) {
                outputMessage.getHeaders().putAll(entityHeaders);
            }
            Object body = responseEntity.getBody();
            if (body != null) {
                writeWithMessageConverters(body, inputMessage, outputMessage);
            }
            else {
                // flush headers
                outputMessage.getBody();
            }
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        private void writeWithMessageConverters(Object returnValue,
                HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
                throws IOException, HttpMediaTypeNotAcceptableException {

            List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
            if (acceptedMediaTypes.isEmpty()) {
                acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
            }
            MediaType.sortByQualityValue(acceptedMediaTypes);
            Class<?> returnValueType = returnValue.getClass();
            List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
            if (getMessageConverters() != null) {
                for (MediaType acceptedMediaType : acceptedMediaTypes) {
                    for (HttpMessageConverter messageConverter : getMessageConverters()) {
                        if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
                            messageConverter.write(returnValue, acceptedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                MediaType contentType = outputMessage.getHeaders().getContentType();
                                if (contentType == null) {
                                    contentType = acceptedMediaType;
                                }
                                logger.debug("Written [" + returnValue + "] as \"" + contentType +
                                        "\" using [" + messageConverter + "]");
                            }
                            this.responseArgumentUsed = true;
                            return;
                        }
                    }
                }
                for (HttpMessageConverter messageConverter : messageConverters) {
                    allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
                }
            }
            throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
        }
    }

    /**
     * Holder for request mapping metadata.
     */
    static class RequestMappingInfo {

        private final String[] patterns;

        private final RequestMethod[] methods;

        private final String[] params;

        private final String[] headers;

        RequestMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) {
            this.patterns = (patterns != null ? patterns : new String[0]);
            this.methods = (methods != null ? methods : new RequestMethod[0]);
            this.params = (params != null ? params : new String[0]);
            this.headers = (headers != null ? headers : new String[0]);
        }

        public boolean hasPatterns() {
            return (this.patterns.length > 0);
        }

        public String[] getPatterns() {
            return this.patterns;
        }

        public int getMethodCount() {
            return this.methods.length;
        }

        public int getParamCount() {
            return this.params.length;
        }

        public int getHeaderCount() {
            return this.headers.length;
        }

        public boolean matches(HttpServletRequest request) {
            return matchesRequestMethod(request) && matchesParameters(request) && matchesHeaders(request);
        }

        public boolean matchesHeaders(HttpServletRequest request) {
            return ServletAnnotationMappingUtils.checkHeaders(this.headers, request);
        }

        public boolean matchesParameters(HttpServletRequest request) {
            return ServletAnnotationMappingUtils.checkParameters(this.params, request);
        }

        public boolean matchesRequestMethod(HttpServletRequest request) {
            return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request);
        }

        public Set<String> methodNames() {
            Set<String> methodNames = new LinkedHashSet<String>(this.methods.length);
            for (RequestMethod method : this.methods) {
                methodNames.add(method.name());
            }
            return methodNames;
        }

        @Override
        public boolean equals(Object obj) {
            RequestMappingInfo other = (RequestMappingInfo) obj;
            return (Arrays.equals(this.patterns, other.patterns) && Arrays.equals(this.methods, other.methods) &&
                    Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
        }

        @Override
        public int hashCode() {
            return (Arrays.hashCode(this.patterns) * 23 + Arrays.hashCode(this.methods) * 29 +
                    Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append(Arrays.asList(this.patterns));
            if (this.methods.length > 0) {
                builder.append(',');
                builder.append(Arrays.asList(this.methods));
            }
            if (this.headers.length > 0) {
                builder.append(',');
                builder.append(Arrays.asList(this.headers));
            }
            if (this.params.length > 0) {
                builder.append(',');
                builder.append(Arrays.asList(this.params));
            }
            return builder.toString();
        }
    }

    /**
     * Subclass of {@link RequestMappingInfo} that holds request-specific data.
     */
    static class RequestSpecificMappingInfo extends RequestMappingInfo {

        private final List<String> matchedPatterns = new ArrayList<String>();

        RequestSpecificMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) {
            super(patterns, methods, params, headers);
        }

        RequestSpecificMappingInfo(RequestMappingInfo other) {
            super(other.patterns, other.methods, other.params, other.headers);
        }

        public void addMatchedPattern(String matchedPattern) {
            matchedPatterns.add(matchedPattern);
        }

        public void sortMatchedPatterns(Comparator<String> pathComparator) {
            Collections.sort(matchedPatterns, pathComparator);
        }

        public String bestMatchedPattern() {
            return (!this.matchedPatterns.isEmpty() ? this.matchedPatterns.get(0) : null);
        }
    }

    /**
     * Comparator capable of sorting {@link RequestSpecificMappingInfo}s (RHIs) so that
     * sorting a list with this comparator will result in:
     * <ul>
     * <li>RHIs with {@linkplain AnnotationMethodHandlerAdapter.RequestSpecificMappingInfo#matchedPatterns better matched paths}
     * take precedence over those with a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String)
     * path pattern comparator}.) Typically, this means that patterns without wild cards and uri templates
     * will be ordered before those without.</li>
     * <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be
     * ordered before those without a method, or with more than one method.</li>
     * <li>RHIs with more {@linkplain RequestMappingInfo#params request parameters} will be ordered
     * before those with less parameters</li>
     * </ol>
     */
    static class RequestSpecificMappingInfoComparator implements Comparator<RequestSpecificMappingInfo> {

        private final Comparator<String> pathComparator;

        private final ServerHttpRequest request;

        RequestSpecificMappingInfoComparator(Comparator<String> pathComparator, HttpServletRequest request) {
            this.pathComparator = pathComparator;
            this.request = new ServletServerHttpRequest(request);
        }

        @Override
        public int compare(RequestSpecificMappingInfo info1, RequestSpecificMappingInfo info2) {
            int pathComparison = pathComparator.compare(info1.bestMatchedPattern(), info2.bestMatchedPattern());
            if (pathComparison != 0) {
                return pathComparison;
            }
            int info1ParamCount = info1.getParamCount();
            int info2ParamCount = info2.getParamCount();
            if (info1ParamCount != info2ParamCount) {
                return info2ParamCount - info1ParamCount;
            }
            int info1HeaderCount = info1.getHeaderCount();
            int info2HeaderCount = info2.getHeaderCount();
            if (info1HeaderCount != info2HeaderCount) {
                return info2HeaderCount - info1HeaderCount;
            }
            int acceptComparison = compareAcceptHeaders(info1, info2);
            if (acceptComparison != 0) {
                return acceptComparison;
            }
            int info1MethodCount = info1.getMethodCount();
            int info2MethodCount = info2.getMethodCount();
            if (info1MethodCount == 0 && info2MethodCount > 0) {
                return 1;
            }
            else if (info2MethodCount == 0 && info1MethodCount > 0) {
                return -1;
            }
            else if (info1MethodCount == 1 & info2MethodCount > 1) {
                return -1;
            }
            else if (info2MethodCount == 1 & info1MethodCount > 1) {
                return 1;
            }
            return 0;
        }

        private int compareAcceptHeaders(RequestMappingInfo info1, RequestMappingInfo info2) {
            List<MediaType> requestAccepts = request.getHeaders().getAccept();
            MediaType.sortByQualityValue(requestAccepts);

            List<MediaType> info1Accepts = getAcceptHeaderValue(info1);
            List<MediaType> info2Accepts = getAcceptHeaderValue(info2);

            for (MediaType requestAccept : requestAccepts) {
                int pos1 = indexOfIncluded(info1Accepts, requestAccept);
                int pos2 = indexOfIncluded(info2Accepts, requestAccept);
                if (pos1 != pos2) {
                    return pos2 - pos1;
                }
            }
            return 0;
        }

        private int indexOfIncluded(List<MediaType> infoAccepts, MediaType requestAccept) {
            for (int i = 0; i < infoAccepts.size(); i++) {
                MediaType info1Accept = infoAccepts.get(i);
                if (requestAccept.includes(info1Accept)) {
                    return i;
                }
            }
            return -1;
        }

        private List<MediaType> getAcceptHeaderValue(RequestMappingInfo info) {
            for (String header : info.headers) {
                int separator = header.indexOf('=');
                if (separator != -1) {
                    String key = header.substring(0, separator);
                    String value = header.substring(separator + 1);
                    if ("Accept".equalsIgnoreCase(key)) {
                        return MediaType.parseMediaTypes(value);
                    }
                }
            }
            return Collections.emptyList();
        }
    }

}

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter:处理器适配器

从注释中可以看到让我们使用新的处理器映射器和处理器适配器类,而我们只需要将这两个类的实例交给 Spring 管理,Spring MVC 就会默认使用上这两个类,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置控制器扫描-->
    <context:component-scan base-package="com.zze.springmvc.web.controller"/>
    <!--配置处理器映射器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <!--配置处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
</beans>

springmvc.xml

而这样配置显然不够简洁,Spring MVC 还为我们提供了一个更简洁的配置方式,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置控制器扫描-->
    <context:component-scan base-package="com.zze.springmvc.web.controller"/>
    <!--配置注解驱动,相当于同时使用最新处理器映射器和处理器适配器-->
    <mvc:annotation-driven/>
</beans>

springmvc.xml

从 spring3.1 版本开始,废除了 DefaultAnnotationHandlerMapping 的使用,推荐使用 RequestMappingHandlerMapping 完成注解式处理器映射。

从 spring3.1 版本开始,废除了 AnnotationMethodHandlerAdapter 的使用,推荐使用 RequestMappingHandlerAdapter 完成注解式处理器适配。

映射器与适配器必需配套使用,如果映射器使用了推荐的 RequestMappingHandlerMapping,适配器也必需使用推荐的 RequestMappingHandlerAdapter。

视图解析器

在 Spring 核心配置文件中配置上如下视图解析器:

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

此时 Controller 返回的 viewName 如果是 'userlist',那么它实际上会转发到 '/WEB-INF/jsp/userlist.jsp'。

默认支持的入参

package com.zze.springmvc.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

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

@Controller
public class TestController {
    @RequestMapping("test")
    public String test(Model model, ModelMap modelMap, HttpServletRequest request, HttpServletResponse response){
//        ModelAndView modelAndView = new ModelAndView();
//        此时的入参 model 及 modelMap,就对应于 modelAndView 中的 model 部分,而 String 返回值,就相当于 modelAndView 中的 view 部分
//          modelAndView.addObject() 对应 model.addAttribute()、 modelMap.addAttribute()
//          modelAndView.setViewName("list") 对应 return "list"
//        默认还支持直接传入 HttpServletRequest、HttpServletResponse 实例,这样就可以直接访问到原生 Servlet 相关 API
        return null;
    }
}

参数绑定

package com.zze.springmvc.web.controller;

import com.zze.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
    /**
     * 简单参数绑定
     *  请求参数名必须和请求方法入参名一致
     */
    @RequestMapping("test")
    public void test(Integer id,String name) {
        System.out.println(name);
        System.out.println(id);
        /*
        zhangsan
        123
         */
    }
}

例:简单参数绑定

package com.zze.springmvc.web.controller;

import com.zze.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
    /**
     * pojo 参数绑定
     *  请求参数名必须和 pojo 属性名一致
     */
    @RequestMapping("test")
    public void test(User user) {
        System.out.println(user.getName());
        System.out.println(user.getId());
        /*
        zhangsan
        123
         */
    }
}

例:pojo 参数绑定

package com.zze.springmvc.web.controller;

import com.zze.springmvc.pojo.QueryVo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
    /**
     * 包装 pojo 参数绑定
     */
    @RequestMapping("test")
    public void test(QueryVo queryVo) {
        System.out.println(queryVo.getUser().getName());
        System.out.println(queryVo.getUser().getId());
        /*
        zhangsan
        123
         */
    }
}

例:包装 pojo 参数绑定

与MyBatis整合

1、创建 maven web 项目,引入如下依赖:

<?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>

    <groupId>com.zze.springmvc</groupId>
    <artifactId>springmvc_mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.11</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.18</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>commons-pool</groupId>
            <artifactId>commons-pool</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.11.0.GA</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.0-rc1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.0-rc1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.7</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--使用 tomcat7:run-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

pom.xml

2、使用逆向工程生成 MyBatis 相关类及映射文件。

package com.zze.springmvc.pojo;

import java.util.Date;

public class User {
    private Integer id;

    private String name;

    private Date birthday;

    private String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address == null ? null : address.trim();
    }
}

com.zze.springmvc.pojo.User

package com.zze.springmvc.pojo;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

public class UserExample {
    protected String orderByClause;

    protected boolean distinct;

    protected List<Criteria> oredCriteria;

    public UserExample() {
        oredCriteria = new ArrayList<Criteria>();
    }

    public void setOrderByClause(String orderByClause) {
        this.orderByClause = orderByClause;
    }

    public String getOrderByClause() {
        return orderByClause;
    }

    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    public boolean isDistinct() {
        return distinct;
    }

    public List<Criteria> getOredCriteria() {
        return oredCriteria;
    }

    public void or(Criteria criteria) {
        oredCriteria.add(criteria);
    }

    public Criteria or() {
        Criteria criteria = createCriteriaInternal();
        oredCriteria.add(criteria);
        return criteria;
    }

    public Criteria createCriteria() {
        Criteria criteria = createCriteriaInternal();
        if (oredCriteria.size() == 0) {
            oredCriteria.add(criteria);
        }
        return criteria;
    }

    protected Criteria createCriteriaInternal() {
        Criteria criteria = new Criteria();
        return criteria;
    }

    public void clear() {
        oredCriteria.clear();
        orderByClause = null;
        distinct = false;
    }

    protected abstract static class GeneratedCriteria {
        protected List<Criterion> criteria;

        protected GeneratedCriteria() {
            super();
            criteria = new ArrayList<Criterion>();
        }

        public boolean isValid() {
            return criteria.size() > 0;
        }

        public List<Criterion> getAllCriteria() {
            return criteria;
        }

        public List<Criterion> getCriteria() {
            return criteria;
        }

        protected void addCriterion(String condition) {
            if (condition == null) {
                throw new RuntimeException("Value for condition cannot be null");
            }
            criteria.add(new Criterion(condition));
        }

        protected void addCriterion(String condition, Object value, String property) {
            if (value == null) {
                throw new RuntimeException("Value for " + property + " cannot be null");
            }
            criteria.add(new Criterion(condition, value));
        }

        protected void addCriterion(String condition, Object value1, Object value2, String property) {
            if (value1 == null || value2 == null) {
                throw new RuntimeException("Between values for " + property + " cannot be null");
            }
            criteria.add(new Criterion(condition, value1, value2));
        }

        protected void addCriterionForJDBCDate(String condition, Date value, String property) {
            if (value == null) {
                throw new RuntimeException("Value for " + property + " cannot be null");
            }
            addCriterion(condition, new java.sql.Date(value.getTime()), property);
        }

        protected void addCriterionForJDBCDate(String condition, List<Date> values, String property) {
            if (values == null || values.size() == 0) {
                throw new RuntimeException("Value list for " + property + " cannot be null or empty");
            }
            List<java.sql.Date> dateList = new ArrayList<java.sql.Date>();
            Iterator<Date> iter = values.iterator();
            while (iter.hasNext()) {
                dateList.add(new java.sql.Date(iter.next().getTime()));
            }
            addCriterion(condition, dateList, property);
        }

        protected void addCriterionForJDBCDate(String condition, Date value1, Date value2, String property) {
            if (value1 == null || value2 == null) {
                throw new RuntimeException("Between values for " + property + " cannot be null");
            }
            addCriterion(condition, new java.sql.Date(value1.getTime()), new java.sql.Date(value2.getTime()), property);
        }

        public Criteria andIdIsNull() {
            addCriterion("id is null");
            return (Criteria) this;
        }

        public Criteria andIdIsNotNull() {
            addCriterion("id is not null");
            return (Criteria) this;
        }

        public Criteria andIdEqualTo(Integer value) {
            addCriterion("id =", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotEqualTo(Integer value) {
            addCriterion("id <>", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdGreaterThan(Integer value) {
            addCriterion("id >", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdGreaterThanOrEqualTo(Integer value) {
            addCriterion("id >=", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdLessThan(Integer value) {
            addCriterion("id <", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdLessThanOrEqualTo(Integer value) {
            addCriterion("id <=", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdIn(List<Integer> values) {
            addCriterion("id in", values, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotIn(List<Integer> values) {
            addCriterion("id not in", values, "id");
            return (Criteria) this;
        }

        public Criteria andIdBetween(Integer value1, Integer value2) {
            addCriterion("id between", value1, value2, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotBetween(Integer value1, Integer value2) {
            addCriterion("id not between", value1, value2, "id");
            return (Criteria) this;
        }

        public Criteria andNameIsNull() {
            addCriterion("name is null");
            return (Criteria) this;
        }

        public Criteria andNameIsNotNull() {
            addCriterion("name is not null");
            return (Criteria) this;
        }

        public Criteria andNameEqualTo(String value) {
            addCriterion("name =", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotEqualTo(String value) {
            addCriterion("name <>", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameGreaterThan(String value) {
            addCriterion("name >", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameGreaterThanOrEqualTo(String value) {
            addCriterion("name >=", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLessThan(String value) {
            addCriterion("name <", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLessThanOrEqualTo(String value) {
            addCriterion("name <=", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLike(String value) {
            addCriterion("name like", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotLike(String value) {
            addCriterion("name not like", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameIn(List<String> values) {
            addCriterion("name in", values, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotIn(List<String> values) {
            addCriterion("name not in", values, "name");
            return (Criteria) this;
        }

        public Criteria andNameBetween(String value1, String value2) {
            addCriterion("name between", value1, value2, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotBetween(String value1, String value2) {
            addCriterion("name not between", value1, value2, "name");
            return (Criteria) this;
        }

        public Criteria andBirthdayIsNull() {
            addCriterion("birthday is null");
            return (Criteria) this;
        }

        public Criteria andBirthdayIsNotNull() {
            addCriterion("birthday is not null");
            return (Criteria) this;
        }

        public Criteria andBirthdayEqualTo(Date value) {
            addCriterionForJDBCDate("birthday =", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayNotEqualTo(Date value) {
            addCriterionForJDBCDate("birthday <>", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayGreaterThan(Date value) {
            addCriterionForJDBCDate("birthday >", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayGreaterThanOrEqualTo(Date value) {
            addCriterionForJDBCDate("birthday >=", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayLessThan(Date value) {
            addCriterionForJDBCDate("birthday <", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayLessThanOrEqualTo(Date value) {
            addCriterionForJDBCDate("birthday <=", value, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayIn(List<Date> values) {
            addCriterionForJDBCDate("birthday in", values, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayNotIn(List<Date> values) {
            addCriterionForJDBCDate("birthday not in", values, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayBetween(Date value1, Date value2) {
            addCriterionForJDBCDate("birthday between", value1, value2, "birthday");
            return (Criteria) this;
        }

        public Criteria andBirthdayNotBetween(Date value1, Date value2) {
            addCriterionForJDBCDate("birthday not between", value1, value2, "birthday");
            return (Criteria) this;
        }

        public Criteria andAddressIsNull() {
            addCriterion("address is null");
            return (Criteria) this;
        }

        public Criteria andAddressIsNotNull() {
            addCriterion("address is not null");
            return (Criteria) this;
        }

        public Criteria andAddressEqualTo(String value) {
            addCriterion("address =", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressNotEqualTo(String value) {
            addCriterion("address <>", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressGreaterThan(String value) {
            addCriterion("address >", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressGreaterThanOrEqualTo(String value) {
            addCriterion("address >=", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressLessThan(String value) {
            addCriterion("address <", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressLessThanOrEqualTo(String value) {
            addCriterion("address <=", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressLike(String value) {
            addCriterion("address like", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressNotLike(String value) {
            addCriterion("address not like", value, "address");
            return (Criteria) this;
        }

        public Criteria andAddressIn(List<String> values) {
            addCriterion("address in", values, "address");
            return (Criteria) this;
        }

        public Criteria andAddressNotIn(List<String> values) {
            addCriterion("address not in", values, "address");
            return (Criteria) this;
        }

        public Criteria andAddressBetween(String value1, String value2) {
            addCriterion("address between", value1, value2, "address");
            return (Criteria) this;
        }

        public Criteria andAddressNotBetween(String value1, String value2) {
            addCriterion("address not between", value1, value2, "address");
            return (Criteria) this;
        }
    }

    public static class Criteria extends GeneratedCriteria {

        protected Criteria() {
            super();
        }
    }

    public static class Criterion {
        private String condition;

        private Object value;

        private Object secondValue;

        private boolean noValue;

        private boolean singleValue;

        private boolean betweenValue;

        private boolean listValue;

        private String typeHandler;

        public String getCondition() {
            return condition;
        }

        public Object getValue() {
            return value;
        }

        public Object getSecondValue() {
            return secondValue;
        }

        public boolean isNoValue() {
            return noValue;
        }

        public boolean isSingleValue() {
            return singleValue;
        }

        public boolean isBetweenValue() {
            return betweenValue;
        }

        public boolean isListValue() {
            return listValue;
        }

        public String getTypeHandler() {
            return typeHandler;
        }

        protected Criterion(String condition) {
            super();
            this.condition = condition;
            this.typeHandler = null;
            this.noValue = true;
        }

        protected Criterion(String condition, Object value, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.typeHandler = typeHandler;
            if (value instanceof List<?>) {
                this.listValue = true;
            } else {
                this.singleValue = true;
            }
        }

        protected Criterion(String condition, Object value) {
            this(condition, value, null);
        }

        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.secondValue = secondValue;
            this.typeHandler = typeHandler;
            this.betweenValue = true;
        }

        protected Criterion(String condition, Object value, Object secondValue) {
            this(condition, value, secondValue, null);
        }
    }
}

com.zze.springmvc.pojo.UserExample

package com.zze.springmvc.mapper;

import com.zze.springmvc.pojo.User;
import com.zze.springmvc.pojo.UserExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    long countByExample(UserExample example);

    int deleteByExample(UserExample example);

    int deleteByPrimaryKey(Integer id);

    int insert(User record);

    int insertSelective(User record);

    List<User> selectByExample(UserExample example);

    User selectByPrimaryKey(Integer id);

    int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example);

    int updateByExample(@Param("record") User record, @Param("example") UserExample example);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);
}

com.zze.springmvc.mapper.UserMapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zze.springmvc.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.zze.springmvc.pojo.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="birthday" jdbcType="DATE" property="birthday" />
    <result column="address" jdbcType="VARCHAR" property="address" />
  </resultMap>
  <sql id="Example_Where_Clause">
    <where>
      <foreach collection="oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Update_By_Example_Where_Clause">
    <where>
      <foreach collection="example.oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Base_Column_List">
    id, name, birthday, address
  </sql>
  <select id="selectByExample" parameterType="com.zze.springmvc.pojo.UserExample" resultMap="BaseResultMap">
    select
    <if test="distinct">
      distinct
    </if>
    <include refid="Base_Column_List" />
    from user
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null">
      order by ${orderByClause}
    </if>
  </select>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from user
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <delete id="deleteByExample" parameterType="com.zze.springmvc.pojo.UserExample">
    delete from user
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
  </delete>
  <insert id="insert" parameterType="com.zze.springmvc.pojo.User">
    insert into user (id, name, birthday,
      address)
    values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{birthday,jdbcType=DATE},
      #{address,jdbcType=VARCHAR})
  </insert>
  <insert id="insertSelective" parameterType="com.zze.springmvc.pojo.User">
    insert into user
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="name != null">
        name,
      </if>
      <if test="birthday != null">
        birthday,
      </if>
      <if test="address != null">
        address,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=INTEGER},
      </if>
      <if test="name != null">
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="birthday != null">
        #{birthday,jdbcType=DATE},
      </if>
      <if test="address != null">
        #{address,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <select id="countByExample" parameterType="com.zze.springmvc.pojo.UserExample" resultType="java.lang.Long">
    select count(*) from user
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
  </select>
  <update id="updateByExampleSelective" parameterType="map">
    update user
    <set>
      <if test="record.id != null">
        id = #{record.id,jdbcType=INTEGER},
      </if>
      <if test="record.name != null">
        name = #{record.name,jdbcType=VARCHAR},
      </if>
      <if test="record.birthday != null">
        birthday = #{record.birthday,jdbcType=DATE},
      </if>
      <if test="record.address != null">
        address = #{record.address,jdbcType=VARCHAR},
      </if>
    </set>
    <if test="_parameter != null">
      <include refid="Update_By_Example_Where_Clause" />
    </if>
  </update>
  <update id="updateByExample" parameterType="map">
    update user
    set id = #{record.id,jdbcType=INTEGER},
      name = #{record.name,jdbcType=VARCHAR},
      birthday = #{record.birthday,jdbcType=DATE},
      address = #{record.address,jdbcType=VARCHAR}
    <if test="_parameter != null">
      <include refid="Update_By_Example_Where_Clause" />
    </if>
  </update>
  <update id="updateByPrimaryKeySelective" parameterType="com.zze.springmvc.pojo.User">
    update user
    <set>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="birthday != null">
        birthday = #{birthday,jdbcType=DATE},
      </if>
      <if test="address != null">
        address = #{address,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.zze.springmvc.pojo.User">
    update user
    set name = #{name,jdbcType=VARCHAR},
      birthday = #{birthday,jdbcType=DATE},
      address = #{address,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

com/zze/springmvc/mapper/UserMapper.xml

3、引入 MyBatis 核心配置文件,包含文件头的空文件即可:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

config/SqlMapConfig.xml

4、引入 jdbc 属性文件:

jdbc.url=jdbc:mysql://192.168.208.192:3306/test?characterEncoding=utf-8
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root

config/jdbc.properties

5、dao 层,在 Spring 中配置数据库连接及事务:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!--读取属性文件-->
    <context:property-placeholder location="classpath:config/jdbc.properties"/>
    <!--连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="maxActive" value="20"/>
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
    </bean>

    <!--配置 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置核心配置文件路径-->
        <property name="configLocation" value="classpath:config/SqlMapConfig.xml"/>
        <!--配置别名扫描包-->
        <property name="typeAliasesPackage" value="com.zze.springmvc.pojo"/>
    </bean>

    <!--动态代理配置方式二:配置 mapper 包扫描-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.zze.springmvc.mapper"/>
    </bean>
</beans>

config/spring/applicationContext-dao.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置事务管理器-->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务的通知/增强-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--事务管理规则-->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="modify*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="remove*" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <!--
            name : 匹配方法名
            read-only : 为 true 时表示只做只读操作
            propagation : 事务的传播行为
            timeout : 事务过期时间,为 -1 时不会过期
            isolation : 事务的隔离级别
            -->
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
        </tx:attributes>
    </tx:advice>

    <!--AOP 配置-->
    <aop:config>
        <aop:pointcut id="pc_service" expression="execution(* com.zze.springmvc.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc_service"/>
    </aop:config>
</beans>

config/spring/applicationContext-trans.xml

6、service 层,在 Spring 中配置包扫描:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置控制器扫描-->
    <context:component-scan base-package="com.zze.springmvc.service"/>
</beans>

config/spring/applicationContext-service.xml

7、web 层,SpringMVC 的核心配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置控制器扫描-->
    <context:component-scan base-package="com.zze.springmvc.web.controller"/>
    <!--注解驱动-->
    <mvc:annotation-driven/>
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

config/spring/springmvc.xml

8、引入 log4j 属性文件:

log4j.rootLogger = debug,stdout

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

log4j.properties

9、配置 Spring 核心监听器及加载 SpringMVC 核心配置文件:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/spring/applicationContext-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--加载 SpringMVC 核心配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:config/spring/springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--注意:这里使用 / ,如果使用 /* 会出现找不到视图的问题-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

WEB-INF/web.xml

9、编写 service 层测试代码:

package com.zze.springmvc.service;

import com.zze.springmvc.pojo.User;

import java.util.List;

public interface UserService {

    List<User> getAll();
}

com.zze.springmvc.service.UserService

package com.zze.springmvc.service.impl;

import com.zze.springmvc.mapper.UserMapper;
import com.zze.springmvc.pojo.User;
import com.zze.springmvc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    public List<User> getAll() {
        return userMapper.selectByExample(null);
    }
}

com.zze.springmvc.service.impl.UserServiceImpl

10、编写 web 层测试代码:

package com.zze.springmvc.web.controller;

import com.zze.springmvc.pojo.User;
import com.zze.springmvc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.text.ParseException;
import java.util.List;

@Controller
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("list")
    public String list(Model model) throws ParseException {
        List<User> all = userService.getAll();
        model.addAttribute("list", all);
        return "userlist";
    }
}

com.zze.springmvc.web.controller.UserController

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<head>
    <title>list</title>
</head>
<body>

<table border="1">
    <thead>
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Birthday</th>
        <th>Address</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td><fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/></td>
            <td>${user.address}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

WEB-INF/jsp/userlist.jsp

11、启动项目,测试:

浏览器:

访问 'http://localhost:8080/user/list'

java框架之SpringMVC(1)-入门&整合MyBatis的更多相关文章

  1. 【java框架】SpringBoot(7) -- SpringBoot整合MyBatis

    1.整合MyBatis操作 前面一篇提到了SpringBoot整合基础的数据源JDBC.Druid操作,实际项目中更常用的还是MyBatis框架,而SpringBoot整合MyBatis进行CRUD也 ...

  2. Java开发学习(十四)----Spring整合Mybatis及Junit

    一.Spring整合Mybatis思路分析 1.1 环境准备 步骤1:准备数据库表 Mybatis是来操作数据库表,所以先创建一个数据库及表 create database spring_db cha ...

  3. JAVA 框架 / SSM / SSM SPRING+SPING MVC + MYBATIS 三大框架整合详细步骤

    http://how2j.cn/k/ssm/ssm-tutorial/1137.html

  4. 【Java框架型项目从入门到装逼】第七节 - 学生管理系统项目搭建

    本次的教程是打算用Spring,SpringMVC以及传统的jdbc技术来制作一个简单的增删改查项目,对用户信息进行增删改查,就这么简单. 1.新建项目 首先,打开eclipse,新建一个web项目. ...

  5. java框架之springmvc

    一.HelloWorld程序 (1)导包:四个spring 核心包(core.beans.context.expression).一个aop包.两个 web 包和一个logging 包: (2)配置 ...

  6. java框架之Quartz-任务调度&整合Spring

    准备 介绍 定时任务,无论是互联网公司还是传统的软件行业都是必不可少的.Quartz,它是好多优秀的定时任务开源框架的基础,使用它,我们可以使用最简单基础的配置来轻松的使用定时任务. Quartz 是 ...

  7. java框架之Spring(1)-入门

    介绍 概述 Spring 是一个开放源代码的设计层面框架,它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring 是于 2003 年兴起的一个轻量级的 J ...

  8. java框架之Spring(4)-Spring整合Hibernate和Struts2

    准备 导包 Struts2 导入 Struts2 zip 包解压目录下 'apps/struts-blank.war' 中所有 jar 包,如下: asm-3.3.jar asm-commons-3. ...

  9. java框架之SpringBoot(1)-入门

    简介 Spring Boot 用来简化 Spring 应用开发,约定大于配置,去繁从简,just run 就能创建一个独立的.产品级别的应用. 背景: J2EE 笨重的开发.繁多的配置.低下的开发效率 ...

随机推荐

  1. 物联网架构成长之路(29)-Jenkins环境搭建

    0. 说明 哈哈,前面中间插入了一篇Eclipse增加Git插件,在此之前真的没有用过GIT. 1. 运行Jenkins 这里为了方便,还是用Docker方式安装,由于这个是标准的war报,不对Doc ...

  2. cx_Oracle读写clob

    cx_Oracle读写clob 读 读到相应字段后,使用read()方法即可:例如读取到clob对象a,想要查看其内容,使用下列代码即可: a.read() 写 参考下列代码: id='123' cl ...

  3. 100BASE-TX、100Base-FX等含义

    100BASE-TX:双绞线,使用两对非屏蔽双绞线或两对1类屏蔽双绞线连接,传输距离100米 100Base-FX,是在光纤上实现的100 Mbps以太网标准,其中F指示光纤,IEEE标准为802.3 ...

  4. sqoop 测试 --hive-delims-replacement 参数

    在hive的官方文档中给的例子中字段分隔符是\001,但是在他的API文档中--hive-delims-replacement ,--hive-drop-import-delims   参数中会处理的 ...

  5. 【ML入门系列】(一)训练集、测试集和验证集

    训练集.验证集和测试集这三个名词在机器学习领域极其常见,但很多人并不是特别清楚,尤其是后两个经常被人混用. 在有监督(supervise)的机器学习中,数据集常被分成2~3个,即:训练集(train ...

  6. Git分支操作——查看、新建、删除、提交、合并

    查看分支 1 查看本地分支 $ git branch   2 查看远程分支 $ git branch -r     创建分支 1 创建本地分支 $ git branch branchName 2 切换 ...

  7. 通用Mapper新特性:ExampleBuilder 2017年12月18日

    package tk.mybatis.mapper.test.example; import org.apache.ibatis.session.SqlSession; import org.juni ...

  8. AssetManager

    AssetManager用于获取assets下的资源. 1.getassets()得到AssetManager 2.AssetManager.close() 关闭AssetManager 3.Reso ...

  9. Unity游戏推送技术

    https://www.cnblogs.com/wuzhang/p/wuzhang20150401.html https://www.cnblogs.com/yangwujun/p/5789969.h ...

  10. 【物联网】 9个顶级开发IoT项目的开源物联网平台(转)

    物联网(IoT)是帮助人工智能(AI)以更好的方式控制和理解事物的未来技术. 我们收集了一些最有名的物联网平台,帮助您以受控方式开发物联网项目. 物联网平台是帮助设置和管理互联网连接设备的组件套件. ...