SpringMVC(视图解析器)

上一期我们模拟了前端控制器,接收访问,并解析请求连接,能直接定位到指定的controller中的方法中.
这次我们看一下视图解析器

ModelAndView

/**
 * ModleAndView实体对象, 保存一些对应需要在页面显示的信息
 */
@Data
public class ModleAndView {

    /**
     * 视图名称
     */
    private Object viewData;

    /**
     * 相应参数
     */
    private HashMap model;

    /**
     * 是否为一个视图对象
     */
    private boolean hasView;


}

DispatchServlet

//获得参数适配器
        HandlerAdapter ha=handleradapters.get(handleMapping);
        //执行方法,得到视图信息
        ModleAndView modleAndView=ha.handle(req,resp,handleMapping);
        //把视图名称解析成对应文件名
        applyDefaultViewName(modleAndView);

        //返回json
        if(!modleAndView.isHasView()){
            resp.getWriter().write(modleAndView.getViewData().toString());
            return ;
        }
        //根据视图名称找到视图文件
        View view=getView(modleAndView.getViewData().toString());

        //读取jsp文件,替换${}内容
        view.render(modleAndView,resp);

修改HandlerAdapter

Object result = handleMapping.getMethod().invoke(handleMapping.getController(), params);

            //需要返回视图
            if (result instanceof ModleAndView) {
                modleAndView = (ModleAndView) result;
                modleAndView.setHasView(true);
            } else {
                //非视图返回Json
                modleAndView.setHasView(false);
                modleAndView.setViewData(result);
            }

将视图文件名称解析成jsp文件名称

 /**
     * 把返回字符串解析成文件名mvc=>mvc.jsp
     *
     * @param modleAndView
     */
    private void applyDefaultViewName(ModleAndView modleAndView) {
        if (modleAndView.isHasView()) {
            //mvc
            String viewName = modleAndView.getViewData().toString();
            String prefix = vewConfig.get("view.prefix").toString();
            String suffix = vewConfig.get("view.suffix").toString();
            viewName = prefix + viewName + suffix;
            modleAndView.setViewData(viewName);
        }
    }

根据视图名称找到视图文件

private View getView(String viewName) {
        for(View view:viewList){
            if(view.getViewName().equals(viewName)) return view;
        }
        return null;
    }

读取jsp文件,替换自定义内容

/**
 * 视图文件信息
 */
@Data
public class View {

    public View(String viewName, File file) {
        this.viewName = viewName;
        this.file = file;
    }

    //视图名称mvc.jsp
    private String  viewName;

    //视图文件
    private File file;

