5、springMVC

springMVC

什么是SpringMVC?

SpringMVC是Spring框架内置的MVC的实现。SpringMVC是Spring的一个MVC框架。MVC框架,它解决了WEB开发中常见的问题,如参数的接收,文件的上传下载,表单验证,国际化等等。而且使用起来简单,与spring无缝集成。还支持restful风格的URL请求。采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。

springMVC的作用

MVC模式:(Model-View-Controller):为了解决后台代码与页面代码的分离

SpringMVC底层就是的Servlet,SpringMVC就是对Servlet进行更深层次的封装

传统的web开发模式

目前web应用中,99%的项目都用到MVC模式。

MVC的web开发

WEB开发从20世纪90+年代开始,也是使用MVC模式。在最原始的MVC上有一些改进

优秀的框架改变了这种模式,将model更广泛的使用,这样会比原始的mvc好多了.

像现在一些优秀的mvc的框架,如Struts2,springMVC

在客户端提交也使用了模型来请求参数

spring MVC 也实现的相关的功能

MVC的入门案例

  1. 导入两个支持mvc的jar

  1. springmvc的配置

    <!-- 配置包扫描 -->
        <context:component-scan base-package="cn.zj.springmvc"/>
        <!-- 开启springmvc的注解 -->
        <mvc:annotation-driven/>
    
  2. springmvc的前端控制器

    <!-- 配置前端控制器 -->
        <servlet>
            <servlet-name>springMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- 初始化:读出springmvc.xml的配置文件 -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>springMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
  3. 使用@RequestMapping注解映射访问路径

springmvc的执行流程

SpringMVC流程:
01、用户发送出请求到前端控制器DispatcherServlet。
02、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

03、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
04、DispatcherServlet调用HandlerAdapter(处理器适配器)。
05、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
06、Controller执行完成返回ModelAndView对象。
07、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
08、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
09、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。

涉及组件分析:
1、前端控制器DispatcherServlet(不需要程序员开发),由框架提供,在web.xml中配置。
作用:接收请求,响应结果,相当于转发器,中央处理器。

2、处理器映射器HandlerMapping(不需要程序员开发),由框架提供。
作用:根据请求的url查找Handler(处理器/Controller),可以通过XML和注解方式来映射。

3、处理器适配器HandlerAdapter(不需要程序员开发),由框架提供。
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler。

