一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

spring MVC工作机制与设计模式总结

时间:2015-07-19 编辑:简简单单 来源:一聚教程网

spring MVC的总体设计

在一个工程中如果想要使用 spring MVC的话,只需要两个步骤

    在web.xml中配置一个DispatcherServlet。

需要配置一个org.springframework.web.servlet.DispatcherServlet的servlet。

    再定义一个dispatcherServlet-servlet.xml配置文件。

在这个配置文件里面我们只需要扩展一个路径映射关系,定义一个视图解析器,再定义一个业务逻辑的处理流程规则。

这样就可以搞定一个最基本的Spring MVC的应用了。


对于DispatcherServlet初始化的时候初始了哪些东西,这些可以在initStrategies中看到。

/**
     * Initialize the strategy objects that this servlet uses.
     *

May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        //初始化MultipartResolver,主要是处理文件上传服务。
                initMultipartResolver(context);
                //用于处理应用的国际化问题
        initLocaleResolver(context);
                //用于定义一个主题
        initThemeResolver(context);
                //用于定义用户设置的请求映射关系
        initHandlerMappings(context);
                //用于根据Handler的类型定义不同的处理规则
        initHandlerAdapters(context);
                //当Handler处理错误的时候,通过这个handler来做统一的处理
        initHandlerExceptionResolvers(context);
                //将指定的ViewName按照定义的RequestToViewNameTranslator替换成想要的格式。
        initRequestToViewNameTranslator(context);
                //用于将view解析成页面
        initViewResolvers(context);
                //用于映射flash管理的。
        initFlashMapManager(context);
    }


小结: 对于spring MVC框架中,有三个组件是用户必须定义和扩展的:

    定义URL映射规则:handlerMapping
    实现业务逻辑的handler实例对象:handlerAdapter
    渲染模版资源:ViewResolver


看看DispatcherServlet启用的时候做了哪些工作?

    调用HttpServletBean的init()方法。在init()方法中,创建了BeanWrapper对象,并且执行了initServletBean()方法;

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // Set bean properties from init parameters.
    try {
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // Let subclasses do whatever initialization they like.
    initServletBean();

    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

调用FrameworkServlet的createWebApplicationContext方法,初始化spring容器。
调用FrameworkServlet的onRefresh方法完成配置文件的加载,配置文件的加载同样是先查找servlet的init-param参数中设置的路径,如果没有,就会根据namespace+Servlet的名称来查找XML文件。
Spring容器加载的时候会调用DispatcherServlet的initStrategies方法来初始化springMVC框架所需要的八个组件,这八个组件对应的八个bean对应都保存在DispatcherServlet类中。

这样:spring MVC的初始化工作就完成了。这样就可以接受你的http请求了。


 Control设计

Spring MVC 的Control主要是由HandlerMapping(interface)和HandlerAdapter(interface)两个组件提供。

    org.springframework.web.servlet.HandlerMapping
    org.springframework.web.servlet.HandlerAdapter

对于HandlerMapping:主要负责映射用户的URL和对应的处理类,HandlerMapping并没有规定这个URL与应用的处理类如何映射,HandlerMapping接口中只定义了根据一个URL必须返回一个由HandlerExceptionChain代表的处理链,我们可以在这个处理链中添加任意的HandlerAdapter实例来处理这个URL对应的请求,这个设计思路和Servlet规范中的Filter处理是类似的。

/**
     * Return a handler and any interceptors for this request. The choice may be made
     * on request URL, session state, or any factor the implementing class chooses.
     *

The returned HandlerExecutionChain contains a handler Object, rather than
     * even a tag interface, so that handlers are not constrained in any way.
     * For example, a HandlerAdapter could be written to allow another framework's
     * handler objects to be used.
     *

Returns null if no match was found. This is not an error.
     * The DispatcherServlet will query all registered HandlerMapping beans to find
     * a match, and only decide there is an error if none can find a handler.
     * @param request current HTTP request
     * @return a HandlerExecutionChain instance containing handler object and
     * any interceptors, or null if no mapping found
     * @throws Exception if there is an internal error
     */
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

HandlerMapping初始化

Spring MVC本身也提供了很多HandlerMapping 的实现,默认使用的是BeanNameUrlHandlerMapping,也可以根据Bean的name属性映射到URL中。

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
 
    /**
     * Checks name and aliases of the given bean for URLs, starting with "/".
     */
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List urls = new ArrayList();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = getApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }
 
}


对于HandlerMapping,可以帮助我们管理URL和处理类的映射关系,简单的说就是可以帮助我们将一个或者多个URL映射到一个或者多个spring Bean中。

下面总结下spring MVC是如何将请求的URL映射到我们定义的bean中的。

