继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

spring mvc学习笔记

编程猫
关注TA
已关注
手记 1
粉丝 3
获赞 7

hello world

步骤:

  1. 加入jar包
  2. 在web.xml中配置DispactherServlet
  3. 加入spring MVC的配置文件
  4. 编写处理请求的处理器,并标识为处理器
  5. 编写视图

准备工作

创建maven项目出现警告

problems
n 问题;疑难问题;令人困惑的情况;【数,物】习题
adj 成问题的;难处理的;关于社会问题的

Build path specifies execution environment J2SE-1.5. There are no JREs installed in the workspace that are strictly compatible with this environment.

生成路径指定执行环境 j2se-1.5。工作区中没有安装与此环境严格兼容的 jres。

原因

maven是项目管理工具,默认使用J2SE-1.5的库管理项目(我的是1.5,也可能是其他版本),
用maven-compiler-plugin插件,就是为了告诉maven使用什么版本的库来管理项目。

解决办法

在pom文件中加入以下代码

<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.zhangjinbang</groupId>
	<artifactId>springmvcdemo</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>springmvcdemo 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>
	</dependencies>
	<build>
		<finalName>springmvcdemo</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

其中关键在于在pom.xml文件的build标签中添加

<plugins>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-compiler-plugin</artifactId>
		<configuration>
		<source>1.8</source>
		<target>1.8</target>
		</configuration>
	</plugin>
</plugins>

更新项目

右键项目/maven/update project/勾选 force update of snapshows/releases

最终的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.zhangjinbang</groupId>
	<artifactId>springmvcdemo</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>springmvcdemo 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>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.3.20.RELEASE</version>
		</dependency>

	</dependencies>
	<build>
		<finalName>springmvcdemo</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

web.xml

<!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>
	<display-name>Archetype Created Web Application</display-name>
	<!-- The front controller of this Spring Web application, responsible for 
		handling all application requests -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

springmvc.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"
	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-4.3.xsd">
	
	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="springmvcdemo.handlers"></context:component-scan>
	
	<!-- 配置视图解析器 :如何把handler方法返回值解析为实际的物理视图-->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	
</beans>

HelloWorld.java

package springmvcdemo.handlers;

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

@Controller
public class HelloWorld {
	
	/**
	 * 通过@RequestMapping 注解来映射请求的URL
	 * 
	 * 返回值通过视图解析器解析为实际的物理视图,对于InternalResourceViewResolver视图解析器,会做如下的解析
	 * 通过prefix(前缀)+returnVal(返回值)+suffix(后缀)这样的方式得到实际的物理视图,然后做转发操作
	 * 
	 * /WEB-INF/views/success.jsp
	 * */
	@RequestMapping("/helloworld")
	public String hello() {
		System.out.println("hello world");
		return "success";
	}

}

WEB-INF/views/success.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<base href="<%=basePath%>">    
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	success Page
</body>
</html>

运行结果

浏览器访问: http://127.0.0.1:8080/springmvcdemo/helloworld
浏览器显示: success Page
控制台输出:hello world


配置文件

静态资源访问

在进行Spring MVC的配置时,通常我们会配置一个dispatcher servlet用于处理对应的URL。配置如下:

<servlet>
	<servlet-name>SpringMVC</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-mvc.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>SpringMVC</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

如果你的DispatcherServlet拦截"*.do"这样的有后缀的URL,就不存在访问不到静态资源的问题。

<servlet-mapping>
	<servlet-name>SpringMVC</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>

如果你的DispatcherServlet拦截"/",为了实现REST风格,拦截了所有的请求,那么同时对*.js,*.jpg等静态文件的访问也就被拦截了。

我们要解决这个问题。在web.xml文件中经常看到这样的配置<servlet-name>default</servlet-name>,这个配置的作用是:对客户端请求的静态资源如图片、js文件等的请求交由默认的servlet进行处理,如下所示:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.ico</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>

【注意】要写在DispatcherServlet的前面, 让 defaultServlet先拦截请求,这样请求就不会进入spring了,我想性能是最好的吧。

优雅REST风格的资源URL不希望带 .html 或 .do 等后缀.由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml中配置DispatcherServlet的请求映射,往往使用 *.do 、 *.xhtml等方式。这就决定了请求URL必须是一个带后缀的URL,而无法采用真正的REST风格的URL。

如果将DispatcherServlet请求映射配置为"/",则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。

如何让Spring框架能够捕获所有URL的请求,同时又将静态资源的请求转由Web容器处理,是可将DispatcherServlet的请求映射配置为"/"的前提。由于REST是Spring3.0最重要的功能之一,所以Spring团队很看重静态资源处理这项任务,给出了堪称经典的两种解决方案。

先调整web.xml中的DispatcherServlet的配置,使其可以捕获所有的请求:

<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

通过上面url-pattern的配置,所有URL请求都将被Spring MVC的DispatcherServlet截获。

采用<mvc:default-servlet-handler />

在springMVC-servlet.xml中配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。

一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所有的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:

<mvc:default-servlet-handler default-servlet-name="所使用的Web服务器默认使用的Servlet名称" />

推荐】在spring3.0.4以后版本提供了mvc:resources , 使用方法:

