Хотелось бы обсудить тему работы с ошибками в веб-приложении на основе стратса.
Ловить на каждом экшине рантайм-ошибки и выбрасывать собственное исключение не здОрово...
Что посоветуете использовать или почитать?
errorpage — коряво, годится только для fatal errors, типа упавшей базы и невозможности работы приложения
далее код, который использовался/прогрессировал примерно с 2002 года на 5 проектах размером от 30 до 400 экранов
Struts, errors handling
1.
ошибки делятся на бизнес и технические. при возникновении бизнес-ошибки делается редирект на input page action-а , при технической — на errorpage с сообщением "приходите завтра"
2. классы
Action занимаются интерпретацией параметров http-запросов и вызовом методов бизнес-объектов (сервисов и т.п.). Бизнес-методы могут выкидовать exception-ы, которые наследуют
AppException. AppException определяет атрибут
code — код ошибки
3. все классы Action приложения наследуют
BaseAction, который занимается
обработкой ошибок, все остальные классы Action не содержат код обработки ошибок. BaseAction перекрывает execute и определяет executeImpl и addError (помимо прочего)
BaseAction
...
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
try {
long time = System.currentTimeMillis();
ActionForward actionForward = null;
ActionErrors errors = new ActionErrors();
// security checks here
// errors handling
try {
actionForward = executeImpl(mapping, form, request, response);
}
catch (AppException e) {
addError(e, errors, request);
actionForward = mapping.getInputForward();
}
catch (ParseException e) {
errors.add(Const.RESOURCE_KEY_ERROR_DATE_PARSE,
new ActionError(
Const.RESOURCE_KEY_ERROR_DATE_PARSE));
saveErrors(request, errors);
actionForward = mapping.getInputForward();
}
return actionForward;
}
// unexpected error, also can be handled by <error-page> in web.xml
catch (Throwable throwable) {
request.setAttribute("javax.servlet.error.exception", throwable);
request.setAttribute("javax.servlet.error.request_uri",
request.getRequestURI());
// forward to error page
return mapping.findForward(Const.FORWARD_ERROR);
}
}
public ActionForward executeImpl(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, AppException, ParseException {
throw new ServletException(
"BaseAction.executeImpl is not implemented");
}
/**
* processes an error
*
* @param e exception
* @param errors errors
* @param request request we are processing
*/
protected void addError(AppException e, ActionErrors errors, HttpServletRequest request) {
String key = Const.RESOURCE_KEY_ERROR_PREFIX + e.getCode();
errors.add(key, new ActionError(key, e.getMessage()));
saveErrors(request, errors);
}
типичный код Action — только вызов бизнес-логики
public class CreationAction extends BaseAction {
public ActionForward executeImpl(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, TopadException, ParseException {
HttpSession session = request.getSession(false);
// get process
CreationProcess proc =
(CreationProcessus) ProcessManager.get(CreationProcess.class);
// get value object from session
CompositeValue value =
(CompositeValue )session.getAttribute(
Const.SESSION_KEY_UA_VALUE);
User user = getUser(session);
// cancel
if (isCancelled(request)) {
proc.cancel(value, user);
clear(request, mapping);
return mapping.findForward(Const.FORWARD_CANCEL);
}
// get form
DetailForm dForm = (DetailForm) form;
String action = dForm.getAction();
// CONFIRM action
if (Const.ACTION_CONFIRM.equals(action)) {
// check transactional control token
// token was set by this action
ActionForward tokenCheckResult = checkToken(request, mapping);
if (tokenCheckResult != null) {
return tokenCheckResult;
}
// copy form into action
dForm.copyTo(value);
// cofirm, this method can throw business or technical exception
proc.confirm(value, user);
// clean
clear(request, mapping);
return mapping.findForward(Const.FORWARD_SUCCESS);
}
else if (...) {
...
}
}
}
4.
ApplicationResources.properties содержит такстовые сообщения для кодов ошибок, ключ начинается с префикса RESOURCE_KEY_ERROR_PREFIX
ApplicationResources.properties
# Validation errors block header and footer
errors.header=alert(''
errors.footer=);
...
# Error messages
error.prefix.=not defined, programmer's error !!!
error.prefix.null=invalid code, programmer's error !!!
...
error.prefix.S002=S002 Message 1.
error.prefix.S004=S004 Message 2.
...
error.parse.date=Date invalide
5. страница template приложения содержит следующий код, который отображает сообщения, если они есть, в javascript popup-окошке. таким образом пользователь одинаково информируется об clientside javascript validation ошибках, serverside validation ошибках и business logic ошибках
<script language="Javascript">
function showErrors () {
<logic:messagesPresent>
<bean:message key="errors.header"/>
<html:messages id="error">
+ '<%= StringUtils.filterForJavaScript((String) pageContext.getAttribute("error")) %>\n'
</html:messages>
<bean:message key="errors.footer"/>
</logic:messagesPresent>
}
...
</script>
</head>
<body onload="showErrors();void(0);">
...