SpringMVC基础及使用

基本使用配置

Spring的jar包官网下载地址

http://repo.spring.io/release/org/springframework/spring/

springMVC流程

1、请求首先到达DispatcherServlet(控制器)

2和3、找Handlermapping,由它负责将url交给某个controller处理。

4、Controller与service等交互,处理完成后返回视图名称

5、根据返回的视图名称找到对应视图ViewResolver

6、返回视图。

 

HandlerMapping的继承结构:

默认是用BeanNameUrlHandlerMapping,

但一般都不用,而是使用DefaultAnnotationHandlerMapping

 

源码:

jsp:

domain:

controller:

 

配置:

1、加入jar包:

1、加入springmvc的jar

选择3.2.0的版本即可下载。

libs中包含了所有jar,其中有doc(文档包)和sources(源码包)

将doc和sources包去掉即可,

放入web工程lib中。

2、加入第三方包:

commons-logging-1.1.3.jar、commons-io-2.2.jar、commons-fileupload-1.3.jar

2A、基于配置文件方式配置DispatcherServlet(BeanNameUrlHandlerMapping,不常用)

2A.1、web.xml中配置

<servlet>

    <servlet-name>hello</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <load-on-startup>1</load-on-startup>

</servlet>

 

<servlet-mapping>

    <servlet-name>hello</servlet-name>

    <url-pattern>/</url-pattern>

</servlet-mapping>

其中DispatcherServlet可以用ctrl shift T快捷键找到,关联的源代码包为spring-webmvc-3.2.0.RELEASE-sources.jar。

