java框架之SpringMVC(1)-入門&整合MyBatis

前言

SpringMVC簡介

SpringMVC 是一個相似於 Struts2 表現層的框架,屬於 SpringFramework 的後續產品。html

學習SpringMVC的緣由

SpringMVC 與 Struts2 的區別
對比項目 SpringMVC Struts2 優點
國內市場狀況 有大量用戶,通常新項目啓動都會選用 SpringMVC。 有部分老用戶,老項目組,因爲習慣了,一直在使用。 國內狀況,SpringMVC 的使用率已經超過了 Struts2。
框架入口 基於 Servlet。 基於 Filter。 本質上沒有太大優點,只是配置方式不一樣。
框架設計思想 控制器基於方法級別的攔截,處理器設計爲單實例。 控制器基於類級別的攔截,處理器設計爲多實例。 因爲設計自己緣由,形成了 Struts2 一般只能設計爲多實例模式,相比於 SpringMVC 設計爲單實例模式,Struts2 會消耗更多內存。
參數傳遞 參數經過方法入參傳遞。 參數經過類的成員變量傳遞。 Struts2 經過成員變量傳遞參數,致使了參數的線程不安全,有可能引起併發問題。
與 Spring 整合 與 Spring 屬於同一家公司開發,可與 Spring 無縫整合。 須要整合包。 SpringMVC 能夠與 Spring 更輕鬆的整合。

SpringMVC架構圖

 

使用

入門程序

一、建立 maven web 工程,引入以下依賴:java

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

二、建立 SpringMVC 核心配置文件:mysql

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

三、配置 SpringMVC 核心監聽 Servlet 並加載核心配置文件:web

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

四、建立測試用的 pojo:spring

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

五、建立視圖:sql

<%@ 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

六、建立控制器:數據庫

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

七、測試:express

瀏覽器:

請求 'localhost:8080/list'

配置相關

處理器適配器和映射器

Spring MVC 有許多默認配置,以下:apache

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

查看處理器映射器和處理器適配器類能夠發現這兩個類是已過時的,以下:api

/*
 * 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整合

一、建立 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

二、使用逆向工程生成 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

三、引入 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

四、引入 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

五、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

六、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

七、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

八、引入 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

九、配置 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

九、編寫 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

十、編寫 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

十一、啓動項目,測試:

瀏覽器:

訪問 'http://localhost:8080/user/list'
相關文章
相關標籤/搜索