• spring源码分析之freemarker整合


      FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
      FreeMarker是免费的,基于Apache许可证2.0版本发布。其模板编写为FreeMarker Template Language(FTL),属于简单、专用的语言。需要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,主要用于如何展现数据, 而在模板之外注意于要展示什么数据。

    1.定义(准备工作)

    freemarker整合需要定义FreeMarkerViewResolver

    package com.jverstry.Configuration;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
    import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
    
    @EnableWebMvc
    @Configuration
    @ComponentScan(basePackages = "com.jverstry")
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Bean
        public ViewResolver getViewResolver() {
            
            FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
            resolver.setCache(false);
    //        resolver.setPrefix("");
            resolver.setSuffix(".ftl");
    
            return resolver;
            
        }
        
        @Bean
        public FreeMarkerConfigurer getFreemarkerConfig() {
            
            FreeMarkerConfigurer result = new FreeMarkerConfigurer();
            
            result.setTemplateLoaderPath("WEB-INF/pages/");
            
            return result;
            
        }    
        
    }

    在web.xml定义:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
        <context-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.jverstry.Configuration</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <servlet>
            <servlet-name>MyServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value></param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>MyServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <welcome-file-list>
            <welcome-file></welcome-file>
        </welcome-file-list>
    
    </web-app>

    注意:上面的contextclass定义在FrameworkServlet中,contextclass设置了一个自定义的context类,且必须是WebApplicationContext的实现。

        /**
         * Set a custom context class. This class must be of type
         * {@link org.springframework.web.context.WebApplicationContext}.
         * <p>When using the default FrameworkServlet implementation,
         * the context class must also implement the
         * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
         * interface.
         * @see #createWebApplicationContext
         */
        public void setContextClass(Class<?> contextClass) {
            this.contextClass = contextClass;
        }

    可以看出,在dispatcherServlet时定义了bean:

    FreeMarkerViewResolver、
    FreeMarkerConfigurer 

    那么在DispatcherServlet中是如何识别的呢?

    /**
         * Initialize the ViewResolvers used by this class.
         * <p>If no ViewResolver beans are defined in the BeanFactory for this
         * namespace, we default to InternalResourceViewResolver.
         */
        private void initViewResolvers(ApplicationContext context) {
            this.viewResolvers = null;
    
            if (this.detectAllViewResolvers) {
                // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
                Map<String, ViewResolver> matchingBeans =
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
                    // We keep ViewResolvers in sorted order.
                    OrderComparator.sort(this.viewResolvers);
                }
            }
            else {
                try {
                    ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
                    this.viewResolvers = Collections.singletonList(vr);
                }
                catch (NoSuchBeanDefinitionException ex) {
                    // Ignore, we'll add a default ViewResolver later.
                }
            }
    
            // Ensure we have at least one ViewResolver, by registering
            // a default ViewResolver if no other resolvers are found.
            if (this.viewResolvers == null) {
                this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
                if (logger.isDebugEnabled()) {
                    logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
                }
            }
        }

    然后FreeMarkerViewResolver设置FreeMarkerView

        public FreeMarkerViewResolver() {
            setViewClass(requiredViewClass());
        }
    
        /**
         * Requires {@link FreeMarkerView}.
         */
        @Override
        protected Class<?> requiredViewClass() {
            return FreeMarkerView.class;
        }
    FreeMarkerView在初始化时查找
    FreeMarkerConfigurer 的bean
    /**
         * Invoked on startup. Looks for a single FreeMarkerConfig bean to
         * find the relevant Configuration for this factory.
         * <p>Checks that the template for the default Locale can be found:
         * FreeMarker will check non-Locale-specific templates if a
         * locale-specific one is not found.
         * @see freemarker.cache.TemplateCache#getTemplate
         */
        @Override
        protected void initServletContext(ServletContext servletContext) throws BeansException {
            if (getConfiguration() != null) {
                this.taglibFactory = new TaglibFactory(servletContext);
            }
            else {
                FreeMarkerConfig config = autodetectConfiguration();
                setConfiguration(config.getConfiguration());
                this.taglibFactory = config.getTaglibFactory();
            }
    
            GenericServlet servlet = new GenericServletAdapter();
            try {
                servlet.init(new DelegatingServletConfig());
            }
            catch (ServletException ex) {
                throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex);
            }
            this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper());
        }

    自动检测

    /**
         * Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext.
         * @return the Configuration instance to use for FreeMarkerViews
         * @throws BeansException if no Configuration instance could be found
         * @see #getApplicationContext
         * @see #setConfiguration
         */
        protected FreeMarkerConfig autodetectConfiguration() throws BeansException {
            try {
                return BeanFactoryUtils.beanOfTypeIncludingAncestors(
                        getApplicationContext(), FreeMarkerConfig.class, true, false);
            }
            catch (NoSuchBeanDefinitionException ex) {
                throw new ApplicationContextException(
                        "Must define a single FreeMarkerConfig bean in this web application context " +
                        "(may be inherited): FreeMarkerConfigurer is the usual implementation. " +
                        "This bean may be given any name.", ex);
            }
        }

    2. 渲染视图整个过程

    DispatcherServlet开始

    /**
         * Render the given ModelAndView.
         * <p>This is the last stage in handling a request. It may involve resolving the view by name.
         * @param mv the ModelAndView to render
         * @param request current HTTP servlet request
         * @param response current HTTP servlet response
         * @throws ServletException if view is missing or cannot be resolved
         * @throws Exception if there's a problem rendering the view
         */
        protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
            // Determine locale for request and apply it to the response.
            Locale locale = this.localeResolver.resolveLocale(request);
            response.setLocale(locale);
    
            View view;
            if (mv.isReference()) {
                // We need to resolve the view name.
                view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
                if (view == null) {
                    throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                            "' in servlet with name '" + getServletName() + "'");
                }
            }
            else {
                // No need to lookup: the ModelAndView object contains the actual View object.
                view = mv.getView();
                if (view == null) {
                    throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                            "View object in servlet with name '" + getServletName() + "'");
                }
            }
    
            // Delegate to the View object for rendering.
            if (logger.isDebugEnabled()) {
                logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            try {
                view.render(mv.getModelInternal(), request, response);
            }
            catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                            getServletName() + "'", ex);
                }
                throw ex;
            }
        }

    2.1 创建视图View

    如红色1所示,调用DispatcherServlet的

    resolveViewName方法
        /**
         * Resolve the given view name into a View object (to be rendered).
         * <p>The default implementations asks all ViewResolvers of this dispatcher.
         * Can be overridden for custom resolution strategies, potentially based on
         * specific model attributes or request parameters.
         * @param viewName the name of the view to resolve
         * @param model the model to be passed to the view
         * @param locale the current locale
         * @param request current HTTP servlet request
         * @return the View object, or {@code null} if none found
         * @throws Exception if the view cannot be resolved
         * (typically in case of problems creating an actual View object)
         * @see ViewResolver#resolveViewName
         */
        protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
                HttpServletRequest request) throws Exception {
    
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
            return null;
        }

    然后调用各种的ReviewResolver来解析视图AbstractCachingViewResolver

    @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            if (!isCache()) {
                return createView(viewName, locale);
            }
            else {
                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) {
                                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);
            }
        }

    调用子类UrlBasedViewResolver来创建view对象

    /**
         * Overridden to implement check for "redirect:" prefix.
         * <p>Not possible in {@code loadView}, since overridden
         * {@code loadView} versions in subclasses might rely on the
         * superclass always creating instances of the required view class.
         * @see #loadView
         * @see #requiredViewClass
         */
        @Override
        protected View createView(String viewName, Locale locale) throws Exception {
            // If this resolver is not supposed to handle the given view,
            // return null to pass on to the next resolver in the chain.
            if (!canHandle(viewName, locale)) {
                return null;
            }
            // Check for special "redirect:" prefix.
            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.
            if (viewName.startsWith(FORWARD_URL_PREFIX)) {
                String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
                return new InternalResourceView(forwardUrl);
            }
            // Else fall back to superclass implementation: calling loadView.
            return super.createView(viewName, locale);
        }

    若前缀是redirect:或者forward:则跳入相应的逻辑进行处理,否则使用父逻辑

        /**
         * Create the actual View object.
         * <p>The default implementation delegates to {@link #loadView}.
         * This can be overridden to resolve certain view names in a special fashion,
         * before delegating to the actual {@code loadView} implementation
         * provided by the subclass.
         * @param viewName the name of the view to retrieve
         * @param locale the Locale to retrieve the view for
         * @return the View instance, or {@code null} if not found
         * (optional, to allow for ViewResolver chaining)
         * @throws Exception if the view couldn't be resolved
         * @see #loadView
         */
        protected View createView(String viewName, Locale locale) throws Exception {
            return loadView(viewName, locale);
        }
    
        /**
         * Delegates to {@code buildView} for creating a new instance of the
         * specified view class, and applies the following Spring lifecycle methods
         * (as supported by the generic Spring bean factory):
         * <ul>
         * <li>ApplicationContextAware's {@code setApplicationContext}
         * <li>InitializingBean's {@code afterPropertiesSet}
         * </ul>
         * @param viewName the name of the view to retrieve
         * @return the View instance
         * @throws Exception if the view couldn't be resolved
         * @see #buildView(String)
         * @see org.springframework.context.ApplicationContextAware#setApplicationContext
         * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
         */
        @Override
        protected View loadView(String viewName, Locale locale) throws Exception {
            AbstractUrlBasedView view = buildView(viewName);
            View result = applyLifecycleMethods(viewName, view);
            return (view.checkResource(locale) ? result : null);
        }

     2.2 渲染视图

    如DispatchServlet红色部分2所示,调用View的render方法

    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

    具体实现由AbstractView来做

    /**
         * Prepares the view given the specified model, merging it with static
         * attributes and a RequestContext attribute, if necessary.
         * Delegates to renderMergedOutputModel for the actual rendering.
         * @see #renderMergedOutputModel
         */
        @Override
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            if (logger.isTraceEnabled()) {
                logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
                    " and static attributes " + this.staticAttributes);
            }
    
            Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
            prepareResponse(request, response);
            renderMergedOutputModel(mergedModel, request, response);
        }

    调用子类AbstractTemplateView实现上述红色部分

    @Override
        protected final void renderMergedOutputModel(
                Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            if (this.exposeRequestAttributes) {
                for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {
                    String attribute = en.nextElement();
                    if (model.containsKey(attribute) && !this.allowRequestOverride) {
                        throw new ServletException("Cannot expose request attribute '" + attribute +
                            "' because of an existing model object of the same name");
                    }
                    Object attributeValue = request.getAttribute(attribute);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Exposing request attribute '" + attribute +
                                "' with value [" + attributeValue + "] to model");
                    }
                    model.put(attribute, attributeValue);
                }
            }
    
            if (this.exposeSessionAttributes) {
                HttpSession session = request.getSession(false);
                if (session != null) {
                    for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) {
                        String attribute = en.nextElement();
                        if (model.containsKey(attribute) && !this.allowSessionOverride) {
                            throw new ServletException("Cannot expose session attribute '" + attribute +
                                "' because of an existing model object of the same name");
                        }
                        Object attributeValue = session.getAttribute(attribute);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Exposing session attribute '" + attribute +
                                    "' with value [" + attributeValue + "] to model");
                        }
                        model.put(attribute, attributeValue);
                    }
                }
            }
    
            if (this.exposeSpringMacroHelpers) {
                if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
                    throw new ServletException(
                            "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
                            "' because of an existing model object of the same name");
                }
                // Expose RequestContext instance for Spring macros.
                model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
                        new RequestContext(request, response, getServletContext(), model));
            }
    
            applyContentType(response);
    
            renderMergedTemplateModel(model, request, response);
        }

    再调用子类FreeMarkerView实现

        /**
         * Process the model map by merging it with the FreeMarker template.
         * Output is directed to the servlet response.
         * <p>This method can be overridden if custom behavior is needed.
         */
        @Override
        protected void renderMergedTemplateModel(
                Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            exposeHelpers(model, request);
            doRender(model, request, response);
        }

    然后调用doRender方法

    /**
         * Render the FreeMarker view to the given response, using the given model
         * map which contains the complete template model to use.
         * <p>The default implementation renders the template specified by the "url"
         * bean property, retrieved via {@code getTemplate}. It delegates to the
         * {@code processTemplate} method to merge the template instance with
         * the given template model.
         * <p>Adds the standard Freemarker hash models to the model: request parameters,
         * request, session and application (ServletContext), as well as the JSP tag
         * library hash model.
         * <p>Can be overridden to customize the behavior, for example to render
         * multiple templates into a single view.
         * @param model the model to use for rendering
         * @param request current HTTP request
         * @param response current servlet response
         * @throws IOException if the template file could not be retrieved
         * @throws Exception if rendering failed
         * @see #setUrl
         * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
         * @see #getTemplate(java.util.Locale)
         * @see #processTemplate
         * @see freemarker.ext.servlet.FreemarkerServlet
         */
        protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            // Expose model to JSP tags (as request attributes).
            exposeModelAsRequestAttributes(model, request);
            // Expose all standard FreeMarker hash models.
            SimpleHash fmModel = buildTemplateModel(model, request, response);
    
            if (logger.isDebugEnabled()) {
                logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'");
            }
            // Grab the locale-specific version of the template.
            Locale locale = RequestContextUtils.getLocale(request);
            processTemplate(getTemplate(locale), fmModel, response);
        }

    继续处理模板

    /**
         * Process the FreeMarker template to the servlet response.
         * <p>Can be overridden to customize the behavior.
         * @param template the template to process
         * @param model the model for the template
         * @param response servlet response (use this to get the OutputStream or Writer)
         * @throws IOException if the template file could not be retrieved
         * @throws TemplateException if thrown by FreeMarker
         * @see freemarker.template.Template#process(Object, java.io.Writer)
         */
        protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response)
                throws IOException, TemplateException {
    
            template.process(model, response.getWriter());
        }

    调用freemarker jar中的freemarker.template.Template类的process方法

    此过程超出spring的范围,故略去不述。

    3. 小结

      1.spring和freemarker的整合,需要定义两个bean:FreeMarkerViewResolver、FreeMarkerConfigurer。

      2.spring在Dispatcher中定义了视图渲染的过程:创建视图,然后利用Freemarker本身提供的Template方法来处理。

      处理过程中以Mode、request、response为参数。

    4. 附录:依赖包

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.jverstry</groupId>
        <artifactId>spring-freemarker-integration</artifactId>
        <packaging>war</packaging>
        <version>1.0.0</version>
    
        <name>Spring-FreeMarker-Integration</name>
    
        <properties>
            <java-version>1.6</java-version>
            <spring.version>3.1.2.RELEASE</spring.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
    
            <!-- Spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- FreeMarker -->
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.19</version>
            </dependency>
    
            <!-- CGLIB, only required and used for @Configuration usage -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib-nodep</artifactId>
                <version>2.2</version>
            </dependency>
    
            <!-- @Inject -->
            <dependency>
                <groupId>javax.inject</groupId>
                <artifactId>javax.inject</artifactId>
                <version>1</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>${java-version}</source>
                        <target>${java-version}</target>
                     <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>tomcat-maven-plugin</artifactId>
                    <version>1.1</version>
                    <configuration>
                        <port>8282</port>
                    </configuration>
                </plugin>
    
            </plugins>
        </build>
    </project>

    参考文献

    【1】http://baike.baidu.com/link?url=ETR7FFS21YwRaoIkWHYpJWl1rorsLrF3xgha7HepmiIRlrXwJ3ed7ZWBGgiCXF0fr1yezCCFzQomnSAU4tzljK

    【2】https://github.com/JVerstry/Web-Related-Examples/tree/master/Spring-FreeMarker-Integration

  • 相关阅读:
    237. Delete Node in a Linked List
    430. Flatten a Multilevel Doubly Linked List
    707. Design Linked List
    83. Remove Duplicates from Sorted List
    160. Intersection of Two Linked Lists
    426. Convert Binary Search Tree to Sorted Doubly Linked List
    142. Linked List Cycle II
    类之间的关系
    初始化块
    明确类和对象
  • 原文地址:https://www.cnblogs.com/davidwang456/p/5713461.html
Copyright © 2020-2023  润新知