这里的<url-pattern>/</url-pattern>而不是/*,/*的话会拦截jsp请求,而/不会,因此为了避免返回的jsp也被拦截,就用/。

 

2A.2、新建WEB-INF/hello-servlet.xml(就是以前的applicationContext.xml)

名字=web.xml中配置的servlet-name加上"-servlet"

内容:

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

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

        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-3.1.xsd">

        

    <bean name="/welcome.html" class="com.test.controller.WelcomeController"></bean>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="prefix" value="/WEB-INF/jsp/"></property>

        <property name="suffix" value=".jsp"></property>

    </bean>

</beans>

其中/welcome.html表示请求路径,InternalResourceViewResolver表示返回资源的处理,返回资源地址由配置的"prefix+返回字符串+suffix"组成。

2A.3、新建com.test.controller.WelcomeController类,

继承org.springframework.web.servlet.mvc.AbstractController,内容:

 

package com.test.controller;

 

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.AbstractController;

 

public class WelcomeController extends AbstractController {

 

    @Override

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("welcome");

        return new ModelAndView("welcome");

    }

}

如:请求地址为http://localhost:8077/springmvc_hello/welcome.html

则会到AbstractController,找到/WEB-INF/jsp/welcome.jsp返回给客户端。

 

2A.4创建/WEB-INF/jsp/welcome.jsp,用于返回

 

2B、基于注解方式配置DispatcherServlet(DefaultAnnotationHandlerMapping,常用)

2B .1、web.xml中配置DispatcherServlet

同2A.1。

2B .2、新建WEB-INF/hello-servlet.xml

类似2A.2的内容,不过不用配置具体的controller了

内容:

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

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

        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-3.1.xsd">

    <context:component-scan base-package="com.test.controller"/>

    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="prefix" value="/WEB-INF/jsp/"></property>

        <property name="suffix" value=".jsp"></property>

    </bean>

</beans>

其中component-scan表示扫描controller的包的根路径

<mvc:annotation-driven/>表示开启annotation

InternalResourceViewResolver含义同2A.3中一样。

2B.3新建com.test.controller.HelloController类

不用继承任何类,内容:

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

@Controller

@RequestMapping("cdd")

public class HelloController {

    @RequestMapping({"hello1", "hello2"})

    public String hello() {

        System.out.println("hello");

        return "hello";

    }

}

其中@Controller表示这是一个controller,扫描的时候可以被发现。

@RequestMapping表示匹配的路径,比如这里访问路径是:

http://localhost:8077/springmvc_hello/cdd/hello1

或者

http://localhost:8077/springmvc_hello/cdd/hello2

 

@RequestMapping("cdd")

等同于

@RequestMapping(value="cdd")

 

2A.4创建/WEB-INF/jsp/hello.jsp,用于返回

传参数:

接收参数

    public String hello(@RequestParam("username") String username) {

        System.out.println(username);

        return "hello";

    }

或者

    public String hello(String username) {

        System.out.println(username);

        return "hello";

    }

访问方式:

http://localhost:8077/springmvc_hello/cdd/hello1?username=kkk

则后台会打印出kkk,如果加了@RequestParam而又没有带参数访问会报400错误,因为

@RequestParam表示参数是请求的一部分。

@RequestParam("username")中的username是浏览器带的实参名,String username的username是形参名,两个可以不同。浏览器可以不为参数设置值例如"&username=",此时服务器接收到的是空串,但必须带上,否则报错。

请求路径参数

可以在方法中声明带某个参数的请求才进该方法,如:

    @RequestMapping(value="/users", method=RequestMethod.GET, params="jj")

    public String list(String a, Model model) {

        …

    }

此时带了jj这个参数的请求该路径请求才会进入这个方法。

http://localhost:8077/springmvc_hello/user/users不会进入该方法,

http://localhost:8077/springmvc_hello/user/users?jj才会进入

 

返回参数到jsp

    public String hello(String username, Model model) {

        System.out.println(username);

        model.addAttribute("hello", "12345");

        return "hello";

    }

 

jsp中用${username}即可输出"12345"。

hello方法的第一个参数是接收,第二个model参数是返回,类似于returnMap。model.addAttribute以键值对的方式设置返回值。

 

简化的方式:

model.addAttribute("666");

相当于

model.addAttribute("string", "666");

没有key时,默认以value的类型作为key,如:

model.addAttribute(new User());

相当于

model.addAttribute("user", new User());

 

struts2的参数是放到成员变量中的,因此为了避免冲突不能用单例,而springmvc的controller不存在这种问题因为可以用单例,不用每次都创建对象,效率会高一些,速度快一些。

 

REST风格:

用目录名代替参数的方式,如:

/user_show?id=120

/user/120

 

/user_delete?id=120

/user/120/delete

 

/user_update?id=120

/user/120/update

 

/user_list

/users或者/user/users

 

REST风格不等于使用了REST技术。

可以根据不同的请求类型GET、POST、PUT、DELETE等执行不同的方法,如:

    //链接到add页面时是GET请求,会访问这段代码

    @RequestMapping(value="/add",method=RequestMethod.GET)

 

    //在具体添加用户时,是post请求,就访问以下代码

    @RequestMapping(value="/add",method=RequestMethod.POST)

 

使用方法:

1、查询用户详情

    @RequestMapping(value="/{username}",method=RequestMethod.GET)

    public String show(@PathVariable String username, Model model) {

        model.addAttribute(users.get(username));

        return "user/show";

    }

这个show方法是根据用户名查询详情,返回单个用户详情页面,@PathVariable String username表示传入的用户id,users.get(username)是为了简化,user的查询过程,把所有user都放到users这个Map中,实际开发时应该从数据库查询user。

 

这个方法相当于拦截了本类中的所有查询详情的请求,然后根据不同的id查找user,不过如果没有找到,则model.addAttribute(null);会报错" Model object must not be null"

2、跳转到更新页面

    @RequestMapping(value="/{username}/update", method=RequestMethod.GET)

    public String update(@PathVariable String username, Model model) {

        model.addAttribute(users.get(username));

        return "user/update";

    }

value="/{username}/update"用于拦截类似123/update这种rest风格的跳转到更新页面的请求

 

3、更新

    

    @RequestMapping(value="/{username}/update", method=RequestMethod.POST)

    public String update(@PathVariable String username, @Validated User user, BindingResult br) {

        if (br.hasErrors()) {

            return "user/update";

        }

        users.put(username, user);

        return "redirect:/user/users";

    }

拦截地址跟跳转更新相同,只是拦截的是POST请求。

4、删除

    @RequestMapping(value="/{username}/delete", method=RequestMethod.GET)

    public String delete(@PathVariable String username) {

        users.remove(username);

        return "redirect:/user/users";

    }

springMVC标签库

jsp头部引入:

 

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>

使用:

    <sf:form method="post" modelAttribute="user">

        用户名:<sf:input path="username"/><sf:errors path="username"/><br/>

        密码:<sf:input path="password"/><sf:errors path="password"/><br/>

        昵称:<sf:input path="nickname"/><br/>

        邮箱:<sf:input path="email"/><sf:errors path="email"/><br/>

        <input type="submit" value="提交"/>

    </sf:form>

其中modelAttribute="user"表示自动取服务器端的user对象,提交的时候也封装user对象提交。

<sf:input path="username"/>user对象的一个属性,用path表示。

<sf:errors path="username"/>是出错提示,这里用的是JSR303。

开启model driven

显示新增页面的对应controller方法是:

 

    @RequestMapping(value="/add", method=RequestMethod.GET)

    public String add(@ModelAttribute("user")User user) {

        return "user/add";

    }

参数这里加了@ModelAttribute("user"),如果不加,则前台页面modelAttribute="user"会取得null,而<sf:input path="username"/>再取其属性显示就没有,会报错。

加了@ModelAttribute("user")以后,表示开启了model driven前台页面会自动创建user对象

JSR303验证

JSR303由sun公司提供。可以用于在bean类的get属性方法上增加注解的方式验证get出来的值是否合法。

用法:

1、导入bean-validator.jar

2、实体类的get属性的方法上加注解,如:

 

    @NotEmpty(message="用户名不能为空")

    public String getUsername() {

        return username;

    }

 

    @Size(min=1,max=10,message="密码长度110")

    public String getPassword() {

        return password;

    }

其中message是出错后的错误信息。

3、controller中这样使用:

    @RequestMapping(value="/add", method=RequestMethod.POST)

    public String add(@Validated User user, BindingResult br) {

        if (br.hasErrors()) {

            return "user/add";

        }

        users.put(user.getUsername(), user);

        // 重定向跳转

        return "redirect:/user/users";

    }

 

其中参数@Validated User user, BindingResult br

表示user类是需要验证的,验证的结果放到br中。通过br.hasErrors()判断验证是否通过。注意这里的BindingResult br定义必须紧跟@validate User user

4、前台jsp页面提示错误:

<sf:form method="post" modelAttribute="user">

        用户名:<sf:input path="username"/><sf:errors path="username"/><br/>

        密码:<sf:input path="password"/><sf:errors path="password"/><br/>

        昵称:<sf:input path="nickname"/><br/>

        邮箱:<sf:input path="email"/><sf:errors path="email"/><br/>

        <input type="submit" value="提交"/>

    </sf:form>

里面的<sf:errors>标签就是提示错误信息的位置,如:

 

重定向

return "redirect:/user/users"

重定向到另一个方法中去。

取request、response、session等容器对象

直接在方法参数中声明即可使用,如:

    @RequestMapping(value="login", method=RequestMethod.POST)

    public String login(String username, String password, HttpSession session) {

}

这样方法里面就可以使用session了。

异常处理

局部异常处理

处理某个控制器中的特定异常,如用户登录失败时自定义的UserException可以这样手动处理:

    // 局部异常处理,仅仅只能处理这个控制器中的异常

    @ExceptionHandler(value={UserException.class})

    public String handlerException(UserException e, HttpServletRequest request) {

        request.setAttribute("e", e);

        return "error";

    }

 

error.jsp可以用${e}输出异常信息

 

全局异常处理

全局异常处理对所有的控制器都起作用,方法是在hello-servlet.xml中配置:

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

        <property name="exceptionMappings">

            <props>

                <prop key="com.test.domain.UserException">error</prop>

            </props>

        </property>

    </bean>

其中SimpleMappingExceptionResolver是spring提供的一个异常处理类,对它的exceptionMappings属性进行注入Map即可,如注入的key是com.test.domain.UserException,value是error,当捕获到UserException异常就会自动跳转到error.jsp。

在error.jsp中用${exception}可以看到异常信息,exception这个变量名默认的。

定义resources静态资源文件排除springmvc拦截

css、js这种文件路径默认会被springmvc拦截,需要配置排除的资源路径,如在hello-servlet.xml中配置:

    <!-- 将静态文件指定到某个文件夹统一处理 -->

    <mvc:resources location="/resources/" mapping="/resources/**"/>

此时resources及其子目录下的所有资源文件都不会被springmvc拦截,可以直接访问,一般将所有资源文件都放到resources目录下,统一排除拦截。

 

文件上传

单文件上传

1、hello-servlet.xml中加入配置:

 

    <!-- 设置multipartResolver -->

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

        <property name="maxUploadSize" value="5000000"/>

    </bean>

 

其中CommonsMultipartResolver是处理文件上传的类,继承自CommonsFileUploadSupport

maxUploadSizemaxUploadSize的方法,表示文件最大值,这里是5M

 

2、页面上的form标签增加属性:enctype="multipart/form-data"

file的输入域,如:

        <input type="file" name="attach"/>

 

 

3、后台接收:

    @RequestMapping(value="/add", method=RequestMethod.POST)

    public String add(MultipartFile attach, HttpSession session) throws IOException {

        // 得到文件绝对路径

        String realPath = session.getServletContext().getRealPath("/resources/upload")

                                + "/" + attach.getOriginalFilename();

        // 保存文件

        FileUtils.copyInputStreamToFile(attach.getInputStream(), new File(realPath));

        

        System.out.println(attach.getOriginalFilename()); // 文件原始名

        System.out.println(attach.getName()); // 文件输入域的name,如"attach"

        

        // 重定向跳转

        return "redirect:/user/users";

    }

 

多文件上传

代码:

页面上:

 

        <input type="file" name="attachs"/>

        <input type="file" name="attachs"/>

        <input type="file" name="attachs"/>

后台:

    @RequestMapping(value="/add", method=RequestMethod.POST)

    public String add(@RequestParam("attachs")MultipartFile[] attachs, HttpSession session) throws IOException {

        // 保存文件

        for (MultipartFile multipartFile : attachs) {

            if (multipartFile.isEmpty()) {

                continue;

            }

            String realPath = session.getServletContext().getRealPath("/resources/upload")

                    + "/" + multipartFile.getOriginalFilename();

            FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), new File(realPath));

        }

        // 重定向跳转

        return "redirect:/user/users";

    }

跟单文件上传类似,只是参数必须加@RequestParam指定别名,接收类型改成了数组。遍历的时候要判断multipartFile.isEmpty(),因为用户可能没有选择所有的输入域。

返回JSON

1、加入jackson-all-1.9.4.jar

2、后台:

    @RequestMapping(value="/testjson")

    @ResponseBody

    public User testjson() {

        return users.get("ww");

    }

 

增加了@ResponseBody,方法直接返回user对象,会自动转成json字符串,如:

{"username":"ww","password":"12356","nickname":"王五","email":"cc@qq.com"}

也可以是Map等集合,如:

        Map<String, Object> map = new HashMap<String, Object>();

        map.put("name", "zs");

        map.put("age", "33");

        return map;

 

返回:

{"age":"33","name":"zs"}

 

进阶使用

配置

maven配置

pom.xml,内容:

<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>

    <artifactId>testspringmvc2</artifactId>

    <packaging>war</packaging>

    <version>0.0.1-SNAPSHOT</version>

    <name>testspringmvc2 Maven Webapp</name>

    <url>http://maven.apache.org</url>

    <dependencies>

        <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>3.8.1</version>

            <scope>test</scope>

        </dependency>

 

        <dependency>

            <groupId>javax.servlet</groupId>

            <artifactId>servlet-api</artifactId>

            <scope>provided</scope>

            <version>2.5</version>

        </dependency>

 

        <!-- spring mvc,以下的corecontextbeans可以不要-->

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-core</artifactId>

            <version>4.2.6.RELEASE</version>

        </dependency>

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-context</artifactId>

            <version>4.2.6.RELEASE</version>

        </dependency>

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-beans</artifactId>

            <version>4.2.6.RELEASE</version>

        </dependency>

 

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-webmvc</artifactId>

            <version>4.2.6.RELEASE</version>

        </dependency>

 

        <!-- jstl -->

        <dependency>

            <groupId>jstl</groupId>

            <artifactId>jstl</artifactId>

            <version>1.2</version>

        </dependency>

    </dependencies>

    <build>

        <finalName>testspringmvc2</finalName>

        <plugins>

            <!-- 部署web应用到Tomcat下的插件,并设置Tomcat启动端口与访问路径 -->

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>tomcat-maven-plugin</artifactId>

                <version>1.1</version>

                <configuration>

                    <port>80</port>

                    <path>/</path>

                    <uriEncoding>utf-8</uriEncoding>

                </configuration>

            </plugin>

        </plugins>

    </build>

</project>

 

ps:jstl的依赖根据实际情况而定(这里使用到了jstl,所以加了它的依赖)

web.xml配置

src/main/webapp/WEB-INF/web.xml

内容:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns="http://java.sun.com/xml/ns/javaee"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    version="2.5">

 

    <servlet>

        <servlet-name>hello</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>classpath*:aaa.xml</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>

 

    <servlet-mapping>

        <servlet-name>hello</servlet-name>

        <url-pattern>/</url-pattern>

    </servlet-mapping>

 

</web-app>

 

ps:就配置了一个DispatcherServlet

 

servlet的spring配置

src/main/resources/aaa.xml

内容:

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

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

        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-3.1.xsd">

 

    <!-- HandlerMapping -->

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

 

    <!-- HandlerAdapter -->

    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

 

    <!-- viewResolver -->

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

        <property name="prefix" value="/WEB-INF/jsp/"></property>

        <property name="suffix" value=".jsp"></property>

    </bean>

    

    <!-- 处理器 -->

    <bean name="/hello" class="com.test.controller.HelloWorldController"/>

</beans>

 

 

ps:

BeanNameUrlHandlerMapping:

表示将请求的URL和Bean名字映射,如URL为 "上下文/hello",则Spring配置文件必须有一个名字为"/hello"的Bean,上下文默认忽略。

 

SimpleControllerHandlerAdapter:

表示所有实现了org.springframework.web.servlet.mvc.Controller接口的Bean可以作为Spring Web MVC中的处理器,这是由它的supports方法决定的,它会调用controller的handleRequest方法。

 

 

 

BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter也可以不用配置,因为DispatcherServlet.properties提供了默认使用的

 

编写HelloWorldController处理类

src/main/java/com/test/controller/HelloWorldController.java

内容:

package com.test.controller;

 

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.Controller;

 

public class HelloWorldController implements Controller {

 

    @Override

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 1、收集参数、验证参数

        // 2、绑定参数到命令对象

        // 3、将命令对象传入业务对象进行业务处理

        // 4、选择下一个页面

        ModelAndView mv = new ModelAndView();

        // 添加模型数据 可以是任意的POJO对象

        mv.addObject("message", "Hello kkk!");

        // 设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面

        mv.setViewName("hello");

        return mv;

    }

 

}

 

 

ps:实现Controller接口只用实现handleRequest方法接口。

编写jsp

src/main/webapp/WEB-INF/jsp/hello.jsp

内容:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Hello World</title>

</head>

<body>

${message}

</body>

</html>

 

访问

http://localhost/hello

 

 

AbstractController和WebContentGenerator

AbstractController实现了Controller接口,因此可以直接继承AbstractController。

而AbstractController的handleRequest调用了抽象方法handleRequestInternal,因此只需要实现handleRequestInternal方法即可。

如:

 

 

AbstractController继承了WebContentGenerator,而WebContentGenerator里面有众多web配置,如

 

 

AbstractController在handleRequest时会对请求进行校验,也会把配置的是否使用缓存等放到response

 

 

 

 

WebContentGenerator的参数

cacheSeconds:设置浏览器缓存的时间

 

测试:

controller

 

spring配置:

    <bean name="/hello" class="com.test.controller.HelloWorldController">

        <property name="cacheSeconds" value="5"/>

    </bean>

 

页面:

 

设置缓存5秒

 

此时点this链接,间隔5秒以上才会去后台请求,5秒以内重复点击,页面显示的时间不变(对F5刷新无效)。

原理是response返回的Headers里面增加了Cache-Control:max-age=5参数。

 

ps:

1、对于一般的页面跳转(如超链接点击跳转、通过js调用window.open打开新页面都是会使用浏览器缓存的,在未过期情况下会直接使用浏览器缓存的副本,在未过期情况下一次请求也不发送);

2、对于刷新页面(如按F5键刷新),会再次发送一次请求到服务器的;

 

设置为0时,表示不缓存,每次都用最新

 

 

而设置成负数,则不会有Cache-Control属性,表示不控制缓存,由浏览器默认自己控制。

lastModified由服务端控制页面是否用缓存数据

服务端返回的header中可以写入Last-Modified属性,是一个时间,如:

 

客户端下次请求相同的链接时,会把该参数以If-Modified-Since带上,如:

 

服务端可根据该参数判断这段时间是否有内容改变,如果没有,可以像客户端返回304状态码,客户端就会使用缓存数据了,如:

 

 

 

springmvc中使用lastModified只需要controller实现LastModified接口即可。

如:

实现getLastModified方法即可。

 

DispatcherServlet执行dispatch方法时,会先做lastModified的判断,该方法会判断如果controller实现了LastModified接口,则会去调用它的getLastModified取得时间,判断该时间如果小于等于客户端传来的时间,则表示服务端没有更新,直接返回304,不执行handler.handle方法,也不进controller。

 

Etag由服务端控制页面是否用缓存数据

web.xml中增加shallowEtagHeaderFilter的配置

    <filter>

        <filter-name>shallowEtagHeaderFilter</filter-name>

        <filter-class>

            org.springframework.web.filter.ShallowEtagHeaderFilter

        </filter-class>

    </filter>

    <filter-mapping>

        <filter-name>shallowEtagHeaderFilter</filter-name>

        <servlet-name>hello</servlet-name>

    </filter-mapping>

 

这里可以用<servlet-name>也可以用<url-mapping>

 

Etag由filter实现,与lastModified类似,它会自动把服务端返回的内容通过md5加密生成一个字符串,response的Headers会带上它,key是Etag,客户端下次请求时会headers带上(key是If-None-Match),filter会自动根据传入的key和需要返回的内容生成的key判断倆key是否相同。如果相同则返回304,告诉客户端使用缓存。

 

ServletForwardingController(转向servlet)

将请求转给servlet处理,如:

 

ForwardServlet:

public class ForwardServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

 

public ForwardServlet() {

super();

}

 

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.getWriter().write("forwardServletGet");

    }

 

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.getWriter().write("forwardServletPost");

    }

 

}

 

web.xml中的servlet配置:

 

    <servlet>

        <servlet-name>ForwardServlet</servlet-name>

        <servlet-class>com.test.controller.ForwardServlet</servlet-class>

    </servlet>

 

springmvc配置:

 

    <bean name="/forwardToServlet"

        class="org.springframework.web.servlet.mvc.ServletForwardingController">

        <property name="servletName" value="ForwardServlet"></property>

    </bean>

 

此时访问该controller会将请求转到servlet处理:

 

 

源码可以看到直接forward过去了

 

ParameterizableViewController(配置转向页,精确匹配)

配置文件中指定转向页面,没有java代码,通常用于帮助页面等静态页。

如:

springmvc配置:

    <bean name="/parameterizableView"

        class="org.springframework.web.servlet.mvc.ParameterizableViewController">

        <property name="viewName" value="hello2" />

    </bean>

 

直接转向hello2.jsp

UrlFilenameViewController (配置转向页,模糊匹配)

与ParameterizableViewController类似,通过配置指定转向页面,不需要java代码,不过是模糊匹配路径。如:

 

    <bean name="/bbb/*"

        class="org.springframework.web.servlet.mvc.UrlFilenameViewController" >

        <property name="prefix" value="ccc/"/>

    </bean>

 

 

转向到页面ccc/hello3.jsp

 

匹配规则:

/index1/*:可以匹配/index1/demo,但不匹配/index1/demo/demo,如/index1/demo逻辑视图名为demo;

 

/index2/**:可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo,"/index2/demo"的逻辑视图名为demo,而"/index2/demo/demo"逻辑视图名为demo/demo;

 

/*.html:可以匹配如/abc.html,逻辑视图名为abc,后缀会被删除(不仅仅可以是html);

/index3/*.html:可以匹配/index3/abc.html,逻辑视图名也是abc;

? 匹配一个字符,如/index? 可以匹配 /index1 , 但不能匹配 /index 或 /index12

* 匹配零个或多个字符,如/index1/*,可以匹配/index1/demo,但不匹配/index1/demo/demo

** 匹配零个或多个路径,如/index2/**:可以匹配/index2路径下的所有子路径,如匹配/index2/demo,或/index2/demo/demo

 

如果我有如下模式,那Spring该选择哪一个执行呢?当我的请求为"/long/long"时如下所示:

/long/long

/long/**/abc

