我正在编写一个Java Struts应用程序,该应用程序根据本地存储的XSD文件验证传入的XML文件。我遇到了两个问题:
XSD文件需要存储在本地,即不通过http调用。我应该在Struts应用程序中存储XSD文件?例如,它应该进入WEB-INF吗?我需要将它放在我的一个src包中吗?我已经看到了很多关于本地存储的XSD,但没有特别针对Struts。
存储后,如何从Java代码中引用此XSD文件?我不知道如何告诉我的程序在哪里找到该文件。
换句话说,这就是我所拥有的:
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(new File("/path/to/schema.xsd"));
我不知道/path/to/schema.xsd
应该是什么样的。
我在这个论坛上看过类似的问题,但我仍然有点失落。我很感激社区可以提供的任何帮助。
提前致谢!
答案 0 :(得分:4)
因此,经过更多的研究,我了解到我因缺乏Java反射和Struts上下文的知识而受到阻碍。哎呀,我甚至不知道什么是"资源"是。
我已经解决了这个问题,但在我向解决方案迈进的过程中,我编写了以下教程来帮助我跟踪我正在学习的新概念。希望这将有助于下一个遇到此问题并需要更明确细节的人。请告诉我是否有我尚未明确的事情,或者我是否误解了这个概念,并且我会纠正并澄清。
首先,让我们从一个资源开始。我在Java的上下文中学到的资源是与应用程序相关的任何文件或数据片段,而不是Java代码。例如,这可以是文本或图像文件,或者在我的例子中,可以是XSD文件。
现在,要获取该资源,我们必须告诉Java程序在哪里找到它。这意味着我们需要资源的完整(绝对)文件路径。对于普通的Java项目,获取此文件路径的一种方法是使用
this.getClass().getResource("file.txt");
其中file.txt
是我们要查找的文件的名称。这将返回一个URL对象,一种包含文件路径或URL的特殊类型的Java对象。我们可以调用对象的toString()
方法来获取完整的URL。因此,下面的代码块
URL thisFile1 = this.getClass().getResource("resource.txt");
System.out.println(thisFile1.toString());
打印以下内容:
file:/C:/path/to/workspace/folder/bin/sandbox/test/pkg1/resource.txt
但是,只有当resource.txt与进行上述调用的源代码(在本例中为sandbox.test.pkg1
)位于同一个包中时,才会出现这种情况。
这是因为getResource()
假定其输入参数是相对文件路径,除非文件路径以" /"开头。如果是这样,getResource()
将输入参数视为绝对文件路径,其中根目录是已编译的代码目录,即包层次结构的顶部(" bin"文件夹)。换句话说,这两行代码将产生相同的结果:
URL thisFile1 = this.getClass().getResource("resource.txt");
URL thisFile1 = this.getClass().getResource("/sandbox/test/pkg1/resource.txt");
在我们致电getClass()
之前,我们首先必须致电getResource()
,这是不太明显的原因。我想知道为什么我们需要打电话给getClass()
。 getResource()
成为Object方法会不会更容易?这似乎是一个令人困惑和不必要的步骤。
好吧,getClass()
返回一个Java Class对象的实例,这是一个返回Java类信息的特殊Java对象。换句话说,拿这段代码:
public class myClass
{
public void myMethod()
{
Class thisClass = this.getClass();
}
public void method2() {...}
public void method3() {...}
}
如果我们调用myMethod()
,我们得到一个包含myClass对象信息的Class实例。例如,如果我们再调用
Method[] theseMethods = thisClass.getMethods();
我们得到myClass中的所有方法的数组(myMethod()
,method2()
,method3()
等)。这是Java反射,它具有广泛的用途,因为我们可以在程序运行时收集和检查Java类的属性。
那么,如果我们获得外部资源,为什么还要关心这个呢?因为要获取资源,我们首先需要获取有关想要获取资源的类的信息。
让我们回到之前的例子:
URL thisFile1 = this.getClass().getResource("resource.txt");
这将搜索文件" resource.txt"在当前目录中,getResource()
不会自动知道当前目录是什么。它需要获取有关调用它的类的信息,以了解要搜索的目录。 Class对象只包含那种信息,这就是getResource()
是Class对象而不是Object对象的方法的原因。
到目前为止,这么好。但是当我们尝试从在本地Tomcat服务器上运行的Java Struts应用程序获取资源时,事情变得复杂,而不是在我们尝试从传统Java项目获取资源时。使用Struts和Tomcat,文件最终位于一个非常不同的位置:
URL thisFile1 = this.getClass().getResource("resource.txt");
System.out.println(thisFile1.toString());
产生
file:/C:/path/to/Tomcat/server/folder/.metadata/.me_tcat/webapps/ProjectName/WEB-INF/classes/sandbox/test/pkg1/resource.txt
这更复杂,更不直观。另外,如果我们想要的文件与调用它的源代码不在同一个目录中,该怎么办?使用Struts应用程序时,通常会将资源文件放在其他位置。这是因为Struts建立在分离程序的不同部分以更好地管理程序流(MVC范例)的原则之上,因此将资源文件与源代码捆绑似乎违反了该原则(在我看来,无论如何)。 / p>
我想把我的资源文件放到WEB-INF中但不在"类"文件夹 - 在"资源"文件夹 - 但这会使getResource()
更复杂。从getResource()
的角度来看,根目录是
file:/C:/path/to/Tomcat/server/folder/.metadata/.me_tcat/webapps/ProjectName/WEB-INF/classes/
因此,要在文件层次结构中向上移动,我们必须使用一个或多个../
来制定复杂的相对路径。这很麻烦,难以理解。有更优雅的方式吗?
有!
URL thisFile1 = this.getServlet().getServletContext().getResource("/WEB-INF/resources/resource.txt");
这里发生了什么?好吧,请记住,Struts是一个使用Java servlet的框架。这意味着我们的源代码在servlet中。反过来,该servlet是Web应用程序的一部分(据我所知,它可以运行多个servlet)。该应用程序是servlet运行的上下文。应用程序有自己的getResource()
方法,该方法用于为作为应用程序一部分运行的所有servlet提供资源。
换句话说,我们获取servlet,然后获取servlet的上下文(Web应用程序),然后从上下文中获取资源。
这this.getServlet().getServletContext().getResource()
与this.getClass().getResource()
的区别。前者从运行Struts的Web应用程序的角度处理本地资源,而后者从请求资源的类的角度处理本地资源。因此,前者查找具有Web应用程序(ProjectName,在上面的示例文件路径中)作为根目录的资源,而后者查找具有包层次结构顶部的资源作为根目录。这是有道理的。在传统的Java项目中,我们从来没有比包层次结构更高的东西,即没有更大的上下文"项目正在运行中。但是,使用Struts,Java项目是运行Struts的更大的Web应用程序(更大的上下文")的一部分。
所以现在我的修改后的代码如下所示:
String xsdFile = "";
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try
{
xsdFile = this.getServlet().getServletContext().getResource("/WEB-INF/resources/schema.xsd").toString();
}
catch (Exception e)
{
return e.getMessage();
}
Schema schema = schemaFactory.newSchema(new File(xsdFile));
我将它封装在try-catch块中,因为如果我们找不到该文件,就会得到NullPointerException。
但这仍然不是很正确。我在上面的代码块的最后一行收到错误。代码无法找到文件schema.xsd。根据控制台窗口,它正在尝试引用
file:/C:/Users/user/AppData/Local/MyEclipse%20Professional%202014/plugins/com.genuitec.eclipse.easie.tomcat.myeclipse_11.0.0.me201211151802/tomcat/bin/jndi:/localhost/ProjectName/WEB-INF/resources/schema.xsd
看起来而不是返回文件的完整路径,this.getServlet().getServletContext().getResource()
正在返回本地网址:
jndi:/localhost/ProjectName/WEB-INF/resources/schema.xsd
我了解到,JNDI是一种Java服务,它允许远程组件通过集中位置相互通信。该集中位置是运行JNDI的位置,它记录了每个组件的位置,以便每个其他组件都可以找到它。换句话说,要找到组件B,组件A只需要ping JNDI并询问" B在哪里?"。 JNDI返回正确的位置。如果两个组件与JNDI在同一服务器上,则JNDI返回" localhost"作为IP,后跟相对于Web应用程序根目录的组件路径。这只是说" A和B都在这台机器上这里"并且与我们习惯使用的HTTP网址类似。
所以默认情况下一切都在JNDI上运行。这样可以保留所有文件引用相对于Web应用程序,从而可以更轻松地将应用程序从一台计算机移动到另一台计算机,重新定位组件,并且如上所述,可以跨机器进行通信。这对于应用程序管理来说很好,但是如果我需要一个关于我的机器的绝对路径,而不是关于应用程序,它就不行了。
好吧,我需要一个绝对路径到我的机器,因为我在Schema构造函数中实例化了File类,而File类在用String实例化时假定它的输入参数是本地文件路径。幸运的是,Schema构造函数还会将一个URL(确实是一个URI)带到模式文件中,而URL正好是上面调用getResource()
之前将其转换为String所返回的内容:
我的最终解决方案如下:
URL xsdFile = "";
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try
{
xsdFile = this.getServlet().getServletContext().getResource("/WEB-INF/resources/schema.xsd");
}
catch (Exception e)
{
return e.getMessage();
}
Schema schema = schemaFactory.newSchema(xsdFile);
这个新的解决方案完美无缺!
答案 1 :(得分:4)
您可以在任何地方使用的更简单方法是:
SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = schemaFactory.newSchema(loadXSD());
方法loadXSD()
:
private static Source loadXSD(){
Source xsd = new StreamSource(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("File stored in src/"));
return xsd;
}