Inversion of Control Containers and the Dependency Injection

这篇文章看来也不错,还有中文的译文的pdf文档。贴在这里,以后慢慢看。

(转自:http://martinfowler.com/articles/injection.html)

 Home Blog Articles Books About Me Contact Me ThoughtWorks

Inversion of Control Containers and the Dependency Injection pattern

Martin Fowler

In the Java community there's been a rush of lightweight containers that help to assemble components from different projects into a cohesive application. Underlying these containers is a common pattern to how they perform the wiring, a concept they refer under the very generic name of "Inversion of Control". In this article I dig into how this pattern works, under the more specific name of "Dependency Injection", and contrast it with the Service Locator alternative. The choice between them is less important than the principle of separating configuration from use.

Last significant update: 23 Jan 04

| Chinese | Portuguese | French |

Contents

Components and Services

A Naive Example

Inversion of Control

Forms of Dependency Injection

Constructor Injection with PicoContainer

Setter Injection with Spring

Interface Injection

Using a Service Locator

Using a Segregated Interface for the Locator

A Dynamic Service Locator

Using both a locator and injection with Avalon

Deciding which option to use

Service Locator vs Dependency Injection

Constructor versus Setter Injection

Code or configuration files

Separating Configuration from Use

Some further issues

Concluding Thoughts

--------------------------------------------------------------------------------

One of the entertaining things about the enterprise Java world is the huge amount of activity in building alternatives to the mainstream J2EE technologies, much of it happening in open source. A lot of this is a reaction to the heavyweight complexity in the mainstream J2EE world, but much of it is also exploring alternatives and coming up with creative ideas. A common issue to deal with is how to wire together different elements: how do you fit together this web controller architecture with that database interface backing when they were built by different teams with little knowledge of each other.A number of frameworks have taken a stab at this problem, and several are branching out to provide a general capability to assemble components from different layers. These are often referred to as lightweight containers, examples include PicoContainer, and Spring.

Underlying these containers are a number of interesting design principles, things that go beyond both these specific containers and indeed the Java platform. Here I want to start exploring some of these principles. The examples I use are in Java, but like most of my writing the principles are equally applicable to other OO environments, particularly .NET.

--------------------------------------------------------------------------------

Components and Services

The topic of wiring elements together drags me almost immediately into the knotty terminology problems that surround the terms service and component. You find long and contradictory articles on the definition of these things with ease. For my purposes here are my current uses of these overloaded terms.

I use component to mean a glob of software that's intended to be used, without change, by application that is out of the control of the writers of the component. By 'without change' I mean that the using application doesn't change the source code of the components, although they may alter the component's behavior by extending it in ways allowed by the component writers.

A service is similar to a component in that it's used by foreign applications. The main difference is that I expect a component to be used locally (think jar file, assembly, dll, or a source import). A service will be used remotely through some remote interface, either synchronous or asynchronous (eg web service, messaging system, RPC, or socket.)

I mostly use service in this article, but much of the same logic can be applied to local components too. Indeed often you need some kind of local component framework to easily access a remote service. But writing "component or service" is tiring to read and write, and services are much more fashionable at the moment.

--------------------------------------------------------------------------------

A Naive Example

To help make all of this more concrete I'll use a running example to talk about all of this. Like all of my examples it's one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize what's going on without falling into the bog of a real example.

In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly useful function is implemented by a single method.

class MovieLister...

    public Movie[] moviesDirectedBy(String arg) {

        List allMovies = finder.findAll();

        for (Iterator it = allMovies.iterator(); it.hasNext();) {

            Movie movie = (Movie) it.next();

            if (!movie.getDirector().equals(arg)) it.remove();

        }

        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);

    }

The implementation of this function is naive in the extreme, it asks a finder object (which we'll get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This particular piece of naivety I'm not going to fix, since it's just the scaffolding for the real point of this article.

The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I want my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.

public interface MovieFinder {

    List findAll();

}

Now all of this is very well decoupled, but at some point I have to come up with a concrete class to actually come up with the movies. In this case I put the code for this in the constructor of my lister class.

class MovieLister...

  private MovieFinder finder;

  public MovieLister() {

    finder = new ColonDelimitedMovieFinder("movies1.txt");

  }

The name of the implementation class comes from the fact that I'm getting my list from a colon delimited text file. I'll spare you the details, after all the point is just that there's some implementation.

Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called "movies1.txt" then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.

Figure 3: The dependencies using a simple creation in the lister class

Figure 3 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?

In my book P of EAA, we described this situation as a Plugin. The implementation class for the finder isn't linked into the program at compile time, since I don't know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.

Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.

So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.

--------------------------------------------------------------------------------

Inversion of Control

When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled. Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.

The question, is what aspect of control are they inverting? When I first ran into inversion of control, it was in the main control of a user interface. Early user interfaces were controlled by the application program. You would have a sequence of commands like "Enter name", "enter address"; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.

For this new breed of containers the inversion is about how they lookup a plugin implementation. In my naive example the lister looked up the finder implementation by directly instantiating it. This stops the finder from being a plugin. The approach that these containers use is to ensure that any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.

As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.

I'm going to start by talking about the various forms of dependency injection, but I'll point out now that that's not the only way of removing the dependency from the application class to the plugin implementation. The other pattern you can use to do this is Service Locator, and I'll discuss that after I'm done with explaining Dependency Injection.

--------------------------------------------------------------------------------

Forms of Dependency Injection

The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 1

Figure 1: The dependencies for a Dependency Injector

There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection. If you read about this stuff in the current discussions about Inversion of Control you'll hear these referred to as type 1 IoC (interface injection), type 2 IoC (setter injection) and type 3 IoC (constructor injection). I find numeric names rather hard to remember, which is why I've used the names I have here.

Constructor Injection with PicoContainer

I'll start with showing how this injection is done using a lightweight container called PicoContainer. I'm starting here primarily because several of my colleagues at ThoughtWorks are very active in the development of PicoContainer (yes, it's a sort of corporate nepotism.)

PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.

class MovieLister...

    public MovieLister(MovieFinder finder) {

        this.finder = finder;      

    }

The finder itself will also be managed by the pico container, and as such will have the filename of the text file injected into it by the container.

class ColonMovieFinder...

    public ColonMovieFinder(String filename) {

        this.filename = filename;

    }

The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into the finder.

    private MutablePicoContainer configureContainer() {

        MutablePicoContainer pico = new DefaultPicoContainer();

        Parameter[] finderParams =  {new ConstantParameter("movies1.txt")};

        pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);

        pico.registerComponentImplementation(MovieLister.class);

        return pico;

    }

This configuration code is typically set up in a different class. For our example, each friend who uses my lister might write the appropriate configuration code in some setup class of their own. Of course it's common to hold this kind of configuration information in separate config files. You can write a class to read a config file and set up the container appropriately. Although PicoContainer doesn't contain this functionality itself, there is a closely related project called NanoContainer that provides the appropriate wrappers to allow you to have XML configuration files. Such a nano container will parse the XML and then configure an underlying pico container. The philosophy of the project is to separate the config file format from the underlying mechanism.

To use the container you write code something like this.

    public void testWithPico() {

        MutablePicoContainer pico = configureContainer();

        MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);

        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

        assertEquals("Once Upon a Time in the West", movies[0].getTitle());

    }

Although in this example I've used constructor injection, PicoContainer also supports setter injection, although it's developers do prefer constructor injection.

Setter Injection with Spring

The Spring framework is a wide ranging framework for enterprise Java development. It includes abstraction layers for transactions, persistence frameworks, web application development and JDBC. Like PicoContainer it supports both constructor and setter injection, but its developers tend to prefer setter injection - which makes it an appropriate choice for this example.

To get my movie lister to accept the injection I define a setting method for that service

class MovieLister...

    private MovieFinder finder;

  public void setFinder(MovieFinder finder) {

    this.finder = finder;

  }

Similarly I define a setter for the string the finder.

class ColonMovieFinder...

    public void setFilename(String filename) {

        this.filename = filename;

    }

The third step is to set up the configuration for the files. Spring supports configuration through XML files and also through code, but XML is the expected way to do it.

    <beans>

        <bean id="MovieLister" class="spring.MovieLister">

            <property name="finder">

                <ref local="MovieFinder"/>

            </property>

        </bean>

        <bean id="MovieFinder" class="spring.ColonMovieFinder">

            <property name="filename">

                <value>movies1.txt</value>

            </property>

        </bean>

    </beans>

The test then looks like this.

    public void testWithSpring() throws Exception {

        ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");

        MovieLister lister = (MovieLister) ctx.getBean("MovieLister");

        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

        assertEquals("Once Upon a Time in the West", movies[0].getTitle());

    }

Interface Injection

The third injection technique is to define and use interfaces for the injection. Avalon is an example of a framework that uses this technique in places. I'll talk a bit more about that later, but in this case I'm going to use it with some simple sample code.

With this technique I begin by defining an interface that I'll use to perform the injection through. Here's the interface for injecting a movie finder into an object.

public interface InjectFinder {

    void injectFinder(MovieFinder finder);

}

This interface would be defined by whoever provides the MovieFinder interface. It needs to be implemented by any class that wants to use a finder, such as the lister.

class MovieLister implements InjectFinder...

    public void injectFinder(MovieFinder finder) {

        this.finder = finder;

    }

I use a similar approach to inject the filename into the finder implementation.

public interface InjectFinderFilename {

    void injectFilename (String filename);

}

class ColonMovieFinder implements MovieFinder, InjectFinderFilename......

    public void injectFilename(String filename) {

        this.filename = filename;

    }

Then, as usual, I need some configuration code to wire up the implementations. For simplicity's sake I'll do it in code.

class Tester...

    private Container container;

     private void configureContainer() {

       container = new Container();

       registerComponents();

       registerInjectors();

       container.start();

    }

This configuration has two stages, registering components through lookup keys is pretty similar to the other examples.

class Tester...

  private void registerComponents() {

    container.registerComponent("MovieLister", MovieLister.class);

    container.registerComponent("MovieFinder", ColonMovieFinder.class);

  }

A new step is to register the injectors that will inject the dependent components. Each injection interface needs some code to inject the dependent object. Here I do this by registering injector objects with the container. Each injector object implements the injector interface.

class Tester...

  private void registerInjectors() {

    container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));

    container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());

  }

public interface Injector {

  public void inject(Object target);

}

When the dependent is a class written for this container, it makes sense for the component to implement the injector interface itself, as I do here with the movie finder. For generic classes, such as the string, I use an inner class within the configuration code.

class ColonMovieFinder implements Injector......

  public void inject(Object target) {

    ((InjectFinder) target).injectFinder(this);       

  }

class Tester...

  public static class FinderFilenameInjector implements Injector {

    public void inject(Object target) {

      ((InjectFinderFilename)target).injectFilename("movies1.txt");     

    }

    }

The tests then use the container.

class IfaceTester...

    public void testIface() {

      configureContainer();

      MovieLister lister = (MovieLister)container.lookup("MovieLister");

      Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

      assertEquals("Once Upon a Time in the West", movies[0].getTitle());

    }

The container uses the declared injection interfaces to figure out the dependendencies and the injectors to inject the correct dependents. (The specific container implementation I did here isn't important to the technique, and I won't show it because you'd only laugh.)

--------------------------------------------------------------------------------

Using a Service Locator

The key benefit of a Dependency Injector is that it removes the dependency that the MovieLister class has on the concrete MovieFinder implementation. This allows me to give listers to friends and for them to plug in a suitable implementation for their own environment. Injection isn't the only way to break this dependency, another is to use a service locator.

The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need. So a service locator for this application would have a method that returns a movie finder when one is needed. Of course this just shifts the burden a tad, we still have to get the locator into the lister, resulting in the dependencies of Figure 2

Figure 2: The dependencies for a Service Locator

In this case I'll use the ServiceLocator as a singleton Registry. The lister can then use that to get the finder when it's instantiated.

class MovieLister...

    MovieFinder finder = ServiceLocator.movieFinder();

class ServiceLocator...

    public static MovieFinder movieFinder() {

        return soleInstance.movieFinder;

    }

    private static ServiceLocator soleInstance;

    private MovieFinder movieFinder;

As with the injection approach, we have to configure the service locator. Here I'm doing it in code, but it's not hard to use a mechanism that would read the appropriate data from a configuration file.

class Tester...

    private void configure() {

        ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));

    }

class ServiceLocator...

    public static void load(ServiceLocator arg) {

        soleInstance = arg;

    }

    public ServiceLocator(MovieFinder movieFinder) {

        this.movieFinder = movieFinder;

    }

Here's the test code.

class Tester...

    public void testSimple() {

        configure();

        MovieLister lister = new MovieLister();

        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

        assertEquals("Once Upon a Time in the West", movies[0].getTitle());

    }

I've often heard the complaint that these kinds of service locators are a bad thing because they aren't testable because you can't substitute implementations for them. Certainly you can design them badly to get into this kind of trouble, but you don't have to. In this case the service locator instance is just a simple data holder. I can easily create the locator with test implementations of my services.

For a more sophisticated locator I can subclass service locator and pass that subclass into the registry's class variable. I can change the static methods to call a method on the instance rather accessing instance variables directly. I can provide thread specific locators by using thread specific storage. All of this can be done without changing clients of service locator.

A way to think of this is that service locator is a registry not a singleton. A singleton provides a simple way of implementing a registry, but that implementation decision is easily changed.

Using a Segregated Interface for the Locator

One of the issues with the simple approach above, is that the MovieLister is dependent on the full service locator class, even though it only uses one service. We can reduce this by using a segregated interface. That way, instead of using the full service locator interface, the lister can declare just the bit of interface it needs.

In this situation the provider of the lister would also provide a locator interface which it needs to get hold of the finder.

public interface MovieFinderLocator {

    public MovieFinder movieFinder();

The locator then needs to implement this interface to provide access to a finder.

    MovieFinderLocator locator = ServiceLocator.locator();

    MovieFinder finder = locator.movieFinder();

   public static ServiceLocator locator() {

        return soleInstance;

    }

    public MovieFinder movieFinder() {

        return movieFinder;

    }

    private static ServiceLocator soleInstance;

    private MovieFinder movieFinder;

You'll notice that since we want to use an interface, we can't just access the services through static methods any more. We have to use the class to get a locator instance and then use that to get what we need.

A Dynamic Service Locator

The above example was static, in that the service locator class has methods for each of the services that you need. This isn't the only way of doing it, you can also make a dynamic service locator that allows you to stash any service you need into it and make your choices at runtime.

In this case, the service locator uses a map instead of fields for each of the services, and provides generic methods to get and load services.

class ServiceLocator...

    private static ServiceLocator soleInstance;

    public static void load(ServiceLocator arg) {

        soleInstance = arg;

    }

    private Map services = new HashMap();

    public static Object getService(String key){

        return soleInstance.services.get(key);

    }

    public void loadService (String key, Object service) {

        services.put(key, service);

    }

Configuring involves loading a service with an appropriate key.

class Tester...

