setvalue函式_常成員函式
一、傳統Servlet請求
Java之間的網路通訊是使用的Socket,而B/S結構的專案,是瀏覽器和伺服器之間互動的專案,由於瀏覽器不是我們寫的,且瀏覽器只發送http請求,因此才會有了web容器(Tomcat、Weblogic等)幫我接收http請求,然後再將請求交給Servlet處理。
關於Servlet,其生命週期包括:初始化、服務、銷燬三個階段;Servlet的建立是由Web容器建立的,建立時機是第一次訪問該Servlet程式的時候,或者是web容器啟動的時候(需要在web.xml中配置啟動項 load-on-startup=2)
對於一個http請求,包含 協議、域名/IP、埠號、專案名、介面URI幾個內容組成(http://127.0.0.1:8080/lclspring/queryUser),其中協議是用來描述通訊協議的,域名/IP + 埠號 是用來定位某一臺機器中的指定程序的服務,然後通過URI在web.xml中找到對應的Servlet來進行處理。
web.xml分為兩類:全域性web.xml和自定義web.xml,所有的app專案都會使用全域性web.xml,裡面包含了DefaultServlet和JspServlet,其中DefaultServlet主要是用來做全路徑匹配的(/),而JspServlet是用來對jsp訪問做處理的(*.jsp)
那麼這裡引申出來一個問題,為什麼在web.xml中不能設定 /* : 因為Servelet的路徑匹配順序是 完全路徑匹配(/a/b/c) > 模糊匹配(/*) > 指定型別匹配(*.jsp) > 全路徑匹配(/),那麼如果我們在web.xml中配置了最高層級的模糊匹配(/*),那麼jsp檔案就會被我們自定義的Servlet攔截掉,而不會走JspServlet。
處於傳統的Servlet程式碼編寫,只需要兩個步驟:1、web.xml配置Servlet;2、編寫自定義Servlet類並繼承HttpServlet
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <description></description> <display-name>MyServlet</display-name> <servlet-name>MyServlet</servlet-name> <servlet-class>com.lcl.galaxy.springmvc.frame.dispatcher.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/MyServlet</url-pattern> </servlet-mapping> </web-app>
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello lcl............."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
然後就可以使用在web.xml中配置的Servlet對應的路徑進行請求了。
但是對於我們來說,每增加一個請求地址,就要寫一個Servlet類,就太麻煩了,因此就會有Spring MVC
二、Spring MVC處理
(一)SpringMvc元件介紹及流程分析
Spring MVC主要分為六個元件,分別是前端控制器、處理器對映器、處理器、處理器介面卡、檢視解析器、檢視。
其中,前端控制器、處理器和檢視就是我們通常說的MVC(Model-View-Controller)
其中前端控制器就是C(Controller),用來做介面請求、響應結果和分發請求;
處理器就是M(Model),用來處理業務請求
檢視就是V(View),用來將返回結果進行美化,並返回給瀏覽器
那麼還有三個元件,分別是處理器對映器、處理器介面卡和檢視解析器,這三個元件主要是用來對不同的處理器進行適配和返回的結果進行處理的。
處理器對映器:在該對映器內維護了一個對映Map,key為請求路徑,value為實際的處理器。同時提供了根據key獲取處理的方法。
處理器介面卡:由於處理器有很多,因此當前端控制器接收到一個請求時,其不知道應該呼叫哪一個處理器來進行處理,因此,前端控制器就不與處理器直接互動處理,而是呼叫處理器介面卡進行處理,在處理器介面卡中會呼叫真正的處理器進行處理。
檢視解析器:在拿到處理結果後,可以對結果進行處理,例如加上字首路徑和字尾檔名等操作。
對於SpringMvc的處理流程如下:
1、接收到請求
2、根據請求從處理器對映器中獲取對應的處理器
3、根據處理器獲取對應的處理器介面卡,並呼叫處理器介面卡處理
4、處理器介面卡呼叫對應的處理器進行處理
5、處理器執行完成返回結果給處理器介面卡,處理器介面卡再將結果返回給前端控制器
6、前端控制器呼叫檢視解析器進行處理
7、前端控制器呼叫試圖進行頁面渲染
8、將結果進行返回
由於現在都是前後端分離專案,且前後端基本上都用Json來進行傳輸,因此本次不處理檢視解析器和檢視。
(二)手寫SpringMvc框架
通過上述流程分析,可以發現,有六個元件,那麼我們分別建立六個元件的物件
1、前端控制器
前端控制器要繼承HttpServlet類,並且重寫doGet、doPost等方法,那麼,就先寫一個抽象的Servelt類,讓該類繼承HttpServlet,然後doGet和doPost統一呼叫doDispatch方法。
package com.lcl.galaxy.springmvc.frame.dispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public abstract class MyAbstructServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatch(req, resp); }
protected abstract void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException;
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatch(req, resp); } }
然後編寫我們的前端控制器,繼承HttpServlet方法,在該方法中,需要根據請求獲取處理器,然後根據處理器獲取介面卡,然後再呼叫介面卡進行處理,那麼在方法呼叫之前,就需要將相關的處理器和介面卡進行載入完畢,這裡就需要重寫Servlet的init方法,該方法是在tomcat容器啟動後進行載入的,因此可以在該方法中對處理器對映器、處理器進行載入。
在處理init方法時,主要分為兩步,第一步就是載入Spring配置檔案,第二不就是根據配置檔案組裝處處理器對映器集合和處理器介面卡集合:
載入Spring配置檔案:直接使用之前寫的spring框架,載入springmvc.xml配置檔案(這裡需要特殊說明一點,由於springmvc配置檔案的位置和名稱不定,因此,程式中直接載入web.xml中配置contextConfigLocation對應的值,因此web.xml在配置前端控制器時,需要配置springmvc配置檔案的位置和名稱);
組裝處理器對映器:這裡也是使用之前寫的Spring框架,載入所有HandlerMapping類(包含子類);這裡會建立一個MyHandlerMapping的介面,所有的處理器對映器都繼承自該介面,改內容如何建立,後續說明。
組裝處理器介面卡:這裡也是使用之前寫的Spring框架,載入所有HandlerAdapter類(包含子類)這裡會建立一個MyHandlerAdapter的介面,所有的處理器介面卡都繼承自該介面,改內容如何建立,後續說明。
public class MyDispatcherServlet extends MyAbstructServlet { private final String SPRING_MVC_CONTEXT = "contextConfigLocation"; private MyDefaultListableBeanFactory beanFactory; private List<MyHandlerMapping> handlerMappingList = new ArrayList<>(); private List<MyHandlerAdapter> handlerAdapterList = new ArrayList<>(); /** * Servelet自身的生命週期方法;servlet被例項化後就會執行初始化方法 * * @param config */ @Override public void init(ServletConfig config){ loadContext(config); initStratery(); } private void loadContext(ServletConfig config){ String pathvalue = config.getInitParameter(SPRING_MVC_CONTEXT); //實用配置話獲取spring mvc配置檔案 MyResources resources = new MyClassPathResource(pathvalue); beanFactory = new MyDefaultListableBeanFactory(); MyXmlBeanDefinitionReader reader = new MyXmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(resources); } private void initStratery(){ handlerMappingList = beanFactory.getBeansByType(MyHandlerMapping.class); handlerAdapterList = beanFactory.getBeansByType(MyHandlerAdapter.class); } @Override protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { } }
2、web.xml配置
<servlet> <servlet-name>MyDispatcherServlet</servlet-name> <servlet-class>com.lcl.galaxy.springmvc.frame.dispatcher.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>springmvc.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MyDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
3、處理器對映器
定義一個統一的處理器對映器介面,同時提供根據請求獲取處理器的方法
public interface MyHandlerMapping { Object getHandler(HttpServletRequest request); }
寫一個實現類,讓其繼承MyHandlerMapping,實現對映器的獲取handler方法,在該方法中,從handler的map中直接獲取。
那麼handler的map資料從哪來呢?這就要寫一個實現類的初始化方法init,在init方法中對map進行初始化,使用beanFactory獲取帶有請求路徑的BeanName,並將對應的Bean放入handlerMap中。
那麼仍然有個問題,BeanFactory從哪來呢?在手寫Spring原始碼時,已經介紹過,只要類實現了BeanFactoryAware介面,就可以直接實現其setFactory方法,因此就可以獲得到beanFactory
public class MyBeanNameUrlHandlerMapping implements MyHandlerMapping, MyBeanFactoryAware { private MyDefaultListableBeanFactory beanFactory; private Map<String, Object> urlHanlderMap = new HashMap<>(); public void init(){ try { List<MyBeanDefinition> beanDefinitions = beanFactory.getBeanDefinitions(); for (MyBeanDefinition beanDefinition : beanDefinitions){ String beanName = beanDefinition.getBeanName(); if(beanName.startsWith("/")){ urlHanlderMap.put(beanName, beanFactory.getBean(beanName)); } } }catch (Exception e){ e.printStackTrace(); } } @Override public Object getHandler(HttpServletRequest request) { return urlHanlderMap.get(request.getRequestURI()); } @Override public void setBeanFactory(MyBeanFactory beanFactory) { this.beanFactory = (MyDefaultListableBeanFactory) beanFactory; } }
然後需要將該處理器對映器新增到springmvc.xml配置檔案中,由於需要初始化handlerMap,因此需要配置初始化方法
<bean class="com.lcl.galaxy.springmvc.frame.handlermapping.MyBeanNameUrlHandlerMapping" initMethod="init"></bean>
4、處理器介面卡
第1步中說到除了初始化處理器對映器之外,還需要初始化處理器介面卡,那麼和處理器對映器一樣,需要先寫一個處理器介面卡介面,該介面提供處理方法和是否使用該介面卡的判斷方法。
public interface MyHandlerAdapter { void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException; boolean support(Object handler); }
然後定義實現類:
執行方法:將處理器強制轉換為該介面卡對應的處理器,然後呼叫處理器進行處理
是否要使用該介面卡:判斷處理器是否為該介面卡所對應的處理器
public class MyHttpRequestHandlerAdapter implements MyHandlerAdapter { @Override public void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException { MyHttpRequestHandler httpRequestHandler = (MyHttpRequestHandler) handler; httpRequestHandler.handleRequest(request, response); } @Override public boolean support(Object handler) { return handler instanceof MyHttpRequestHandler; } }
然後將介面卡配置在pringmvc.xml中
<bean class="com.lcl.galaxy.springmvc.frame.adapter.MyHttpRequestHandlerAdapter"></bean>
5、處理器
定義一個與介面卡對應的handler介面,該介面只提供處理方法
public interface MyHttpRequestHandler { void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException; }
然後分別寫兩個實現類
public class SaveUserHandler implements MyHttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("SaveUserHandler"); } }
public class QueryUserHandler implements MyHttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("QueryUserHandler"); } }
最後將這兩個實現類配置到springmvc.xml中
<bean name="/queryUser" class="com.lcl.galaxy.springmvc.frame.handler.QueryUserHandler"></bean> <bean name="/saveUser" class="com.lcl.galaxy.springmvc.frame.handler.SaveUserHandler"></bean>
6、執行流程
上面幾步已經將相關的內容進行了配置,然後就是執行流程了(第一步中的doDispatch方法),在該方法中,就是我們springmvc的處理流程,(1)根據請求從載入過的handlermapping集合中獲取對應的handler;(2)使用handler從介面卡集合中獲取對應的介面卡;(3)呼叫介面卡的執行方法(在介面卡中會呼叫對應的handler的執行方法)
@Override protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { //查詢處理器 Object handler = getHandler(request); if(handler == null){ return; } //執行處理器方法 MyHandlerAdapter mha = getHandlerAdapter(handler); if(mha == null){ return; } //執行並響應結果 try { mha.handleRequest(handler, request, response); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * 根據請求獲取處理器 * @param request * @return */ private Object getHandler(HttpServletRequest request) { if(handlerMappingList != null && handlerMappingList.size()>0){ for (MyHandlerMapping handlerMapping : handlerMappingList){ Object handler = handlerMapping.getHandler(request); if(handler != null){ return handler; } } } return null; } /** * 根據處理器查詢介面卡 * @param handler * @return */ private MyHandlerAdapter getHandlerAdapter(Object handler) { if(handlerAdapterList != null && handlerAdapterList.size() >0){ for (MyHandlerAdapter handlerAdapter : handlerAdapterList){ if(handlerAdapter.support(handler)){ return handlerAdapter; } } } return null; }
三、註解方式Spring MVC處理
在第二步中,已經將SpringMvc的處理流程編寫完畢,但是這仍不是我們平時使用註解的寫法,並且一個類只能對應一個請求,同時該類還需要在springmvc.xml上進行配置,非常的繁瑣。
1、自定義註解
對於註解的處理,首先我們分別仿照@Controller、@RequestMapping、@ResponseBody來建立三個自定義註解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyController { String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyRequestMapping { String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface MyResponseBody { }
2、使用自定義註解
我們先來寫處理器,然後再看應該如何進行載入介面卡和對映器
首先我們按照平時寫程式碼的方式來寫一個Controller
@MyController @MyRequestMapping("/user") public class UserController implements Serializable { private static long serialUUid = 1L; @MyRequestMapping("/get") @MyResponseBody public Map<String, Object> getUser(Integer id, String name){ Map<String, Object> map = new HashMap<>(); map.put("id", id); map.put("name", name); return map; } @MyRequestMapping("/save") @MyResponseBody public String saveUser(){ return "OK"; } }
這種請求模式,我們平時程式碼都這麼寫,無需多說,主要是使用了我們第1步中自定義的註解
接下來,我們按照第二步中的寫法一一梳理
3、前端控制器和web.xml
對於前端控制器和web.xml,可以服用第二步中的內容,無需新增
4、控制器
對於一個控制器,那麼肯定對應一個請求,但是像第二步中的請求一樣,實際上一個請求對應的是一個類中的一個方法,而非直接針對一個類,所以,我們的handler也應該是有兩個屬性:類和方法;
那我們就定義一個統一的handler類
@Data public class MyHandlerMethod { private Object controller; private Method method; public MyHandlerMethod(Object controller, Method method){ super(); this.controller = controller; this.method = method; } }
同時將Controller配置到springmvc.xml檔案中,讓spring進行載入,以便於後續初始化handlerMapping和handlerApater;(這一步可以使用AOP註解掃描來處理,由於我們使用的是自定義註解和自己手寫的spring框架,因此就直接在配置檔案中配置了)
<bean class="com.lcl.galaxy.springmvc.frame.controller.UserController"></bean>
5、處理器對映器
新建立一個針對註解的handlerMapping實現類,在該類中,同樣實現根據請求獲取handler方法和設定BeanFactory方法。
但是最重要的,還是需要去載入處理器對映器中的handler集合。
初始化handler集合主要分為以下幾步:
(1)判斷BeanDefinition是否使用了@MyController註解或@MyRequestMapping註解
(2)如果使用註解,則獲取該類下所有的方法
(3)迴圈方法,判斷方法上是否使用了@MyRequestMapping註解
(4)如果使用,則初始化一個HandlerMethod方法,對其封裝,然後將HandlerMethod放入handler集合中,key是請求的uri,value是HandlerMethod
public class MyRequestMappingHandlerMapping implements MyHandlerMapping, MyBeanFactoryAware { private MyDefaultListableBeanFactory beanFactory; private Map<String, MyHandlerMethod> urlHanlderMap = new HashMap<>(); public void init(){ //獲取並遍歷所有的BeanDefinition List<MyBeanDefinition> beanDefinitions = beanFactory.getBeanDefinitions(); for (MyBeanDefinition beanDefinition : beanDefinitions){ String clazzName = beanDefinition.getClazzName(); Class<?> clazz = resolveClass(clazzName); //判斷Beandefinition是否使用了MyController註解 if(isHandler(clazz)){ //如果使用了MyController註解,則獲取它的所有方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { MyRequestMapping classAnnotation = clazz.getAnnotation(MyRequestMapping.class); String classUri = classAnnotation.value(); //判斷方法是否使用了MyRequestMapping註解 if(method.isAnnotationPresent(MyRequestMapping.class)){ //獲取MyRequestMapping註解中配置的值 MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class); String methodUri = methodAnnotation.value(); //封裝MyHandlerMethod物件(controller + method) MyHandlerMethod handlerMethod = new MyHandlerMethod(beanFactory.getBean(beanDefinition.getBeanName()), method); urlHanlderMap.put(classUri + methodUri, handlerMethod); } } } } } private boolean isHandler(Class<?> clazz) { return clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyRequestMapping.class); } private Class<?> resolveClass(String clazzName) { try { return Class.forName(clazzName); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } @Override public Object getHandler(HttpServletRequest request) { return urlHanlderMap.get(request.getRequestURI()); } @Override public void setBeanFactory(MyBeanFactory beanFactory) { this.beanFactory = (MyDefaultListableBeanFactory) beanFactory; } }
將該對映器配置到springmvc.xml中
<bean class="com.lcl.galaxy.springmvc.frame.handlermapping.MyRequestMappingHandlerMapping" initMethod="init"></bean>
6、處理器介面卡
和第二步中建立介面卡一樣建立一個對於註解處理的介面卡MyRequestMappingHandlerAdapter,同樣提供兩個方法:是否要使用該介面卡、介面卡呼叫處理器進行處理。
是否使用該介面卡方法:如果處理器為MyHandlerMethod,則要使用該介面卡
呼叫介面卡方法:由於現在傳過來的處理器為MyHandlerMethod,該物件並非真正要呼叫的方法,但是該類中封裝了要執行的類和方法,那麼就可以使用反射進行呼叫;總體處理流程分為以下幾步:
(1)獲取controller和method
(2)根據請求的引數和方法的形參來獲取引數
(3)利用反射呼叫方法執行
(4)對返回結果進行處理
public class MyRequestMappingHandlerAdapter implements MyHandlerAdapter { @Override public void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException { MyHandlerMethod handlerMethod = (MyHandlerMethod) handler; Object controller = handlerMethod.getController(); Method method = handlerMethod.getMethod(); Object[] args = getParamters(request, method); Object returnValue = null; if(args == null){ returnValue = method.invoke(controller); }else{ returnValue = method.invoke(controller, args); } handlerReturnValue(returnValue, method, response); } private void handlerReturnValue(Object returnValue, Method method, HttpServletResponse response) throws IOException { } private Object[] getParamters(HttpServletRequest request, Method method) { } private void handlerParamterType(String[] stringValues, Class<?> type, List<Object> params) { } @Override public boolean support(Object handler) { return handler instanceof MyHandlerMethod; } }
可以看到以上為實現了support方法和主流程的類,對於抽出來的獲取引數和對結果進行處理方法如下:
獲取引數:先從入參請求中獲取對應的請求map集合,然後獲取方法的形參,迴圈所有形參並獲取形參的名稱,根據名稱從request引數map中獲取對應的String值,然後將值轉換為形參對應的型別,然後放入list集合中,最終返回一個請求引數的陣列。
private Object[] getParamters(HttpServletRequest request, Method method) { List<Object> params = new ArrayList(); //從物件中獲取KV請求 Map<String, String[]> parameterMap = request.getParameterMap(); if(parameterMap != null && parameterMap.size() > 0){ //獲取方法的形參集合 Parameter[] parameters = method.getParameters(); //遍歷所有形參集合 for (Parameter parameter : parameters){ //獲取形參名稱和型別(該步需要特殊處理) String name = parameter.getName(); //獲取引數名稱去引數集合中獲取對應的值 String[] stringValues = parameterMap.get(name); Class<?> type = parameter.getType(); //引數型別轉換 handlerParamterType(stringValues, type, params); } return params.toArray(); } return null; } private void handlerParamterType(String[] stringValues, Class<?> type, List<Object> params) { if(type == String.class){ params.add(String.valueOf(stringValues[0])); }else if(type == Integer.class){ params.add(Integer.parseInt(stringValues[0])); } }
這裡需要特殊說明一下,使用parameter.getName()獲取引數名稱時,需要做特殊的處理,如果不做特殊處理,獲取到的名稱則是arg0、arg1這樣的跟位置相關的名稱,而非真正的名稱。
特殊處理:pom中的maven外掛,需要配置引數compilerArgs
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <!--通過反射獲取引數時,需要設定以下引數才有意義,否則返回的就是arg0,arg1這樣的引數名稱--> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin>
對結果進行處理:對結果處理主要是判斷方法上是否使用了自定義的@MyResponseBody註解,如果使用,則對設定響應結果為json等等處理
private void handlerReturnValue(Object returnValue, Method method, HttpServletResponse response) throws IOException { if(method.isAnnotationPresent(MyResponseBody.class)){ if(returnValue instanceof String){ response.setContentType("text/plain;charset=utf8"); response.getWriter().write(String.valueOf(returnValue)); }else { response.setContentType("application/json;charset=utf8"); response.getWriter().write(JSON.toJSONString(returnValue)); } }else { } }