Java自定義Spring配置標(biāo)簽
引言:
在Sping中,一般使用<bean>這樣的元素來(lái)配置一個(gè)bean,Spring在創(chuàng)建容器的時(shí)候會(huì)掃描這些配置,根據(jù)配置創(chuàng)建對(duì)象存放于容器中,然后我們?cè)購(gòu)娜萜髦腥〕?,或者在配置其他bean的時(shí)候作為屬性注入。使用bean配置的一個(gè)限制是我們必須遵循配置文件的XML Schema定義,這在大多數(shù)情況下不會(huì)出現(xiàn)問(wèn)題。但是在一些情況下,我們希望實(shí)現(xiàn)更為靈活的bean配置。Spring為此提供了 Custom tag Support,也稱(chēng)為Extensible XML Authoring。通過(guò)這個(gè)拓展點(diǎn),我們可以靈活定制自己需要的配置格式。
例如,如果我們使用了責(zé)任鏈設(shè)計(jì)應(yīng)用程序,那么我們可能希望用下面的方式來(lái)配置責(zé)任鏈:
<chain id="orderChain" class="foo.bar">
<handler> handler1</handler>
<hadnler> handler2</handler>
</chain>檔Spring創(chuàng)建容器時(shí),掃描到這樣的元素的時(shí)候,會(huì)根據(jù)我們事先的定義實(shí)例化一個(gè)責(zé)任鏈對(duì)象,并填充屬性。因此,這種特殊的<chain>標(biāo)簽可以作為<bean>標(biāo)簽以外的另一種形式。借助Spring的Custome Tag,我們完全可以實(shí)現(xiàn)這樣的bean配置。在產(chǎn)品級(jí)的應(yīng)用框架中,可以實(shí)現(xiàn)更為復(fù)雜的定制標(biāo)簽元素。作為一個(gè)入門(mén)級(jí)別的介紹,我們定義一個(gè)用于配置日期格式化的一個(gè)類(lèi)SimpleDateFormat。當(dāng)然,使用傳統(tǒng)的<bean>完全夠用,我們這里只是作為例子。
一個(gè)HelloWorld例子:
定制標(biāo)簽的第一步是要定義標(biāo)簽元素的XML結(jié)構(gòu),也就是采用XSD來(lái)元素我們要定制的元素的結(jié)構(gòu)時(shí)怎樣的。
我們定義如下一個(gè)簡(jiǎn)單的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>在這個(gè)XSD定義中,有一個(gè)標(biāo)簽叫dateformat,這就是我們用來(lái)替換bean標(biāo)簽的自定義標(biāo)簽。注意到我們導(dǎo)入了Spring本身的beans命名空間,并且在beans:identifiedType基礎(chǔ)之上定義dateformat標(biāo)簽。也就是我們這個(gè)標(biāo)簽可以像<bean>標(biāo)簽一樣擁有id屬性。同時(shí)我們?cè)黾恿藘蓚€(gè)屬性lenient和pattern。這有點(diǎn)繼承的味道。
定義完XSD之后,我們要告訴Spring遇到這樣的標(biāo)記(命名空間+元素名稱(chēng))時(shí),如何創(chuàng)建對(duì)象。Spring中,完成這個(gè)任務(wù)的是NamespaceHandler。因此我們需要提供一個(gè)NamespaceHandler實(shí)現(xiàn)來(lái)處理自定義的<dateformat>標(biāo)簽元素。
一個(gè)簡(jiǎn)單的實(shí)現(xiàn)如下:
package extensiblexml.customtag;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat",
new SimpleDateFormatBeanDefinitionParser());
}
}我們?cè)诔跏蓟椒ㄖ凶?cè)了一個(gè)Bean定義的解析器,這個(gè)解析器就是用來(lái)解析定制的配置標(biāo)簽的。
其實(shí)現(xiàn)如下:
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));
}
}
}這個(gè)解析器的doParse中,實(shí)現(xiàn)了解析的具體邏輯,借助Spring提供的支持類(lèi),我們可以很輕松地完成解析。以上三個(gè)文件放在同一個(gè)目錄下,即把XSD文件跟Java代碼放在同一目錄。編碼完畢之后,還需要做一些配置工作。我們必須告訴Spring我們準(zhǔn)備使用自定義的標(biāo)簽元素,告訴Spring如何解析元素,否則Spring沒(méi)那么聰明。這里需要2個(gè)配置文件,在與代碼根路徑同一級(jí)別下,床墊一個(gè)叫META-INF的文件。并在里面創(chuàng)建名為spring.handlers和spring.schemas,用于告訴Spring自定義標(biāo)簽的文檔結(jié)構(gòu)以及解析它的類(lèi)。兩個(gè)文件內(nèi)容分別如下:
spring.handlers:
http\://www.mycompany.com/schema/myns=extensiblexml.customtag.MyNamespaceHandler
等號(hào)的左邊是XSD定義中的targetNamespace屬性,右邊是NamespaceHandler的全稱(chēng)限定名。
spring.schemas:
http\://www.mycompany.com/schema/myns/myns.xsd=extensiblexml/customtag/myns.xsd
然后像往常一樣配置bean,作為簡(jiǎn)單的測(cè)試,我們定義一個(gè)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中,整個(gè)項(xiàng)目結(jié)構(gòu)如下圖:

最后我們寫(xiě)個(gè)測(cè)試類(lèi)測(cè)試一下能否工作:
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()));
}
}一切正常,輸出如下:

更實(shí)用的例子
第一個(gè)例子主要是為了舉例,在實(shí)際中用處不大,我們接著來(lái)看一個(gè)更復(fù)雜的自定義標(biāo)簽。我們自定義一個(gè)<fileList>標(biāo)簽,當(dāng)Spring掃描到這個(gè)標(biāo)簽的時(shí)候,創(chuàng)建一個(gè)指定目錄下的File類(lèi)的集合。另外,可以使用<fileFilter>對(duì)該目錄的文件進(jìn)行過(guò)濾。
如下:
<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源碼文件。
使用下面的測(cè)試迭代輸出文件名:
@SuppressWarnings("unchecked")
List<File> fileList = (List<File>) context.getBean("xmlList");
for (File file : fileList) {
System.out.println(file.getName());
}輸出結(jié)果如下:

根據(jù)第一個(gè)例子中的步驟,各部分配置及代碼如下:
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
小結(jié):
要自定義Spring的配置標(biāo)簽,需要一下幾個(gè)步驟:
- 使用XSD定義XML配置中標(biāo)簽元素的結(jié)構(gòu)(myns.XSD)
- 提供該XSD命名空間的處理類(lèi),它可以處理多個(gè)標(biāo)簽定義(MyNamespaceHandler.java)
- 為每個(gè)標(biāo)簽元素的定義提供解析類(lèi)。(SimpleDateFormatBeanDefinitionParser.java)
- 兩個(gè)特殊文件通知Spring使用自定義標(biāo)簽元素(spring.handlers 和spring.schemas)
到此這篇關(guān)于Java自定義Spring配置標(biāo)簽的文章就介紹到這了,更多相關(guān)Java Spring配置標(biāo)簽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄
本文主要介紹了手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Spring boot 路徑映射的實(shí)現(xiàn)
這篇文章主要介紹了spring boot 路徑映射的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
java 通過(guò)cmd 調(diào)用命令啟動(dòng)tomcat的操作
這篇文章主要介紹了java 通過(guò)cmd 調(diào)用命令啟動(dòng)tomcat的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Java16 JDK安裝并設(shè)置環(huán)境變量的方法步驟
突然想起自己大學(xué)剛接觸java的時(shí)候,要下載JDK和配置環(huán)境變量,那時(shí)候我上網(wǎng)找了很多教學(xué),本文就詳細(xì)的介紹一下Java16 JDK安裝并設(shè)置環(huán)境變量,感興趣的可以了解一下2021-09-09
spring boot 常見(jiàn)http請(qǐng)求url參數(shù)獲取方法
這篇文章主要介紹了spring boot 常見(jiàn)http請(qǐng)求url參數(shù)獲取,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
java高并發(fā)InterruptedException異常引發(fā)思考
這篇文章主要為大家介紹了java高并發(fā)InterruptedException異常引發(fā)思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
SpringCloud服務(wù)的發(fā)現(xiàn)與調(diào)用詳解
在Java微服務(wù)越來(lái)越火的今天。幾乎什么公司都在搞微服務(wù)。而使用的比較多的就是Spring?Cloud技術(shù)棧。今天就來(lái)研究一下Spring?Cloud中服務(wù)發(fā)現(xiàn)與調(diào)用的基本原理2022-07-07