对于HandlerMapping是如何初始化的。spring MVC提供了一个HandlerMapping的抽象类 AbstractHandlerMapping。

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered {
}
可以通过让HandlerMapping设置setOrder方法提高优先级和通过覆盖initApplicationContext方法实现初始化的工作。

@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.mappedInterceptors);
    initInterceptors();
}


对于SimpleUrlHandlerMapping类是如何初始化的:


首先调用ApplicationObjectSupport的setApplicationContext()方法。


public final void setApplicationContext(ApplicationContext context) throws BeansException {
        if (context == null && !isContextRequired()) {
            // Reset internal context state.
            this.applicationContext = null;
            this.messageSourceAccessor = null;
        }
        else if (this.applicationContext == null) {
            // Initialize with passed-in context.
            if (!requiredContextClass().isInstance(context)) {
                throw new ApplicationContextException(
                        "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
            }
            this.applicationContext = context;
            this.messageSourceAccessor = new MessageSourceAccessor(context);
            initApplicationContext(context);
        }
        else {
            // Ignore reinitialization if same context passed in.
            if (this.applicationContext != context) {
                throw new ApplicationContextException(
                        "Cannot reinitialize with different application context: current one is [" +
                        this.applicationContext + "], passed-in one is [" + context + "]");
            }
        }
    }




    调用SimpleUrlHandlerMapping的initApplicationContext()方法


/**
     * Calls the {@link #registerHandlers} method in addition to the
     * superclass's initialization.
     */
    @Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }


    调用super.initApplicationContext(),调用了AbstractHandlerMapping的initApplicationContext()方法

/**
     * Initializes the interceptors.
     * @see #extendInterceptors(java.util.List)
     * @see #initInterceptors()
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.mappedInterceptors);
        initInterceptors();
    }


初始化initInterceptors()方法:将SimpleUrlHandlerMapping中定义的interceptors包装成handlerInterceptor对象保存在adaptedInterceptors数组中。


    SimpleUrlHandlerMapping继续执行registerHandlers(this.urlMap)方法;

在方法registerHandlers中,调用了AbstractUrlHandlerMapping的registerHandler(url, handler)方法;

在方法registerHandler(url, handler)中,将在SimpleUrlHandlerMapping中定义的mappings注册到handlerMap集合中。

protected void registerHandlers(Map urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }


在registerHandler方法中;

/**
 * Register the specified handler for the given URL path.
 * @param urlPath the URL the bean should be mapped to
 * @param handler the handler instance or handler bean name String
 * (a bean name will automatically be resolved into the corresponding handler bean)
 * @throws BeansException if the handler couldn't be registered
 * @throws IllegalStateException if there is a conflicting handler registered
 */


小结: HandlerMapping初始化工作完成的两个最重要的工作就是:

    将URL与Handler的对应关系保存在HandlerMapping集合中,并将所有的interceptors对象保存在adaptedInterceptors数组中,等请求到来的时候执行所有的adaptedIntercoptors数组中的interceptor对象。
    所有的interceptor必须实现HandlerInterceptor接口。



HandlerAdapter初始化

HandlerMapping 可以完成URL与Handler的映射关系,那么HandlerAdapter就可以帮助自定义各种handler了。因为SpringMVC首先帮助我们把特别的URL对应到一个Handler,那么这个Handler必定要符合某种规则,最常见的方法就是我们的所有handler都继承某个接口,然后SpringMVC 自然就调用这个接口中定义的特性方法。

对于spring MVC提供了三种典型的handlerAdapter实现类。

    SimpleServletHandlerAdapter:可以直接继承Servlet接口。
    SimpleControllerHandlerAdapter:可以继承Controller接口。
    SimpleRequestHandlerAdapter:可以继承HttpRequsetHandler接口。

对于handlerAdapter的初始化没有什么特别之处,只是简单的创建一个handlerAdapter对象,将这个对象保存在DispatcherServlet的HandlerAdapters集合中。当Spring MVC将某个URL对应到某个Handler时候,在handlerAdapters集合中查询那个handlerAdapter对象supports这个Handler,handlerAdapter对象将会被返回,用了调用这个handlerAdapter接口对应的方法。


Control的调用逻辑

整个Spring MVC的调用是从DispatcherServlet的doService方法开始的,在doService方法中会将ApplicationContext、localeResolver、themeResolver等对象添加到request中便于在后面使用,接着就调用doDispatch方法,这个方法是主要的处理用户请求的地方。


 Control的调用

对于control的处理关键就是:DispatcherServlet的handlerMappings集合中根据请求的URL匹配每一个handlerMapping对象中的某个handler,匹配成功之后将会返回这个handler的处理连接handlerExecutionChain对象。而这个handlerExecutionChain对象中将会包含用户自定义的多个handlerInterceptor对象。