    private void configure() {

        ServiceLocator locator = new ServiceLocator();

        locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));

        ServiceLocator.load(locator);

    }

I use the service by using the same key string.

class MovieLister...

    MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");

On the whole I dislike this approach. Although it's certainly flexible, it's not very explicit. The only way I can find out how to reach a service is through textual keys. I prefer explicit methods because it's easier to find where they are by looking at the interface definitions.

Using both a locator and injection with Avalon

Dependency injection and a service locator aren't necessarily mutually exclusive concepts. A good example of using both together is the Avalon framework. Avalon uses a service locator, but uses injection to tell components where to find the locator.

Berin Loritsch sent me this simple version of my running example using Avalon.

public class MyMovieLister implements MovieLister, Serviceable {

    private MovieFinder finder;

    public void service( ServiceManager manager ) throws ServiceException {

        finder = (MovieFinder)manager.lookup("finder");

    }

     

The service method is an example of interface injection, allowing the container to inject a service manager into MyMovieLister. The service manager is an example of a service locator. In this example the lister doesn't store the manager in a field, instead it immediately uses it to lookup the finder, which it does store.

--------------------------------------------------------------------------------

Deciding which option to use

So far I've concentrated on explaining how I see these patterns and their variations. Now I can start talking about their pros and cons to help figure out which ones to use and when.

Service Locator vs Dependency Injection

The fundamental choice is between Service Locator and Dependency Injection. The first point is that both implementations provide the fundamental decoupling that's missing in the naive example - in both cases application code is independent of the concrete implementation of the service interface. The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control.

Inversion of control is a common feature of frameworks, but it's something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn't to say it's a bad thing, just that I think it needs to justify itself over the more straightforward alternative.

The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem.

Using dependency injection can help make it easier to see what the component dependencies are. With dependency injector you can just look at the injection mechanism, such as the constructor, and see the dependencies. With the service locator you have to search the source code for calls to the locator. Modern IDEs with a find references feature make this easier, but it's still not as easy as looking at the constructor or setting methods.

A lot of this depends on the nature of the user of the service. If you are building an application with various classes that use a service, then a dependency from the application classes to the locator isn't a big deal. In my example of giving a Movie Lister to my friends, then using a service locator works quite well. All they need to do is to configure the locator to hook in the right service implementations, either through some configuration code or through a configuration file. In this kind of scenario I don't see the injector's inversion as providing anything compelling.

The difference comes if the lister is a component that I'm providing to an application that other people are writing. In this case I don't know much about the APIs of the service locators that my customers are going to use. Each customer might have their own incompatible service locators. I can get around around some of this by using the segregated interface. Each customer can write an adapter that matches my interface to their locator, but in any case I still need to see the first locator to lookup my specific interface. And once the adapter appears then the simplicity of the direct connection to a locator is beginning to slip.

Since with an injector you don't have a dependency from a component to the injector, the component cannot obtain further services from the injector once it's been configured.

A common reason people give for preferring dependency injection is that it makes testing easier. The point here is that to do testing, you need to easily replace real service implementations with stubs or mocks. However there is really no difference here between dependency injection and service locator: both are very amenable to stubbing. I suspect this observation comes from projects where people don't make the effort to ensure that their service locator can be easily substituted. This is where continual testing helps, if you can't easily stub services for testing, then this implies a serious problem with your design.

Of course the testing problem is exacerbated by component environments that are very intrusive, such as Java's EJB framework. My view is that these kinds of frameworks should minimize their impact upon application code, and particularly should not do things that slow down the edit-execute cycle. Using plugins to substitute heavyweight components does a lot help this process, which is vital for practices such as Test Driven Development.

So the primary issue is for people who are writing code that expects to be used in applications outside of the control of the writer. In these cases even a minimal assumption about a Service Locator is a problem.

Constructor versus Setter Injection

For service combination, you always have to have some convention in order to wire things together. The advantage of injection is primarily that it requires very simple conventions - at least for the constructor and setter injections. You don't have to do anything odd in your component and it's fairly straightforward for an injector to get everything configured.

Interface injection is more invasive since you have to write a lot of interfaces to get things all sorted out. For a small set of interfaces required by the container, such as in Avalon's approach, this isn't too bad. But it's a lot of work for assembling components and dependencies, which is why the current crop of lightweight containers go with setter and constructor injection.

The choice between setter and constructor injection is interesting as it mirrors a more general issue with object-oriented programming - should you fill fields in a constructor or with setters.

My long running default with objects is as much as possible, to create valid objects at construction time. This advice goes back to Kent Beck's Smalltalk Best Practice Patterns: Constructor Method and Constructor Parameter Method. Constructors with parameters give you a clear statement of what it means to create a valid object in an obvious place. If there's more than one way to do it, create multiple constructors that show the different combinations.

Another advantage with constructor initialization is that it allows you to clearly hide any fields that are immutable by simply not providing a setter. I think this is important - if something shouldn't change then the lack of a setter communicates this very well. If you use setters for initialization, then this can become a pain. (Indeed in these situations I prefer to avoid the usual setting convention, I'd prefer a method like initFoo, to stress that it's something you should only do at birth.)

But with any situation there are exceptions. If you have a lot of constructor parameters things can look messy, particularly in languages without keyword parameters. It's true that a long constructor is often a sign of an over-busy object that should be split, but there are cases when that's what you need.

If you have multiple ways to construct a valid object, it can be hard to show this through constructors, since constructors can only vary on the number and type of parameters. This is when Factory Methods come into play, these can use a combination of private constructors and setters to implement their work. The problem with classic Factory Methods for components assembly is that they are usually seen as static methods, and you can't have those on interfaces. You can make a factory class, but then that just becomes another service instance. A factory service is often a good tactic, but you still have to instantiate the factory using one of the techniques here.

Constructors also suffer if you have simple parameters such as strings. With setter injection you can give each setter a name to indicate what the string is supposed to do. With constructors you are just relying on the position, which is harder to follow.

If you have multiple constructors and inheritance, then things can get particularly awkward. In order to initialize everything you have to provide constructors to forward to each superclass constructor, while also adding you own arguments. This can lead to an even bigger explosion of constructors.

Despite the disadvantages my preference is to start with constructor injection, but be ready to switch to setter injection as soon as the problems I've outlined above start to become a problem.

This issue has led to a lot of debate between the various teams who provide dependency injectors as part of their frameworks. However it seems that most people who build these frameworks have realized that it's important to support both mechanisms, even if there's a preference for one of them.

Code or configuration files

A separate but often conflated issue is whether to use configuration files or code on an API to wire up services. For most applications that are likely to be deployed in many places, a separate configuration file usually makes most sense. Almost all the time this will be an XML file, and this makes sense. However there are cases where it's easier to use program code to do the assembly. One case is where you have a simple application that's not got a lot of deployment variation. In this case a bit of code can be clearer than separate XML file.

A contrasting case is where the assembly is quite complex, involving conditional steps. Once you start getting close to programming language then XML starts breaking down and it's better to use a real language that has all the syntax to write a clear program. You then write a builder class that does the assembly. If you have distinct builder scenarios you can provide several builder classes and use a simple configuration file to select between them.

I often think that people are over-eager to define configuration files. Often a programming language makes a straightforward and powerful configuration mechanism. Modern languages can easily compile small assemblers that can be used to assemble plugins for larger systems. If compilation is a pain, then there are scripting languages that can work well also.

It's often said that configuration files shouldn't use a programing language because they need to be edited by non-programmers. But how often is this the case? Do people really expect non-programmers to alter the transaction isolation levels of complex server-side application? Non-language configuration files work well only to the extent they are simple. If they become complex then it's time to think about using a proper programming language.

One thing we're seeing in the Java world at the moment is a cacophony of configuration files, where every component has its own configuration files which are different to everyone else's. If you use a dozen of these components, you can easily end up with a dozen configuration files to keep in sync.

My advice here is to always provide a way to do all configuration easily with a programmatic interface, and then treat a separate configuration file as an optional feature. You can easily build configuration file handling to use the programmatic interface. If you are writing a component you then leave it up to your user whether to use the programmatic interface, your configuration file format, or to write their own custom configuration file format and tie it into the programmatic interface

Separating Configuration from Use

The important issue in all of this is to ensure that the configuration of services is separated from their use. Indeed this is a fundamental design principle that sits with the separation of interfaces from implementation. It's something we see within an object-oriented program when conditional logic decides which class to instantiate, and then future evaluations of that conditional are done through polymorphism rather than through duplicated conditional code.

If this separation is useful within a single code base, it's especially vital when you're using foreign elements such as components and services. The first question is whether you wish to defer the choice of implementation class to particular deployments. If so you need to use some implementation of plugin. Once you are using plugins then it's essential that the assembly of the plugins is done separately from the rest of the application so that you can substitute different configurations easily for different deployments. How you achieve this is secondary. This configuration mechanism can either configure a service locator, or use injection to configure objects directly.

--------------------------------------------------------------------------------

Some further issues

In this article, I've concentrated on the basic issues of service configuration using Dependency Injection and Service Locator. There are some more topics that play into this which also deserve attention, but I haven't had time yet to dig into. In particular there is the issue of life-cycle behavior. Some components have distinct life-cycle events: stop and starts for instance. Another issue is the growing interest in using aspect oriented ideas with these containers. Although I haven't considered this material in the article at the moment, I do hope to write more about this either by extending this article or by writing another.

You can find out a lot more about these ideas by looking at the web sites devoted to the lightweight containers. Surfing from the picocontainer and spring web sites will lead to you into much more discussion of these issues and a start on some of the further issues.

--------------------------------------------------------------------------------

Concluding Thoughts

The current rush of lightweight containers all have a common underlying pattern to how they do service assembly - the dependency injector pattern. Dependency Injection is a useful alternative to Service Locator. When building application classes the two are roughly equivalent, but I think Service Locator has a slight edge due to its more straightforward behavior. However if you are building classes to used in multiple applications then Dependency Injection is a better choice.

If you use Dependency Injection there are a number of styles to choose between. I would suggest you follow constructor injection unless you run into into one of the specific problems with that approach, in which case switch to setter injection. If you are choosing to build or obtain a container, look for one that supports both constructor and setter injection.

The choice between Service Locator and Dependency Injection is less important than the principle of separating service configuration from the use of services within an application.

--------------------------------------------------------------------------------

--------------------------------------------------------------------------------

Acknowledgements

My sincere thanks to the many people who've helped me with this article. Rod Johnson, Paul Hammant, Joe Walnes, Aslak Helles?on Tirs鮠and Bill Caputo helped me get to grips with these concepts and commented on the early drafts of this article. Berin Loritsch and Hamilton Verissimo de Oliveira provided some very helpful advice on how Avalon fits in. Dave W Smith persisted in asking questions about my initial interface injection configuration code and thus made me confront the fact that it was stupid.

Significant Revisions

23 Jan 04: Redid the configuration code of the interface injection example.

16 Jan 04: Added a short example of both locator and injection with Avalon.

14 Jan 04: First Publication

--------------------------------------------------------------------------------

   

--------------------------------------------------------------------------------

© Copyright Martin Fowler, all rights reserved

(转帖)Spring Gossip: Inversion of Control

在翻译一篇涉及Spring的文章,里面有个概念叫作:Inversion of Control,我觉得这篇文章解释很透。就贴在这里了。

(转自:http://caterpillar.onlyfun.net/Gossip/SpringGossip/IOC.html)

现在还没有专门的计划去学习Spring,就把它统归在“学习java”这个大的分类里,以后再转到合适的目标下。(需要完善日记重新归类的功能)

From Gossip@caterpillar

Spring Gossip: Inversion of Control

Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。

IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。

Dependency Inversion The Dependency Inversion Principle 有清楚的解釋。

簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。

舉個例子,例如下面這個程式:

#include <floppy.h>

....

void save() {

        ....

        saveToFloppy()

    }

}

由於save()程式依賴於依賴於saveToFloppy(),如果今天要更換低層的存儲模組為Usb碟,則這個程式沒有辦法重用,必須加以修改才行,低層模組的更動造成了高層模組也必須跟著更動,這不是一個好的設計方式,在設計上希望模組都依賴於模組的抽象,這樣才可以重用高層的業務設計。

如果以物件導向的方式來設計,依賴反轉(Dependency Inversion)的解釋變為程式不應依賴實作,而是依賴於抽象,實作必須依賴於抽象。來看看下面這個 Java 程式:

public class BusinessObject {

