Java怎么自定义Spring配置标签

引言:

在Sping中,一般使用这样的元素来配置一个bean,Spring在创建容器的时候会扫描这些配置,根据配置创建对象存放于容器中,然后我们再从容器中取出,或者在配置其他bean的时候作为属性注入。使用bean配置的一个限制是我们必须遵循配置文件的XML Schema定义,这在大多数情况下不会出现问题。但是在一些情况下,我们希望实现更为灵活的bean配置。Spring为此提供了 Custom tag Support,也称为Extensible XML Authoring。通过这个拓展点,我们可以灵活定制自己需要的配置格式。

例如,如果我们使用了责任链设计应用程序,那么我们可能希望用下面的方式来配置责任链:

     handler1     handler2

登录后复制

档Spring创建容器时,扫描到这样的元素的时候,会根据我们事先的定义实例化一个责任链对象,并填充属性。因此,这种特殊的标签可以作为标签以外的另一种形式。借助Spring的Custome Tag,我们完全可以实现这样的bean配置。在产品级的应用框架中,可以实现更为复杂的定制标签元素。作为一个入门级别的介绍,我们定义一个用于配置日期格式化的一个类SimpleDateFormat。当然,使用传统的完全够用,我们这里只是作为例子。

一个HelloWorld例子:

立即学习“Java免费学习笔记(深入)”;

定制标签的第一步是要定义标签元素的XML结构,也就是采用XSD来元素我们要定制的元素的结构时怎样的。

我们定义如下一个简单的XSD:

                                                                                                                              

登录后复制

在这个XSD定义中,有一个标签叫dateformat,这就是我们用来替换bean标签的自定义标签。注意到我们导入了Spring本身的beans命名空间,并且在beans:identifiedType基础之上定义dateformat标签。也就是我们这个标签可以像标签一样拥有id属性。同时我们增加了两个属性lenient和pattern。这有点继承的味道。

定义完XSD之后,我们要告诉Spring遇到这样的标记(命名空间+元素名称)时,如何创建对象。Spring中,完成这个任务的是NamespaceHandler。因此我们需要提供一个NamespaceHandler实现来处理自定义的标签元素。

一个简单的实现如下:

package extensiblexml.customtag;import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class MyNamespaceHandler extends NamespaceHandlerSupport {public void init() {registerBeanDefinitionParser("dateformat",new SimpleDateFormatBeanDefinitionParser());} }

登录后复制

我们在初始化方法中注册了一个Bean定义的解析器,这个解析器就是用来解析定制的配置标签的。

其实现如下:

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));        }    } }

登录后复制

这个解析器的doParse中,实现了解析的具体逻辑,借助Spring提供的支持类,我们可以很轻松地完成解析。以上三个文件放在同一个目录下,即把XSD文件跟Java代码放在同一目录。编码完毕之后,还需要做一些配置工作。我们必须告诉Spring我们准备使用自定义的标签元素,告诉Spring如何解析元素,否则Spring没那么聪明。这里需要2个配置文件,在与代码根路径同一级别下,床垫一个叫META-INF的文件。并在里面创建名为spring.handlers和spring.schemas,用于告诉Spring自定义标签的文档结构以及解析它的类。两个文件内容分别如下:

spring.handlers:

http://www.mycompany.com/schema/myns=extensiblexml.customtag.MyNamespaceHandler

等号的左边是XSD定义中的targetNamespace属性,右边是NamespaceHandler的全称限定名。

spring.schemas:

http://www.mycompany.com/schema/myns/myns.xsd=extensiblexml/customtag/myns.xsd

然后像往常一样配置bean,作为简单的测试,我们定义一个bean:

 

登录后复制

在Eclipse中,整个项目结构如下图:

Java怎么自定义Spring配置标签

最后我们写个测试类测试一下能否工作:

package extensiblexml.customtag;import java.text.SimpleDateFormat;import java.util.Date;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");SimpleDateFormat format = (SimpleDateFormat) context.getBean("defaultDateFormat");System.out.println(format.format(new Date())); } }

登录后复制