4、处理器Handler(也称之为Controller,需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
作用:接受用户请求信息,调用业务方法处理请求,也称之为后端控制器。

5、视图解析器ViewResolver(不需要程序员开发),由框架提供
作用:进行视图解析,把逻辑视图名解析成真正的物理视图。
SpringMVC框架支持多种View视图技术,包括:jstlView、freemarkerView、pdfView等。

6、视图View(需要工程师开发)
作用:把数据展现给用户的页面
View是一个接口,实现类支持不同的View技术(jsp、freemarker、pdf等)

对静态资源的访问

如果在项目的web.xml中servlet的拦截路径使用/进行拦截,会对静态资源进行拦截。原因是将父类的web.xml中的路径也有一个/,那里是对静态资源进行放行,这样就冲突了,所以请求路径可以写成*.do,要想请求,以.do结尾的都可以访问。在我们的注解@RequestMapping中的url可以不用写.do,会默认加上的,好后期的维护。

@RequestMapping

@RequestMapping("")默认使用的是value的属性。@RequestMapping(value="")

两种限制

  1. 参数的限制

    @RequestMapping的params={"username","password"}可以对请求的参数限制name属性,如果与里面的参数不一致,就会报400参数的异常,格式还有params={"username=张三"}限制username只能等于张三,params={"!username"}限制没有这个属性的都可以,params={"userid!=123"}参数不是指定值。

  2. 方法的限制

    @RequestMapping的method属性,如果method="get",那么请求的方式必须是get,如果是其他的请求方式,就会报405方法不匹配的错误。

	/**
	 * @RequestMapping 映射请求路径的注解
	 * method:方法的限制
	 * params:参数的限制
	 * @return
	 */
	@RequestMapping(value = "/method",method = RequestMethod.POST,params = {"username","password"})
	public ModelAndView handleRequest() {
		//新建一个ModelAndView对象
		ModelAndView mv = new ModelAndView();
		//数据传输
		mv.addObject("msg", "你好呀");
		//请求转发
		mv.setViewName("/WEB-INF/jsp/hello.jsp");
		//返回ModelAndView
		return mv;
	}

数据绑定

数据绑定通俗的说就是接收参数。

@Controller
@RequestMapping("/request")
public class HelloController{
	/**
	 * 传统的接收参数
	 */
	@RequestMapping("/method2")
	public void method2(HttpServletRequest req,HttpServletResponse resp) {
		String username = req.getParameter("username");
		String age = req.getParameter("age");
		System.out.println("username : " +username);
		System.out.println("age : " +age);
	}
	/**
	 * 直接通过形参传进来
	 * 这样就不用强转类型了
	 */
	@RequestMapping("/method3")
	public ModelAndView method3(String username,Integer age) {
		System.out.println("123");
		System.out.println("username:"+username);
		System.out.println("age:"+age);
		return null;
	}
	/**
	 * 前台不同名接收参数
	 * 使用@RequestParam
	 * @param username
	 * @param age
	 * @return
	 */
	@RequestMapping("/method4")
	public ModelAndView method4(@RequestParam("name") String username,@RequestParam("age") Integer age) {
		System.out.println("username:"+username);
		System.out.println("age:"+age);
		return null;
	}
	
	/**
	 * 接收数组
	 */
	@RequestMapping("/method5")
	public ModelAndView method5(@RequestParam("name") String username,@RequestParam("age") Integer age,String[] hobbys){
		System.out.println("username:"+username);
		System.out.println("age:"+age);
		System.out.println("hobbys:"+Arrays.toString(hobbys));
		return null;
	}
	
	/**
	 * 接收pojo对象
	 */
	@RequestMapping("/method6")
	public ModelAndView method6(User user){
		System.out.println(user);
		return null;
	}
	
	/**
	 * 接受map:只能接收单个值
	 */
	@RequestMapping("/method7")
	public ModelAndView method7(@RequestParam Map<String,Object> map){
		System.out.println(map);
		return null;
	}
}

响应传值

	/*	有 A 和 B
	 * 请求转发:
	 * 	1.A请求转发到B时,地址栏还是A(地址栏的url不会改变)
	 * 	2.A可以通过请求对象共享数据给B
	 * 	3.可以转发访问到WEB-INF下的资源
	 * 	4.不可以跨域
	 * 重定向:
	 * 	1.A重定向到B时,地址栏的url改变成B
	 * 	2.A不可以通过请求对象共享数据给B
	 * 	3.不可以转发访问到WEB-INF下的资源
	 * 	4.可以跨域
	 */
	
	/**
	 * 传递的方式,只发生在请求转发中
	 * @throws IOException 
	 * @throws ServletException 
	 * 1.传统的请求对象
	 * 2.ModelAndView中的setViewName(path):默认是请求转发,通过addObject()传值
	 * 3.返回一个字符串的形式:通过Model来传值,返回值就是转发路径,默认是请求转发(用得最多)
	 * 
	 */
  1. void方式:传统的方式使用request里面设置属性,通过请求转发传值给页面。

    @RequestMapping("/method1")
    	public void method(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    		req.setAttribute("name", "旺财");
    		req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req, resp);
    	}
    
  2. 通过ModelAndView转值

    @RequestMapping("/method2")
    	public ModelAndView method2() {
    		ModelAndView mv = new ModelAndView();
    		mv.addObject("name", "菲菲");
    		mv.setViewName("/WEB-INF/jsp/hello.jsp");
    		return mv;
    	}
    
    
  3. Model:通过springmvc的Model对象,可以通过addAtrribute方法,传递数据给页面

    @RequestMapping("method3")
    	public String method3(Model md) {
    		md.addAttribute("name", "兮兮");
    		return "/WEB-INF/jsp/hello.jsp";
    	}
    
    
  4. 通过JSON字符串的形式(用得最多的):spring有专门对处理JSON字符串传值的方式进行了封装

    /**
    	 * @ResponseBody:将响应内容设为普通字符串
    	 * 请求转发的路径是:/WEB-INF/jsp/response+返回首字母小写作为文件夹.jsp
    	 */
    	@RequestMapping("/method")
    	@ResponseBody
    	public User method() {
    		User user = new User();
    		user.setEmail("www@qq.com");
    		user.setPassword("aaa");
    		user.setUsername("旺财");
    		return user;
    	}
    	/**
    	 * 还可以返回一个list集合
    	 */
    	@RequestMapping("/method2")
    	@ResponseBody
    	public List<User> method2(){
    		List<User> users = new ArrayList<User>();
    		for (int i = 0; i < 10; i++) {
    			User user = new User();
    			user.setEmail("www@qq.com");
    			user.setPassword("aaa");
    			user.setUsername("旺财");
    			users.add(user);
    		}
    		return users;
    	}
    
    

处理乱码问题

springMVC可以对乱码进行处理,只需要设置springmvc的过滤器,对编码进行设置。

 <!-- 解决中文乱码问题:springmvc的过滤器
   post方式的设置
   get方式:在tomcat8.x后就默认是utf-8,之前还需要到tomcat的配置文件servlet.xml中配置,
   找到端口号8080的那一行,加上URIEncoding="UTF-8"
    -->
   <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>

支持RESTFUL风格的url请求

请求的地址栏是这样的,这种url地址称为伪静态的页面。

代码如下:可用来接收id等。

/**
	 * @PathVariable:路径变量
	 * 
	 * 伪静态的请求
	 * 
	 */
	@RequestMapping("/method1/{id}")
	public ModelAndView method(@PathVariable("id") Long id) {
		System.out.println(id);
		return null;
	}

请求转发和重定向

首先,我们返回一个url地址时,有重复的url路径,我们可以设置前后两边的url,那我们只需要返回我们的逻辑的url就行了。

 <!--配置视图解析器:开启前缀后缀的命名 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 配置前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 配置后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

我们这样做有一个缺陷,就是我们想转发到其他的页面转发不了,因此我们需要在返回的时候在逻辑的url前加上forward:这样表示让配置的视图解析器无效。这样就能跳出了。

我们在使用ModelAndView和返回String类型时,默认都是请求转发,有没有重定向的呢?当然有,不过ModelAndView只能请求转发,返回String类型的只需要在前面加上redirect:就表示请求转发。

	/**
	 * 只有返回值是String类型的才可以
	 */
	@RequestMapping("/method")
	public String method(Model md) {
		md.addAttribute("name", "兮兮");
		return "hello";
	}
	/**
	 * 设置重定向,或者转发到其他的页面时
	 * 已forward为前缀:请求转发
	 * 以redirect为前缀:重定向
	 * 
	 */
	@RequestMapping("/method2")
	public String method2() {
		return "redirect:http://www.jd.com";
	}
	@RequestMapping("/method3")
	public String method3() {
		return "forward:/response.jsp";
	}

文件上传和下载

springmvc给我们集成了对文件上传下载的简化操作。

文件上传

在web开发中一般会有文件上传的操作

一般JavaWeb开发中文件上传使用的 Apache组织的Commons FileUpload组件

SpringMVC中使用 MultipartFile file对象接受上传文件,必须保证 后台参数的名称和表单提交的文件的名称一致

文件上传必须条件

\1. 表单必须post

\2. 表单必须有 file 文件域

\3. 表单的 enctype="multipart/form-data"

准备jar

配置文件的上传解析器

<!-- 创建文件上传的解析器
    id:必须是multipartResolver,不然会执行错误
     -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 配置文件上传时限定大小,#{1024*1024}可以计算并返回值 -->
        <property name="maxUploadSize" value="#{1024*1024}"/>
    </bean>

Controller

@Controller
public class UploadController {