<mvc:resources mapping="/resources/**" location="/resources/" />
  • /* 指文件夹下的所有文件(不包括子文件夹)
  • /** 指文件夹下的所有文件(包括子文件夹)

如果出现下面的错误,可能是没有配置<mvc:annotation-driven />的原因。

报错WARNING: No mapping found for HTTP request with URI [/mvc/user/findUser/lisi/770] in DispatcherServlet with name 'springMVC'

<mvc:default-servlet-handler />将静态资源的处理经由Spring MVC框架交回Web应用服务器处理。而<mvc:resources />更进一步,由Spring MVC框架自己处理静态资源,并添加一些有用的附加值功能。

首先,<mvc:resources />允许静态资源放在任何地方,如WEB-INF目录下、类路径下等,你甚至可以将JavaScript等静态文件打到JAR包中。通过location属性指定静态资源的位置,由于location属性是Resources类型,因此可以使用诸如"classpath:"等的资源前缀指定资源位置。传统Web容器的静态资源只能放在Web容器的根路径下,<mvc:resources />完全打破了这个限制。

其次,<mvc:resources />依据当前著名的Page Speed、YSlow等浏览器优化原则对静态资源提供优化。你可以通过cacheSeconds属性指定静态资源在浏览器端的缓存时间,一般可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的Expires 和 Cache-Control值。

在接收到静态资源的获取请求时,会检查请求头的Last-Modified值,如果静态资源没有发生变化,则直接返回303相应状态码,提示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。

springMVC-servlet中添加如下配置:

<mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>

以上配置将Web根路径"/"及类路径下/META-INF/publicResources/的目录映射为/resources路径。假设Web根路径下拥有imagesjs这两个资源目录,在images下面有bg.gif图片,在js下面有test.js文件,则可以通过 /resources/images/bg.gif/resources/js/test.js访问这二个静态资源。

假设WebRoot还拥有images/bg1.gifjs/test1.js,则也可以在网页中通过 /resources/images/bg1.gif/resources/js/test1.js 进行引用。

标签库

form标签

form标签可以更快的开发出表单页面,而且可以更方便的进行表单值的回显。

可以通过modeAttribute属性指定绑定的模型属性,若没有指定该属性,则默认从request域对象中读取command的表单bean。如果该属性值也不存在,则会发生错误。

spring mvc认为表单一定是要进行回显的,即便是你第一次来。

在jsp页面中加入

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

表单(get方式请求/emp得到的界面 以下代码假设文件名为input.jsp

<form:form action="emp" method="post" modeAttribute="employee">
    <!--path对应html input的name属性-->
    LastName:<form:input path="lastName"/>
    Email:<from:input path="email"/>
    <%
    	Map<String,String> genders=new HashMap();
    	genders.put("1","Male");
    	genders.put("0","Female");
    	request.setAttribute("genders",genders);
    %>
    Gender:<form:radiobuttons path="gender" items="${genders}"/>
    Department:<form:select path="department" items="${departments}" itemLabel="departmentName" itemValue="id"></form:select>
    <input type="submit" value="Submit"/>
</form:form>

控制器中应该这么写

@Controller
public class EmployeeHandler{
    @Autowirte
    private DepartmentDao departmentDao;
    @RequestMapping(value="/emp",method=RequstMethod.GET)
    public String input(Map<String,Object> map){
        map.put("departments",departmentDao.getDepartments());
        map.put("employee",new Employee());//用于数据的回显
        return "input";//WEB-INF/views/input.jsp
    }
}

控制器

@RequestMapping

@RequestMapping 可以修饰方法,也可以修饰类

Spring MVC使用@RequestMapping注解为控制器制定可以处理哪些URL请求

  • 类定义处:提供初步的请求映射洗脑洗,相对于web应用的根目录
  • 方法处:提供进一步的细分映射信息,相对于类定义处的URL,若类定义处未标注@RequestMapping,则方法处标记的URL相对于web应用的根目录。

DispatcherServlet截获请求后,就通过控制器上的@RequestMapping提供的映射信息确定请求所对应的处理方法。

RequestMapping接口的源码如下,里面定义了七个属性

public interface RequestMapping extends Annotation {

  // 指定映射的名称
    public abstract String name();

  // 指定请求路径的地址
    public abstract String[] value();

  // 指定请求的方式,是一个RequsetMethod数组,可以配置多个方法
    public abstract RequestMethod[] method();

  // 指定参数的类型
    public abstract String[] params();
     
    public abstract String[] headers();

    /**
     * consumes属性 :指定处理请求的提交内容类型(Content-Type),
     * 例如application/json, text/html;
     */
    public abstract String[] consumes();

  // 指定返回的内容类型
    /**
     * produces属性:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回,
     * 本人实验了一下,如果请求的头中不包含这个属性也能有正确的返回值,但是如果有Accept这个属性,
     * 那么前后要匹配,否则输出报错;
     */
    public abstract String[] produces();
}

@RequestMapping除了可以使用请求URL映射请求外,还可以使用请求方法,请求参数及请求头的映射条件,@RequestMapping的valuemethodparamsheads分别表示请求URL、请求方法、请求参数及请求头的映射条件,他们之间是关系,联合使用对各条件可以让请求映射更加精确化

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

params和heads支持简单的表达式

  • param1:表示请求必须包含为param1的请求参数
  • !param1:表示请求不能包含为param1的请求参数
  • param1!=value1:表示请求包含param1的请求参数,但是其值不能为value1
  • {"param1=value","param2"}:请求必须包含名为param1和param2的请求参数,且param1参数的值必须为value1
@RequestMapping(value="/test",params={"username","age!=10"})
@RequestMapping(value="/test",params={"username","age!=10"},headers={"Accept-Language=zh-CN,zh;q=0.8"})

处理多个 URI
@RequestMapping 来处理多个 URI
你可以将多个请求映射到一个方法上去,只需要添加一个带有请求路径值列表的 @RequestMapping 注解就行了。

@RestController
@RequestMapping("/home")
public class IndexController {

    @RequestMapping(value = {
        "",
        "/page",
        "page*",
        "view/*,**/msg"
    })
    String indexMultipleMapping() {
        return "Hello from index multiple mapping.";
    }
}

如你在这段代码中所看到的,@RequestMapping 支持统配符以及ANT风格的路径。前面这段代码中,如下的这些 URL 都会由 indexMultipleMapping() 来处理:

localhost:8080/home 
localhost:8080/home/ 
localhost:8080/home/page 
localhost:8080/home/pageabc 
localhost:8080/home/view/ 
localhost:8080/home/view/viewlocalhost:8080/home 
localhost:8080/home/ 
localhost:8080/home/page 
localhost:8080/home/pageabc 
localhost:8080/home/view/ 
localhost:8080/home/view/view

@RequestMapping支持Ant风格的URL,Ant风格资源地址支持3种通配符

通配符 说明
? 匹配文件名中的一个字符
* 匹配文件名中的任意字符
** **匹配多层路径

@PathVariable

映射URL绑定的占位符
带占位符的URL是spring3.0新增的功能,该功能在spring mvc向REST目标挺进发展过程中具有里程碑意义

通过@@PathVariable 可以将URL中占位符参数绑定到处理器方法的入参中:

@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") Integer id){
	userDao.delete(id);
	return "reirect:/user/list.action";
}

注意,占位符中大括号中的名字需要与@PathVariable注解中的value的值一致。

REST

REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

  • REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。

  • URI 的设计只要负责把资源通过合理方式暴露出来就可以了。对资源的操作与它无关,操作是通过 HTTP动词来体现,所以REST 通过 URI 暴露资源时,会强调不要在 URI 中出现动词。

比如:左边是错误的设计,而右边是正确的

GET /rest/api/getDogs --> GET /rest/api/dogs 获取所有小狗狗 
GET /rest/api/addDogs --> POST /rest/api/dogs 添加一个小狗狗 
GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一个小狗狗 
GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 删除一个小狗狗
--------------------- 

Springmvc提供了一个filter,可以把delete请求和put请求转化成post请求。
在web.xml中配置过滤器:org.springframework.web.filter.HiddenHttpMethodFilter

<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
        
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

发送一个get请求:

<a href="springmvc/helloworld/1">Test Rest GET</a>

发送一个post请求:

<form action="springmvc/helloworld/1" method="post">
     <input type="submit" value="Test REST POST"/>
</form>

发送一个put请求,发送put请求的时候,我们需求添加一个隐藏域。

<form action="springmvc/helloworld/1" method="post">
      <input type="hidden" name="_method" value="PUT" />
      <input type="submit" value="Test REST PUT"/>
</form>

发送一个DELETE请求,发送DELETE请求的时候,我们需求添加一个隐藏域。

<form action="springmvc/helloworld/1" method="post">
      <input type="hidden" name="_method" value="DELETE" />
      <input type="submit" value="Test REST DELETE"/>
</form>

spring 控制层代码

package com.gwolf.springmvc.handlers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/springmvc")
public class HelloWorldController {
                
        @RequestMapping(value="/helloworld/{id}",method=RequestMethod.GET)
        public String hello(@PathVariable Integer id) {
                System.out.println("test rest get:" + id);
                
                return "success";
        }
        
        @RequestMapping(value="/helloworld",method=RequestMethod.POST)
        public String hello() {
                System.out.println("test POST:" );
                
                return "success";
        }
        
        @RequestMapping(value="/helloworld/{id}",method=RequestMethod.DELETE)
        public String helloDelete(@PathVariable Integer id) {
                System.out.println("test rest delete:" + id);
                
                return "success";
        }
        
