I've looked at a bunch of sample projects and I can't seem to tease out a common best practice. I've seen Spring bean config files sometimes go in the src/main/webapp/WEB-INF directory. I've seen this in conjunction with with a Servlet definition in web.xml like this:
<servlet>
<servlet-name>my-stuff</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/my-stuff-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
But I've also seen bean config files included within web.xml top level -- i.e. outside of a Servlet. What does this mean? Is this for cross-Servlet beans? Sometimes it's in the src/main/webapp/WEB-INF directory and sometimes it's in src/main/resources. Also I've seen other bean config files defined in WAR modules with just about everything in src/main/resources.
I've read and re-read the Spring documentation, but the only convention I found is that by default a Servlet context config file should be in the src/main/webapp/WEB-INF directory named {servlet-name}-servlet.xml.
So what's the best practice and why?
Application contexts in Spring can form hierarchies where child context has access to beans defined in parent context.
A typical Spring MVC web application contains a hierarchy with two levels:
Root web application context loaded by ContextLoaderListener.
Config location of this context is applicationContext.xml by default and can be configured using <context-param> named contextConfigLocation, i.e. at the top level of web.xml. This context usually contains a core application logic.
Servlet-specifc context loaded by DispatcherServlet. Its config location is by default <servletname>-servlet.xml and can be configured using <init-param> named contextConfigLocation, i.e. at servlet level. This context usually contains a Spring MVC-related stuff (controllers, etc) since DispatcherServlet is a part of Spring MVC.
The latter context is a child of the former.
If web application doesn't use Spring MVC as a presentation framework, it doesn't have DispatcherServlet and its context. Some extremely simple Spring MVC samples doesn't have ContextLoaderListener and the root context (however, you need root context for cross-servlet functionality such as Spring Security).
Config files of web application are by default located in webapp's root folder. However, they can be placed in the classpath (i.e. in src/main/webapp), in this case they are accessed via classpath: prefix. This may be useful if you are going to use some of these files in integration tests without servlet container. Also classpath: prefix may be useful when you want to load a config file from a separate artifact, i.e. from a jar file in /WEB-INF/lib.
I think it is often good style to keep the code, its spring configuration an in a separate JAR that is included into the WAR, such that the WAR is basically empty but for web.xml and the like. This saves you from even asking this question. :-) You can reference those spring configurations with classpath: prefix.
One advantage of this layout is that you can easily write Unittests that instantiate the complete Spring configuration of the WAR within the JAR. I would not necessarily recommend to use this for actual tests (although you can do integration tests this way), but you get a quick feedback when you accidentially break the overall structure of the config files without having to redeploy the application.
If you really need to put spring configuration files into the WAR (perhaps since it also references beans that are implemented in the WAR itself) I would also put them into the normal resources path /WEB-INF/classes, for the reasons discussed above.
Related
I am using spring version 4.1.1.RELEASE with junit version 4.11
I created a Maven with type "maven-archetype-webapp"
Before using junit I put spring-servlet.xml within webapp/WEB-INF/. This config works well for Tomcat web container, but Junit cannot refer to this context file because junit's awareness of classpath does not include webapp/WEB-INF/.
In order for junit to be able to read the context configuration for spring, I had to put spring's servlet config file at: src/main/resources/config/spring-servlet.xml . Maven will copy this file into /target//WEB-INF/classes/config/spring-servlet.xml
In order for this config to work for both JUnit and Tomcat web container , I have to made below modification
For Junit:
1> Using Annotation to inform junit about the location of file "spring-servlet.xml"
#ContextConfiguration({ "classpath:config/spring-servlet.xml" })
public class AnotherTest { ... }
For Tomcat deployment:
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
And things work well for both junit and tomcat deployment.
But from some sources, they said that using "classpath:" would cause confuse about context in some cases (I don't know which cases?)
And they suggest we should use below config (namely option2) instead , to avoid using "classpath:"
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/classes/config/spring-servlet.xml</param-value> <!-- this config does not use classpath -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
With option2 , junit works fine. But running this web project within eclipse would cause FileNotFoundException at the startup time of tomcat because tomcat container does not see spring-servlet.xml . Of course I had executed Maven clean & build and ensure that war had been created, I also inspect and see /target//WEB-INF/classes/config/spring-servlet.xml had been created, Java classes had been compiled, before I execute Maven to start Tomcat.
I found out that, if I copy war file created by Maven, and manually deploy this war into Tomcat then everything work fine.
I doubt that, Maven with Eclipse some how, does not generate WEB-INF/classes/config/spring-servlet.xml properly at the time Maven start Tomcat. At least, I think Maven and Eclipse does not simply using what had been built (i.e project's java class and other resources) to run/deploy with Tomcat.
Is there anyway that I can use the config at option2 above to develop within Eclipse to start Tomcat, and to execute Junit properly?
Or is there any other better way (e.g another directory) to store and config file spring-servlet.xml , please explain.
Thank you.
There can be many ways to achieve and here is one of the way which I follow to avoid this type of situations.
I hope the same custom directory structure given by maven is followed.
Project
Module
src
main
Java
resources
webapp
test
Java
resources
Here are couple of options that we do.
Accessing the context XML file using classpath*:
for the test cases in #ContextConfiguration which will be access from main/resources
for the reference in web.xml, context file will be in main/resources
Writing a separate context file for the junit tests
But from some sources, they said that using "classpath:" would cause confuse about context in some cases (I don't know which cases?)
confusing the context ? Seriously ?
Most of the cases, using classpath* will give access to all the files in the execution. Here might be the case where name is same for multiple jars, though I haven't encountered that any point of time.
Another context file for Junit under test/resources
This enables the flexibility of making changes to the environment that we use for testing.
For instance
mocking beans
Configuring derby database for mock scenarios
Accessing the generated classes is not preferable method even though internal access will happen.
And I believe here, there is nothing to do with the version of the spring/junit. Its all about accessing the context file from different perspectives.
I have a web application which uses Spring. Bean definitions and spring configuration for the web application are declared in webapp-spring.xml. This web application uses a library as its dependency. Let us say the library is called data-access.jar. This library is on the web application's classpath. The web application is configured to initialise spring context by the usual web.xml configuration:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:webapp-spring.xml</param-value>
</context-param>
The data-access.jar library also has its own spring beans xml definition data-access-spring.xml embedded within it (directly at the root level) for its internal use of Spring dependency injection.
When the web application context initialisation happens on starting the container (Tomcat), I would like Spring to initialise context using data-access-spring.xml along with web-app-spring.xml. How do I do this?
I tried following which didn't work:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:web-app-spring.xml,classpath*:data-access-spring.xml</param-value>
</context-param>
Spring silently ignores data-access-spring.xml and only initialises the bean definitions from web-app-spring.xml.
classpath*: will basically ignore files which don't match the pattern. But your syntax is correct. So it looks like data-access-spring.xml isn't being found. Are you sure it's at the root?
You could also use patterns, btw, e.g. classpath*:*-spring.xml, but that shouldn't help you here. classpath*:web-app-spring.xml,classpath*:**/data-access-spring.xml might if it's not at the root.
I created a simple spring-web-mvc project based on maven. The sample includes two configuration files. One in src / main / resources / spring / application-config.xml
and a second in src / main / webapp / WEB-INF / mvc-config.xml
Is that config normal? I think it is just configuration splitting basics mvc stuff in mvc-config.xml and application-config.xml for spring commons or?
For a web application then that is fairly normal - although the naming convention for the two files is often dispatcher-servlet.xml and applicationContext.xml.
The dispatcher-servlet.xml (or mvc-config.xml as in your question) is the configuration file for the web application context and contains the web specific beans and configuration for Spring MVC. It is loaded by the DispatcherServlet when the application starts up.
The applicationContext.xml (or application-config.xml as in your question) is the configuration file for the main Spring application context and contains the non-web business beans (typically services, DAOs etc). This file is often spilt into fragments - with a fragment containing the beans for each logical layer within the app. This file is typically loaded by the ContextLoaderListener defined in the web.xml.
Spring automatically sets the main application context as the parent of the web application context. This ensures that web components (such as controllers) have access to business beans in the application context. However, business beans are not able to see beans in the web application context.
When I first started learning about Spring, things were configured in the applicationContext.xml file. Then as I started to read books specifically on more recent versions of spring, they've all done the configuration in separate XML files such as myapp-servlet-xml, myapp-security.xml, myapp-service.xml, etc., by configuring a contextConfigLocation in the web.xml file. So, for instance, the code I've been following along with had this as it's contextConfigLocation:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/myapp-servlet.xml
/WEB-INF/myapp-data.xml
</param-value>
</context-param>
Anyway, recently I ran into a configuration issue (which the helpful people here at StackOverflow helped me figure out) that was due to this separation. There was no applicationContext.xml file for the examples from these books and later on when I tried adding automatic scanning and annotations to the app this caused issues. I tried moving everything into applicationContext.xml and doing away with the other files and that solved the problem. Nothing else changed, I just put everything in applicationContext.xml.
So, this, along with comments from others, has lead to me sort of understand that even if you don't create an applicationContext.xml, it's still being used and it is the top level of some sort of configuration hierarchy. I'm hoping someone else can explain to me how this all works because I've not come across any explanation on it anywhere.
So for example, if I put certain context:component-scan tags into configuration files that are below applicationContext.xml, it could cause certain classes to not get scanned. Things of that nature. I don't understand the precedence and what has to go where to be sure it's seen application wide and so on. If anyone can clearly explain it or point me to a resource that explains it I would much appreciate it, thank you. Hopefully what I'm asking makes sense.
There's nothing special about the file named "applicationContext.xml" except that it's the name Spring tends to expect as its default configuration file. Using one file named that or multiple files named "dog.xml", "cat.xml", and "alien.xml" will work exactly the same way. The trouble you're having comes from having multiple ApplicationContexts in use at the same time, not from having multiple XML files. I've recently answered a couple of questions from people who had problems caused by not understanding these concepts. Check out those answers, and see what questions you still have:
Declaring Spring Bean in Parent Context vs Child Context
Spring-MVC: What are a "context" and "namespace"?
Edit: In response to your new question:
I had a <context:component-scan base-package="com.myapp"/> tag in my servlet.xml.
I'm guessing this "servlet.xml" file is named like foo-servlet.xml, where the DispatcherServlet configured in your web.xml is named "foo", like
<servlet>
<servlet-name>foo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
By convention, when this DispatcherServlet starts, it'll create a new ApplicationContext that's configured by the file foo-servlet.xml, derived from the servlet-name. Now, since you put a context:component-scan in there, it's going to recursively scan the given package and create beans for all annotated classes. The package you gave it, com.myapp, looks like it's the base package for your entire app, so Spring will create beans from all of the annotated classes in your app, including the data access ones, in this one ApplicationContext that's associated to the DispatcherServlet. Typically, this context should only have view-layer stuff and beans that directly support the DispatcherServlet in it, so this was something of a misconfiguration.
In my data.xml file I had data source beans and that was it. No other beans, everything else was autowired and annotated.
Presumably, this "data.xml" file is the one you listed in the contextConfigLocation context-param. Assuming you'd also added the ContextLoaderListener to your web.xml, like
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
then that file will be used to create a second ApplicationContext--the root context. That's what this listener does. Note that it actually builds the context from all the files listed in contextConfigLocation, and if you also included your "servlet.xml" in that list, then you've loaded that config twice: here in the root context as well as in the context associated with the DipatcherServlet. Hopefully you see now how there's a distinct division between the XML configuration files and the ApplicationContexts that they configure. The same XML file can easily be used to configure two different contexts. Whether doing so is correct or not is another question. In this particular case, it isn't.
The order I've described these two contexts in is actually backwards. I was just following your description of what you did. The ContextLoaderListener, being a ServletContextListener, will always execute before any servlet starts up. This means the root context is created first, and the other context second. This is by design so that when the DispatcherServlet creates its context, it can add that context as a child of the root context. I've described this relationship in those other posts. The most important effect of this is that beans in the root context are available to and via the DispatcherServlet's context. That applies to autowired relationships, too. That's important because the DispatcherServlet only looks in its associated context for beans that it needs, like controller instances. Your controllers, though, obviously have to be wired with supporting beans. Thus, traditionally, the controllers live in the DispatcherServlet's context, and the supporting beans live in the root context.
I then tried to add #Transacational to my service bean and it wouldn't persist.
In order for #Transactional to work, you must include the <tx:annotation-driven/> tag in the configuration of the ApplicationContext where the annotated bean lives. The trick is figuring out the "where it lives" part. Beans in a child can override beans in a parent context. Therefore--I'm just guessing here--if you loaded all your beans into the DispatcherServlet context as I described above but put the <tx:annotation-driven/> in the root context, you might have a bean in the root context that's correctly transactional, but it's not the one being used because the duplicate is "closer" to the servlet in the parent/child hierarchy, and the context it's in didn't get a <tx:annotation-driven/> configuration.
When I changed the servlet context:component-scan tag to instead point at com.myapp.web and then added a context:component-scan tag to the data.xml file, everything worked.
It still depends somewhat on exactly which config files you were including in which ApplicationContexts, but at the very least I can say that by doing this, you removed a lot of beans from the DispatcherServlet's context which were causing problems. In particular, your correctly-configured #Transactional beans in the root context would no longer be shadowed by beans in the child context and would be injected into your controllers, so your persistence stuff would work then.
So... the main thing to take away is that you have two related ApplicationContexts. You have to remain aware of that fact and stay in control of which beans go in which context.
Does that cover everything?
There is a tutorial video that introduces Spring MVC 3.0. In the demo-project they use the following directory structure:
<proj>
src
main
webapp
WEB-INF
spring
appServlet
controllers.xml
servlet-context.xml
root-context.xml
Let's say I have a project with Maven-support and I want to write JUnit tests using Spring's configuration. Currently we use JUnit 4.8.2. This would obviously require to load the three files listed above.
In the test I could use annotations like
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath*:/WEB-INF/spring/**/*.xml")
However, that doesn't find the XML-files. I took a look at the classpath and noticed, that only the <proj>/target/classes and <proj>/target/test-classes are included by default.
One obvious solution would be to add the proper path to the classpath, but I don't know if that is what the guys at Spring had in mind.
Therefore, my question: What do I need to do to load the configuration files while letting it look as if I'm the total pro-coder using Spring?
Another option is to use file system resource loader:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
You should put the "normal" spring configuration in the resources folder but not in the webapp folder: src\main\ressources\WEB-INF\spring\root/spring-context.xml. Then you can access it without problems from the test.
Put only the web related spring configuration (servlet-context.xml) in the webapp folder.
The structure that you described is the one generated by the STS-Spring-Template:MVC-Template, however Spring-ROO and Spring-Fuse generate the structure that I have described.
For example Spring ROO:
<project>/src/main/resources/META-INF/spring/applicationContext.xml
<project>/src/main/webapp/WEB-INF/spring/webmvc-config.xml
<project>/src/main/webapp/WEB-INF/web.xml
web.xml:
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
</context-param>