一、springMVC介绍
问题: 在学习完spring后,基于MVC设计模式的项目,可以将controller层和service层进行解耦。但在mvc模式中,controller层的servlet为请求的入口。但是该流程存在许多问题:
- 需要为每个功能创建一个servlet,麻烦
- 请求的参数需要一个一个获取,麻烦
解决: 项目只声明一个Servlet,该Servlet作为项目请求的公共入口。并且在该Servlet必须声明代码,此代码根据请求地址调用对应的逻辑代码处理请求,也就是必须在Servlet的service方法中声明项目资源调用的逻辑代码。但是项目中的功能是非常多的,也就是我们在后台其实已经声明了很多的功能处理的逻辑代码,这样在Servlet中的service方法中就需要声明所有功能的逻辑处理的调用代码,但问题是请求不来无法确定在Servlet中应该调用哪个逻辑来处理请求,所以Servlet中的逻辑调用代码必须是根据请求动态的调用,也就是使用反射。而且,既然该Servlet中使用反射封装了根据请求动态调用逻辑处理请求,则该Servlet中只能存在调用逻辑的公共封装,但不能存在请求处理相关的逻辑,也就是我们在MVC目前的结构中需要在Servlet和service层之间增加一层代码,该层代码中需要进行请求数据的获取以及响应处理,由该层代码继续调用业务层进行请求处理。
SpringMVC将上述的解决方案实现了,他的本质就是讲servlet进行了封装,提供一个公共的servlet。该servlet可以根据请求动态调用对应的逻辑方法完成请求处理
使用流程:
- 配置web.xml
- 配置公共servlet的依赖文件路径(springmvc.xml和applicationcontext.xml的路径)
- 配置公共servlet的访问路径
- 配置编码过滤器
- springmvc.xml的配置
- 注解扫描
- 注解驱动器
- 静态资源放行
- 配置applicationcontext.xml
- 注解扫描
- 配置文件导入
- dataSource配置
- factory配置
- 配置mapper扫描(将factory注入)
- 创建事务管理bean
- 配置事务管理方法
- 配置事务管理的aop
二、单元方法获取请求数据
(一)紧耦方式
- 在控制器类中声明请求处理单元方法,并在单元方法上声明形参,形参类型为 HttpServletRequest,接收DispactherServlet传递的封装了此次请求的请求数据的 request对象。
- 在单元方法中使用request.getParameter(“键名”)获取请求数据
- 在单元方法中声明请求处理的逻辑代码
@RequestMapping("reqData")
public String getDataByRequest(HttpServletRequest request){
//获取请求数据
String uname= request.getParameter("uname");
int age=Integer.parseInt(request.getParameter("age"));
//处理请求
System.out.println("紧耦合方式获取请求数据(request对象):"+uname+age);
//响应
return "aa";
}
(二) 解耦方式
1. 形参名为请求数据键名
含义:让DispatcherServlet获取请求数据后再讲请求数据传递给匹配的单元方法。我们只需要保持请求数据的键名与形参名一致就可以了
/**
* 解耦方式获取请求数据:单元方法的形参名和请求数据的键名一致
* 注意①:
* 如果形参名和请求数据的键名不一致,不会报错,传入null。
* 注意②:
* 如果请求数据的类型和后台单元方法的形参的类型不匹配,则会报400异常或者类型转换异常
* 注意③:
* 如果形参类型为基本类型,则如果请求中没有对应的请求数据,可能会出现
* 数据类型转换异常,比如:将null转换为int,建议将形参都声明为包装类的类型
*/
@RequestMapping("reqDataByArgName")
public String getDataByArgName(String uname,Integer age){
//处理请求
System.out.println("解耦方式形参名为键名获取请求数据:"+uname+age);
//响应结果
return "aa";
}
2. 解决形参名与请求数据键名不一致
当请求数据名与形参名不一致的时候,无论是修改前端的请求数据还是形参名,都会有不小的代价,我们可以借助一种类似于“转接头”的东西,将形参与请求数据键名匹配起来
可以通过注解@RequestParam来实现
/**
* 解耦方式获取请求数据:形参名和请求数据的键名不一致
* 解释:
* DispatcherServlet默认是使用单元方法的形参名即为请求数据的键名来获取请求数据的
* 那么如果形参名和请求数据的键名不一致, 则在单元方法上的形参前使用注解
* @RequestParam来指明请求数据的键名
* 使用:
* value属性:声明请求数据的键名,如果只有value时,value可以省略不写
* required:true|false,设置为true表示请求中必须携带键名为指定键名的请求数据,否则400
* defaultValue:默认值,如果请求中没有对应的请求数据,则使用默认值,不建议和required一起使用
*
*/
@RequestMapping("getDataByReqParam")
public String getDataByReqParam(@RequestParam(value = "name",defaultValue = "哈哈") String uname, Integer age){
//处理请求
System.out.println("解耦方式获取请求数据:形参名和请求数据的键名不一致:"+uname+age);
//响应结果
return "aa";
}
3.使用实体类对象获取请求数据
有时我们请求数据的键名过多,一个一个写形参也会有点麻烦,我们可以为它创建一个实体类对象,直接用这个对象来接收这些数据。
/**
*解耦方式使用实体类对象接收请求
* 要求:
* 实体类的属性和请求数据的键名一致,必须提供get/set方法。
* 注意:
* 实体类的属性类型使用包装类,避免请求中没有对应的数据时出现类型转换异常。
*/
@RequestMapping("argObject")
public String demoArgObject(User user){
//处理请求
System.out.println("MyController.demoArgObject:请求数据使用实体类对象接收:"+user);
//响应结果
return "aa";
}
4.获取同键不同值的请求数据
有某些请求中,请求数据是同键不同值的。比如,在页面中的多项选择的请求数据,
像爱好,fav=1&fav=2&fav=3.像这样的请求数据,如何获取呢?
/**
* 解耦合方式获取同键不同值的数据
* 要求:
* 使用String类型的数组来接收,形参名为请求数据的键名
*/
@RequestMapping("argKeyNotValue")
public String demoArgKeyNotValue(String uname,Integer age,String[] fav){
//处理请求数据
System.out.println("MyController.demoArgKeyNotValue:获取同键不同值的请求数据:"+uname+":"+age+":"+fav[0]);
//响应结果
return "aa";
}
5.混合使用紧耦和解耦方式获取请求数据
问题:
目前我们可以在单元方法中使用形参,实体类,request对象方式获取请求数据,但是如果请求中的数据,一部分要放入到对应的实体类中,一部分要使用形参直接获取怎么办?
@RequestMapping("getData")
public String getData(User user,@RequestParam("uname") String name,Integer age,String[] fav,HttpServletRequest request){
//处理请求
System.out.println("实体类:"+user);
System.out.println("形参名:"+name+age);
System.out.println("同键不同值:"+fav);
System.out.println("request对象的:"+request.getParameter("uname"));
//响应结果
return "aa";
}
三、restful请求
为了解决前台请求数据和后端形参名的耦合问题,我们将原来的键值对的请求数据格式改为请求地址,如下
http://localhost:8080/project01/login?uname=123&pwd=1234
变为
http://localhost:8080/project01/login/123/1234
这样做就不会存在前台请求数据的键名好单元方法中的形参不匹配的问题了。
我们知道单元方法是根据url动态响应的,如果要写一个单元方法面相restful格式的请求,那么它肯定都要面对很多不同的url地址
那后台如何获取restful格式的请求地址来获取数据呢
springMVC通过占位{字符}声明公共单元方法
/**
* 声明单元方法:获取restful请求地址中的请求数据
* 解释:
* 既然前台将请求数据拼接在了请求地址中,造成不同的用户发起的同类型的
* 请求的请求地址不同,比如注册:
* 张三的请求地址:
* localhost:8080/mvc/reg/zhangsan/18/song
* 李四的请求地址:
* localhost:8080/mvc/reg/lisi/20/dance
* .......
* 获取请求地址中的数据
* @PathVariable:
* 声明:在单元方法的形参前声明
* 作用:
* 告诉DispatcherServlet此形参的数据,不要再按照之前的形参名为键名的方式获取了
* 而是从请求地址中获取。默认按照占位符和形参名一致的规则获取,不一致则在
* 注解中声明占位符的名称。
* 注意:
* restful只是一种请求数据携带的格式,它只是表明将请求数据作为请求地址的一部分发送给后台
* 和请求方式没有关系,请求地址可以是get方式也可以是post方式来发送。并且restful格式本身
* 仍然可以使用键值对携带数据.并且仍然按照解耦方式获取即可。
* 总结:
* 其实我们后台就是从请求地址中截取一部分数据来使用了。
* restful+正常的键值对数据
* 单纯的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 "/aa";
}
四、编码过滤器及静态资源放行
(一) 编码过滤器
我们可以配置DispatcherServlet的编码设置,这样每个请求都不需要额外配置了。
<!--配置编码过滤器-->
<filter>
<filter-name>code</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>code</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(二) 静态资源放行
问题:
按照SpringMVC的使用流程,需要在web.xml文件中配置DispatcherServlet的拦截范围,而我们配置的拦截范围为”/”,表示拦截除jsp请求以外的所有请求。这样造成,请求是js,css,图片等静态资源的请求,也会被DispatcherServlet拦截,调用对应的单元方法来处理请求。但是,我们呢是一个静态资源的请求,不应该按照普通单元方法请求的流程来处理,而是将对应的静态资源响应给浏览器使用。
怎么办?
解决:
- 将DispatcherServlet的底层逻辑变更,静态资源的请求就不要作为单元方法请求处理,而是查找对应资源给浏览器
- 在springmvc.xml中配置静态资源放行,告诉DispatcherServlet哪些请求应该放行
代码
<?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.bjsxt.controller"></context:component-scan>
<!--配置注解解析器-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--配置静态资源放行-->
<mvc:resources mapping="/js/**" location="/js/"></mvc:resources>
<mvc:resources mapping="/css/**" location="/css/"></mvc:resources>
<mvc:resources mapping="/images/**" location="/images/"></mvc:resources>
</beans>
注意:
浏览器发起静态资源请求,DispatcherServlet会先按照正常的单元方法逻辑进行处理,如果找不到对应的单元方法,则根据SpringMVC的配置文件的静态资源,判定此次请求是否为静态资源请求,如果是则将资源响应给浏览器,如果不是,则响应404.也就说,不要设置某个单元方法的路径和静态资源的路径
是一致,这样就算配置了静态资源放行,也会导致静态资源无法访问的问题。
五、springMVC的响应
以前我们是自己在Servlet中使用response对象来完成响应的,那么在SpringMVC中如何响应请求的处理结果呢?
解决:
- 紧耦方式使用原生的response对象完成
- 单元方法中的返回值决定springmvc的响应
1.紧耦方式:response
/***
* 使用response对象完成响应
* 1.单元方法的返回值类型设置void
* 因为使用response对象在单元方法中直接对此次请求进行了响应,不再通过
* DispatcherServlet了,既然已经响应了,就不需要再给DispatcherServlet返回值了。
* 2. 在单元方法上声明HttpServletResponse形参,来接收
* 此次请求的response对象。
* 3.在单元方法中直接使用response对象完成响应
* 直接响应
* 请求转发
* 重定向
*/
@RequestMapping("resp")
public void demoResp(String uname,Integer age,HttpServletRequest req,HttpServletResponse response) throws IOException, ServletException {
//处理请求
System.out.println("MyControllerResp.demoResp:使用原生的response对象完成响应:"+uname+":"+age);
//响应结果
response.setContentType("text/html;charset=utf-8");
//直接响应
//response.getWriter().write("直接响应");
//请求转发
//req.getRequestDispatcher("/index.jsp").forward(req,response);
//重定向
response.sendRedirect(req.getContextPath()+"/redirect.jsp");
}
2.使用forward关键字完成响应
- 作用:实现请求转发
- 使用:通过单元方法的返回值来告诉DispatcherServlet请求转发指 定的资源。
- 注意:如果是请求转发,forward关键字可以省略不写的。**
/**
* 使用forward关键字完成请求转发jsp资源
* 使用:
* 使用返回值告诉DispatcherServlet请求转发到指定的资源。
* 注意:
* 单元方法的返回值类型为String
* 示例:
* return "forward:/index.jsp";
* return "响应方式:资源路径";
* 注意:
* 路径中的第一个"/"表示项目根据目录,使用了"/"后资源路径为绝对路径
* 从项目的webapp目录下开始查找对应的jsp资源了。
*/
@RequestMapping("demoForwardJsp")
public String demoForwardJsp(String uname,Integer age){
//处理请求
System.out.println("使用forwrd关键字请求转发Jsp资源:"+uname+age);
//响应结果
//请求转发
return "forward:/index.jsp";
}
3.使用redirect关键字完成响应
/**
* 使用redirect关键字重定向到jsp资源
* 使用:
* 使用返回值告诉DispatcherServlet重定向到指定的jsp资源
* 示例:
* return "redirect:/index.jsp";
* return "redirect:/资源路径"
* 注意:
* 路径中的第一个"/"表示项目根据目录,使用了"/"后资源路径为绝对路径
* 从项目的webapp目录下开始查找对应的jsp资源了。
*
*
*/
@RequestMapping("demoRedirectJsp")
public String demoRedirectJsp(String uname,Integer age){
//处理请求
System.out.println("使用redirect关键字重定向到jsp资源:"+uname+age);
//响应结果
return "redirect:/index.jsp";
}