当前位置:

首页 > 编程开发 > Java怎么自定义Spring配置标签

Java怎么自定义Spring配置标签

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

本文目录

    引言:

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

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

    
         handler1
         handler2
    

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

    一个HelloWorld例子:

    定制标签的第一步是要定义标签元素的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 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 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 nestedFiles = new ManagedList();
    		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>
    	{
     
    		String directory;
    		private Collection filters;
    		private Collection nestedFiles;
     
    		@Override
    		public Collection 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 files = new ArrayList();
    			Collection results = new ArrayList(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 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 unique
    			for (Collection 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 filters = new ManagedList();
     
    			// 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>
    	{
     
    		private final List filters = new ArrayList();
     
    		@Override
    		public Collection 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  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 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)

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    为什么要购买正版软件?
    01 官方授权

    授权来源透明,订单与许可信息可随时查询。

    02 即时交付

    付款成功后自动发放激活信息,支持订单追踪。

    03 中文协助

    安装、激活、迁移与续费问题都有专人响应。

    04 长期可用

    提供升级提醒与授权管理,换机时也能轻松迁移。

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

    Copyright ©2018-2026