springMVC整理03--處理數據模型 & 試圖解析器 & @ResponseBody & HttpEntity


1.處理模型數據

SpringMVC 中的模型數據是非常重要的,因為 MVC 中的控制(C)請求處理業務邏輯來生成數據模型(M),而視圖(V)就是為了渲染數據模型的數據。當有一個查詢的請求,控制器(C)會把請求攔截下來,然后把根據請求的內容對它進行分配適合的處理方法,在處理方法上進行處理查詢的業務邏輯,得到了數據,再把數據封裝成數據模型對象,最后把數據模型(M)對象傳給了視圖(V),讓視圖去渲染數據模型。SpringMVC 提供了以下幾種途徑輸出模型數據:

  • ModelAndView:處理方法返回值類型為 ModelAndView 時,方法體即可通過該對象添加模型數據。
  • @ModelAttribute:方法入參標注該注解后,入參的對象就會放到數據模型中。
  • Map 及 Model:入參為 org.springframework.ui.Model、org.springframework.uiModelMap 或 java.util.Map 時,處理方法返回時,Map 中的數據會自動添加到模型中。
  • @SessionAttributes:將模型中的某個屬性暫存到 HttpSession 中,以便多個請求之間可以共享這個屬性。

1.1 Map 和 和 Model  入參

 1 /**
 2 * 當參數為 Map 時
 3 * SpirngMVC 會傳入 一個 BindingAwareModelMap
 4 * 往 BindingAwareModelMap 里面存入的值 會在后面存入 request 域中
 5 * 相當於在方法返回前執行了一個 request.setAttribute 的操作
 6 */
 7 @RequestMapping("/map.html")
 8 public String map(Map<String, Object> map) {
 9 System. out .println(map.getClass().getName());
10 map.put("name", "aaa");
11 map.put("id", 123);
12 return "/model.jsp";
13 }
14 /**
15 * 參數為 Model 類型的,作用和 Map 一樣
16 */
17 @RequestMapping("/model.html")
18 public String model(Model model) {
19 model.addAttribute("id",123);
20 model.addAttribute("name","aaa");
21 return "/model.jsp";
22 }

測試頁面:

1 name=${name}<br/>
2 id=${id}<br/>

運行結果:訪問 map.html 或 model.html 的時候,頁面上顯示:name=aaa id=123

1.2ModelAndView

ModelAndView 既包含數據模型,又包含視圖信息

1 @RequestMapping("/modelandview.html")
2 public ModelAndView testModeAndView(){
3 ModelAndView modelAndView = new ModelAndView();
4 //將 Model 數據作為 request.attribute Foward 到下一個頁面。
5 modelAndView.addObject("id",123);
6 modelAndView.addObject("name","abc");
7 modelAndView.setViewName("/model.jsp");//設置要返回的頁面
8 return modelAndView;
9 }

1.3 @SessionAttributes

若希望在多個請求之間共用某個模型屬性數據,則可以在控制器類上標注一個 @SessionAttributes,SpringMVC 將在模型中對應的屬性暫存到 HttpSession 中。
@SessionAttributes 只能標注在類上。
@SessionAttributes 除了可以通過屬性名指定需要放到會話中的屬性外,還可以通過模型屬性的對象類型指定哪些模型屬性需要放到會話中.

– @SessionAttributes(types=Dept.class) 會將隱含模型中所有類型為 Dept.class 的屬性添加到session 中。
– @SessionAttributes(value={“user”,”admin”})會將模型中名為 user 和 admin 的屬性添加到session 中
– @SessionAttributes(types={Dept.class, Employee.class})會將模型中所有類型為 Dept 和Employee 的屬性添加到 session 中
– @SessionAttributes(value={“user”,”admin”}, types={Dept.class})會將模型中名為 user和 admin和類型為 Dept 的對象放到 session 中。
在類上添加 @SessionAttributes 注解

