Different Strategies for Acquiring Dependency inside a Method

When we create or refactor a method, we find some input data is needed for the method to do its job. What are the various options to acquire these dependency? I just went through a major code refactoring and module restructuring, and would like to share some thoughts:
  1. Pass it in as method parameters. This seems to be most common approach, and some considerations are:
    • First forget about implementation details and needs, does it make sense to require the additional data in order to do the work? Think in terms of raw materials for completing the task, as opposed to method parameters.

    • The client code will need to acquire the new data, if not already available, or the target method declares a parameter injection.

    • All subtypes (e.g., subclasses overriding this method) and callers need to be updated to the new method signature. That may not be a problem if we are changing internal interfaces or implementation classes. But for public interfaces, it presents a backward compatibility problem.

    • To what granularity do we want to add the new parameter? Is it a coarse-grained or fine-grained parameter? For example, do we pass in (String name), or (UserInfo userInfo)? Some guidelines:

      • Conceptually, what input is needed for the method to perform its task? Try to reduce the granularity to what's really necessary, to make it usable in more contexts. Some calling code may only have the fine-grained data (e.g., zipCode), but not the coarse-grained data (e.g., userInfo)

      • Be consistent with other methods in the same interface or class. If many peer methods take UserInfo, it makes sense to have UserInfo in the new method even if only part of UserInfo is needed.

      • Unless it's remote invocation, the overhead of parameter passing is the same between a fine-grained and a coarse-grained parameter.

      • Avoid exporting information that is specific to a design tier or implementation layer. If the coarse-grained data fall into this category, then choose to export the fine-grained data that are not tied to the current tier or layer. For example,

        • HttpServletRequest or HttpServletResponse are tied to web tier, and may be passed around during the current request processing inside web tier, but should never be exported to business tier.

        • Security realm instances should not be passed outside security layer.

      • The above point is more evident and enforced by a module system like OSGi. A public class that is not exported by its host module will not be visible to other modules, and so may not be passed outwards. In this case, a fine-grained data type or even a string literal is more appropriate.


  2. Derive from existing method parameters, when the required data is indirectly reachable from existing parameters. For example,

    • String zip = person.getAddress().getZip();

  3. Is it already available as static or instance fields, or inherited from super classes?

    If some data are intrinsic attributes and relationship fields of a class, they should be initialized in constructors or injected via IoC framework and available to be shared by all methods. They consistute the class and instance state. It's possible that a method takes a type of parameter that is already available in class or instance state. They are intended to be distinct objects. For example,
    /**
    * @param userInfo a different UserInfo instance than represented by this person.
    * @return true if userInfo is a potential friend of this person; false otherwise.
    */
    public boolean maybeFriends(UserInfo userInfo) {
    return this.userInfo.similarTo(userInfo);
    }

  4. Derive it from existing static or instance fields, for example,

    • String zipCode = this.userInfo.getZipCode();

  5. Is it available from a global registry, or naming service? Typically, the global namespace is initialized and populated upon program start. If subsequent concurrent and write operations are supported, the global namespace needs to be thread-safe. For example,

    • User user = GlobalPlace.getCurrent(User.class);

    • String m = InitialContext.<String>doLookup("config/mode");

  6. Create my own instances, using direct instantiation or some sort of factory method. Use this option if the current method has adequate information and knows how to create. For example,

    • Config config = new Config(mapping);

    • Handler handler = HandlerFactory.createHandler();

    • Logger logger = Logger.getLogger("abc"); //find or create the named logger

How to Instantiate Annotation Type and Qualifier

Java annotations are usually used to annotate a language element, to inject dependency, or to provide metadata. They are interface types and cannot be directly instantiated. But sometimes we find it necessary to have instances of annotation types. This post demonstrates how to instantiate annotation types and use qualifier in simple search.

