首先当然是从配置文件入手。先看看 web.xml.
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
- listener>
这句的作用是初始化 applicationContext, 默认使用的配置文件就是 applicationContext.xml
- <servlet>
- <servlet-name>countriesservlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
- <load-on-startup>2load-on-startup>
- servlet>
- <servlet-mapping>
- <servlet-name>countriesservlet-name>
- <url-pattern>*.htmurl-pattern>
- servlet-mapping>
- <servlet-mapping>
- <servlet-name>countriesservlet-name>
- <url-pattern>*.pdfurl-pattern>
- servlet-mapping>
- <servlet-mapping>
- <servlet-name>countriesservlet-name>
- <url-pattern>*.xlsurl-pattern>
- servlet-mapping>
这些代码就是配置servlet,非常熟悉不过了。通过这样的配置,所有的请求都由org.springframework.web.servlet.DispatcherServlet 来处理。
接下来看看 applicationContext.xml.
- <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
- <property name="basename" value="messages"/>
- bean>
- <bean id="countryService" class="org.springframework.samples.countries.DefaultCountryService"/>
messageSource 是spring国际化的方式。 在这个例子里,spring 会在classpath 里寻找 messages_en.properties, messages_zh_cn.properties 等国际化的文件。一般这些文件都放到 /WEB-INF/classes 里,当然你放到别的也可以,
只要是在 classpath 里。
countryService 是一个 bean 了,可以看到它具体对应的class.
在 web.xml 里,我们配置了 servlet 的处理方式,那么处理那些请求的配置文件在哪呢?
在这个例子里,默认是 “countries-servlet.xml”。规则是什么呢?
如果定义servlet 的时候是如下这样:
- <servlet-name>countries<servlet-name>
那么默认的就是 “countries-servlet.xml”。也就是 servlet名字+"-servlet.xml" .
那么精化部分就应该都在 countries-servlet.xml 里了,看看这个配置文件吧。
- <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
- <property name="basename" value="views-countries"/>
- <property name="defaultParentView" value="modelView"/>
- bean>
这里用了spring 中的一种视图方式,关于视图的详细信息,请参考
http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-viewresolver
不过这里也简单说一下这个 ResourceBundleViewResolver.
它有2个属性。
basename : 指定了去读取哪个配置文件。在这个例子中,spring 会去读取 views-countries.properties, 如果有国际化的
的信息,还会去读取 views-countries_en.properties, views-countries_zh_CN.properties 等文件。
defaultParentView : 指定了视图的父类。这里的父类并不是java的class. 而是配置文件的继承。
多说无用,非常抽象,还是看看 views-countries.properties 文件来得实在。
- modelView.(class)=org.springframework.web.servlet.view.JstlView
- modelView.requestContextAttribute=rc
- modelView.url=/WEB-INF/views/jsp/countries/model.jsp
- #--------------------------------------------------------
- homeView.attributesCSV=htitle=[home.htitle],nav=[nav.jsp],content=[home.jsp]
- copyView.attributesCSV=htitle=[copy.htitle],nav=[nav.jsp],content=[copy.jsp]
- configView.attributesCSV=htitle=[config.htitle],nav=[nav.jsp],content=[config.jsp]
- countries_mainView.attributesCSV=htitle=[countries.main.htitle],\
- nav=[main/nav.jsp],\
- content=[main/home.jsp]
- countries_detailView.attributesCSV=htitle=[countries.detail.htitle],\
- nav=[main/detailnav.jsp],\
- content=[main/detail.jsp]
- countries_excelView.(class)=org.springframework.samples.countries.web.CountriesExcelView
- countries_pdfView.(class)=org.springframework.samples.countries.web.CountriesPdfView
- #--------------------------------------------------------
- errorHttp404View.attributesCSV=htitle=[error.http404.htitle],nav=[nav.jsp],content=[../errors/http404.jsp]
首先我们看到对 modelView 的定义,也就是 defaultParentView 的 value.
处理这个View的类是 org.springframework.web.servlet.view.JstlView
处理这个View的页面是 /WEB-INF/views/jsp/countries/model.jsp
请记住,这个 modelView 是下面所有其他的view的“父类”,也就是下面的view都是“继承”这些配置的。
举个例子来说,homeView.attributesCSV=htitle=[home.htitle],nav=[nav.jsp],content=[home.jsp]
处理这个 homeView 的类就是 org.springframework.web.servlet.view.JstlView
处理这个 homeView 的页面就是 /WEB-INF/views/jsp/countries/model.jsp
那我们是不是很疑惑,为什么所有的view的处理页面都是 /WEB-INF/views/jsp/countries/model.jsp ??
没错,看看 model.jsp , 其实就是一个模板!
- >
- <%@ include file="../common/includes.jsp" %>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title><fmt:message key="${htitle}"/>title>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
- <c:set var="css"><spring:theme code="css"/>c:set>
- <c:if test="${not empty css}"><link rel="stylesheet" href="
- head>
- <body>
- <div id="pageContent">
- <div id="inContent">
- <div id="nav"><c:import url="${nav}"/>div>
- <div id="top"><c:import url="top.jsp"/>div>
- <div id="content"><h1><fmt:message key="app.short"/>h1><c:import url="${content}"/>div>
- <div id="footer"><c:import url="${nav}"/>div>
- div>
- div>
- body>
- html>
具体的 htitle, content, 都是子类(如 homeView)里传递过去的。
(这部分比较头晕的是 父类,子类,继承,千万不要弄成java里的概念)
如何处理视图已经弄清楚了,那么继续往下看 countries-servlet.xml.
- <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
- <bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver">
- <property name="defaultThemeName" value="spring"/>
- bean>
这里定义了两个 Resolver bean, 关于这两个 Resolver 的详细信息,可以参考:
http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-localeresolver
http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-themeresolver
这里就不多解释了,相信上面的资料足够让您看懂。
在继续往下看:
- <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="interceptors">
- <list>
- <ref bean="localeChangeInterceptor"/>
- <ref bean="themeChangeInterceptor"/>
- list>
- property>
- <property name="mappings">
- <props>
- <prop key="/home.htm">countriesControllerprop>
- <prop key="/config.htm">countriesControllerprop>
- <prop key="/copy.htm">countriesControllerprop>
- <prop key="/main/home.htm">countriesControllerprop>
- <prop key="/main/detail.htm">countriesControllerprop>
- <prop key="/main/countries.xls">countriesControllerprop>
- <prop key="/main/countries.pdf">countriesControllerprop>
- <prop key="/notfound.htm">errorsControllerprop>
- props>
- property>
- bean>
这里定义了 url 的处理,例如 /home.htm 这个 url 会由 countriesController 这个类来处理。
不过这里还有 “interceptors”这个配置,这个叫做拦截器,详细信息可以参考
http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-handlermapping-interceptor
相信上面的信息足够让人明白拦截器是什么东东。
我们先看看这两个拦截器是如何定义的:
- <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
- <bean id="themeChangeInterceptor" class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
都是用的 spring 默认的拦截器。
好了,拦截器就到此为止。继续看看上面的 url 处理的定义。
我们可以看到,好多 url 都用的同一个 controller 来处理,那么这个 controller 该如何处理这些请求?
下意识的想法可能是在 controller 里有这样的代码:
- if ( url 来自 home,htm) {
- } else if (url 来自 copy.htm ) {
- } else {
- }
那就看看 spring 是如何做的吧.
- <bean id="errorsController" class="org.springframework.samples.countries.web.ErrorsController">
- <property name="methodNameResolver">
- <bean
- class="org.springframework.web.servlet.mvc.multiaction.Propertie
- sMethodNameResolver">
- <property name="mappings">
- <props>
- <prop key="/notfound.htm">handleHttp404</prop>
- </props>
- </property>
- </bean>
- </property>
- </bean>
- <!-- Application specific multi-action controller -->
- <bean id="countriesController"
- class="org.springframework.samples.countries.web.CountriesController">
- <property name="methodNameResolver">
- <bean
- class="org.springframework.web.servlet.mvc.multiaction.P
- ropertiesMethodNameResolver">
- <property name="mappings">
- <props>
- <prop key="/home.htm">handleHome</prop>
- <prop key="/config.htm">handleConfig</prop>
- <prop key="/copy.htm">handleCopy</prop>
- <prop key="/main/home.htm">handleMain</prop>
- <prop key="/main/detail.htm">handleDetail</prop>
- <prop
- key="/main/countries.xls">handleExcel
- </prop>
- <prop
- key="/main/countries.pdf">handlePdf
- </prop>
- </props>
- </property>
- </bean>
- </property>
- <property name="countryService" ref="countryService"/>
- </bean>
虽然这里定义了2个controller,但是都是一样的用法。
我们注意到这样的定义:
- "methodNameResolver">
- class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
- 。。。。 省略
不用说,这肯定是 spring 的一种用法了!
不过我们还是先看看 org.springframework.samples.countries.web.CountriesController 是怎么定义的吧:
- public class CountriesController extends MultiActionController
“MultiActionController”?从名字可以知道,应该是一个特殊的controller,专门处理多个action的!
可以参考这里 http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-controller-multiaction
相信上面的信息会给您满意的答案!
明白了 MultiActionController,我们也就知道了,这样的配置,意思是 对于 /home.htm 这样的url, 会调用 handleHome 这个方法。
- <prop key="/home.htm">handleHome<prop>
可见,如果一个 controller 处理多个 action,可以把被调用的方法的名字直接写到配置文件里。
到目前为止,我们还没看过这个例子里的java代码,但我想,我们对整个的结构,应该有一个大概的了解了。
那么现在就去看看 CountriesController.java .
先挑一个方法看看:
- public ModelAndView handleHome(HttpServletRequest request, HttpServletResponse response) throws ServletException {
- return new ModelAndView(homeView);
- }
简单不过的方法,这个 homeView 通过查代码知道是 “homeView”这个字符串。
那么整个流程我们现在应该十分清晰了:
系统得到一个 home.htm 的请求,spring 调用 CountriesController 的 handleHome 来处理,返回 "homeView" 构造
ModelAndView 对象。根据前面的 ResourceBundleViewResolver 的定义,系统会显示
/WEB-INF/views/jsp/countries/model.jsp 这个页面,不过这个页面其实只是个 模板,里面的信息是通过
homeView.attributesCSV=htitle=[home.htitle],nav=[nav.jsp],content=[home.jsp] 来确定的。
另外不要忘了那些国际化的部分,在这个例子里,如果有国际化信息,那么相应的配置的文件也会加上一些 "_en",
"_zh_CN" 的后缀。
CountriesController 里还有一个类的定义:
private CountryService countryService;
这个类可不是 countryService = new CountryService () 方式获得的,
- <property name="countryService" ref="countryService"/>
这样的方式注入的。
CountryService 类的内容就不多说,因为和spring本身的特点没什么太大的关系。
这个例子不复杂,但是涉及的内容却不少,Resolver, Interceptor, MultiActionController, 国际化。
从这个小小的例子也可以看出,spring 实在是太灵活,
如果说 structs 类的framework 是降龙十八掌,那 spring 就得说是有九九八十一招的武功,招数实在太多,
临敌的时候选择哪个招式,还真的仔细考虑 :)