本人博客: https://blog.onfree.cn (●ˇ?ˇ●)

11分鐘詳析Spring MVC

我手中的魔法,是守護摯愛的力量,是堅定這個信念所必須的力量,我一定會拯救你的,無論在何時、何地。


Spring MVC是當前最優秀的MVC框架,支持注解配置,易用性有了大幅度的提高,使用簡單,學習成本低,靈活性高,易拓展。

1. 工作流程

image

    <!-- 配置處理器 Handle,映射 “/controller1”請求 -->
	<bean name="/controller1" class="com.controller.Controller1" />
    <!-- 已下默認不用寫 在Spring 4.0 會自動使用默認的來處理 -->
	<!-- 處理器映射器  將處理器的name作為URL請求進行查找-->
	<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>	
	<!-- 處理器適配器 配置對處理器中handleRequest()方法的調用 -->
	<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
	<!--視圖解析器  -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

2. DispatcherServlet

    <!-- 配置Springmvc -->
	<servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

// 1為啟動程序立即加載該

// / 為攔截所有URL交給DispatcherServlet處理

3.Controller 層

3.1實現接口 org.springframework.web.servlet.mvc.Controller

public class Controller1 implements Controller {
	public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
		ModelAndView modelAndView =new ModelAndView();
		modelAndView.addObject("msg", "hello world");
		modelAndView.setViewName("WEB-INF/jsp/index1.jsp");
		return modelAndView;
	}

	<!-- 配置處理器 Handle,映射 “/controller1”請求 -->
	<bean name="/controller1" class="com.controller.Controller1" />

3.2 使用注解 @Controller @RequestMapping

@Controller
@RequestMapping(value="/hello")  //標注在類上時所有請求前必須有定義的名稱下"/hello
public class Controller1{
	
	//跳轉登錄界面
	@RequestMapping(value="/login",method=RequestMethod.GET)  //使用 /login 即可調用
	public String toLogin(){
		return "login";
	}
	
	//獲取登錄視圖傳遞來的信息進行配對 成功后視圖跳轉
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public String login(User user,Model model,HttpSession session) {
		System.out.println("當前用戶: "+user);
		String name=user.getName();
		String password=user.getPassword();
		if(name.equals("jz")&&password.equals("123")) {
			session.setAttribute("session_user", user);
	        //redirect 重定向的意思時跳轉到相應的方法而不是跳到jsp視圖
			return "redirect:main";  
		}else if(name.equals("aa")&&password.equals("123")) {
			session.setAttribute("session_user", user);
			return "redirect:upload";
		}
		model.addAttribute("msg", "用戶名或密碼錯誤");
		return "login";
	}

//組合注解: GetMapping PostMapping PutMapping DeleteMapping PatchMapping

4. 視圖解析器

   <!-- 定義視圖 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property name="prefix" value="/WEB-INF/jsp/"></property>
    	<property name="suffix" value=".jsp"></property>
    </bean>

5.數據綁定

5.1綁定默認數據類型

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • Model/ModelMap

//model是接口 ModelMap是接口實現 作用是將數據填充到Request

5.2綁定簡單數據類型

String 、Interget、double..
//視圖傳遞來的name屬性要和控制器方法的形參相同
//不同要使用注解 @RequestParam(value="name") String name 定義

5.3綁定POJO類型

public String login(User user) {}

賬號:
密碼:

//解決請求參數中中文亂碼問題

	<!-- 字符過濾為UTF-8 -->
<filter>
	<filter-name>Encoding</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>Encoding</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

5.4綁定包裝POJO類型

public class Order {
	private Integer id;
	private User user;
}

	//獲取order.jsp界面傳遞來的參數
	@RequestMapping("/order")
	public String order(Order order) {
		System.out.println(order);
		return "index2";
	}

	<form action="${pageContext.request.contextPath}/hello/order " method="post"> 
		訂單號: <input type="text" name="order.id"><br/>
		用戶名: <input type="text" name="user.name"><br/>
		<input type="submit" value="繼續">
		<input type="reset" value="重置">
	</form>

//兩種規范:

1.查詢條件參數是包裝類的直接基本屬性 則參數名直接用對應的屬性名

2.查詢條件參數是包裝類的POJO的子屬性 則參數名必須為 【對象.屬性】對象為包裝類里POJO的對象名

5.5自定義綁定數據

5.5.1 Converter轉換器 //源格式可以為任何格式

public interface Converter<S, T>{
T convert(S source)
} //S 為源格式 T為目標格式

public class DateConverter implements Converter<String, Date> {
	public Date convert(String source) {
		String datePattern="yyyy-MM-dd HH:MM:SS";
		SimpleDateFormat simpleDateFormat=new SimpleDateFormat(datePattern);
		try {
			return simpleDateFormat.parse(source);
		} catch (ParseException e) {
			throw new IllegalArgumentException("無效格式,請使用這種格式"+datePattern);
		}
	}
}


xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
                              http://www.springframework.org/schema/mvc/spring-mvc.xsd"
    <!-- 顯示裝配的自定義的轉換器 -->
  <mvc:annotation-driven conversion-service="conversionservice1"></mvc:annotation-driven> 
      
