Suresh Rohan's Blog

This blog is all about Java, J2EE,Spring, Angular, React JS, NoSQL, Microservices, DevOps, BigData, Tutorials, Tips, Best practice, Interview questions, Views, News, Articles, Techniques, Code Samples, Reference Application and much more

Thursday, August 8, 2013

Using Method Injection

Using Method Injection


Spring’s method injection capabilities come in two loosely related forms, lookup method injection and method replacement. Lookup method injection provides a new mechanism by which a bean can obtain one of its dependencies, and method replacement allows you to replace the implementation of any method on a bean arbitrarily, without having to change the original source code.

To provide these two features, Spring uses the dynamic bytecode enhancement capabilities of CGLIB. If you want to use lookup method injection or method replacement in your application, make sure you have the CGLIB JAR file on your classpath

Lookup Method Injection

Lookup method injection was added to Spring to overcome the problems encountered when a bean depends on another bean with a different life cycle—specifically, when a singleton depends on a nonsingleton. In this situation, both setter and constructor injection result in the singleton maintaining a single instance of what should be a nonsingleton bean.

In this example, we create one nonsingleton bean and two singleton beans; all beans implement the same interface. One of the singletons obtains an instance of the nonsingleton bean using traditional setter injection; the other uses method injection.

The MyHelper Bean

public class MyHelper {
public void doSomethingHelpful() {
// do it!
}
}

This bean is decidedly unexciting, but it serves the purposes of this example perfectly.

The DemoBean Interface

public interface DemoBean {
MyHelper getMyHelper();
void someOperation();
}

you can see the DemoBean interface, which is implemented by both of the singleton
beans.

The DemoBean Interface
public interface DemoBean {
MyHelper getMyHelper();
void someOperation();
}

This interface has two methods: getMyHelper() and someOperation(). The sample application uses the getMyHelper() method to get a reference to the MyHelper instance and, in the case of the method lookup bean, to perform the actual method lookup. The someOperation() method is a simple method that depends on the MyHelper class to do its processing.

The StandardLookupDemoBean Class

public class StandardLookupDemoBean implements DemoBean {
private MyHelper helper;
public void setMyHelper(MyHelper helper) {
this.helper = helper;
}
public MyHelper getMyHelper() {
return this.helper;
}
public void someOperation() {
helper.doSomethingHelpful();
}
}

This code should all look familiar, but notice that the someOperation() method uses the stored instance of MyHelper to complete its processing.

you can see the AbstractLookupDemoBean class, which uses method injection to obtain an instance of the MyHelper class.

The AbstractLookupDemoBean Class

package com.apress.prospring2.ch04.mi;
public abstract class AbstractLookupDemoBean implements DemoBean {
public abstract MyHelper getMyHelper();
public void someOperation() {
getMyHelper().doSomethingHelpful();
}
}

Notice that the getMyHelper() method is declared as abstract, and that this method is called by the someOperation() method to obtain a MyHelper instance.

Configuring Method Lookup Injection

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helper" class="com.apress.prospring2.ch04.mi.MyHelper" scope="prototype"/>
<bean id="abstractLookupBean"
class="com.apress.prospring2.ch04.mi.AbstractLookupDemoBean">
<lookup-method name="getMyHelper" bean="helper"/>
</bean>
<bean id="standardLookupBean"
class="com.apress.prospring2.ch04.mi.StandardLookupDemoBean">
<property name="myHelper" ref="helper"/>
</bean>
</beans>

The configuration for the helper and standardLookupBean beans should look familiar to you by now. For the abstractLookupBean, you need to configure the lookup method using the <lookup-method> tag. The name attribute of the <lookup-method> tag tells Spring the name of the method on the bean that it should override. This method must not accept any arguments, and the return type should be that of the bean you want to return from the method. The bean attribute tells Spring which bean the lookup method should return.

The LookupDemo Class

public class LookupDemo {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(
new ClassPathResource("/META-INF/spring/midemo-context.xml")
);
stressTest(factory, "abstractLookupBean");
stressTest(factory, "standardLookupBean");
}
private static void stressTest(XmlBeanFactory factory, String beanName) {
DemoBean bean = (DemoBean)factory.getBean(beanName);
MyHelper helper1 = bean.getMyHelper();
MyHelper helper2 = bean.getMyHelper();
System.out.println("Testing " + beanName);
System.out.println("Helper Instances the Same?: " + (helper1 == helper2));
StopWatch stopWatch = new StopWatch();
stopWatch.start("lookupDemo");
for (int i = 0; i < 100000; i++) {
MyHelper helper = bean.getMyHelper();
helper.doSomethingHelpful();
}
stopWatch.stop();
System.out.println("100000 gets took " + stopWatch.getTotalTimeMillis() +
" ms");
}
}

Method Replacement


