SpringMVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型。
SpringMVC前端控制器是DispatcherServlet,应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和试图解析器(View Resolver)进行试图管理;页面控制器/动作/处理器为Controller接口的实现;支持本地化解析、主题解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定制,提供了强大的约定大于配置的契约式编程支持。
SpringMVC框架也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则发给响应的页面控制器进行处理。
SpringMVC处理请求的流程:
SpringMVC提供一个公共的Servlet:根据请求动态获取此次请求要调用的控制器对象和单元方法。给每个控制器取个别名,控制器的单元方法也都取个别名,在服务器启动的时候我们会创建Spring容器的子容器对象。子容器对象就会完成控制器对象的初始化创建,以及所有单元方法的扫描。生成一个Map集合,并将map集合赋值给Servlet的全局属性。
(1)导入jar包
commons-logging-1.1.3.jar
spring-aop-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
spring-web-4.1.6.RELEASE.jar
spring-webmvc-4.1.6.RELEASE.jar
(2)配置web.xml
mvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
mvc
/
(3)配置springmvc.xml
(4)创建控制器类并声明单元方法,以及给单元方法使用注解配置别名
@Controller
public class MyCon {
// 单元方法
@RequestMapping("demo")
public String demo(){
System.out.println("MyCon.demo()");
return "index.jsp";
}
}
(5)启动服务器发起请求
localhost:8080/虚拟项目名/单元方法别名
控制器:声明了servlet要调用的方法的类
单元方法:控制器中声明的被servlet调用处理请求的方法
流程:浏览器-->请求-->服务器-->DispactherServlet-->获取请求中附带的单元方法的别名-->根据别名获取单元方法对象-->执行单元方法。
/**
* 紧耦方式
* 使用: 直接在单元方法上声明形参 HttpServletRequest req
* 特点: DispactherServlet会将此次请求的req对象作为实参传递给调用的单元方法
* 缺点: 需要程序员自己编写代码获取req中封存的请求数据。
*/
@RequestMapping("demo1")
public String demo1(HttpServletRequest req){
//获取请求数据
String uname=req.getParameter("uname");
int age=Integer.parseInt(req.getParameter("age"));
System.out.println("MyCon.demo1()"+uname+age);
return "index.jsp";
}
/**
* 使用:
* 在单元方法上声明形参,形参名和请求数据的键名一致。
* 特点:
* DispactherServlet在调用单元方法时会根据单元方法的形参名去获取此次请求
* 的request对象中的请求数据,并将获取的数据作为实参传递给单元方法。
* 优点:
* DispactherServlet会根据形参的类型将请求数据强制转换后传递。
*/
@RequestMapping("demo2")
public String demo2(String uname,int age){
System.out.println("MyCon.demo2(解耦方式一:)"+uname+age);
return "index.jsp";
}
/**
* 解耦方式二:请求中的键名和单元方法的形参名不一致
* 使用:如果单元方法的形参名和请求的键名不一致,则在形参中使用注解@RequestParam("请求数据键名")
* 的方式来获取请求数据。
* 内容
* 简写方法:@RequestParam("请求数据键名")
* 官方写法
* @RequestParam(value="请求数据键名",defaultValue="",required=true)
* defaultValue:默认值
* required:
* false:默认值,此次请求中可以没有该形参的值。
* true:此次请求必须给该形参赋值,否则400
*/
@RequestMapping("demo3")
public String demo3(@RequestParam(value="uname2",defaultValue="hhh",required=true)String uname,int age){
System.out.println("MyCon.demo2(解耦方式二:)"+uname+age);
return "index.jsp";
}
/**
* 解耦方式三:可以声明一个实体类类型的形参,接收封装好的对象
* 使用:声明一个实体类,该类的属性名必须和请求的键名一致,在单元方法上声明实体类类型的形参即可
*/
@RequestMapping("demo4")
public String demo4(User u,String uname){
System.out.println("MyCon.demo2(解耦方式三:)"+u.getUname()+u.getAge()+uname);
return "index.jsp";
}
/**
* 解耦方式四:获取同键不同值的请求数据
* 使用:
* 在单元方法中声明String数组类型的形参,形参名为同键不同值的键。
* 在单元方法中声明ArrayList 集合,但是需要使用@RequestParam("请求键名")获取请求数据。
*/
@RequestMapping("demo5")
public String demo5(String uname,int age,@RequestParam("fav")ArrayList fav){
System.out.println("MyCon.demo2(解耦方式四:)"+uname+age+fav.get(0));
return "index.jsp";
}
/**
*restful格式的请求地址及其请求数据
* 传统方式的请求地址及请求数据:
* localhost:8080/project/demo6?uname=zhangsan&age=18
* restful格式的请求地址及其请求数据:
* localhost:8080/project/demo6/zhangsan/lisi
* restful格式的特点:
* ①该格式支持get请求方式
* ②该格式将请求数据以请求地址的形式发送给服务器,而不是键值对。
* 技能点:
* ①在@RequestMapping中声明的单元方法的别名可以使用{键名}进行通用声明
* ②可以在单元方法的形参声明中使用注解@PathVariable("键名")获取请求地址中的数据。
* 总结:
* 所谓restful风格的请求,说白了就是将请求数据声明在了请求地址信息中,然后在后台
* 中声明对应的占位,然后将请求数据从路径中切割出来,然后赋值给单元方法的形参
*/
@RequestMapping("demo6/{a1}/{b1}")
public String demo6(@PathVariable("a1")String uname,@PathVariable("b1")String age){
System.out.println("MyCon.demo2(解耦方式五:)"+uname+age);
return "index.jsp";
}
响应方式:请求转发、重定向、直接响应
执行流程:服务器接受到请求后会调用DispactherServlet处理请求,而DispactherServlet会根据请求动态的调用对应的单元方法,在单元中声明了请求处理代码,单元方法被执行完毕后继续执行DispactherServlet,由DispactherServlet根据单元方法的执行结果来响应处理结果。
响应的实现:以单元方法返回值的形式。因为Servlet不是我们编写的,但是我们又要告诉Servlet如何进行处理结果的响应,可以以单元方法的返回值的形式告诉DispactherServlet是该请求转发还是重定向。
返回值的内容:请求转发或者重定向的标识符以及资源路径。(foward:index.jsp)(redirect:index.jsp)
返回值的类型:String类型、View接口类型、ModelAndView类型
@Controller
public class MyConReturn {
//返回值类型为String类型
//请求转发
@RequestMapping("mf")
public String myForward(HttpServletRequest req,String uname,int age){
//使用request作用域存储数据
req.setAttribute("uname", uname);
//响应
return "forward:/findex.jsp";
}
//重定向
@RequestMapping("mr")
public String myRedirect(HttpServletRequest req,String uname,int age){
//将数据存储到session中
HttpSession ss = req.getSession();
ss.setAttribute("age",age);
//响应
return "redirect:/rindex.jsp";
}
//直接响应
@RequestMapping("mp")
public void myResp(HttpServletResponse resp) throws IOException{
//直接响应
resp.getWriter().write("today is a good day, To study is nice");
}
//返回值类型为View接口类型
/**
* ①View v=new InternalResourceView("/findex.jsp");
* 作用:存储了请求转发的路径。
* ②View v5=new RedirectView("/27-SpringMVC-return/rindex.jsp");
* 作用:存储了重定向的路径
* 总结:
* 将路径存储到View接口的不同的实例化对象中,来区分是请求转发还是重定向。
* 将其返回给DispactherServlet使用
*/
@RequestMapping("mv")
public View testView(String name,int age){
//请求转发
View v=new InternalResourceView("/findex.jsp");
//重定向
View v5=new RedirectView("/27-SpringMVC-return/rindex.jsp");
return v5;
}
//返回值类ModelAndView
/**
* ModelAndView是SpringMVC继View类型返回的升级版
* 使用:
* 支持字符格式
* 支持View类型
* 作用:
* 根据存储的资源类型来判断请求转发和重定向。
* Model对象的特点
* Model对象可以存储作用域数据,类似request.
* 但是在重定向的第二次请求会将其存储的基本类型数据以请求数据的方式
* 再次发给服务器,而不是第一次请求结束就销毁。
*/
@RequestMapping("mav")
public ModelAndView testModelAndView(Model m,String uname,int age){
//请求转发
ModelAndView mv=new ModelAndView("forward:/findex.jsp");
m.addAttribute("uname",uname);
//重定向
ModelAndView mv2=new ModelAndView("redirect:/rindex.jsp");
return mv2;
}
默认视图解析器:我们在单元方法中直接返回字符串数据给DispachterServlet让其解析返回的字符串数据,来完成请求转发和重定向。
问题:在web项目中在webContent文件夹下除WEB-INF文件夹以外的所有资源对浏览器是可见的。也就是只要我们知道该文件的资源路径,在浏览器中就可以直接访问,非常的不安全。
解决:因为web-inf文件夹对浏览器是不可见的,所以我们可以将重要的静态资源文件放到web-inf文件夹下。这样浏览器就算知道地址也无法直接访问,但是可以使用请求转发的方法来间接的访问web-inf下的资源。
实现:在单元方法的返回值,使用请求转发WEB-INF下的资源,return "forward:/WEB-INF/资源路径"
问题:在请求转发WEB-INF下的资源的返回值的路径书写起来过于麻烦,而且因为使用的地方多了会造成不好修改。
解决:使用自定义试图解析器。
@Controller
public class MyConSelf {
//默认视图解析器
@RequestMapping("ms")
public String mySelf(){
System.out.println("MyConSelf.mySelf()");
//请求转发Jsp
//return "forward:/index.jsp";
//请求转发其他单元方法
return "forward:ms2";
}
@RequestMapping("ms2")
public String mySelf2(){
System.out.println("MyConSelf.mySelf2()");
return "forward:/index.jsp";
}
//自定义视图解析器
@RequestMapping("ms3")
public String mySelf3(){
System.out.println("MyConSelf.mySelf()");
//请求转发Jsp
//return "forward:/index.jsp";
//请求转发其他单元方法
return "main";
}
}
注意:在Springmvc的配置文件中配置了自定义视图解析器后,如果单元方法的返回值中没有forward,则会被自定义视图解析器给返回值添加了前缀和后缀后再转给DispactherServlet,如果声明了forward,则会走默认试图解析器,直接根据返回值进行资源的请求转发。
问题:SpringMVC将Service进行了封装后,变成了公共的。以前我们是将上传下载的代码直接声明在servlet的,那么在使用SpringMVC之后怎么办呢?
解决:使用SpringMVC的上传下载。
aopalliance.jar
asm-3.3.1.jar
aspectjweaver.jar
cglib-2.2.2.jar
commons-fileupload-1.3.2.jar
commons-io-2.5.jar
commons-logging-1.1.1.jar
commons-logging-1.1.3.jar
javassist-3.17.1-GA.jar
jstl-1.2.jar
log4j-1.2.17.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
mybatis-spring-1.2.3.jar
mysql-connector-java-5.1.30.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
spring-aop-4.1.6.RELEASE.jar
spring-aspects-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
spring-jdbc-4.1.6.RELEASE.jar
spring-tx-4.1.6.RELEASE.jar
spring-web-4.1.6.RELEASE.jar
spring-webmvc-4.1.6.RELEASE.jar
standard-1.1.2.jar
limit.jsp
contextConfigLocation
classpath:applicationcontext.xml
org.springframework.web.context.ContextLoaderListener
mvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
mvc
/
code
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
code
/*
@Controller
public class MyConUpload {
/**
* 将上传数据存储到服务器硬盘中
* @param uid 上传的用户ID
* @param uname 上传的用户名
* @param photo 封存了上传文件所有相关数据的对象。
* @return
* @throws IOException
*/
@RequestMapping("upload")
public String myUpload(int uid,String uname,MultipartFile photo,HttpServletRequest req) throws IOException{
//创建动态的文件名
//获取文件的后缀名,photo.getOriginalFilename()获取原始文件名
String suffixName = photo.getOriginalFilename().substring(photo.getOriginalFilename().lastIndexOf("."));
//校验文件类型
if(!(".jpg".equals(suffixName) ||".png".equals(suffixName) || ".bmp".equals(suffixName))){
return "forward:/error.jsp";
}
//创建动态的文件名
String name=UUID.randomUUID()+""+System.currentTimeMillis();
//拼接新的文件名
String newName=name+suffixName;
//获取动态的存储路径
//动态的获取项目根目录下的绝对路径
String path=req.getServletContext().getRealPath("/images");
File fpath=new File(path);
if(!fpath.exists()){
fpath.mkdirs();
}
//拼接数据存储的绝对路径
File f=new File(fpath,newName);
photo.transferTo(f);
//将上传记录存储到数据库记录表中(用户ID,上传文件原始名,上传文件新名,时间,文件类型)
int i = uploadServiceImpl.insUploadInfo(uid, photo.getOriginalFilename(),
newName, photo.getContentType());
if(i>0){
return "forward:/success";
}else{
return "forward:/fail.jsp";
}
}
//查询所有的上传信息
@RequestMapping("success")
public String getUploadInfo(HttpServletRequest req){
List lp=uploadServiceImpl.selUploadInfoService();
//将数据存储到作用域中
req.setAttribute("lp",lp);
return "forward:/success.jsp";
}
}
@Controller
public class MyConDown {
//下载图片资源
@RequestMapping("down")
public void getImage(String newName,String contentType,HttpServletResponse resp,HttpServletRequest req)
throws IOException{
//设置响应的数据的MIME类型
resp.setContentType(contentType);
//设置响应头:告诉浏览器将接受的流数据另存为存储到客户端,而不是直接解析
String str1="文件名";
String str2=new String(str1.getBytes("utf-8"), "iso-8859-1");
resp.setHeader("Content-Disposition", "attachment;filename="+str2);
//获取要下载的资源的读取流对象
//获取资源存储路径
String path=req.getServletContext().getRealPath("/images");
//FileInputStream fs=new FileInputStream(new File(path,newName));
//获取输出流
ServletOutputStream os = resp.getOutputStream();
os.write(FileUtils.readFileToByteArray(new File(path,newName)));
}
}
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
Insert title here
SpringMVC之上传
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Insert title here
显示所有的上传记录
上传编号
用户编号
原始名
存储名
预览
上传时间
类型
操作
${p.upid}
${p.uid}
${p.oldName}
${p.newName}
${p.uploadTime}
${p.contentType}
下载
问题:我们之前学习了过滤器,我们知道,过滤器是拦截Servlet请求的。先过滤后Servlet,但是在使用了SpringMVC后,Servlet就只有一个了,这样造成只要项目中配置了过滤器,就会拦截非JSP以外的所有请求,无法根据业务需求拦截指定的请求?
解决:
①目前责任链执行流程:请求-》过滤器-》DispactherServlet-》控制器单元方法
②解决方案流程:请求-》过滤器-》DispactherServlet-》拦截器-》控制器单元方法
作用:拦截单元方法,将Servlet传递给单元方法的数据进行预处理,将单元方法的返回值给Servlet之前进行预处理。
特点:①创建一个实现了拦截器接口的java类,②在springmvc.xml中配置拦截器bean,③在springmvc.xml中配置拦截器的拦截返回
public class MyIn implements HandlerInterceptor{
@Resource
private MyCon myCon;
/**
* 作用:拦截单元方法,在单元方法之前执行,对数据进行预处理
* 参数:Object arg2(HandleMethod类型) 该参数的实参中封装了两次请求要调用的单元方法Method对象
* 可以实现在preHandle方法中调用一次此次请求的单元方法
* 返回值:boolean false拦截此次请求不再继续执行单元方法
* true放行,继续执行单元方法
*/
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object arg2)
throws Exception {
//在拦截器中调用单元方法
HandlerMethod hm=(HandlerMethod) arg2;
hm.getMethod().invoke(myCon, "李四",20);
return true;
}
/**
* 作用:在单元方法之后执行,jsp之前,可以对单元方法跳转的资源进行重新定义,对敏感数据进行和谐
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,
ModelAndView mv) throws Exception {
//获取当次请求的单元方法的返回值
System.out.println(mv.getViewName());
//敏感词汇过滤
String str=(String) mv.getModel().get("str");
if(str.contains("中国")){
str=str.replaceAll("中国", "**");
}
mv.getModel().put("str",str);
//修改返回值,改变资源跳转
//mv.setViewName("forward:/index222222.jsp");
}
/**
* 作用:在最后执行,处理前面的方法的异常
* 参数:Exception arg3 接受异常信息
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,
Exception arg3)throws Exception {
System.out.println("MyIn.afterCompletion(3):");
}
}
注意:在SpringMVC.xml文件中配置在前面的拦截器为外拦截器,配置在后面的拦截器为内拦截器。
DispatchServlet:servelt分发器,整个SpringMVC框架入口
HandlerMapping:寻找URL所请求的HandlerMethod,找@RequestMapper(),使用实现类DefaultAnnotationHandlerMapping实际工作
HandlerAdapter:实际调用HandlerMethod的组件,使用实现类AnnotationMethodHandlerAdapater
ViewResovler:试图解析器,作用解析HandlerMethod返回值,把逻辑视图转换为需要调用的物理视图。自定义时InternalResourceViewResolver
当配置了
Controller:控制器类
HandlerMethod:控制器方法
View:视图
Model:模型
ModelAndView:模型和视图,SpringMVC所有HandlerMethod最终都会转换成ModelAndView
HandlerInterceptor:拦截器
HandlerExceptionResolver:异常映射解析器
MultipartResolver:Multipart解析器
CharacterEncodingFilter:字符编码过滤器
当用户发起请求后,执行DiapacherServlet,如果是JSP直接调用jsp页面,如果不是JSP,DispactherServlet调用HandlerMapping判断请求URL是否合法,如果不是URL不存在报错,如果URL存在使用HandlerAdpater调用具体的HandlerMethod,当Handler执行完成后会返回ModelAndView,会被ViewResovler解析,调用具体的物理视图,最终响应给客户端浏览器。