一切正常,输出如下:

Java怎么自定义Spring配置标签

更实用的例子

第一个例子主要是为了举例,在实际中用处不大,我们接着来看一个更复杂的自定义标签。我们自定义一个标签,当Spring扫描到这个标签的时候,创建一个指定目录下的File类的集合。另外,可以使用对该目录的文件进行过滤。

如下:

            

登录后复制

上面的bean定义中,我们从“src/extensible/example”目录中筛选出java源码文件。

使用下面的测试迭代输出文件名:

@SuppressWarnings("unchecked")List fileList = (List) context.getBean("xmlList");for (File file : fileList) {System.out.println(file.getName());}

登录后复制

输出结果如下:

Java怎么自定义Spring配置标签

根据第一个例子中的步骤,各部分配置及代码如下:

core-commons-1.0.xsd:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

登录后复制

CoreNamespaceHandler.java:

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 nestedFiles = new ManagedList();if (fileLists.size() > 0) {// Just make a new Parser for each one and let them do the workFileListDefinitionParser fldp = new FileListDefinitionParser();for (Element fileListElem : fileLists) {nestedFiles.add(fldp.parse(fileListElem, nestedCtx));}} // Support for other tags that return File (value will be converted to file)try {// Go through any other tags we may find.  This does not mean we support// any tag, we support only what parseLimitedList will processNodeList nl = element.getChildNodes();for (int i=0; i>{ String directory;private Collection filters;private Collection nestedFiles; @Overridepublic Collection getObject() throws Exception {// These can be an array list because the directory will have unique's and the nested is already only unique'sCollection files = new ArrayList();Collection results = new ArrayList(0); if (directory != null) {// get all the files in the directoryFile dir = new File(directory);File[] dirFiles = dir.listFiles();if (dirFiles != null) {files = Arrays.asList(dirFiles);}} // If there are any files that were created from the nested tags,// add those to the list of filesif (nestedFiles != null) {files.addAll(nestedFiles);} // If there are filters we need to go through each filter// and see if the files in the list pass the filters.// If the files does not pass any one of the filters then it// will not be included in the listif (filters != null) {boolean add;for (File f : files) {add = true;for (FileFilter ff : filters) {if (!ff.accept(f)) {add = false;break;}}if (add) results.add(f);}return results;} return files;} @Overridepublic Class> getObjectType() {return Collection.class;} @Overridepublic boolean isSingleton() {return false;} public void setDirectory(String dir) {this.directory = dir;}public void setFilters(Collection filters) {this.filters = filters;} /** * What we actually get from the processing of the nested tags * is a collection of files within a collection so we flatten it and * only keep the uniques */public void setNestedFiles(Collection> nestedFiles) {this.nestedFiles = new HashSet(); // keep the list uniquefor (Collection nested : nestedFiles) {this.nestedFiles.addAll(nested);}} }}

登录后复制

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);}}}}

登录后复制

spring.schemas

http://www.mycompany.com/schema/myns/myns.xsd=extensiblexml/customtag/myns.xsd
http://www.example.com/schema/core-commons-1.0.xsd=extensiblexml/example/core-commons-1.0.xsd

spring.handlers

http://www.mycompany.com/schema/myns=extensiblexml.customtag.MyNamespaceHandler
http://www.example.com/schema/core-commons-1.0=extensiblexml.example.CoreNamespaceHandler

小结:

要自定义Spring的配置标签,需要一下几个步骤:

使用XSD定义XML配置中标签元素的结构(myns.XSD)

提供该XSD命名空间的处理类,它可以处理多个标签定义(MyNamespaceHandler.java)

为每个标签元素的定义提供解析类。(SimpleDateFormatBeanDefinitionParser.java)

两个特殊文件通知Spring使用自定义标签元素(spring.handlers 和spring.schemas)

以上就是Java怎么自定义Spring配置标签的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2627501.html

(0)
上一篇 2025年3月7日 01:06:12
下一篇 2025年3月6日 12:37:24

AD推荐 黄金广告位招租... 更多推荐

相关推荐

发表回复

登录后才能评论