boot automatic configuration freemarker lack freemarkerVariables
Comment From: wilkinsona
What do you mean by "freemarkerVariables"? Can you please show what you're currently having to configure that the auto-configuration should be able to configure for you?
Comment From: ohalo
in freemarker automatic configuration , it lack of freemarkerVariables configuration
Comment From: ohalo
it lack of freemarkerVariables configuration
Comment From: ohalo
freemarker:
settings:
object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper
datetime_format: yyyy-MM-dd HH:mm:ss
date_format: yyyy-MM-dd
time_format: HH:mm:ss
Comment From: ohalo
@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
it is no freemarkerVariables
Comment From: ohalo
but FreeMarkerConfigurationFactory has freemarkerVariables,i need it
Comment From: ohalo
I need a solution
Comment From: wilkinsona
Ok, so you'd like to be able to set the freemarkerVariables property on either the auto-configured FreeMarkerConfigurationFactoryBean or on the auto-configured FreeMarkerConfigurer bean, correct? Both are subclasses of FreeMarkerConfigurationFactory.
What variables do you want to be able to set? Can you please provide some specific examples? Please note that values other than maps, lists and scalar values are not well-suited to being set via application.properties.
Comment From: ohalo
shiro
shiro:
realm: com.control.back.halo.authorization.shiro.realm.UserRealm
login-url: /login.html
success-url: /index.html
unauthorized-url: /
#filter chian
filterChainDefinitions:
"/login": anon
"/login/**": anon
"/logout": logout
"/css/**": anon
"/fonts/**": anon
"/img/**": anon
"/js/**": anon
"/**": authc
i use shiro ,and definition a ShiroProperties,i hope freemarkerVariables use map definition . yes?
@ConfigurationProperties(prefix = "shiro")
public class ShiroProperties {
/**
* Custom Realm
*/
private String realm;
/**
* URL of login
*/
private String loginUrl;
/**
* URL of success
*/
private String successUrl;
/**
* URL of unauthorized
*/
private String unauthorizedUrl;
/**
* filter chain
*/
private Map<String, String> filterChainDefinitions;
/**
*
*/
private String ehacheConfig;
Comment From: ohalo
According to the yaml's document,it can configure the map ,Why not do it
Comment From: wilkinsona
@ohalo Please take the time to format your comments as it will make them much easier to read. Also, please keep this issue focused on FreeMarker. Your latest example appears to be solely about Shiro. I still need some examples of the freemarker variables that you are trying to set.
Comment From: ohalo
@ConfigurationProperties(prefix = "freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = ".ftl";
/**
* Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
*/
private Map<String, String> settings = new HashMap<String, String>();
private Map<String, String> freemarkerVariables = new HashMap<String, String>();
and configuration is
#freemarker
freemarker:
settings:
object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper
datetime_format: yyyy-MM-dd HH:mm:ss
date_format: yyyy-MM-dd
time_format: HH:mm:ss
freemarker-variables:
base: http://127.0.0.1:80
Comment From: ohalo
in FreeMarkerAutoConfiguration ,and join factory.setFreemarkerVariables(this.properties.getFreemarkerVariables());
``` protected static class FreeMarkerConfiguration {
@Autowired
protected FreeMarkerProperties properties;
protected void applyProperties(FreeMarkerConfigurationFactory factory) {
factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
factory.setDefaultEncoding(this.properties.getCharsetName());
Properties settings = new Properties();
settings.putAll(this.properties.getSettings());
factory.setFreemarkerSettings(settings);
factory.setFreemarkerVariables(this.properties.getFreemarkerVariables());
}
}
**Comment From: ohalo**
Is that ok?sorry my english
**Comment From: wilkinsona**
The formatting is much better, thank you. Unfortunately, it's not really what it was looking for.
You've shown how what you're asking for could be implemented in Spring Boot and I already know how to do that. What I don't know is what _values_ you want to set in `freemarkerVariables`. You've shown one example which is setting `base` to `http://127.0.0.1:80`. What other variables would you like to be able to set and what values might they have? I am asking because the values in `freemarkerVariables` can be anything (it's a Map<String, Object>) and `application.properties` or `application.yml` aren't a good way to set some types of `Object`. As I said above, it's only suited to setting maps, lists and scalar values.
If you want to configure something that's more complex, or you just want to do this without an enhancement to Spring Boot, then registering a `BeanPostProcessor` for the `FreeMarkerConfigurer` or `FreeMarkerConfigurationFactoryBean` may be what you need to do.
**Comment From: ohalo**
ok , i understand you,because application.properties or application.yml aren't a good way to set some types of Object,thank you
**Comment From: wilkinsona**
> because application.properties or application.yml aren't a good way to set some types of Object
Yes, that's right. Can you please share some examples of the values that you would like to set?
**Comment From: ohalo**
i want set String values ,not Object value,like: url(static resource url:img,js,css;and other system's url)
**Comment From: philwebb**
@ohalo It's still not clear what properties you're setting and what you expect to happen. I think the best way to proceed is if you could provide a small focused sample application that shows the problem.
**Comment From: ohalo**
@druidDataSource com.alibaba.druid.pool.DruidDataSource @driverClass com.mysql.jdbc.Driver
freemarker
freemarker: settings: object_wrapper: com.control.back.halo.basic.utils.FreemarkerObjectWrapper datetime_format: yyyy-MM-dd HH:mm:ss date_format: yyyy-MM-dd time_format: HH:mm:ss template_update_delay : 0 freemarker-variables: base: http://127.0.0.1:8080 siteName: speedbuy druidDataSource: druidDataSource driverClass: driverClass
**Comment From: philwebb**
I'm not convinced that we should support this out of the box. AFAICT most of our other template engines don't have properties that we set. Mustache does have a `MustacheEnvironmentCollector` which allows Spring Environment variables to be accessed, but there is no `variables` section in its `@ConfigurationProperties`.
**Comment From: philwebb**
We feel like the properties is a little restrictive and a customizer might be a better option. We can use `FreemarkerConfigurationFactory.setFreemarkerVariables()`.
**Comment From: bclozel**
We could also consider a more general solution, for all `ViewResolvers` extending `UrlBasedViewResolver`. This class has a `UrlBasedViewResolver.setAttributesMap(Map<String, Object)` method that adds those attributes to all views resolved ([see javadoc](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/UrlBasedViewResolver.html#setAttributesMap-java.util.Map-)).
Those attributes will be available in the model when the template is being rendered.
**Comment From: spt-developer**
I want the customizer. Now I implement like this (no need to custom freemarker).
```java
public class FreeMarkerVariablesInterceptor extends HandlerInterceptorAdapter {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
modelAndView.addObject("key", new Value());
}
}
Comment From: ohalo
thanks
Comment From: moqi2011
A solution:
spring:
freemarker:
settings:
auto_import: "/spring.ftl as spring , /fragment/global.ftl as _global_"
global.ftl:
<#assign static_server = "http://127.0.0.1">
Use global variable:
${_global_.static_server}
Comment From: moqi2011
Or put global variable to ServletContext
public class HomeApp extends SpringBootServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext
(ServletContext servletContext) {
WebApplicationContext ctx = super.createRootApplicationContext(servletContext);
HomeAppConfig conf = ctx.getBean(HomeAppConfig.class);
servletContext.setAttribute("_config_",conf);
return ctx;
}
}
Comment From: ohalo
@moqi2011 thanks
Comment From: wo8335224
@Configuration
public class FreemarkerConfiguration extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
@Value("${ctx.static}")
private String ctx;
@Value("${ctx.custom}")
private Integer custom;
@Override
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = super.freeMarkerConfigurer();
Map<String, Object> sharedVariables = new HashMap<>();
if (custom != null && custom == 1) {
sharedVariables.put("sctx", ctx);
}
configurer.setFreemarkerVariables(sharedVariables);
return configurer;
}
}
workd for me
@ohalo
Comment From: bianjp
Spring Boot 2.0 breaks the solution provided by @wo8335224, as FreeMarkerWebConfiguration is replaced by FreeMarkerServletWebConfiguration, which is unfortunately package-private and thus cannot be subclassed.
A currently working solution is to configure freemarker.template.Configuration bean:
@Configuration
public class FreemarkerConfig {
public FreemarkerConfig(freemarker.template.Configuration configuration) throws TemplateModelException {
configuration.setSharedVariable("name", "whatever type of value");
}
}
Internally FreeMarkerConfigurer#setFreemarkerVariables delegates its work to freemarker.template.Configuration#setAllSharedVariables.
Comment From: satahippy
Hey @bianjp !
Yes, you can do this, but you'll lose some auto-configuration.
So better use BeanPostProcessor.
@Component
class FreeMarkerPostProcessor : BeanPostProcessor {
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
if (bean is FreeMarkerConfigurer) {
bean.setFreemarkerVariables(mapOf(
"globalVar" to "globalVarValue"
))
}
return bean
}
}
Comment From: kaliatech
Thanks @satahippy. Java version:
@Configuration
public class CustomFreeMarkerConfig implements BeanPostProcessor {
Object sharedWithAllFreeMarkerTemplatesObj = new Object();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
Map<String, Object> sharedVariables = new HashMap<>();
sharedVariables.put("globalVar", sharedWithAllFreeMarkerTemplatesObj);
configurer.setFreemarkerVariables(sharedVariables);
}
return bean;
}
}
[edit: my original comment confused Before/After Initialization)
I feel like there should be some easier, built-in way, of doing this, but I haven't found it yet. Related question on StackOverflow: https://stackoverflow.com/questions/52730368/how-to-make-globally-shared-objects-available-to-freemarker-templates-in-spring
Comment From: snicoll
To be able to use FreeMarkerConfigurationFactory in a customizer, we need a framework change. I've opened https://github.com/spring-projects/spring-framework/issues/23054
Comment From: mauromol
+1 for this. Today I encountered the same need.
Comment From: wilkinsona
I don't think this needs to be blocked on the Framework issue. While allowing the variables to be amended could be useful, being able to set them (overwriting anything already set) would still be a step forwards. As and when there's a Framework API that allows the variables to be amended, that would be an additional benefit of a customizer callback in Boot.