Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripes cannot scan classpath correctly with Spring-Boot #35

Open
jbcpollak opened this issue Oct 29, 2015 · 31 comments
Open

Stripes cannot scan classpath correctly with Spring-Boot #35

jbcpollak opened this issue Oct 29, 2015 · 31 comments

Comments

@jbcpollak
Copy link

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
Copy link
Author

@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
Copy link

iluvtr commented Oct 29, 2015

HI Joshua, can you use simple exploded war?

2015-10-29 13:48 GMT-05:00 Joshua Chaitin-Pollak [email protected]:

@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
#35 (comment)
.

@iluvtr
Copy link

iluvtr commented Oct 29, 2015

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 [email protected]:

HI Joshua, can you use simple exploded war?

2015-10-29 13:48 GMT-05:00 Joshua Chaitin-Pollak <[email protected]

:

@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
#35 (comment)
.

@wilkinsona
Copy link

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
Copy link
Author

@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
Copy link
Author

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
Copy link
Contributor

harawata commented Nov 4, 2015

@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
Copy link
Author

@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
Copy link
Author

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
Copy link
Author

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
Copy link
Contributor

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
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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
Copy link

@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
Copy link
Member

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
Copy link

@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
Copy link
Member

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
Copy link

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
Copy link
Member

I have modified ResolverUtil so that this works.

It's basically here :

7604b6a#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
Copy link

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: spring-projects/spring-boot#321

@vankeisb
Copy link
Member

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
Copy link
Member

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
Copy link

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
Copy link
Author

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
Copy link
Member

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 <
[email protected]> 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
#35 (comment)
.

@vankeisb
Copy link
Member

@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
Copy link
Member

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 <
[email protected]> 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
#35 (comment)
.

@hanswesterbeek
Copy link

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

@hanswesterbeek
Copy link

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
Copy link
Contributor

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants