Thursday, February 10, 2011

Dependency Inversion Based on Java Annotations

The key concept within the annotation-based  dependency inversion framework is the concept of service extension points. As defined in the snippet in List 1, a service extension point identifies the base service method that can be extended with new features.

A service extension point can be extended by calls to the new feature methods either before or after the invocation of the base service method. This is done by identifying the corresponding service extension point, i.e.the targeted class, method name and the signature, and the intended extension type, as in the following code example:

@IServiceExtension(targetClass = "LoginService", 
   targetMethod = "login", 
   targetSignature = {"String", "String", "Session"},
   extensionType = IServiceExtension.AFTER)
 public void countUp() {
  ……
 }
}
List 1: Service Extension

Basically this annotation says that the method countUp from the current class shall be invoked before every call of the service extension point login method in the corresponding service class LoginService.

The IServiceExtension annotation can be defined by the following annotation interface:

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD})
public @interface IServiceExtension {
 
//Shall be invoked before the targetMethod
public static final int BEFORE = 0;
//Shall be invoked before the targetMethod
public static final int AFTER = 1;
 
   String targetClass() default "java.lang.Object";
   String targetMethod() default "";
   String[] targetSignature () default "";
   int extensionType() default IServiceExtension.BEFORE ; 
    
}
List 2: Extension to the Login Service

As an example, suppose for a web-based application we have the implementation of a user management module which provides the login and logout services. These services are realized by the class LoginService and LogoutService as in List 3.

public class LoginService {

 public User user;
private Session session;
  
 @IServiceExtensionPoint 
 public void login(String password, String username, Sessions session) {
  // Look for a user that matches the credentials in Database
  user = getUserFromDB (password, username);
  //user will be null if no match was found
  session.setUser(user);
  this.session = session;
 } 

 private User getUserFromDB(String password, String username) {
……
 }

 public User getUser() {
  return user;
 }

 public void setUser(User user) {
 session.setUser(user); 
 }
}

public class LogoutService {
  
 @IServiceExtensionPoint 
 public void login(Sessions session) {
  //Save the seesion information back to the DB
  storeSession (session);
 }

 private void storeSession(Sessions session) {
 ……  
 } 
}
List 3: The Base Login Service



public class LoginExtension {
 
 private static final int LOGIN_LIMIT = 1000;

 private LoginService parent = null;
 
 private static int  loginCounter = 0;

 public LoginRestriction(LoginService parent) {
  super();
  this.parent = parent;
 }

 @IServiceExtension(targetClass = "LoginService", 
   targetMethod = "login", 
   targetSignature = {"java.lang.String", " java.lang.String ", "Session"},
   extensionType = IServiceExtension.AFTER)
 public void countUp() {
  loginCounter++;
  if (loginCounter > LOGIN_LIMIT) parent.setUser(null);
 }
 
 public static void countDown() {
  if (loginCounter > 0) loginCounter--;
 }
}

List 4: Extension to the Login Service

public class CountLogouts {
 
 @IServiceExtension(targetClass = "LogoutService", 
   targetMethod = "logout", 
   targetSignature = {"Session"},
   extensionType = IServiceExtension.BEFORE) 
 public void countDown() {
  LoginExtension.countDown();
 }
}
 
List 5: Extension to the Logout Service

The base login service takes three parameters, the password, the user name and the current session object as input, and
  • retrieves the user object from the from the datanbase that matches the password/username pair,
  • sets the user into the session object. In case no user was found, the value null will be stored into the session instance.
The base logout service simply saves the relevant session information back into the user DB.

This implementation offers limited functionality but is sufficient for the basic scenarios and for the initial lauch of the application.

Now suppose that sometime later, due to the live capacity problems the operation manager decides that the number of user logged into the portal shall be limited. This can be a temperoal restriction, because it can be erased once, e.g. the running campaign expires or extra hardware becomes available.

To suport this restruction, we have to implement a counter for the live user sessions and block any new logins after the limit is reached. The corresponding code snippet is listed in List 4. Similarly, as listed in List 5, the logout service shall be extended to for reducing the count for each logout operation.

Service extensions can access the public fields and method of the service class to be extended. To use such methods or fields to get context from the service executions or to influence the base service logics, the default pattern for service extensions can has a dedicated constructor as in the List 4.


A dedicated annotation processor will process each IServiceExtension annotation in the following steps:

1. The corresponding byte code for the service class to be extended will be retrieved from the class path.

2. The service method to be extended will be retrieved via the targeted method name and signature.

3. The extension method invocation will be added to the service extension point in the following three sub-steps:

a. for the default service extension pattern mentioned above, an instance of the extension class will be generated using the specified constructor,

b. the extension method will be called upon this instance,

c. the codes generated for a and b will be inserted into the service extension point method at the beginning or before every final return command depending on the extension type.

4. After processing the annotations the modified byte code will be written back into the java .class file.

One possible variant to the default pattern is that one can omit the constructor which refers to the service instance to be extended. In this case, the default object constructor (without parameter) will be called in 3.a. to generate the instance for the extension class.

Another more useful variant is to define the extension method as static. In this case, the annotation processing will simply call the extension method with the {class name for the service extension point} + “.” prefix. In this way the extension method can directly access the public static fields and methods from the base service.
If several features shall extend one single service extension point, the ordering of the extensions in the final byte code is not determined.


Next Page - Dependency Inversion in Software Configuration Management

1 comment:

  1. Good post man.Spring now supports Annotation which makes code more readable and less cluttered.

    Javin
    Ldap authentication using Spring with Example

    ReplyDelete