写在前面:本文简单讲解SpringMVC的一些注解
公众号:小白编码
MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
1.清晰的角色划分:
2.分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
3.由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象。
4.和Spring 其他框架无缝集成,是其它Web框架所不具备的。
5.可适配,通过HandlerAdapter可以支持任意的类作为处理器。
6.可定制性,HandlerMapping、ViewResolver等能够非常简单的定制。
7.功能强大的数据验证、格式化、绑定机制。
8.利用Spring提供的Mock对象能够非常简单的进行Web层单元测试。
9.本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
10.强大的JSP标签库,使JSP编写更容易。
其他:还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
新建一个maven骨架web工程
Maven依赖准备:
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<spring.version>5.0.2.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
dependencies>
web.xml:
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
配置jsp:
<a href="hello">hello,worlda>
配置控制器类:
//控制器
@Controller
public class HelloController {
/**
*
* @return
*/
@RequestMapping(path = "/hello")
public String sayHello() {
System.out.println("Hello StringMVC");
return "success";
}
}
springmvc.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.codewhite"/>
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/">property>
<property name="suffix" value=".jsp">property>
bean>
<mvc:annotation-driven >mvc:annotation-driven>
beans>
用户请求到达前端控制器,它就相当于mvc 模式中的c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。
DispatcherServlet:前端控制器
HandlerMapping 负责根据用户请求找到Handler 即处理器, SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler:处理器
它就是我们开发中要编写的具体业务控制器。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理。
HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
View:视图
SpringMVC框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
说明
在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。 使用
自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用
替代注解处理器和适配器的配置。
源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mappinga
public @interface RequestMapping {
}
作用:用于建立请求URL和处理请求方法之间的对应关系。
出现位置: 类上:
请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。 它出现的目的是为了使我们的URL可以按照模块化管理。
例如:账户模块:
/user/login
/user/logout
/user/register
加粗的部分就是把RequsetMappding写在类上,使我们的URL更加精细。
方法上: 请求URL的第二级访问目录。
此时请求地址需要写成 /user/login
属性:
value
:用于指定请求的URL。它和path属性的作用是一样的。
method
:用于指定请求的方式。(get,post,put,delete)
params
:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。
params
:其他用法:请求参数必须为username=123
,否则其他404
属性
jsp:
<a href="anno/testRequestParam?name=xiaobai">requestParama>
此时控制器:由于形参不对应,所以username
赋值为null 因为:(name!=username)
添加注解@RequestParam
:指定参数名称:封装成功。
点击超链接结果: username封装为:xiaobai
属性
required
:是否必须有请求体,默认值是truepost请求jsp代码:
<form action="testRequestBody" method="post">
用户名称:<input type="text" name="username"><br/>
用户密码:<input type="password" name="password"><br/>
用户年龄:<input type="text" name="age"><br/>
<input type="submit" value="保存">
form>
控制器:
/**
* 测试请求体:@RequestBody
* @param body
* @return
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body) {
System.out.println("执行了SpringMVC");
System.out.println(body);
return "success";
}
get请求结果: null
url
中的占位符的。例如:url中有/delete/{id}
,{id}就是占位符,涉及到restful设计风格<a href="testPathVariable/10">PathVariablea>
控制器:
/**
* 测试 @PathVariable
* @param id
* @return
*/
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") String id) {
System.out.println("执行了SpringMVC");
System.out.println(id);
return "success";
}
作用: 用于获取请求消息头。
属性: value:提供消息头名称 required:是否必须有此消息头
注: 在实际开发中一般不怎么用。
演示:jsp
<a href="testRequestHeader">testRequestHeadera>
控制器:
/**
* 测试 @RequestHeader
*
* @param requestHeader
* @return
*/
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader("Accept-Language") String requestHeader) {
System.out.println("执行了SpringMVC");
System.out.println(requestHeader);
return "success";
}
运行结果:
作用: 用于把指定cookie
名称的值传入控制器方法参数。
属性: value
:指定cookie
的名称。
required
:是否必须有此cookie。
jsp:
<a href="testCookieValue">testCookieValuea>
控制器:
/**
* 测试 @CookieValue
*
* @param jessionid
* @return
*/
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String jessionid) {
System.out.println("执行了SpringMVC");
System.out.println(jessionid);
return "success";
}
作用:
该注解是SpringMVC4.3
版本以后新加入的。它可以用于修饰方法和参数。
@ModelAttribute
注解作用在方法上或者方法的参数上,表示将被注解的方法的返回值或者是被注解的参数作为Model的属性加入到Model中,然后Spring框架自会将这个Model传递给ViewResolver。Model的生命周期只有一个http请求的处理过程,请求处理完后,Model就销毁了
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性: value:用于获取数据的key。key可以是POJO的属性名称,也可以是map结构的key。
应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:我们有一个表单:(他的请求封装没有用户生日的提交表单信息)
<form action="testModelAttribute" method="post">
用户姓名:<input type="text" name="uname" /><br/>
用户年龄:<input type="text" name="age" /><br/>
<input type="submit" value="提交" />
form>
User:
@Data
public class User implements Serializable {
private String uname;
private Integer age;
private Date birthday;
}
控制器:(由于表单没有用户生日,所以user里的birthday属性赋值不上)
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("执行了SpringMVC");
System.out.println(user);
return "success";
}
点击提交:(由于表单没有用户生日,所以user里的birthday属性赋值不上)
此时在控制器里,就需要使用@ModelAttribute
注解写一个方法,对原有控制器的testModelAttribute方法
里的user赋值
:(有返回值情况,自动补充user)
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("执行了SpringMVC");
System.out.println(user);
return "success";
}
/**
* 该方法会先执行,然后将返回的user,封装到testModelAttribute()里面的User
*
* @ModelAttribute
*/
@ModelAttribute
public User showUser(String uname) {
System.out.println("showUser执行了...");
// 通过用户查询数据库(模拟)
User user = new User();
user.setUname(uname);
user.setAge(20);
user.setBirthday(new Date());
return user;
}
此时重新提交表单运行结果:(User封装成功)
@ModelAttribute
注解没返回值情况:通过Map
方式
/**
* @ModelAttribute("1")取的是map里面的key
* @param user
* @return
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("1") User user) {
System.out.println("执行了SpringMVC");
System.out.println(user);
return "success";
}
/**
* Map里存放了一个User
* @param uname
* @param map
*/
@ModelAttribute
public void showUser(String uname, Map<String,User> map){
System.out.println("showUser执行了...");
// 通过用户查询数据库(模拟)
User user = new User();
user.setUname(uname);
user.setAge(20);
user.setBirthday(new Date());
map.put("1",user);
}
作用: 用于多次执行控制器方法间的参数共享。(域对象)
属性:
value
:用于指定存入的属性名称
type
:用于指定存入的数据类型。
jsp:
<a href="anno/testPut">存入SessionAttributea>
<hr/>
<a href="anno/testGet">取出SessionAttributea>
<hr/>
<a href="anno/testClean">清除SessionAttributea>
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"username", "password", "age"}, types = {String.class, Integer.class})
// 把username,password,age都存入到session域对象中
public class AnnoController {
/**
* 把数据存入SessionAttribute
*
* Model是spring提供的一个接口,该接口有一个实现类ExtendedModelMap
*
* 该类继承了ModelMap,而ModelMap就是LinkedHashMap子类
*/
@RequestMapping("/testPut")
public String testPut(Model model) {
//底层会存取到request域对象中,可以从jsp里获取request的域数据
model.addAttribute("username", "小白");
model.addAttribute("password", "123");
model.addAttribute("age", 18);
System.out.println("存入数据!");
//跳转之前将数据保存到username、password和age中,
// 因为注解@SessionAttribute中有这几个参数
return "success";
}
/**
* 从session中获取值
*
* @return
*/
@RequestMapping(path = "/testGet")
public String testGet(ModelMap modelMap) {
//获取session域中的值
String username = (String) modelMap.get("username");
String password = (String) modelMap.get("password");
Integer age = (Integer) modelMap.get("age");
System.out.println("取出数据:" + username + " : " + password + " : " + age);
return "success";
}
/**
* 清除值
*
* @return
*/
@RequestMapping(path = "/testClean")
public String delete(SessionStatus status) {
System.out.println("清除了数据!");
//清除域中的值
status.setComplete();
return "success";
}
}
我们都知道,表单中请求参数都是基于key=value的。
SpringMVC绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。
比如:参数是username=小白
jsp:
<a href="params/testParam?username=小白">测试参数绑定a>
控制器:testparam是二级地址
@RequestMapping("/testParam")
public String testParam(String username){
System.out.println("用户名:" + username);
return "success";
}
Account:
@Data
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
}
User:
@Data
public class User implements Serializable {
private String uname;
private Integer age;
}
封装规则:(表单)
控制器:
运行结果:
测试结果:
支持的数据类型:
基本类型参数: 包括基本类型和String类型
包括实体类,以及关联的实体类
数组和集合类型参数封装:包括List结构和Map结构的集合(包括数组)
实现效果:将User封装到List和Map中
实现效果:将User封装到List和Map中
Account:
@Data
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
private List<User> list;
private Map<String,User> map;
}
User:
@Data
public class User implements Serializable {
private String uname;
private Integer age;
}
表单:
<%--把数据封装Account类中,类中存在list和map的集合--%>
<form action="params/saveAccount" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="text" name="password" /><br/>
金额:<input type="text" name="money" /><br/>
<%-- 将User放入list集合list[index].value--%>
用户姓名:<input type="text" name="list[0].uname" /><br/>
用户年龄:<input type="text" name="list[0].age" /><br/>
<%--将User放入Map集合,map['key'].value--%>
用户姓名:<input type="text" name="map['one'].uname" /><br/>
用户年龄:<input type="text" name="map['one'].age" /><br/>
<input type="submit" value="提交" />
form>
Controller:
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println(account);
return "success";
}
web.xml:
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
在springmvc的配置文件中可以配置,静态资源不过滤:
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/scripts/" mapping="/javascript/**"/>
get请求方式: tomacat对GET和POST请求处理方式是不同的,GET请求的编码问题,要改tomcat的server.xml配置文件,如下:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
改为:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"
useBodyEncodingForURI="true"/>
如果遇到ajax请求仍然乱码,请把: useBodyEncodingForURI="true"改为URIEncoding="UTF-8" 即可。
问题出现:封装Date类型使用2020-10-10的格式无法封装,只能使用2020/10/10的格式,那么如何自定义格式来封装呢?如下:
表单:
<%--自定义类型转换器 --%>
<form action="params/saveUser" method="post">
用户姓名:<input type="text" name="uname" /><br/>
用户年龄:<input type="text" name="age" /><br/>
用户生日:<input type="text" name="birthday" /><br/>
<input type="submit" value="提交" />
form>
控制器:
@RequestMapping("/saveUser")
public String saveUser(User user) {
System.out.println(user);
return "success";
}
User:
@Data
public class User implements Serializable {
private String uname;
private Integer age;
private Date birthday;
}
测试正确格式封装:
提交结果:(封装成功)
若日期写成:2020-12-12
则封装失败:
此时需要自定义类型转换器:
1.表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明
Spring框架内部会默认进行数据类型转换。
/**
* 把字符串转换成日期的转换器
* @create 2020-05-29 12:05
*/
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (source == null) {
throw new RuntimeException("参数不能为空");
}
try {
//日期解析器
DateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = dataFormat.parse(source);
return date;
} catch (ParseException e) {
throw new RuntimeException("类型转换错误");
}
}
}
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" >
<set>
<bean class="cn.codewhite.contorller.StringToDateConverter">bean>
set>
property>
bean>
<mvc:annotation-driven conversion-service="conversionService">mvc:annotation-driven>
此时重新测试:
SpringMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。
支持原始 ServletAPI 对象有:
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
/**
* 原生的API
* @return
*/
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了...");
System.out.println(request);
HttpSession session = request.getSession();
System.out.println(session);
ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);
System.out.println(response);
return "success";
}