    <!-- 自定義類型轉換器配置 converters-->
  <bean id="conversionservice1" class="org.springframework.context.support.ConversionServiceFactoryBean">
    	<property name="converters">
    		<set>
    			<bean class="com.converter.DateConverter"></bean>
    		</set>
    	</property>
    </bean>
	//通過自定義綁定數據Converter或DateFormat 獲取Date
	@RequestMapping("customDate")
	public String customDate(Date date) {
		System.out.println(date);
		return "index2";
	}
5.5.2 Formatter格式化 //源格式只能為String

public interface Formatter extends Printer,Parser{}

public class DateFormatter implements Formatter<Date> {
	private String datePattern="yyyy-MM-dd HH:MM:SS";
	private SimpleDateFormat simpleDateFormat;
	//返回目標對象的字符串
	public String print(Date date, Locale locale) {
		return new SimpleDateFormat().format(date);
	}
	//利用指定的local將一個String類型解析成目類型
	public Date parse(String source, Locale locale) throws ParseException {
		simpleDateFormat=new SimpleDateFormat(datePattern);
		return simpleDateFormat.parse(source);
	}
}
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
                              http://www.springframework.org/schema/mvc/spring-mvc.xsd"
    <!-- 顯示裝配的自定義的轉換器 -->
  <mvc:annotation-driven conversion-service="conversionservice1"></mvc:annotation-driven> 
    <!-- 自定義類型轉換器配置formatters-->
    <bean id="conversionservice2" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    	<property name="formatters">
    		<set>
    			<bean class="com.converter.DateFormatter"></bean>
    		</set>
    	</property>
    </bean>

5.6綁定數組

	//獲取綁定要執行刪除的數組 
	@RequestMapping("/delID")
	public String delID(Integer[] ids) {
		if(ids!=null) {
			for (Integer id : ids) {
				System.out.println("你刪除了第 "+id+"數據");
			}
		}else{
			System.out.println("ids為null");
		}
		return "editUser";
	}
	<form action="${pageContext.request.contextPath}/hello/delID" method="post">
		<table border=1 width="20%">
			<thead>刪除用戶</thead>
			<tbody>
				<tr>
					<td>選擇</td>
					<td>用戶名</td>
				</tr>
				<tr>
					<td> <input type="checkbox" name="ids" value="1"></input> </td>
					<td>aa</td>
				</tr>
				<tr>
					<td> <input type="checkbox" name="ids" value="2"></input> </td>
					<td>bb</td>
				</tr>
			</tbody>
		</table>
		<br/><input type="submit" value="確定">
	</form>

5.6綁定集合

//后臺不允許使用集合作為形參 只能用包裝類來包裝一個集合

public class UserList {
private List userList;
}

	//獲取綁定要執行修改數據的集合
	@RequestMapping("/editUser")
	public String editUser(UserList userList1) {
		List<User> users=userList1.getUserList();
		for (User user : users) {
			if(user.getId()!=null) {
				System.out.println("修改了第 "+user.getId()+"數據");
				System.out.println("用戶名為:"+user.getName());
			}
		}
		return "index2";
	}

<form action="${pageContext.request.contextPath}/hello/editUser" method="post">
		<table border=1>
			<thead>修改用戶</thead>
			<tr>
				<td>選擇</td>
				<td>用戶名</td>
			</tr>
			<tr>
				<td> <input type="checkbox" name="userList[0].id" value="1"></input> </td>
				<td> <input type="text" name="userList[0].name" value="jz"></input></td>
			</tr>
			<tr>
				<td> <input type="checkbox" name="userList[1].id" value="2"></input> </td>
				<td> <input type="text" name="userList[1].name" value="aa"></input></td>
			</tr>
		</table>
		<br/><input type="submit" value="確定">
	</form>

6.JSON 和RESTful

6.1 JSON 格式

對象結構:

{
key1:value1,
key2:value2
}

數組結構:

[
value1,
value2
]

6.2JSON數據交換

包類:

jackson-annotations-2.9.9.jar

jackson-core-2.9.9.jar

databind-2.9.9.jar

注解:

@RequestBody //將請求體的數據綁定在形參上 作用在形參
@ResponseBody //返回JSON格式 作用在方法上