1 @SessionAttributes(types = Dept.class, value = {"user", "admin"})
2 @Controller
3 public class ModelController {

測試方法:

 1 @RequestMapping("/session.html")
 2 public ModelAndView testSettion(Map<String, Object> map) {
 3 map.put("admin", "I am admin");
 4 map.put("user", "I am user");
 5 Dept dept = new Dept();
 6 dept.setName("session name");
 7 map.put("dept", dept);
 8 //@SessionAttributes 注解里沒有聲明 other 這個屬性,所以不會在 session 中
 9 map.put("other", "I'm other");
10 return new ModelAndView("/model.jsp", "result", map);
11 }

測試頁面:

 1 request 中的屬性:<br/>
 2 admin:${requestScope.admin}<br/>
 3 user:${requestScope.user}<br/>
 4 dept.name:${requestScope.dept.name}<br/>
 5 other:${requestScope.other}<br/>
 6 session 中的屬性:<br/>
 7 admin:${sessionScope.admin}<br/>
 8 user:${sessionScope.user}<br/>
 9 dept.name:${sessionScope.dept.name}<br/>
10 other:${sessionScope.other}<br/>

運行效果:

可以看到模型中的屬性都放到了request的域中。@SessionAttributes中沒有聲明other,所以 session 中的 other 是空的。

1.4 @ModelAttribute

1.4.1  方法參數上使用@ ModelAttribute

在參數前使用@ModelAttribute,在進去方法時可以通過參數給對象賦值,如下面的代碼,當請求/model1.html?id=1 的時候,會給 dept 的 id 屬性賦值。在方法中可以對 dept 做進一步的處理。@ModelAttribute 可以自動將被注解的對象作為數據模型返回給頁面。

1 @RequestMapping("/model1.html")
2 public String testModelAttribute(@ModelAttribute Dept dept) {
3 dept.setId(123);
4 dept.setName("test");
5 //使用@ModelAttribute 注解 dept
6 //相當於執行了 request.setAttribute("dept",dept);
7 //頁面上可以直接取數據
8 return "/model.jsp";
9 }

測試頁面 model.jsp,使用 EL 表達式取值 :

1 <body>
2 dept.id=${dept.id}<br/>
3 dept.name=${dept.name}
4 </body

運行結果

 

1.4.2  定義方法時使用@ ModelAttribute

在方法上使用@ModelAttribute 后,執行這個 Controller 的任意一個方法之前,都會調用這個方法給對象賦值。

 1 /**
 2 * 在方法上使用@ModelAttribute,調用這個 Controller 任意一個方法之前
 3 * 都會執行這個方法給模型賦值
 4 */
 5 @ModelAttribute("dept")
 6 public Dept getDept() {
 7 Dept dept = new Dept();
 8 dept.setId(456);
 9 dept.setName("name");
10 return dept;
11 }
12 /**
13 * 在調用這個方法前,會執行 getDept()
14 * 如果請求中有參數,會覆蓋掉 getDept()的值
15 * dept 會作為數據模型返回到頁面上
16 */
17 @RequestMapping("/model2.html")
18 public String testModelAttribute2(@ModelAttribute Dept dept) {
19 System. out .println(dept.getId());
20 System. out .println(dept.getName());
21 return "/model.jsp";
22 }

2. 視圖和視圖解析器

對於Controller的目標方法,無論其返回值是String、View、ModelMap或是ModelAndView,SpringMVC 都會在內部將它們封裝為一個 ModelAndView 對象進行返回。
Spring MVC 借助視圖解析器(ViewResolver)得到最終的視圖對象(View),最終的視圖可以是 JSP 也可是 Excell、 JFreeChart 等各種表現形式的視圖。

View ---View 接口表示一個響應給用戶的視圖,例如 jsp 文件,pdf 文件,html 文件等。
視圖的作用是渲染模型數據,將模型里的數據以某種形式呈現給客戶。
為了實現視圖模型和具體實現技術的解耦,Spring 在 org.springframework.web.servlet 包中定義了一個高度抽象的 View 接口。
視圖對象由視圖解析器負責實例化。由於視圖是無狀態的,所以他們不會有線程安全的問題。所謂視圖是無狀態的,是指對於每一個請求,都會創建一個 View 對象。
JSP 是最常見的視圖技術。

2.1 ViewResolver
ViewResolver 的主要作用是把一個邏輯上的視圖名稱解析為一個真正的視圖,SpringMVC
中用於把 View 對象呈現給客戶端的是 View 對象本身,而 ViewResolver 只是把邏輯視圖名稱
解析為對象的 View 對象。

2.1.1 InternalResourceViewResolver
InternalResourceViewResolver 可以在視圖名稱前自動加前綴或后綴:

1 <!-- 配置視圖解析器-->
2 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
3 <property name="prefix" value="/"></property><!--前綴-->
4 <property name="suffix" value=".jsp"></property><!--后綴-->
5 </bean>

如果配置了上面的解析器,Controller中返回字符串就不需要寫/index.jsp了,直接返回“index”,就會按照/index.jsp 去解析

2.1.2 MappingJackson2JsonView

2.1.3 FreeMarkViewResolver
FreeMaker 后面會進行介紹,
FreeMarker 與 spring 整合需要導入 jar:

 1 <dependency>
 2 <groupId>org.freemarker</groupId>
 3 <artifactId>freemarker</artifactId>
 4 <version>2.3.23</version>
 5 </dependency>
 6 <dependency>
 7 <groupId>org.springframework</groupId>
 8 <artifactId>spring-context-support</artifactId>
 9 <version>4.3.11.RELEASE</version>
10 </dependency>

使用 FreeMarker 模板生成靜態網頁,需要在 springMVC-servlet.xml 中配置:

1 <bean id="freemarkerConfig"
2 class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
3 <!--模板存放路徑-->
4 <property name="templateLoaderPath" value="/WEB-INF/ftl/" />
5 <property name="defaultEncoding" value="UTF-8" />
6 </bean>

在 WEB-INF/ftl 下創建模板文件 hello.ftl:

1 <html>
2 <head>
3 <title>Title</title>
4 </head>
5 <body>
6 <h1>${hello}</h1>
7 </body>

通過模板生成靜態網頁:

 1 @Controller
 2 public class  FreeMarkerController { {
 3 @Autowired
 4 private  FreeMarkerConfigurer  freeMarkerConfigurer;
 5 @RequestMapping( "/freemarker.html")
 6 @ResponseBody
 7 public  String genHtml() throws  Exception { {
 8 // 1 、從 g spring  容器中獲得 r FreeMarkerConfigurer  對象。
 9 // 2 、從 r FreeMarkerConfigurer  對象中獲得 n Configuration  對象。
10 Configuration  configuration  =  freeMarkerConfigurer.getConfiguration();
11 // 3 、使用 n Configuration  對象獲得 e Template  對象。
12 Template  template  =  configuration.getTemplate( "hello.ftl");
13 // 4 、創建數據集
14 Map  dataModel  =  new HashMap <>();
15 dataModel. put( "hello",  "1000");
16 // 5 、創建輸出文件的 r Writer  對象。
17 Writer  out  =  new FileWriter( new File( "F:/spring- - freemarker.html"));
18 // 6 、調用模板對象的 s process  方法,生成文件。
19 template.process( dataModel,  out);
20 // 7 、關閉流。
21 out. close();
22 return  "OK";
23 } }
24 } }

在 F 盤下就能看到 spring-freemark.html 文件了

以上是生成靜態網頁的配置。
如果想像讀取 jsp 一樣動態展示 freeMarker 的頁面,可以配置視圖解析器:

1 <bean id="viewResolverFtl"
2 class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
3 <property name="viewClass"
4 value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
5 <property name="contentType" value="text/html; charset=utf-8"/>
6 <property name="cache" value="true" />
7 <property name="suffix" value=".ftl" />
8 <property name="order" value="0"/>
9 </bean>

order 越小,視圖解析器的優先級就越高。

1 @RequestMapping( "/hellofm.html")
2 public  String sayHello( ModelMap  map) { {
3 // 傳遞屬性到頁面
4 map.addAttribute( "hello",  " Hello FreeMarker!");
5 return  "/hello"; // 去找  hello.ftl
6 } }

運行結果:

2.1.4 BeanNameViewResolver
引入 servlet 的 jar

1 <!--servlet 依賴 jar 包-->
2 <dependency>
3 <groupId>javax.servlet</groupId>
4 <artifactId>javax.servlet-api</artifactId>
5 <version>3.1.0</version>
6 </dependency>

自定義一個視圖,然后聲明成 bean,Controller 中返回這個 bean 的名字,就可以顯示當前的視圖:

 1 public class  HelloView  implements View { {
 2 public  String getContentType() { {
 3 return  "text/html";
 4 } }
 5 public void render(Map< < String,  ?>  model, HttpServletRequest  request,
 6 HttpServletResponse  response)  throws  Exception { {
 7 // 向相應中寫入數據
 8 response. getWriter().print( "Welcome to  View:Hello");
 9 } }
10 } }

springMVC-servlet.xml 中配置:

1 <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
2 <property name="order" value="10" /><!--優先級靠后-->
3 </bean>
4 <bean id="helloView" class="view.HelloView"/>

Controller

1 @RequestMapping( "helloview.html")
2 public  String hello() { {
3 // 因為當前沒有  helloView.jsp
4 // 所以視圖解析器依次執行,找到 w id=helloView  的視圖並顯示
5 return  "helloView";
6 } 

運行結果:

2.2  自定義 View

處理 json 數據需要 json 的 jar 包

1 <!-- Jackson Json 處理工具包 -->
2 <dependency>
3 <groupId>com.fasterxml.jackson.core</groupId>
4 <artifactId>jackson-databind</artifactId>
5 <version>2.7.4</version>
6 </dependency>
 1 public class  JsonView  extends AbstractView { {
 2 /**
 3 *  該 w View  對應的輸出類型
 4 */
 5 @Override
 6 public  String getContentType() { {
 7 return  "application/json; charset=UTF- - 8";
 8 } }
 9 // 向響應中寫入數據
10 @Override
11 protected void renderMergedOutputModel(Map< < String,  Object >  model,
12 HttpServletRequest  request,HttpServletResponse  response)
13 throws  Exception { {
14 ObjectMapper  mapper  =  new ObjectMapper();
15 // 設置 e Date  類型的格式,默認是顯示毫秒數
16 mapper.setDateFormat( new SimpleDateFormat( "yyyy- - MM- - dd HH:mm:ss"));
17 // 需要注意的是放入 l model  的對象一定要實現 e Serializable  接口才能轉化成  json
18 String  jsonStr  =  mapper.writeValueAsString( model);
19 response. setContentType(getContentType());
20 response. setHeader( "Cache- - Control",  "no- - cache");
21 response. setCharacterEncoding( "UTF- - 8");
22 PrintWriter  out  =  null;
23 try { {
24 out  =  response. getWriter();
25 out.print( jsonStr);
26 out.flush();
27 }  catch ( IOException e e) { {
28 }  finally { {
29 if ( out  !=  null) { {
30 out.close();
31 out  =  null;
32 } }
33 } }
34 } }
35 } }

測試代碼:

1 @RequestMapping( "myview.html")
2 public  ModelAndView myView() { {
3 Map< < String,  Object >  result  =  new HashMap <>();
4 result. put( "key1",  "123");
5 result. put( "key2",  new String[]{ { "a",  "b"} });
6 result. put( "key3",  new Date());
7 return new ModelAndView( new JsonView(),  result);
8 } }

如果是 map 中的值是其它對象類型的,傳給 ModelAndView 的數據必須有一個 modelName

2.3  轉發和重定向

1 public String showView2() {
2 //轉發前面加 forward:
3 return "index.html";
4 }
5 @RequestMapping("/redirect.html")
6 public String showView3() {
7 //重定向前面加 redirect:
8 return "redirect:index.html";
9 }

3. @ReponseBody

該注解用於將 Controller 的方法返回的對象,通過適當的 HttpMessageConverter 轉換為指定格式后,寫入到 Response 對象的 body 數據區。使用時機:返回的數據不是 html 標簽的頁面,而是其他某種格式的數據時(如 json、xml 等)使用。

3.1  返回 json  數據

處理 json 數據需要 json 的 jar 包

1 <!-- Jackson Json 處理工具包 -->
2 <dependency>
3 <groupId>com.fasterxml.jackson.core</groupId>
4 <artifactId>jackson-databind</artifactId>
5 <version>2.7.4</version>
6 </dependency

返回 json 類型的數據,需要在 spring 配置文件中加入如下配置:

 1 <!--配置返回值轉換器-->
 2 <bean id="contentNegotiationManagerFactoryBean"
 3 class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
 4 <!--是否支持后綴匹配-->
 5 <property name="favorPathExtension" value="true"/>
 6 <!--是否支持參數匹配-->
 7 <property name="favorParameter" value="true"/>
 8 <!--是否 accept-header 匹配-->
 9 <property name="ignoreAcceptHeader" value="false"/>
10 <property name="mediaTypes">
11 <map>
12 <!--表示.json 結尾的請求返回 json-->
13 <entry key="json" value="application/json"/>
14 <!--表示.xml 結尾的返回 xml-->
15 <entry key="xml" value="application/xml"/>
16 </map>
17 </property>
18 </bean>

測試 favorPathExtension 請求后綴分別是.xml 和.json
測試 favorParameter 請求中參數 format=json 和 format=xml
測試 ignoreAcceptHeader,請求的 Header 中 Accept=application/json 或 Accept=application/xml
如果要返回 Xml,需要將要轉換為 xml 的實體類上添加注解,如:

1 @XmlRootElement
2 public class Dept {

在<mvc:annotation-driven/>標簽中指定 content-negotation-manager

1 <mvc:annotation-driven
2 content-negotiation-manager="contentNegotiationManagerFactoryBean"/>

測試類:

 1 @Controller
 2 @RequestMapping("/json")
 3 public class JsonController {
 4 @RequestMapping("/get ")
 5 @ResponseBody//會自動將返回值轉換成 json
 6 public Dept getJson() {
 7 Dept dept = new Dept();
 8 dept.setId(1);
 9 dept.setName("張三");
10 return dept;
11 }
12 }

測試結果:

 

3.2  實現 RESTFUL

 

 1 @RestController//相當於本類中所有的方法都加了@ResponseBody
 2 @RequestMapping("/rest")
 3 public class RestTestController {
 4 //通過 method 限制請求的方式
 5 @RequestMapping(value = "/{id}", method = RequestMethod. GET )
 6 public Dept getDept(@PathVariable Integer id) {
 7 //模擬從數據庫中查出一條數據
 8 Dept dept = new Dept();
 9 dept.setId(id);
10 dept.setName("張三");
11 return dept;
12 }
13 @RequestMapping(method = RequestMethod. POST )
14 public Dept addDept(Dept dept) {
15 //模擬插入數據后生成主鍵
16 dept.setId(1);
17 System. out .println(dept.getName());
18 return dept;
19 }
20 @RequestMapping(method = RequestMethod. PUT , consumes = "application/json")
21 public Dept updateDept(@RequestBody Dept dept) {
22 System. out .println(dept.getName());
23 //執行修改的業務略
24 dept.setName("修改");//模擬修改名字
25 return dept;
26 }
27 @RequestMapping(value = "/{id}", method = RequestMethod. DELETE )
28 public String deleteDept(@PathVariable Integer id) {
29 //執行刪除的業務略
30 System. out .println(id);
31 return "刪除成功";
32 }
33 }

通過 postman 可以測試請求(ajax 方式無法測試 PUT 和 DELETE)。測試 put 的時候,請求的 body 設置為 raw,Headers 的 ContentType=application/json,否則會報 415:

注意類名上方的@RestController,相當於在類中每個方法上都添加了@ReponseBody。deleteDept 方法返回了一句 String 類型的提示信息,默認的 String 類型的返回值,編碼是ISO-8859-1,中文會亂碼,解決方案是在配置文件中修改編碼:

修改<mvc:annotation-driven>節點,添加<mvc:message-converters>

1 <mvc:annotation-driven
2 content-negotiation-manager="contentNegotiationManagerFactoryBean">
3 <!--String 返回值默認編碼是 ISO-8859-1,需要-->
4 <mvc:message-converters>
5 <bean class="org.springframework.http.converter.StringHttpMessageConverter">
6 <constructor-arg value="UTF-8" />
7 </bean>
8 </mvc:message-converters>
9 </mvc:annotation-driven>

4 HttpEntity

HttpEntity 和@RequestBody 和@ResponseBody 類似,除了可以得到 request 和 response的 body 以外,還可以操作 header。

 1 @RequestMapping("/entity.html")
 2 public ResponseEntity<Dept> getEntity(RequestEntity<Dept> requestEntity) {
 3 //獲取請求頭
 4 String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
 5 System. out .println(requestHeader);
 6 Dept dept = new Dept();
 7 dept.setId(1);
 8 dept.setName("張三");
 9 HttpHeaders responseHeaders = new HttpHeaders();//創建響應頭
10 responseHeaders.set("MyResponseHeader", "MyValue");//自定義響應頭
11 //響應對象
12 ResponseEntity<Dept> responseEntity =
13 new ResponseEntity<>(dept, responseHeaders, HttpStatus. OK );
14 return responseEntity;
15 }

測試的頁面:

 1 <input type="button" onclick=" testEntity ()" value="測試 HttpEntity"/>
 2 <script type="text/javascript">
 3 function  testEntity (){
 4 $.ajax({
 5 type: "POST",
 6 url: "/json/entity.html",
 7 headers:{"MyRequestHeader":"abc"},
 8 success: function(data, status, xhr){//xhr 可以看到響應的頭
 9 alert(data.id);
10 alert(status);
11 alert("Header="+xhr.getResponseHeader("MyResponseHeader"));
12 },
13 error: function(data, status, xhr){
14 alert(data.id);
15 alert(status);
16 alert("Header="+xhr.getResponseHeader("MyResponseHeader"));
17 }
18 });
19 }
20 </script>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM