这篇blog主要是介绍如何实现一个超级灵活的SpringMVC多视图协商配置,试想如果一个项目需要使用jsp、freemaker、jasper、 velosity等,而返回客服端数据需要用到json、xml。那么改如果配置SpringMVC视图呢?
在本例中,我们提供了几种视图:JSP 文件、freemaker、jasper、SWF 文件(flash 文件)以及一个自定义后缀名(.config和.txt)的文件,为了更方便的支持 SWF 视图和自定义文件后缀名的视图,我们开发了自定义的视图对象,希望能够使用该视图对象来支持 SWF 文件和 .config .txt 文件,另外还开发了两个视图解析器来实现本例。
实际上,Spring 已经提供了RESTful的多视图解析器-ContentNegotiatingViewResolver (以下简称CNV),它可以根据请求的文件后缀名或请求的 Accept 头或者请求的参数来查找视图。CNV本身并不负责查找视图,它只是将视图查找工作代理给所注册的视图解析器,但是在实际开发中如果把所以视图都交给CNV 处理,CVN就显得有点笨拙,比如我在测试中发现把freemarker和InternalResourceViewResolver都放到CNV的 viewResolvers里,所有返回freemarker的视图都跑到InternalResourceViewResolver里去处理了,结果导 致404。但是用CNV来处理json和xml可以说是相当的方便。本例中的RESTful MVC就是采用CNV来处理json和xml。比如访问http://localhost/sample/message.json 那么返回客服端的数 据内容格式为json,数据为ModelMap里携带的数据。
一、自定义视图类代码
为了简化程序该视图类只能用于处理浏览器能直接显示的请求文件资源,如文本文件、SWF 文件等
- public class GenericFileView extends AbstractUrlBasedView {
- // default content type
- private final static String CONTENT_TYPE = "text/plain";
- //content of http response
- private String responseContent;
- public GenericFileView() {
- super();
- setContentType(CONTENT_TYPE);
- }
- @Override
- public void setContentType(String contentType) {
- super.setContentType(contentType);
- }
- @Override
- protected void renderMergedOutputModel(Map
model, - HttpServletRequest request, HttpServletResponse response)
- throws Exception {
- response.setContentType(getContentType());
- response.getWriter().write(this.responseContent);
- response.getWriter().close();
- }
- /**
- * Set http request content
- * @param responseContent
- */
- public void setResponseContent(String responseContent) {
- this.responseContent = responseContent;
- }
- }
二、Spring视图解析器
有了视图类,我们还需要查找该视图类的视图解析器。所有的视图解析器都需要实现接口 org.springframework.web.servlet.ViewResolver,但同视图的实现一样,Spring 还提供了一个抽象类AbstractCachingViewResolver常用的 Spring 中的视图解析器都继承了该抽象类,我们同样可以通过实现该抽象类来节省开发工作。
- public class GenericFileViewResolver extends AbstractCachingViewResolver
- implements Ordered {
- private Logger logger = Logger.getLogger(GenericFileViewResolver.class
- .getName());
- private int order = Integer.MIN_VALUE;
- // requested file location under web app
- private String location;
- // View
- private String viewName;
- public void setViewName(String viewName) {
- this.viewName = viewName;
- }
- public void setOrder(int order) {
- this.order = order;
- }
- public void setLocation(String location) {
- this.location = location;
- }
- public int getOrder() {
- return this.order;
- }
- @Override
- protected View loadView(String viewName, Locale locale) throws Exception {
- if (location == null) {
- throw new Exception(
- "No location specified for GenericFileViewResolver.");
- }
- String requestedFilePath = location + viewName;
- Resource resource = null;
- //String url = "";
- try {
- logger.finest(requestedFilePath);
- resource = getApplicationContext().getResource(requestedFilePath);
- //url = resource.getURI().toString();
- } catch (Exception e) {
- // this exception should be catched and return null in order to call
- // next view resolver
- logger.finest("No file found for file: " + requestedFilePath);
- return null;
- }
- logger.fine("Requested file found: " + requestedFilePath + ", viewName:" + viewName);
- //get view from application content, scope=prototype
- GenericFileView view = this.getApplicationContext().getBean(this.viewName, GenericFileView.class);
- view.setUrl(requestedFilePath);
- view.setResponseContent(inputStreamTOString(resource.getInputStream(), "UTF-8"));
- return view;
- }
- final static int BUFFER_SIZE = 4096;
- /**
- * Convert Input to String based on specific encoding
- *
- * @param in
- * @param encoding
- * @return
- * @throws Exception
- */
- public static String inputStreamTOString(InputStream in, String encoding)
- throws Exception {
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- byte[] data = new byte[BUFFER_SIZE];
- int count = -1;
- while ((count = in.read(data, 0, BUFFER_SIZE)) != -1)
- outStream.write(data, 0, count);
- data = null;
- return new String(outStream.toByteArray(), encoding);
- }
- }
三、自定义多视图解析器
- package org.mkwu.web.view.viewresolver;
- import java.util.Iterator;
- import java.util.Locale;
- import java.util.Map;
- import org.springframework.core.Ordered;
- import org.springframework.util.StringUtils;
- import org.springframework.web.servlet.View;
- import org.springframework.web.servlet.ViewResolver;
- import org.springframework.web.servlet.view.AbstractCachingViewResolver;
- public class MultipleViewResolver extends AbstractCachingViewResolver implements Ordered{
- private int order = Integer.MIN_VALUE;
- public int getOrder() {
- return order;
- }
- public void setOrder(int order) {
- this.order = order;
- }
- private Map
resolvers; - public void setResolvers(Map
resolvers) { - this.resolvers = resolvers;
- }
- private String getViewResolverKey(String fileExtension){
- Iterator
keyIt = resolvers.keySet().iterator(); - while (keyIt.hasNext()) {
- String viewResolverKey = (String) keyIt.next();
- String[] arr = viewResolverKey.split(",");
- for (String subKey : arr) {
- if(subKey.equals(fileExtension)) return viewResolverKey;
- }
- }
- return null;
- }
- @Override
- protected View loadView(String viewName, Locale locale) throws Exception {
- String fileExtension = StringUtils.getFilenameExtension(viewName);
- // return null to invoke next resolver if no extension found
- if (fileExtension == null) {
- return null;
- }
- String viewResolverKey = getViewResolverKey(fileExtension);
- ViewResolver resolver = resolvers.get(viewResolverKey);
- // get resolver by extension
- //return null to invoke next resolver if no resolver found
- return resolver == null ? null : resolver.resolveViewName(viewName,locale);
- }
- }
四、SpringMVC配置 也是本文的重点
- xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:p="http://www.springframework.org/schema/p"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xmlns:util="http://www.springframework.org/schema/util"
- xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
- <context:component-scan base-package="com.martinwu.example" use-default-filters="false">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
- context:component-scan>
- <mvc:annotation-driven/>
- <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
- <property name="messageConverters">
- <list>
- <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
- <property name="supportedMediaTypes">
- <list>
- <value>text/html;charset=UTF-8value>
- list>
- property>
- bean>
- <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"p:marshaller-ref="xstreamMarshaller" p:unmarshaller-ref="xstreamMarshaller" />
- <bean class="org.springframework.http.converter.FormHttpMessageConverter">bean>
- <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter">bean>
- <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">bean>
- <bean class="org.springframework.http.converter.StringHttpMessageConverter">bean>
- <bean class="org.springframework.http.converter.ResourceHttpMessageConverter">bean>
- list>
- property>
- bean>
- <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
- <property name="classesToBeBound">
- <list>
- <value>com.martinwu.example.entity.Uservalue>
- list>
- property>
- bean>
- <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
- <property name="streamDriver">
- <bean class="com.thoughtworks.xstream.io.xml.StaxDriver" />
- property>
- <property name="annotatedClasses">
- <list>
- <value>com.martinwu.example.entity.Uservalue>
- list>
- property>
- bean>
- <bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource">
- <property name="basenames">
- <list>
- <value>i18n/messagesvalue>
- list>
- property>
- <property name="useCodeAsDefaultMessage" value="true"/>
- bean>
- <mvc:default-servlet-handler/>
- <bean id="freemarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
- <property name="templateLoaderPath" value="/WEB-INF/freemarker/" />
- <property name="defaultEncoding" value="UTF-8" />
- <property name="freemarkerSettings">
- <props>
- <prop key="template_update_delay">10prop>
- <prop key="locale">zh_CNprop>
- <prop key="datetime_format">yyyy-MM-dd HH:mm:ssprop>
- <prop key="date_format">yyyy-MM-ddprop>
- <prop key="number_format">#.##prop>
- props>
- property>
- bean>
- <bean id="multipleViewResolver" class="com.martinwu.example.view.viewresolver.MultipleViewResolver" p:order="0">
- <property name="resolvers">
- <map>
- <entry key="config,txt">
- <bean class="com.martinwu.example.view.viewresolver.GenericFileViewResolver"
- p:location="/WEB-INF/config/" p:cache="false">
- <property name="viewName" value="configFileView"/>
- bean>
- entry>
- <entry key="swf">
- <bean class="com.martinwu.example.view.viewresolver.GenericFileViewResolver"
- p:location="/WEB-INF/swf/" p:cache="false">
- <property name="viewName" value="swfFileView"/>
- bean>
- entry>
- <entry key="ftl">
- <bean id="freeMarkerResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" p:order="2">
- <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
- <property name="contentType" value="text/html;charset=UTF-8" />
- <property name="exposeRequestAttributes" value="true" />
- <property name="exposeSessionAttributes" value="true" />
- <property name="exposeSpringMacroHelpers" value="true" />
- bean>
- entry>
- <entry key="jasper">
- <bean id="jasperReportsViewResolver" class="org.springframework.web.servlet.view.jasperreports.JasperReportsViewResolver"
- p:viewClass="com.scinvest.util.support.JasperView"
- p:requestContextAttribute="rc"
- p:prefix="/WEB-INF/reportView/">
- <property name="reportDataKey">
- <value>datasourcevalue>
- property>
- <property name="exporterParameters">
- <map>
- <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI">
- <value>#{rpe.resourceRoot}/images/reports/value>
- entry>
- <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.CHARACTER_ENCODING">
- <value>UTF-8value>
- entry>
- <entry key="net.sf.jasperreports.engine.export.JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET">
- <bean id="java.lang.Boolean.TRUE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
- entry>
- map>
- property>
- bean>
- entry>
- <entry key="jsp">
- <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
- <property name="contentType" value="text/html"/>
- <property name="prefix" value="/WEB-INF/views/"/>
- bean>
- entry>
- map>
- property>
- bean>
- <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver" p:order="1">
- <property name="ignoreAcceptHeader" value="true"/>
- <property name="favorPathExtension" value="true"/>
- <property name="favorParameter" value="false"/>
- <property name="parameterName" value="format"/>
- <property name="defaultContentType" value="text/html"/>
- <property name="mediaTypes">
- <map>
- <entry key="html" value="text/html;charset=UTF-8"/>
- <entry key="xml" value="application/xml" />
- <entry key="json" value="application/json" />
- map>
- property>
- <property name="defaultViews">
- <list>
- <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
- <bean class="org.springframework.web.servlet.view.xml.MarshallingView" >
- <constructor-arg>
- <ref bean="xstreamMarshaller"/>
- constructor-arg>
- bean>
- list>
- property>
- bean>
- <bean id="configFileView" class="com.martinwu.example.view.GenericFileView"
- p:contentType="text/plain" p:url="" scope="prototype"/>
- <bean id="swfFileView" class="com.martinwu.example.view.GenericFileView"
- p:contentType="application/x-shockwave-flash" p:url="" scope="prototype"/>
- <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" p:order="3">
- <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
- <property name="suffix" value=".ftl" />
- <property name="contentType" value="text/html;charset=UTF-8" />
- <property name="exposeRequestAttributes" value="true" />
- <property name="exposeSessionAttributes" value="true" />
- <property name="exposeSpringMacroHelpers" value="true" />
- bean>
- <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="5">
- <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
- <property name="contentType" value="text/html"/>
- <property name="prefix" value="/WEB-INF/views/"/>
- <property name="suffix" value=".jsp"/>
- bean>
- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
- <property name="maxUploadSize" value="100000"/>
- bean>
- <mvc:view-controller path="/" view-name="redirect:/welcome"/>
- beans>
参考:http://www.ibm.com/developerworks/cn/java/j-lo-springview/