在Sping中,一般使用这样的元素来配置一个bean,Spring在创建容器的时候会扫描这些配置,根据配置创建对象存放于容器中,然后我们再从容器中取出,或者在配置其他bean的时候作为属性注入。使用bean配置的一个限制是我们必须遵循配置文件的XML Schema定义,这在大多数情况下不会出现问题。但是在一些情况下,我们希望实现更为灵活的bean配置。Spring为此提供了 Custom tag Support,也称为Extensible XML Authoring。通过这个拓展点,我们可以灵活定制自己需要的配置格式。
package extensiblexml.customtag;import org.springframework.beans.factory.support.BeanDefinitionBuilder;import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;import org.springframework.util.StringUtils;import org.w3c.dom.Element;import java.text.SimpleDateFormat;public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { protected Class getBeanClass(Element element) { return SimpleDateFormat.class; } @SuppressWarnings("deprecation")protected void doParse(Element element, BeanDefinitionBuilder bean) { // this will never be null since the schema explicitly requires that a value be supplied String pattern = element.getAttribute("pattern"); bean.addConstructorArg(pattern); // this however is an optional property String lenient = element.getAttribute("lenient"); if (StringUtils.hasText(lenient)) { bean.addPropertyValue("lenient", Boolean.valueOf(lenient)); } } }
package extensiblexml.example;import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class CoreNamespaceHandler extends NamespaceHandlerSupport{ @Override public void init() { this.registerBeanDefinitionParser("fileList", new FileListDefinitionParser()); this.registerBeanDefinitionParser("fileFilter", new FileFilterDefinitionParser()); }}
登录后复制
FileListDefinitionParser.java:
public class FileListDefinitionParserextends AbstractSingleBeanDefinitionParser{/** * The bean that is created for this tag element * * @param element The tag element * @return A FileListFactoryBean */@Overrideprotected Class> getBeanClass(Element element) {return FileListFactoryBean.class;} /** * Called when the fileList tag is to be parsed * * @param element The tag element * @param ctx The context in which the parsing is occuring * @param builder The bean definitions build to use */@Overrideprotected void doParse(Element element, ParserContext ctx, BeanDefinitionBuilder builder) {// Set the directory propertybuilder.addPropertyValue("directory", element.getAttribute("directory")); // Set the scopebuilder.setScope(element.getAttribute("scope")); // We want any parsing to occur as a child of this tag so we need to make// a new one that has this as it's owner/parentParserContext nestedCtx = new ParserContext(ctx.getReaderContext(), ctx.getDelegate(), builder.getBeanDefinition()); // Support for filtersElement exclusionElem = DomUtils.getChildElementByTagName(element, "fileFilter");if (exclusionElem != null) {// Just make a new Parser for each one and let the parser do the workFileFilterDefinitionParser ff = new FileFilterDefinitionParser();builder.addPropertyValue("filters", ff.parse(exclusionElem, nestedCtx));} // Support for nested fileListList fileLists = DomUtils.getChildElementsByTagName(element, "fileList");// Any objects that created will be placed in a ManagedList// so Spring does the bulk of the resolution work for usManagedList
登录后复制
FileFilterDefinitionParser.java
public class FileFilterDefinitionParserextends AbstractSingleBeanDefinitionParser{ /** * The bean that is created for this tag element * * @param element The tag element * @return A FileFilterFactoryBean */@Overrideprotected Class> getBeanClass(Element element) {return FileFilterFactoryBean.class;} /** * Called when the fileFilter tag is to be parsed * * @param element The tag element * @param ctx The context in which the parsing is occuring * @param builder The bean definitions build to use */@Overrideprotected void doParse(Element element, ParserContext ctx, BeanDefinitionBuilder builder) { // Set the scopebuilder.setScope(element.getAttribute("scope")); try {// All of the filters will eventually end up in this list// We use a 'ManagedList' and not a regular list because anything// placed in a ManagedList object will support all of Springs// functionalities and scopes for us, we dont' have to code anything// in terms of reference lookups, EL, etcManagedList filters = new ManagedList(); // For each child node of the fileFilter tag, parse it and place it// in the filtes listNodeList nl = element.getChildNodes();for (int i=0; i>{ private final List filters = new ArrayList(); @Overridepublic Collection getObject() throws Exception {return filters;} @Overridepublic Class> getObjectType() {return Collection.class;} @Overridepublic boolean isSingleton() {return false;} /** * Go through the list of filters and convert the String ones * (the ones that were set with and make them NameFileFilters */public void setFilters(Collection filterList) {for (Object o : filterList) {if (o instanceof String) {filters.add(new NameFileFilter(o.toString()));}else if (o instanceof FileFilter) {filters.add((FileFilter)o);}}} }}
登录后复制
DefinitionParserUtil.java:
package extensiblexml.example; import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.config.BeanDefinitionHolder;import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.beans.factory.support.ManagedList;import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;import org.springframework.beans.factory.xml.ParserContext;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.w3c.dom.Element;import org.w3c.dom.Node; public class DefinitionParserUtil { /** * Parses the children of the passed in ParentNode for the following tags: * * value * ref * idref * bean * property * *custom* * * * The value tag works with Spring EL even in a Spring Batch scope="step" * * @param objects The list of resultings objects from the parsing (passed in for recursion purposes) * @param parentNode The node who's children should be parsed * @param ctx The ParserContext to use * @param parentBean The BeanDefinition of the bean who is the parent of the parsed bean * (i.e. the Bean that is the parentNode) * @param scope The scope to execute in. Checked if 'step' to provide Spring EL * support in a Spring Batch env * @throws Exception */public static void parseLimitedList(ManagedList objects, Node node,ParserContext ctx, BeanDefinition parentBean, String scope)throws Exception{parseLimitedList(objects, node, ctx, parentBean, scope, true);} /** * Parses the children of the passed in ParentNode for the following tags: * * value * ref * idref * bean * property * *custom* * * * The value tag works with Spring EL even in a Spring Batch scope="step" * * @param objects The list of resultings objects from the parsing (passed in for recursion purposes) * @param parentNode The node who's children should be parsed * @param ctx The ParserContext to use * @param parentBean The BeanDefinition of the bean who is the parent of the parsed bean * (i.e. the Bean that is the parentNode) * @param scope The scope to execute in. Checked if 'step' to provide Spring EL * support in a Spring Batch env * @param supportCustomTags Should we support custom tags within our tags? * @throws Exception */@SuppressWarnings("deprecation")public static void parseLimitedList(ManagedList objects, Node node,ParserContext ctx, BeanDefinition parentBean, String scope, boolean supportCustomTags)throws Exception{// Only worry about element nodesif (node.getNodeType() == Node.ELEMENT_NODE) {Element elem = (Element)node;String tagName = node.getLocalName(); if (tagName.equals("value")) {String val = node.getTextContent();// to get around an issue with Spring Batch not parsing Spring EL// we will do it for themif (scope.equals("step")&& (val.startsWith("#{") && val.endsWith("}"))&& (!val.startsWith("#{jobParameters"))){// Set up a new EL parserExpressionParser parser = new SpelExpressionParser();// Parse the valueExpression exp = parser.parseExpression(val.substring(2, val.length()-1));// Place the results in the list of created objectsobjects.add(exp.getValue());}else {// Otherwise, just treat it as a normal value tagobjects.add(val);}}// Either of these is a just a lookup of an existing beanelse if (tagName.equals("ref") || tagName.equals("idref")) {objects.add(ctx.getRegistry().getBeanDefinition(node.getTextContent()));}// We need to create the beanelse if (tagName.equals("bean")) {// There is no quick little util I could find to create a bean// on the fly programmatically in Spring and still support all// Spring functionality so basically I mimic what Spring actually// does but on a smaller scale. Everything Spring allows is// still supported // Create a factory to make the beanDefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// Set up a parser for the beanBeanDefinitionParserDelegate pd = new BeanDefinitionParserDelegate(ctx.getReaderContext());// Parse the bean get its information, now in a DefintionHolderBeanDefinitionHolder bh = pd.parseBeanDefinitionElement(elem, parentBean);// Register the bean will all the other beans Spring is aware ofBeanDefinitionReaderUtils.registerBeanDefinition(bh, beanFactory);// Get the bean from the factory. This will allows Spring// to do all its work (EL processing, scope, etc) and give us// the actual bean itselfObject bean = beanFactory.getBean(bh.getBeanName());objects.add(bean);}/* * This is handled a bit differently in that it actually sets the property * on the parent bean for us based on the property */else if (tagName.equals("property")) {BeanDefinitionParserDelegate pd = new BeanDefinitionParserDelegate(ctx.getReaderContext());// This method actually set eh property on the parentBean for us so// we don't have to add anything to the objects objectpd.parsePropertyElement(elem, parentBean);}else if (supportCustomTags) {// handle custom tagBeanDefinitionParserDelegate pd = new BeanDefinitionParserDelegate(ctx.getReaderContext());BeanDefinition bd = pd.parseCustomElement(elem, parentBean);objects.add(bd);}}}}