SpringMVC核心技术

目录

  • 1.转发与重定向

  • 2.异常处理器

  • 3.类型转换器

  • 4.初始化参数绑定

  • 4.文件上传

  • 4.拦截器

转发与重定向


当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。

注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中的页面。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中的资源

SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。现在可以使用简单的方式实现转发和重定向。

forward:表示转发

  • 封装了: request.getRequestDispatcher(“xx.jsp”).forward()

redirect表示重定向

  • 封装了: response.sendRedirect(“xxx.jsp”)

请求转发

  • 处理器方法返回 ModelAndView 时,需在 setViewName()指定的视图前添加 forward

  • 处理器方法返回 String,在视图路径前面加入 forward: ,转发到视图页面。

  • 转发到其他处理器的格式:forward:xxx.do

    	@RequestMapping(value="/register.do")
    	public ModelAndView doFirst(String name,int age) {
           
    		System.out.println(name);
    
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("pname", name);
    		modelAndView.addObject("page", age);
    
    		//加上forward就是请求转发
    		modelAndView.setViewName("forward:/WEB-INF/jsp/welcome.jsp");
    		return modelAndView;
    	}
    

重定向

  • 在处理器方法返回的视图字符串的前面添加 redirect:,则可实现重定向跳转。

  • 重定向ModelAndView,会将modelAndView.addObject(“key”, value);添加的数据当作请求参数传递

    	@RequestMapping(value="/register.do")
    	public ModelAndView doFirst(String name,int age) {
           
    		System.out.println(name);
    
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("pname", name);
    		modelAndView.addObject("page", age);
    
    		//加上redirect是重定向,重定向不能到WEB-INF里的内容
    		//重定向会将addObject中添加的数据作为参数进行传递
    		//modelAndView.setViewName("redirect:/welcome.jsp?pname=name&page=age");
    		modelAndView.setViewName("redirect:/welcome.jsp");
    
    		return modelAndView;
    	}
    
  • 如何在重定向后获取modelandView.addObject(“key”,value);中添加的数据

      <body>
       
       name =  ${param.pname }<br>
       age = ${param.page }
      body>
    
  • 重定向到其他处理器,并且通过model携带数据(SpringBoot中使用RedirectAttributes)

    @RequestMapping(value="/register.do")
    	public String doFirst(String name,int age,Model model,HttpServletRequest request) throws UnsupportedEncodingException {
           
    		request.setCharacterEncoding("UTF-8");
    
    		System.out.println(name);
    		//通过model携带数据
    		//不能传递对象,因为是通过http协议传输
    		model.addAttribute("pname",name);
    		model.addAttribute("page", age);
    
    		return "redirect:/doother.do";
    	}
    
    	@RequestMapping(value="/doother.do")
    	public String doOther(String pname,int page) {
           
    		System.out.println(pname);
    		System.out.println(page);
    
    		//转发
    		return "/welcome.jsp";
    	}
    

异常处理器


定义异常处理器

  • @ExceptionHandler:用于捕获所有控制器里面的异常,并进行处理。

  • 其他类继承此异常处理器即可,也可以直接将异常处理方法写到Controller中

    @Controller	//指定此类为控制器
    public class BaseController {
           
    
    	//处理NameException异常
    	@ExceptionHandler(NameException.class)
    	public ModelAndView MyNameExceptionHandler(Exception exception) {
           
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("message", exception);
    		System.out.println(exception.getMessage());
    
    		//默认跳转NameError页面
    		modelAndView.setViewName("/errors/NameError.jsp");
    		return modelAndView;
    	}
    
    	//处理AgeException异常
    	@ExceptionHandler(AgeException.class)
    	public ModelAndView MyAgeExceptionHandler(Exception exception) {
           
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("message", exception);
    		System.out.println(exception.getMessage());
    
    		//默认跳转AgeError页面
    		modelAndView.setViewName("/errors/AgeError.jsp");
    		return modelAndView;
    
    	}
    
    	//处理其他异常
    	@ExceptionHandler()
    	public ModelAndView MyOtherExceptionHandler(Exception exception) {
           
    		ModelAndView modelAndView = new ModelAndView();
    		modelAndView.addObject("message", exception);
    
    		//默认跳转AgeError页面
    		modelAndView.setViewName("/errors/error.jsp");
    		return modelAndView;
    
    	}
    
    }
    

