1、springMVC是什么
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架!
简单说就是运用在web层的一个框架!
2、springMVC和Struts2比较
(1)共同点:都是表现层(web层,控制层)框架,都是基于MVC模型编写,底层都离不开ServletAPI,处理请求的机制都是一个核心控制器;
(2)区别:
A、入口不同,struts2的入口是一个Filter(StrutsPreparedAndExecuteFilter),而springMVC的入口是一个Servlet(DispatcherServlet);
B、springMVC是基于方法设计的,而struts是基于类设计的,前者是单例的,后者是多例的;
C、springMVC使用更加简洁,同事还支持(JSR303),能更方便的处理ajax;(注:JSR303是一套JavaBean参数校验标准,它定义了很多常用的校验注解,直接将这些注解加在JavaBean上面就可以在需要时,直接校验了。)
D、struts2的OGNL使页面开发效率比springMVC高,但是执行效率相较于JSTL没有提升,struts2提供的表单标签,也没有HTML执行效率高!
3、springMVC入门案列:
(1)新建一个maven的webapp项目,在web.xml中配置springMVC的核心控制器DispatcherServlet:
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>
<servlet>
<servlet-name>dispatcherServlet</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>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
(2)在main目录的resources目录下创建一个配制文件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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="cn.melo"></context:component-scan>
<!--试图解析器对象-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--控制器方法返回的字符串的前面会加上这个前缀:/WEB-INF/pages/-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!--控制器方法返回的字符串的后面会加上这个后缀:.jsp-->
<property name="suffix" value=".jsp"/>
</bean>
<!--开启springmvc注解支持-->
<mvc:annotation-driven/>
</beans>
(3)在main目录的java目录下创建控制器类:
HelloController.java:
package cn.melo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//创建控制器类对象
@Controller
public class HelloController {
//声明请求此方法的路径是/hello,这里的属性可以是path,也可以是value
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello World!!");
//返回的字符串和配置文件中的前后缀进行拼接,跳转到相应的页面
return "success";
}
}
4、springMVC入门案列的流程分析:
(1)因为在web.xml的DispatcherServlet的配置中加了:
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
所以,服务器在启动时就会去classpath下找springmbc.xml并加载它!
(2)因为开启了注解扫描,所以当扫描dao@Controller的时候就会为控制类创建对象;
(3)用户发出请求,先到DispatcherServlet,根据路径,和springmvc的注解,找到对应的请求方法,执行之;
(4)根据方法的返回值和配置的视图解析器,跳转到对应的jsp页面!
5、springMVC入门案例中的控制器
(1)DispatcherServlet:前端控制器,相当于MVC的C,是整个流程控制的中心,由它调用其他组件,处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性;
(2)HandlerMapping:处理器映射,负责根据用户请求找到Handler(处理器),springmvc提供了不同的映射器实现不同的映射方式,如配置文件,实现接口,注解等!
(3)Handler:处理器,就是我们要编写的业务控制器,由DispatcherServlet把用户请求转发到Handler,由Handler对具体的请求进行处理;
(4)HandlerAdapter:处理器适配,对处理器进行适配,是适配器模式的应用,通过扩展适配器可以对更多类型的适配器进行执行!
(5)ViewResolver:视图解析器,负责将处理结果映射成View(视图);
(6)View:视图,springMVC提供了很多的View视图类型的支持,包括jstlView,freemarkerView,pdfView等,最常用的是jsp;
注:HandlerMapping、HandlerAdapter、ViewResolver被称为springMVC的三大框架!
(7)<mvc:annotation-driven/>
:在springmvc.xml中配置这个可以替代注解处理器和适配器的配置;
springMVC的工作原理(图片来源于网络):
6、@RequestMapping注解
它的作用是建立请求和处理方法之间的对应关系;
(1)可以作用在类上或者方法上:
A、作用在类上,表示第一级的访问目录;
B、作用在方法上,表示第二级的访问目录;
(2)属性:
A、path:指定请求路径的url;
B、value:和path属性一样的;
C、method:指定该方法的请求方式;
D、params:指定限制请求参数的条件;
例如:
params = {“accountName”},表示请求参数必须有 accountName
params = {“moeny!100”},表示请求参数中 money不能是 100。
E、headers:发送的请求中必须包含的请求头;
7、请求参数的绑定
springMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的,要求是表单中name属性值与参数名相同,支持的数据类型有:基本数据类型,字符串类型,JavaBean类型,数组封装!
(1)基本数据类型与字符串类型的要求:表单的name属性值和参数名称一致,区分大小写!
(2)实体类型(JavaBean):表单的name属性值与JavaBean的属性名一致,如果一个JavaBean中包含了其他引用类型,那么表单的name要写成:
对象.属性名
测试:
jsp中:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>HelloWorld</h1>
<a href="hello">点击</a>
<form action="params" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
爱好:<input type="checkbox" name="hobbies" value="code">写代码
<input type="checkbox" name="hobbies" value="basketball">打篮球
<input type="checkbox" name="hobbies" value="sing">唱歌<br>
<input type="submit" value="提交">
</form>
</body>
</html>
控制器类:
package cn.melo.controller;
import cn.melo.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
@Controller
public class UserController {
@RequestMapping(path = "/params")
public String getParameters(String name, Integer age, String[] hobbies){
System.out.println("name:"+name);
System.out.println("age:" + age);
System.out.println("hobbies:"+ Arrays.toString(hobbies));
return "success";
}
@RequestMapping(path = "/user")
public String getUser(User user){
System.out.println(user);
return "success";
}
}
请求页面:
结果:
把表单提交路径改为user的测试结果:
也能封装上!
但是发现请求参数中有乱码,解决办法:
在web.xml中配置一个spring提供的过滤器(CharacterEncodingFilter):
<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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
再测试就没有乱码了!!
(3)自定义类型转换器,表单提交的默认类型都是String类型,但是后台定义Integer类型,也可以完成封装,说明springmvc内部会默认进行数据类型转换。
A、如果想自定义数据类型转换器,可以自定义一个类实现Convert<K,T>接口,K是String,T是逆向转化成的类型,重写converse方法即可!
如,在表单中加一个:
日期:<input type="date" name="date">
在控制器类中:
自定义的类型转换类:
MyConverter.java
package cn.melo.util;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
if(!s.isEmpty() && s != null) {
date = sdf.parse(s);
}else{
System.out.println("请输入日期!!!");
}
} catch (ParseException e) {
throw new RuntimeException("日期转化异常了");
}
return date;
}
}
最后,在springmvc.xml中注册自己写的转换器:
<!--注册自定义的类型转换器-->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<!--用array,set,list都可以-->
<array>
<bean class="cn.melo.util.MyConverter"></bean>
</array>
</property>
</bean>
<!--使用自定义类型转换器-->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean" />
测试结果:
(4)在控制器类中使用servlet的原生api:
只需要在方法的形参上定义HttpServletRequest和HttpServletResponse对象即可!
8、常用的注解
测试用的表单:
(1)@RequestParam:把请求中指定名称的参数传递给控制器中的形参赋值。
属性:value:请求中参数的名称;required:请求参数中是否必须提供该参数,默认true;
如:
@RequestMapping(path = "/paramTest")
public String testParameters(@RequestParam(value = "name",required = false) String username){
System.out.println("name:"+username);
return "success";
}
测试结果:
(2)@RequestBody:用于获取请求体的内容(get没有请求体),属性:required:是否必须有请求体,默认true;
测试代码:
@RequestMapping(path = "/paramTest")
public String testParameters(@RequestBody(required = false) String body){
System.out.println(body);
return "success";
}
测试结果:
name=%E8%A9%B9%E5%A7%86%E6%96%AF&age=20&hobbies=code&hobbies=basketball&hobbies=sing&date=2019-11-20
(3)@PathVariable:绑定url中的占位符,如/xx/{id},{id}就是一个占位符
属性:value:指定url中占位符的名称!
注:Restful风格的url,请求路径一样,可以根据不同的请求方式,执行后台中不同的控制器方法!
Restful风格编程的优点:结构清晰,符合标准,易于理解,扩展方便!
测试:
测试结果:
(4)@RequestHeader:获取指定请求头的值
属性:value:请求头的名称
测试:
@RequestMapping(path = "/paramTest")
public String testParameters(@RequestHeader(value = "Accept") String header){
System.out.println(header);
return "success";
}
测试结果:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
(5)@CookieValue:获取指定Cookie的值
属性:value:cookie的名称
测试:
jsp页面加上一段脚本:
<%
Cookie cookie = new Cookie("xixi","777");
cookie.setMaxAge(60*60);
response.addCookie(cookie);
%>
后台:
@RequestMapping(path = "/paramTest")
public String testParameters(@CookieValue(value = "xixi") String cookieValue){
System.out.println(cookieValue);
return "success";
}
测试结果:
(6)@ModelAttribute:
A、用在方法上,表示当前方法在控制器方法执行前执行;
B、在参数上:获取指定数据给参数赋值;
使用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
就是现在方法上用这个注解,然后查询数据库,得到信息,再在参数上用这个注解,如果表单中没有传上来的参数,就用在数据库中查到的!!
第一种情况测试,用在方法上,方法有返回值:
页面:
实体类:User1.java:
package cn.melo.bean;
import java.io.Serializable;
public class User implements Serializable {
private Integer uid;
private String uname;
private Double umoney;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public Double getUmoney() {
return umoney;
}
public void setUmoney(Double umoney) {
this.umoney = umoney;
}
@Override
public String toString() {
return "User1{" +
"uid=" + uid +
", uname='" + uname + '\'' +
", umoney=" + umoney +
'}';
}
}
后台:
/**
* 模拟去查询数据库的方法
*/
@ModelAttribute
private User1 findUser(){
User1 user1 = new User1(1,"abc",1000.00);
System.out.println("查询出的用户:"+user1);
return user1;
}
@RequestMapping(path = "/test3")
public String test3(User1 user1){
System.out.println("修改了的用户:"+user1);
return "success";
}
测试结果:
第二种情况测试,方法没有返回值,那么就要定义一个Map形式参数来存储数据:
测试代码:
/**
* 模拟去查询数据库的方法
*/
@ModelAttribute
private void findUser(Map<String,User1> map){
User1 user1 = new User1(1,"abc",1000.00);
System.out.println("查询出的用户:"+user1);
map.put("abc",user1);
}
@RequestMapping(path = "/test3")
//value属性值是map的键
public String test3(@ModelAttribute(value = "abc")User1 user1){
System.out.println("修改了的用户:"+user1);
return "success";
}
测试结果:
(7)@SessionAttributes:用于多次执行控制器方法之间的参数共享,写在类上:
属性:value:指定存入属性的名称,types:指定存入属性的类型(Xxx.class)
页面:
首先在控制器类上加上:
@SessionAttributes(value = {"1st","2nd","3rd"},types = {Integer.class,String.class,User1.class})
控制器方法:
@RequestMapping("/testPut")
public String testPut(Model model){
model.addAttribute("1st",123);
model.addAttribute("2nd","XXXX");
model.addAttribute("3rd",new User1(2,"cj",100.0));
System.out.println("数据已经存储!!!");
return "success";
}
@RequestMapping("/testGet")
public String testGet(ModelMap model){
System.out.println("取出数据:");
System.out.println(model.get("1st"));
System.out.println(model.get("2nd"));
System.out.println(model.get("3rd"));
return "success";
}
@RequestMapping("/testClear")
public String testClear(SessionStatus status){
status.setComplete();
System.out.println("数据已经清除!!!");
return "success";
}
测试结果: