StripesFramework / stripes

Stripes is a Java framework with the goal of making Servlet/JSP based web development in Java as easy, intuitive and straight-forward as it should be. It's stripey and it doesn't suck.
http://www.stripesframework.org/
171 stars 73 forks source link

Stripes cannot scan classpath correctly with Spring-Boot #35

Open jbcpollak opened 8 years ago

jbcpollak commented 8 years ago

Hello, I've been trying to track down problems between Spring-Boot and Stripes. Originally I thought it was a Spring-Boot issue, but it appears to be a problem with the Stripes VFS implementation.

I'm working through a workaround now, but wanted to create a note here about it. There is a also a demo project that shows the problems.

jbcpollak commented 8 years ago

@wilkinsona - Stripes cannot load the ActionBeans when compiled as war because it is path-matching here, and it is trying to compare (for example):

/org/example/action/

with what it found in the war:

/WEB-INF/classes/org/example/action/WelcomeBean.class

What is the proper way to handle that? Should the code be augmented to ignore /WEB-INF/classes/, or is there a simpler mechanism?

iluvtr commented 8 years ago

HI Joshua, can you use simple exploded war?

2015-10-29 13:48 GMT-05:00 Joshua Chaitin-Pollak notifications@github.com:

@wilkinsona https://github.com/wilkinsona - Stripes cannot load the ActionBeans when compiled as war because it is path-matching here https://github.com/StripesFramework/stripes/blob/master/stripes/src/main/java/net/sourceforge/stripes/vfs/DefaultVFS.java#L177-L180, and it is trying to compare (for example):

/org/example/action/

with what it found in the war:

/WEB-INF/classes/org/example/action/WelcomeBean.class

What is the proper way to handle that? Should the code be augmented to ignore /WEB-INF/classes/, or is there a simpler mechanism?

— Reply to this email directly or view it on GitHub https://github.com/StripesFramework/stripes/issues/35#issuecomment-152284551 .

iluvtr commented 8 years ago

Here I found something related to exploded jar in Spring boot

https://wimdeblauwe.wordpress.com/2014/11/04/spring-boot-application-with-exploded-directory-structure/

2015-10-29 14:12 GMT-05:00 Nestor Hernandez iluvtr@gmail.com:

HI Joshua, can you use simple exploded war?

2015-10-29 13:48 GMT-05:00 Joshua Chaitin-Pollak <notifications@github.com

:

@wilkinsona https://github.com/wilkinsona - Stripes cannot load the ActionBeans when compiled as war because it is path-matching here https://github.com/StripesFramework/stripes/blob/master/stripes/src/main/java/net/sourceforge/stripes/vfs/DefaultVFS.java#L177-L180, and it is trying to compare (for example):

/org/example/action/

with what it found in the war:

/WEB-INF/classes/org/example/action/WelcomeBean.class

What is the proper way to handle that? Should the code be augmented to ignore /WEB-INF/classes/, or is there a simpler mechanism?

— Reply to this email directly or view it on GitHub https://github.com/StripesFramework/stripes/issues/35#issuecomment-152284551 .

wilkinsona commented 8 years ago

IMO, it should be using each URL that's on the classpath as a root for its search. One of those entries points to WEB-INF/classes inside the war file.

jbcpollak commented 8 years ago

@iluvtr @wilkinsona - I'll check out both suggestions.

My other thought was to just move our ActionBeans into a separate module which the .war depends on. I suspect that would work. It would get odd with the ActionBeans in a jar and the jsps in the .war, but I could deal with that.

jbcpollak commented 8 years ago

I seem to have gotten this working by using a multi-module setup. I moved the ActionBeans to a dependency jar, and left the JSPs in the webapp. I still need to use the work-around SpringBootVfs class and build the application as a war because of Spring-Boot issues, but it seems to work.

You can see this branch for this solution: https://github.com/AssuredLabor/spring-boot-issue-4310/tree/multi-module

I consider this a work-around though, not a long term solution.

harawata commented 8 years ago