类型转换器


为什么在表单中输入"12",处理器方法可以以int类型接收参数? 这是因为框架内部帮我们做了类型转换的工作。将String转换成int,下面我们将定义类型转换器,将表单提交的String转换Date类型。

可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器

Converter:将S类型对象转为T类型对象

  • jsp页面

    <form action="${pageContext.request.contextPath }/test/myConverter.do">
    		年龄:<input type="text" name="age">
    		生日:<input type="text" name="birthday">
    		<input type="submit" value="提交">
    form>
    
  • Controller

    @Controller	//指定这个类是处理器
    @RequestMapping(value="/test")	//命名空间
    //以Date形式接收前端提交的参数birthday
    public class MyController extends MyExceptionHandler{
           
    
    	@RequestMapping(value="/myConverter.do")
    	public ModelAndView dotypeC(int age,Date birthday) {
           
    		ModelAndView modelAndView = new ModelAndView();
    
    		modelAndView.addObject("age", age).addObject("birthday", birthday);
    		modelAndView.setViewName("/WEB-INF/jsp/welcome.jsp");
    
    		return modelAndView;
    	}
    
    }
    
    /*
     *Converter
     *参数1:源类型
     *参数2:需要转换的类型 
     */
    public class myDateConverter implements Converter<String, Date> {
           
    
    	@Override
    	//source: 即为前端传入的birthday
    	public Date convert(String source) {
           
    		//创建SimpleDateFormat通过getDateFromat(source)方法得到对象
    		SimpleDateFormat date = getDateFromat(source);
    
    		//返回 date.parse(source)
    		try {
           
    			return date.parse(source);
    		} catch (ParseException e) {
           
    			e.printStackTrace();
    		}
    		return null;
    	}
    
    	//此方法用于获取SimpleDateFormat对象,通过正则表达式对source进行匹配
    	private SimpleDateFormat getDateFromat(String source) {
           
    		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    		if (Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    		}else if (Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
    		}else if (Pattern.matches("^\\d{4}\\d{2}\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
    		}else if (Pattern.matches("^\\d{4}年\\d{2}月\\d{2}日$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    		}
    		return simpleDateFormat;
    	}
    }
    
  • 注册类型转换器

    
    	<bean id="Converter" class="com.chuangmei.converter.myDateConverter" />
    
    	
    	<bean id="conversionServiceFactory" class="org.springframework.context.support.ConversionServiceFactoryBean">
    		
    		<property name="converters">
    			<set>
    				<ref bean="Converter"/>
    			set>
    		property>
    	bean>
    
    	
    	<mvc:annotation-driven conversion-service="conversionServiceFactory"/>
    

初始化参数绑定


  • Controller

    @Controller	//指定这个类是处理器
    @RequestMapping(value="/test")	//命名空间
    public class MyController {
           
    
    	@RequestMapping(value="/myConverter.do")
    	public ModelAndView dotypeC(int age,Date birthday) {
           
    		ModelAndView modelAndView = new ModelAndView();
    
    		modelAndView.addObject("age", age).addObject("birthday", birthday);
    		modelAndView.setViewName("/WEB-INF/jsp/welcome.jsp");
    
    		return modelAndView;
    	}
    
    	/*
    	 * 初始话参数绑定:
    	 * 		在参数传递之前执行此方法
    	 */
    	@InitBinder
    	public void initBinder(WebDataBinder webDataBinder) {
           
    
    		webDataBinder.registerCustomEditor(Date.class, new MyEditor());
    
    	}
    
    }
    
  • 自定义编辑器

    public class MyEditor extends PropertiesEditor {
           
    
    	@Override
    	public void setAsText(String source) throws IllegalArgumentException {
           
    		// TODO Auto-generated method stub
    		SimpleDateFormat simpleDateFormat = getDateFormat(source);
    
    		try {
           
    			Date date = simpleDateFormat.parse(source);
    			setValue(date);
    		} catch (ParseException e) {
           
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    	private SimpleDateFormat getDateFormat(String source) {
           
    		// TODO Auto-generated method stub
    		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    		if (Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    		}else if (Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
    		}else if (Pattern.matches("^\\d{4}\\d{2}\\d{2}$", source)) {
           
    			simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
    		}else {
           
    			System.out.println("抛出异常");
    			throw new TypeMismatchException("aaa", Integer.class);
    		}
    
    		return simpleDateFormat;
    	}
    
    }
    

文件上传


  • jsp页面

    <form action="${pageContext.request.contextPath }/test/upload.do" method="post" enctype="multipart/form-data">
    	文件1:<input type="file" name="imgs"/><br>
    	文件2:<input type="file" name="imgs"/><br>
    	文件3:<input type="file" name="imgs"/><br>
    	<input type="submit" value="上传"/>
    form>
    
  • Controller

    @Controller		//表示当前类为控制器
    @RequestMapping("/test")	//命名空间
    public class myController {
           
    
    	@RequestMapping(value={
           "/upload.do"})
    	//这里的参数名要与表单提交的参数名相同
    	//RequestParam:用于将指定的请求参数赋值给方法中的形参。
    	//value是表单的name名称如果表单的name与方法形参相同,即可省略value
    	public String doFileUpload(@RequestParam(value="imgs") MultipartFile[] imgs,HttpSession session) throws IllegalStateException, IOException {
           
    
    		//创建目录
    		/*
    		 * 这样写很不安全
    		 * File filePath = new File("D:/images");
    		 * */
    		//用户只具有当前项目根下的操作权限
    		String realPath = session.getServletContext().getRealPath("/images");
    		File filePath = new File(realPath);
    
    		System.out.println(realPath);
    		//如果不存在则创建
    		if (!filePath.exists()) {
           
    			filePath.mkdirs();
    		}
    
    		//遍历MultipartFile数组逐个判断上传
    		for (MultipartFile img : imgs) {
           
    			//如果获取到的字节大于0,才执行上传,不能用null判断,因为即使前端不传入参数,img也不会为null
    			if(img.getSize()>0){
           
    				//获取用户上传的文件的文件名
    				String FileName = img.getOriginalFilename();
    				//限制上传的文件类型,只有后缀位jpg和png的才能上传
    				if (FileName.endsWith("jpg") || FileName.endsWith("png")) {
           
    					//指定文件的上传路径
    					File file = new File(filePath, FileName);
    					//上传文件
    					img.transferTo(file);
    				}else {
           
    					return "/message.jsp";
    				}
    			}
    
    		}
    
    		return "/success.jsp";
    	}
    
    }
    
  • SpringMVC.xml

    
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    	
    	<property name="defaultEncoding" value="utf-8"/>
    	
    	<property name="maxUploadSize" value="1048576"/>
    bean>
    
    
    <mvc:annotation-driven/>
    

拦截器


· SpringMVC 中的 Interceptor 拦截器的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其

·自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

  • preHandle(request, response, Object handler):该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。

  • postHandle(request, response, Object handler, modelAndView):该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。

  • afterCompletion(request, response, Object handler, Exception ex): 当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。

· 拦截器的使用

  • Controller

    /*
     * 拦截器执行顺序
     * 		1.preHandler
     * 		2.myController
     * 		3.postHandler
     * 		4.afterCompletion
     */
    @Controller		//表示当前类是个控制器
    @RequestMapping("/test")	//命名空间
    public class myController {
           
    
    	//提交*.do或/hello.do的请求 执行此方法
    	@RequestMapping("some.do")	
    	public String doFirst() {
           
    
    		System.out.println("doSOme------------");
    
    		return "/WEB-INF/jsp/welcome.jsp";
    	}
    }
    
  • interceptor

    /*
     * 拦截器执行顺序
     * 		1.preHandler
     * 		2.myController
     * 		3.postHandler
     * 		4.afterCompletion
     */
    public class Oneinterceptor implements HandlerInterceptor {
           
    
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
           
    		// TODO Auto-generated method stub
    		System.out.println("Oneinterceptor -------------------- preHandle");
    
    		//此处返回false即表示不在往下执行
    		return true;
    	}
    
    	@Override
    	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    			ModelAndView modelAndView) throws Exception {
           
    		// TODO Auto-generated method stub
    		System.out.println("Oneinterceptor -------------------- postHandle");
    
    	}
    
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    			throws Exception {
           
    		// TODO Auto-generated method stub
    		System.out.println("Oneinterceptor -------------------- afterCompletion");
    
    	}
    
    }
    
  • springmvc.xml(注册拦截器)

    		
    		<mvc:interceptors>
    
    			
    			<mvc:interceptor>
    				
    				<mvc:mapping path="/**"/>	
    
    				
    				
    
    				
    				<bean class="com.chuangmei.interceptor.Oneinterceptor"/>
    			mvc:interceptor>
    
    			
    			<mvc:interceptor>
    				<mvc:mapping path="/**"/>
    				<bean class="com.chuangmei.interceptor.Twointerceptor"/>
    			mvc:interceptor>
    
    		mvc:interceptors>
    

· 源码解析

  • DispatcherServlet.doDispatch

  • 找到处理器适配器后,会执行这段代码

     //在找到处理器适配器后会执行到此处去调用HandlerExecutionChain的applyPreHandle(processedRequest, response)
     //如果 preHandle 方法返回false,那么 applyPreHandle 也会返回false,最后就会执行return语句,程序将不再往下执行
     if (!mappedHandler.applyPreHandle(processedRequest, response)) {
           
    	 return;
     }
    
  • HandlerExecutionChain.applyPreHandle

  • 此方法是在找到处理器适配器后由处理器执行链进行调用,此方法会让 interceptor 去调用 preHandle 方法,返回值与 preHandle 相同

    	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
           
    		//可能有多个拦截器,所以数组的形式接收
    		HandlerInterceptor[] interceptors = this.getInterceptors();
    		//判断拦截器数组是否为空
    		if (!ObjectUtils.isEmpty(interceptors)) {
           
    			//遍历拦截器数组
    			for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
           
    				//取出数组中的拦截器
    				HandlerInterceptor interceptor = interceptors[i];
    				/*让拦截器去执行我们实现 HandlerInterceptor 后重写的 preHandle 方法
    				  如果 preHandle 返回false,那么此处也返回false,否则返回true
    				*/
    				if (!interceptor.preHandle(request, response, this.handler)) {
           
    					//如果preHandle 返回false, 执行 triggerAfterCompletion 
    					this.triggerAfterCompletion(request, response, (Exception)null);
    					return false;
    				}
    			}
    		}
    		return true;
    	}
    
    
  • HandlerExecutionChain.applyPostHandle

  • 在执行完 applyPreHandle 方法后,适配器会调用 handle 方法去执行 HandlerExecutionChain 中的处理器,处理器执行结束后便会由 HandlerExecutionChain 去执行 applyPostHandle 方法

     mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
           
    		//拦截器
    		HandlerInterceptor[] interceptors = this.getInterceptors();
    		if (!ObjectUtils.isEmpty(interceptors)) {
           
    			//倒叙遍历
    			for(int i = interceptors.length - 1; i >= 0; --i) {
           
    				HandlerInterceptor interceptor = interceptors[i];
    				//interceptor 执行 postHandle 方法
    				interceptor.postHandle(request, response, this.handler, mv);
    			}
    		}
    	}
    
  • DispatcherServlet.processDispatchResult

  • 执行完 applyPostHandle 方法后, 由 DispatcherServlet 去调用 processDispatchResult 方法,该方法会调用DispatcherServlet 的 render 方法, 在 DispatcherServlet 的 render 方法中会调用 ModelAndView 的 render 方法进行渲染数据

    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,.....) {
           
    ....
    
    		if (mv != null && !mv.wasCleared()) {
           
    			//调用 DispatcherServlet 的 render 方法,将 mv 传入
    			this.render(mv, request, response);
    		}
    ....
    }
    
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
           
    
    ....
    //调用view对象自己的 render 方法渲染数据
    view.render(mv.getModelInternal(), request, response);
    
    ...
    
    }
    

你可能感兴趣的:(SpringMVC,java,mvc)