您好,欢迎访问一九零五行业门户网

Java怎么自定义Spring配置标签

引言:
在sping中,一般使用60e23eb984d18edbb092da6b8f295aba这样的元素来配置一个bean,spring在创建容器的时候会扫描这些配置,根据配置创建对象存放于容器中,然后我们再从容器中取出,或者在配置其他bean的时候作为属性注入。使用bean配置的一个限制是我们必须遵循配置文件的xml schema定义,这在大多数情况下不会出现问题。但是在一些情况下,我们希望实现更为灵活的bean配置。spring为此提供了 custom tag support,也称为extensible xml authoring。通过这个拓展点,我们可以灵活定制自己需要的配置格式。
例如,如果我们使用了责任链设计应用程序,那么我们可能希望用下面的方式来配置责任链:
<chain id="orderchain" class="foo.bar"> <handler> handler1</handler> <hadnler> handler2</handler></chain>
档spring创建容器时,扫描到这样的元素的时候,会根据我们事先的定义实例化一个责任链对象,并填充属性。因此,这种特殊的<chain>标签可以作为<bean>标签以外的另一种形式。借助spring的custome tag,我们完全可以实现这样的bean配置。在产品级的应用框架中,可以实现更为复杂的定制标签元素。作为一个入门级别的介绍,我们定义一个用于配置日期格式化的一个类simpledateformat。当然,使用传统的<bean>完全够用,我们这里只是作为例子。
一个helloworld例子:
定制标签的第一步是要定义标签元素的xml结构,也就是采用xsd来元素我们要定制的元素的结构时怎样的。
我们定义如下一个简单的xsd:
<?xml version="1.0" encoding="utf-8"?><xsd:schema xmlns="http://www.mycompany.com/schema/myns" xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:beans="http://www.springframework.org/schema/beans" targetnamespace="http://www.mycompany.com/schema/myns" elementformdefault="qualified" attributeformdefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:element name="dateformat"> <xsd:complextype> <xsd:complexcontent> <xsd:extension base="beans:identifiedtype"> <xsd:attribute name="lenient" type="xsd:boolean"/> <xsd:attribute name="pattern" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexcontent> </xsd:complextype> </xsd:element></xsd:schema>
在这个xsd定义中,有一个标签叫dateformat,这就是我们用来替换bean标签的自定义标签。注意到我们导入了spring本身的beans命名空间,并且在beans:identifiedtype基础之上定义dateformat标签。也就是我们这个标签可以像<bean>标签一样拥有id属性。同时我们增加了两个属性lenient和pattern。这有点继承的味道。
定义完xsd之后,我们要告诉spring遇到这样的标记(命名空间+元素名称)时,如何创建对象。spring中,完成这个任务的是namespacehandler。因此我们需要提供一个namespacehandler实现来处理自定义的<dateformat>标签元素。
一个简单的实现如下:
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<simpledateformat> 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:
<?xml version="1.0" encoding="utf-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:myns="http://www.mycompany.com/schema/myns" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd" > <myns:dateformat id="defaultdateformat" pattern="yyyy-mm-dd hh:mm" lenient="true" /></beans>
在eclipse中,整个项目结构如下图:
最后我们写个测试类测试一下能否工作:
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())); } }
一切正常,输出如下:
更实用的例子
第一个例子主要是为了举例,在实际中用处不大,我们接着来看一个更复杂的自定义标签。我们自定义一个<filelist>标签,当spring扫描到这个标签的时候,创建一个指定目录下的file类的集合。另外,可以使用<filefilter>对该目录的文件进行过滤。
如下:
<core-commons:filelist id="xmllist" directory="src/extensiblexml/example"> <core-commons:filefilter> <bean class="org.apache.commons.io.filefilter.regexfilefilter"> <constructor-arg value=".*.java" /> </bean> </core-commons:filefilter></core-commons:filelist>
上面的bean定义中,我们从“src/extensible/example”目录中筛选出java源码文件。
使用下面的测试迭代输出文件名:
@suppresswarnings("unchecked")list<file> filelist = (list<file>) context.getbean("xmllist");for (file file : filelist) { system.out.println(file.getname());}
输出结果如下:
根据第一个例子中的步骤,各部分配置及代码如下:
core-commons-1.0.xsd:
<?xml version="1.0" encoding="utf-8"?><xsd:schema xmlns="http://www.example.com/schema/core-commons-1.0" targetnamespace="http://www.example.com/schema/core-commons-1.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:beans="http://www.springframework.org/schema/beans" elementformdefault="qualified" attributeformdefault="unqualified" version="1.0"> <xsd:import namespace="http://www.springframework.org/schema/beans" schemalocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"/> <xsd:element name="filelist"> <xsd:complextype> <xsd:complexcontent> <xsd:extension base="beans:identifiedtype"> <xsd:sequence> <xsd:element ref="filefilter" minoccurs="0" maxoccurs="1"/> <xsd:element ref="filelist" minoccurs="0" maxoccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="directory" type="xsd:string"/> <xsd:attribute name="scope" type="xsd:string"/> </xsd:extension> </xsd:complexcontent> </xsd:complextype> </xsd:element> <xsd:element name="filefilter"> <xsd:complextype> <xsd:complexcontent> <xsd:extension base="beans:identifiedtype"> <xsd:group ref="limitedtype"/> <xsd:attribute name="scope" type="xsd:string"/> </xsd:extension> </xsd:complexcontent> </xsd:complextype> </xsd:element> <xsd:group name="limitedtype"> <xsd:sequence> <xsd:choice minoccurs="1" maxoccurs="unbounded"> <xsd:element ref="beans:bean"/> <xsd:element ref="beans:ref"/> <xsd:element ref="beans:idref"/> <xsd:element ref="beans:value"/> <xsd:any minoccurs="0"/> </xsd:choice> </xsd:sequence> </xsd:group></xsd:schema>
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 filelistdefinitionparser extends abstractsinglebeandefinitionparser{ /** * the bean that is created for this tag element * * @param element the tag element * @return a filelistfactorybean */ @override protected 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 */ @override protected void doparse(element element, parsercontext ctx, beandefinitionbuilder builder) { // set the directory property builder.addpropertyvalue("directory", element.getattribute("directory")); // set the scope builder.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/parent parsercontext nestedctx = new parsercontext(ctx.getreadercontext(), ctx.getdelegate(), builder.getbeandefinition()); // support for filters element exclusionelem = domutils.getchildelementbytagname(element, "filefilter"); if (exclusionelem != null) { // just make a new parser for each one and let the parser do the work filefilterdefinitionparser ff = new filefilterdefinitionparser(); builder.addpropertyvalue("filters", ff.parse(exclusionelem, nestedctx)); } // support for nested filelist list<element> 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 us managedlist<object> nestedfiles = new managedlist<object>(); if (filelists.size() > 0) { // just make a new parser for each one and let them do the work filelistdefinitionparser 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 process nodelist nl = element.getchildnodes(); for (int i=0; i<nl.getlength(); i++) { // parse each child tag we find in the correct scope but we // won't support custom tags at this point as it coudl destablize things definitionparserutil.parselimitedlist(nestedfiles, nl.item(i), ctx, builder.getbeandefinition(), element.getattribute("scope"), false); } } catch (exception e) { throw new runtimeexception(e); } // set the nestedfiles in the properties so it is set on the factorybean builder.addpropertyvalue("nestedfiles", nestedfiles); } public static class filelistfactorybean implements factorybean<collection<file>> { string directory; private collection<filefilter> filters; private collection<file> nestedfiles; @override public collection<file> getobject() throws exception { // these can be an array list because the directory will have unique's and the nested is already only unique's collection<file> files = new arraylist<file>(); collection<file> results = new arraylist<file>(0); if (directory != null) { // get all the files in the directory file 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 files if (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 list if (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; } @override public class<?> getobjecttype() { return collection.class; } @override public boolean issingleton() { return false; } public void setdirectory(string dir) { this.directory = dir; } public void setfilters(collection<filefilter> 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<collection<file>> nestedfiles) { this.nestedfiles = new hashset<file>(); // keep the list unique for (collection<file> nested : nestedfiles) { this.nestedfiles.addall(nested); } } }}
filefilterdefinitionparser.java
public class filefilterdefinitionparser extends abstractsinglebeandefinitionparser{ /** * the bean that is created for this tag element * * @param element the tag element * @return a filefilterfactorybean */ @override protected 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 */ @override protected void doparse(element element, parsercontext ctx, beandefinitionbuilder builder) { // set the scope builder.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, etc managedlist<object> filters = new managedlist<object>(); // for each child node of the filefilter tag, parse it and place it // in the filtes list nodelist nl = element.getchildnodes(); for (int i=0; i<nl.getlength(); i++) { definitionparserutil.parselimitedlist(filters, nl.item(i), ctx, builder.getbeandefinition(), element.getattribute("scope")); } // add the filtes to the list of properties (this is applied // to the factory beans setfilters below) builder.addpropertyvalue("filters", filters); } catch (exception e) { throw new runtimeexception(e); } } public static class filefilterfactorybean implements factorybean<collection<filefilter>> { private final list<filefilter> filters = new arraylist<filefilter>(); @override public collection<filefilter> getobject() throws exception { return filters; } @override public class<?> getobjecttype() { return collection.class; } @override public boolean issingleton() { return false; } /** * go through the list of filters and convert the string ones * (the ones that were set with <value> and make them namefilefilters */ public void setfilters(collection<object> 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: * <br/> * value * ref * idref * bean * property * *custom* * <p/> * * 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<object> 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: * <br/> * value * ref * idref * bean * property * *custom* * <p/> * * 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<object> objects, node node, parsercontext ctx, beandefinition parentbean, string scope, boolean supportcustomtags) throws exception { // only worry about element nodes if (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 them if (scope.equals("step") && (val.startswith("#{") && val.endswith("}")) && (!val.startswith("#{jobparameters")) ) { // set up a new el parser expressionparser parser = new spelexpressionparser(); // parse the value expression exp = parser.parseexpression(val.substring(2, val.length()-1)); // place the results in the list of created objects objects.add(exp.getvalue()); } else { // otherwise, just treat it as a normal value tag objects.add(val); } } // either of these is a just a lookup of an existing bean else if (tagname.equals("ref") || tagname.equals("idref")) { objects.add(ctx.getregistry().getbeandefinition(node.gettextcontent())); } // we need to create the bean else 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 bean defaultlistablebeanfactory beanfactory = new defaultlistablebeanfactory(); // set up a parser for the bean beandefinitionparserdelegate pd = new beandefinitionparserdelegate(ctx.getreadercontext()); // parse the bean get its information, now in a defintionholder beandefinitionholder bh = pd.parsebeandefinitionelement(elem, parentbean); // register the bean will all the other beans spring is aware of beandefinitionreaderutils.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 itself object 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 object pd.parsepropertyelement(elem, parentbean); } else if (supportcustomtags) { // handle custom tag beandefinitionparserdelegate 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配置标签的详细内容。
其它类似信息

推荐信息