/long/**

/**

Spring的AbstractUrlHandlerMapping使用:最长匹配优先;

如请求为"/long/long" 将匹配第一个"/long/long"

请求"/long/acd" 则将匹配 "/long/**"

请求"/long/aa/abc"则匹配"/long/**/abc

请求"/abc"则将匹配"/**"

 

 

Annotation方式使用

配置和原理

使用的HandlerMapping和HandlerAdapter配置如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

<bean

class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

 

由于RequestMappingHandlerMapping和RequestMappingHandlerAdapter被springmvc默认使用,所以可以不用配置。

springmvc加载bean时,发现有@Controller配置的bean就会把它当做Controller来处理。

请求到来时,springmvc会通过反射调用Controller里面有@RequestMapping方法。

使用

 

使用如:

@Controller

@RequestMapping("t1")

public class HelloWorldController {

    @RequestMapping("tt")

    public String tt() {

        return "ccc/hello3";

    }

}

其中@Controller替代了继承Controller的方式,@RequestMapping

对应了路径的映射,这里的请求地址是:

http://localhost/t1/tt

 

 

也可以用返回ModelAndView的方式:

@Controller

@RequestMapping("t1")

public class HelloWorldController {

    @RequestMapping("tt")

    public ModelAndView tt() {

        ModelAndView modelAndView = new ModelAndView();

        modelAndView.setViewName("ccc/hello3");

        return modelAndView;

    }

}

 

 