	@RequestMapping("/upload")
	public ModelAndView upload(MultipartFile headImg, String username) {
		/*
		 * 使用springmvc集成的上传组件,MultipartFile接口 1.创建上传的解析器,
		 * org.springframework.web.multipart.commons.CommonsMultipartResolver
		 * id必须是:multipartResolver
		 * 
		 * 2.调用MultipartFile的transferTo(dest)来上传文件到指定位置
		 */
		System.out.println("username:" + username);
		System.out.println(headImg.getContentType());// 获取上传文件的MEMI类型,比如text/html
		System.out.println(headImg.getName());// 获取文件上传时的文件名的name属性
		System.out.println(headImg.getOriginalFilename());// 获取文件上传时的文件名
		System.out.println(headImg.getSize());// 获取文件上传的大小

		File path = new File("F:/upload");
		// 判断文件夹是否存在
		if (!path.exists()) {
			// 不存在就创建
			path.mkdirs();
		}
		File dest = new File(path, headImg.getOriginalFilename());
		// 开始接收文件的,dest所要存储的位置
		try {
			headImg.transferTo(dest);
		} catch (IllegalStateException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/*
	 * 多文件上传,同名的name使用一个MultipartFile[]来接收,循环遍历即可 不同名只能一个一个的去接收。
	 */
	@RequestMapping("/uploads")
	public ModelAndView uploads(MultipartFile[] headImgs) {
		// 先指定存放的位置,只需要创建一个即可
		File path = new File("f:/upload");
		if (!path.exists()) {
			path.mkdirs();
		}
		
		for (int i = 0; i < headImgs.length; i++) {
			// 使用UUID解决文件上传重名问题
			String fileName = UUID.randomUUID().toString().replaceAll("-", "");
			String extension = FilenameUtils.getExtension(headImgs[i].getOriginalFilename());
			//还需要夹后缀
			File dest = new File(path,fileName+"."+extension);
			try {
				headImgs[i].transferTo(dest);
			} catch (IllegalStateException | IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}

	public static void main(String[] args) {
		// 使用UUID解决文件上传重名问题
		String fileName = UUID.randomUUID().toString().replaceAll("-", "");
		System.out.println(fileName);
		String path = "c:/aa/aa/tom.jpg";
		String extension = FilenameUtils.getExtension(path);
		System.out.println(FilenameUtils.getBaseName(path));
		
		System.out.println(extension);
		System.out.println(FilenameUtils.getName(path));
	}
}

文件下载

使用到Common-io的一个工具类IOUtils.copy(input,output)

@RequestMapping("/download")
	public void download(String fileName, HttpServletResponse resp,HttpServletRequest req) throws IOException {
		/*
		 * 需要用到输入输出流
		 */
		// 从磁盘上读到内存中
		InputStream input = new FileInputStream("F:/music/" + fileName);
		// 通过响应流给浏览器
		ServletOutputStream output = resp.getOutputStream();
		
		//处理浏览器编码的问题
		//获取响应头
		String userAgent = req.getHeader("User-Agent");
		if(!userAgent.contains("MSIE")) {
			//根据编码格式转为字节数组
			byte[] bytes = fileName.getBytes("utf-8");
			//根据字节和编码转为字符串;ISO-8859-1是国际标准的编码,W3C标准
			fileName = new String(bytes, "ISO-8859-1");
		}
		//响应的内容应该是以附件的形式响应给浏览器(设置响应头)
		// 响应下载时的名称
		resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);

		// 通过IOUtils将输入流拷贝给输出流
		IOUtils.copy(input, output);
	}

springmvc的拦截器

跟web的三大组件之一的Filter差不了的功能,只是springmvc对拦截的规则更加简洁。

<!-- 配置拦截器 -->
    <mvc:interceptors>
        <!-- 可以配置多个拦截器 -->
        <mvc:interceptor>
            <!-- 配置拦截规则
            /*:一级拦截,只对最后的一级目录拦截,比如delete.do,list.do
            /**:多级拦截,比如/user/delete.do,/user/list.do,/a/ab/a.do
             -->  
            <mvc:mapping path="/**"/>
            <!-- 对不需要拦截的放行
                                多个之间用逗号隔开
             -->
            <mvc:exclude-mapping path="/user/login.do"/>
            <!-- 拦截器的类型 -->
            <bean class="cn.zj.ssm.interceptor.CheckLoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>    

SpringMVC 控制器 Controller的生命周期

Spring 容器创建的对象默认 都是单例 对象

SpringMVC对象 Controller的对象的创建有三种情况

Request : 在用户的一次请求中生效(用户每次请求都会创建Controller对象)多例

Session : Controller对象在一次会话中创建一个对象

如果控制器中有成员变量 设置或者赋值操作,必须使用 request 返回

问题:Web表现层控制器到底使用单例singleton还是request,还是session?

答 :

首先看控制器有没有成员变量要并发修改,如果有,必须使用request/prototype

早期的Struts2框架使用的就是成员变量接受请求参数,对应的控制器对象必须是多例

SpringMVC接受请求参数,作为方法形式参数,每次请求都会重新没参数开辟新的内存存储这个参数,不会产生线程安全问题

所以SpringMVC使用默认的单例即可

posted @ 2022-04-07 19:23  站着说话不腰疼  阅读(54)  评论(0)    收藏  举报