Error with the .class location when building a WAR for a Play! framework application
I'm working on a 1.2 Play! framework application, and I have a problem when deploying it as a WAR on a Tomcat 6.
One page of my application displays a list of information.
These information are retrieved from a .yml
file.
So I have a controller that generate a Iterable<Object>
from this .yml
file, like that:
public static void myFunction() {
Constructor constructor = new Constructor(MyClass.class); // org.yaml.snakeyaml.constructor.Constructor
constructor.addTypeDescription(new TypeDescription(MyClass.class));
Yaml yaml = new Yaml(constructor);
Iterable<Object> listOfInfo = yaml.loadAll(Application.class.getResourceAsStream("/my-file.yml"));
render("Application/my-page.html", listOfInfo);
}
The important point is that MyClass
is located in the app/my/company/my-app/
package (my.company.my-app.MyClass
).
When I run my application using play run
, there is no problem.
Now, I build the WAR package (using play war -o some/dir --zip
), and I install this generated WAR on a Tomcat (6.0).
Once the server is started, and try to access the corresponding page, I get the following error:
@683lh76fn
Internal Server Error (500)
Template execution error (In /app/views/Application/my-page.html around line 9)
Execution error occured in template /app/views/Application/my-page.html. Exception raised was ConstructorException : null; Can't construct a java object f
or tag:yaml.org,2002:my.company.my-app.MyClass; exception=Class not found: my.company.my-app.MyClass.
play.exceptions.TemplateExecutionException: null; Can't construct a java object for tag:yaml.org,2002:my.company.my-app.MyClass; exception
=Class not found: my.company.my-app.MyClass
at play.templates.BaseTemplate.throwException(BaseTemplate.java:84)
at play.templates.GroovyTemplate.internalRender(GroovyTemplate.java:252)
at play.templates.Template.render(Template.java:26)
at play.templates.GroovyTemplate.render(GroovyTemplate.java:184)
at play.mvc.results.RenderTemplate.<init>(RenderTemplate.java:24)
at play.mvc.Controller.renderTemplate(Controller.java:659)
at play.mvc.Controller.renderTemplate(Controller.java:639)
at play.mvc.Controller.render(Controller.java:694)
at controllers.Application.myFunction(Application.java:311)
If I have a look in the exploded war, I see that my.company.my-app.MyClass
is located in the directory WEB-INF/application/precompiled/java/开发者_如何转开发my/company/my-app/
directory.
If I move this directory into WEB-INF/classes
, then I don't get this error anymore.
Why does this error occurs? What are my options to make it work (without modifying manually the WAR)?
By the default, Tomcat only looks at files in /WEB-INF/classes and /WEB-INF/lib (for each war file) as they are included in the classpath. There are a number of things you could do..
- Put your classes in the Tomcat lib folder and they'll be handled by the common loader rather than the webapp loader. (not recommended as they will be available to other apps)
- You can enable the shared loader in conf/catalina.properties and use whatever directory you want.
- add the path to the CLASSPATH variable in the '/bin/setclasspath.sh' script (or setclasspath.bat for Windows).
Personally i would fix/modify how i build the war file and make sure that the classes are copied to WEB-INF/classes instead of WEB-INF/application folder.
I finally found the answer of this problem!
The Yaml parser (snakeyaml) creates its own ClassLoader in order to parse a .yml
file. The structure of the Play! framework uses its own ClassLoader, and the compiled classes are located in the WEB-INF/application/precompiled/java
directory, which is not the WAR standard.
Due to that, Yaml was not able to retrieve the classes, in particular MyClass.class
.
The solution to solve this issue (except by modifying the WAR using Ant for example), is to give the Play! ClassLoader to the Yaml parser. Instead of writing that:
Constructor constructor = new Constructor(MyClass.class);
constructor.addTypeDescription(new TypeDescription(MyClass.class));
I write:
CustomClassLoaderConstructor constructor = new CustomClassLoaderConstructor(MyClass.class, MyController.class.getClassLoader());
constructor.addTypeDescription(new TypeDescription(MyClass.class));
using this, I can use the WAR created by Play! directly without getting the Yaml error anymore!
精彩评论