    private FloppyWriter writer = new FloppyWriter();

    ....

   

    public void save() {

        ...

        writer.saveToFloppy();

    }

}

在這個程式中,BusinessObject 的存檔依賴於實際的 FloppyWriter,如果今天想要將存檔改為存至 Usb 碟,則必須修改或繼承 BusinessObject 進行擴展,而無法直接使用BusinessObject。

如果透過介面的宣告,可以改進此一情況,例如:

public interface IDeviceWriter {

    public void saveToDevice();

}

public class BusinessObject {

    private IDeviceWriter writer;

    public void setDeviceWriter(IDeviceWriter writer) {

        this.writer = writer;

    }

    public void save() {

        ....

        writer.saveToDevice();

    }

}

這樣一來,BusinessObject 就是可重用的,如果今天有存儲至 Floppy 或 Usb 碟的需求,只要實作 IDeviceWriter 即可,而不用修改 BusinessObject:

public class FloppyWriter implement IDeviceWriter {

    public void saveToDevice() {

        ....

        // 實際儲存至Floppy的程式碼

    }

}

public class UsbDiskWriter implement IDeviceWriter {

    public void saveToDevice() {

        ....

        // 實際儲存至UsbDisk的程式碼

    }

}

從這個角度來看,Dependency Inversion 的意思即是程式不依賴於實作,而是程式與實作都要依賴於抽象。

IoC 的 Control 是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,可以獲得元件的可重用性,在上面的 Java 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的 IDeviceWriter 介面上,使得BusinessObject、FloppyWriter、UsbDiskWriter 這幾個實現依賴於抽象的 IDeviceWriter 介面。

程式的業務邏輯部份應是可以重用的,不應受到所使用框架或容器的影響,因為可能轉移整個業務邏輯至其它的框架或容器,如果業務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。

IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:"Don't call me, I'll call you." 以程式的術語來說的話,就是「不要向容器要求您所需要的(物件)資源,容器會自動將這些物件給您!」。IoC 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。

真是惭愧,至今也没有通读一遍Struts的教程

  电子教程倒是下了好几本,都是临到用时去翻。一些基本的东西的概念也是模糊的。像<html:link/>的用法。我就记不得了。书上说:

  The html:link tag renders an HTML anchor tag (i.e., a hyperlink). This tag uses a lot of the same common

attributes as described earlier.You have multiple options for rendering the URL of a hyperlink. You can use the

href, action, forward, or page attributes to specify the URL.

 The href attribute is used to specify a full

URL without any knowledge of the web context of this web application.