    <!-- 配置注解驅動 
自動注冊 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    	和org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 
-->
    <mvc:annotation-driven />
    <!--第1種. 配置靜態資源的訪問映射 使其不被前端控制器攔截 -->
    <mvc:resources location="/js/" mapping="/js/**" />
    <!-- 第2種.使用使用默認的服務器請求處理器自動判斷篩選 -->
    <!-- <mvc:default-servlet-handler/> -->

?

//測試JSON 
@RequestMapping(value="/login2",method=RequestMethod.POST)
@ResponseBody  //返回JSON格式
public User login2(@RequestBody User user) {
	System.out.println("login2 測試");
	System.out.println(user);
	return user;
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<title>Insert title here</title>
	<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.3.1.min.js"></script>
	<script type="text/javascript">
		function check() {
			var name=$("#name").val();
			var password=$("#password").val();
			if(name==null||password==null||name==""||password==""){
				alert("賬號或密碼不能為空!!");
				return false;
			}
			return true;
		}
//使用JQuery的AJAX傳遞JSON格式
		function login2(){
			//alert("1");
			var name=$("#name").val();
			var password=$("#password").val();
			var id=1;	
			if(check()){
				$.ajax({
//url前往路徑
					url:"${pageContext.request.contextPath}/hello/login2", 
					type:"post",
//data請求發送的數據
					data:JSON.stringify({id:id,name:name,password:password}),
//json格式時 必須為application/json
					contentType:"application/json;charset=UTF-8",
//定義回調屬性為json
					dataType:"json",
					success:function(data){
						alert(data.name+data.password);
					}	
				});
			}	
		}
	</script>
</head>
<body>
	<!-- ${pageContext.request.contextPath}<br/>-->
	<div style="color: red">${msg}</div>
	<form action="${pageContext.request.contextPath}/hello/login " method="post" onsubmit="return check()"> 
		賬號: <input type="text" name="name" id="name"><br/>
		密碼: <input type="password" name="password" id="password"><br/>
		<input type="submit" value="登錄">
		<input type="reset" value="重置">
		<input type="button" value="登錄2" onclick="login2()" />
	</form>
</body>
</html>

6.3RESTful風格

如:http://hello/items/1
//參數寫在連接之后

	//用RESTful風格測試JSON
	@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
	@ResponseBody
	public User selectUser(@PathVariable("id") Integer id) {
		User user1=new User();
		//模擬在數據庫找到此ID數據
		if(id.equals(1)) {
			user1.setName("jzz");
			user1.setId(id);
			System.out.println("ID: "+user1.getId()+" 用戶名: "+user1.getName());
		}else {
			System.out.println("沒有找到此ID的用戶");
		}
		return user1;
	}
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.3.1.min.js"></script>
	<script type="text/javascript">
		function select1(){
			//alert("1");
			var id=$("#number").val();
			$.ajax({
				url:"${pageContext.request.contextPath}/hello/user/"+id,
				type:"GET",
				dataType:"json",
				success:function(data){
					if(data.id!=null&&data.name!=null){
						alert("id: "+data.id+"用戶名: "+data.name);
					}else{
						alert("沒有找到此ID的用戶");
					}
				}
				
			}) 
		}
	</script>

7.攔截器interceptor

  • // 攔截器需實現接口 HandlerInterceptor 或它的實現類 HandlerInterceptorAdapter

  • //或者 實現接口WebRequestInterceptor或實現類WebRequestHandlerInterceptorAdapter

    public class LoginInterceptor extends HandlerInterceptorAdapter {
        	//此方法在控制器方法前執行  返回true時繼續執行  fasle時中斷控制器方法及攔截器方法
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
    		//判斷是否登錄 /login 連接
    		String url=request.getRequestURI();
    		System.out.println("當前連接:"+url);
    		if(url.indexOf("/login")>=0) {
    			return true;
    		}
    		if(url.indexOf("/login2")>=0) {
    			return true;
    		}
    		
    		//判斷session是否有數據User用戶
    		HttpSession session=request.getSession();
    		User user =(User) session.getAttribute("session_user");
    		if(user!=null) {
    			return true;
    		}
    	
    	//不符合條件重新轉發到登錄界面
    	request.setAttribute("msg", "你還沒有登錄,請登錄!");
    	request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
    	return false;
    }
    //此方法在控制器調用之后 視圖解析前執行  以便對模型和視圖進行修改
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView modelAndView)
    		throws Exception {
    	// TODO Auto-generated method stub
    
    }
    //此方法在視圖解析之后進行  可進行資源清理、日子記錄等等
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
    			throws Exception {
    		// TODO Auto-generated method stub
    		
    	}
    }
    
        <!-- 配置攔截器 -->
    	<mvc:interceptors>
    		<!-- 全局攔截器 -->
    		<!--<bean class="" />  -->
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/> <!-- 配置攔截器作用的路徑 -->
    			<mvc:exclude-mapping path=""/> <!-- 配置攔截器不作用的路徑 -->
    			<bean class="com.interceptor.LoginInterceptor"></bean>
    		</mvc:interceptor>
    	</mvc:interceptors>
    