        @RequestMapping(value="/helloworld/{id}",method=RequestMethod.PUT)
        public String helloPUt(@PathVariable Integer id) {
                System.out.println("test rest put:" + id);
                
                return "success";
        }

}

@RequestParam

以前写controller层的时候都是默认带上 @RequestParam 的, 今天发现不加@RequestParam 也能接收到参数

下面我们来区分一下加与不加的区别
这里有两种写法

写法一:

@RequestMapping("/list")
public String test(@RequestParam  Long parentId) {
	...
}

写法二:

@RequestMapping("/list")
public String test( Long parentId) {
	...
}

第一种必须带有参数,也就是说你直接输入localhost:8080/list 会报错 不会执行方法 只能输入localhost:8080/list?parentId=...才能执行相应的方法
第二种 可带参数也可不带参数 就是说你输入localhost:8080/list 以及 localhost:8080/list?parentId=...方法都能执行

当然你也可以设置 @RequestParam 里面的required为false(默认为true 代表必须带参数) 这样就跟第二种是一样的了
如下:

@RequestMapping("/list")

public String test(@RequestParam(required=false)  Long parentId) {
	...
}

当然你还可以设置里面的defaultValue的属性
如下:

@RequestMapping("/list")

public String test(@RequestParam(defaultValue="0")  Long parentId) {
	...
}

里面还有一个value属性也讲一下, 前面所有的方法 传入的参数必须为parentId 才能接收到值
但是如果你加了value属性

@RequestMapping("/list")
public String test(@RequestParam(value="id")  Long parentId) {

}

这样会用id 代替parentId 也就是说你地址里面传入的参数名称为id localhost:8080/list?id=... 这种


@RequestHeader

@RequestHeader 作用是映射请求头信息,用法与@RequestParam相似,使用机会较少,了解即可


@CookieValue

用法与@RequestParam相似,获取cookie

@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("cookieName") String cookieVal){
	...	
}

关于重定向

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理,如果返回字符串中带***forward:***或***redirect:***前缀时,Spring MVC会对他们进行特殊处理,将***forward:***和***redirct:***当成指示符,其后的字符串作为URL来处理。

  • redirect:success.jsp 会完成一个到success.jsp的重定向操作;
  • forward:success.jsp 会完成一个到success.jsp的转发操作;

使用POJO对象绑定请求参数的值

Spring MVC会按请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值。支持级联属性。如dept.deptId、dept.address.tel等

@RequestMapping("/test")
public String test(User user){
	...
}

请求:.../test?userName=张三&dept.deptId=1&dept.address.tel=6442255


使用Servlet API作为入参

@RequestMapping("/test")
public String test(HttpServletRequest request,HttpServletResponse response){
	...
}

很明显,要使用Sevlet的一些API,只要直接将其作为参数传入即可。你需要request,就传入request, 需要session,就传入session。springmvc支持传入的Sevlet原生api一共有以下这些:

  1. HttpServletRequest
  2. HttpServletResponse
  3. HttpSession
  4. java.security.Principal
  5. Locale
  6. InputStream
  7. OutputStream
  8. Reader
  9. Writer

处理模型数据

Spring MVC 提供了以下几种途径输出模型数据:

  • ModelAndView: 处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据
  • Map 及 Model: 入参为org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。
  • @SessionAttributes: 将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性
  • @ModelAttribute: 方法入参标注该注解后, 入参的对象就会放到数据模型中

ModelAndView

通过ModelAndView构造方法可以指定返回的页面名称,也可以通过setViewName()方法跳转到指定的页面 ,
使用addObject()设置需要返回的值,控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。
SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中.

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    String viewName = SUCCESS;
    ModelAndView modelAndView = new ModelAndView(viewName);
        
    //添加模型数据到 ModelAndView 中.
    modelAndView.addObject("time", new Date());
        
    return modelAndView;
}

添加模型数据的方法:

MoelAndView addObject(String attributeName, Object attributeValue);
ModelAndView addAllObject(Map<String, ?> modelMap);

设置视图的方法:

void setView(View view );
void setViewName(String viewName);

Map 及 Model

Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存储模型数据
具体步骤
Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
如果方法的入参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据

//目标方法可以添加 Map 类型(实际上也可以是 Model 类型或 ModelMap 类型)的参数.
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    System.out.println(map.getClass().getName()); 
    map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
    return SUCCESS;
}

@SessionAttributes

若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes, Spring MVC 将在模型中对应的属性暂存到 HttpSession 中。
@SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中

@SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 的属性添加到会话中。
@SessionAttributes(value={“user1”, “user2”}) 
@SessionAttributes(types={User.class, Dept.class}) 
@SessionAttributes(value={“user1”, “user2”}, types={Dept.class})

示例一:

@SessionAttributes({"user"})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{
	
	private static final String SUCCESS = "success";

	@RequestMapping("/testSessionAttributes")
	public String testSessionAttributes(Map<String,Object> map){
		User user = new User("tom","6442255","tom@qq.com","15");

		// user会放在request域中,因为类上面加了@SessionAttributes({"user"})
		// 所以user也会放在session域中
		map.put("user",user);


		return SUCCESS;
	}
}

示例二:

@SessionAttributes(value={"user"},type={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{
	
	private static final String SUCCESS = "success";

	@RequestMapping("/testSessionAttributes")
	public String testSessionAttributes(Map<String,Object> map){
		User user = new User("tom","6442255","tom@qq.com","15");
		
		map.put("user",user);//加到request中,这个是"user",也会被加入到session中
		map.put("school","atguigu");//加到request中,这个是字符串类型,也会被加入到session中

		return SUCCESS;
	}
}

@ModelAttribute

在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute 的方法。
在方法的入参前使用@ModelAttribute 注解:

举个在使用中的例子,假如要更新一个用户,可以通过@ModelAttribute修饰一个方法,这个方法从数据库中先查询出要修改的信息。在执行修改用户的信息方法之前可以先获取要修改的用户的信息,然后更新用户的方法中,拿到这个用户信息,只需要set要修改的属性即可,其他没有修改的属性仍然会保持原来的值,而不会像新建的用户对象没有set的属性为NULL那样。

运行流程(要自己理解)

  1. 执行@ModelAttribute注解修饰的方法:从数据库中取出对象,把对象放入到Map中,键为 user
  1. SpringMVC 从Map中取出User对象,并把表单请求参数赋给该User对象的对应属性
  2. SpringMVC 把上述对象传入目标方法的参数

下面例子,在获得请求/helloWorld 后,populateModel方法在helloWorld方法之前先被调用,它把请求参数(/helloWorld?abc=text)加入到一个名为attributeName的model属性中,在它执行后 helloWorld被调用,返回视图名helloWorld和model已由@ModelAttribute方法生产好了。

@Controller 
public class HelloWorldController { 
    @ModelAttribute 
    public void populateModel(@RequestParam String abc, Model model) { 
         model.addAttribute("attributeName", abc); 
      } 

    @RequestMapping(value = "/helloWorld") 
    public String helloWorld() { 
       return "helloWorld"; 
        } 
 }

这个例子中model属性名称和model属性对象有model.addAttribute()实现,不过前提是要在方法中加入一个Model类型的参数。
  当URL或者post中不包含次参数时,会报错,其实不需要这个方法,完全可以把请求的方法写成如下,这样缺少此参数也不会出错

@RequestMapping(value = "/helloWorld") 
public String helloWorld(String abc) { 
    return "helloWorld"; 
}
@ModelAttribute注解返回具体类的方法
@ModelAttribute 
public Account addAccount(@RequestParam String number) { 
	
    return accountManager.findAccount(number); 
    // 这种情况,model属性的名称没有指定,它由返回类型隐含表示,
    //  如这个方法返回Account类型,那么这个model属性的名称是account。
    //  相当于:model.addAttribute("account", accountManager.findAccount(number)); 
} 
@ModelAttribute(value="")注释返回具体类的方法

这个例子中使用@ModelAttribute注释的value属性,来指定model属性的名称。model属性对象就是方法的返回值。它无须要特定的参数。

@Controller 
public class HelloWorldController { 
	@ModelAttribute("attributeName") 
	public String addAccount(@RequestParam String abc) { 
    
	    return abc; 
	    //相当于 model.addAttribute("attributeName",abc);

	} 
    

	@RequestMapping(value = "/helloWorld") 
	public String helloWorld() { 

	    return "helloWorld"; 

	} 
}
@ModelAttribute和@RequestMapping同时注释一个方法
@Controller 
public class HelloWorldController { 
    @RequestMapping(value = "/helloWorld.do") 
    @ModelAttribute("attributeName") 
    public String helloWorld() { 
        return "hi"; 
    } 
}

这时这个方法的返回值并不是表示一个视图名称,而是model属性的值,视图名称由RequestToViewNameTranslator根据请求"/helloWorld.do"转换为逻辑视图helloWorld。
Model属性名称有@ModelAttribute(value=””)指定,相当于在request中封装了key=attributeName,value=hi。

@ModelAttribute注释一个方法的参数
@Controller 
public class HelloWorldController { 
    @ModelAttribute("user") 
    public User addAccount() { 
        return new User("jz","123"); 
    } 

    @RequestMapping(value = "/helloWorld") 
    public String helloWorld(@ModelAttribute("user") User user) { 
        user.setUserName("jizhou"); 
        return "helloWorld"; 
    } 
}

在这个例子里,@ModelAttribute(“user”) User user注释方法参数,参数user的值来源于addAccount()方法中的model属性。
  此时如果方法体没有标注@SessionAttributes(“user”),那么scope为request,如果标注了,那么scope为session

@ModelAttribute具有如下三个作用:

绑定请求参数到命令对象:

放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用。

其实  @ModelAttribute 此处对于供视图页面展示来说与 model.addAttribute(“attributeName”, abc); 功能类似。

@Controller 
public class HelloWorldController { 
    @RequestMapping(value = "/helloWorld") 
    public String helloWorld(@ModelAttribute User user) { 
        return "helloWorld"; 
     } 
}

此处多了一个注解@ModelAttribute(“user”),它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。

暴露@RequestMapping 方法返回值为模型数据:
放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。

public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user) {...}

可以看到返回值类型是命令对象类型,而且通过 @ModelAttribute(“user2”) 注解,此时会暴露返回值到模型数据( 名字为user2 ) 中供视图展示使用

@ModelAttribute 注解的返回值会覆盖 @RequestMapping 注解方法中的 @ModelAttribute 注解的同名命令对象

暴露表单引用对象为模型数据:
放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法( @RequestMapping 注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用


视图解析器

视图解析器如何解析视图

  • 无论请求处理方法返回值类型是String、ModelAndView、还是View,Spring MVC都会在内部把它装配成ModelAndView类型,它包含了逻辑名和模型对象的视图。
  • Spring MVC借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是JSP,也可能是Excel、JFreeChart等各种表现形式的视图。
  • 对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。

JSP是最常见的视图技术,可以使用InternalResourceViewResolver作为视图解析器

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

若项目中使用了JSTL,则Spring MVC会自动把视图由InternalResourceView转换为JstlView。

若使用了JSTL的fmt标签则需要在spring MVC的配置文件中配置国际化资源文件

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n"></property>
</bean>

若希望直接响应通过Spring MVC渲染的页面,可以使用 mvc:view-controller 标签实现 ,可以直接响应转发的页面,而无需经过handler的方法。

<!-- 配置直接转发的页面
	可以直接响应转发的页面,而无需经过handler的方法。
	在实际开发中通常都需配置 mvc:annotation-driven
-->
<mvc:view-controller path="springmvc/testJstlView" view-name="success"/>
<!--如果使用了上面的,还希望原来@RequestMapping起作用,就需要加上下面的标签-->
<mvc:annotation-driven></mvc:annotation-driven>

自定义视图

若希望使用Excel展示数据列表,仅需要扩展Spring MVC提供的AbstractExcelView或AbstractJExcelView即可。实现buildExcelDocument()方法,在方法中使用模型数据对象构建Excel文档就可以了。

AbstractExcelView基于POI API,而AbstractJExcelView是基于JExcelAPI的

视图对象需要配置IOC容器中的Bean,使用BeanNameViewResolver作为视图解析器即可

若希望直接在浏览器中下载Excel文档,则可以设置响应头为 attachment;filename=xxx.xls

//通过Component注解将视图添加到IOC容器中去
@Component
public class HelloView implements View{
    @Override
    public String getContentType(){
        return "text/html";
    }
    @Override
    public void render(Map<String,?> model,HttpServletRequest request,HttpServletResponse response) throws Exception{
        reponse.getWriter().print("hello view,time:"+new Date());
    }
}

配置视图解析器

<!--
	配置视图解析器:
	如何把handler方法返回值解析为实际的物理视图
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>
<!--
	配置视图BeanNameViewResolver解析器:
	使用视图的名字来解析视图
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<!--通过order属性来定义视图解析器的优先级,order值越小优先级越高-->
    <property name="order" value="100"></property>
</bean>

注意:上面的配置中InternalResourceViewResolver优先级没有BeanNameViewResolver的优先级高,因为InternalResourceViewResolver的优先级默认是Integer.MAX_VALUE (也就是int类型的最大值,order值越大,优先级越高)

测试代码

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{
    @RequestMapping("/testView")
    public String testView(){
        return "helloView";
    }
}

超链接

<a href="springmvc/testView">Test View</a>

以上的执行流程:

  • 点击超链接,请求被映射到testView()方法上
  • 因为BeanNameViewResolver的优先级高,所以先根据testView()方法的返回值 "helloView" 查找IOC容器中是否存在idhelloView的视图
  • IOC容器中存在helloView视图,所以程序的最终运行结果为浏览器中显示**hello view,time:Sat Dec 13 21:46:02 CST 2018**

数据绑定

  • 数据类型转换的问题

  • 数据类型格式化的问题

  • 数据校验的问题

Spring MVC主框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象。

DataBinder调用装配在Spring MVC上下文中的**ConversionService组件进行数据类型转换数据格式化工作。将Servlet中的请求信息填充到入参对象中。

DataBinder调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果bindingData对象。

Spring MVC抽取BindingResult中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。

数据转换

Spring MVC上下文内建了很多转换器,可完成大多数java类型的转换工作。

自定义类型转换器

ConversionService是Spring类型转换体系的核心接口。

可以利用ConversionServiceFactory在Spring的IOC容器中定义一个ConversionService,Spring将自动识别出IOC容器中的ConversionService。并在Bean属性配置及Spring MVC处理入参绑定等场合使用它进行数据绑定的转换。

可通过ConversionServiceFactoryBean的Converters属性注册自动一的数据类型转换器

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
    	<set>
            <bean class="com.zhangjinbang.springmvc.UserConverter"></bean>
        </set>
    </property>
</bean>

Spring支持的转换器

Spring定义了3种类型转换的接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:

  • Converter<S,T>:将S类型转换为T类型的对象。
  • ConverterFactory:将相同系列多个“同质”Converter封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number子类(Integer,Long,Double等)对象)可使用该转换器工厂类。
  • GeneicConveter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换。

具体实现:

前台传递的数据形式:ZhangSan:888

http://localhost:8080/springMVCTest/test/conversionTest.action?person=Tom:520

将ZhangSan:888字符串转换成Person对象

public class Person{
    private String username;
    private String password;
    //getter setter...
}

实现过程:

  1. 定义转换器类,实现Converter<S,T>接口
  2. 装配自定义的conversionService

定义类型转换类

@Component
public class StringToPersonConverter implements Converter<String,Person>{
    @Override
    public Person convert(String source){
        
        Person p = new Person();
        if(source != null){
            String[] items = source.split(":");
            if(items != null && items.length == 2)
            {
                 p.setUsername(items[0]);
                 p.setPassword(items[1]);
            }

        }
        
        return p;
    }
}

装配自定义的conversionService

<!--装配自定义的conversionService-->
<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService" class="org.springframework.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <!--
				ref标签用来引用另外一个bean
				这里引用的是id为stringToPersonConveter的转换器
				因为在定义StringToPersonConveter中使用了@Component
				注解,所以它在IOC容器中的id默认为类名首字母小写
			-->
        	<ref bean="stringToPersonConveter"/>
        </set>
    </property>
</bean>

mvc:annotation-driven

  • <mvc:annotation-driven/>会自动注册RequestMappingHandlerMappingRequestMappingHanderAdapterExceptionHanderExceptionResolver三个bean。
  • 还将提供一下支持:
    • 支持使用**ConversionService**实例对表单参数进行类型转换
    • 支持使用 @NumberFormatannotation@DateTimeFormat注解完成数据类型格式化
    • 支持使用@Valid注解对JavaBean实例进行JSR 303验证
    • 支持使用@RequestBody@ResponseBody注解

<mvc:annotation-driven /> 是一种简写形式,完全可以手动配置替代这种简写形式,简写形式可以让初学都快速应用默认配置方案。<mvc:annotation-driven/> 会自动注册DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter 两个bean,是spring MVC@Controllers分发请求所必须的。


@InitBinder

由@InitBinder标识的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定

@InitBinder方法不能有返回值,它必须声明为void

@InitBinder方法的参数通常是WebDataBinder

/**
 * 方法是写在Controller中的
 * 不自动绑定对象中的roleset属性,另行处理
 */
@InitBinder
public void initBinder(WebDataBinder dataBinder){
    dataBinder.setDisallowedFields("roleset");
}

数据类型格式化

对属性对象的输入/输出进行格式化,从本质上来讲依然属于类型转换的范畴

spring在格式化模块中定义了一个实现ConversionService的接口的FormattingConversionService实现类,该类扩展了GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能

FormattingConversionService拥有一个FormattingConversionServiceFactoryBean工厂类,后者用于在spring上下文中构建前者

FormattingConversionServiceFactroyBean内部已经注册了:

  • NumberFormateAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解
  • JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解

装配了FormattingConversionServiceFactoryBean后,就可以在Spring MVC入参绑绑定及模型输出时使用注解驱动了

时间类型的格式化

@DateTimeFormat注解 可以对java.util.Date、java.util.Calendar、java.long.Long时间类型进行标注。

属性值:

  • pattern:字符串类型。指定解析/格式化字段数据的模式,eg:"yyyy-MM-dd"前台传递的String数据为 2017-03-03 最为常用 pattern。

  • iso:类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种ISO.NONE(不使用)–默认值,ISO.DATE(yyyy-MM-dd),ISO.TIME(HH:mm:ss.SSSZ, e.g. 01:30:00.000-05:00),ISO.DATE_TIME(HH:mm:ss.SSSZ, e.g. 01:30:00.000-05:00.)

  • style:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期格式,第二位表示时间格式;

public class User{
    //在目标属性上面加上下面的注解就可以了
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth;
    
    //getter,setter等其他代码...
}

【注意】配置文件中需要添加 <mvc:annotation-driven/> 以支持 @DateTimeFormat 注解

数值类型的格式化

@NumberFormat 可以对类似数字类型的属性进行标注。他的两个属性为互斥的。

  • style:类型为NumberFormatStyle。用于指定样式类型。包括
    • Style.CURRENCY,货币类型
    • Style.NUMBER,正常数字类型
    • Style.PERCENT,百分数类型
  • pattern:类型为String,自定义样式 pattern="#,###,###.#"注意这里的匹配用的是#号,常用
public class User{
    // 传进来的字符串 1,234,567.8 将被格式化为 123456.8 赋值给salary 
    //‘#’表示数字
    @NumberFormat(patten="#,###,###.#")
    private Folat salary;
    
    //getter,setter等其他代码...
}

注意

格式化与自定义转换

(以下没有亲自实践,也没有自己思考,当需要用到的时候需要注意)

需要注意的是,如果使用了自定义的数据转换类型,为了使格式化注解仍起作用,可以将配置中的ConversionServiceFactoryBean替换为FormattingConversionServiceFactoryBean

<!--装配自定义的conversionService-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--
<bean id="conversionService" class="org.springframework.support.ConversionServiceFactoryBean">
-->
<bean id="conversionService" class="org.springframework.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
        	<ref bean="stringToPersonConveter"/>
        </set>
    </property>
</bean>
失败了怎么办

在控制器目标方法的入参之中假如BindingResult对象的参数,可以通过BindingResult 对象获取出错的消息

如果类型转换或者格式化失败怎么办?

@RequestMapping("/test")
public String save(User user,BindingResult result){
    if(result.getErrorCount()>0){
        //出现错误
        System.out.println("Error:");
        for(FieldError error:result.getFieldErrors()){
            System.out.println(error.getField()+":"+error.getDefaultMessage());
        }
    }
}

数据校验

要解决的问题

  • 如何校验?
    • 使用JSR303验证标准
    • 加入hibernate validator 验证框架的jar包
    • 在spring mvc配置文件中添加<mvc:annotation-driven/>
    • 需要在bean的属性上添加对应的注解
    • 在目标方法bean类型前面添加@valid注解
  • 验证出错了转向哪一个界面?
  • 如何显示错误消息?如何把错误消息进行国际化?

JSR 303

JSR 303是java为bean数据合法性校验提供的标准框架,它已经包含在JavaEE6.0中

JSR 303通过在bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对bean进行验证。

注解 功能说明
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator(验证器)是JSR 303的一个参考实现,除支持所有的校验注解外,他还支持以下的扩展注解:

注解 功能说明
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串大小必须在指定的范围内
@NotEmpty 被注释的字符串必须非空
@Range 被注释的元素必须在合适的范围内

**【注意】**Hibernate Validator并不是 Hibernate

如何使用

Spring 4.0拥有自己独立的数据校验框架,同时支持JSR 303标准的校验框架

Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作。在Spring MVC中可直接通过注解驱动的方式进行数据校验

Spring的LocalValidatorFactoryBean既实现了Spring的Validator接口,也实现了JSR 303的Validator接口,只要在Spring容器中定义一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的bean中

Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。

<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解即可让Spring MVC在完成数据绑定后执行数据校验工作

在已经标注了 JSPR 303 注解的表单/命令对象前标注一个@Valid,Spring MVC框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验

Spring MVC是通过对处理方法签名的规约来保存校验结果的,前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中

在bean的属性上添加对应的注解:

public class Employee{
    private Integer id;
    
    @NotEmpty
    private String lastName;
    
    @Email
    private String email;
    
    private Integer gender;
    
    //校验生日必须是过去的一个时间
    @Past
    //数据(日期)格式化
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth();
    
    private Float salary;
    
    //getter,setter ....
}

在目标方法(controller类中的方法)上添加@valid注解

@Controller
public class ValidatorTest{
    
    //注意验证的bean employee,后面的参数必须是BinddingResult
    //他们两个必须挨着,不能中间有其他的入参
    
    @RequestMapping(value="/emp",method=RequestMethod.POST)
    public String save(@Valid Employee employee,BinddingResult result){
        System.out.println("save"+employee);
        if(result.getErrorCount()>0){
            System.out.println("出错了!");
            
            for(FieldError error:result.getFieldErrors()){
                System.out.println(error.getField()+":"+error.getDefaultMessage());
            }
            //如果验证出错,则转向定制的界面
            return "input"; //   WEB-INF/views/input.jsp
        }
        employeeDao.save(employee);
        return "redirect:/emps";
    }
}

需要校验的bean对象和其绑定结果或错误对象是成对出现的,他们之间允许声明其他的入参

Errors接口提供了获取错误信息的方法,如getErrorCount()或getFieldErrors(String field)

BindingResult扩展了Errors接口

//其他代码...
public String handle01(@Valid User user,BindingResult userBindResult,String sessionId,ModelMap mm,@Valid Dept dept,Errors deptErrprs){
    //其他代码...
}
//其他代码...

显示错误消息

利用spring mvc的form标签显示:在JSP页面表单中添加<form:errors path="*">会把错误消息集中显示在一块

在页面添加<form:errors path="lastname">可以显示在对应字段的后面,分开显示

错误消息国际化

每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的FiledError对象

当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合modeAttribute、属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性被@Pattern注解修饰,当该属性不满足@Pattern所定义的规则的时候,就会产生以下4个错误代码:

  • Pattern.user.password
  • Pattern.password
  • Pattern.java.lang.String
  • Pattern

当使用Spring MVC标签显示错误消息的时候,spring mvc会查看web上下文是否配置了对应的国际化消息,如果没有,则会默认显示错误消息,否则使用国际化消息

示例说明:

在classpath路径下创建i18n,properties文件

NoteEmpty.employee.lastName= LastName不能为空
Email.employee.email=Email 地址不合法
Past.employee.birth 时间不能是一个将来的时间

【注意】当你在实际开发中,上面文件中的中文是不是显示为中文的,他会以另外一种情况显示

文件中的内容是一系列的键值对,键的规则是校验注解类名.bean在request域中的属性名.要校验的那个属性名

在spring mvc中配置国际化资源文件

<!--配置国际化资源文件-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
	<property name="basename" value="i18n"></property>
</bean>

若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:

  • required:必要的参数不存在,如@RequiredParam("param1")标注了一个入参,但是该参数不存在
  • typeMismath:在数据绑定时,发生数据类型不匹配的问题
  • methodInvocation: spring mvc在调用处理方法时发生了错误
typeMismath.employee.birth= Birth不是一个日期

返回json

只需要在想返回 json 的目标0方法上加上 @ResponseBody 注解就可以了简单到没有朋友

步骤

  1. 加入需要的jar包

    jackson-annotation-2.1.5.jar
    jackson-core-2.1.5.jar
    jackson-databind-2.1.5.jar
    
  2. 编写目标方法,使其返回json对应的对象或集合

    @RequestMapping("/users")
    public List<User> testAjax(){
        List<User> users=new ArrayList<>();
        users.add(new User(1,"tom",new Date(),1000));
        users.add(new User(2,"joe",new Date(),2000));
        return users;
    }
    
  3. 在方法上添加@ResponseBody注解

    @ResponseBody
    @RequestMapping("/users")
    public List<User> testAjax(){
        List<User> users=new ArrayList<>();
        users.add(new User(1,"tom",new Date(),1000));
        users.add(new User(2,"joe",new Date(),2000));
        return users;
    }
    

    简单的部分代码示例:

    Ajax请求

<!--以上省略N行代码-->
<a id="testJson" href="javascript:void(0);">testJson</a>
<script type="text/javascript">
    $(function(){
        $("#testJson").click(function(){
            var url=this.href;
            var args={};
            $.post(url,args,function(data){
                for(var i=0;i<data.length;i++){
                    var id=data[i].id;
                    var lastName=data[i].lastName;
                    alert(id+":"+lastName);
                } 
            });
        });
    })
</script>
<!--以下省略N行代码-->

【提示】

<a id="testJson" href="javascript:void(0);">testJson</a>

javascript:是伪协议,表示url的内容通过javascript执行。void(0)表示不作任何操作,这样会防止链接跳转到其他页面。这么做往往是为了保留链接的样式,但不让链接执行实际操作(跳转页面)

Controller响应

//获取所有的employee
//Collection是一个接口,List接口和Set接口都是继承与Collection

@ResponseBody
@RequestMapping("/testJson")
public Collection<Employee> testJson(){
    return employeeDao.getAll();
}

HttpMessageConverter

HttpMessageConverter<T>是spring 3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为T),将对象(类型T)输出为响应信息