请求的映射

springmvc对请求的各部分有不同的注解支持。

其中1、2、4、6是可变的,因此主要是对这几部分进行注解支持。

 

URL映射

通过@RequestMapping实现:

Controller类上的@RequestMapping是该Controller所有方法路径的前缀,类似于namespace,全路径=Controller类上的@RequestMapping路径+方法上的@RequestMapping路径。

 

需要被映射的方法必须加@RequestMapping,否则无法映射。

@RequestMapping的value值前面可以不加斜杠,springmvc会自动补上,但如果没有指定value值,则不会自动补上。

如:

Controller类和方法上都没有指定value值,此时通过localhost访问会报404,因为localhost默认映射/。

指定value值为空即可:

 

value值如果是/,则必须带/访问,如:

访问路径:

正确:http://localhost/t1/

错误:http://localhost/t1

 

而value为空的情况下,带不带/都可以访问

 

普通URI映射

@RequestMapping(value={"/test1", "/user/create"})

/test1或者/user/create都匹配

同一个Controller中有普通映射路径相同的方法(参数、请求类型等都相同),启动不会报错,但访问时会提示报错。

如:

 

不同的Controller中有普通映射路径相同的方法,启动就会报错(参数、请求类型等都相同)

如:

启动报错:

 

java.lang.IllegalStateException: Cannot map handler 'com.test.controller.HelloWorldController2#0' to URL path [/t1/t2]: There is already handler of type [class com.test.controller.HelloWorldController] mapped.

 

