Maison >Java >javaDidacticiel >Comment personnaliser les balises de configuration Spring en Java
Introduction :
Dans Spring, des éléments tels que
Par exemple, si nous utilisons une chaîne de responsabilité pour concevoir une application, alors nous pouvons vouloir configurer la chaîne de responsabilité de la manière suivante :
<chain id="orderChain" class="foo.bar"> <handler> handler1</handler> <hadnler> handler2</handler> </chain>
Lorsque Spring crée un conteneur, lorsqu'un tel élément est scanné, il sera configuré selon notre a priori. La définition instancie un objet chaîne de responsabilité et renseigne les propriétés. Par conséquent, cette balise spéciale
Un exemple HelloWorld :
La première étape de la personnalisation des balises consiste à définir la structure XML de l'élément tag, à quoi elle ressemble lorsque l'on utilise XSD pour élémenter la structure de l'élément que nous souhaitons personnaliser.
Nous définissons un XSD simple comme suit :
<?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>
Dans cette définition XSD, il y a une balise appelée dateformat, qui est la balise personnalisée que nous utilisons pour remplacer la balise bean. Notez que nous avons importé le propre espace de noms des beans de Spring et défini la balise dateformat basée sur beans:identifiedType. C'est-à-dire que notre balise peut avoir l'attribut id comme la balise
Après avoir défini le XSD, nous devons indiquer à Spring comment créer un objet lorsqu'il rencontre une telle balise (espace de noms + nom de l'élément). Au printemps, NamespaceHandler est utilisé pour accomplir cette tâche. Par conséquent, nous devons fournir une implémentation de NamespaceHandler pour gérer l'élément de balise
Une implémentation simple est la suivante :
package extensiblexml.customtag; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class MyNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser()); } }
Nous avons enregistré un analyseur défini par Bean dans la méthode d'initialisation. Cet analyseur est utilisé pour analyser les balises de configuration personnalisées.
L'implémentation est la suivante :
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)); } } }
Le doParse de cet analyseur implémente la logique spécifique de l'analyse Avec les classes de support fournies par Spring, nous pouvons facilement terminer l'analyse. Les trois fichiers ci-dessus sont placés dans le même répertoire, c'est-à-dire que le fichier XSD et le code Java sont placés dans le même répertoire. Une fois le codage terminé, certains travaux de configuration doivent encore être effectués. Nous devons dire à Spring que nous allons utiliser un élément de balise personnalisé et indiquer à Spring comment analyser l'élément, sinon Spring n'est pas si intelligent. Deux fichiers de configuration sont requis ici. Au même niveau que le chemin racine du code, le matelas possède un fichier appelé META-INF. Et créez des classes nommées spring.handlers et spring.schemas à l'intérieur, qui sont utilisées pour indiquer à Spring la structure du document de la balise personnalisée et la classe qui l'analyse. Le contenu des deux fichiers est le suivant :
spring.handlers:
http://www.mycompany.com/schema/myns=extensiblexml.customtag.MyNamespaceHandler
Le côté gauche du signe égal est le targetNamespace dans l'attribut de définition XSD, le côté droit est le nom complet de NamespaceHandler.
spring.schemas:
http://www.mycompany.com/schema/myns/myns.xsd=extensiblexml/customtag/myns.xsd
Ensuite, configurez le bean comme d'habitude, aussi simplement Pour test, nous définissons un 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>
Dans Eclipse, toute la structure du projet est la suivante :
Enfin, nous écrivons une classe de test pour tester si cela peut fonctionner :
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())); } }
Everything est normal, la sortie est la suivante :
Des exemples plus pratiques
Le premier exemple est principalement destiné à l'illustration et n'est pas très utile en pratique. Passons à une balise personnalisée plus complexe. Nous personnalisons une balise
est la suivante :
<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>
Dans la définition du bean ci-dessus, nous filtrons les fichiers de code source Java du répertoire "src/extensible/example".
Utilisez le nom de fichier de sortie de l'itération de test suivant :
@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)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!