    public void render(ModleAndView modleAndView, HttpServletResponse response){
        try {
            Pattern pattern=Pattern.compile("[.\\w]*[%{]([\\w]+)[}]");
            //读取flie内容
            BufferedReader reader=new BufferedReader(new FileReader(file));
            //替换完成结果
            StringBuffer result=new StringBuffer();

            while (reader.read()>0){
                String line=reader.readLine();
                //使用正则表达式替换掉 ¥{}内容
                Matcher matcher=pattern.matcher(line);
                //有匹配的表达式
                while (matcher.find()){
                    //拿到表达式名称
                    String text=matcher.group();
                    String key=matcher.group(1);
                    //从返回结果中找到与表达式匹配的变量,并替换
                    if(modleAndView.getModel().containsKey(key)){
                        //替换对应的内容
                        line=line.replace("%{"+key+"}",modleAndView.getModel().get(key).toString());
                    }

                }
                result.append(line);
            }
            response.getWriter().write(result.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    };

}

SpringMVC用于处理视图最重要的两个接口是ViewResolver和View。ViewResolver的主要作用是把一个逻辑上的视图名称解析为一个真正的视图,SpringMVC中用于把View对象呈现给客户端的是View对象本身,而ViewResolver只是把逻辑视图名称解析为对象的View对象。View接口的主要作用是用于处理视图,然后返回给客户端。
在这里插入图片描述

各种视图解析器

  1. AbstractCachingViewResolver:这是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。

  2. UrlBasedViewResolver:它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。如prefix=/WEB-INF/jsps/,suffix=.jsp,返回的视图名称viewName=test/indx,则UrlBasedViewResolver解析出来的视图URL就是/WEB-INF/jsps/test/index.jsp。默认的prefix和suffix都是空串。URLBasedViewResolver支持返回的视图名称中包含redirect:前缀,这样就可以支持URL在客户端的跳转,如当返回的视图名称是”redirect:test.do”的时候,URLBasedViewResolver发现返回的视图名称包含”redirect:”前缀,于是把返回的视图名称前缀”redirect:”去掉,取后面的test.do组成一个RedirectView,RedirectView中将把请求返回的模型属性组合成查询参数的形式组合到redirect的URL后面,然后调用HttpServletResponse对象的sendRedirect方法进行重定向。同样URLBasedViewResolver还支持forword:前缀,对于视图名称中包含forword:前缀的视图名称将会被封装成一个InternalResourceView对象,然后在服务器端利用RequestDispatcher的forword方式跳转到指定的地址。使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。下面是一段UrlBasedViewResolver的定义,根据该定义,当返回的逻辑视图名称是test的时候,UrlBasedViewResolver将把逻辑视图名称加上定义好的前缀和后缀,即“/WEB-INF/test.jsp”,然后新建一个viewClass属性指定的视图类型予以返回,即返回一个url为“/WEB-INF/test.jsp”的InternalResourceView对象。

<bean  
	class="org.springframework.web.servlet.view.UrlBasedViewResolver">  
	<property name="prefix" value="/WEB-INF/" />  
	<property name="suffix" value=".jsp" />  
	<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>  
</bean> 
  1. InternalResourceViewResolver:它是URLBasedViewResolver的子类,所以URLBasedViewResolver支持的特性它都支持。在实际应用中InternalResourceViewResolver也是使用的最广泛的一个视图解析器。那么InternalResourceViewResolver有什么自己独有的特性呢?单从字面意思来看,我们可以把InternalResourceViewResolver解释为内部资源视图解析器,这就是InternalResourceViewResolver的一个特性。InternalResourceViewResolver会把返回的视图名称都解析为InternalResourceView对象,InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务器端把请求forword重定向到目标URL。比如在InternalResourceViewResolver中定义了prefix=/WEB-INF/,suffix=.jsp,然后请求的Controller处理器方法返回的视图名称为test,那么这个时候InternalResourceViewResolver就会把test解析为一个InternalResourceView对象,先把返回的模型属性都存放到对应的HttpServletRequest属性中,然后利用RequestDispatcher在服务器端把请求forword到/WEB-INF/test.jsp。这就是InternalResourceViewResolver一个非常重要的特性,我们都知道存放在/WEB-INF/下面的内容是不能直接通过request请求的方式请求到的,为了安全性考虑,我们通常会把jsp文件放在WEB-INF目录下,而InternalResourceView在服务器端跳转的方式可以很好的解决这个问题。下面是一个InternalResourceViewResolver的定义,根据该定义当返回的逻辑视图名称是test的时候,InternalResourceViewResolver会给它加上定义好的前缀和后缀,组成“/WEB-INF/test.jsp”的形式,然后把它当做一个InternalResourceView的url新建一个InternalResourceView对象返回。
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
		<property name="prefix" value="/WEB-INF/"/>  
		<property name="suffix" value=".jsp"></property>  
	</bean>  
  1. XmlViewResolver:它继承自AbstractCachingViewResolver抽象类,所以它也是支持视图缓存的。XmlViewResolver需要给定一个xml配置文件,该文件将使用和Spring的bean工厂配置文件一样的DTD定义,所以其实该文件就是用来定义视图的bean对象的。在该文件中定义的每一个视图的bean对象都给定一个名字,然后XmlViewResolver将根据Controller处理器方法返回的逻辑视图名称到XmlViewResolver指定的配置文件中寻找对应名称的视图bean用于处理视图。该配置文件默认是/WEB-INF/views.xml文件,如果不使用默认值的时候可以在XmlViewResolver的location属性中指定它的位置。XmlViewResolver还实现了Ordered接口,因此我们可以通过其order属性来指定在ViewResolver链中它所处的位置,order的值越小优先级越高。以下是使用XmlViewResolver的一个示例:
  • 在SpringMVC的配置文件中加入XmlViewResolver的bean定义。使用location属性指定其配置文件所在的位置,order属性指定当有多个ViewResolver的时候其处理视图的优先级。关于ViewResolver链的问题将在后续内容中讲到。

    <bean class="org.springframework.web.servlet.view.XmlViewResolver">  
    	<property name="location" value="/WEB-INF/views.xml"/>  
    	<property name="order" value="1"/>  
    </bean>
    
  • 在XmlViewResolver对应的配置文件中配置好所需要的视图定义。在下面的代码中我们就配置了一个名为internalResource的InternalResourceView,其url属性为“/index.jsp”。

    <?xml version="1.0" encoding="UTF-8"?>  
    	<beans xmlns="http://www.springframework.org/schema/beans"  
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    	xsi:schemaLocation="http://www.springframework.org/schema/beans  
    	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    	<bean id="internalResource" class="org.springframework.web.servlet.view.InternalResourceView">  
    		<property name="url" value="/index.jsp"/>  
    	</bean>  
    </beans>
  • 定义一个返回的逻辑视图名称为在XmlViewResolver配置文件中定义的视图名称——internalResource。
    @RequestMapping("/xmlViewResolver")
    public String testXmlViewResolver() {
    return “internalResource”;
    }
  • 这样当我们访问到上面定义好的testXmlViewResolver处理器方法的时候返回的逻辑视图名称为“internalResource”,这时候Spring就会到定义好的views.xml中寻找id或name为“internalResource”的bean对象予以返回,这里Spring找到的是一个url为“/index.jsp”的InternalResourceView对象。
  1. BeanNameViewResolver:这个视图解析器跟XmlViewResolver有点类似,也是通过把返回的逻辑视图名称去匹配定义好的视图bean对象。不同点有二,一是BeanNameViewResolver要求视图bean对象都定义在Spring的application context中,而XmlViewResolver是在指定的配置文件中寻找视图bean对象,二是BeanNameViewResolver不会进行视图缓存。看一个例子,在SpringMVC的配置文件中定义了一个BeanNameViewResolver视图解析器和一个id为test的InternalResourceview bean对象。
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">  
		<property name="order" value="1"/>  
	</bean>  
 
	<bean id="test" class="org.springframework.web.servlet.view.InternalResourceView">  
		<property name="url" value="/index.jsp"/>  
	</bean>

这样当返回的逻辑视图名称是 test的时候,就会解析为上面定义好id为test的InternalResourceView。

  1. ResourceBundleViewResolver:它和XmlViewResolver一样,也是继承自AbstractCachingViewResolver,但是它缓存的不是视图,这个会在后面有说到。和XmlViewResolver一样它也需要有一个配置文件来定义逻辑视图名称和真正的View对象的对应关系,不同的是ResourceBundleViewResolver的配置文件是一个属性文件,而且必须是放在classpath路径下面的,默认情况下这个配置文件是在classpath根目录下的views.properties文件,如果不使用默认值的话,则可以通过属性baseName或baseNames来指定。baseName只是指定一个基名称,Spring会在指定的classpath根目录下寻找以指定的baseName开始的属性文件进行View解析,如指定的baseName是base,那么base.properties、baseabc.properties等等以base开始的属性文件都会被Spring当做ResourceBundleViewResolver解析视图的资源文件。ResourceBundleViewResolver使用的属性配置文件的内容类似于这样:
resourceBundle.(class)=org.springframework.web.servlet.view.InternalResourceView  
	resourceBundle.url=/index.jsp  
	test.(class)=org.springframework.web.servlet.view.InternalResourceView  
	test.url=/test.jsp

在这个配置文件中我们定义了两个InternalResourceView对象,一个的名称是resourceBundle,对应URL是/index.jsp,另一个名称是test,对应的URL是/test.jsp。从这个定义来看我们可以知道resourceBundle是对应的视图名称,使用resourceBundle.(class)来指定它对应的视图类型,resourceBundle.url指定这个视图的url属性。会思考的读者看到这里可能会有这样一个问题:为什么resourceBundle的class属性要用小括号包起来,而它的url属性就不需要呢?这就需要从ResourceBundleViewResolver进行视图解析的方法来说了。ResourceBundleViewResolver还是通过bean工厂来获得对应视图名称的视图bean对象来解析视图的。那么这些bean从哪里来呢?就是从我们定义的properties属性文件中来。在ResourceBundleViewResolver第一次进行视图解析的时候会先new一个BeanFactory对象,然后把properties文件中定义好的属性按照它自身的规则生成一个个的bean对象注册到该BeanFactory中,之后会把该BeanFactory对象保存起来,所以ResourceBundleViewResolver缓存的是BeanFactory,而不是直接的缓存从BeanFactory中取出的视图bean。然后会从bean工厂中取出名称为逻辑视图名称的视图bean进行返回。接下来就讲讲Spring通过properties文件生成bean的规则。它会把properties文件中定义的属性名称按最后一个点“.”进行分割,把点前面的内容当做是bean名称,点后面的内容当做是bean的属性。这其中有几个特别的属性,Spring把它们用小括号包起来了,这些特殊的属性一般是对应的attribute,但不是bean对象所有的attribute都可以这样用。其中(class)是一个,除了(class)之外,还有(scope)、(parent)、(abstract)、(lazy-init)。而除了这些特殊的属性之外的其他属性,Spring会把它们当做bean对象的一般属性进行处理,就是bean对象对应的property。所以根据上面的属性配置文件将生成如下两个bean对象:

<bean id="resourceBundle" class="org.springframework.web.servlet.view.InternalResourceView">  
		<property name="url" value="/index.jsp"/>  
	</bean>  
	
	<bean id="test" class="org.springframework.web.servlet.view.InternalResourceView">  
		<property name="url" value="/test.jsp"/>  
	</bean>
  1. FreeMarkerViewResolver、VolocityViewResolver:这两个视图解析器都是UrlBasedViewResolver的子类。FreeMarkerViewResolver会把Controller处理方法返回的逻辑视图解析为FreeMarkerView,而VolocityViewResolver会把返回的逻辑视图解析为VolocityView。因为这两个视图解析器类似,所以这里我就只挑FreeMarkerViewResolver来做一个简单的讲解。FreeMarkerViewResolver和VilocityViewResolver都继承了UrlBasedViewResolver。
    对于FreeMarkerViewResolver而言,它会按照UrlBasedViewResolver拼接URL的方式进行视图路径的解析。但是使用FreeMarkerViewResolver的时候不需要我们指定其viewClass,因为FreeMarkerViewResolver中已经把viewClass定死为FreeMarkerView了。

我们先在SpringMVC的配置文件里面定义一个FreeMarkerViewResolver视图解析器,并定义其解析视图的order顺序为1。

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">  
		<property name="prefix" value="fm_"/>  
		<property name="suffix" value=".ftl"/>  
		<property name="order" value="1"/>  
	</bean>

那么当我们请求的处理器方法返回一个逻辑视图名称viewName的时候,就会被该视图处理器加上前后缀解析为一个url为“fm_viewName.ftl”的FreeMarkerView对象。对于FreeMarkerView我们需要给定一个FreeMarkerConfig的bean对象来定义FreeMarker的配置信息。FreeMarkerConfig是一个接口,Spring已经为我们提供了一个实现,它就是FreeMarkerConfigurer。我们可以通过在SpringMVC的配置文件里面定义该bean对象来定义FreeMarker的配置信息,该配置信息将会在FreeMarkerView进行渲染的时候使用到。对于FreeMarkerConfigurer而言,我们最简单的配置就是配置一个templateLoaderPath,告诉Spring应该到哪里寻找FreeMarker的模板文件。这个templateLoaderPath也支持使用“classpath:”和“file:”前缀。当FreeMarker的模板文件放在多个不同的路径下面的时候,我们可以使用templateLoaderPaths属性来指定多个路径。在这里我们指定模板文件是放在“/WEB-INF/freemarker/template”下面的。

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">  
		<property name="templateLoaderPath" value="/WEB-INF/freemarker/template"/>  
	</bean>

ViewResolver的提供的接口的主要功能是生成View对象,源码如下:
public interface ViewResolver {

/**
 * Resolve the given view by name.
 * <p>Note: To allow for ViewResolver chaining, a ViewResolver should
 * return {@code null} if a view with the given name is not defined in it.
 * However, this is not required: Some ViewResolvers will always attempt
 * to build View objects with the given name, unable to return {@code null}
 * (rather throwing an exception when View creation failed).
 * @param viewName name of the view to resolve
 * @param locale Locale in which to resolve the view.
 * ViewResolvers that support internationalization should respect this.
 * @return the View object, or {@code null} if not found
 * (optional, to allow for ViewResolver chaining)
 * @throws Exception if the view cannot be resolved
 * (typically in case of problems creating an actual View object)
 */
View resolveViewName(String viewName, Locale locale) throws Exception;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值