接口方法 说明
Boolean canRead(Class<?> clazz,MediaType mediaType) 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为clazz类型的对象,同时指定MIME类型(text/html,application/json等)
Boolean canWrite(Class<?> clazz,MediaType mediaType) 指定转换器可否将clazz类型对象写到响应流中,响应流支持的媒体类型在MediaType中定义
List<MediaType> getSupportMediaType() 该转换器支持的媒体类型
T read(Class<? extends T> clazz,HttpInputMessage inputMessage) 将请求信息流转换为T类型的对象
void write(T t,MediaType contentType,HttpOutputMessage outputMessage) 将T类型的对象写到响应流中,同时指定响应的媒体类型为contentType

流程

  • 请求报文转为HttpInputMessage对象,然后通过HttpMessageConverter转换为Java对象交给spring mvc

  • spring mvc将java对象通过HttpMessageConverter转换为HttpOutputMessage对象,然后给出响应

RequestMappingHanderAdapter装配的HttpMessageConverter如下:

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • ResourceHttpMessageConverter
  • SourceHttpMessageConverter<T>
  • AllEncompassingFormHttpMessageConverter
  • Jaxb2RootElementHttpMessageConverter
  • MappingJackson2HttpMessageConverter(加入jackson jar包后)

使用HttpMessageConverter<T>