In short, you will need to write an abstract class that implements your annotation type T, and extends javax.enterprise.util.AnnotationLiteral<T> (see ContainsQualifier class below). The client code then can instantiate this abstract class by using anonymous inner class that supplies appropriate values to T (see AnnotationLiteralTest#findBy below).

A somewhat contrived use case: there are bags and baskets that contain items like gold or fruit. So bag and basket are annotated with what they contain. In a warehouse, the staff needs to find all packgages containing gold, all packages containing fruit, etc, based on one or more qualifiers.

This is implemented in the following maven project (project source in github):


pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.blogspot.javahowto</groupId>
<artifactId>annotation-literal</artifactId>
<version>1.0</version>

<build>
<defaultGoal>install</defaultGoal>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.0-SP4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
The annotation type qualifier: Contains

package com.blogspot.javahowto;

import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Qualifier
@Documented
@Retention(value= RetentionPolicy.RUNTIME)
public @interface Contains {
String value() default "";
}
Bag type containing gold:

package com.blogspot.javahowto;

@Contains("Gold")
public class Bag {
}
Basket type containing fruit:

package com.blogspot.javahowto;

@Contains("Fruit")
public class Basket {
}
Abstract class that implements the qualifier Contains:

package com.blogspot.javahowto;

import javax.enterprise.util.AnnotationLiteral;

public abstract class ContainsQualifier extends AnnotationLiteral<Contains>
implements Contains {}
The qualifier-based search is implemented in CollectionManager:

package com.blogspot.javahowto;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedList;

public class CollectionManager {
private Collection collection;

public CollectionManager(Collection collection) {
this.collection = collection;
}

/**
* Finds all objects matching all criteria represented by annotations
* @param annotationsToMatch one or multiple search qualifiers
* @return a collection of objects that match all the search qualifiers
*/
public Collection findBy(Annotation... annotationsToMatch) {
Collection result = new LinkedList();
for(Object obj : collection) {
boolean matched = false;
Annotation[] classAnnotations = obj.getClass().getAnnotations();
for(Annotation an : annotationsToMatch) {
if(contains(classAnnotations, an)) {
matched = true;
} else {
matched = false;
break;
}
}
if(matched)
result.add(obj);
}
return result;
}

/**
* Checks if an array of Annotations contains an individual Annotation
* @param annotations an array of annotations
* @param ann an individual annotation
* @return true if ann is equal to at least one of the element in
* annotations array; false otherwise
*/
private boolean contains(Annotation[] annotations, Annotation ann) {
for(Annotation a : annotations) {
if(a.equals(ann)) {
return true;
}
}
return false;
}
}
The JUnit test class:

package com.blogspot.javahowto.test;

import com.blogspot.javahowto.*;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class AnnotationLiteralTest {
private static List mixed = new LinkedList();
private static CollectionManager collectionManager;

@BeforeClass
public static void setUp() throws Exception {
for (int i = 0; i < 2; i++) {
mixed.add(new Bag());
mixed.add(new Basket());
}
collectionManager = new CollectionManager(mixed);
}

@Test
public void testContainsGold() throws Exception {
System.out.printf("contains Gold: %s%n%n", findBy("Gold"));
}

@Test
public void testContainsFruit() throws Exception {
System.out.printf("contains Fruit: %s%n%n", findBy("Fruit"));
}

@Test
public void testContainsFruitGold() throws Exception {
System.out.printf("contains Gold and Fruit: %s%n%n", findBy("Gold", "Fruit"));
}

/**
* Finds all objects matching all criteria
* @param criteria one or multiple search qualifiers
* @return a collection of objects that match all the search qualifiers
*/
private Collection findBy(final String... criteria) {
Contains[] qualifiers = new Contains[criteria.length];
for (int i = 0; i < criteria.length; i++) {
final String s = criteria[i];
Contains containsQualifier = new ContainsQualifier() {
@Override
public String value() {
return s;
}
};
qualifiers[i] = containsQualifier;
}
return collectionManager.findBy(qualifiers);
}
}
To build the project and run the JUnit tests:

$ mvn install

Running com.blogspot.javahowto.test.AnnotationLiteralTest
contains Gold: [com.blogspot.javahowto.Bag@7b112783, com.blogspot.javahowto.Bag@23394894]

contains Fruit: [com.blogspot.javahowto.Basket@69198891, com.blogspot.javahowto.Basket@b551d7f]

contains Gold and Fruit: []

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.047 sec
Some points for discussion:

The subclass of ContainsQualifier (the anonymous inner class) represents a strategy in strategy pattern. Traditionally the strategy is an implementation of a common interface. Now this seems another area that annotation can replace the traditional interface.

In this project the search criteria are based on string values, and wrap them as annotation may be overkill. But in more complex cases, annotation offers a very flexible and generic interface for specifying conditions and qualifiers.

Wall Decals for Cosy Kitchen Design

Wall Decals for Cosy Kitchen Design. This wall decoration is very unique and attractive, ideal for decorating your Cosy Kitchen Design. wall decal is also easy to create and delete them.



To create a cozy atmosphere of the kitchen and the comfort you are not wrong when choosing vinyl wall decal wall decal or japanese or something. I love these wall hangings, so eat more savory and delicious and convenient kitchen Cosy Kitchen


Add some accesories like exotic flowers will add to your kitchen cozy, unique wall art decor or classic wall decor will also make-your mood more cheerful and energetic.

ConcurrentHashMap Examples

4 steps when accessing a cache implemented with java.util.ConcurrentHashMap (javadoc):
  1. get the value from the ConcurrentMap;
  2. if null, assume it's the first access, and create the value;
  3. call putIfAbsent on the concurrentMap to store the new value;
  4. if return value is not null (it's rare but happens), use the return value as the golden copy, and discard the newly-created object.
The following test class, SqrtTest, displays the square root of numbers, and each square root value is accessed multiple times concurrently. The intent is to calculate it only on the first access and return the cached value for subsequent requests.
import java.util.*;
import java.util.concurrent.*;

public class SqrtTest {
private static final String CONCURRENCY_LEVEL_DEFAULT = "50";
private static final String CONCURRENCY_KEY = "concurrency";
private ConcurrentMap<Double, Double> sqrtCache = new ConcurrentHashMap<Double, Double>();

public static void main(String args[]) {
final SqrtTest test = new SqrtTest();
final int concurrencyLevel = Integer.parseInt(System.getProperty(CONCURRENCY_KEY, CONCURRENCY_LEVEL_DEFAULT));
final ExecutorService executor = Executors.newCachedThreadPool();

try {
for(int i = 0; i < concurrencyLevel; i++) {
for(String s : args) {
final Double d = Double.valueOf(s);
executor.submit(new Runnable() {
@Override public void run() {
System.out.printf("sqrt of %s = %s in thread %s%n",
d, test.getSqrt(d), Thread.currentThread().getName());
}
});
}
}
} finally {
executor.shutdown();
}
}

// 4 steps as outlined above
public double getSqrt(Double d) {
Double sqrt = sqrtCache.get(d);
if(sqrt == null) {
sqrt = Math.sqrt(d);
System.out.printf("calculated sqrt of %s = %s%n", d, sqrt);
Double existing = sqrtCache.putIfAbsent(d, sqrt);
if(existing != null) {
System.out.printf("discard calculated sqrt %s and use the cached sqrt %s", sqrt, existing);
sqrt = existing;
}
}
return sqrt;
}
}
To compile and run the SqrtTest (-Dconcurrency=123 can be used to adjust the concurrency level):
$ javac SqrtTest.java
$ java SqrtTest 0.5 11 999 0.1

calculated sqrt of 0.5 = 0.7071067811865476
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-1
calculated sqrt of 11.0 = 3.3166247903554
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-2
calculated sqrt of 999.0 = 31.606961258558215
sqrt of 999.0 = 31.606961258558215 in thread pool-1-thread-1
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-2
calculated sqrt of 0.1 = 0.31622776601683794
calculated sqrt of 0.1 = 0.31622776601683794
sqrt of 999.0 = 31.606961258558215 in thread pool-1-thread-1
sqrt of 11.0 = 3.3166247903554 in thread pool-1-thread-8
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-4
sqrt of 0.5 = 0.7071067811865476 in thread pool-1-thread-7 calculated sqrt of 0.1 = 0.31622776601683794
discard calculated sqrt 0.31622776601683794 and use the cached sqrt 0.31622776601683794sqrt of 0.1 = 0.31622776601683794 in thread pool-1-thread-6
...
From the above output, we can see at least one calculation is discarded since the value already exists in the cache. It had been added to the cache by another thread between step 1 and step 3.

Multiple input double numbers are used to increase thread contention. When testing with one single input number, I couldn't trigger the race condition as evidenced by the "discard calculated sqrt" log message. It is probably because it takes time for the thread pool to create the second thread, and by the time it kicks in, the result is already calculated by the first thread and well established in the cache.

Followers

Pageviews Last 7 Days