写在前面:本文简单讲解SpringMVC的一些注解
公众号:小白编码
本文目录
第一章:三层架构和MVC
- 咱们开发服务器端程序,一般都基于两种形式,一种C/S架构程序,一种B/S架构程序
- 使用Java语言基本上都是开发B/S架构的程序,B/S架构又分成了三层架构
三层架构:
- 表现层: WEB层,用来和客户端进行数据交互的。表现层一般会采用MVC的设计模型
- 业务层: 处理公司具体的业务逻辑的
- 持久层: 用来操作数据库的
MVC模型
MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
- Model:数据模型,JavaBean的类,用来进行数据封装。
- View:指JSP、HTML用来展示数据给用户
- Controller:用来接收用户的请求,整个流程的控制器。用来进行数据等。
第二章:SpringMVC优势
SpringMVC优势
1.清晰的角色划分:
- 前端控制器(DispatcherServlet)
- 请求到处理器映射(HandlerMapping)
- 处理器适配器(HandlerAdapter)
- 视图解析器(ViewResolver)
- 处理器或页面控制器(Controller)
- 验证器( Validator)
- 命令对象(Command 请求参数绑定到的对象就叫命令对象)
- 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
2.分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
3.由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象。
4.和Spring 其他框架无缝集成,是其它Web框架所不具备的。
5.可适配,通过HandlerAdapter可以支持任意的类作为处理器。
6.可定制性,HandlerMapping、ViewResolver等能够非常简单的定制。
7.功能强大的数据验证、格式化、绑定机制。
8.利用Spring提供的Mock对象能够非常简单的进行Web层单元测试。
9.本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
10.强大的JSP标签库,使JSP编写更容易。
其他:还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
第三章: SpringMVC入门案例
新建一个maven骨架web工程
Maven依赖准备:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 编译版本-->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- Spring版本锁定-->
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<!--SpringIOC-->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
web.xml:
<servlet>
<!--配置spring mvc的核心控制器 -->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置初始化参数,用于读取SpringMVC的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--spring配置文件的地址-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--配置servlet的对象的创建时间点:应用加载时创建。 取值只能是非0正整数,表示启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!--SpirngMVC-->
<servlet-name>dispatcherServlet</servlet-name>
<!--拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
配置jsp:
<a href="hello">hello,world</a>
配置控制器类:
//控制器
@Controller
public class HelloController {
/**
*
* @return
*/
@RequestMapping(path = "/hello")
public String sayHello() {
System.out.println("Hello StringMVC");
return "success";
}
}
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- 默认到/WEB-INF/pages/下-->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!-- 匹配后缀.jsp-->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置Spring开启MVC的支持,并且开启自定义的类型转换器-->
<mvc:annotation-driven ></mvc:annotation-driven>
</beans>
success.jsp地址:
success.jsp:
启动测试!:
点击!跳转!
第四章:SpringMVC中的组件:
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于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。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
<mvc:annotation-driven>
说明
在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。 使用
<mvc:annotation-driven>
自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用<mvc:annotation-driven>
替代注解处理器和适配器的配置。
第五章:SpringMVC常用注解:
@RequestMapping注解
源码:
@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必须和配置的一模一样。
此时链接地址必须有username
这个参数:
params
:其他用法:请求参数必须为username=123
,否则其他404
@RequestParam注解
- 作用:把请求中的指定名称的参数传递给控制器中的形参赋值
属性
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数,默认值是true,必须提供
jsp:
<a href="anno/testRequestParam?name=xiaobai">requestParam</a>
此时控制器:由于形参不对应,所以username
赋值为null 因为:(name!=username)
第一次只进行结果:赋值失败。
添加注解@RequestParam
:指定参数名称:封装成功。
点击超链接结果: username封装为:xiaobai
@RequestBody注解
- 作用:用于获取请求体内容。直接使用得到是key=value&key=value…结构的数据。注意:get方法不可以)
属性
required
:是否必须有请求体,默认值是true
post请求jsp代码: <!-- request body注解 -->
<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";
}
post请求执行结果:
get请求结果: null
@PathVariable注解:
- 作用:拥有绑定
url
中的占位符的。例如:url中有/delete/{id}
,{id}就是占位符,涉及到restful设计风格 - 属性
- value:指定url中的占位符名称
<a href="testPathVariable/10">PathVariable</a>
控制器:
/**
* 测试 @PathVariable
* @param id
* @return
*/
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") String id) {
System.out.println("执行了SpringMVC");
System.out.println(id);
return "success";
}
运行结果:
@RequestHeader注解
作用: 用于获取请求消息头。
属性: value:提供消息头名称 required:是否必须有此消息头
注: 在实际开发中一般不怎么用。
演示:jsp
<a href="testRequestHeader">testRequestHeader</a>
控制器:
/**
* 测试 @RequestHeader
*
* @param requestHeader
* @return
*/
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader("Accept-Language") String requestHeader) {
System.out.println("执行了SpringMVC");
System.out.println(requestHeader);
return "success";
}
运行结果:
@CookieValue注解
作用: 用于把指定cookie
名称的值传入控制器方法参数。
属性: value
:指定cookie
的名称。
required
:是否必须有此cookie。
jsp:
<a href="testCookieValue">testCookieValue</a>
控制器:
/**
* 测试 @CookieValue
*
* @param jessionid
* @return
*/
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String jessionid) {
System.out.println("执行了SpringMVC");
System.out.println(jessionid);
return "success";
}
结果:
@ModelAttribute注解
作用:
该注解是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);
}
运行结果:
@SessionAttribute注解
作用: 用于多次执行控制器方法间的参数共享。(域对象)
属性:
value
:用于指定存入的属性名称
type
:用于指定存入的数据类型。
jsp:
<!-- SessionAttribute注解的使用 -->
<a href="anno/testPut">存入SessionAttribute</a>
<hr/>
<a href="anno/testGet">取出SessionAttribute</a>
<hr/>
<a href="anno/testClean">清除SessionAttribute</a>
控制器代码:
@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";
}
测试结果: 参数封装成功!
封装POJO类型参数
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:
<!-- 解决Post请求乱码,配置SpringMVC编码过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 设置过滤器中的属性值 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param> <!-- 启动过滤器 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<!-- 过滤所有请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
在springmvc的配置文件中可以配置,静态资源不过滤: <!-- location表示路径,mapping表示文件,**表示该目录下的文件以及子目录的文件 -->
<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框架内部会默认进行数据类型转换。
- 如果想自定义数据类型转换,可以实现Converter的接口
/**
* 把字符串转换成日期的转换器
* @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("类型转换错误");
}
}
}
- 注册自定义类型转换器,在springmvc.xml配置文件中编写配置:
<!-- 注册自定义类型转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters" >
<set>
<bean class="cn.codewhite.contorller.StringToDateConverter"></bean>
</set>
</property>
</bean>
<!-- 配置Spring开启MVC的支持,并且开启自定义的类型转换器-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
此时重新测试:
测试结果:封装成功。
第八章: 在控制器中使用原生的ServletAPI对象
SpringMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。
支持原始 ServletAPI 对象有:
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
- 只需要在控制器的方法参数定HttpServletRequestHttpServletResponse对象。
/**
* 原生的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";
}