Struts相对来说是一个比较简单的MVC框架,实现的 Service to Worker模式。研究Struts源代码从ActionServlet开始,因为它是统一的入口处理类,通常配置如下:
ActionServlet是一个Servlet,继承HttpServlet。先分析一个这个类的init方法,这里做了很多初始化的工作,会被容器调用,初始化代码如下:
初始化的工作基本见名知义。当收到请求时,ActionServlet的doGet()或doPost()方法都调用process()方法来处理请求:
这个方法中重点是14行,调用RequestProcessor的process方法。RequestProcessor类可以定制,在struts.config中设置:
process方法具体如下:
每项处理都封装成一个方法,对struts控制器的扩展,可以继承这两个类,然后覆盖以上方法。
具体看一下processActionCreate方法:
针对所有的请求,只实例化一个Action,因此必须注意线程安全的问题。
1
<
servlet
>
2 < servlet-name > action </ servlet-name >
3 < servlet-class > org.apache.struts.action.ActionServlet </ servlet-class >
4 < init-param >
5 < param-name > config </ param-name >
6 < param-value > /WEB-INF/struts-config.xml </ param-value >
7 </ init-param >
8 < init-param >
9 < param-name > debug </ param-name >
10 < param-value > 2 </ param-value >
11 </ init-param >
12 < init-param >
13 < param-name > application </ param-name >
14 < param-value > ApplicationResources </ param-value >
15 </ init-param >
16 < load-on-startup > 2 </ load-on-startup >
17 </ servlet >
18 < servlet-mapping >
19 < servlet-name > action </ servlet-name >
20 < url-pattern > *.do </ url-pattern >
21 </ servlet-mapping >
2 < servlet-name > action </ servlet-name >
3 < servlet-class > org.apache.struts.action.ActionServlet </ servlet-class >
4 < init-param >
5 < param-name > config </ param-name >
6 < param-value > /WEB-INF/struts-config.xml </ param-value >
7 </ init-param >
8 < init-param >
9 < param-name > debug </ param-name >
10 < param-value > 2 </ param-value >
11 </ init-param >
12 < init-param >
13 < param-name > application </ param-name >
14 < param-value > ApplicationResources </ param-value >
15 </ init-param >
16 < load-on-startup > 2 </ load-on-startup >
17 </ servlet >
18 < servlet-mapping >
19 < servlet-name > action </ servlet-name >
20 < url-pattern > *.do </ url-pattern >
21 </ servlet-mapping >
ActionServlet是一个Servlet,继承HttpServlet。先分析一个这个类的init方法,这里做了很多初始化的工作,会被容器调用,初始化代码如下:
1
public
void
init()
throws
ServletException {
2 final String configPrefix = " config/ " ;
3 final int configPrefixLength = configPrefix.length() - 1 ;
4
5 // Wraps the entire initialization in a try/catch to better handle
6 // unexpected exceptions and errors to provide better feedback
7 // to the developer
8 try {
9 initInternal();
10 initOther();
11 initServlet();
12 initChain();
13
14 getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this );
15 initModuleConfigFactory();
16
17 // Initialize modules as needed
18 ModuleConfig moduleConfig = initModuleConfig( "" , config);
19
20 initModuleMessageResources(moduleConfig);
21 initModulePlugIns(moduleConfig);
22 initModuleFormBeans(moduleConfig);
23 initModuleForwards(moduleConfig);
24 initModuleExceptionConfigs(moduleConfig);
25 initModuleActions(moduleConfig);
26 moduleConfig.freeze();
27
28 Enumeration names = getServletConfig().getInitParameterNames();
29
30 while (names.hasMoreElements()) {
31 String name = (String) names.nextElement();
32
33 if ( ! name.startsWith(configPrefix)) {
34 continue ;
35 }
36
37 String prefix = name.substring(configPrefixLength);
38
39 moduleConfig =
40 initModuleConfig(prefix,
41 getServletConfig().getInitParameter(name));
42 initModuleMessageResources(moduleConfig);
43 initModulePlugIns(moduleConfig);
44 initModuleFormBeans(moduleConfig);
45 initModuleForwards(moduleConfig);
46 initModuleExceptionConfigs(moduleConfig);
47 initModuleActions(moduleConfig);
48 moduleConfig.freeze();
49 }
50
51 this .initModulePrefixes( this .getServletContext());
52
53 this .destroyConfigDigester();
54 } catch (UnavailableException ex) {
55 throw ex;
56 } catch (Throwable t) {
57 // The follow error message is not retrieved from internal message
58 // resources as they may not have been able to have been
59 // initialized
60 log.error( " Unable to initialize Struts ActionServlet due to an "
61 + " unexpected exception or error thrown, so marking the "
62 + " servlet as unavailable. Most likely, this is due to an "
63 + " incorrect or missing library dependency. " , t);
64 throw new UnavailableException(t.getMessage());
65 }
66 }
2 final String configPrefix = " config/ " ;
3 final int configPrefixLength = configPrefix.length() - 1 ;
4
5 // Wraps the entire initialization in a try/catch to better handle
6 // unexpected exceptions and errors to provide better feedback
7 // to the developer
8 try {
9 initInternal();
10 initOther();
11 initServlet();
12 initChain();
13
14 getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this );
15 initModuleConfigFactory();
16
17 // Initialize modules as needed
18 ModuleConfig moduleConfig = initModuleConfig( "" , config);
19
20 initModuleMessageResources(moduleConfig);
21 initModulePlugIns(moduleConfig);
22 initModuleFormBeans(moduleConfig);
23 initModuleForwards(moduleConfig);
24 initModuleExceptionConfigs(moduleConfig);
25 initModuleActions(moduleConfig);
26 moduleConfig.freeze();
27
28 Enumeration names = getServletConfig().getInitParameterNames();
29
30 while (names.hasMoreElements()) {
31 String name = (String) names.nextElement();
32
33 if ( ! name.startsWith(configPrefix)) {
34 continue ;
35 }
36
37 String prefix = name.substring(configPrefixLength);
38
39 moduleConfig =
40 initModuleConfig(prefix,
41 getServletConfig().getInitParameter(name));
42 initModuleMessageResources(moduleConfig);
43 initModulePlugIns(moduleConfig);
44 initModuleFormBeans(moduleConfig);
45 initModuleForwards(moduleConfig);
46 initModuleExceptionConfigs(moduleConfig);
47 initModuleActions(moduleConfig);
48 moduleConfig.freeze();
49 }
50
51 this .initModulePrefixes( this .getServletContext());
52
53 this .destroyConfigDigester();
54 } catch (UnavailableException ex) {
55 throw ex;
56 } catch (Throwable t) {
57 // The follow error message is not retrieved from internal message
58 // resources as they may not have been able to have been
59 // initialized
60 log.error( " Unable to initialize Struts ActionServlet due to an "
61 + " unexpected exception or error thrown, so marking the "
62 + " servlet as unavailable. Most likely, this is due to an "
63 + " incorrect or missing library dependency. " , t);
64 throw new UnavailableException(t.getMessage());
65 }
66 }
初始化的工作基本见名知义。当收到请求时,ActionServlet的doGet()或doPost()方法都调用process()方法来处理请求:
1
protected
void
process(HttpServletRequest request,
2 HttpServletResponse response)
3 throws IOException, ServletException {
4 ModuleUtils.getInstance().selectModule(request, getServletContext());
5
6 ModuleConfig config = getModuleConfig(request);
7
8 RequestProcessor processor = getProcessorForModule(config);
9
10 if (processor == null ) {
11 processor = getRequestProcessor(config);
12 }
13
14 processor.process(request, response);
15 }
2 HttpServletResponse response)
3 throws IOException, ServletException {
4 ModuleUtils.getInstance().selectModule(request, getServletContext());
5
6 ModuleConfig config = getModuleConfig(request);
7
8 RequestProcessor processor = getProcessorForModule(config);
9
10 if (processor == null ) {
11 processor = getRequestProcessor(config);
12 }
13
14 processor.process(request, response);
15 }
这个方法中重点是14行,调用RequestProcessor的process方法。RequestProcessor类可以定制,在struts.config中设置:
<
controller
processorClass
="org.springframework.web.struts.DelegatingTilesRequestProcessor"
/>
process方法具体如下:
1
public
void
process(HttpServletRequest request, HttpServletResponse response)
2 throws IOException, ServletException {
3 // Wrap multipart requests with a special wrapper
4 request = processMultipart(request);
5
6 // Identify the path component we will use to select a mapping
7 String path = processPath(request, response);
8
9 if (path == null ) {
10 return ;
11 }
12
13 if (log.isDebugEnabled()) {
14 log.debug( " Processing a ' " + request.getMethod() + " ' for path ' "
15 + path + " ' " );
16 }
17
18 // Select a Locale for the current user if requested
19 processLocale(request, response);
20
21 // Set the content type and no-caching headers if requested
22 processContent(request, response);
23 processNoCache(request, response);
24
25 // General purpose preprocessing hook
26 if ( ! processPreprocess(request, response)) {
27 return ;
28 }
29
30 this .processCachedMessages(request, response);
31
32 // Identify the mapping for this request
33 ActionMapping mapping = processMapping(request, response, path);
34
35 if (mapping == null ) {
36 return ;
37 }
38
39 // Check for any role required to perform this action
40 if ( ! processRoles(request, response, mapping)) {
41 return ;
42 }
43
44 // Process any ActionForm bean related to this request
45 ActionForm form = processActionForm(request, response, mapping);
46
47 processPopulate(request, response, form, mapping);
48
49 // Validate any fields of the ActionForm bean, if applicable
50 try {
51 if ( ! processValidate(request, response, form, mapping)) {
52 return ;
53 }
54 } catch (InvalidCancelException e) {
55 ActionForward forward = processException(request, response, e, form, mapping);
56 processForwardConfig(request, response, forward);
57 return ;
58 } catch (IOException e) {
59 throw e;
60 } catch (ServletException e) {
61 throw e;
62 }
63
64 // Process a forward or include specified by this mapping
65 if ( ! processForward(request, response, mapping)) {
66 return ;
67 }
68
69 if ( ! processInclude(request, response, mapping)) {
70 return ;
71 }
72
73 // Create or acquire the Action instance to process this request
74 Action action = processActionCreate(request, response, mapping);
75
76 if (action == null ) {
77 return ;
78 }
79
80 // Call the Action instance itself
81 ActionForward forward =
82 processActionPerform(request, response, action, form, mapping);
83
84 // Process the returned ActionForward instance
85 processForwardConfig(request, response, forward);
86 }
2 throws IOException, ServletException {
3 // Wrap multipart requests with a special wrapper
4 request = processMultipart(request);
5
6 // Identify the path component we will use to select a mapping
7 String path = processPath(request, response);
8
9 if (path == null ) {
10 return ;
11 }
12
13 if (log.isDebugEnabled()) {
14 log.debug( " Processing a ' " + request.getMethod() + " ' for path ' "
15 + path + " ' " );
16 }
17
18 // Select a Locale for the current user if requested
19 processLocale(request, response);
20
21 // Set the content type and no-caching headers if requested
22 processContent(request, response);
23 processNoCache(request, response);
24
25 // General purpose preprocessing hook
26 if ( ! processPreprocess(request, response)) {
27 return ;
28 }
29
30 this .processCachedMessages(request, response);
31
32 // Identify the mapping for this request
33 ActionMapping mapping = processMapping(request, response, path);
34
35 if (mapping == null ) {
36 return ;
37 }
38
39 // Check for any role required to perform this action
40 if ( ! processRoles(request, response, mapping)) {
41 return ;
42 }
43
44 // Process any ActionForm bean related to this request
45 ActionForm form = processActionForm(request, response, mapping);
46
47 processPopulate(request, response, form, mapping);
48
49 // Validate any fields of the ActionForm bean, if applicable
50 try {
51 if ( ! processValidate(request, response, form, mapping)) {
52 return ;
53 }
54 } catch (InvalidCancelException e) {
55 ActionForward forward = processException(request, response, e, form, mapping);
56 processForwardConfig(request, response, forward);
57 return ;
58 } catch (IOException e) {
59 throw e;
60 } catch (ServletException e) {
61 throw e;
62 }
63
64 // Process a forward or include specified by this mapping
65 if ( ! processForward(request, response, mapping)) {
66 return ;
67 }
68
69 if ( ! processInclude(request, response, mapping)) {
70 return ;
71 }
72
73 // Create or acquire the Action instance to process this request
74 Action action = processActionCreate(request, response, mapping);
75
76 if (action == null ) {
77 return ;
78 }
79
80 // Call the Action instance itself
81 ActionForward forward =
82 processActionPerform(request, response, action, form, mapping);
83
84 // Process the returned ActionForward instance
85 processForwardConfig(request, response, forward);
86 }
每项处理都封装成一个方法,对struts控制器的扩展,可以继承这两个类,然后覆盖以上方法。
具体看一下processActionCreate方法:
protected
Action processActionCreate(HttpServletRequest request,
HttpServletResponse response, ActionMapping mapping)
throws IOException {
// Acquire the Action instance we will be using (if there is one)
String className = mapping.getType();
if (log.isDebugEnabled()) {
log.debug( " Looking for Action instance for class " + className);
}
// If there were a mapping property indicating whether
// an Action were a singleton or not ([true]),
// could we just instantiate and return a new instance here?
Action instance;
synchronized (actions) {
// Return any existing Action instance of this class
instance = (Action) actions.get(className);
if (instance != null ) {
if (log.isTraceEnabled()) {
log.trace( " Returning existing Action instance " );
}
return (instance);
}
// Create and return a new Action instance
if (log.isTraceEnabled()) {
log.trace( " Creating new Action instance " );
}
try {
instance = (Action) RequestUtils.applicationInstance(className);
// Maybe we should propagate this exception
// instead of returning null.
} catch (Exception e) {
log.error(getInternal().getMessage( " actionCreate " ,
mapping.getPath()), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage( " actionCreate " , mapping.getPath()));
return ( null );
}
actions.put(className, instance);
if (instance.getServlet() == null ) {
instance.setServlet( this .servlet);
}
}
return (instance);
}
HttpServletResponse response, ActionMapping mapping)
throws IOException {
// Acquire the Action instance we will be using (if there is one)
String className = mapping.getType();
if (log.isDebugEnabled()) {
log.debug( " Looking for Action instance for class " + className);
}
// If there were a mapping property indicating whether
// an Action were a singleton or not ([true]),
// could we just instantiate and return a new instance here?
Action instance;
synchronized (actions) {
// Return any existing Action instance of this class
instance = (Action) actions.get(className);
if (instance != null ) {
if (log.isTraceEnabled()) {
log.trace( " Returning existing Action instance " );
}
return (instance);
}
// Create and return a new Action instance
if (log.isTraceEnabled()) {
log.trace( " Creating new Action instance " );
}
try {
instance = (Action) RequestUtils.applicationInstance(className);
// Maybe we should propagate this exception
// instead of returning null.
} catch (Exception e) {
log.error(getInternal().getMessage( " actionCreate " ,
mapping.getPath()), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage( " actionCreate " , mapping.getPath()));
return ( null );
}
actions.put(className, instance);
if (instance.getServlet() == null ) {
instance.setServlet( this .servlet);
}
}
return (instance);
}
针对所有的请求,只实例化一个Action,因此必须注意线程安全的问题。