 The page attribute is used to specify a

web context relative link.

 The action attribute is used to specify a link to an action mapping, as described in the

Struts config file.

 The forward attribute is used to specify a link to a global forward, as described in the Struts

config file.

Tip: TIP: If you are following a Model 2/MVC architecture, then you should use the page attribute and

the href attribute sparingly. In fact, you should almost never use the page attribute. The href

attribute should only be used to link to resources that are not in the current web application. This helps

you separate the controller from the View by not letting the View select the next View directly. Only the

controller should select the next View. Using the action and forward attributes instead forces you

to delegate selection of the next View to the controller.

Here is an example of linking to an action (/html-link.do) with the page attribute:

<html:link page="/html-link.do">

Linking with the page attribute.

</html:link>

Notice that you do not have to specify the web context of the web application. Conversely, if you used the href

attribute, you would have to specify the web context as follows (where the context is struts-exercise):

<html:link href="/struts-exercise-taglib/html-link.do">

Using Href

</html:link>

Obviously, it is better to use the page attribute when you are linking to things in the same web application (thus,

the same context). You can also use the href attribute to create links that are not on the same server as follows:

<html:link

href="http://otherserver/strutsTut/html-link.do">

Using Href

</html:link>

Another way to link to the html-link.do action is to use the action attribute as follows:

<html:link action="/html-link">

Using Action attribute

</html:link>

(转帖)web中下拉列表的几种实现

转自:http://www.matrix.org.cn/resource/article/43/43809.html

Matrix首页 Java文栏 业界新闻 部落格 资源下载 Java 论坛 web中下拉列表的几种实现

wldandan 发表于2005-09-23 作者:wldandan 评价:0/0 评论数:4 点击数:1490 [收藏]

摘要:

总结一下关于web上使用下拉框的情况

本文Matrix永久镜像:http://www.matrix.org.cn/resource/article/43/43809.html

说明:本文可能由Matrix原创,也可能由Matrix的会员整理,或者由

Matrix的Crawler在全球知名Java或者其他技术相关站点抓取并永久

保留镜像,Matrix会保留所有原来的出处URL,并在显著地方作出说明,

如果你发觉出处URL有误,请联系Matrix改正.

总结一下关于web上使用下拉框的情况

从数据库中获得数据List,将数据放到Request里面

        使用setAttribute(”AList”,AList)

A中有2个属性(String id,String value)

1.        使用JSTL的forEach方式

<select name=”xx” ……..>

<c:forEach items="${AList}" var="p" >

        <c:choose>

                <c:when test="${xxx == p.id}">

                        <option value='<c:out value="${p.id}"/>' selected="selected">

                                        <c:out value="${p.value}"/>

                        </option>

                </c:when>

        <c:otherwise>

                        <option value='<c:out value="${p.id}"/>'>

                                <c:out value="${p.value}"/>

                        </option>

                </c:otherwise>

        </c:choose>       

<c:forEach>

</select>

2.        使用struts的标签

<html:select property=”xxx”>

<html:options collection="AList" labelProperty="value" property="id" />

</html:select>

查一下struts的api文档,可以看到select 中选项有3 taglib可以使用。

第一种直接使用把所有选项写在中间。

<html:option value="0-15">0-15</html:option> <html:option value="15-20" >15-20</html:option> <html:option value="20-30" >20-30</html:option> <html:option value="20 or above">30 or above</html:option>

第二种:把选项放在一个Collection中(这里使用List).在实际项目中,更多的是可能数据来源于db,文件等。这种情况用得比较多。

<html:options collection="AList" property="value" labelProperty="label"/>把option放在list中的过程在Action中作处理//prepare the age selector list.List ageList =new ArrayList();ageList.add(new LabelValueBean("0-15","0-15"));ageList.add(new LabelValueBean("15-20","15-20"));ageList.add(new LabelValueBean("20-30","20-30"));ageList.add(new LabelValueBean("30 or above","30 or above"));request.setAttribute("AList",AList);

这里使用了LabelValueBean,可以不用的,象

<html:options collection="AList" labelProperty="value" property="id" />

只要在AList中填入的bean有value和id属性就可以

第三种,把此list 作为Form 的一个属性.

<html:optionsCollection property="AList" />

在Form 中添加AList 的setter和getter. Form中作如下处理。

//the list can be a form property.

f.setAgeList(AList);

1.        从数据库中获得数据,你应该在Action里面取得数据后,将数据放到Request里面

2.        数据取出来后放在一个List或Collection或Map里面,我习惯用List

3.        从List或其它的容器中取数据应该用<html:options> 或<html:optionsCollection>

4.        <html:options> 和<html:optionsCollection>外层必须用<html:select property="">,所以这个属性你必须在FormBean里定义

5.        由于你要用到这些标签,所以你必须定义FormBean

6.       

从Action取数据,以List为例

List list = xxxxx;//从数据库中取得下拉列表中的数据

request.setAttribute("list",list);

在页面显示

<html:form action="xxxx">...<html:select property="xxx"><html:options collection="list" labelProperty="下拉框中显示的内容,一般是name或其它相似属性" property="各选项对应的值,一般是id" /></html:select>...</html:form>

补充一点点:

因为数据你要从 数据库去取, 所以一般在 action 里调用 DAO ,作为 request 的一个属性传到页面上; 这时一般用 <html:options .../> 标签

另外,如果数据不从数据库去取,而是代码固定的,则一般把这种放到 ActionForm 里,作为属性在页面上取,这时一般用 <html:optionsCollection ... />

我来评价此文: 非常好 还行 一般 扔鸡蛋 总得分:0 投票人次:0

→用户评论列表

#7269 评论作者: littlebat 发表时间:2005-12-27 10:34

正准备用它,对于我来说,上面的信息足够了。谢谢。

#6861 评论作者: jctr 发表时间:2005-12-09 03:59

还应该有第三种,Tapestry的实现,其实也不只这三种还有很多很多吧

#5288 评论作者:xuerldx 发表时间:2005-10-25 07:37

还是不错,就是 有点肤浅

#4811 评论作者: highfan 发表时间:2005-10-05 04:46

你可以把collection 作为

actionform 的

一个属性,在 actionform 中 初始化 也可以 ,在 action 中

初始化 也可以。这样更简单。同时减少view和 control 直接的命名依赖。

(转帖)Struts资源文件:Struts Message Resources

转自:http://www.javafan.net/article/20040430102526725.html

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】   

  

 

Struts Message Resources

作者:Nick Heudecker    来自:未知

总览

许多刚刚学习Struts的程序员在使用Struts的MessageResources特性的时候会遭遇很多困难。本文将试图阐述MessageResources特性的优点并给出了具体的例子说明它的用法。

作者: Nick Heudecker, System Mobile Inc.

概述

类MessageResources可以使开发者方便地支持多语言,包括支持多时间格式和数字格式。使用资源包的另一个好处是允许开发者将标签字符串集中存储在一个位置,而不是分散在不同的JSP页面里。例如,对于每个用户的名字的标签"First Name" ,我们可以将它写在资源包中,在适当的地方通过Struts标签简单的进行引用:

<bean:write key="label.first.name"/>

这样做将会让你对程序的更改变的简单容易,你不必在每一个JSP页面里更改标签的内容了。

用法

使用消息资源包需要你做下面的事情:

1. 为你想要支持的地方创建一个消息资源包。

2. 配置WEB应用,加载消息资源包。

3. 使用相应的JSP标签加载资源或者...

4. ...在一个Action类中加载资源。

创建资源包

MessageResources 类的默认的实现是一个包含"key=value" 对的文件,下面的一个消息资源包文件的例子。

label.username=Username

label.password=Password

label.first.name=First Name

label.last.name=Last Name

label.email=Email Address

label.phone.number=Phone Number

label.welcome=Welcome back {0} {1}!

error.min.length=The input must be at least {0} characters in length.

error.max.length=The input cannot be longer than {0} characters in length.

大括号包围的整数是对java.text.MessageFormat 类的支持,程序员可以向value字符串中传递参数,对每个value字符串你最多可以传递4个参数。

配置

有两种途径通知Struts你的资源包的位置:web.xml 文件或者struts-config.xml 文件。首先来看web.xml 文件的配置:

<servlet>

<servlet-name>action</servlet-name>

<servlet-class>

    org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>

    application

</param-name>

<param-value>

    com.systemmobile.example.ApplicationResources

</param-value>

</init-param>

</servlet>

这个配置说明你的资源包的名字是ApplicationResources.properties,它位于com.systemmobile.example 包中。后缀".properties" 是隐含的,你不必显式地写出来。如果你还有另一个资源文件在相同的包中,例如ApplicationResources_fr.properties ,用来支持法语,你只需要象上面定义的那样列出文件名字即可。

定义资源文件的第二中方法(上面已经提到),是在struts-config.xml 文件中配置:

<message-resources parameter="com.systemmobile.example.ApplicationResources"/>

属性parameter 是必须的。和在web.xml文件中配置一样, 需要注意的是文件在包中的位置。

使用struts-config.xml 文件来配置消息资源文件是推荐的做法,因为它更有可扩展性,更灵活。

你可以使用message-resources 标签从不同的资源文件取不同的消息,前提是在配置的时候为不同的资源文件给出不同的key 属性的值。例如: <message-resources key="myResources" parameter="com.systemmobile.example.ApplicationResources"/>

<message-resources key="moreResources" parameter="com.systemmobile.example.MoreApplicationResources"/>

然后你必须这样使用bean:message 标签: <bean:message bundle="moreResources" key="some.message.key"/>

设置属性null 的值为"false" 后,如果某个资源字符串不存在将返回???key??? 而不是仅仅显示null。这样很容易在JSP页面中看到你没有定义的资源,使得内部测试的速度更快。(关于如何从资源文件中获得消息的详细内容参见国际化 一节) <message-resources parameter="com.systemmobile.example.ApplicationResources" null="false"/>

另外,message-resources 标签允许你使用自己实现的MessageResourcesFactory 接口,这不在本文的讨论范围。

资源文件放在哪里

关于资源文件最常见的问题是将资源文件放在WAR文件的哪里。简单的回答是该文件必须放在你的classpath下面,这意味着将资源文件放在一个JAR 文件中,或者放在/WEB-INF/classes 目录极其子目录下。下表给出了资源文件的位置,在message-resources 标签中"parameter" 属性的值以及简短的说明。

Resources Location parameter Value Description

/WEB-INF/classes/ApplicationResources.properties ApplicationResources 文件放在classes 目录下, 该目录在web应用的classpath中.

/WEB-INF/classes/resources/ApplicationResources.properties resources.ApplicationResources 该文件放在"resources" 目录下, 所以包名也就是路径名要给出。

In the app.jar file, in the com.systemmobile.example package/directory. com.systemmobile.example.ApplicationResources 文件在JAR文件中的全路径。

Tags

最常用Struts 标签是bean:message 标签。使用这个标签的"key" 可以从资源文件中读特定的消息资源。你还可以传入四个参数中的一个或全部:

<bean:message key="label.password"/>

<bean:message key="error.min.length" arg0="6"/>

<bean:message key="label.welcome" arg0="Ralph" arg1="Nader"/>

html:message 可以让你向用户显示错误信息(默认)或消息信息,而html:errors 只显示错误信息。很明显,错误信息或消息信息一定要保存在request里,否则就什么也不会显示。这里有一个显示消息信息的例子:

<logic:messagesPresent message="true">

  <html:messages id="msg" message="true">

    <div class="success">

      <bean:write name="msg"/>

    </div><br/>

  </html:messages>

</logic:messagesPresent>

还有一些标签也有限地支持消息资源,比如html:link。html:link标签通过定义"titleKey" 属性来显示标题文本。许多html 使用 "altKey" 属性从资源文件里获得alternate(替代)文本。

Actions

你还可以在Action 类中使用消息资源文件。Action 类有两个方法得到一个MessageResource 类的实例:

// 返回一个request里的资源文件

protected MessageResources getResources(HttpServletRequest request);

// 返回一个request里的资源文件,

// 该资源文件的标志上<message-resources/> 元素的内容

protected MessageResources getResources(javax.servlet.http.HttpServletRequest request, java.lang.String key);

MessageResources类可以让你从资源文件中得到本地化的消息。The API for MessageResources 可以在资源中找到。比较常用的方法有:

// these methods load a resources key for the given locale

public String getMessage(java.util.Locale locale, java.lang.String key);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object[] args);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1)

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

// these methods load a resources key for the locale retrieved

// from the HttpServletRequest

public String getMessage(java.lang.String key);

public String getMessage(java.lang.String key, java.lang.Object arg0);

public String getMessage(java.lang.String key, java.lang.Object[] args);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

这些返回的字符串可以被设置成request 或 session 的参数并串会表现层。你可能注意到了一些重载方法getMessage(...) 选择了参数Object,而另外一些采用了参数arg0...arg3。这和 bean:message arg0...arg3 属性等价。

除了MessageResources 类,还有一些类使用了资源文件。ActionMessage类被用来从action 向JSP之间传递消息资源中的keys 。消息被用来作为bean 的属性。ActionError, ActionMessage的子类,使用消息资源中的keys 存储验证失败后的错误信息。

国际化

从资源文件中提取一个本地化信息可以由类MessageResources 来处理,或者由它的直接子类PropertyMessageResources类处理。既然类PropertyMessageResources 等经常地被用到,那么我们就来看看它是怎样使用getMessage(Locale, String) 方法来从资源文件中读取消息的。

举例说明:

1. 如果你在ApplicationResources_pt_br.properties (Brazilian Portuguese)中没有发现消息的定义,系统将在ApplicationResources_pt.properties 文件中找,如果ApplicationResources_pt.properties 文件不存在或者也没有该消息,那就去ApplicationResources.properties 文件里查找了。

2. 如果消息找到了,它就被加到本地化的缓存中并返回java.lang.String型数据。

3. 如果消息没有找到,此时如果returnNull 属性被为默认的true,将返回 null。 否则将返回类似 ???key??? 的字符串,key 就是那个被传递的参数。

JSTL

JSTL (JavaServer Pages Standard Tag Library) 的fmt标签最近开始流行起来,用来向JSP中显示资源文件的信息。它能很好地和Struts结合在一起。使用它非常简单,只要下载JSTL 的jar 文件和TLDs 并把它们拷贝到你的应用的正确的位置,然后在web.xml文件中加入下面的内容:

<context-param>

  <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>

  <param-value>ApplicationResources</param-value>

</context-param>

上面的配置是假定你的ApplicationResources.properties文件是放在/WEB-INF/classes 目录下的。 参见above 更多情况。

然后将这个标签库直接放在你的JSP中:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

最后,下面的标签将显示资源文件的内容:

<fmt:message key="label.first.name"/>

还有一个使用fmt 标签获得资源的例子。(注意: 该段程序取自Jakarta JSTL examples。)

// loading a resource from a specific bundle and populating a parameter

<fmt:message key="currentTime" bundle="${deBundle}">

 <fmt:param value="${currentDateString}"/>

</fmt:message>

// using the forEach iterator to populate paramters

<fmt:message key="serverInfo" bundle="${deBundle}">

 <c:forEach var="arg" items="${serverInfoArgs}">

  <fmt:param value="${arg}"/>

 </c:forEach>

</fmt:message>

结论

在向JSP文件方便地传递消息的同时,Struts使用消息资源文件还帮助我们创建国际化的Web应用。我们既可以使用正在快速发展中的JSTL标签,也可以使用Struts标签,或直接在action中得到一条具体的消息。我希望这篇文章为您阐明了一些Struts中常用的但有时会混淆的东西。

关于作者

Nick Heudecker 是一位软件开发人员,具有6年的企业应用的开发经验。 他所在的公司, System Mobile, Inc.,专门从事应用集成,定制软件开发和无线应用。 他还是Sun认证JAVA程序员,现在居住在Ann Arbor, Michigan。

资源

下面的资源也许对您了解更多的关于Struts资源文件有帮助:

JavaDoc for the classes of interest:

java.util.ResourceBundle

java.util.Locale

org.apache.struts.util.MessageResources

org.apache.struts.action.ActionError

org.apache.struts.action.ActionMessage

Ted Husted's Struts Tips: Very handy advice, especially for resource bundle usage.

Struts Home Page

Struts-User Mailing List Archive: Many people use Struts, so there is a very good chance that your question has be answered already. Please use all available resources before asking your question on the mailing list.

JSTL Homepage and the Jakarta JSTL Implementation

注意事项

Packages are just directory structures used to avoid naming conflicts. If you put a message bundle in a directory named resources under /WEB-INF/classes, this is the equivalent of putting the file in a package named resrouces. While basic, this point seems to trip up many new programmers.

 

 

  

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】    

 

 

Copyright © 2003 - 2005 JavaFan.NET All Rights Reserved

慎用setActionForm() 在使用strutstestcase测试struts程序时

setActionForm()方法会调用ActionForm里的reset()方法清掉你传入actionForm中的测试数据。

如:

   EmailResetPsdForm form = new EmailResetPsdForm();

  form.setUserName("ppig");

  setActionForm(form);

在这里,测试数据userName="ppig"不会被传入使用这个form bean的action中。

应该使用替代的方法来传递测试数据:

addRequestParameter("userName", "ppig");

下面是strutstestcase里的文档对setActionForm方法的说明。

    /*

     * setActionForm

     * public void setActionForm(ActionForm form)Sets an ActionForm instance to be used in this

     * test. The given ActionForm instance will be stored in the scope specified in the Struts

     * configuration file (ie: request or session). Note that while this ActionForm instance

     * is passed to the test, Struts will still control how it is used. In particular, it

     * will call the ActionForm.reset() method, so if you override this method in your

     * ActionForm subclass, you could potentially reset attributes in the form passed

     * through this method.

     *

     * Parameters:

     * form - the ActionForm instance to be used in this test.

     *

     */

ant初始化数据库的问题

如果数据库是utf-8编码,用下面的初始化数据库任务会出错:

<target name="initdb" >

<sql driver="com.mysql.jdbc.Driver"

url="jdbc:mysql://localhost:3306/learndiarydb"

userid="dbuser"

password="1234"

classpath="lib/mysql-connector-java-3.1.12-bin.jar"

autocommit="true"

rdbms="mysql"

print="true"

src="database/testdata_mysql.sql" />

</target>

错误报告为:

Buildfile: build.xml

initdb:

      [sql] Executing file: F:\learndiary\learndiary\old\database\testdata_mysql.sql

      [sql] 0 rows affected

      [sql] Failed to execute:    INSERT INTO article VALUES (1,1,0,'鍏憡鐗?,'鍏憡鐗?,'2004-09-28 08:38:54','',1,'2005-11-10 08:27:50',6,1,'admin',1)

BUILD FAILED

F:\learndiary\learndiary\old\build.xml:124: java.sql.SQLException: Syntax error or access violation,  message from server: "You have an error in your SQL syntax.  Check the manual that corresponds to your MySQL server version for the right syntax to use near '閸忣剙鎲¢悧锟?,'2004-09-28 08:38:54','',1,'2005-11-10 08:27:50"

Total time: 12 seconds

而这个初始化通过mysql的source learndiarydb yourpath\testdata_mysql.sql的命令来进行则不会出错。

不知是什么原因?

Junit不能用于多线程的测试吗?

  我在为(改善学习日记的登录模式 (0篇) http://www.learndiary.com/disGoalContentAction.do?goalID=1214&naviStr=a10a60)写发送重置密码信件的测试代码时,发现测试过程中,测试代码中另一个发送邮件的线程没有执行,测试程序就结束了。

  难道junit不能用于多线程的程序的测试吗?

  

  这是含发送邮件的程序:

// $Id: EmailResetPsdAction.java,v 1.8 2005/12/19 16:00:49 dashing_meng Exp $

// Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

// Permission to use, copy, modify, and distribute this software and its

// documentation without fee, and without a written agreement is hereby

// granted, provided that the above copyright notice and this paragraph

// appear in all copies.  This software program and documentation are

// copyrighted by http://www.learndiary.com. The software program and

// documentation are supplied "AS IS", without any accompanying services

// from The LearnDiary. The LearnDiary does not warrant that the operation

// of the program will be uninterrupted or error-free. The end-user

// understands that the program was developed for research purposes and is

// advised not to rely exclusively on the program for any reason.

// IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

// DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

// POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

// DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

// SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

// HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.*;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionMapping;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.manager.EmailResetPsdManager;

import com.learndiary.website.manager.EmailSender;

import com.learndiary.website.manager.UserManager;

import com.learndiary.website.model.Email;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

import com.learndiary.website.util.Util;

/**

 * Process user's asking for sending resetting password token email request.

 * @author http://www.learndiary.com,LearnDiary Develop Group

 */

public class EmailResetPsdAction extends Action {

  /**

   *  Function:

   *  Process user's asking for sending resetting password token email request.

   *  Pseudo Coding:

   * 

   *  {

   *    String userName;

   *    UserInfo userInfo;

   *    int userID;

   *    UserManager userManager;

   *    EmailResetPsdManager emailResetPsdManager;

   *    EmailResetPsdInfo emailResetPsdInfo;

   *    Email email;

   * 

   *    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

   *    userInfo= userManager. findByName(userName);//Get userInfo

   *    if (userInfo==null){

   *      forward to emailFailure.jsp;

   *    }

   *    userID=userInfo.getUserID();

   *    emailResetPsdInfo=emailResetPsdManager.findByID();// Get emailResetPsdInfo

   *    if (emailResetPsdInfo!=null){

   *      foward to emailFailure.jsp;

   *    }

   * 

   *    //set emaiResetPsdInfo

   *    emaiResetPsdInfo.setUserID(userID);

   *    emaiResetPsdInfo.setToken(com.learndiary.website.util.Util.genRandomStr());

   *    emailResetPsdInfo.setSendDate(System.getCurrentTime());

   *    emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);//record emailResetPsdInfo

   *    //set reset passwrod email's content

   *    email.setRecipient(userInfo.getEmail());

   *    email.setSubject("your reset password request.");

   *    email.setText( "http://localhost:8080/learndiary/resetPsdAction.do?userID=" + userID + "token=" + emaiResetPsdInfo.getToken());

   *    emailSender.send(email);

   *    forward to emailSuccess.jsp;

   *  }

   * 

   * 

   *   

   */

    private org.apache.commons.logging.Log __log = LogFactory.getFactory().getInstance(this.getClass());

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {

String myaction = mapping.getPath(); 

    __log.info("Enter action: "+myaction);

    String target=null;

    String userName=null;

    UserInfo userInfo=null;

    int userID;

    UserManager userManager=new UserManager();

    EmailResetPsdManager emailResetPsdManager=new EmailResetPsdManager();

    EmailResetPsdInfo emailResetPsdInfo=null;

    Email email=null;

    EmailSender emailSender=new EmailSender();

   

    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

    try {

__log.debug("Before findByName,and name is: "+userName);

        userInfo= userManager.findByName(userName);//Get userInfo

__log.debug("After findByName,and userInfo is: "+userInfo);

    } catch (ClassNotFoundException e1) {

e1.printStackTrace();

    }

    if (userInfo==null){

      target=new String("failure");

  return (mapping.findForward(target));//no such a user,forward to emailFailure.jsp;

    }

    userID=userInfo.getUserID();

    __log.debug("userID: "+userID);

    try {

        emailResetPsdInfo=emailResetPsdManager.findByID(userID);// Get emailResetPsdInfo

    } catch (ClassNotFoundException e) {

e.printStackTrace();

    }

   

    __log.debug("existed emailResetPsdInfo is: " + emailResetPsdInfo);

    if (emailResetPsdInfo!=null){//user requested emailing resetting password token in 72 hours

      target=new String("failure");

  return (mapping.findForward(target));//foward to emailFailure.jsp;

    }

 

    //set emaiResetPsdInfo

emailResetPsdInfo=new EmailResetPsdInfo();

    emailResetPsdInfo.setUserID(userID);

    emailResetPsdInfo.setToken(Util.genRandomStr());

    emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()));

    __log.debug("new emailResetPsdInfo is: "+emailResetPsdInfo);

    try {

      emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);

      __log.debug("insert emailResetPsdInfo ok!");

    } catch (Exception e2) {

      e2.printStackTrace();

    }//record emailResetPsdInfo

    //set reset passwrod email's content

    email=new Email();

    email.setRecipient(userInfo.getEmail());

    email.setSubject("Your resetting password request.");

    email.setText( "http://localhost:8080/learndiary_login/account/resetPsd.jsp?userID="

      + userID + "&token=" + emailResetPsdInfo.getToken());

    __log.debug("email is: " + email); 

    emailSender.send(email);//这里要产生另一个线程发送邮件,但在测试程序中没有执行,在正常运行时会执行

   

    target=new String("success");

