該文章主要簡單粗暴的實現了struts的請求轉發功能。 其他的功能后續會慢慢補上。
最近在學習javassist的內容,看到一篇文章 大家一起寫mvc 主要簡單的描述了mvc的工作流程,同時實現了簡單的struts2功能。
這里仿照的寫了個簡單的struts2框架,同時加上了自己的一些理解。
該文章主要簡單粗暴的實現了struts的請求轉發功能。 其他的功能后續會慢慢補上。
首先,在struts2框架中,請求的實現、跳轉主要是通過在struts.xml進行相關配置。 一個<action>標簽表示一個請求的定義,action中包含了①請求的名稱“name”;②請求對應的實現類“class” ;③同時還可通過“method”屬性自定義執行的方法,若沒配置默認執行execute0方法。<result》標簽定義了①結果的類型“name”,包括'SUCCESS'、'NONE'、'LOGIN'、'INPUT'、'ERROR';②請求的類型“type”,包括'dispatcher(默認)'、'chain'、'redirect'、'redirectAction'、'stream';③結果的跳轉。 在配置完struts.xml后,界面中的表單就可以通過action屬性與action定義的name屬性值相匹配找到對應的action標簽,從而找到對應的class以及執行的方法。再根據執行方法返回的string字符串同result標簽中的name相匹配,根據定義的type類型,進行下一步請求操作。
好了,在了解了struts2是怎么將界面請求同程序功能相連接后,我們通過自己的代碼來實現這部分的功能。
那么,我們該如何下手了?
我們將需要實現的功能簡單的分為兩部分 ①action部分 ②result部分
action部分
①我們需要根據界面的請求找到對應的類以及執行的方法
result部分
①我們需要根據方法執行的邏輯返回'SUCCESS'、'NONE'、'LOGIN'、'INPUT'、'ERROR'這類型的字符串
②需要對不同的返回類型,指定不同的下一步請求地址
③需要定義請求的類型,包括'dispatcher(默認)'、'chain'、'redirect'、'redirectAction'、'stream'
在本文章中,result的返回類型只實現了'SUCCESS'、'LOGIN'兩種,并且暫不考慮請求類型,實現的是默認的dispatcher請求轉發類型。完善的功能后期會再補充。
那么,下面我們來通過代碼看怎么實現如上功能。
首先定義了ActionAnnotation和ResultAnnotation 兩個自定義注解來請求需要對應的方法以及方法返回的字符串對應的跳轉請求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** * action注解:ActionName相當于web.xml配置中的url-pattern * @author linling * */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.METHOD) public @interface ActionAnnotation { String ActionName() default "" ; ResultAnnotation[] results() default {}; } /** * 返回注解對象:name相當于struts配置中的result的name,包括'SUCCESS'、'NONE'、'ERROR'、'INPUT'、'LOGIN';value相當于struts配置中對應返回跳轉內容 * @author linling * */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.METHOD) public @interface ResultAnnotation { ResultType name() default ResultType.SUCCESS; String value() default "index.jsp" ; } |
然后我們定義一個ActionContext類,來保存一個請求所需要的內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
/** * 實現模擬struts根據配置文件跳轉至action執行相應方法中需要的內容 * @author linling * */ public class ActionContext { /** * 相當于web.xml中url-pattern,唯一的 */ private String Url; /** * ActionAnnotation注解對應方法,也就是action中要執行的方法 */ private String method; /** * ActionAnnotation中的Result,對應action方法返回的類型。例如:key:'SUCCESS';value:'index.jsp' */ private Map<ResultType, String> results; /** * action的類 */ private Class<?> classType; /** * action的對象 */ private Object action; /** * 方法參數類型 */ private Class<?>[] paramsType; /** * 方法參數的名稱,注意這里方法名稱需要和上面paramType參數一一對應 * 可以理解為是struts中action中的屬性 */ private String[] actionParamsName; /** * 本次請求的HttpServletRequest */ private HttpServletRequest request; /** * 本次請求的HttpServletResponse */ private HttpServletResponse response; |
analysePackage是在組裝ActionContext需要的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
/** * 遍歷scan_package包下的class文件,將使用了ActionAnnotation注解的方法進行解析,組裝成ActionContext對象 并放入urlMap中 * @param real_path * @param scan_package * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws NotFoundException */ public static void analysePackage(String real_path, String scan_package) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NotFoundException { File file = new File(real_path); if (file.isDirectory()) { File[] files = file.listFiles(); for (File f : files) { analysePackage(f.getAbsolutePath(),scan_package); } } else { String str = real_path.replaceAll( "/" , "." ); if (str.indexOf( "classes." + scan_package) <= 0 || !str.endsWith( ".class" )) { return ; } String fileName = str.substring(str.indexOf(scan_package),str.lastIndexOf( ".class" )); Class<?> classType = Class.forName(fileName); Method[] methods = classType.getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(ActionAnnotation. class )) { ActionContext actionContext = new ActionContext(); ActionAnnotation actionAnnotation = (ActionAnnotation)method.getAnnotation(ActionAnnotation. class ); String url = actionAnnotation.ActionName(); ResultAnnotation[] results = actionAnnotation.results(); if (url.isEmpty() || results.length < 1 ) { throw new RuntimeException( "method annotation error! method:" + method + " , ActionName:" + url + " , result.length:" + results.length); } actionContext.setUrl(url); actionContext.setMethod(method.getName()); Map<ResultType, String> map = new HashMap<ResultType, String>(); for (ResultAnnotation result : results) { String value = result.value(); if (value.isEmpty()) { throw new RuntimeException( "Result name() is null" ); } map.put(result.name(), value); } actionContext.setResults(map); actionContext.setClassType(classType); actionContext.setAction(classType.newInstance()); actionContext.setParamsType(method.getParameterTypes()); actionContext.setActionParamsName(getActionParamsName(classType, method.getName())); urlMap.put(url, actionContext); } } } } |
getParams是根據httpServletRequest請求中的請求內容獲得請求參數數組,該參數數組為調用方法體的參數內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
/** * 根據 參數類型parasType 和 參數名actinParamsName 來解析請求request 構建參數object[] * @param request * @param paramsType * @param actionParamsName * @return * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws NoSuchMethodException * @throws SecurityException */ public static Object[] getParams(HttpServletRequest request, Class<?>[] paramsType, String[] actionParamsName) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Object[] objects = new Object[paramsType.length]; for ( int i = 0 ; i < paramsType.length; i++) { Object object = null ; if (ParamsUtils.isBasicType(paramsType[i])) { objects[i] = ParamsUtils.getParam(request, paramsType[i], actionParamsName[i]); } else { Class<?> classType = paramsType[i]; object = classType.newInstance(); Field[] fields = classType.getDeclaredFields(); for (Field field : fields) { Map<String, String[]> map = request.getParameterMap(); for (Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();) { String key = iterator.next(); if (key.indexOf( "." ) <= 0 ) { continue ; } String[] strs = key.split( "\\." ); if (strs.length != 2 ) { continue ; } if (!actionParamsName[i].equals(strs[ 0 ])) { continue ; } if (!field.getName().equals(strs[ 1 ])) { continue ; } String value = map.get(key)[ 0 ]; classType.getMethod(convertoFieldToSetMethod(field.getName()), field.getType()).invoke(object, value); break ; } } objects[i] = object; } } return objects; } |
好了,接下來。我們可以來實現action方法了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class LoginAction { @ActionAnnotation (ActionName= "login.action" ,results={ @ResultAnnotation (name=ResultType.SUCCESS,value= "index.jsp" ), @ResultAnnotation (name=ResultType.LOGIN,value= "login.jsp" )}) public ResultType login(String name, String password) { if ( "hello" .equals(name) && "world" .equals(password)) { return ResultType.SUCCESS; } return ResultType.LOGIN; } @ActionAnnotation (ActionName= "loginForUser.action" ,results={ @ResultAnnotation (name=ResultType.SUCCESS,value= "index.jsp" ), @ResultAnnotation (name=ResultType.LOGIN,value= "login.jsp" )}) public ResultType loginForUser( int number, LoginPojo loginPojo) { if ( "hello" .equals(loginPojo.getUsername()) && "world" .equals(loginPojo.getPassword())) { return ResultType.SUCCESS; } return ResultType.LOGIN; } } |
接下來,我們需要做的是讓程序在啟動的時候去遍歷工作目錄下所有類的方法,將使用了ActionAnnotation的方法找出來組裝成ActionContext,這就是我們請求需要執行的方法。這樣在請求到了的時候我們就可以根據請求的地址找到對應的ActionContext,并通過反射的機制進行方法的調用。
我們定了兩個Servlet。一個用于執行初始化程序。一個用來過濾所有的action請求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<servlet> <servlet-name>StrutsInitServlet</servlet-name> <servlet- class >com.bayern.struts.one.servlet.StrutsInitServlet</servlet- class > <init-param> <param-name>scan_package</param-name> <param-value>com.bayern.struts.one</param-value> </init-param> <load-on-startup> 10 </load-on-startup> </servlet> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet- class >com.bayern.struts.one.servlet.DispatcherServlet</servlet- class > </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> |
DispatcherServlet實現了對所用action請求的過濾,并使之執行對應的action方法,以及進行下一步的跳轉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
ublic void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding( "utf-8" ); String url = request.getServletPath().substring( 1 ); ActionContext actionContext = DispatcherServletUtil.urlMap.get(url); if (actionContext != null ) { actionContext.setRequest(request); actionContext.setResponse(response); try { Object[] params = DispatcherServletUtil.getParams(request, actionContext.getParamsType(), actionContext.getActionParamsName()); Class<?> classType = actionContext.getClassType(); Method method = classType.getMethod(actionContext.getMethod(), actionContext.getParamsType()); ResultType result = (ResultType)method.invoke(actionContext.getAction(), params); Map<ResultType,String> results = actionContext.getResults(); if (results.containsKey(result)) { String toUrl = results.get(result); request.getRequestDispatcher(toUrl).forward(request, response); } else { throw new RuntimeException( "result is error! result:" + result); } } |
好了,現在我們已經實現了最簡單的strut2框架的請求轉發的功能。功能寫得很粗糙,很多情況都還未考慮進來,希望大家多多指點~
以上所述就是本文的全部內容了,希望大家能夠喜歡。