/**
     * Return the HandlerExecutionChain for this request.
     *

Tries all handler mappings in order.
     * @param request current HTTP request
     * @return the HandlerExecutionChain, or null if no handler could be found
     */
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }


而对于handlerInterceptor接口中定义的三个方法中,preHandler和postHandler分别在handler的执行前和执行后执行,afterCompletion在view渲染完成、在DispatcherServlet返回之前执行。

PS:这么我们需要注意的是:当preHandler返回false时,当前的请求将在执行完afterCompletion后直接返回,handler也将不会执行。

在类HandlerExecutionChain中的getHandler()方法是返回object对象的;

/**
     * Return the handler object to execute.
     * @return the handler object
     */
    public Object getHandler() {
        return this.handler;
    }


这里的handler是没有类型的,handler的类型是由handlerAdapter决定的。dispatcherServlet会根据handler对象在其handlerAdapters集合中匹配哪个HandlerAdapter实例支持该对象。接下来去执行handler对象的相应方法了,如果该handler对象的相应方法返回一个ModelAndView对象接下来就是去执行View渲染了。

/**
     * Return the handler object to execute.
     * @return the handler object
     */
    public Object getHandler() {
        return this.handler;
    }


Model设计

如果handler兑现返回了ModelAndView对象,那么说明Handler需要传一个Model实例给view去渲染模版。除了渲染页面需要model实例,在业务逻辑层通常也有Model实例。


ModelAndView对象是连接业务逻辑层与view展示层的桥梁,对spring MVC来说它也是连接Handler与view的桥梁。ModelAndView对象顾名思义会持有一个ModelMap对象和一个View对象或者View的名称。ModelMap对象就是执行模版渲染时候所需要的变量对应的实例,如jsp的通过request.getAttribute(String)获取的JSTL标签名对应的对象。velocity中context.get(String)获取$foo对应的变量实例。

public class ModelAndView {
 
/** View instance or view name String */
    private Object view;
 
    /** Model Map */
    private ModelMap model;
 
    /** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
    private boolean cleared = false;
 
.....
 
}

ModelMap其实也是一个Map,Handler中将模版中需要的对象存在这个Map中,然后传递到view对应的ViewResolver中。

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
 
}

不同的ViewResolver会对这个Map中的对象有不同的处理方式;

    velocity中将这个Map保存到VelocityContext中。
    JSP中将每一个ModelMap中的元素分别设置到request.setAttribute(modelName,modelValue);


view设计

在spring MVC中,view模块需要两个组件来支持:RequestToViewNameTranslator和ViewResolver

public interface RequestToViewNameTranslator {
 
    /**
     * Translate the given {@link HttpServletRequest} into a view name.
     * @param request the incoming {@link HttpServletRequest} providing
     * the context from which a view name is to be resolved
     * @return the view name (or null if no default found)
     * @throws Exception if view name translation fails
     */
    String getViewName(HttpServletRequest request) throws Exception;
 
}


对于 ViewResolver,前面有写到了,就不写了;


RequestToViewNameTranslator:主要支持用户自定义对viewName的解析,如将请求的ViewName加上前缀或者后缀,或者替换成特定的字符串等。

ViewResolver:主要是根据用户请求的viewName创建适合的模版引擎来渲染最终的页面,ViewResolver会根据viewName创建一个view对象,调用view对象的Void render方法渲染出页面;

public interface View {
void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}


下面来总结下 Spring MVC解析View的逻辑:

    dispatcherServlet方法调用getDefaultViewName()方法;

/**
     * Translate the supplied request into a default view name.
     * @param request current HTTP servlet request
     * @return the view name (or null if no default found)
     * @throws Exception if view name translation failed
     */
    protected String getDefaultViewName(HttpServletRequest request) throws Exception {
        return this.viewNameTranslator.getViewName(request);
    }

    调用了RequestToViewNameTranslator的getViewName方法;


public interface RequestToViewNameTranslator {
 
    /**
     * Translate the given {@link HttpServletRequest} into a view name.
     * @param request the incoming {@link HttpServletRequest} providing
     * the context from which a view name is to be resolved
     * @return the view name (or null if no default found)
     * @throws Exception if view name translation fails
     */
    String getViewName(HttpServletRequest request) throws Exception;
 
}

    调用LocaleResolver接口的resolveLocale方法;


Locale resolveLocale(HttpServletRequest request);

    调用ViewResolver接口的resolveViewName方法,返回view对象

View resolveViewName(String viewName, Locale locale) throws Exception;

    调用render方法渲染出页面

热门栏目