URI模板模式映射

@RequestMapping(value="/users/{userId}")

匹配如:/users/123456或/users/abcd

 

@RequestMapping(value="/users/{userId}/create")

匹配如:/users/123/create

 

@RequestMapping(value="/users/{userId}/topics/{topicId}")

匹配如:/users/123/topics/456

 

Ant风格的URL路径映射

@RequestMapping(value="/users/**"):

匹配如:/users/abc、/users/a/b、/users/a/b/c/d

但/users/123会被/users/{userId}优先匹配

 

@RequestMapping(value="/product?")

匹配如:/product1、/producta

不匹配如:/product、/productaa";

 

 

@RequestMapping(value="/product*")

匹配如:/productabc、/product

不匹配如:/productabc/abc

 

@RequestMapping(value="/product/*")

匹配如:/product/abc

不匹配如:/productabc

 

@RequestMapping(value="/products/**/{productId}")

匹配如:/products/abc/abc/123、/products/123

 

正则表达式匹配:

@RequestMapping(value="/products/{categoryCode:\\d+}-{pageNumber:\\d+}")

匹配如:/products/123-1

不匹配如:/products/abc-1

其中categoryCode和pageNumber是自定义的变量名字

 

请求类型限定映射

如:

@RequestMapping(value="tt", method=RequestMethod.POST)

可以在Controller上或者方法上加类型限定。比如此时如果GET访问,就会提示:

 

Controller上和方法上的请求类型过滤是独立的,必须都通过才能访问,如:

 

@Controller

@RequestMapping(value="t1", method=RequestMethod.POST)

public class HelloWorldController {

    @RequestMapping(value="tt", method=RequestMethod.GET)

    public String inCookie(HttpServletResponse response) {

        return "ccc/hello3";

    }

}

此时所有请求都不能通过,因为Controller上和方法上的请求类型没有交集。

 

ps:

请求一共有GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE七种。

 

不配置时,默认支持GET、POST、PUT、DELETE、HEAD五种

 

如果需要支持OPTIONS、TRACE,需要在web.xml中为DispatcherServlet配置初始化参数:dispatchOptionsRequest 和 dispatchTraceRequest 为true

 

请求参数限定映射

如:

    @RequestMapping(value="tt", params="kkk")

请求中必须有kkk参数,否则显示404.

@RequestMapping(value="tt", params="kkk=5")

必须有kkk参数,且它的值为5,否则404

如:http://localhost/t1/tt?kkk=5

如:

@RequestMapping(value="tt", params="!kkk")

必须没有kkk参数,如果有就404.

 

@RequestMapping(value="tt", params="kkk!=5")

没有kkk参数或者kkk不为5都可以,如果有kkk且为5404.

 

@RequestMapping(value="tt", params={"kkk=5", "ccc=6"})

kkk=5ccc=6才能访问,否则404

localhost/t1/tt?kkk=5&ccc=6

 

请求头参数限定映射

@RequestMapping(value="tt", headers="k3")

headers中包含名字为k3的参数才能访问,否则404

firefox搜索modify headers插件,安装打开设置


添加k3参数,start

 

访问即可带上k3的header了

 

@RequestMapping(value="tt", headers="!k3")

请求头中必须没有k3,否则404

@RequestMapping(value="tt", headers="k3!=3")

没有k3或者k3不等于3,否则404

@RequestMapping(value="tt", headers={"k2=3", "k3=4"})

k2=3且k3=4,否则404

数据绑定

自动绑定

ServletRequest、ServletResponse、HttpServletRequest、HttpServletResponse参数

如:

@RequestMapping(value = "hello3")

public String hello1(ServletRequest servletRequest, ServletResponse servletResponse, HttpServletRequest request, HttpServletResponse response) {

        return "ccc/hello3";

    }

 

自动绑定

InputStream、OutputStream、Reader、Writer

其中InputStream/OutputStream和Reader/Writer一次只能用其中一组。

测试:

页面:

<form action="upload" method="post" enctype="multipart/form-data">

    文件:<input type="file" name="k5"/><br/>

    <input type="submit" value="上传"/>

</form>

 

后台:

@RequestMapping(value = "upload")

public String upload(InputStream inputStream) throws IOException {

        String inputStr = StreamUtils.copyToString(inputStream, Charset.forName("utf-8"));

        System.out.println(inputStr);

        return "ccc/hello3";

    }

 

操作:

上传文件内容:

 

 

后台打印:

 

查看浏览器请求体:

 

由于inputStream中除了包含文件内容,还包含了

------WebKitFormBoundary405G2nXIHhp6lzwQ—

等这些分隔符信息,因此不能直接使用,需要用上传组件进行封装后使用。

 

自动绑定

WebRequest、NativeWebRequest

WebRequest可以直接操作session和ServletContext,(感觉这个用处不大,HttpServletRequest也可以getSession操作,不如直接绑HttpServletRequest)。

如:

第一次访问打印null,因为没有设置过,以后每次访问都会打印,都是从session中取。

 

 

NativeWebRequest可以取得HttpServletRequest,如:

 

HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);

(感觉这个没啥用,不如直接在方法上绑定HttpServletRequest)

 

自动绑定

HttpSession

如:

 

    @RequestMapping(value = "hello3")

    public String hello3(HttpSession session) {

        return "ccc/hello3";

    }

 

自动绑定方法参数

如:


访问:

http://localhost/ccc/hello3?userName=555&&group.groupName=666

 

打印:

会自动去找user的setUserName设置属性。对group.groupName属性也会自动创建Group对象设置属性。

但是对Map是不会自动设置的,如:

 

访问:

http://localhost/ccc/hello3?map.userName=555

打印:

 

 

自动绑定

Model、 Map 、ModelMap

如:

 

 

页面:

 

访问显示:

 

可以看出Model、 Map 、ModelMap设置的属性都被渲染到页面了,后台打印:,说明它们其实是同一个对象。这个对象同时继承了Model、 Map 、ModelMap。

 

 

断点看到这个类是BindingAwareModelMap。

继承结构如下:

 

 

 

如果使用返回ModelAndView的方式,也会自动添加model里的键值对进去(其实Model在调用方法之前就已经创建,这里只是暴露出来,所以不是model的键值对添加到了ModelAndView,而是model本来就有)。

如:

 

此时虽然modelAndView是新建的,但springmvc依然会把model里的键值对设置进去

 

 

使用modelAndView方式的方法返回以后,springmvc会将modelAndView里面设置键值对再覆盖到model里,传给页面渲染,如:

此时Model里的modelKey键被ModelAndView的覆盖了

 

自动绑定

Locale 、Principal

如:

可以用

        request.getLocale();

        request.getUserPrincipal();

代替。

@

RequestParam

页面参数可以直接对应到方法参数,而且名字一致,是因为java文件编译成class时为局部变量指定了LocalVariableTable属性,该属性中有参数的名字,springmvc反射时才能通过参数名字对应到页面参数变量。

 

如果在项目属性中将生成变量属性到class文件选项去掉,则不会生成LocalVariableTable属性.

此时springmvc在反射的时候就不知道方法对应参数名字,进而报错:

 

用@RequestParam指定参数名字可以让springmvc在反射的时候取得参数名字,而且名字可以自己定,如:

 

 

参数名uuu(不一定跟变量名一样用userName),

required=false,表示可以不传,默认是true,如果不用@RequestParam注解,而依赖LocalVariableTable属性直接写参数,则默认为false(但如果形参是基本数据类型,则必须传值)。

设置为true时不传会报错

设置为false时对象类型不传默认为null,Boolean包装类型默认为false,基本数据类型(int、long等)即使设置成false也不能不传,会报错,因此最好用基本数据类型的包装类型。

 

 

defaultValue="999",表示如果不传,默认值是999.

 

页面:

访问测试1:

有参数时取参数,没有参数时用默认的999

 

 

测试2:required默认为true,不传参数时会报错。

 

测试3:required设置为false时,参数是int型,不传参数时也会报错

 

 

多个同名参数处理

访问:

http://localhost/ccc/hello3?userName=555&userName=666

 

 

对于名字相同的参数(表单提交时为name指定),传统方式是用request.getParameterValues获取到数组,用数组方式处理。

springmvc会自动处理为逗号分隔的字符串,但如果参数值中本来就有逗号,就会引起混乱。

推荐方式使用数组或者List接收。

如:

 

用List接收时必须加@RequestParam,否则会报错

 

@PathVariable

取路径中的参数值(URI模板变量值)

如:

打印:


 

@CookieValue

绑定cookie值

如:

value:cookie的key

require:是否必须存在该key,默认true。

defaultValue:不存在该key时,取默认值

 

 

@RequestHeader

绑定请求头数据

同样有value、required、defaultValue,含义同上。

如:

 

@ModelAttribute

绑定视图层对象

1、方法参数上的@ModelAttribute

@ModelAttribute在方法参数上时自动加到model中(默认也开启@ModelAttribute,所以可以不加这个注解),不过@ModelAttribute可以通过value设置对象的key,如:

 

设置成了uuu

页面也用uuu取

访问测试效果:

 

 

注意:不加@ModelAttribute注解时,绑定model里时,key是类名的首字母小写,而不是变量名字,如:

 

此时页面需用user取,而不是user1取

 

2、普通方法上的@ModelAttribute

@ModelAttribute在普通方法声明上时,执行controller的任何一个方法都会先执行这个方法,并且会将返回值放入ModelMap中,如:

 

 

@ModelAttribute的方法可以接收参数,如:

 

3、@RequestMapping方法上的@ModelAttribute

@ModelAttribute在有@RequestMapping方法声明上时,返回值会自动加入Model(默认也开启@ModelAttribute,可以不加这个注解),但此时返回值已经不能指明jsp路径了,会自动根据访问路径找jsp(这样不太灵活),如:

3、@ModelAttribute匿名时的默认key和默认绑定

普通对象类型默认key为类名首字母小写,List类型默认key为类名List,如:

访问:

 

默认String类型的参数是不会自动绑定到Model的,其他的对象或者List等类型可以。方法参数为List时,不能用List接口,而只能用ArrayList等实现类。但方法声明上绑定Model则可以用List接口。

maven依赖关系及mybatis的集成

从依赖关系可以看出spring-webmvc依赖了spring-beans、spring-context、spring-core,因此后面3个其实可以不用配置到maven了。

 