@jbcpollak I have looked into your demo project. The .jar generated with mvn package cannot be extracted using jar xvf command (I'm using JDK 1.8.0_60). It can be extracted using unzip -q with a warning "5124 extra bytes at beginning or within zipfile" and I see some shell script code at the beginning of the file in binary editor [1].

So, I think creating a custom VFS is the proper way to handle this JAR file.

FYI, I recently investigated a Spring-Boot related issue in MyBatis [2] regarding nested JARs and concluded that it should be handled by a custom VFS implementation.

[1] If I changed the Spring-Boot version to 1.2.4.RELEASE, mvn package generates a regular JAR archive and WelcomeBean is registered on startup. [2] MyBatis uses the VFS mechanism ported from Stripes.

jbcpollak commented 8 years ago

@harawata - The problem with the jar as the project is setup is that Spring is appending an init.d style start-stop bash script to the start of the jar file. You can disable that by changing the following line in the Spring-Boot plugin to false:

<executable>false</executable>

However, even when I do that, the Action Beans still aren't loaded with the latest version of Spring-Boot.

jbcpollak commented 8 years ago

Do you know if there is any intention to provide a SpringBoot VFS standard in Stripes or MyBatis? It seems that since Spring-Boot is rapidly gaining traction, this would be a good idea.

Notice my multi-module workaround branch provides a VFS but that there were still problems.

jbcpollak commented 8 years ago

Also keep in mind that due to a Spring error, you need to build a .war file, not a .jar file so that the JSPs can be processed correctly. This has an impact on the VFS operation as well.

harawata commented 8 years ago

Hi @jbcpollak

Do you know if there is any intention to provide a SpringBoot VFS standard in Stripes or MyBatis?

Not that I know of.

It seems that since Spring-Boot is rapidly gaining traction, this would be a good idea.

I agree. The difficulty is that Spring-Boot can generate various forms of JAR/WAR (executable or not, embedded, etc.) and users would expect the Spring-Boot VFS to handle all the variations.

vankeisb commented 8 years ago

Annotation scanning is (starting from JEE6) a service that the container provides. I think Stripes should leverage this instead of trying to implement proprietary VFSs for all supported app servers...

http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html

vankeisb commented 8 years ago

Just pushed a preview of how we could leverage ServletContextInitializer in order to "scan" the classpath for us. This seems to work fine on the examples webapp (all tests are green).

Thing is, it requires java8 and Servlet3... I think it's time to upgrade.

rgrashel commented 8 years ago

I don't see how we can remove VFS support. I worked with someone in IRC to get a Stripes-based SpringBoot application working using myBatis' SpringBootExecutableJarVFS. It required no changes at all. Just drop it right in. Everything worked fine. There is no real standard about what functionality "listResources" needs to provide from a classloader perspective. Especially when it comes to things like nested and/or encoded JAR files. In the case of the unit tests, we have no container-based unit tests around VFS support. Actually, SpringBoot would be a great way to actually create an executable suite of unit tests in Jenkins that run inside of a real container.

hanswesterbeek commented 8 years ago

@rgrashel I think that person in IRC would have been me :) I have done some more testing since and found that the solution with the SpringBootExecutableJarVFS works while running the app with 'mvn spring-boot:run'.

However, after packaging the app and executing it the way it would be when deployed (eg. java -jar app.war), none of my ActionBeans are discovered. I'll be looking into this today.

vankeisb commented 8 years ago

That's what I'm talking about : trying to implement another VFS every time is a waste of time if you consider that this service can be provided by the container...

Are you using a servlet3 container ? Not sure what Spring Boot does, I'm not familiar with it.

@hanswesterbeek If you're using a Servlet3 container, could you please try the VFS-less approach I have commited ? I have tested it on Tomcat and Jetty and it seems to work just fine. It's only a draft at the moment, but it shows a way to bypass the VFS completely and rely on container-provided Classes.

hanswesterbeek commented 8 years ago

@vankeisb Yes, Spring Boot uses an embedded Tomcat 8 so that should be fine.

I was looking at your commit but don't quite understand how I would use it as that initializer, or how it works. Maybe you can elaborate on it?

Initial investigation leads me to believe that this won't be easy, because the required META-INF/services/javax.servlet.ServletContainerInitializer file must be inside a jar.

vankeisb commented 8 years ago

Cool thanks for giving it a try.

Yep, it's initialized using a META-INF/services/javax.servlet.ServletContainerInitializer file, with the class name net.sourceforge.stripes.init.StripesContainerInitializer in it.

I'm adding this file (with the dir structure of course) to src/main/resources in the app. It ends up packaged in the war (regular mvn build) :

WEB-INF/classes/META-INF/services/javax.servlet.ServletContainerInitializer

The war then runs in tomcat, with those kind of logs at startup :

12:40:21,737  INFO StripesContainerInitializer:47 - 99 classes loaded. 

Tested with the stripes-examples webapp.

I've also tried to embed this file right in the stripes jar, but it didn't work. It only worked with the file present in the app's classes, not the dependent jars.

Btw the init code is quite ugly, the static and all, but we'll find a proper way to do this later.

hanswesterbeek commented 8 years ago

What I don't get is that if this works, and the classes have indeed bean loaded, how would I take advantage of it? How do I make sure that Stripes finds my action beans?

vankeisb commented 8 years ago

I have modified ResolverUtil so that this works.

It's basically here :

https://github.com/StripesFramework/stripes/commit/7604b6ab91c159a40e4a8affb4ce9d13fd6b7d7b#diff-e2e6ee2c77b5b207c2714ecf4a5617d0

