multiple tiles containers
Posted on 2020-07-09 17:00 in Java
其实使用 spring mvc theme 也可以实现同样的需求,不过还是用 tiles multiple container 更加灵活一些。
multiple-containers
https://tiles.apache.org/framework/tutorial/advanced/multiple-containers.html
一套代码,多个site显示不一样
- 新增一个 Servlet Filter:通过 request 的域名来判断是那个站点,request.setAttribute
- 多个 tiles container
- 新建 tilesView,通过站点来判断,用不同的 tiles container 来输出
- 加载 definition 的时候修改来实现不同的站点不同的 definition
SiteFilter
public class SiteFilter implements Filter {
public static final String SITE_FILTER_ATTRIBUTE = "SITE_FILTER_ATTRIBUTE";
public static final String EXAMPLE1_SITE = "example1";
public static final String EXAMPLE2_SITE = "example2";
public static final String EXAMPLE1_SITE_DOMAIN = "example1.com";
public static final String EXAMPLE2_SITE_DOMAIN = "example2.com";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request.getAttribute("SITE_FILTER_ATTRIBUTE") != null) {
chain.doFilter(request, response);
} else {
String site = null;
if (request.getServerName().equals(EXAMPLE1_SITE_DOMAIN)){
site = EXAMPLE1_SITE;
} else if (request.getServerName().equals(EXAMPLE2_SITE_DOMAIN)) {
site = EXAMPLE2_SITE;
}
if (site == null) {
site = request.getParameter("site");
}
if (EXAMPLE1_SITE.equals(site)) {
request.setAttribute(SITE_FILTER_ATTRIBUTE, EXAMPLE1_SITE);
} else if (EXAMPLE2_SITE.equals(site)){
request.setAttribute(SITE_FILTER_ATTRIBUTE, EXAMPLE2_SITE);
} else { // 默认是 EXAMPLE1_SITE
request.setAttribute(SITE_FILTER_ATTRIBUTE, EXAMPLE1_SITE);
}
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
SiteTilesView
把默认的 org.springframework.web.servlet.view.tiles3.TilesView
替换为自己写的 SiteTilesView
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass">
<value>
com.example.web.tiles.SiteTilesView
</value>
</property>
</bean>
在 TilesView 上修改,根据 filter 中设置的 SITE_FILTER_ATTRIBUTE,使用不同的 Tiles Container 来 render。
public class SiteTilesView extends AbstractUrlBasedView {
private Map<String, Renderer> siteRendererMap;
private boolean exposeJstlAttributes = true;
private boolean alwaysInclude = false;
private ApplicationContext applicationContext;
/**
* Whether to expose JSTL attributes. By default set to {@code true}.
*
* @see JstlUtils#exposeLocalizationContext(RequestContext)
*/
protected void setExposeJstlAttributes(boolean exposeJstlAttributes) {
this.exposeJstlAttributes = exposeJstlAttributes;
}
/**
* Specify whether to always include the view rather than forward to it.
* <p>Default is "false". Switch this flag on to enforce the use of a
* Servlet include, even if a forward would be possible.
*
* @see TilesViewResolver#setAlwaysInclude
* @since 4.1.2
*/
public void setAlwaysInclude(boolean alwaysInclude) {
this.alwaysInclude = alwaysInclude;
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
ServletContext servletContext = getServletContext();
Assert.state(servletContext != null, "No ServletContext");
this.applicationContext = ServletUtil.getApplicationContext(servletContext);
if (this.siteRendererMap == null) {
siteRendererMap = new HashMap<>(4);
TilesContainer defaultContainer = TilesAccess.getContainer(this.applicationContext);
Renderer defaultRenderer = new DefinitionRenderer(defaultContainer);
siteRendererMap.put("example1", defaultRenderer);
TilesContainer fiContainer = TilesAccess.getContainer(this.applicationContext, "example2");
Renderer fiRenderer = new DefinitionRenderer(fiContainer);
siteRendererMap.put("example2", fiRenderer);
}
}
@Override
public boolean checkResource(final Locale locale) throws Exception {
Assert.state(this.siteRendererMap != null, "No Renderer set");
HttpServletRequest servletRequest = null;
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
}
String site = (String) requestAttributes.getAttribute(SiteFilter.SITE_FILTER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
Request request = new ServletRequest(this.applicationContext, servletRequest, null) {
@Override
public Locale getRequestLocale() {
return locale;
}
};
Renderer renderer = siteRendererMap.get(site);
return renderer.isRenderable(getUrl(), request);
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
Assert.state(this.siteRendererMap != null, "No Renderer set");
exposeModelAsRequestAttributes(model, request);
if (this.exposeJstlAttributes) {
JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
}
if (this.alwaysInclude) {
request.setAttribute(AbstractRequest.FORCE_INCLUDE_ATTRIBUTE_NAME, true);
}
String site = (String) request.getAttribute(SiteFilter.SITE_FILTER_ATTRIBUTE);
Request tilesRequest = createTilesRequest(request, response);
Renderer renderer = siteRendererMap.get(site);
renderer.render(getUrl(), tilesRequest);
}
/**
* Create a Tiles {@link Request}.
* <p>This implementation creates a {@link ServletRequest}.
*
* @param request the current request
* @param response the current response
* @return the Tiles request
*/
protected Request createTilesRequest(final HttpServletRequest request, HttpServletResponse response) {
return new ServletRequest(this.applicationContext, request, response) {
@Override
public Locale getRequestLocale() {
return RequestContextUtils.getLocale(request);
}
};
}
}
SiteSpringTilesConfigurer
系统默认的 org.springframework.web.servlet.view.tiles3.TilesConfigurer
只能初始化一个 default 的 container,新建一个 SiteSpringTilesConfigurer
public class SiteSpringTilesConfigurer implements ServletContextAware, InitializingBean, DisposableBean {
private static final boolean tilesElPresent =
ClassUtils.isPresent("org.apache.tiles.el.ELAttributeEvaluator", SiteSpringTilesConfigurer.class.getClassLoader());
protected final Log logger = LogFactory.getLog(getClass());
private TilesInitializer tilesInitializer;
private String[] definitions;
private boolean checkRefresh = false;
private boolean validateDefinitions = true;
private Class<? extends DefinitionsFactory> definitionsFactoryClass;
private Class<? extends PreparerFactory> preparerFactoryClass;
private boolean useMutableTilesContainer = false;
private ServletContext servletContext;
private String containerKey;
/**
* Configure Tiles using a custom TilesInitializer, typically specified as an inner bean.
* <p>Default is a variant of {@link org.apache.tiles.startup.DefaultTilesInitializer},
* respecting the "definitions", "preparerFactoryClass" etc properties on this configurer.
* <p><b>NOTE: Specifying a custom TilesInitializer effectively disables all other bean
* properties on this configurer.</b> The entire initialization procedure is then left
* to the TilesInitializer as specified.
*/
public void setTilesInitializer(TilesInitializer tilesInitializer) {
this.tilesInitializer = tilesInitializer;
}
/**
* Set the Tiles definitions, i.e. the list of files containing the definitions.
* Default is "/WEB-INF/tiles.xml".
*/
public void setDefinitions(String... definitions) {
this.definitions = definitions;
}
/**
* Set whether to check Tiles definition files for a refresh at runtime.
* Default is "false".
*/
public void setCheckRefresh(boolean checkRefresh) {
this.checkRefresh = checkRefresh;
}
/**
* Set whether to validate the Tiles XML definitions. Default is "true".
*/
public void setValidateDefinitions(boolean validateDefinitions) {
this.validateDefinitions = validateDefinitions;
}
/**
* Set the {@link org.apache.tiles.definition.DefinitionsFactory} implementation to use.
* Default is {@link org.apache.tiles.definition.UnresolvingLocaleDefinitionsFactory},
* operating on definition resource URLs.
* <p>Specify a custom DefinitionsFactory, e.g. a UrlDefinitionsFactory subclass,
* to customize the creation of Tiles Definition objects. Note that such a
* DefinitionsFactory has to be able to handle {@link java.net.URL} source objects,
* unless you configure a different TilesContainerFactory.
*/
public void setDefinitionsFactoryClass(Class<? extends DefinitionsFactory> definitionsFactoryClass) {
this.definitionsFactoryClass = definitionsFactoryClass;
}
/**
* Set the {@link org.apache.tiles.preparer.factory.PreparerFactory} implementation to use.
* Default is {@link org.apache.tiles.preparer.factory.BasicPreparerFactory}, creating
* shared instances for specified preparer classes.
* <p>Specify {@link SimpleSpringPreparerFactory} to autowire
* {@link org.apache.tiles.preparer.ViewPreparer} instances based on specified
* preparer classes, applying Spring's container callbacks as well as applying
* configured Spring BeanPostProcessors. If Spring's context-wide annotation-config
* has been activated, annotations in ViewPreparer classes will be automatically
* detected and applied.
* <p>Specify {@link SpringBeanPreparerFactory} to operate on specified preparer
* <i>names</i> instead of classes, obtaining the corresponding Spring bean from
* the DispatcherServlet's application context. The full bean creation process
* will be in the control of the Spring application context in this case,
* allowing for the use of scoped beans etc. Note that you need to define one
* Spring bean definition per preparer name (as used in your Tiles definitions).
*
* @see SimpleSpringPreparerFactory
* @see SpringBeanPreparerFactory
*/
public void setPreparerFactoryClass(Class<? extends PreparerFactory> preparerFactoryClass) {
this.preparerFactoryClass = preparerFactoryClass;
}
/**
* Set whether to use a MutableTilesContainer (typically the CachingTilesContainer
* implementation) for this application. Default is "false".
*
* @see org.apache.tiles.mgmt.MutableTilesContainer
* @see org.apache.tiles.impl.mgmt.CachingTilesContainer
*/
public void setUseMutableTilesContainer(boolean useMutableTilesContainer) {
this.useMutableTilesContainer = useMutableTilesContainer;
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void setContainerKey(String containerKey) {
this.containerKey = containerKey;
}
/**
* Creates and exposes a TilesContainer for this web application,
* delegating to the TilesInitializer.
*
* @throws TilesException in case of setup failure
*/
@Override
public void afterPropertiesSet() throws TilesException {
ApplicationContext preliminaryContext = new SpringWildcardServletTilesApplicationContext(this.servletContext);
if (this.tilesInitializer == null) {
this.tilesInitializer = new SiteSpringTilesInitializer();
}
this.tilesInitializer.initialize(preliminaryContext);
}
/**
* Removes the TilesContainer from this web application.
*
* @throws TilesException in case of cleanup failure
*/
@Override
public void destroy() throws TilesException {
this.tilesInitializer.destroy();
}
private static class CompositeELResolverImpl extends CompositeELResolver {
public CompositeELResolverImpl() {
add(new ScopeELResolver());
add(new TilesContextELResolver(new TilesContextBeanELResolver()));
add(new TilesContextBeanELResolver());
add(new ArrayELResolver(false));
add(new ListELResolver(false));
add(new MapELResolver(false));
add(new ResourceBundleELResolver());
add(new BeanELResolver(false));
}
}
private class SiteSpringTilesInitializer extends DefaultTilesInitializer {
@Override
protected String getContainerKey(
ApplicationContext applicationContext) {
return containerKey;
}
@Override
protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context) {
return new SiteSpringTilesContainerFactory();
}
}
private class SiteSpringTilesContainerFactory extends BasicTilesContainerFactory {
@Override
protected TilesContainer createDecoratedContainer(TilesContainer originalContainer, ApplicationContext context) {
return (useMutableTilesContainer ? new CachingTilesContainer(originalContainer) : originalContainer);
}
@Override
protected List<ApplicationResource> getSources(ApplicationContext applicationContext) {
if (definitions != null) {
List<ApplicationResource> result = new LinkedList<ApplicationResource>();
for (String definition : definitions) {
Collection<ApplicationResource> resources = applicationContext.getResources(definition);
if (resources != null) {
result.addAll(resources);
}
}
return result;
} else {
return super.getSources(applicationContext);
}
}
@Override
protected BaseLocaleUrlDefinitionDAO instantiateLocaleDefinitionDao(ApplicationContext applicationContext,
LocaleResolver resolver) {
BaseLocaleUrlDefinitionDAO dao = new ModifyDefinitionDAO(applicationContext);
if (checkRefresh && dao instanceof CachingLocaleUrlDefinitionDAO) {
((CachingLocaleUrlDefinitionDAO) dao).setCheckRefresh(true);
}
return dao;
}
@Override
protected DefinitionsReader createDefinitionsReader(ApplicationContext context) {
DigesterDefinitionsReader reader = (DigesterDefinitionsReader) super.createDefinitionsReader(context);
reader.setValidating(validateDefinitions);
return reader;
}
@Override
protected DefinitionsFactory createDefinitionsFactory(ApplicationContext applicationContext,
LocaleResolver resolver) {
if (definitionsFactoryClass != null) {
DefinitionsFactory factory = BeanUtils.instantiate(definitionsFactoryClass);
if (factory instanceof ApplicationContextAware) {
((ApplicationContextAware) factory).setApplicationContext(applicationContext);
}
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(factory);
if (bw.isWritableProperty("localeResolver")) {
bw.setPropertyValue("localeResolver", resolver);
}
if (bw.isWritableProperty("definitionDAO")) {
bw.setPropertyValue("definitionDAO", createLocaleDefinitionDao(applicationContext, resolver));
}
return factory;
} else {
return super.createDefinitionsFactory(applicationContext, resolver);
}
}
@Override
protected PreparerFactory createPreparerFactory(ApplicationContext context) {
if (preparerFactoryClass != null) {
return BeanUtils.instantiate(preparerFactoryClass);
} else {
return super.createPreparerFactory(context);
}
}
@Override
protected LocaleResolver createLocaleResolver(ApplicationContext context) {
return new SpringLocaleResolver();
}
@Override
protected AttributeEvaluatorFactory createAttributeEvaluatorFactory(ApplicationContext context,
LocaleResolver resolver) {
AttributeEvaluator evaluator;
if (tilesElPresent && JspFactory.getDefaultFactory() != null) {
evaluator = new TilesElActivator().createEvaluator();
} else {
evaluator = new DirectAttributeEvaluator();
}
return new BasicAttributeEvaluatorFactory(evaluator);
}
}
private class TilesElActivator {
public AttributeEvaluator createEvaluator() {
ELAttributeEvaluator evaluator = new ELAttributeEvaluator();
evaluator.setExpressionFactory(
JspFactory.getDefaultFactory().getJspApplicationContext(servletContext).getExpressionFactory());
evaluator.setResolver(new CompositeELResolverImpl());
return evaluator;
}
}
}
ModifyDefinitionDAO 中修改 tiles.xml 定义,也可以 load 一个新的xml 来覆盖 tiles.xml 中的定义
public class ModifyDefinitionDAO extends ResolvingLocaleUrlDefinitionDAO {
public ModifyDefinitionDAO(ApplicationContext applicationContext) {
super(applicationContext);
}
@Override
protected Map<String, Definition> loadDefinitionsFromResource(ApplicationResource resource) {
Map<String, Definition> defsMap = super.loadDefinitionsFromResource(resource);
// modify DefinitionDAO
if (defsMap != null) {
modifyDefinition(defsMap);
}
return defsMap;
}
protected void modifyDefinition(Map<String, Definition> defsMap) {
// modify login
Definition loginDefinition = defsMap.get("login");
loginDefinition.getTemplateAttribute().setValue("/WEB-INF/jsp/example2login.jsp");
// modify baselayout
Definition layoutDefinition = defsMap.get("baselayout");
layoutDefinition.getTemplateAttribute().setValue("/WEB-INF/jsp/example2layout.jsp");
}
}
结论
以上代码可以完成目标,不过感觉不够灵活,不知道除了 theme 以外是否还有其他方案。