集成mybatis时,所需的必要依赖如下:

 

        <!-- spring mvc -->

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-webmvc</artifactId>

            <version>${spring.version}</version>

        </dependency>

 

        <!-- mybatis -->

        <dependency>

            <groupId>org.mybatis</groupId>

            <artifactId>mybatis</artifactId>

            <version>3.3.1</version>

        </dependency>

        <dependency>

            <groupId>org.mybatis</groupId>

            <artifactId>mybatis-spring</artifactId>

            <version>1.2.4</version>

        </dependency>

        

        <!-- mysql-jdbc -->

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <version>5.1.39</version>

        </dependency>

 

        <!-- spring数据源(可以替换为druidc3p0dbcp等其他的数据源) -->

        <dependency>

            <groupId>org.springframework</groupId>

            <artifactId>spring-jdbc</artifactId>

            <version>${spring.version}</version>

        </dependency>

 

        <!-- servlet api -->

        <dependency>

            <groupId>javax.servlet</groupId>

            <artifactId>servlet-api</artifactId>

            <scope>provided</scope>

            <version>2.5</version>

        </dependency>

 

一般还会添加一些常用的如:

 

        <!-- jstl -->

        <dependency>

            <groupId>jstl</groupId>

            <artifactId>jstl</artifactId>

            <version>1.2</version>

        </dependency>

        <!-- fastjson -->

        <dependency>

            <groupId>com.alibaba</groupId>

            <artifactId>fastjson</artifactId>

            <version>1.1.33</version>

        </dependency>

        <!-- junit -->

        <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>4.12</version>

            <scope>test</scope>

        </dependency>

原理分析

配置

web.xml配置一个DisptchServlet,它实现了HttpServlet。

继承结构图:

HttpServletBean的init方法会去加载spring的bean,默认找配置文件的路径是WEB-INF/[servlet名字]-servlet.xml"。

也可以通过contextConfigLocation参数指定文件路径,如:

    <servlet>

        <servlet-name>hello</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>

            <param-name>contextConfigLocation</param-name>

            <param-value>classpath*:aaa.xml</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

    </servlet>

 

使用listener也可以配置spring加载的xml文件,如:

    <context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>classpath:aaa.xml</param-value>

    </context-param>

    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

不过一般只用servlet的配置就够了

 

加载的bean对象放到了XmlWebApplicationContext中。

 

总体设计

servlet中使用mvc一般想法是在servlet中创建一个map成员变量,key是url路径,value是controller实例,servlet的init执行时加载controller的配置文件,将配置的controller创建实体对象存放到这个map中,servlet的doPost和doGet等方法调用时通过url路径从map中get取出controller实体,执行controller的方法。

 

springmvc也是采用类似的思路,只是多了一层HandlerMapping封装,HandlerMapping对象内部有一个handlerMap,key是url路径,value即是controller对象,跟上面方案中的map一致,HandlerMapping作为Servlet的成员变量。servlet执行init时也是加载controller配置文件,生成controller实体,放到handlerMap中,再把handlerMap赋值给HandlerMapping成员变量,最后把HandlerMapping赋值给DispatcherServlet的成员变量

请求到来时,根据url路径去DispatcherServlet的HandlerMapping的handlerMap中去get出controller来执行里面的方法。

 