It will now look first for container-resolved classes (action beans, and all other Stripes "scannable" stuff). If it finds those, it completely bypasses the VFS : it simply looks in the list of classes provided by the container.

This means that if you define the ServletContainerInitializer in META-INF/services, then Stripes will take advantage of this instead of using VFS, which is now basically a "fallback" for those who don't use the container initializer, or for the Mock testing APIs.

hanswesterbeek commented 8 years ago

Unfortunately Spring Boot does not honor the full EE-spec and will not invoke the ServletContainerInitializer. So while your solution would help people on newer appservers, it won't help users of Spring Boot.

See this issue: https://github.com/spring-projects/spring-boot/issues/321

vankeisb commented 8 years ago

Damnit.

Apparently they have an alternative of their own, but to be honest I don't have time to look into it at the moment. Also, I'm thinking that Spring has its own scanning mechanism. Maybe this could be leveraged instead of the VFS which is way too "low level" (the container can inject a Collection instead of trying to find stuff in opaque jar files and all...).

Was worth a try anyway, thanks for helping out.

vankeisb commented 8 years ago

Hmmmm, out of curiosity, I took a glance. Check this out :

http://www.eclecticlogic.com/2014/09/01/classpath-scanning/

Seems pretty easy to write the same mechanism I wrote with the container initializer, but using this API in order to store the scanned Collection<Class>, just like I did in my experiment. Definitely easier than a custom VFS :P

hanswesterbeek commented 8 years ago

We ended up patching the SpringBootVfs that we were already using. It defers all searching to Spring's PathMatchingResourcePatternResolver and works. https://gist.github.com/hanswesterbeek/94c359b22c6fed6dff1c

jbcpollak commented 8 years ago

Hello, we are no longer working on this project, but we did finally get it working. I'm not sure if its helpful, but here is a Gist ripped from our code showing how we plug the VFS into our App:

https://gist.github.com/jbcpollak/84b1d7c8c06854b1291a

I do like @hanswesterbeek solution to defer to Spring's resolver - that is a good approach.

rgrashel commented 8 years ago

Thanks a lot for this, folks. I'm going to ping the MyBatis guys to see if they are interested in this solution also. I also think using Spring's resolver is a lot more elegant because it doesn't require manual JAR logic. I've already spoken to the Spring-Boot team and they gave me a recommendation for a dependable piece of code to use within Stripes which allow it to "auto-detect" if it is running within a Spring-Boot environment. So that's all we'll need to pull this into the Stripes core.

Many thanks again, gents!

-- Rick

On Wed, Jan 13, 2016 at 10:42 AM, Joshua Chaitin-Pollak < notifications@github.com> wrote:

Hello, we are no longer working on this project, but we did finally get it working. I'm not sure if its helpful, but here is a Gist ripped from our code showing how we plug the VFS into our App:

https://gist.github.com/jbcpollak/84b1d7c8c06854b1291a

I do like @hanswesterbeek https://github.com/hanswesterbeek solution to defer to Spring's resolver - that is a good approach.

— Reply to this email directly or view it on GitHub https://github.com/StripesFramework/stripes/issues/35#issuecomment-171358203 .

vankeisb commented 8 years ago

@hanswesterbeek thanks for sharing, this definitely has to be integrated.

I still think VFS is obsolete and probably not the appropriate "level of abstraction" (using Class objects would be much easier)... but if we have good coverage (major fwks/ASs are supported) then I'm good with it.

rgrashel commented 8 years ago

Remi,

Can you come to IRC to discuss? I'm not really clear on why you think VFS is obsolete if there are containers which do not provide consistent expected classpath scanning using the Java resources classes within the core JDK.

-- Rick

On Wed, Jan 13, 2016 at 11:02 AM, Remi Vankeisbelck < notifications@github.com> wrote:

@hanswesterbeek https://github.com/hanswesterbeek thanks for sharing, this definitely has to be integrated.

I still think VFS is obsolete and probably not the appropriate "level of abstraction" (using Class objects would be much easier)... but if we have good coverage (major fwks/ASs are supported) then I'm good with it.

— Reply to this email directly or view it on GitHub https://github.com/StripesFramework/stripes/issues/35#issuecomment-171364587 .

hanswesterbeek commented 8 years ago

One more thing, the impl I showed you does not take sub-packes into account. Working on that still...

hanswesterbeek commented 8 years ago

Solved. This should be the one: https://gist.github.com/hanswesterbeek/94c359b22c6fed6dff1c

I am also under the impression that it performs quite a bit better than the one we had before.

harawata commented 8 years ago

Thank you for sharing, @hanswesterbeek ! May I assume your custom VFS is licensed under ASL v2.0 as Stripes is? We consider porting it to MyBatis-Spring-Boot project. (apology for a off-topic comment...couldn't get a reply on the gist)