/* 假設配置多個攔截器interceptor1 interceptor2
則其攔截器的方法的調用的順序是:
interceptor1(preHandle) - interceptor2(preHandle) - handleAdapter(Handle) -interceptor2(postHandle) - interceptor1(postHandle) - DispatcherServlet(reader) - interceptor2(afterCompletion) - interceptor1(afterCompletion) */

7.文件上傳

類包:

  • commons-fileupload-1.4.jar

  • commons-io-2.6.jar

      <!-- 因為CommonsMultipartResolver內部是引用MultipartResolver字符串來完成文件解析 所以指定的ID必須為multipartResolver  -->
    
<!--  enctype屬性必須為"multipart/form-data"   multiple="multiple" 表示可以多文件上傳 -->
	<form id="form1" action="${pageContext.request.contextPath}/hello/upload" enctype="multipart/form-data" method="post" onsubmit="return check()" >
		上傳人: <input type="text" id="name1" name="name1" /> <br/>    
		<input type="file" multiple="multiple"  name="ufile" id="ufile" />
		<input type="submit" value="上傳" />
	</form>
//上傳文件處理
	@RequestMapping(value="/upload" ,method=RequestMethod.POST)
	public String upload(@RequestParam(value="name1") String name1,@RequestParam(value="ufile") List<MultipartFile> ufile,HttpServletRequest request,HttpSession session) {
		
		//System.out.println("1成功");
		User user=(User) session.getAttribute("session_user");
		//System.out.println(user);
		
		//判斷上傳文件是否存在
		if(!ufile.isEmpty()&&ufile.size()>0) {
			for (MultipartFile multipartFile : ufile) {
				//獲取上傳文件的源名字
				String sfilename=multipartFile.getOriginalFilename();
				//上傳文件的最后的保存路徑
				String dirPath=request.getServletContext().getRealPath("/upload")+"/"+user.getName();
				System.out.println("dirPath :"+dirPath);
				//假設目錄不存在 就創建目錄
				File pathfile=new File(dirPath);
				if(!pathfile.exists()) {
					pathfile.mkdirs();
				}
				//上傳文件的最后的文件名 上傳人_UUID_源文件名
				String lfilename=name1+"_"+UUID.randomUUID()+" "+sfilename;
				try {
					multipartFile.transferTo(new File(dirPath,lfilename));
				} catch (IllegalStateException | IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					System.out.println("錯誤"+e);
				}
			}
			return "index2";
		}else {
			System.out.println("錯誤2");
			return null;
		}
	}

// MultipartFile 接口常用方法

  • byte[] getBytes() 返回文件的內容作為一個字節數組
  • String getContentType() 返回文件的內容類型
  • InputStream getInputStream() 返回InputStream讀取文件的內容
  • String getName() 返回參數的名稱多部分的形式
  • String getOriginalFilename() 返回原來的文件名
  • long getSize() 返回文件的大小,以字節為單位
  • boolean isEmpty() 返回是否上傳文件是空
  • void transferTo(File dest) 接收到的文件轉移到給定的目標文件。

8.文件下載

// 使用 ResponseEntity<byte[]> 對象設置 HttpHeaders 和 HttpStatus 配置信息下載

<%@ page import="java.net.URLEncoder"%>
...
<a href="${pageContext.request.contextPath}/hello/download?
filename=<%=URLEncoder.encode("看.jpg","UTF-8")%>">下載</a>

//下載文件處理
	@RequestMapping(value="/download")
	public ResponseEntity<byte[]> download(@RequestParam("filename") String filename,HttpSession session,HttpServletRequest request) throws IOException{
		System.out.println("download 開始!");
		//System.out.println(filename);
		
		User user =(User) session.getAttribute("session_user");
		//下載文件路徑
	//String pathname=request.getServletContext().getRealPath("/upload")+"/"+user.getName();
	//File pathfile=new File(pathname+File.separator+filename);
		File pathfile=new File("C:\\Users\\jz\\Pictures\\Camera Roll\\"+filename);
		System.out.println(pathfile);
		if(!pathfile.exists()) {
			System.out.println("文件為空");
		}
		//設置頭信息
		HttpHeaders headers = new HttpHeaders(); 
		//中文文件名處理
		String filename1=new String(filename.getBytes("utf-8"),"ISO-8859-1");
		 //設置請求頭內容,告訴瀏覽器以下載方式打開窗口
		headers.setContentDispositionFormData("attachment", filename1);
		//定義以流的形式返回下載數據
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(pathfile),    
                                              headers, HttpStatus.OK);  
	}

本博客原文:https://blog.onfree.cn/posts/65754b0a.html
轉載請申明原作者Athink,謝謝!