SpringMVC源码解析之配置解析篇

阅读须知

  • Spring源码版本:4.3.8
  • 文章中使用/* */注释的方法会做深入分析

正文

我们在使用SpringMVC时首先要配置web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

在web.xml中我们配置了 ContextLoaderListener,我们就以这个类作为入口分析 SpringMVC 的源码实现。ContextLoaderListener 实现了 ServletContextListener,我们知道在 servlet 容器启动之后会调用它的 contextInitialized 方法,我们来看ContextLoaderListener的 contextInitialized 方法的实现:

public void contextInitialized(ServletContextEvent event) {
	/* 初始化 web 应用上下文 */
    initWebApplicationContext(event.getServletContext());
}

ContextLoader:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        // 在 web.xml 中配置了多个 ContextLoader 会抛异常
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }
    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    // 记录初始化开始时间
    long startTime = System.currentTimeMillis();
    try {
        if (this.context == null) {
            /* 创建 web 应用上下文 */
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            // 如果没有激活
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 如果配置了 parentContextKey,则设置父级上下文
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                /* 配置并刷新上下文 */
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 记录到 ServiceContext 中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
        	// 计算并打印初始化耗时
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }
        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        // 初始化失败记录到异常
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); // 初始化失败记录到异常
        throw err;
    }
}

ContextLoader:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    Class<?> contextClass = determineContextClass(sc); /* 寻找上下文类 */
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 反射初始化上下文对象
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

ContextLoader:

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 如果用户配置了 contextClass,则使用用户自定义的上下文类
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        /* 没有自定义配置上下文类则使用默认的配置 */
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

ContextLoader:

private static final Properties defaultStrategies;

static {
    try {
        /* 在静态块中 defaultStrategies 加载了 ContextLoader.properties 配置文件 */
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    }
}

在ContextLoader.properties中的配置内容:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

也就是说默认的上下文类为 XmlWebApplicationContext,它实现了 ConfigurableWebApplicationContext,初始化对象之后并没有激活,所以下面会配置并刷新上下文:
ContextLoader:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        // 如果用户自定义了 contextId 配置,则使用用户自定义的contextId
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            // 没有自定义则生成默认的 contextId
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        // 将我们配置的 contextConfigLocation 设置到上下文对象中
        wac.setConfigLocation(configLocationParam);
    }
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        // 为环境类初始化一些属性,主要为其设置 ServletContext 和ServletConfig
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
    // 找到用户配置的 globalInitializerClasses 和 contextInitializerClasses
    // 两个配置的类需要实现 ApplicationContextInitializer,并且按照 @Order 注解进行排序调用 initialize 方法
    // 当我们需要对应用程序上下文进行一些编程初始化时可以使用它,例如根据上下文环境注册属性源或激活配置文件
    customizeContext(sc, wac); 
    // 刷新,我们之前标签解析的文章都是在介绍这个刷新的过程,这里就不重复了
    wac.refresh();
}

这里最后的刷新操作我们在之前的文章中用了大篇幅来介绍,其中有一步调用了postProcessBeanFactory方法,默认是空实现,而XmlWebApplicationContext的在初始化过程中覆盖了这个方法:
AbstractRefreshableWebApplicationContext:

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 添加 ServletContextAwareProcessor,用于为实现 ServletContextAware、ServletConfigAware 两个接口的 bean 设置相关资源
    beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
    // 添加两个忽略自动装配的接口
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
    /* 注册几个自定义的 scope,添加几个自动装配的接口 */
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
    /* 注册几个环境 bean */
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

WebApplicationContextUtils:

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
    // 注册几个自定义的 scope
    beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
    beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
    if (sc != null) {
        ServletContextScope appScope = new ServletContextScope(sc);
        beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
        sc.setAttribute(ServletContextScope.class.getName(), appScope);
    }
    // 添加几个自动装配的接口
    beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
    beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
    beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
    beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
    if (jsfPresent) {
        FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
    }
}

