Spring MVC 中攔截器的使用示例詳解"攔截器基本配置"和 "攔截器高級配置"
1. Spring MVC 中的攔截器的使用“攔截器基本配置” 和 “攔截器高級配置”
2. 攔截器
攔截器(Interceptor) 類似于過濾器(Filter)
Spring MVC 的攔截器作用是在請求到達(dá)控制器之前或之后進(jìn)行攔截,可以對請求和響應(yīng)進(jìn)行一些特定的處理。
攔截器可以用于很多場景下:
- 登錄驗證:對于需要登錄才能訪問的地址,使用攔截器可以判斷用戶是否已登錄,如果未登錄,則跳轉(zhuǎn)到登錄頁面。
- 權(quán)限校驗:根據(jù)用戶權(quán)限對部分網(wǎng)址進(jìn)行訪問控制,拒絕未經(jīng)授權(quán)的用戶訪問。
- 請求日志:記錄請求信息,例如:請求地址,請求參數(shù),請求時間等,用于排查問題和性能優(yōu)化。
- 更改響應(yīng):可以對響應(yīng)的內(nèi)容進(jìn)行修改,例如:添加頭信息,調(diào)整響應(yīng)內(nèi)容格式等。
攔截器和過濾器的區(qū)別在于它們的作用層面不同:
- 過濾器更注重在請求和響應(yīng)的流程中進(jìn)行處理,可以修改請求和響應(yīng)的內(nèi)容,例如:設(shè)置編碼和字符集,請求頭,狀態(tài)碼等。
- 攔截器則更加側(cè)重于對控制器進(jìn)行前置或后置處理,在請求到達(dá)控制器之前或之后進(jìn)行特定的操作,例如:打印日志,權(quán)限驗證等。
Filter、Servlet、Interceptor、Controller的執(zhí)行順序:
3. Spring MVC 中的攔截器的創(chuàng)建和基本配置
3.1 定義攔截
實現(xiàn)org.springframework.web.servlet.HandlerInterceptor
接口,共有三個方法可以進(jìn)行選擇性的實現(xiàn):
- preHandle( ):處理器方法調(diào)用之前執(zhí)行。只有該方法有返回值,返回值是布爾類型,true 表示放行,false 表示攔截 。
- postHandle( ):處理器方法調(diào)用之后執(zhí)行。
- afterCompletion( ):渲染完成后執(zhí)行。
3.2 攔截器基本配置
第一步:編寫攔截器,該攔截器要實現(xiàn)org.springframework.web.servlet.HandlerInterceptor
接口 。
package com.rainbowsea.springmvc.interceptors; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class Interceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor1's preHandle!"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Interceptor1's postHandle!"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("Interceptor1's afterCompletion!"); } }
在 Spring MVC 中攔截器的基本配置有兩種方式:
- 第一種方式是:通過 xml 進(jìn)行配置
- 第二種方式是:通過 @Component 注解 + xml 文件進(jìn)行配置
需要注意的是:這個基本配置,默認(rèn)情況下是攔截所有請求的。
在 springmvc.xml 文件中進(jìn)行如下配置:
<mvc:interceptors> <bean class="com.powernode.springmvc.interceptors.Interceptor1"/> </mvc:interceptors>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 組件掃描--> <context:component-scan base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan> <!-- 視圖解析器--> <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver"> <!--作用于視圖渲染的過程中,可以設(shè)置視圖渲染后輸出時采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> <!--如果配置多個視圖解析器,它來決定優(yōu)先使用哪個視圖解析器,它的值越小優(yōu)先級越高--> <property name="order" value="1"/> <!--當(dāng) ThymeleafViewResolver 渲染模板時,會使用該模板引擎來解析、編譯和渲染模板--> <property name="templateEngine"> <bean class="org.thymeleaf.spring6.SpringTemplateEngine"> <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器負(fù)責(zé)根據(jù)模板位置、模板資源名稱、文件編碼等信息,加載模板并對其進(jìn)行解析--> <property name="templateResolver"> <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver"> <!--設(shè)置模板文件的位置(前綴)--> <property name="prefix" value="/WEB-INF/templates/"/> <!--設(shè)置模板文件后綴(后綴),Thymeleaf文件擴展名不一定是html,也可以是其他,例如txt,大部分都是html--> <property name="suffix" value=".html"/> <!--設(shè)置模板類型,例如:HTML,TEXT,JAVASCRIPT,CSS等--> <property name="templateMode" value="HTML"/> <!--用于模板文件在讀取和解析過程中采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- 配置攔截器--> <mvc:interceptors> <!-- 基本配置,第一種方式 注意:基本配置,默認(rèn)情況下是攔截所有請求的--> <bean class="com.rainbowsea.springmvc.interceptors.Interceptor1"></bean> </mvc:interceptors> </beans>
編寫對應(yīng)的 Controller 控制器進(jìn)行測試:
package com.rainbowsea.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller // 交給 Spring IOC 容器管理 public class IndexController { @RequestMapping("/index") public String toIndex() { System.out.println("IndexController#toIndex() ---> 處理器方法執(zhí)行了"); return "index"; } @RequestMapping("ok") public String toOK() { System.out.println("IndexController#OK() ---> 處理器方法執(zhí)行了"); return "ok"; } }
運行測試:
第二種方式是:通過 @Component 注解 + xml 文件進(jìn)行配置
注意:同樣的,對于這種基本配置來說,攔截器是攔截所有請求的。
第二種方式的前提:
前提1:包掃描,在 spring mvc 中配置組件掃描
前提2:使用 @Component 注解進(jìn)行對 編寫的攔截器類進(jìn)行標(biāo)注即可。
兩個前提都搞定了,就可以在 spring mvc.xml 文件中進(jìn)行配置了。
<mvc:interceptors> <ref bean="interceptor1"/> </mvc:interceptors>
運行測試:
3.3 攔截器的高級配置
采用以上基本配置方式,攔截器是攔截所有請求路徑的。如果要針對某些路徑進(jìn)行攔截,某些路徑不攔截,某些路徑攔截,可以采用高級配置:在 spring mvc.xml 文件當(dāng)中進(jìn)行配置
以上的配置表示,除 /ok 請求路徑之外,剩下的路徑全部攔截。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 組件掃描--> <context:component-scan base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan> <!-- 視圖解析器--> <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver"> <!--作用于視圖渲染的過程中,可以設(shè)置視圖渲染后輸出時采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> <!--如果配置多個視圖解析器,它來決定優(yōu)先使用哪個視圖解析器,它的值越小優(yōu)先級越高--> <property name="order" value="1"/> <!--當(dāng) ThymeleafViewResolver 渲染模板時,會使用該模板引擎來解析、編譯和渲染模板--> <property name="templateEngine"> <bean class="org.thymeleaf.spring6.SpringTemplateEngine"> <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器負(fù)責(zé)根據(jù)模板位置、模板資源名稱、文件編碼等信息,加載模板并對其進(jìn)行解析--> <property name="templateResolver"> <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver"> <!--設(shè)置模板文件的位置(前綴)--> <property name="prefix" value="/WEB-INF/templates/"/> <!--設(shè)置模板文件后綴(后綴),Thymeleaf文件擴展名不一定是html,也可以是其他,例如txt,大部分都是html--> <property name="suffix" value=".html"/> <!--設(shè)置模板類型,例如:HTML,TEXT,JAVASCRIPT,CSS等--> <property name="templateMode" value="HTML"/> <!--用于模板文件在讀取和解析過程中采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <!-- 高級配置:指定一些路徑被攔截,一些路徑不攔截--> <mvc:interceptors> <mvc:interceptor> <!-- /** 表示攔截所有路徑--> <mvc:mapping path="/**"/> <!-- /ok 請求路徑不攔截--> <mvc:exclude-mapping path="/ok"/> <!-- /index 請求路徑攔截--> <!-- <mvc:mapping path="/index"/>--> <!-- 設(shè)置對應(yīng)的那個攔截器--> <ref bean="interceptor1"></ref> </mvc:interceptor> </mvc:interceptors> </beans>
運行測試:
4. Spring MVC中多個攔截器的執(zhí)行順序
這里我們?yōu)榱颂骄?,多個攔截器存在的時候的執(zhí)行順序,我們創(chuàng)建 3 個 攔截器。如下:
4.1 如果所有攔截器 preHandle( ) 方法 都返回 true時,多個攔截器的的執(zhí)行順序
配置多個攔截器
<mvc:interceptors> <!-- 配置多個攔截器,這個是基本配置,默認(rèn)是所有請求都會進(jìn)行攔截處理--> <ref bean="interceptor1"></ref> <ref bean="interceptor2"></ref> <ref bean="interceptor3"></ref> </mvc:interceptors>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 組件掃描--> <context:component-scan base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan> <!-- 視圖解析器--> <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver"> <!--作用于視圖渲染的過程中,可以設(shè)置視圖渲染后輸出時采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> <!--如果配置多個視圖解析器,它來決定優(yōu)先使用哪個視圖解析器,它的值越小優(yōu)先級越高--> <property name="order" value="1"/> <!--當(dāng) ThymeleafViewResolver 渲染模板時,會使用該模板引擎來解析、編譯和渲染模板--> <property name="templateEngine"> <bean class="org.thymeleaf.spring6.SpringTemplateEngine"> <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器負(fù)責(zé)根據(jù)模板位置、模板資源名稱、文件編碼等信息,加載模板并對其進(jìn)行解析--> <property name="templateResolver"> <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver"> <!--設(shè)置模板文件的位置(前綴)--> <property name="prefix" value="/WEB-INF/templates/"/> <!--設(shè)置模板文件后綴(后綴),Thymeleaf文件擴展名不一定是html,也可以是其他,例如txt,大部分都是html--> <property name="suffix" value=".html"/> <!--設(shè)置模板類型,例如:HTML,TEXT,JAVASCRIPT,CSS等--> <property name="templateMode" value="HTML"/> <!--用于模板文件在讀取和解析過程中采用的編碼字符集--> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> <mvc:interceptors> <!-- 配置多個攔截器,這個是基本配置,默認(rèn)是所有請求都會進(jìn)行攔截處理--> <ref bean="interceptor1"></ref> <ref bean="interceptor2"></ref> <ref bean="interceptor3"></ref> </mvc:interceptors> </beans>
如果所有攔截器 preHandle 都返回 true
按照 springmvc.xml文件中配置的順序,自上而下調(diào)用 preHandle:
4.2 如果其中一個攔截器 preHandle ( ) 方法,返回 false,多個攔截器的的執(zhí)行順序
Interceptor3 攔截器中的 preHandle()方法返回 false。其他兩個攔截器返回 true.
規(guī)則:只要有一個攔截器preHandle
返回false,任何postHandle
都不執(zhí)行。但返回false的攔截器的前面的攔截器按照逆序執(zhí)行afterCompletion
。
只要有一個攔截器
preHandle()
方法,返回false,則任何攔截器的postHandle()方法
都不執(zhí)行。但返回 false 的攔截器的前面的攔截器按照逆序執(zhí)行afterCompletion
。返回 false 攔截器,攔截住了,則其中的 Controllor控制器不執(zhí)行了,其中的 postHandle
一個也不會執(zhí)行。而對應(yīng)的 afterCompletion()方法,的執(zhí)行是按照配置攔截器(自上而下)的倒序執(zhí)行,但其中返回 false 的攔截器中的 afterCompletion()方法不會被執(zhí)行
只要有一個攔截器
preHandle
返回false,任何postHandle
都不執(zhí)行。但返回false的攔截器的前面的攔截器按照逆序執(zhí)行afterCompletion
。只要有一個攔截器preHandle
返回false,任何postHandle
都不執(zhí)行。但返回false的攔截器的前面的攔截器按照逆序執(zhí)行afterCompletion
。
5. 補充:源碼分析
5.1 方法執(zhí)行順序的源碼分析
public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 調(diào)用所有攔截器的 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 調(diào)用處理器方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 調(diào)用所有攔截器的 postHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); // 處理視圖 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // 渲染頁面 render(mv, request, response); // 調(diào)用所有攔截器的 afterCompletion 方法 mappedHandler.triggerAfterCompletion(request, response, null); } }
5.2 攔截與放行的源碼分析
public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 調(diào)用所有攔截器的 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 如果 mappedHandler.applyPreHandle(processedRequest, response) 返回false,以下的return語句就會執(zhí)行 return; } } }
public class HandlerExecutionChain { boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for (int i = 0; i < this.interceptorList.size(); i++) { HandlerInterceptor interceptor = this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); // 如果 interceptor.preHandle(request, response, this.handler) 返回 false,以下的 return false;就會執(zhí)行。 return false; } this.interceptorIndex = i; } return true; } }
5.3 DispatcherServlet 和 HandlerExecutionChain 的部分源碼:
public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 按照順序執(zhí)行所有攔截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 執(zhí)行處理器方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 按照逆序執(zhí)行所有攔截器的 postHanle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); // 處理視圖 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // 渲染視圖 render(mv, request, response); // 按照逆序執(zhí)行所有攔截器的 afterCompletion 方法 mappedHandler.triggerAfterCompletion(request, response, null); } }
public class HandlerExecutionChain { // 順序執(zhí)行 preHandle boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { for (int i = 0; i < this.interceptorList.size(); i++) { HandlerInterceptor interceptor = this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { // 如果其中一個攔截器preHandle返回false // 將該攔截器前面的攔截器按照逆序執(zhí)行所有的afterCompletion triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } return true; } // 逆序執(zhí)行 postHanle void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); interceptor.postHandle(request, response, this.handler, mv); } } // 逆序執(zhí)行 afterCompletion void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } }
6. 總結(jié):
實現(xiàn)org.springframework.web.servlet.HandlerInterceptor
接口,共有三個方法可以進(jìn)行選擇性的實現(xiàn):
- preHandle( ):處理器方法調(diào)用之前執(zhí)行。只有該方法有返回值,返回值是布爾類型,true 表示放行,false 表示攔截 。
- postHandle( ):處理器方法調(diào)用之后執(zhí)行。
- afterCompletion( ):渲染完成后執(zhí)行。
在 Spring MVC 中攔截器的基本配置有兩種方式:
- 第一種方式是:通過 xml 進(jìn)行配置
- 第二種方式是:通過 @Component 注解 + xml 文件進(jìn)行配置
- 對于這種基本配置來說,攔截器是攔截所有請求的。
攔截器的高級配置:采用以上基本配置方式,攔截器是攔截所有請求路徑的。如果要針對某些路徑進(jìn)行攔截,某些路徑不攔截,某些路徑攔截,可以采用高級配置:在 spring mvc.xml 文件當(dāng)中進(jìn)行配置
Spring MVC中多個攔截器的執(zhí)行順序:
如果所有攔截器 preHandle( ) 方法 都返回 true時,多個攔截器的的執(zhí)行順序:
按照 springmvc.xml文件中配置的順序,自上而下調(diào)用 preHandle:
如果其中一個攔截器 preHandle ( ) 方法,返回 false,多個攔截器的的執(zhí)行順序
只要有一個攔截器preHandle()
方法,返回false,則任何攔截器的postHandle()方法
都不執(zhí)行。但返回 false 的攔截器的前面的攔截器按照逆序執(zhí)行afterCompletion
。攔截器源碼分析。
7. 最后:
到此這篇關(guān)于Spring MVC 中的攔截器的使用“攔截器基本配置” 和 “攔截器高級配置”的文章就介紹到這了,更多相關(guān)Spring MVC 攔截器的使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Http請求中Content-Type講解以及在Spring MVC中的應(yīng)用
這篇文章主要介紹了Http請求中Content-Type講解以及在Spring MVC中的應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-02-02Java中float類型的范圍及其與十六進(jìn)制的轉(zhuǎn)換例子
這篇文章主要介紹了Java中float類型的范圍及其與十六進(jìn)制的轉(zhuǎn)換例子,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10Jmeter中的timeshift()函數(shù)獲取當(dāng)前時間進(jìn)行加減
這篇文章主要介紹了Jmeter中的timeshift()函數(shù)獲取當(dāng)前時間進(jìn)行加減,TimeShift(格式,日期,移位,語言環(huán)境,變量)可對日期進(jìn)行移位加減操作,本文給大家詳細(xì)講解,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10thymeleaf實現(xiàn)前后端數(shù)據(jù)交換的示例詳解
Thymeleaf?是一款用于渲染?XML/XHTML/HTML5?內(nèi)容的模板引擎,當(dāng)通過?Web?應(yīng)用程序訪問時,Thymeleaf?會動態(tài)地替換掉靜態(tài)內(nèi)容,使頁面動態(tài)顯示,這篇文章主要介紹了thymeleaf實現(xiàn)前后端數(shù)據(jù)交換,需要的朋友可以參考下2022-07-07JAVA時間戳-Calendar類使用(包括set,get,add方法)
這篇文章主要介紹了JAVA時間戳-Calendar類使用(包括set,get,add方法),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04Java多維數(shù)組和Arrays類方法總結(jié)詳解
這篇文章主要介紹了Java多維數(shù)組和Arrays類方法總結(jié)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03