|
Posted on 2005-09-26 22:36 切尔斯基 阅读(2737) 评论(0) 编辑 收藏
Digest coming from << Professional Java Development with the Spring Framework>>
Managing the Container
You've learned the basics of creating and using bean factories and application contexts. There are a number of more advanced capabilities and strategies for real-world usage of the container. We're going to examine some of the most common ones.
Resource Location Paths in ApplicationContext Constructors
Both ClasspathXmlApplicationContext and FilesystemXmlApplication context have constructors that take one or more string locations pointing to XML resources that should be merged as the context definition. When the location paths fed to these class constructors are unqualified by any prefix, they will be interpreted as ClasspathResource and FilesystemResource locations by the respective contexts.
However, for either context class, it's actually legal to use any valid URL prefix (such as file: or http://) to override this and treat the location string as a UrlResource. It's also legal to use Spring's own classpath: prefix to force a ClasspathResource, although this is redundant with the ClasspathXmlApplicationContext:
ApplicationContext ctx = new ClasspathXmlApplicationContext(
"http://myserver.xom/data/myapp/ApplicationContext.xml");
You would probably expect that the two following definitions should work identically with the first treating the path as a FileSystemResource, and the second treating the path as a UrlResource with a file: URL:
ApplicationContext ctx = new FileSystemXmlApplicationContext(
"/data/data/myapp/ApplicationContext.xml");
ApplicationContext ctx = new FileSystemXmlApplicationContext(
"file:/data/data/myapp/ApplicationContext.xml");
However, for backward compatibility reasons, FilesystemXmlApplicationContext treats FileSystemResources used in the constructor and getResource() calls as being relative to the current working directory, not absolute, even if they start with a slash, /. If you want true absolute paths, you should always use the file: prefix with Resources location strings fed to FileSystemXmlApplicationContext, which will force them to be treated as UrlResources, not FilesystemResources.
The Special classpath*: Prefix
In a location string used to create an XML-configured application context, you may also use the special classpath*: prefix: ApplicationContext ctx = new ClasspathXmlApplicationContext(
"classpath*:applicationContext.xml");
The classpath*: prefix specifies that all classpath resources matching the name after the prefix, as available to the ClassLoader, should be obtained, and then merged to form the final context definition. (Internally, this would resolve to ClassLoader.getResources("applicationContext.xml") to find all matching resources.) Note that you can't use a classpath*: prefix to create an actual Resource because the latter always refers to just one file.
Ant-Style Paths
In a location string used to create file-based, XML-configured application contexts, you may also use ant-style patterns (containing the * and ** wildcards):
ApplicationContext ctx = new FilesystemXmlApplicationContext(
"file:/data/myapp/*-context.xml");
The preceding example would match 0 or more files in the specified directory and merge their contents as the definition for the application context. Note that ant-style wildcards are really usable only where directory locations can actually be resolved to a File because that location then needs to be searched for matches to the wildcards. The following ApplicationContext ctx = new ClasspathXmlApplicationContext(
"/data/myapp/*-context.xml");
will work in environments where the classpath resolves to actual filesystem locations, but will fail where it is actually the contents of a JAR, so it should not be relied upon.
Declarative Usage of Application Contexts
Following IoC principles, as little application code as possible should know about the application context. We've mentioned that in spite of these principles, it is sometimes necessary for a small amount of glue code to know about the context. For a Spring-enabled J2EE web-app, it is possible to reduce or completely eliminate even this small amount of code by using Spring code to declaratively specify that an application context should be loaded at web-app startup.
Spring's ContextLoader is the class that provides this capability. However, it must actually be instigated by using another helper class. In a Servlet 2.4 environment, or a Servlet 2.3 environment that follows the 2.4 guarantee of executing servlet context listeners before any load-on-startup servlets, the preferred mechanism to kick off ContextLoader is via the use of a servlet context listener, called ContextLoaderListener. For all other environments, a load-on-startup servlet called ContextLoaderServlet may be used instead for this purpose.
At this time, containers known to work with the listener approach are Apache Tomcat 4.x+, Jetty 4.x+, Resin 2.1.8+, JBoss 3.0+, WebLogic 8.1 SP3+, and Orion 2.0.2+. Containers known to not work with the listener approach, and which need the servlet approach, are BEA WebLogic up to 8.1 SP2, IBM WebSphere up to 5.x, and Oracle OC4J up to 9.0.x.
Let's look at how ContextLoaderListener is configured via a <listener> element in the standard J2EE web.xml file that is part of every web-app:
...
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-
class>
</listener>
...
Setting up ContextLoaderServlet instead is done via the <servlet> element in the web.xml file:
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-
class>
<load-on-startup>1</load-on-startup>
</servlet>
It's important that the servlet is set as the first to load, or at least has a higher precedence than any other load-on-startup servlet that uses the application context.
When the web-app starts up, the listener or servlet will execute ContextLoader, which initializes and starts an XmlWebApplicationContext based on one or more XML file fragments that are merged. By default, the following file is used: /WEB-INF/applicationContext.xml
but you may override this to point to one or more files in an alternate location by using the contextConfigLocation servlet context param in the web.xml file as in the following two examples:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/myApplicationContext.xml</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:services-applicationContext.xml,
classpath:dao-applicationContext.xml
</param-value>
</context-param>
While unqualified locations will be considered to be files relative to the web-app's context root location, you can use the resource location prefixes and ant-style paths as previously described in this chapter for location strings in application context constructors (as shown in the second example). Multiple XML fragments may be specified, as in the last example, to be merged to form the definition, with white space, commas (,), and semicolons (;) used as delimiters in the location list.
When the application context has been loaded, it is bound to the J2EE ServletContext for the webapp. Effectively it's a singleton, although keyed to the ServletContext, not stored inside a static class field. On destruction of the web-app, the context will automatically be closed. Any application code that needs to access the application context may do so by calling a static helper method from the WebApplicationContextUtils class:
WebApplicationContext getWebApplicationContext(ServletContext sc)
For most applications using Spring MVC, there is not actually any need for application code to get the context in this fashion, however. The framework itself, when processing a request, obtains the context from this location, gets appropriate request handlers, and kicks off request handling, with all objects having been configured in an IoC fashion. The same is possible when using Spring's Struts integration mechanism. Please see the web MVC sections of the book for more information in this area. Note that this is a very lightweight process in use; almost all the work is done at servlet initialization time, and after that the mechanism is generally as efficient as hardwiring your application.
When integrating another web layer technology, you'll have to create a small amount of glue code equivalent to the preceding. All it needs to do, when a request is received, is to obtain the application context using the previously listed method. It may then get any needed objects, and kick off the request handling to them, or with them. Try to architect the application code so that as little code as possible is aware of this mechanism, though, to reduce coupling to Spring and maintain inversion of control. For example, when integrating a traditional style action-based web MVC framework, as opposed to each action object using the preceding method call to get services and objects it needs to work with, it's cleaner to get these collaborators via a method call, which is handled in a superclass, so that the super- class is the only place that knows about Spring. It's cleaner yet to hook into whatever mechanism exists in the web framework for obtaining action objects, and make that code look up the actions directly from the context, and then just call them normally. The actions will already have had dependencies injected, and will be used in a pure IoC fashion, without knowing about Spring at all. The exact approach used depends on the particular architecture of the framework that Spring needs to be integrated with. Although integration is not incredibly complicated, you can probably also benefit by searching mailing lists and forums for information on how other people have done the same thing. Besides looking in Spring itself for integration code for a particular framework, also look for Spring-related code in that framework itself.
Splitting Up Container Definitions into Multiple Files
There is real value to be found in splitting up bean factory or application context definitions into multiple files. As a container definition grows as one file, it becomes harder to understand what all the bean definitions within it are for, and harder to manage, including making changes.
Generally we recommend splitting up a context either by vertical architectural layer within the application, or horizontally, by module or component. In the latter case, the definitions in each file fragment make up a narrow vertical slice, which encompasses multiple layers. It can make sense to split using a combination of both of these strategies.
Combining the File Fragments Externally
You've already seen in the ContextLoader example that when using the ContextLoader for declarative context creation, it's legal to specify multiple fragments that should all be used to create one context. For programmatic creation of a context from multiple fragments, simply use the constructor variant that takes multiple locations:
ApplicationContext ctx = new ClasspathXmlApplicationContext(new String[] {
"applicationContext-web.xml",
"applicationContext-services.xml",
"ApplicationContext-dao.xml" } );
Another effect of splitting up the context definition is that it can make testing easier. For example, it is relatively common to be using Spring's JTATransactionManager (as described in Chapter 6,"Transaction and DataSource Management") and a DataSource coming from JNDI for the deployed application in the appserver, but for integration or unit tests running outside of the appserver, you need to use a local transaction manager (such as HibernateTransactionManager) and local DataSource (such as one created with DBCP). If the transaction manager and DataSource are in their own separate XML fragment, then it is easy to handle tests by setting up the classpaths for tests so that a version of the file specific to the tests is first on the classpath, and overrides the normal deployment variants. You might even want to wire up an in-memory database such as HSQLDB.
Combining File Fragments with the Import Element
We normally prefer to combine multiple XML files into one definition from an external point of control, as described previously. This is because the files themselves are unaware of being combined, which feels cleanest in some respects. Another mechanism exists, however, and this is to import bean definitions into one XML definition file from one or more external XML definition files by using one or more instances of the import element. Any instances of import must occur before any bean elements.
Here's an example: <beans>
<import resource="data-access.xml"/>
<import resource="services.xml "/>
<import resource="resources/messgeSource.xml"/>
<bean id="..." class="..."/>
...
</beans>
In this case, three external files are being imported. Note that all files being imported must be in fully valid XML configuration format (including the top-level beans element), according to the XML DTD. The external definition file to merge in is specified as a location path with the resource attribute. All location paths are relative to the location of the definition file doing the importing. In this case, definition files data-access.xml and services.xml must be located in the same directory or classpath package as the file performing the import, while messageSource.xml must be located in a resource directory or package below that.
Combining File Fragments Programmatically
When programmatically creating a bean factory or application context using bean definition readers, you may combine multiple XML file fragments by simply pointing the reader at multiple files, as shown in the following example:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
ClassPathResource res = new ClassPathResource("beans.xml");
reader.loadBeanDefinitions(res);
res = new ClassPathResource("beans2.xml");
reader.loadBeanDefinitions(res);
// now use the factory
Strategies for Handling Components
There are a few different strategies for handling Spring-based components or modules. As the words component and module are somewhat nebulous, let's clarify first what we mean when we use them. We are essentially talking about a relatively coarse-grained physical bundling or logical separation of code that handles related functionality. While a JAR file could be a component or module, so could be all the code underneath a certain package in a larger package tree. The fundamental idea is that the code is related somehow, and can be used as a library to provide certain services.
When trying to build an app in a component-oriented style (i.e., the app assembles and uses components), one workable strategy with respect to handling the container definition is to not have components care at all about the definition. Components, when using this strategy, simply provide Java code. It is considered the responsibility of the main application to define a bean factory or application context configuration, whether in one file or multiple files, which wires together all the object instances for classes provided by the components, including providing them with their dependencies. This strategy is quite viable because code should be written to be utilized in an IoC fashion anyway, and should not care about container initialization or its definition. Because the application using the component needs to do all the wiring, what is required, however, is a full and thorough documentation of all the component classes that need to be wired together for the externally visible component classes to be used, even if external users of the component don't care about many of these classes. This is a disadvantage of using this approach. While this is not a disadvantage on the level of code coupling or something of that nature (after all, we're just talking about an easily changed configuration file), it's not ideal that configuration concerns for a component are not handled in the component itself.
A nicer strategy is to try to have components bundle together with their code, one or more XML file fragments that define how to wire together some or all of the component classes. In the final app, an application context definition is assembled from all the XML fragments provided by all the components, along with one or more XML fragments defining objects needed by the application itself, or objects needed by the components, which the component fragments do not define. To avoid bean name collisions, bean names in component XML fragments should use some sort of simple namespace scheme, such as by adding componentname- or componentname: as a prefix on all bean names.
One question is how two or more components that do not know about each other can refer to the same external bean (consider, for example, a DataSource), which they do not themselves provide, but have a dependency on. Because each component does not know about the other, they cannot use a common name. The solution is for each component to use a component-specific name for the external bean, and then have the application provide the definition for this bean, making sure to add aliases to the bean name that match the name(s) expected by the component(s).
Let's look at an example. Consider a "devices" module, which provides mappers (i.e., DAOs for handling persistence for some device-related domain objects). These mappers are internally implemented with Spring's JDBC convenience classes, so they need a DataSource fed to them when they are initialized. An XML fragment for wiring up the mappers could look like the following:
<beans>
<bean id="devices:deviceDescriptorDataMapper"
class="ch04.sampleX.devices.DeviceDescriptorDataMapperImpl">
<property name="dataSource"><ref bean="devices:dataSource"/></property>
</bean>
<bean id="devices:deviceMapper"
class="ch04.sampleX.devices.DeviceMapperImpl">
<property name="dataSource"><ref bean="devices:dataSource"/></property>
</bean>
</beans>
An external user (the application) of this module simply needs to combine this fragment along with others, and refer to the mappers by their bean names, as needed. It also needs to ensure that a DataSource with the bean name devices:dataSource is available.
Let's look at another component, a "user" module:
<beans>
<bean id="users:userMapper"
class="ch04.sampleX.users.userMapperImpl">
<property name="dataSource"><ref bean="users:dataSource"/></property>
</bean>
<bean id="users:roleMapper"
class="ch04.sampleX.users.DeviceMapperImpl">
<property name="dataSource"><ref bean=" users:dataSource "/></property>
</bean>
</beans>
This module provides DAOs for mapping users and roles. These also need to be initialized with a DataSource. Now consider that we have an application using these components. It will create an application context using the XML fragments from the components, along with one or more fragments defined for the application itself, used at a minimum for tying things together. Here's an XML fragment for the application, showing how a service object refers to the mappers, and how a DataSource is defined so both component definitions can see it:
...
<--- UserDeviceService works with both user and device mappers -->
<bean id="app:userDeviceService"
class="ch04.sampleX.users.UserDeviceServiceImpl">
<property name="userMapper"><ref bean="users:userMapper "/></property>
<property name="deviceMapper"><ref bean="devices:deviceMapper "/></property>
</bean>
<bean id="app:dataSource" name="users:dataSource,devices:dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
...
</bean>
...
As you can see, while the DataSource instance has the primary ID app:dataSource, it is also aliased as users:dataSource and devices:dataSource, so that the references in the XML fragments coming from the components can be resolved.
This approach to component handling obviously requires discipline in some respects. There is no builtin namespace protection, so the developer is required to manage all bean names carefully, by using the distinct component-specific prefixes on bean names and generally trying to avoid name conflicts. In practice, however, this approach has proven to be usable and effective.
Another general strategy for mixing code from multiple components is to use multiple application contexts in some fashion. One Spring user has reported employing a Factory Bean deployed in one context, which exposed just one bean (via an interface) from another context that it instantiated. In this way, the mixing of beans was tightly controlled.
Singletons for Accessing the Container
We have already emphasized that most code should never have to know about the container when it's written and deployed in an IoC style. For some architectural scenarios, however, there is no avoiding the fact that some glue code (ideally a very small amount of it) in the application does have to have access to the container so that it may get one or more populated objects out of it and initiate a sequence of method calls.
One example is when using EJBs. Because the J2EE AppServer is responsible for creating actual EJB objects, if the EJB wants to delegate to one or more POJOs managed by the Spring container, to actually implement the EJB method's functionality, the EJB (or a common base class) effectively has to have some knowledge of the container so it can get these objects from it, normally on creation of the EJB. One strategy is for each EJB instance to create its own copy of the Spring container. This is potentially problematic in terms of memory usage if there are a large number of objects in the Spring container, and is problematic if creating the context or bean factory is expensive in terms of time, for example if a Hibernate SessionFactory needs to be initialized.
For both the EJB scenario, and for some non-EJB scenarios as well, it may make sense to keep a shared instance of the Spring container in some sort of singleton object for access by the glue code that needs it. Spring provides such a singleton access mechanism via the SingletonBeanFactoryLocator and ContextSingletonBeanFactoryLocator classes. The use of these classes also allows for creation and usage of a container hierarchy such that one or more demand-loaded application contexts are parents to individual web-app application contexts. As a more concrete example, inside a J2EE Application (EAR file), a service-layer (and lower layer) shared application context could be the parent of one or more web-app application contexts for web-apps also deployed in the EAR. Chapter 11, "Spring and EJB," contains a full description of how to use the singleton container access classes, and we direct readers to that content if these classes are of interest, even if there is no need to use Spring and EJBs. The relevant section in that chapter is titled "ContextSingletonBeanFactoryLocator and SingletonBeanFactoryLocator."
|