这个问题本身不叫问题,可以直接写完整路径就行了,但是本人比较懒,不想写这么长一串,好吧,跟踪了半天的源代码,最后发现,
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来帮我注入,
代码就改动这个,然后是配置文件,将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里面配置了,
经过我的实验,这样是可行的.