WebApplicationContextUtils:

public static void registerEnvironmentBeans(
        ConfigurableListableBeanFactory bf, ServletContext servletContext, ServletConfig servletConfig) {
    if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
    	// 注册 servletContext
        bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
    }
    if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
    	// 注册 servletConfig
        bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
    }
    if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
        Map<String, String> parameterMap = new HashMap<String, String>();
        if (servletContext != null) {
            Enumeration<?> paramNameEnum = servletContext.getInitParameterNames();
            while (paramNameEnum.hasMoreElements()) {
                String paramName = (String) paramNameEnum.nextElement();
                parameterMap.put(paramName, servletContext.getInitParameter(paramName));
            }
        }
        if (servletConfig != null) {
            Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames();
            while (paramNameEnum.hasMoreElements()) {
                String paramName = (String) paramNameEnum.nextElement();
                parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
            }
        }
        // 注册 contextParameters
        bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
                Collections.unmodifiableMap(parameterMap));
    }
    if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
        Map<String, Object> attributeMap = new HashMap<String, Object>();
        if (servletContext != null) {
            Enumeration<?> attrNameEnum = servletContext.getAttributeNames();
            while (attrNameEnum.hasMoreElements()) {
                String attrName = (String) attrNameEnum.nextElement();
                attributeMap.put(attrName, servletContext.getAttribute(attrName));
            }
        }
        // 注册 contextAttributes
        bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
                Collections.unmodifiableMap(attributeMap));
    }
}

到这里上下文环境就初始化完了,我们在 web.xml 文件中还配置了 DispatcherServlet,它继承了 HttpServlet,我们知道,在 servlet 生命周期的初始化阶段,会调用它的 init 方法,我们在其父类HttpServletBean中找到了 init 方法的实现:

public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }
    try {
        // 解析 init-param 并封装到 PropertyValues 对象中,requiredProperties 用于配置一些必须的属性,如果检测到其中的属性没有设置值,就会抛出异常
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        // 创建 BeanWrapper
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        // 注册自定义属性编辑器,遇到 Resource 类型的属性将使用 ResourceEditor 进行解析
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        // 留给子类扩展对 BeanWrapper 的个性化初始化操作,默认空实现
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        }
        throw ex;
    }
    /* 子类实现初始化 */
    initServletBean();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

FrameworkServlet:

protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    // 记录开始时间
    long startTime = System.currentTimeMillis();
    try {
    	/* 初始化web应用上下文 */
        this.webApplicationContext = initWebApplicationContext();
        // 默认空实现,留给子类扩展一些自定义的初始化需求
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    if (this.logger.isInfoEnabled()) {
    	// 计算并打印初始化耗时
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

FrameworkServlet:

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    // 这个 if 判断内的初始化逻辑,在上文已经介绍过
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 如果上下文为空则尝试从用户配置的 contextAttribute 属性加载 WebApplicationContext,这里要保证 WebApplicationContext 已经加载并存入 ServiceContext 中
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果到这里上下文还是为空则创建本地默认的 XmlWebApplicationContext,创建和初始化过程与上文讲述的过程非常相似
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
    	/* 默认子类实现初始化一些 servlet 需要的对象 */
        onRefresh(wac);
    }
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        // 将上下文对象设置到 ServletContext 的属性中
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }
    return wac;
}

这里我们发现了另外一个创建应用上下文的分支,在文章开始的这种配置情况下,会进入这个判断,也就是说这里会调用createWebApplicationContext方法创建了另外一个上下文,而之前创建的上下文会作为它的父级上下文。
DispatcherServlet:

protected void onRefresh(ApplicationContext context) {
	/* 初始化一些 servlet 需要的对象 */
    initStrategies(context);
}

DispatcherServlet:

protected void initStrategies(ApplicationContext context) {
	/* 初始化 MultipartResolver */
    initMultipartResolver(context);
    /* 初始化 LocaleResolver */
    initLocaleResolver(context);
    /* 初始化 ThemeResolver */
    initThemeResolver(context);
    /* 初始化 HandlerMapping */
    initHandlerMappings(context);
    /* 初始化 HandlerAdapter */
    initHandlerAdapters(context);
    /* 初始化 HandlerExceptionResolver */
    initHandlerExceptionResolvers(context);
    /* 初始化 RequestToViewNameTranslator */
    initRequestToViewNameTranslator(context);
    /* 初始化 ViewResolver */
    initViewResolvers(context);
    /* 初始化 FlashMapManager */
    initFlashMapManager(context);
}

DispatcherServlet:

private void initMultipartResolver(ApplicationContext context) {
    try {
        // 获取到对应 bean 并赋值给对应的属性,MultipartResolver 主要用于解析文件上传的参数
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        this.multipartResolver = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                    "': no multipart request handling provided");
        }
    }
}

DispatcherServlet:

private void initLocaleResolver(ApplicationContext context) {
    try {
        // 获取到对应 bean 并赋值给对应的属性,LocaleResolver 主要用于支持国际化处理
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 没有则使用默认的策略
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                    "': using default [" + this.localeResolver + "]");
        }
    }
}

DispatcherServlet:

private void initThemeResolver(ApplicationContext context) {
    try {
        // 获取到对应bean并赋值给对应的属性,ThemeResolver 主要用于控制网页风格的主题(比如静态资源,css 或图片等)
        this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 没有则使用默认的策略
        this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
                    "': using default [" + this.themeResolver + "]");
        }
    }
}