使用HttpMessageConverter<T>将请求信息转化并绑定到处理方法的入参中或将响应结果转换为对应类型的响应信息,spring提供了两种途径:

  • 使用@RequestBody/@ResponseBody对处理方法进行标注
  • 使用HttpEntity<T>/ResponseEntity<T>作为处理方法的入参或返回值

当控制器处理方法使用到@RequestBody / @ResponseBodyRequestEntity<T>/ResponseEntity<T>时,spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或泛型类型的过滤的得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错

【注意】@RequestBody@ResponseBody不需要成对出现

国际化

关于国际化:

  • 在页面上能够根据浏览器语言设置的情况对文本(并非内容),时间,数值,进行本地化处理
  • 可以在bean中获取国际化资源文件Locale对应的消息
  • 可以通过超链接切换Locale,而不在依赖于浏览器的设置情况

解决:

  • 使用JSTL的fmt标签
  • 在bean中注入ResourceBundleMessageSource的实例,使用其对应的getMessage方法即可
  • 需要配置LocalResolver和LocaleChangeInterceptor
<%@ taglib prefix="fmt" uri="http://java.sun.cpm/jsp/jstl/fmt" %>

笔记没有整理完,待续。。。


文件上传

spring mvc为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的。spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver

spring mvc上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用spring的文件上传功能,需要在上下文中配置MultipartResolver