Although the Spring documentation classifies method replacement as a formof injection, it is very different from what you have seen so far. So far, we have used injection purely to supply beans with their collaborators. Using method replacement, you can replace the implementation of any method on any beans arbitrarily without having to change the source of the bean you are modifying.

Internally, you achieve this by creating a subclass of the bean class dynamically. You use
CGLIB and redirect calls to the method you want to replace to another bean that implements the MethodReplacer interface.

The ReplacementTarget Class

package com.apress.prospring2.ch04.mi;
public class ReplacementTarget {
public String formatMessage(String msg) {
StringBuilder sb = new StringBuilder();
sb.append("<h1>").append(msg).append("</h1>");
return sb.toString();
}
public String formatMessage(Object msg) {
StringBuilder sb = new StringBuilder();
sb.append("<h1>").append(msg).append("</h1>");
return sb.toString();
}
}

You can replace any of the methods on the ReplacementTarget class using Spring’s method
replacement functionality. In this example, we show you how to replace the formatMessage(String method), and we also compare the performance of the replaced method with that of the original.

To replace a method, you first need to create an implementation of the MethodReplacer class

Implementing MethodReplacer

public class FormatMessageReplacer implements MethodReplacer {
public Object reimplement(Object obj, Method method, Object[] args)
throws Throwable {
String msg = (String) args[0];
return "<h2>" + msg + "</h2>";
}
}

The MethodReplacer interface has a single method, reimplement(), that you must implement. Three arguments are passed to reimplement(): the bean on which the original method was invoked, a Method instance that represents the method that is being overridden, and the array of arguments passed to the method. The reimplement() method should return the result of your reimplemented logic and, obviously, the type of the return value should be compatible with the return type of the method you are replacing. In Listing 4-28, the FormatMessageReplacer replaces calls to all methods, regardless of their signatures. We can do this because we instruct Spring to only replace the formatMessage(String) method

Configuring Method Replacement

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="methodReplacer"
class="com.apress.prospring2.ch04.mi.FormatMessageReplacer"/>
<bean id="replacementTarget"
class="com.apress.prospring2.ch04.mi.ReplacementTarget">
<replaced-method name="formatMessage" replacer="methodReplacer">
<arg-type match="java.lang.String"/>
</replaced-method>
</bean>
<bean id="standardTarget"
class="com.apress.prospring2.ch04.mi.ReplacementTarget"/>
</beans>

We then used the <replaced-method> tag to replace the formatMessage(String)
method on the replacementTargetBean. The name attribute of the <replaced-method> tag specifies the name of the method to replace and the replacer attribute is used to specify the name of the MethodReplacer bean that we want to replace the method implementation. In cases where there are overloaded methods such as in the ReplacementTarget class, you can use the <arg-type> tag to specify the method signature to match. The <arg-type> supports pattern matching, so String is matched to java.lang.String and to java.lang.StringBuffer.

Method Replacement in Action

public class MethodReplacementDemo {
static interface StressTestCallback {
void run(ReplacementTarget target);
}
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(
new ClassPathResource("/META-INF/spring/midemo3-context.xml")
);
StressTestCallback stringCallback = new StressTestCallback() {
private final String msg = "Hello";
public void run(ReplacementTarget target) {
target.formatMessage(msg);
}
@Override
public String toString() {
return "formatMessage(String)";
}
};
StressTestCallback objectCallback = new StressTestCallback() {
private final Object msg = new Object();
public void run(ReplacementTarget target) {
target.formatMessage(msg);
}
@Override
public String toString() {
return "formatMessage(Object)";
}
};
stressTest(factory, "replacementTarget", stringCallback);
stressTest(factory, "standardTarget", stringCallback);
stressTest(factory, "standardTarget", objectCallback);
stressTest(factory, "standardTarget", objectCallback);
}
private static void stressTest(XmlBeanFactory factory, String beanName,
StressTestCallback callback) {
ReplacementTarget target = (ReplacementTarget)factory.getBean(beanName);
callback.run(target);
StopWatch stopWatch = new StopWatch();
stopWatch.start("perfTest");
for (int i = 0; i < 1000000; i++) {
callback.run(target);
}
stopWatch.stop();
System.out.println("1000000 invocations of formatMessage("
+ callback + ") on "
+ beanName + " took: "
+ stopWatch.getTotalTimeMillis() + " ms");
}
}

1000000 invocations of formatMessage(formatMessage(String)) on replacementTarget took: 1033 ms
1000000 invocations of formatMessage(formatMessage(String)) on standardTarget took: 241 ms
1000000 invocations of formatMessage(formatMessage(Object)) on replacementTarget took: 330 ms
1000000 invocations of formatMessage(formatMessage(Object)) on standardTarget took: 320 ms

No comments:

Post a Comment