该文章主要简单粗暴的实现了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框架的请求转发的功能。功能写得很粗糙,很多情况都还未考虑进来,希望大家多多指点~
以上所述就是本文的全部内容了,希望大家能够喜欢。