配置MultipartResolver

defaultEncoding:必须和用户JSP的pageEncoding属性一致,以便正确解析表单内容

为了让CommonsMultipartResolver正确工作,必须先将Jakarta Commons FileUpload及Jakarta Commons io的类包添加到类路径下。

<bean id="multipartResolver" class="org.springframework.web.multipart.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="maxUploadSize" value="5242880"></property>
</bean>

文件上传示例

<from method="post" action="hello/upload.action" enctype="multipart/form-data">
	Desc:<input type="text" name="desc"/>
    File:<input type="file" name="file"/>
    <input type="submit" value="Submit"/>
</from>
@RequestMapping("/upload")
public String upload(@RequestParam("desc") String desc,@RequestParam("file") MulyipartFile file)throws IllegalStateException,IOException{
    if(!file.isEmpty()){
        System.out.printn("desc"+desc);
        file.transferTo(new File("d:\\temp\\"+file.getOriginalFilename()));
    }
    return "success"
}

拦截器

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。

  1. 通过实现HandlerInterceptor接口,或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义。
  2. 通过实现WebRequestInterceptor接口,或继承WebRequestInterceptor接口的实现类来定义。

自定义拦截器

spring mvc也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口

  • preHandle() 方法:该方法会在控制器方法前执行,其返回值表示是否中断后续操作。当其返回值为true时,表示继续向下执行;
    当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。
  • postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改。
  • afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作。
public class CustomInterceptor implements HandlerInterceptor{
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception {
        return false;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
            
    }
    public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler,Exception ex) throws Exception {
        
    }
}

开发拦截器就像开发servlet或者filter一样,都需要在配置文件进行配置,配置代码如下:

<!--配置拦截器-->
<mvc:interceptors>
    <!--<bean class="com.ma.interceptor.CustomeInterceptor" />-->
    <!--拦截器1-->
    <mvc:interceptor>
        <!--配置拦截器的作用路径-->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path=""/>
        <!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
        <bean class="com.ma.interceptor.Intercptor1"/>
    </mvc:interceptor>
    <!--拦截器2-->
    <mvc:interceptor>
        <mvc:mapping path="/hello"/>
        <bean class="com.ma.interceptor.Interceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

<mvc:interceptors>元素用于配置一组拦截器,基子元素<bean>中定义的是全局拦截器,它会拦截所有的请求;
<mvc:interceptor>元素中定义的是指定路径的拦截器,它会对指定路径下的请求生效。
<mvc:interceptor>元素的子元素<mvc:mapping>用于配置拦截器作用的路径,该路径在其属性path 中定义。如上述代码中 path 的属性值“/**” 表示拦截所有路径,“/hello” 表示拦截所有以 “/hello” 结尾的路径。如果在请求路径中包含不需要拦截的内容,还可以通过<mvc:exclude-mapping>元素进行配置。

【注意】<mvc:interceptor>中的子元素必须按照上述代码中的配置顺序进行编写,即<mvc:mapping> <mvc:exclude-mapping> <bean>,否则文件会报错。

执行流程

  1. 程序先执行preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行。
  2. 在业务处理器(即控制器Controller类)处理完请求后,会执行postHandle()方法,然后会通过DispatcherServlet向客户端返回响应。
  3. DispatcherServlet处理完请求后,才会执行afterCompletion()方法。

当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置顺序执行,而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行。

异常处理

我们通过异常处理,当出现相应的异常时,可以跳转到指定的异常页面,而不是服务器错误页面,这样给用户体验会好些。

spring mvc通过HandlerExceptionResolver(接口)处理程序的异常,包括Handler映射、数据绑定、及目标方法执行时发生的异常。

DispatcherServlet默认装配的HandlerExceptionResolver:

  • 没有使用<mvc:annotation-driven/>配置:
    • AnnotationMethodHandlerExceptionReslover
    • ResponseStatusExceptionReslover
    • DefaultHandlerExceptionReslover
  • 使用了<mvc:annotation-driven/>配置
    • ExceptionHandlerExceptionResolver
    • ResponseStatusExceptionResolver
    • DefaultHandlerExceptionResolver

ExceptionHandlerExceptionResolver

@ExceptionHandler注解修饰的方法是处理异常的方法,方法的入参中可以加入Exception类型的参数,该参数对应发生的异常对象

@ExceptionHandler修饰的方法中不能传入Map类型的参数,若希望异常信息传递到页面上,需要使用ModelAndView作为返回值

关于异常优先级的问题,@ExceptionHandler标记的方法有优先级问题

如果在当前Controller找不到@ExceptionHandler方法处理异常,将去ControllerAdvice标记的类中查找@ExceptionHandler方法

@ExceptionHandler({ArithmeticException.class})
public ModelAndView testException(Exception ex){
    ModelAndView mv=new ModelAndView("error");
    mv.addObject("exception",ex);
    return mv;
}

在页面上打印异常信息

<!--以上省略N行代码-->
<h1>This is error page</h1>
<p>
    ${exception}
</p>
<!--以下省略N行代码-->

在Controller中标记@ExceptionHandler的方法只能处理当前Controller方法中抛的异常,我们可以这样做:

@ControllerAdvice
public class ControllerException{
    
    @ExceptionHandler({ArithmeticException.class})
    public ModelAndView testException(Exception ex){
        ModelAndView mv=new ModelAndView("error");
        mv.addObject("exception",ex);
        return mv;
    }
}

ResponseStatusExceptionResolver

在实际项目中,可能碰到这种情况,我们提供的服务,调用方并不需要json报文中的消息,调用方只关注响应码,比如200,代表调用正常;404,代表请求资源不存在;502,代表系统异常。。。等等。我们又该如何去做?

@ResponseStatus在自定义异常中的使用

package com.somnus.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value=HttpStatus.BAD_GATEWAY)
public class HttpStatusException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public HttpStatusException() {
        super();        
    }

    public HttpStatusException(String message, Throwable cause) {
        super(message, cause);      
    }

    public HttpStatusException(String message) {
        super(message);     
    }

    public HttpStatusException(Throwable cause) {
        super(cause);
    }

}
@Controller
@RequestMapping(value = "status")
public class ResponseStatusController {

    @RequestMapping(value = "e/{id}", method = { RequestMethod.GET })
    @ResponseBody
    public String status(@PathVariable(value = "id") Integer id){
        if(id % 2 != 0){
            throw new HttpStatusException();
        }
        return id.toString();
    }
}

另外这里不得不提一点需要注意的,不要轻易把@ResponseStatus修饰目标方法,因为无论它执行方法过程中有没有异常产生,用户都会得到异常的界面,而目标方法正常执行。

package com.somnus.controller;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.somnus.exception.HttpStatusException;

@Controller
@RequestMapping(value = "status")
public class ResponseStatusController {

    /**
     * ResponseStatus修饰目标方法,无论它执行方法过程中有没有异常产生,用户都会得到异常的界面。而目标方法正常执行
     * @param id
     * @return
     */
    @RequestMapping(value = "e2/{id}", method = { RequestMethod.GET })
    @ResponseStatus(value=HttpStatus.BAD_GATEWAY)
    @ResponseBody
    public String status2(@PathVariable(value = "id") Integer id){
        System.out.println(10 / id);
        return id.toString();
    }

}

