12.SSM框架集~SpringMVC
本文是上一篇文章的后续,详情点击该连接
SpringMVC
在学习了Spring之后,基于MVC设计模式的项目,我们可以使用Mybatis将数据库替换,使用Spring将Controller层和Service层,以及Service层和数据库层之间进行解耦。但是基于MVC的模式中,在Controller层中的Servlet为请求的代码入口。tomcat服务器在接受到请求后,会根据请求地址自定调用对应的servlet的service方法完成请求处理。
但是此时此刻我们还是会发现一个问题。第一就是每个功能都要声明对应的Servlet,而且在Servlet中获取请求数据比较麻烦,响应的方式的代码其实只想声明对应的响应数据。
项目只声明一个Servlet,该Servlet作为项目请求的公共入口。并且在该Servlet必须声明代码,此代码根据请求地址调用对应的逻辑代码处理请求。如果将逻辑方法全部声明在Servlet中造成代码的体系结构不清晰,将逻辑方法单独声明到逻辑类中(Controller类)。然后Servlet中根据请求动态的调用对应的逻辑类中的逻辑方法处理请求即可。
如何在Servlet中获取逻辑类对象呢?
使用Spring容器的子容器,在子容器中存储所有的Controller的实例化对象,然后Servlet一次性从子容器中获取所有的对象即可。在init方法中实现即可。
如何在Servlet中根据请求动态调用对象的逻辑方法呢
反射+注解
其本质就是将Servlet进行了封装,提供一个公共的Servlet。该Servlet可以根据请求动态的调用对应的逻辑方法完成请求处理。提升开发效率。
[1] 使用Maven创建war类型项目并配置SpringMVC的依赖
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringMVC</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<!--配置版本号-->
<properties>
<servlet-version>3.1.0</servlet-version>
<jsp-version>2.2</jsp-version>
<jstl-version>1.2</jstl-version>
</properties>
<!--配置依赖-->
<dependencies>
<!--配置SpringMVC的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.11.RELEASE</version>
</dependency>
<!--配置web的相关依赖-->
<!--servlet的资源座标-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-version}</version>
<scope>provided</scope>
</dependency>
<!--jsp的资源座标-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-version}</version>
<scope>provided</scope>
</dependency>
<!--jstl的资源座标-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl-version}</version>
</dependency>
</dependencies>
<!--配置tomcat插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port><!--配置tomcat启动的端口号-->
<path>/mvc</path><!--配置项目的访问名称-->
</configuration>
</plugin>
</plugins>
</build>
</project>
配置SpringMVC容器对象的配置文件加载路径
<!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>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置SpringMVC容器对象的配置文件加载路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--服务器启动即完成DispatcherServlet的初始化创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern><!--拦截除Jsp以外的所有请求-->
</servlet-mapping>
</web-app>
在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: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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--配置注解扫描路径-->
<context:component-scan base-package="com.alvin.controller"></context:component-scan>
<!--配置注解解析器-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
在Controller包下创建控制器类并声明单元方法
@Controller
public class MyController {
//声明逻辑方法:给DispatcherServlet使用,必须按照SpringMVC的方式来声明逻辑方法
@RequestMapping("alvin")
public String demo(){
System.out.println("我是控制器的使用方法");
return "hello world";
}
}
在学习了SpringMVC的基本使用流程后,发现SpringMVC将Servlet进行了封装,在外部声明控制器类,并在其中声明单元方法。DispatcherServlet根据请求动态的调用对应的单元方法处理请求,所以我们需要在单元方法中声明请求处理的逻辑代码。而请求的处理需要获取本次请求的请求数据,那么在单元方法中如何获取请求数据呢?
请求被tomcat接受后会调用DispatcherServlet处理请求,Tomcat会将封装了此次请求数据的request对象作为实参传递给DispatcherServlet的service方法,而service方法中又会根据请求调用对应的单元方法处理请求,所以只需要在service方法中将请求数据作为实参传递给单元方法使用即可。注意,单元方法必须声明对应的形参接收数据。
紧耦方式
DispatcherServlet中的service方法直接将此次请求的request对象传递给调用的单元方法即可。同时在单元方法上声明形参HttpServletRequest来接收request实参即可。
DispatcherServlet在其service方法中将请求数据根据需求从request对象中获取出来后,将数据直接传递给对应的单元方法使用。同时在单元方法上直接声明对应的形参接收请求数据即可。
紧耦方式(request)在单元方法中获取请求数据
在控制器类中声明请求处理单元方法,并在单元方法上声明形参,形参类型为 HttpServletRequest,接收DispactherServlet传递的封装了此次请求的请求数据的 request对象。
在单元方法中使用request.getParameter(“键名”)获取请求数据
在单元方法中声明请求处理的逻辑代码
@RequestMapping("alvinreq")
public String getDataByRequest(HttpServletRequest request){
//获取请求数据
String uname = request.getParameter("uname");
int age = Integer.parseInt(request.getParameter("age"));
//处理请求
System.out.println("紧耦合方式获取请求数据(request对象):" + uname + " " + age);
//响应
return "hello world";
}
单元方法都是由DispatcherServlet根据请求来调用,由DispatcherServlet来传入实参数据给单元方法使用
解耦方式获取请求数据
让DispatcherServlet将请求数据获取后传递给单元方法,但是请求数据的获取需要数据的键名,而DispatcherServlet不是我们自己声明的无法修改其底层代码,怎么将请求数据的键名告诉给DispatcherServlet呢?
在单元方法上声明形参来接收请求数据时,形参名必须和请求数据的键名一致,DispatcherServlet会将调用单元方法的形参名作为请求数据的键名获取请求数据,然后传递给单元方法。
@RequestMapping("reqData")
public String getDataByArgName(String name,int age){
//处理请求
System.out.println("解耦方式形参名为键名获取请求数据: " + name + " " + age);
//响应结果
return "hello world";
}
如果形参名和请求数据的键名不一致,不会报错,传入null。
如果请求数据的类型和后台单元方法的形参的类型不匹配,则会报400异常
如果形参类型为基本类型,则如果请求中没有对应的请求数据,可能会出现数据类型转换异常,比如:将null转换为int,建议将形参都声明为包装类的类型
形参名和请求数据的键名不一致怎么办?
在单元方法上的形参声明中使用注解@RequestParam来实现。
@RequestMapping("getDataByArg")
public String getDataByArg(@RequestParam(value = "name") String uname, Integer age){
//处理请求
System.out.println("解耦方式获取请求数据,形参名和请求数据的键名不一致: " + uname + " " + age);
//响应结果
return "hello world";
}
DispatcherServlet默认是使用单元方法的形参名即为请求数据的键名来获取请求数据的
那么如果形参名和请求数据的键名不一致, 则在单元方法上的形参前使用注解
@RequestParam来指明请求数据的键名
value属性:声明请求数据的键名,如果只有value时,value可以省略不写
required:true|false,设置为true表示请求中必须携带键名为指定键名的请求数据,否则400
defaultValue:默认值,如果请求中没有对应的请求数据,则使用默认值,不可以和required一起使用
使用实体类对象获取请求数据
在学习了使用SpringMVC后,我们可以在单元方法上声明形参直接获取请求数据只要形参名和请求数据的键名一致即可。但是如果我们的请求数据过多,总不能咱们声明N个形参来接收请求数据吧?而且按照我们以往的开发经验,请求数据过多我们会将请求封装到实体类对象中进行使用,保证数据的完整性。那么,在SpringMVC中一旦请求数据过多,如何在单元方法上获取请求数据呢?
我们希望在单元方法中直接获取一个封装好请求数据的实体类对象使用,美滋滋。也就说我们希望DispatcherServlet可以将请求数据封装到实体类对象中,然后将实体类对象作为实参传递给单元方法使用。在单元方法上声明对应的实体类的形参类型,来接收DispatcherServlet传递的封装了请求数据的实体类对象,以及告诉DispatcherServlet使用哪个实体类来封装请求数据。而且,要求实体类的属性名必须和请求数据的键名一致,DispatcherServlet会按照该方式将请求数据赋值给实体类的属性。
public class User {
private Integer uid;
private String uname;
private Integer age;
}//get set不在这里写
@RequestMapping("getDataByObject")
public String getDataByObject(User user){
//处理请求
System.out.println("解耦方式:使用实体类获取请求数据: " + user);
//响应结果
return "hello world";
}
获取同键不同值的请求数据
目前我们在单元方法上可以使用形参或者实体类来接收请求数据,美滋滋。但是有某些请求中,请求数据是同键不同值的。比如,在页面中的多项选择的请求数据,爱好,fav=1&fav=2&fav3.像这样的请求数据,如何获取呢?
我们自己使用Request对象获取同键不同值的数据,使用 req.ParameterValues(“键名”),返回值是String[]数组。在单元方法上声明形 参,类型为String[]数组类型,要求形参名和请求数据的键名一致即可。
@RequestMapping("getDataBykey")
public String getDataBykeyValues(String[] fav){
//处理请求
System.out.println("解耦方式:获取同键不同值的请求数据 " + fav[0]);
//响应结果
return "hello world";
}
混合使用紧耦和解耦方式获取请求数据
目前我们可以在单元方法中使用形参,实体类,request对象方式获取请求数据,但是如果请求中的数据,一部分要放入到对应的实体类中,一部分要使用形参直接获取怎么办?
我们可以在单元方法上根据自己的需求来声明形参获取请求数据, DispatcherServlet会想尽一切办法给我们声明的单元方法的形参赋值。那么如果没有赋值,则表明形参声明有问题。我们的获取请求数据的方式可以混合使用。
@RequestMapping("getData")
public String getDataBy(User user,@RequestParam("name") String uname,int age,String[]fav,HttpServletRequest request){
//处理请求
System.out.println("实体类: " + user);
System.out.println("形参名:" + uname + " " + age);
System.out.println("同键不同值: " + fav);
System.out.println("request对象的: " + request.getParameter("name"));
//响应结果
return "hello world";
}
SpringMVC对restful请求的支持
restful格式请求的介绍
目前我们浏览器发起请求给服务器的时候,一般我们常用的请求方式有两个,get 和post方式。不管get还是post方式,请求数据的格式都是键值对格式,get 方式请求数据是在请求地址上以问号隔开的形式,拼接在请求地址后,post请求 呢是有专门的请求实体的,例如:
get方式请求:
localhost:8080/project/aa?uname=zhangsan&age=18
post方式请求:
地址:localhost:8080/project/aa
要求我们后台获取请求数据的代码,必须按照指定的键名来获取请求数据。键名 就是请求数据的键名。这样造成,一旦请求数据的键名发生变更,造成后台的逻辑 代码也需要进行变更。前台的请求数据的键名和后台的逻辑代码之间的耦合性过 高,造成前台和后台的开发过程中相互依赖性过高。怎么办?
让前台和后台代码之间进行解耦。也就说不再让请求数据的键名造成前后台代码之间耦合性高。前台请求数据的键名发生变更,不影响后台逻辑代码的正常执行
请求数据不再以键值对的形式发送给后台使用。直接发送数据本身给后台即可。既然请求数据不再使用键值对,请求数据必须按照指定的格式来进行发送。使用restful格式。
传统的get方式请求格式:
localhost:8080/project/aa?uname=zhangsan&age18
restful格式
localhost:8080/project/aa/zhangsan/18
restful格式要求请求数据作为请求地址的一部分发送给后台使用
SpringMVC使用占位{字符}声明公共单元方法
@RequestMapping("aa/{bb}/{cc}")
public String testRestFul(){
//处理请求
System.out.println("处理restful格式的单元方法");
//响应结果
return "/alvin";
}
SpringMVC的单元方法支持模糊匹配的声明,可以声明一个单元方法处理N个请求
因为不同的请求,有可能使用相同的处理逻辑。
@RequestMapping("aa/{bb}/{cc}")
以aa开头,并且单元方法的路径为三层路径的,第二层和第三层路径为任意字符的请求地址都会
调用该单元方法处理请求,比如:aa/asda/asd,aa/asdas/asda
@RequestMapping("{bb}/{cc}")
单元方法的路径有两层,并且每层为任意字符。
@RequestMapping("{bb}")
单元方法的路径有一层,并且为任意字符。
单元方法中获取restful请求地址中的请求数据
@RequestMapping("reg/{name}/{age}/{fav}")
public String testGetDataRestFul(@PathVariable("name") String uname, @PathVariable Integer age, @PathVariable String fav,String course){
//处理请求
System.out.println(uname+":"+age+":"+fav);
System.out.println("键值对数据:"+course);
//响应结果
return "/alvin";
}
restful只是一种请求数据携带的格式,它只是表明将请求数据作为请求地址的一部分发送给后台
和请求方式没有关系,请求地址可以是get方式也可以是post方式来发送。并且restful格式本身
仍然可以使用键值对携带数据.并且仍然按照解耦方式获取即可。