SpringMVC

一、SpringMVC

SpringMVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型。

SpringMVC前端控制器是DispatcherServlet,应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和试图解析器(View Resolver)进行试图管理;页面控制器/动作/处理器为Controller接口的实现;支持本地化解析、主题解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定制,提供了强大的约定大于配置的契约式编程支持。

(一)SpringMVC能帮我们做什么

  • 让我们能非常简单的设计出干净的web层和薄薄的web层,进行更简洁的web层开发
  • 天生与Spring框架集成
  • 提供强大的约定大于配置的契约式编程支持
  • 能简单的进行web层的单元测试
  • 支持灵活的URL到页面控制器的映射
  • 非常容易与其他试图集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用)
  • 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API
  • 提供一套强大的JSP标签库,简化JSP开发
  • 支持灵活的本地化、主题等解析
  • 更加简单的异常处理
  • 对静态资源的支持
  • 支持Restful风格

SpringMVC框架也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则发给响应的页面控制器进行处理。

SpringMVC处理请求的流程:

(二)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";
		}

(二)解耦方式

1.形参名和请求数据的健名一致

		/**
		 * 	使用:
		 * 		在单元方法上声明形参,形参名和请求数据的键名一致。
		 * 	特点:
		 * 		DispactherServlet在调用单元方法时会根据单元方法的形参名去获取此次请求
		 * 		的request对象中的请求数据,并将获取的数据作为实参传递给单元方法。
		 * 	优点:
		 * 		DispactherServlet会根据形参的类型将请求数据强制转换后传递。
		 */
		@RequestMapping("demo2")
		public  String demo2(String uname,int age){
			System.out.println("MyCon.demo2(解耦方式一:)"+uname+age);
			return "index.jsp";
		}

2.形参名和请求数据的健名不一致

	/**
	 * 解耦方式二:请求中的键名和单元方法的形参名不一致
	 * 	使用:如果单元方法的形参名和请求的键名不一致,则在形参中使用注解@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";
	}	

3.使用实体类类型的形参

	/**
	 * 解耦方式三:可以声明一个实体类类型的形参,接收封装好的对象
	 * 	使用:声明一个实体类,该类的属性名必须和请求的键名一致,在单元方法上声明实体类类型的形参即可
	 */
	@RequestMapping("demo4")
	public  String demo4(User u,String uname){
		System.out.println("MyCon.demo2(解耦方式三:)"+u.getUname()+u.getAge()+uname);
		return "index.jsp";
	}	

4.获取同键不同值得请求数据

	/**
	 * 解耦方式四:获取同键不同值的请求数据
	 *  使用:
	 *  	在单元方法中声明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";
	}

5.restful格式的请求地址及其请求数据

	/**
	 *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的上传下载

问题:SpringMVC将Service进行了封装后,变成了公共的。以前我们是将上传下载的代码直接声明在servlet的,那么在使用SpringMVC之后怎么办呢?

解决:使用SpringMVC的上传下载。

(一)所需jar包

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

(二)springmvc.xml配置上传解析bean



        
        
        
       	
        
        
        
        
        
        
        
        	
                
    		
    		
        
        
        
            
                
        	limit.jsp
        	
            
        
     

(三)web.xml配置



 	
  		
  			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)));
    }
}

(五)JSP页面

1.upload.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>




Insert title here


	

SpringMVC之上传


用户ID:
用户名:
头像:

2.success.jsp

<%@ 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} 下载

六、SpringMVC拦截器

问题:我们之前学习了过滤器,我们知道,过滤器是拦截Servlet请求的。先过滤后Servlet,但是在使用了SpringMVC后,Servlet就只有一个了,这样造成只要项目中配置了过滤器,就会拦截非JSP以外的所有请求,无法根据业务需求拦截指定的请求?

解决:

①目前责任链执行流程:请求-》过滤器-》DispactherServlet-》控制器单元方法

②解决方案流程:请求-》过滤器-》DispactherServlet-》拦截器-》控制器单元方法

作用:拦截单元方法,将Servlet传递给单元方法的数据进行预处理,将单元方法的返回值给Servlet之前进行预处理。

特点:①创建一个实现了拦截器接口的java类,②在springmvc.xml中配置拦截器bean,③在springmvc.xml中配置拦截器的拦截返回

(一)springmvc.xml配置



        
        
        
       	
        
        
        
        
        
        
        
        
        	
        		
        		
        		 
        		 
        			
        			
        			
                                
        			
        			 
        	
   

(二)实现HandlerInterceptor接口

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文件中配置在前面的拦截器为外拦截器,配置在后面的拦截器为内拦截器。

七、SpringMVC运行原理

(一)四大核心组件

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:字符编码过滤器

(四)时序图

SpringMVC_第1张图片

(五)文字叙述

当用户发起请求后,执行DiapacherServlet,如果是JSP直接调用jsp页面,如果不是JSP,DispactherServlet调用HandlerMapping判断请求URL是否合法,如果不是URL不存在报错,如果URL存在使用HandlerAdpater调用具体的HandlerMethod,当Handler执行完成后会返回ModelAndView,会被ViewResovler解析,调用具体的物理视图,最终响应给客户端浏览器。

你可能感兴趣的:(JAVA,SE)