3.5解析ActionResolver
我们已经了解stripes的生命周期,如何通过配置把可配置的组件组合在一起,又了解了如何通过interceptor把这些组件串起来。现在我们应该了解每步每个组件都做了什么,首先我们要了解是ActionResolver,也就stripes如何通过url path去映射到相对应的action。在DispatcherServlet中第一步生命阶段就是resolveActionBean(ctx);这个方法的主体在:DispatcherHelper.java中:
public static Resolution resolveActionBean(final ExecutionContext ctx) throws Exception { return ctx.wrap( new Interceptor() { public Resolution intercept(ExecutionContext ctx) throws Exception { // Look up the ActionBean and set it on the context ActionBeanContext context = ctx.getActionBeanContext(); ActionBean bean = StripesFilter.getConfiguration() .getActionResolver().getActionBean(context); //把找到actionbean实例保存在ExcuteContext中。 ctx.setActionBean(bean); // 把找到actionbean实例保存在request中actionBean中,以供在页面或后面的// 程序直接调用actionbean实例 HttpServletRequest request = context.getRequest(); request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN, bean); return null; } }); }
上面最重要的一部分是 ActionBean bean = StripesFilter.getConfiguration() .getActionResolver().getActionBean(context); 这是通过 configuration 找到配置或默认的 ActionResolver 。 Stripes 默认是 NameBasedActionResolver ; 它继承于 AnnotatedClassActionResolver 。它的实质是采用 annotation 映射。由而我们要定制 ActionResolver ,最好是继承于 AnnotatedClassActionResolver 。由于 ActionResolver 是可配置组件,在 stripes 启动时,就会生成 ActionResolver 的子类的实例并运行它们的 init(Configuraton) 方法。 我们先来看一下 NameBasedActionResolver .getActionBean(context) ,这个方法继承于 AnnotatedClassActionResolver ;
public ActionBean getActionBean(ActionBeanContext context) throws StripesServletException { HttpServletRequest request = context.getRequest(); String path = getRequestedPath(request); //调用本类的getActionBean(context, path)方法,但是此时实例是、、//NameBasedActionResolver,而此实例中又有重写的同名方法,只能是运行子类的方法了。 ActionBean bean = getActionBean(context, path); //把找到actionbean实例放在request中的__stripes_resolved_action。 request.setAttribute(RESOLVED_ACTION, getUrlBindingFromPath(path)); return bean; } @Override public ActionBean getActionBean(ActionBeanContext context, String urlBinding) throws StripesServletException { try { return super.getActionBean(context, urlBinding); } catch (StripesServletException sse) { ActionBean bean = handleActionBeanNotFound(context, urlBinding); if (bean != null) { bean.setContext(context); return bean; } else { throw sse; } } }
它Override父类的同名方法。它的功能是先执行父类的同名方法,如果出现了异常(即没有找到对应的actionbean)就找找看有没有最合适的jsp文件。比如请求user.action,就会找user.jsp或等等。
protected ActionBean handleActionBeanNotFound(ActionBeanContext context, String urlBinding) { ActionBean bean = null; Resolution view = findView(urlBinding); if (view != null) { log.debug("Could not find an ActionBean bound to '", urlBinding, "', but found a view ","at '", view, "'. Forwarding the user there instead."); bean = new DefaultViewActionBean(view); } return bean; } 这个方法先执行findView(urlBinding): protected Resolution findView(String urlBinding) { int lastPeriod = urlBinding.lastIndexOf('.'); String path = urlBinding.substring(0, urlBinding.lastIndexOf("/") + 1); String name = (lastPeriod >= path.length()) ? urlBinding.substring(path.length(), lastPeriod) : urlBinding.substring(path.length()); ServletContext ctx = StripesFilter.getConfiguration().getBootstrapPropertyResolver().getFilterConfig().getServletContext(); try { // This will try /account/ViewAccount.jsp String jsp = path + name + ".jsp"; if (ctx.getResource(jsp) != null) { return new ForwardResolution(jsp); } // This will try /account/viewAccount.jsp name = Character.toLowerCase(name.charAt(0)) + name.substring(1); jsp = path + name + ".jsp"; if (ctx.getResource(jsp) != null) { return new ForwardResolution(jsp); } // And finally this will try /account/view_account.jsp StringBuilder builder = new StringBuilder(); for (int i=0; i<name.length(); ++i) { char ch = name.charAt(i); if (Character.isUpperCase(ch)) { builder.append("_"); builder.append(Character.toLowerCase(ch)); } else { builder.append(ch); } } jsp = path + builder.toString() + ".jsp"; if (ctx.getResource(jsp) != null) { return new ForwardResolution(jsp); } return null; } catch (MalformedURLException mue) { return null; } }}
这个方法很简单,就是找到合适的jsp页面如urlBinding 是ViewAccount.action ViewAccount.jsp viewAccount.jsp、view_account.jsp,之后就看存不存在三个jsp文件,存在就取最先存在jsp文件。 现在回到 handleActionBeanNotFound 中,找到页面的 Resolution ,那接下为了上面的程序统一起来,就用这个 Resolution 生成一个 DefaultViewActionBean(view);
class DefaultViewActionBean implements ActionBean { private ActionBeanContext context; private Resolution view; public DefaultViewActionBean(Resolution view) { this.view = view; } public void setContext(ActionBeanContext context) { this.context = context; } public ActionBeanContext getContext() { return this.context; } public Resolution view() { return view; }}
这个actionbean很很简单,就是DefaultViewActionBean(Resolution view) { this.view = view; }这个方法。用这个主要是为了和整体代码统一起来。
现在我们要回到getActionBean(ActionBeanContext , String)看看:
public ActionBean getActionBean(ActionBeanContext context, String path) throws StripesServletException { String urlBinding = getUrlBindingFromPath(path); Class<? extends ActionBean> beanClass = this.formBeans.get(urlBinding); ActionBean bean; if (beanClass == null) { throw new StripesServletException(“emit”); } try { HttpServletRequest request = context.getRequest(); if (beanClass.isAnnotationPresent(SessionScope.class)) { bean = (ActionBean) request.getSession().getAttribute(path); if (bean == null) { bean = makeNewActionBean(beanClass, context); equest.getSession().setAttribute(path, bean); } bean.setContext(context); } else { bean = (ActionBean) request.getAttribute(path); if (bean == null) { bean = makeNewActionBean(beanClass, context); bean.setContext(context); request.setAttribute(path, bean); } } return bean; } catch (Exception e) { 。。。。。 } }
这段代码首先从路径中找到urlBinding,再从urlBinding找到对应的actionbean.这两个等会儿分析,先看一下下面的代码,下面简单,从代码我们可以看出actionbean可以通过@SessionScope保存在session中。一般情况都是在request存着。通过调用程序员实现的bean.setContext(context);把上下文传到actionbean中。接下把actionbean的实例放在request(session)的path属性中。
回到上面的问题,如何找到urlBinding = getUrlBindingFromPath(path);:
String getUrlBindingFromPath(String path) { String binding = null; while (binding == null && path != null) { if (this.formBeans.containsKey(path)) { binding = path; } else { int lastSlash = path.lastIndexOf("/"); if (lastSlash > 0) { path = path.substring(0, lastSlash); } else { path = null; } } } return binding; }
在解决这个问题之前,我们得知道path是什么样的格式,怎么来的。Path是通过getActionBean(ActionBeanContext context)中的 HttpServletRequest request = context.getRequest(); String path = getRequestedPath(request);而得到:
: protected String getRequestedPath(HttpServletRequest request) { String servletPath = null, pathInfo = null; if (request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH) != null) { servletPath = (String) request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH); pathInfo = (String) request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH_INFO); } else { servletPath = request.getServletPath(); pathInfo = request.getPathInfo(); } return (servletPath == null ? "" : servletPath) + (pathInfo == null ? "" : pathInfo); }
在servlet规范中,把url路径为成三部分requestURI = contextPath + servletPath + pathInfo。
contextPath就是我们应用系统的名称,比如第二章的例子就是register。上面的程序就是取根目录之后URL地址部分,如:/user/register.action/view。我们知道是这种结构,就不难理解上面的程序就是从this.formBeans的找到有没有符合和/user/register.action/view相同的key。没有就再看看有没有/user/register.action这个相同。没有的话就再看/user相同的key.再没有的就是null啦。
在这个方法和上面的方法中都出现this.formBeans,这是什么东西,是map没有错:
rivate Map<String,Class<? extends ActionBean>> formBeans = new HashMap<String,Class<? extends ActionBean>>(); 还记得AnnotatedClassActionResolver是可配置组件呸,应该想到这个formBeans应该在init(Configuration)中设了值。确定在AnnotatedClassActionResolver的init()设了值,但是细心的会问到:这个实例不是NameBasedActionResolver的,而且它也有init(Configuration),先看NameBasedActionResolver的init()方法。 @Override public void init(Configuration configuration) throws Exception { super.init(configuration); addActionBean(DefaultViewActionBean.class); } 它调用了AnnotatedClassActionResolver。之后增加了一个DefaultViewActionBean的bean。这个是为了找到与之对应的jsp页面而用的。不做分析。 public void init(Configuration configuration) throws Exception { is.configuration = configuration; Set<Class<? extends ActionBean>> beans = findClasses(ActionBean.class); ActionClassCache.init(beans); for (Class<? extends ActionBean> clazz : beans) { addActionBean(clazz); } }
这个方法先找到所有的actionbean,之后保存在缓存中。再之后分别处理每个bean.
protected <T> Set<Class<? extends T>> findClasses(Class<T> parentType) { ResolverUtil<T> resolver = new ResolverUtil<T>(); resolver.setPackageFilters(getPackageFilters()); resolver.setLocationFilters(getUrlFilters()); if (!resolver.loadImplementationsFromContextClassloader(parentType)) { ServletContext context = this.configuration.getBootstrapPropertyResolver() .getFilterConfig().getServletContext(); resolver.loadImplementationsFromServletContext(parentType, context); } return resolver.getClasses();
这个功能是找到所有actionbean的子类。Stripes1.5有些变化。接下的缓存没有什么好看的,关键在于addActionBean(clazz);循环把每个actionbean及其对应的urlbinging和处理方法加入到this. formBeans中。
protected void addActionBean(Class<? extends ActionBean> clazz) { //取得actionbean中如@ UrlBinding(“/user/register.action”)中的/user/register.action值 String binding = getUrlBinding(clazz); // Only process the class if it's properly annotated if (binding != null) { this.formBeans.put(binding, clazz); // Construct the mapping of event->method for the class Map<String, Method> classMappings = new HashMap<String, Method>(); processMethods(clazz, classMappings); // Put the event->method mapping for the class into the set of mappings this.eventMappings.put(clazz, classMappings); // Print out the event mappings nicely for (Map.Entry<String,Method> entry : classMappings.entrySet()) { String event = entry.getKey(); Method handler = entry.getValue(); boolean isDefault = DEFAULT_HANDLER_KEY.equals(event); log.debug("Bound: ", clazz.getSimpleName(), ".", handler.getName(), "() ==> ", binding, isDefault ? "" : "?" + event); } } } 在上在的代码中有两个属性: formBeans、eventMappings,formBeans保存urlbinging的路径和与之对应的actionbean关系。eventMappings保存actionbean类与它的所有的处理事件方法的关系。这样,我们就可以通过路径(urlbinging)找到类,再通过actionbean的类找到它的事件方法。Actionbean类通过分析类的@UrlBinding就可以找到,如果没有,因为actonresolver的实例是基于命名。所以String binding = getUrlBinding(clazz)中调用是子类的方法。 @Override public String getUrlBinding(Class<? extends ActionBean> clazz) { String binding = super.基于annotation的; // If there's no annotated binding, and the class is concrete if (binding == null && !Modifier.isAbstract(clazz.getModifiers())) { binding = getUrlBinding(clazz.getName()); } return binding; } getUrlBinding先调用父类的getUrlBinding(clazz)方法。之后把actionbean的类名按一定的规则转为urlbing路径。getUrlBinding(String name)就是实现这个功能: protected String getUrlBinding(String name) { // Chop off the packages up until (and including) any base package for (String base : getBasePackages()) { int i = name.indexOf("." + base + "."); if (i != -1) { name = name.substring(i + base.length() + 1);} else if (name.startsWith(base + ".")) { name = name.substring(base.length());} } // If it ends in Action or Bean (or ActionBean) take that off if (name.endsWith("Bean")) { name = name.substring(0, name.length() - 4); } if (name.endsWith("Action")) { name = name.substring(0, name.length() - 6); } // Replace periods with slashes and make sure it starts with one name = name.replace('.', '/'); if (!name.startsWith("/")) { name = "/" + name; } // Lastly add the suffix name += getBindingSuffix(); return name; } getUrlBinding(String name)就是一些字符转换,它把如com.morik.action.RegisterAction或者RegisterActionBean,变成Register.action. 解决了路径和actionbean的映射问题。接下来的processMethods(clazz, classMappings);就是如何把actionbean和它的所有的事件方法对应起来。 protected void processMethods(Class clazz, Map<String,Method> classMappings) { // Do the super class first if there is one Class superclass = clazz.getSuperclass(); if (superclass != null) { processMethods(superclass, classMappings); } Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if ( Modifier.isPublic(method.getModifiers()) ) { String eventName = getHandledEvent(method); DefaultHandler defaultMapping = method.getAnnotation(DefaultHandler.class); if (eventName != null) { classMappings.put(eventName, method); } if (defaultMapping != null) { // Makes sure we catch the default handler classMappings.put(DEFAULT_HANDLER_KEY, method); } } } } 这个一个递归方法,它实现了从actionbean所有父类开始找标识为@DefaultHandler和DefaultHandler的处理方法。对于基于命名的方法, public String getHandledEvent(Method handler) { String name = super.getHandledEvent(handler); if ( name == null && !Modifier.isAbstract(handler.getModifiers()) &&R esolution.class.isAssignableFrom(handler.getReturnType()) ) { name = handler.getName(); } return name; }
它是在解析事件方法时,如果在基于 annotion 没有找到,就找返回类型为 Resolution 的非抽象的方法同名方法。处理事件方法一般不会有参数的。 这样就 actionResolver 就找到与路径对应的 actionbean 类了,并初始化了类与处事事件方法的关系。