DefaultHandlerExceptionResolver

对一些特殊的异常进行处理,比如

  • NoSuchRequestHandingMethodException
  • HttpRequestMethodNotSupportedException
  • HttpMediaTypeNotSupportedException
  • HttpMediaTypeNotAcceptabelException

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时对应的视图报告异常,他会自己把出的异常放入request请求域中

<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handle.SimpleMappingExceptionResolver">
    <!--配置出现异常变量的名字  Exception ex=... -->
    <property name="exceptionAttribute" value="ex"></property>
    <!--配置异常的类型-->
    <property name="exceptionMapping">
        <!--异常对应的视图-->
    	<prop key="java.lang.ArithmeticException">error</prop>
    </property>
    
</bean>

spring mvc运行流程

  1. 请求到springDispatcherServlet的url-pattern
  2. spring mvc中是否存在映射
    1. 如果不存在 查看是否配置<mvc:default-servlet-handler/>
      1. 如果也没有配置<mvc:default-servlet-handler/>
        1. 控制台:No mapping found for HTTP request with URI [xx/xx] in DispatcherServlet
        2. 404页面
        3. 结束
      2. 如果配置<mvc:default-servlet-handler/>
        1. 返回目标资源
        2. 结束
    2. 如果存在映射
    3. 由HandlerMapping获取HandlerExecutionChain对象
    4. 获取HandlerAdapter对象
    5. 调用拦截器的PreHandle方法
    6. 调用目标Handler的目标方法得到ModelAndView对象
    7. 调用拦截器的postHandle方法
    8. 是否存在异常
      1. 存在异常
        1. 由HandlerExceptionResolver组件处理异常,得到新的ModelAndView对象
    9. 由ViewResolver组件根据ModelAndview对象得到实际的View
    10. 渲染视图
    11. 调用拦截器的afterCompletion方法
    12. 结束

【说明】

  • HandlerExecutionChain包含了拦截器,目标Controller类和目标处理方法
  • HandlerAdapter…

spring 整合spring mvc

3种问法一个意思:

  1. 需要进行spring整合spring mvc吗?
  2. 还是否需要再加入spring的IOC的容器?
  3. 是否需要在web。xml文件中配置启动spring IOC容器的contextLoaderListentr?

这个问题无非两种观点:

  1. 需要(更建议):
    1. 通常情况下,类似于数据源,事务,整合其他框架都是放在spring的配置文件中,而不是放在spring mvc的配置文件中
    2. 实际上放入spring配置文件对应的IOC容器中的还有service和dao.
  2. 不需要:都放在spring mvc的配置文件中,也可以分多个spring的配置文件,然后使用import节点导入其他的配置文件
<!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>
	<display-name>Archetype Created Web Application</display-name>
	<!-- The front controller of this Spring Web application, responsible for 
		handling all application requests -->
    <!--配置spring IoC容器的Listener-->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>classpath:beans.xml</param_value>
    </context-param>
    
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--配置spring mvc-->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

beans.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<context:component-scan base-package="com.zhangjinbang.springmvc"></context:component-scan>
    
    <!--配置数据源,整合其他资源,事务等-->

</beans>

问题:

如果spring的IOC容器和spring mvc的容器扫描的包有重合的部分,就会导致有的bean会被创建两次

解决:

  1. 使spring 的IOC容器扫描的包和spring mvc的IOC容器扫描的包没有重合的部分,但开发的时候如果希望按模块划分包就会有新的问题,controller等会被分散在不同的包里面,难免保证不重合。
  2. 使用exclude-filter和include-filter子节点来规定只能扫描的注解,和不能扫描的注解

spring IOC配置文件

<!--spring mvc 配置文件-->
<beans>
	<context:component-scan base-package="com.zhangjinbang.springmvc">
        <!--不扫描 @Controller-->
    	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--不扫描 @ControllerAdvice-->
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>
    
    <!--配置视图解析器...-->
    
</beans>

spring mvc配置文件

<!--spring mvc 配置文件-->
<beans>
	<context:component-scan base-package="com.zhangjinbang.springmvc" use-default-filters="false">
        <!--扫描 @Controller-->
    	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--扫描 @ControllerAdvice-->
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>
    
    <!--配置视图解析器...-->
    
</beans>

这个文件中beans根节点下只有一个context:component-scan节点,此节点有两个属性base-package属性告诉spring要扫描的包,use-default-filters="false"表示不要使用默认的过滤器, 此处的默认过滤器,会扫描包含@Service@Component@Repository@Controller注解修饰的类use-default-filters属性的默认值为true,这就意味着会扫描指定包下标有@Service@Component@Repository@Controller的注解的全部类,并注册成bean
如果仅仅是在配置文件中写<context:component-scan base-package="com.sparta.trans"/> use-default-filter此时为true,那么会对base-package包或者子包下所有的java类进行扫描,并把匹配的java类注册成bean

所以这用情况下可以发现扫描的力度还是挺大的,但是如果你只想扫描指定包下面的Controller,那该怎么办?此时子标签<context:incluce-filter>就可以发挥作用了。

type和expression的如下

Type Examples Expression Description
annotation org.example.SomeAnnotation 符合SomeAnnoation的target class
assignable org.example.SomeClass 指定class或interface的全名
aspectj org.example…*Service+ AspetJ语法
regex org.example.Default.* Regelar Expression
custom org.example.MyTypeFilter Spring3新增自订Type,称作org.springframework.core.type.TypeFilter

spring的容器和spring mvc容器

spring mvc的IOC容器中的bean可以用来引用spring IOC容器中的bean,反之,在spring IOC容器中的bean却不能引用spring mvc容器中的bean

多个spring IOC容器之间可以设置为父子关系,以实现良好的解耦

spring mvc web层容器可作为"业务层",spring容器的子容器:即web层容器可以引用业务层的Bean,而业务层容器缺访问不到web层容器的bean

spring mvc对比struts2

spring mvc的入口是Servlet,Struts2的入口是Filter

spring mvc会稍微比struts2快些

spring mvc是基于方法设计,struts2是基于类,每发一次请求都会实例一个Action

spring mvc使用更加简洁,开发效率spring mvc确实比struts2高,支持JSR 303,处理ajax的请求更加方便

struts2的OGNL表达式使页面的开发效率相比spring mvc更高些

打开App,阅读手记
2人推荐
发表评论
随时随地看视频慕课网APP