return (mapping.findForward(target));//email resetting password token success,

                                     //forward to emailSuccess.jsp;

  }

 

  /**

   * process general error

   * @param e error object

  

  private void generalError(Exception e) {

    e.printStackTrace();

    __log.error(" [EmailResetPsd] Error - " + e.getMessage());

  }*/

}

这时对应的测试程序:

//$Id: EmailResetPsdActionTest.java,v 1.3 2005/12/19 16:00:49 dashing_meng Exp $

//Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

//Permission to use, copy, modify, and distribute this software and its

//documentation without fee, and without a written agreement is hereby

//granted, provided that the above copyright notice and this paragraph

//appear in all copies.  This software program and documentation are

//copyrighted by http://www.learndiary.com. The software program and

//documentation are supplied "AS IS", without any accompanying services

//from The LearnDiary. The LearnDiary does not warrant that the operation

//of the program will be uninterrupted or error-free. The end-user

//understands that the program was developed for research purposes and is

//advised not to rely exclusively on the program for any reason.

//IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

//DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

//INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

//DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

//POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

//DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

//SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

//HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

//SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import servletunit.struts.MockStrutsTestCase;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.dao.EmailResetPsdDAO;

import com.learndiary.website.dao.TransContext;

import com.learndiary.website.dao.UserDAO;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

/**

 * test EmailResetPsdAction

 * @author http://www.learndiary.com, LearnDiary Develop Group

 */

public class EmailResetPsdActionTest extends MockStrutsTestCase {

Log log = LogFactory.getLog(EmailResetPsdActionTest.class.getName());

private transient TransContext globalTran = null;

UserDAO userDAO=null;

EmailResetPsdDAO emailResetPsdDAO=null;

//the contextDir can be commented by adding classpath:${web} into build.xml

//String contextDir = "E:\zhangwei\eclipse_workspace1\learndiary_login\web";

public EmailResetPsdActionTest(String testName) {

super(testName);

}

   

    /**

     * empty table "user" and "emailresetpsd";

     * insert two user:"ppig" and "tom" into table user,

     * "ppig" to test successful case,

     * "tom" to test duplicate request failure case;

     * insert a record into table emailresetpsd for

     * "tom" to test duplicate request failure case.

     * @see junit.framework.TestCase#setUp()

     */

public void setUp() throws Exception {

super.setUp();

//this.setContextDirectory(new File(contextDir));

log.debug("ahah");

globalTran = new TransContext();

userDAO = new UserDAO(globalTran);

emailResetPsdDAO=new EmailResetPsdDAO(globalTran);

//empty two tables

userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

       

        UserInfo info1=new UserInfo();//for testing successful request

info1.setUserName("ppig");

info1.setPsd("123456");

info1.setEmail("learndiary@126.com");

userDAO.insertObject(info1);

UserInfo info2=new UserInfo();//for testing duplicate request failure

info2.setUserName("tom");

info2.setPsd("223456");

info2.setEmail("tom@tom.com");

userDAO.insertObject(info2);

        int userID2=((UserInfo)userDAO.findByName("tom")).getUserID();

        

EmailResetPsdInfo emailResetPsdInfo=new EmailResetPsdInfo();

emailResetPsdInfo.setUserID(userID2);

emailResetPsdInfo.setToken("12345678");

emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()-1000*60*2));

emailResetPsdDAO.insertObject(emailResetPsdInfo);

}

   

    /**

     * empty table "user" and table "emailresetpsd"

     * @see junit.framework.TestCase#tearDown()

     */

public void tearDown() throws Exception {

super.tearDown();

        userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

}

/**

* request emailing resetting password token successfully

*/

public void testSuccess() {

// a valid request

setRequestPathInfo("/emailResetPsdAction");

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("ppig");

setActionForm(form);

actionPerform();

verifyForward("success");

// verifyForwardPath("/success.jsp");

// assertEquals("deryl",getSession().getAttribute("authentication"));

verifyNoActionErrors();

}

    /**

     *  request emailing resetting password token failure

     *

     */

public void testFailure() {

// fail when duplicate request

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("tom");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

// fail when user not found

form = new EmailResetPsdForm();

form.setUserName("000000000000000");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

verifyNoActionErrors();

}

public static void main(String[] args) {

junit.textui.TestRunner.run(EmailResetPsdActionTest.class);

}

}

呵呵,测试了一下新买的浴霸(非程序:))

  新买某种品牌的浴霸,说明书称:出厂经4。C的冰水测试通过。

  这两天在学测试程序,为了验证这个浴霸的安全性,我对它测试了一番。用还是热的水泼在取暖泡上,哧的一响,经受了考验,再来一下,在另一个取暖泡上测试。结果取暖泡里面冒烟了。表面也有裂纹了。失败。

  看来,出厂的测试是夸大其辞呀。

  老婆骂我。。。

  我只是告诉她,经过测试,这个浴霸不能被淋浴头淋到。

  浴霸真的不能被水淋吗?还是我们这个浴霸本身就有问题?还是测试过度了。我自己也糊涂了。。。