Saturday, August 29, 2009

Using Javascript as DI Container in Java

Javascript is almost as dynamic as any programming language can get. However, it is mostly used only within the browser and many developers (including dynamic language fans) don't know of many of the cool language features of Javascript. When these features are used effectively, it lets us write highly expressive code which even many dynamic languages can't match.

Most Java developers turn to Spring for their DI and AOP requirements. However the configuration of Spring is quite verbose and is mostly XML. Plain JDK can handle AOP using dynamic proxies and as this post demonstrates Javascript can be used to handle the dependency injections.

Starting with Java 6, Java applications can invoke scripting languages within their JVM instance and communicate with them seamlessly using the Scripting API. Sun's JRE also ships with a built-in engine for handling Javasript through this API. We can utilize Javascript's power and expressiveness to perform cool stuffs on top of the JVM.

What are we Building?
  1. Two DAOs and their mock implementations.
  2. A sample service utilizing these DAOs.
  3. JS script wiring them up together.
  4. A simple interceptor to log invocations of service methods.
DAOs
package dao;

public interface BooksDAO {
public int count();
}

package dao;

public interface MembersDAO {
public int count();
}

package dao;

public class MockBooksDAO implements BooksDAO {

@Override
public int count() {
return 500;
}
}

package dao;

public class MockMembersDAO implements MembersDAO {

@Override
public int count() {
return 100;
}
}

Service
package service;

public interface LibraryService {

public abstract int countBooks();

public abstract int countMembers();

}

package service;

import dao.BooksDAO;
import dao.MembersDAO;

public class MockLibraryService implements LibraryService {
private BooksDAO booksDAO;
private MembersDAO membersDAO;

public MockLibraryService(BooksDAO booksDAO, MembersDAO membersDAO) {
this.booksDAO = booksDAO;
this.membersDAO = membersDAO;
}

public int countBooks() {
System.out.println("In countBooks()");
return booksDAO.count();
}

public int countMembers() {
System.out.println("In countMembers()");
return membersDAO.count();
}
}

DI Script

We utilize Javascript's Object literals to define and group our DAOs and services. These can then looked up from our applications to obtain the actual implementations.

src/objects.js
importPackage(Packages.dao)
importPackage(Packages.service)
importPackage(Packages.interceptor)

daos = {
booksDAO : new MockBooksDAO(),
membersDAO : new MockMembersDAO()
}

services = {
libraryService : LoggingInterceptor.getProxy(new MockLibraryService(daos.booksDAO, daos.membersDAO))
}

Application Code

We use the ScriptEngineBuilder introduced in a previous post to evaluate our Javacript and then obtain the service instance from it.

package app;

import javax.script.ScriptEngine;
import javax.script.ScriptException;

import service.LibraryService;
import util.ScriptEngineBuilder;

public class Main {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineBuilder("js").add("/objects.js").build();

LibraryService service = (LibraryService) engine.eval("services.libraryService");

System.out.println("Found " + service.countMembers() + " Members!");
System.out.println("Found " + service.countBooks() + " Members!");
}
}

Adding AOP to the Mix

Let's create a JDK dynamic proxy to log all method invocations.
package interceptor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LoggingInterceptor implements InvocationHandler {
private Object target;

public static Object getProxy(Object target) {
ClassLoader loader = LoggingInterceptor.class.getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
InvocationHandler handler = new LoggingInterceptor(target);

return Proxy.newProxyInstance(loader, interfaces, handler);
}

private LoggingInterceptor(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
System.out.println("Entered Method: " + method.getName());
Object val = method.invoke(target, args);
System.out.println("Completed Method: " + method.getName());
return val;
} catch (Exception e) {
System.out.println("Exception in Method: " + method.getName());
throw e;
}
}

}

Let's now modify our service creation in DI script as below to utilize the proxy.

libraryService : LoggingInterceptor.getProxy(new MockLibraryService(daos.booksDAO, daos.membersDAO))

So there we have a nice and clean DI configuration including AOP without needing anything outside of Java SE. An important thing to note here is that we are still maintaining the DI and AOP config entirely outside our application code unlike when using annotations. Spring would have needed a truck load of XML to achieve the same!

2 comments:

ulsa said...

The "truck load of XML" that you mention would be this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-2.5.xsd"
        default-autowire="constructor">

    <context:component-scan base-package="dao,service" annotation-config="false">
        <context:include-filter type="regex" expression=".*Mock.*" />
    </context:component-scan>

</beans>

Chandru said...

It would also be nice if you'd include the AOP logging into the mix.

Since this is an example, I kept the scenario trivial. What if I wanted two different instances of my service each with different DAO implementations injected?

Forget for a moment that it is even a service. Not all dependency injected objects need to be singletons.

Also what if I had another constructor in my service and wanted to use different constructors for different instances of the service?