DispatcherServlet:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    // 默认为 true
    if (this.detectAllHandlerMappings) {
    	// 加载所有 HandlerMapping 类型的bean,HandlerMapping 的作用在后文会详细说明
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
        	// 设置到 handlerMappings 属性中
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // 按照 @Order 注解排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings); 
        }
    }
    else { 
    	// 如果不想加载所有的 HandlerMapping 类型 bean,只希望加载指定的,可以在 web.xml 的 init-param 配置中将 detectAllHandlerMappings 设置为 false
        try {
            // 获取到指定的 bean 并赋值给对应的属性
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }
    if (this.handlerMappings == null) {
        // 没有则使用默认的策略
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

DispatcherServlet:

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    // 默认为 true
    if (this.detectAllHandlerAdapters) {
    	// 加载所有 HandlerAdapter 类型的 bean,HandlerAdapter 的作用在后文会详细说明
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
        	// 设置到 handlerAdapters 属性中
            this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
            // 按照 @Order 注解排序
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
    	// 如果不想加载所有的 HandlerAdapter 类型 bean,只希望加载指定的,可以在 web.xml 的 init-param 配置中将 detectAllHandlerAdapters 设置为 false
        try {
            // 获取到指定的 bean 并赋值给对应的属性
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }
    if (this.handlerAdapters == null) {
        // 没有则使用默认的策略
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
        }
    }
}

DispatcherServlet:

private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;
    // 默认为 true
    if (this.detectAllHandlerExceptionResolvers) {
    	// 加载所有 HandlerExceptionResolver 类型的 bean,HandlerExceptionResolver 的作用在后文会详细说明
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
        	// 设置到 handlerExceptionResolvers 属性中
            this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
            // 按照 @Order 注解排序
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
    	// 如果不想加载所有的 HandlerExceptionResolver 类型 bean,只希望加载指定的,可以在 web.xml 的 init-param 配置中将 detectAllHandlerExceptionResolvers 设置为 false
        try {
            // 获取到指定的 bean 并赋值给对应的属性
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }
    if (this.handlerExceptionResolvers == null) {
        // 没有则使用默认的策略
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

DispatcherServlet:

private void initRequestToViewNameTranslator(ApplicationContext context) {
    try {
        // 获取到对应bean并赋值给对应的属性,默认情况下会翻译路径返回一个视图
        this.viewNameTranslator =
                context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 没有则使用默认的策略
        this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
                    REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "': using default [" + this.viewNameTranslator +
                    "]");
        }
    }
}

DispatcherServlet:

private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;
    // 默认为 true
    if (this.detectAllViewResolvers) {
    	// 加载所有 ViewResolver 类型的 bean,ViewResolver 的作用在后文会详细说明
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
        	// 设置到 viewResolvers 属性中
            this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
            // 按照 @Order 注解排序
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
    	// 如果不想加载所有的 ViewResolver 类型 bean,只希望加载指定的,可以在 web.xml 的 init-param 配置中将 detectAllViewResolvers 设置为 false
        try {
            // 获取到指定的 bean 并赋值给对应的属性
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }
    if (this.viewResolvers == null) {
        // 没有则使用默认的策略
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

DispatcherServlet:

private void initFlashMapManager(ApplicationContext context) {
    try {
        // 获取到对应的 bean 并赋值给对应的属性,FlashMapManager 的作用主要是提供了一个请求存储属性,可供其他请求使用,例如重定向之后还能使用重定向之前暂存的属性
        this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 没有则使用默认的策略
        this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate FlashMapManager with name '" +
                    FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]");
        }
    }
}

在上面的初始化过程中几乎都有一个默认的初始化策略,默认的初始化策略会加载 DispatcherServlet.properties 配置文件,并创建对应的 bean 返回,我们来看一下其中的配置:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

下篇文章我们会介绍 Spring MVC 的执行过程和这些默认配置在其中的应用。

弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件。本文主要介绍使用注解方式配置spring mvc,之前写的spring3.0 mvc和rest小例子没有介绍到数据层的内容,现在这一补上。下面开始贴代码。 文中用的框架版本:spring 3,hibernate 3,没有的,自己上网下。 先说web.xml配置: [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 03. <display-name>s3h3</display-name> 04. <context-param> 05. <param-name>contextConfigLocation</param-name> 06. <param-value>classpath:applicationContext*.xml</param-value> 07. </context-param> 08. <listener> 09. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10. </listener> 11. 12. <servlet> 13. <servlet-name>spring</servlet-name> 14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15. <load-on-startup>1</load-on-startup> 16. </servlet> 17. <servlet-mapping> 18. <servlet-name>spring</servlet-name> <!-- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --> 19. <url-pattern>*.do</url-pattern> 20. </servlet-mapping> 21. <welcome-file-list> 22. <welcome-file>index.jsp</welcome-file> 23. </welcome-file-list> 24.</web-app> spring-servlet,主要配置controller的信息 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02. <beans 03. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 04. xmlns:context="http://www.springframework.org/schema/context" 05. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 06. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 07. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 09. 10. <context:annotation-config /> 11. <!-- 把标记了@Controller注解的类转换为bean --> 12. <context:component-scan base-package="com.mvc.controller" /> 13. <!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 --> 14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 18. p:prefix="/WEB-INF/view/" p:suffix=".jsp" /> 19. 20. <bean id="multipartResolver" 21. class="org.springframework.web.multipart.commons.CommonsMultipartResolver" 22. p:defaultEncoding="utf-8" /> 23. </beans> applicationContext.xml代码 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<beans 03. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 04. xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" 05. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 06. xsi:schemaLocation=" 07. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 09. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 11. 12. <context:annotation-config /> 13. <context:component-scan base-package="com.mvc" /> <!-- 自动扫描所有注解该路径 --> 14. 15. <context:property-placeholder location="classpath:/hibernate.properties" /> 16. 17. <bean id="sessionFactory" 18. class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 19. <property name="dataSource" ref="dataSource" /> 20. <property name="hibernateProperties"> 21. <props> 22. <prop key="hibernate.dialect">${dataSource.dialect}</prop> 23. <prop key="hibernate.hbm2ddl.auto">${dataSource.hbm2ddl.auto}</prop> 24. <prop key="hibernate.hbm2ddl.auto">update</prop> 25. </props> 26. </property> 27. <property name="packagesToScan"> 28. <list> 29. <value>com.mvc.entity</value><!-- 扫描实体类,也就是平时所说的model --> 30. </list> 31. </property> 32. </bean> 33. 34. <bean id="transactionManager" 35. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 36. <property name="sessionFactory" ref="sessionFactory" /> 37. <property name="dataSource" ref="dataSource" /> 38. </bean> 39. 40. <bean id="dataSource" 41. class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 42. <property name="driverClassName" value="${dataSource.driverClassName}" /> 43. <property name="url" value="${dataSource.url}" /> 44. <property name="username" value="${dataSource.username}" /> 45. <property name="password" value="${dataSource.password}" /> 46. </bean> 47. <!-- Dao的实现 --> 48. <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> 49. <property name="sessionFactory" ref="sessionFactory" /> 50. </bean> 51. <tx:annotation-driven transaction-manager="transactionManager" /> 52. <tx:annotation-driven mode="aspectj"/> 53. 54. <aop:aspectj-autoproxy/> 55.</beans> hibernate.properties数据库连接配置 [java] view plaincopy 01.dataSource.password=123 02.dataSource.username=root 03.dataSource.databaseName=test 04.dataSource.driverClassName=com.mysql.jdbc.Driver 05.dataSource.dialect=org.hibernate.dialect.MySQL5Dialect 06.dataSource.serverName=localhost:3306 07.dataSource.url=jdbc:mysql://localhost:3306/test 08.dataSource.properties=user=${dataSource.username};databaseName=${dataSource.databaseName};serverName=${dataSource.serverName};password=${dataSource.password} 09.dataSource.hbm2ddl.auto=update 配置已经完成,下面开始例子 先在数据库建表,例子用的是mysql数据库 [java] view plaincopy 01.CREATE TABLE `test`.`student` ( 02. `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 03. `name` varchar(45) NOT NULL, 04. `psw` varchar(45) NOT NULL, 05. PRIMARY KEY (`id`) 06.) 建好表后,生成实体类 [java] view plaincopy 01.package com.mvc.entity; 02. 03.import java.io.Serializable; 04. 05.import javax.persistence.Basic; 06.import javax.persistence.Column; 07.import javax.persistence.Entity; 08.import javax.persistence.GeneratedValue; 09.import javax.persistence.GenerationType; 10.import javax.persistence.Id; 11.import javax.persistence.Table; 12. 13.@Entity 14.@Table(name = "student") 15.public class Student implements Serializable { 16. private static final long serialVersionUID = 1L; 17. @Id 18. @Basic(optional = false) 19. @GeneratedValue(strategy = GenerationType.IDENTITY) 20. @Column(name = "id", nullable = false) 21. private Integer id; 22. @Column(name = "name") 23. private String user; 24. @Column(name = "psw") 25. private String psw; 26. public Integer getId() { 27. return id; 28. } 29. public void setId(Integer id) { 30. this.id = id; 31. } 32. 33. public String getUser() { 34. return user; 35. } 36. public void setUser(String user) { 37. this.user = user; 38. } 39. public String getPsw() { 40. return psw; 41. } 42. public void setPsw(String psw) { 43. this.psw = psw; 44. } 45.} Dao层实现 [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.public interface EntityDao { 06. public List<Object> createQuery(final String queryString); 07. public Object save(final Object model); 08. public void update(final Object model); 09. public void delete(final Object model); 10.} [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.import org.hibernate.Query; 06.import org.springframework.orm.hibernate3.HibernateCallback; 07.import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 08. 09.public class EntityDaoImpl extends HibernateDaoSupport implements EntityDao{ 10. public List<Object> createQuery(final String queryString) { 11. return (List<Object>) getHibernateTemplate().execute( 12. new HibernateCallback<Object>() { 13. public Object doInHibernate(org.hibernate.Session session) 14. throws org.hibernate.HibernateException { 15. Query query = session.createQuery(queryString); 16. List<Object> rows = query.list(); 17. return rows; 18. } 19. }); 20. } 21. public Object save(final Object model) { 22. return getHibernateTemplate().execute( 23. new HibernateCallback<Object>() { 24. public Object doInHibernate(org.hibernate.Session session) 25. throws org.hibernate.HibernateException { 26. session.save(model); 27. return null; 28. } 29. }); 30. } 31. public void update(final Object model) { 32. getHibernateTemplate().execute(new HibernateCallback<Object>() { 33. public Object doInHibernate(org.hibernate.Session session) 34. throws org.hibernate.HibernateException { 35. session.update(model); 36. return null; 37. } 38. }); 39. } 40. public void delete(final Object model) { 41. getHibernateTemplate().execute(new HibernateCallback<Object>() { 42. public Object doInHibernate(org.hibernate.Session session) 43. throws org.hibernate.HibernateException { 44. session.delete(model); 45. return null; 46. } 47. }); 48. } 49.} Dao在applicationContext.xml注入 <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Dao只有一个类的实现,直接供其它service层调用,如果你想更换为其它的Dao实现,也只需修改这里的配置就行了。 开始写view页面,WEB-INF/view下新建页面student.jsp,WEB-INF/view这路径是在spring-servlet.xml文件配置的,你可以配置成其它,也可以多个路径。student.jsp代码 [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>添加</title> 09.<mce:script language="javascript" src="<%=request.getContextPath()%><!-- 10./script/jquery.min.js"> 11.// --></mce:script> 12.<mce:style><!-- 13.table{ border-collapse:collapse; } 14.td{ border:1px solid #f00; } 15.--></mce:style><style mce_bogus="1">table{ border-collapse:collapse; } 16.td{ border:1px solid #f00; }</style> 17.<mce:script type="text/javascript"><!-- 18.function add(){ 19. [removed].href="<%=request.getContextPath() %>/student.do?method=add"; 20.} 21. 22.function del(id){ 23.$.ajax( { 24. type : "POST", 25. url : "<%=request.getContextPath()%>/student.do?method=del&id;=" + id, 26. dataType: "json", 27. success : function(data) { 28. if(data.del == "true"){ 29. alert("删除成功!"); 30. $("#" + id).remove(); 31. } 32. else{ 33. alert("删除失败!"); 34. } 35. }, 36. error :function(){ 37. alert("网络连接出错!"); 38. } 39.}); 40.} 41.// --></mce:script> 42.</head> 43.<body> 44. 45.<input id="add" type="button" value="添加"/> 46.<table > 47. <tr> 48. <td>序号</td> 49. <td>姓名</td> 50. <td>密码</td> 51. <td>操作</td> 52. </tr> 53. <c:forEach items="${list}" var="student"> 54. <tr id="<c:out value="${student.id}"/>"> 55. <td><c:out value="${student.id}"/></td> 56. <td><c:out value="${student.user}"/></td> 57. <td><c:out value="${student.psw}"/></td> 58. <td> 59. <input type="button" value="编辑"/> 60. <input type="button" value="${student.id}"/>')" value="删除"/> 61. </td> 62. </tr> 63. </c:forEach> 64. 65.</table> 66.</body> 67.</html> student_add.jsp [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>学生添加</title> 09.<mce:script type="text/javascript"><!-- 10.function turnback(){ 11. [removed].href="<%=request.getContextPath() %>/student.do"; 12.} 13.// --></mce:script> 14.</head> 15.<body> 16.<form method="post" action="<%=request.getContextPath() %>/student.do?method=save"> 17.<div><c:out value="${addstate}"></c:out></div> 18.<table> 19. <tr><td>姓名</td><td><input id="user" name="user" type="text" /></td></tr> 20. <tr><td>密码</td><td><input id="psw" name="psw" type="text" /></td></tr> 21. <tr><td colSpan="2" align="center"><input type="submit" value="提交"/><input type="button" value="返回" /> </td></tr> 22.</table> 23. 24.</form> 25.</body> 26.</html> controller类实现,只需把注解写上,spring就会自动帮你找到相应的bean,相应的注解标记意义,不明白的,可以自己查下@Service,@Controller,@Entity等等的内容。 [java] view plaincopy 01.package com.mvc.controller; 02. 03.import java.util.List; 04. 05.import javax.servlet.http.HttpServletRequest; 06.import javax.servlet.http.HttpServletResponse; 07. 08.import org.apache.commons.logging.Log; 09.import org.apache.commons.logging.LogFactory; 10.import org.springframework.beans.factory.annotation.Autowired; 11.import org.springframework.stereotype.Controller; 12.import org.springframework.ui.ModelMap; 13.import org.springframework.web.bind.annotation.RequestMapping; 14.import org.springframework.web.bind.annotation.RequestMethod; 15.import org.springframework.web.bind.annotation.RequestParam; 16.import org.springframework.web.servlet.ModelAndView; 17. 18.import com.mvc.entity.Student; 19.import com.mvc.service.StudentService; 20. 21.@Controller 22.@RequestMapping("/student.do") 23.public class StudentController { 24. protected final transient Log log = LogFactory 25. .getLog(StudentController.class); 26. @Autowired 27. private StudentService studentService; 28. public StudentController(){ 29. 30. } 31. 32. @RequestMapping 33. public String load(ModelMap modelMap){ 34. List<Object> list = studentService.getStudentList(); 35. modelMap.put("list", list); 36. return "student"; 37. } 38. 39. @RequestMapping(params = "method=add") 40. public String add(HttpServletRequest request, ModelMap modelMap) throws Exception{ 41. return "student_add"; 42. } 43. 44. @RequestMapping(params = "method=save") 45. public String save(HttpServletRequest request, ModelMap modelMap){ 46. String user = request.getParameter("user"); 47. String psw = request.getParameter("psw"); 48. Student st = new Student(); 49. st.setUser(user); 50. st.setPsw(psw); 51. try{ 52. studentService.save(st); 53. modelMap.put("addstate", "添加成功"); 54. } 55. catch(Exception e){ 56. log.error(e.getMessage()); 57. modelMap.put("addstate", "添加失败"); 58. } 59. 60. return "student_add"; 61. } 62. 63. @RequestMapping(params = "method=del") 64. public void del(@RequestParam("id") String id, HttpServletResponse response){ 65. try{ 66. Student st = new Student(); 67. st.setId(Integer.valueOf(id)); 68. studentService.delete(st); 69. response.getWriter().print("{/"del/":/"true/"}"); 70. } 71. catch(Exception e){ 72. log.error(e.getMessage()); 73. e.printStackTrace(); 74. } 75. } 76.} service类实现 [java] view plaincopy 01.package com.mvc.service; 02. 03.import java.util.List; 04. 05.import org.springframework.beans.factory.annotation.Autowired; 06.import org.springframework.stereotype.Service; 07.import org.springframework.transaction.annotation.Transactional; 08. 09.import com.mvc.dao.EntityDao; 10.import com.mvc.entity.Student; 11. 12.@Service 13.public class StudentService { 14. @Autowired 15. private EntityDao entityDao; 16. 17. @Transactional 18. public List<Object> getStudentList(){ 19. StringBuffer sff = new StringBuffer(); 20. sff.append("select a from ").append(Student.class.getSimpleName()).append(" a "); 21. List<Object> list = entityDao.createQuery(sff.toString()); 22. return list; 23. } 24. 25. public void save(Student st){ 26. entityDao.save(st); 27. } 28. public void delete(Object obj){ 29. entityDao.delete(obj); 30. } 31.} OK,例子写完。有其它业务内容,只需直接新建view,并实现相应comtroller和service就行了,配置和dao层的内容基本不变,也就是每次只需写jsp(view),controller和service调用dao就行了。 怎样,看了这个,spring mvc是不是比ssh实现更方便灵活。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值