现在的位置: 首页 > 综合 > 正文

关于strust2 使用freemarker 指定模板路径前缀的纠结

2014年07月17日 ⁄ 综合 ⁄ 共 8435字 ⁄ 字号 评论关闭

这个问题本身不叫问题,可以直接写完整路径就行了,但是本人比较懒,不想写这么长一串,好吧,跟踪了半天的源代码,最后发现,

org.apache.struts2.views.freemarker.FreemarkerManager这个类createTemplateLoader方法用来搞这事的,

     {	TemplateLoader templatePathLoader = null;

         try {
             if(templatePath!=null){
                 if (templatePath.startsWith("class://")) {
                     // substring(7) is intentional as we "reuse" the last slash
                     templatePathLoader = new ClassTemplateLoader(getClass(), templatePath.substring(7));
                 } else if (templatePath.startsWith("file://")) {
                     templatePathLoader = new FileTemplateLoader(new File(templatePath.substring(7)));
                 }
             }
         } catch (IOException e) {
             LOG.error("Invalid template path specified: " + e.getMessage(), e);
         }

         // presume that most apps will require the class and webapp template loader
         // if people wish to
         return templatePathLoader != null ?
                 new MultiTemplateLoader(new TemplateLoader[]{
                         templatePathLoader,
                         new WebappTemplateLoader(servletContext),
                         new StrutsClassTemplateLoader()
                 })
                 : new MultiTemplateLoader(new TemplateLoader[]{
                 new WebappTemplateLoader(servletContext),
                 new StrutsClassTemplateLoader()
         });
     }

可惜两种都不符合我的要求,第一种是用classpath的,没办法,我的模板路径不是classpath下面的,第二种是文件路径,可惜它用的不是俺需要的路径,它是文件系统的路径,

也就是c:\这样的,我目前是放在web-inf下面的一个文件夹里面的,如果这两种都不符合的话,最后会创建一个在根路径下的TemplateLoader,也就是模板放在web应用的根路径下就可以找到了.

话说,struts2没有我这种需求吗?也许是我没有弄清楚.需要有高人解惑.

中间有一段小插曲,就是设置启动参数的时候,我将多个参数值都设置在了一个<context-param>下面,导致我的spring不能正常初始化了,承认这点忽视了,以后记得有多个参数就设置多个<context-param>.

最后的折中解决办法是继承一个freemarkerresult类,然后修改一下里面的逻辑,把我的前缀加在location前面吧,暂时就这样了.

但是接下来又发现了一个小问题,就是我的模板里面有包含别的模板文件,这个路径貌似也要写完整的,这样可不行啊,我只好又接着修改FreemarkerManager这个类了,将上面那个方法进行重载,咱们来继承这个类,暂且取名QFreemarkerManager了

@Override
	protected TemplateLoader createTemplateLoader(
			ServletContext servletContext, String templatePath) {
		TemplateLoader templatePathLoader = null;

		try {
			if (templatePath != null) {
				if (templatePath.startsWith("class://")) {
					// substring(7) is intentional as we "reuse" the last slash
					templatePathLoader = new ClassTemplateLoader(getClass(),
							templatePath.substring(7));
				} else if (templatePath.startsWith("file://")) {
					templatePathLoader = new FileTemplateLoader(new File(
							templatePath.substring(7)));
				} else {
					return new MultiTemplateLoader(new TemplateLoader[] {
							new WebappTemplateLoader(servletContext,
									templatePath),
							new StrutsClassTemplateLoader() });
				}

			}
		} catch (IOException e) {
			LOG.error("Invalid template path specified: " + e.getMessage(), e);
		}

		// presume that most apps will require the class and webapp template
		// loader
		// if people wish to
		return templatePathLoader != null ? new MultiTemplateLoader(
				new TemplateLoader[] { templatePathLoader,
						new WebappTemplateLoader(servletContext),
						new StrutsClassTemplateLoader() })
				: new MultiTemplateLoader(new TemplateLoader[] {
						new WebappTemplateLoader(servletContext),
						new StrutsClassTemplateLoader() });
	}

其实也加了一行代码,就是当templatePath不为空的时候,你又不想用其他两种方式的时候就用第三种方式,然后你还是需要配置在web.xml里面配置context-param,如果你不想配置,那么还得再改FreemarkerManager的init方法了,因为templatePath就是从context-param中获得的.好了,现在可以配置纠结的模板前缀了----- "/WEB-INF/ftl" 

到此结束.嘿嘿.我去试试效果.

呃,忘了说用法了,我前面一篇中写过设置默认result-type的,这个往里面设置参数,参数名字就是freemarkerManager,当然值就是我们的类了,这个可以看源代码就知道了.

为什么会这么麻烦呢,spring mvc里面配置freemarker直接有一个属性可以设置模板前缀路径.

相关代码在org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer这个类里面,不多说了.

实验结果,真无耐,发现无法給我注入,不知道它从哪里实例化这个该死的freemarkerManager了,暂时只能再将我前面的那个继承自freemarkerresult再拿出来用了.

-------------------------------------

昨天由于下班了,就没搞了,今天接着搞,看一下代码,freemarkerManager是用的@inject注解来注入的,跟踪代码看了一下,不知道从注入的实例freemarkerManager,人也看老了,

百度了一把,这个注解如果不指定名称的话,值是默认的,我猜想可能这个类被写在配置文件中了,于是在struts-default中找到了这个类的定义

 <bean class="org.apache.struts2.views.freemarker.FreemarkerManager" name="struts" />

既然这样,我就把它重新申明一遍好了,类名改成我的,然后再试一把效果.

哎,好吧,还是失败了,又百度了,点击打开链接在这边文章中找到了一些关于struts2内部IOC的知识.

在struts2启动的时候会初始化一些类,这个类org.apache.struts2.config.BeanSelectionProvider主要就是用来选择相关的类的,里面有一个方法register

public void register(ContainerBuilder builder, LocatableProperties props) {
        alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props);
        alias(FileManagerFactory.class, StrutsConstants.STRUTS_FILE_MANAGER_FACTORY, builder, props, Scope.SINGLETON);
        alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props);
        alias(TextProvider.class, StrutsConstants.STRUTS_XWORKTEXTPROVIDER, builder, props, Scope.DEFAULT);
        alias(LocaleProvider.class, StrutsConstants.STRUTS_LOCALE_PROVIDER, builder, props);
        alias(ActionProxyFactory.class, StrutsConstants.STRUTS_ACTIONPROXYFACTORY, builder, props);
        alias(ObjectTypeDeterminer.class, StrutsConstants.STRUTS_OBJECTTYPEDETERMINER, builder, props);
        alias(ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS, builder, props);
        alias(MultiPartRequest.class, StrutsConstants.STRUTS_MULTIPART_PARSER, builder, props, Scope.DEFAULT);
        alias(FreemarkerManager.class, StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME, builder, props);

这个方法就是用来注册类的,可以看到调用了alias方法

void alias(Class type, String key, ContainerBuilder builder, Properties props, Scope scope) {
        if (!builder.contains(type)) {
            String foundName = props.getProperty(key, DEFAULT_BEAN_NAME);
            if (builder.contains(type, foundName)) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Choosing bean (#0) for (#1)", foundName, type.getName());
                }
                builder.alias(type, foundName, Container.DEFAULT_NAME);
            } else {
                try {
                    Class cls = ClassLoaderUtil.loadClass(foundName, this.getClass());
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Choosing bean (#0) for (#1)", cls.getName(), type.getName());
                    }
                    builder.factory(type, cls, scope);
                } catch (ClassNotFoundException ex) {
                    // Perhaps a spring bean id, so we'll delegate to the object factory at runtime
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Choosing bean (#0) for (#1) to be loaded from the ObjectFactory", foundName, type.getName());
                    }
                    if (DEFAULT_BEAN_NAME.equals(foundName)) {
                        // Probably an optional bean, will ignore
                    } else {
                        if (ObjectFactory.class != type) {
                            builder.factory(type, new ObjectFactoryDelegateFactory(foundName, type), scope);
                        } else {
                            throw new ConfigurationException("Cannot locate the chosen ObjectFactory implementation: " + foundName);
                        }
                    }
                }
            }
        } else {
            if (LOG.isWarnEnabled()) {
        	    LOG.warn("Unable to alias bean type (#0), default mapping already assigned.", type.getName());
            }
        }
    }

它会根据Key 来选择相应的foundname,这个值又是用来查找注册的bean,如果没有注册的话,就会调用ObjectFactory实例化一个,经过实践,终于可以用我自己的FreemarkerManager了.

<constant name="struts.freemarker.manager.classname" value="QFreemarkerManager" />

key就是这个struts.freemarker.manager.classname字符串,value就是类名,

按道理来说,这里的value也可以用一个bean的名称来代替,跟ObjectFactory类似,我们使用spring 的时候既可以用"spring"这个名称,也可以用类的全名称.这种方法还没实验,可以

参数spring-struts-plugin里面来配置.

好了,这个问题目前就到这里了,继续攻克其他的问题了.

今天,又碰到关于这个类的一个问题了,我写了个freemarker的自定义标签,继承的这个类TemplateDirectiveModel,但是不晓得怎么在struts2里面用起来,我参考了springmvc里面关于freemarkerconfigurer里面的代码,它的这个类里面是可以定义自已的变量的,但是struts的官方文档上面没提供相应的说明,只好参照spring里面的作法了,

好了,这下只能再修改Freemarker的其他方法了,修改就是init方法

@Override
	public void init(ServletContext servletContext) throws TemplateException {
        config = createConfiguration(servletContext);

        // Set defaults:
        config.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
        contentType = DEFAULT_CONTENT_TYPE;

        // Process object_wrapper init-param out of order:
        wrapper = createObjectWrapper(servletContext);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Using object wrapper of class " + wrapper.getClass().getName());
        }
        config.setObjectWrapper(wrapper);

        // Process TemplatePath init-param out of order:
        if (templatePath==null) {
        	templatePath = servletContext.getInitParameter(INITPARAM_TEMPLATE_PATH);
	}
        if (templatePath == null) {
            templatePath = servletContext.getInitParameter("templatePath");
        }

        config.setTemplateLoader(createTemplateLoader(servletContext, templatePath));
	if (!CollectionUtils.isEmpty(this.freemarkerVariables)) {
			config.setAllSharedVariables(new SimpleHash(
					this.freemarkerVariables, config.getObjectWrapper()));
		}
        loadSettings(servletContext);
    }

红色字体的是我添加的,反正要修改,就顺便把这个也改了,关于这个path的问题,在上面捣鼓了很久,这次我把它用spring来帮我注入,

蓝色的是从spring mvc的freemarkerconfigurer中复制过来的,当然你得加一个freemarkerVariables的属性了,map类型的,

代码就改动这个,然后是配置文件,将QFreemarkerManager交給spring去实例化,

<constant name="struts.freemarker.manager.classname" value="freemarkerManager" /> 

将struts.xml中这句话改一下,value这个时候是spring 的id名称,就不是类的全名了,

顺便在这里提一下这里写全类名与名称的区别,struts来选择bean的时候,如果你写的全类名,则将会用struts2的容器去实例化这个类,我们也就没有办法去注入咱们的属性了,

 Class cls = ClassLoaderUtil.loadClass(foundName, this.getClass());
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Choosing bean (#0) for (#1)", cls.getName(), type.getName());
                    }
                    builder.factory(type, cls, scope);

这里会有ClassNotFoundException的异常,也就是说你写bean 的id的话,会报异常,然后就调用

 if (ObjectFactory.class != type) {
                            builder.factory(type, new ObjectFactoryDelegateFactory(foundName, type), scope);
                        } else {
                            throw new ConfigurationException("Cannot locate the chosen ObjectFactory implementation: " + foundName);
                        }

bean 工厂来实例化,这个时候就是咱们spring出马的时候了,这时就可以在spring配置属性了,

spring 的配置文件

<bean id="freemarkerManager" class="com.qcms.cms.result.QFreemarkerManager">
		<property name="templatePath" value="/WEB-INF"/>
		<property name="freemarkerVariables">
			<map>
				<entry key="h" value-ref="h"/>
			</map>
		</property>
</bean>

这里我把templatePath也給注入进来了,换句话就不用在web.xml里面配置了,

经过我的实验,这样是可行的.

抱歉!评论已关闭.