springmvc中的controller、HandlerMapping等都是作为bean管理。controller对象放到了HandlerMapping的成员变量handlerMap里面。请求来的时候会依次到每个HandlerMapping的handlerMap里面去找controller(根据path找),最先找到的就返回了。(HandlerMapping对象一共只有3个到4个,在配置controller的时候可以配置将它放到哪个HandlerMapping里,不配置也会使用默认的,如xml配置的controller会默认使用BeanNameUrlHandlerMapping,注解方式配置的controller则默认使用RequestMappingHandlerMapping

 

HandlerMapping实现了ApplicationContextAware接口,在其HandlerMapping这个bean的回调方法中,它会通过配置或注解找到url和controller的bean的对应关系(会根据配置的beanName调用beanFactory.getBean(beanName)),将其put到HandlerMap里面。

 

DispatcherServlet中的HandlerMappings:

 

HandlerMapping中的handlerMap

 

初始化流程

1、ContextLoaderListener加载spring配置,创建WebApplicationContext,把它放到ServletContext里

    <context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:server_spring.xml

</param-value>

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

如配置了spring的ContextLoaderListener,则它会在容器初始化完成后调用,它的主要作用是创建WebApplicationContext,把它放到ServletContext里,传给servlet,让servlet把它当做父容器,让所有servlet都可以使用里面的bean。

 

ContextLoaderListener实现contextInitialized方法:

 

其中的ApplicationContext默认是使用XmlWebApplicationContext,可以在<context-param>标签中配置成其他的。

默认的WebApplicationContext配置在ContextLoader.properties中

2、HttpServletBean的init()方法加载servlet的spring参数配置

DispatcherServlet的继承关系:

因此首先调用HttpServletBean的init()方法。

 

它默认会去读取WEB-INF/[servlet名字]-servlet.xml中的spring beans配置。将其放到spring的一个bean里面,后面通过这个bean就能取出配置。

 

3、FrameworkServlet实现initServletBean()方法

创建或获取WebApplicationContext(XmlWebApplicationContext)的BeanFactory用于创建bean,此时会判断有没有ContextLoaderListener的listener加载的容器,如果有,则会将它作为父容器(双亲父容器)。getBean时会采用类似双亲委派机制,先到父容器中找bean。

最后将创建好的BeanFactory也就是XmlWebApplicationContext放到DispatcherServlet的成员变量中。以便后面HandlerMapping初始化时去getBean(也就是Controller的Bean)

然后调用onRefresh方法。

 

 

4、DispatcherServlet实现onRefresh方法,创建beans,初始化DispatcherServlet使用的策略的组件(这些组件是DispatcherServlet的成员变量)。

其中核心组件是HandlerMapping、HandlerAdapters、ViewResolvers三个。

 

4.1、创建HandlerMapping

 

 

HandlerMapping一般会有多个,每个HandlerMapping的handlerMap成员变量中存放若干url和controller的对应关系。根据url查找controller的时候会依次到每个HandlerMapping的handlerMap成员变量中去get,最先被找到的controller返回使用。

 

 

常用的3个HandlerMapping实现类:SimpleUrlHandlerMappingBeanNameUrlHandlerMappingRequestMappingHandlerMapping三个,在controller的bean配置文件中可以指定该controller放到哪个HandlerMapping中去,默认是放到BeanNameUrlHandlerMapping,用注解配置的Controller默认放到RequestMappingHandlerMapping中。

 

由于HandlerMapping中的HandlerMap需要保存url和handler的映射关系,因此HandlerMapping对象创建后,需要为HandlerMap设置值。而这是通过Ware接口和BeanPostProsser机制实现的。

 

每个HandlerMapping主要有2步工作:第一是将配置文件中的controller实例化成bean,放到handlerMap成员变量中去。第二是将HandlerMapping赋值给DispatcherServlet的成员变量。

 

 

ps:HandlerMapping的设计:

HandlerMapping只有一个方法

    HandlerExecutionChain getHandler(HttpServletRequest request);

这个方法就是根据url查找对应的controller,不过返回一个HandlerExecutionChain对象,可以将HandlerExecutionChain理解成对Handler。也就是对Controller的一层包装,HandlerExecutionChain本身没有存在handlerMap里面,而是每次调用getHandler的时候临时包装称HandlerExecutionChain。

HandlerExecutionChain里面除了有Handler,还有一个inteceptor的数组。

3.1.1、实例化controller的bean放到handlerMap(注册handler)

ps:Ware接口的回调:

class ApplicationContextAwareProcessor implements BeanPostProcessor

实现了BeanPostProcessor接口,而这个类是springmvc默认会创建的,因此在创建每个bean对象的时候,它的postProcessBeforeInitialization方法必然被调用,它会调用

ApplicationContextAwareProcessor这个方法,如果目标bean实现了Ware接口,它会根据不同的子接口去回调不同的方法,如目标bean实现了ApplicationContextAware子接口,则会回调setApplicationContext方法。

因此实现Ware的各个子接口则可以让bean在创建时执行指定的回调代码。

 

SimpleUrlHandlerMapping实现了ApplicationContextAware接口,因此会在创建SimpleUrlHandlerMapping的时候自动调用setApplicationContext方法。

 

继承结构:

 

ApplicationObjectSupport中实现了setApplicationContext,它调用了initApplicationContext方法,这个方法在AbstractHandlerMappingSimpleUrlHandlerMapping中都有实现。

 

最后是交给registerHandlers去注册controller,将其放到handlerMap中

3.1.2、将handlerMapping赋值给DispatcherServlet的成员变量。

前面一个步骤都是在spring创建HandlerMapping实例对象的时候触发,HandlerMapping实例创建好以后,在handlerMap就已经有了controller,此时再把handlerMapping赋值给DispatcherServlet的成员变量即可。

 

 

 

问题1:urlMap什么时候put到controller中去?

这里以BeanNameUrlHandlerMapping为例,它在spring中的一个配置示例如下:

可以看出其实url和bean的对应关系全都在配置里面了,并且注入到了urlMap里,因此在genBean的时候就会自动注入进去。使用其他的HandlerMapping也是类似,如使用的注解方式,也是在配置(注解)中就完全定义好了url的bean的对应关系。

 

问题2:

class ApplicationContextAwareProcessor implements BeanPostProcessor

中的ApplicationContextAwareProcessor对象是否是必然被创建,默认就有这个BeanPostProcessor吗?

参见springioc

 

4.2、创建HandlerAdapter

HandlerAdapter的创建与HandlerMapping的创建类似,也是先到bean工厂中去找,找到则使用,没有找到则使用DispatcherServlet.properties配置文件中默认的。

 

HandlerAdapter有2个重要方法:

boolean supports(Object handler):判断某否handler是否用该HandlerAdapter

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler):

HandlerAdapter的几个实现:

 

SimpleServletHandlerAdapter

Handler是一个Servlet,请求交给servlet处理,返回的ModelAndView为null。

 

SimpleControllerHandlerAdapter

请求交给Controller处理,返回ModelAndView对象。

 

HttpRequestHandlerAdapter

请求交给HttpRequestHandler处理,返回的ModelAndView对象为null。

 

AbstractHandlerMethodAdapter(RequestMappingHandlerAdapter)

请求交给了HandlerMethod处理,最后调用了RequestMappingHandlerAdapter里面的handleInternal方法,返回ModelAndView对象。

平时用的@Controller注解就是使用的这个HandlerAdapter

RequestMappingHandlerAdapter跟其他几个HandlerAdapter的回调有所区别,它不是回调一个特定的方法,而是根据url,动态反射回调相应方法,极大增加了灵活性。

请求处理流程

1、调用DispacherServlet的doDispatch方法,请求最后调用到了doDispatch方法处理请求。该方法负责整个请求的处理:查找handlerChain,调用拦截器,handler的处理,视图渲染等工作。

2、根据url获取对应的HandlerExecutionChain(含Handler),然后根据handler找到HandlerAdapter.

 

 

查找HandlerAdapter时会调用它的supports方法,它会根据Handler是什么类而返回对应的HandlerAdapter。

 

3、调用HandlerExecutionChain的拦截器方法和handler的handle方法处理请求。

handle方法会返回ModelAndView类型。

 

 

4、调用render方法渲染。

 

遍历在spring中配置的viewResolver列表(一般只配置一个),找到以后,就调用它的resolveViewName去生成view。

如:

在ViewResolver都配置有实现View接口的类,如这里是JstlView,因此生成什么样的View,是ViewResolver决定的。

最后调用view的render方法渲染

 

view渲染:

view最后调用了renderMergedOutputModel渲染输出,该方法有众多实现,如Excel、pdf、json等。

 

以JstlView为例:

ModelAndView结构

ModelAndView由Object 类型的view和ModelMap类型的model构成。

 

model:

ModelMap继承了LinkedHashMap,用于存储键值对,扩展一些方法,如:

addAttribute(Object attributeValue)

addAllAttributes(Collection<?> attributeValues)

addAllAttributes(Map<String, ?> attributes)

mergeAttributes(Map<String, ?> attributes)

等方法。

其中addAttribute(Object attributeValue)、addAllAttributes(Collection<?> attributeValues)这两个方法存储时key是它的值对应的类名字。

 

view:

view可以是String或者View类型(使用RequestMappingHandlerAdapter时是String),为String时表示view的名字,为View时就是实际的View。

View是一个接口,它有一个render方法用于渲染页面等。

第一个参数通常是一个ModelMap,里面存的是给前端渲染的键值对。

 

posted @ 2021-01-27 19:11  吴克兢  阅读(189)  评论(0)    收藏  举报