• SpringMVC源码情操陶冶-ViewResolver视图解析


    简单分析springmvc是如何解析view视图,并返回页面给前端

    SpringMVC配置视图解析器

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property key="prefix" value="/WEB-INF/jsp/"/>
    	<property key="suffix" value=".jsp" />
    </bean>	
    

    配置的为jsp的解析器

    ViewResolver接口

    其内部只有一个接口方法,具体如下

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

    由上可知其是通过解析ViewName来得到View视图对象

    ViewResolver实现类-BeanNameViewResolver

    其意图是参数viewName就是springmvc上下文中的beanName对象,具体源码如下

    	@Override
    	public View resolveViewName(String viewName, Locale locale) throws BeansException {
    		//获得springmvc上下文
    		ApplicationContext context = getApplicationContext();
    		if (!context.containsBean(viewName)) {
    			//viewName不存在,则直接返回null
    			return null;
    		}
    		if (!context.isTypeMatch(viewName, View.class)) {
    			//viewName对应的beanName不是View.class的实现类,则直接返回
    			return null;
    		}
    		return context.getBean(viewName, View.class);
    	}
    

    BeanNameResolver表示返回的viewName必须是springmvc上下文中的beanName并且对应的实体类必须是View.class的实现类,否则返回null

    ViewResolver实现类-AbstractCachingViewResolver

    其是我们常用解析器的抽象类,比如Freemarker/Groovy,我们直接去看其实现的接口方法,具体源码如下

    	@Override
    	public View resolveViewName(String viewName, Locale locale) throws Exception {
    		//不采用缓存方案,则每次都进行创建View
    		if (!isCache()) {
    			return createView(viewName, locale);
    		}
    		else {
    			//此处为使用缓存情况下的获取View对象
    			Object cacheKey = getCacheKey(viewName, locale);
    			View view = this.viewAccessCache.get(cacheKey);
    			if (view == null) {
    				synchronized (this.viewCreationCache) {
    					view = this.viewCreationCache.get(cacheKey);
    					if (view == null) {
    						// Ask the subclass to create the View object.
    						view = createView(viewName, locale);
    						if (view == null && this.cacheUnresolved) {
    							//默认会返回一个null的View对象
    							view = UNRESOLVED_VIEW;
    						}
    						if (view != null) {
    							this.viewAccessCache.put(cacheKey, view);
    							this.viewCreationCache.put(cacheKey, view);
    							if (logger.isTraceEnabled()) {
    								logger.trace("Cached view [" + cacheKey + "]");
    							}
    						}
    					}
    				}
    			}
    			return (view != UNRESOLVED_VIEW ? view : null);
    		}
    	}
    

    接以上代码我们接着分析AbstractCachingViewResolver#createView()方法

    	protected View createView(String viewName, Locale locale) throws Exception {
    		//此处的loadView便是模板方法,供子类去实现
    		return loadView(viewName, locale);
    	}
    

    此处我们只分析其某个实现类UrlBasedViewResolver

    UrlBasedViewResolver-对应请求url的解析器

    1. 常用的内部属性
    	public static final String REDIRECT_URL_PREFIX = "redirect:";
    	
    	public static final String FORWARD_URL_PREFIX = "forward:";
    	//设置前缀
    	private String prefix = "";
    	//设置后缀
    	private String suffix = "";
    	//设置指定的viewName集合
    	private String[] viewNames ;
    
    1. createView()复写父类方法,即在创建view对象前做下跳转的请求检查
    	@Override
    	protected View createView(String viewName, Locale locale) throws Exception {
    		//viewNames集合为null或者对应的viewName在viewNames集合内则返回true
    		if (!canHandle(viewName, locale)) {
    			return null;
    		}
    		// Check for special "redirect:" prefix.
    		//检查handler返回的值为string类型时是否包含"redirect:"前缀
    		//此处处理的便是跳转请求,比如"redirect:/user/list"
    		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
    			RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
    			return applyLifecycleMethods(viewName, view);
    		}
    		// Check for special "forward:" prefix.
    		//同"redirect:"请求,此处为服务端直接跳转
    		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
    			return new InternalResourceView(forwardUrl);
    		}
    		//通过父类再去调用loadView()方法,其实是本类也复写了此方法
    		return super.createView(viewName, locale);
    	}
    
    1. loadView()复写方法
    	@Override
    	protected View loadView(String viewName, Locale locale) throws Exception {
    		//创建View对象
    		AbstractUrlBasedView view = buildView(viewName);
    		//将view对象与viewName绑定注册至springmvc上下文中
    		View result = applyLifecycleMethods(viewName, view);
    		return (view.checkResource(locale) ? result : null);
    	}
    
    1. buildView()创建View对象主逻辑
    	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    		//获取类似FreemarkerView.class/GroovyView.class
    		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
    		//设置view对应的资源路径,此处便可知我们设置prefix和suffix的作用
    		view.setUrl(getPrefix() + viewName + getSuffix());
    		
    		//下面都是设置与UrlBasedViewResolver的相关内部属性
    		String contentType = getContentType();
    		if (contentType != null) {
    			view.setContentType(contentType);
    		}
    
    		view.setRequestContextAttribute(getRequestContextAttribute());
    		view.setAttributesMap(getAttributesMap());
    		//是否暴露路径变量
    		Boolean exposePathVariables = getExposePathVariables();
    		if (exposePathVariables != null) {
    			view.setExposePathVariables(exposePathVariables);
    		}
    		//是否暴露springmvc的bean对象作为属性使用
    		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    		if (exposeContextBeansAsAttributes != null) {
    			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    		}
    		String[] exposedContextBeanNames = getExposedContextBeanNames();
    		if (exposedContextBeanNames != null) {
    			view.setExposedContextBeanNames(exposedContextBeanNames);
    		}
    
    		return view;
    	}
    

    主要作用是设置prefix和suffix参数以及对应的内部属性,可自行查阅,并通过buildView()方法创建ViewClass属性指定的View对象

    AbstractTemplateViewResolver-UrlBasedViewResolver子类

    添加另外的属性

    1. 内部属性
    	//是否暴露request对象的attributes属性给前端引擎
    	private boolean exposeRequestAttributes = false;
    	//是否允许请求处理过程中复写request对象的attributes
    	private boolean allowRequestOverride = false;
    	//是否暴露session对象的attributes属性给前端引擎
    	private boolean exposeSessionAttributes = false;
    	//是否允许请求处理过程中复写session对象的attributes
    	private boolean allowSessionOverride = false;
    	//是否使用暴露springMacroRequestContext属性
    	private boolean exposeSpringMacroHelpers = true;
    
    1. buildView()复写方法,主要是额外添加以上的属性
    	@Override
    	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    		//先调用父类的创建方法创建View对象
    		AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
    		//设置AbstractTemplateView的内部属性
    		view.setExposeRequestAttributes(this.exposeRequestAttributes);
    		view.setAllowRequestOverride(this.allowRequestOverride);
    		view.setExposeSessionAttributes(this.exposeSessionAttributes);
    		view.setAllowSessionOverride(this.allowSessionOverride);
    		view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
    		return view;
    	}
    

    在UrlBasedViewResolver的基础上设置额外的属性,属性集合见上文

    FreeMarkerViewResolver-AbstractTemplateViewResolver子类

    为方便理解,我们选取常用的模板引擎Freemarker,其他的引擎工具则供读者自行分析

    1. 构造函数-设置viewClass,满足上述的模板方法的viewClass的获取
    	public FreeMarkerViewResolver() {
    		//设置的为FreemarkView.class
    		setViewClass(requiredViewClass());
    	}
    

    小结

    本节只解析了ViewResolver的简单逻辑,其根据配置的ViewClass属性,将配置的其他属性都设置到ViewClass对应的实例中,具体的关于视图的渲染,也就是view#render()方法我们放在下节讲解

  • 相关阅读:
    畜栏预定【贪心+小根堆】
    电影【离散化】
    最佳牛栏(前缀和+二分)
    防晒【贪心 + 平衡树】
    货仓选址【中位数】证明
    24个不易混淆的数字和字母,常用于密码或密钥
    win8上的新功能,不知道win7有木有,刚偶然发现的
    通过C#类库绘制正态分布的统计图(通用)
    C#组播消息收发
    使用c#类库绘制柱状图
  • 原文地址:https://www.cnblogs.com/question-sky/p/7208327.html
Copyright © 2020-2023  润新知