SpringMVC核心技術

程式設計小白AA發表於2020-10-09

目錄

轉發與重定向


當處理器對請求處理完畢後,向其它資源進行跳轉時,有兩種跳轉方式:請求轉發與重定向。而根據所要跳轉的資源型別,又可分為兩類:跳轉到頁面與跳轉到其它處理器。

注意,對於請求轉發的頁面,可以是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>
       <!-- 
    		param.pname底層呼叫的是request.getParameter();	//取出的資料是字串
    		requestScop:底層呼叫的是request.getAttribute();	//取出的資料帶型別
    	-->
       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>:將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<String, Date>
     *引數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" ref="Converter"> -->
    		<property name="converters">
    			<set>
    				<ref bean="Converter"/>
    			</set>
    		</property>
    	</bean>
    
    	<!-- 註冊mvc註解驅動 -->
    	<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"/>
    	<!-- 設定最大上傳位元組(b) 1kb = 1024b -->
    	<property name="maxUploadSize" value="1048576"/>
    </bean>
    
    <!-- 註冊mvc註解驅動 -->
    <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>
    
    			<!-- Oneinterceptor -->
    			<mvc:interceptor>
    				<!-- 指定攔截什麼請求 -->
    				<mvc:mapping path="/**"/>	<!-- 攔截所有請求 -->
    
    				<!-- 指定不攔截什麼請求 -->
    				<!-- <mvc:exclude-mapping path=""/> -->
    
    				<!-- 攔截器 -->
    				<bean class="com.chuangmei.interceptor.Oneinterceptor"/>
    			</mvc:interceptor>
    
    			<!-- Twointerceptor -->
    			<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);
    
    ...
    
    }
    

相關文章