• No results found

Problem

Many real-world components have to perform certain types of initialization tasks before they are ready to be used. Such tasks include opening a file, opening a network/database connection, allocating memory, and so on. Also, they have to perform the corresponding destruction tasks at the end of their life cycle. So, you have a need to customize bean initialization and destruction in the Spring IoC container.

Solution

In addition to bean registration, the Spring IoC container is also responsible for managing the life cycle of your beans, and it allows you to perform custom tasks at particular points of their life cycle. Your tasks should be encapsulated in callback methods for the Spring IoC container to call at a suitable time.

The following list shows the steps through which the Spring IoC container manages the life cycle of a bean. This list will be expanded as more features of the IoC container are introduced.

1. Create the bean instance either by a constructor or by a factory method. 2. Set the values and bean references to the bean properties.

3. Call the initialization callback methods. 4. The bean is ready to be used.

5. When the container is shut down, call the destruction callback methods.

There are three ways that Spring can recognize your initialization and destruction callback methods. First, your bean can implement the InitializingBean and DisposableBean life cycle interfaces and implement the afterPropertiesSet() and destroy() methods for initialization and destruction. Second, you can set the init-method and destroy-method attributes in the bean declaration and specify the callback method names. In Spring 2.5 or later, you can also annotate the initialization and destruction callback methods with the life cycle annotations @PostConstruct and @PreDestroy, which are defined in JSR-250, Common Annotations for the Java Platform. Then you can register a

CommonAnnotationBeanPostProcessor instance in the IoC container to call these callback methods.

How It Works

To understand how the Spring IoC container manages the life cycle of your beans, let’s consider an example involving the checkout function. The following Cashier class can be used to check out the products in a shopping cart. It records the time and the amount of each checkout in a text file. package com.apress.springrecipes.shop;

CHAPTER 2 ■ ADVANCED SPRING IOC CONTAINER

78

...

public class Cashier { private String name; private String path;

private BufferedWriter writer; public void setName(String name) { this.name = name;

}

public void setPath(String path) { this.path = path;

}

public void openFile() throws IOException { File logFile = new File(path, name + ".txt"); writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(logFile, true))); }

public void checkout(ShoppingCart cart) throws IOException { double total = 0;

for (Product product : cart.getItems()) { total += product.getPrice();

}

writer.write(new Date() + "\t" + total + "\r\n"); writer.flush();

}

public void closeFile() throws IOException { writer.close();

} }

In the Cashier class, the openFile() method opens the text file with the cashier name as the file name in the specified system path. Each time you call the checkout() method, a checkout record will be appended to the text file. Finally, the closeFile() method closes the file to release its system resources.

Then, you declare a cashier bean with the name cashier1 in the IoC container. This cashier’s checkout records will be recorded in the file c:/cashier/cashier1.txt. You should create this directory in advance or specify another existing directory.

<beans ...> ...

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier"> <property name="name" value="cashier1" />

<property name="path" value="c:/cashier" /> </bean>

However, in the Main class, if you try to check out a shopping cart with this cashier, it will result in a NullPointerException. The reason for this exception is that no one has called the openFile() method for initialization beforehand.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext; public class Main {

public static void main(String[] args) throws Exception {

ApplicationContext context =

new FileSystemXmlApplicationContext("beans.xml"); Cashier cashier1 = (Cashier) context.getBean("cashier1"); cashier1.checkout(cart1);

} }

Where should you make a call to the openFile() method for initialization? In Java, the initialization tasks should be performed in the constructor. But would it work here if you call the openFile() method in the default constructor of the Cashier class? No, because the openFile() method requires both the name and path properties to be set before it can determine which file to open.

package com.apress.springrecipes.shop; ...

public class Cashier { ...

public void openFile() throws IOException { File logFile = new File(path, name + ".txt"); writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(logFile, true))); }

}

When the default constructor is invoked, these properties have not been set yet. So you may add a constructor that accepts the two properties as arguments, and call the openFile() method at the end of this constructor. However, sometimes you may not be allowed to do so, or you might prefer to inject your properties via setter injection. Actually, the best time to call the openFile() method is after all properties have been set by the Spring IoC container.

Implementing the InitializingBean and DisposableBean Interfaces

Spring allows your bean to perform initialization and destruction tasks in the callback methods afterPropertiesSet() and destroy() by implementing the InitializingBean and DisposableBean interfaces. During bean construction, Spring will notice that your bean implements these interfaces and call the callback methods at a suitable time.

CHAPTER 2 ■ ADVANCED SPRING IOC CONTAINER

80

package com.apress.springrecipes.shop; ... import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean;

public class Cashier implements InitializingBean, DisposableBean {

...

public void afterPropertiesSet() throws Exception {

openFile(); }

public void destroy() throws Exception {

closeFile(); }

}

Now if you run your Main class again, you will see that a checkout record is appended to the text file c:/cashier/cashier1.txt. However, implementing such proprietary interfaces will make your beans Spring-specific and thus unable to be reused outside the Spring IoC container.

Related documents