Guice - AOP (Aspect Oriented Programming)

Aspect Oriend Programming (AOP) is a programming methodology or perspective, which was conceived to bring in more modular approach to the already existing 'Object Oriented Methodology'. Dependency Injection, which is another programming methodology to bring in more modular approach or to create loosely coupled modules, which compliments AOP. In this post, I'm going to write something about how the DI framework 'Guice' uses it's DI mechanism, to provide AOP concepts (method interceptors).

Lets think of a small application, where users need to purchase a licence or have some special privileges to use some functionality of the application. Below is the code for this app, which provides the main functionality of this app (Service.java interface and it's implementation - ServiceImpl.java):

Service.java
package com.chetty.licence;

/**
 * Service Interface
 *
 * @author Babji, Chetty
 */
public interface Service {
    public void doSomething1();
    public void doSomething2();
}

ServiceImpl.java
package com.chetty.licence;

/**
 * Service Implementation.
 * 
 * @author Babji, Chetty
 */
public class ServiceImpl implements Service {    
    public void doSomething1() {
        System.out.println("I'm doing something - 1");
    }

    @LicenceRequired
    public void doSomething2() {
        System.out.println("I'm doing something - 2");
    }
}

As you see in the above Service interface implementation, there is a annotation called "LicenceRequired" for the second method. What we are trying to do is to provide limited access to the functionality of this app or that method, by keeping a check (by intercepting) on that method, whenever it is called. The code for the "LicenceRequired" annotation is as below:

LicenceRequired.java
package com.chetty.licence;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** * * @author Babji, Chetty. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LicenceRequired {}

Guice provides the "method interceptors" functionality by using "AOP Alliance" (aopalliance.jar) as it's external library. The annotation "LicenceRequired" is used as a marker (See ServiceImpl.java's doSomething2() method) to intercept the required method. Guice knows that this method has to be intercepted, by the following configuration (Guice Module):

LicenceModule.java
package com.chetty.licence.guice.module;

import com.chetty.licence.LicenceRequired;
import com.chetty.licence.LicenceChecker;
import com.chetty.licence.Service;
import com.chetty.licence.ServiceImpl;
import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;

/** * * @author b.chetty */ public class LicenceModule extends AbstractModule { protected void configure() { LicenceChecker licenceChecker = new LicenceChecker(); requestInjection(licenceChecker); bindInterceptor(Matchers.any(), Matchers.annotatedWith(LicenceRequired.class), licenceChecker); bind(Service.class).to(ServiceImpl.class); } }

The service client or adapter, is as follows:

ServiceAdapter.java
package com.chetty.licence;

import com.google.inject.Inject;

/**
 * Service Adapter.
 * 
 * @author Babji, Chetty
 */
public class ServiceAdapter {
    private final Service service;

    @Inject
    public ServiceAdapter(Service service) {
        this.service = service;
    }

    public void doSomething() {
        service.doSomething1();
        service.doSomething2();
    }
}

And the Licence checking functionality is done in the following class (Licence.java):

Licence.java
package com.chetty.licence;

/**
 *
 * @author Babji, Chetty
 */
public class Licence {
    public boolean checkLicence(String licenceString) {
        String licenceStringFromDB = getLicenceStringFromDB();
        return (licenceString != null && licenceStringFromDB != null && licenceString.equals(licenceStringFromDB) ? true : false);
    }

    private String getLicenceStringFromDB() {
        return "TEST";
    }
}

And below is the class, that aids the method intercepting process. If you check the Guice Configuration module (LicenceModule.java), I have configured the "LicenceRequired" annotation, to work with "LicenceChecker" class, so that when the method with business logic, annotated with "LicenceRequired" is called, control is passed over to the "LicenceChecker" class.

LicenceChecker.java
package com.chetty.licence;

import com.google.inject.Inject;
import java.util.Scanner;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 *
 * @author Babji, Chetty
 */
public class LicenceChecker implements MethodInterceptor {
    @Inject
    private Licence licence;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //Get License String from the user. You can read the licence string from a licence-file, from connsole, etc options.
        //In this case, I'm just reading it from the console.
        System.out.println("You need a valid Licence to use this functionality...Please enter your Licence string: ");
        Scanner input = new Scanner(System.in);
        String licenceString = input.nextLine();
                                
        if(!licence.checkLicence(licenceString)) {
            //Here, you can either throw an exception and show a "Invalid Licence" message or customize it according to your needs.            
            System.out.println("Invalid Licence!");
            throw new IllegalStateException("Invalid Licence : " + invocation.getMethod().getName());
        }
        
        return invocation.proceed();
    }
}

And, below is the main class (entry point) for this application called "licenceCheck":

LicenceApp.java
package com.chetty.licence;

import com.chetty.licence.guice.module.LicenceModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

/**
 * @author Babji, Chetty
 */
public class LicenceApp {
    public static void main(String[] args) {        
        Injector injector = Guice.createInjector(new LicenceModule());
        ServiceAdapter serviceInterface = injector.getInstance(ServiceAdapter.class);
        serviceInterface.doSomething();
    }
}


This app uses a hardcoded "TEST" string as licence string. This is checked with the user input licence string and if they don't match, the method with "LicenceRequired" annotation will not be executed. If you execute the application with a test string "TEST1234", "I'm doing something - 1" is printed, followed by "Invalid Licence!" and an IllegalStateExcception (implying that the second method - doSomething2() is not executed). If the user input licence string is "TEST", both the methods are executed. This idea can be customized and implemented in your own projects. There are also other uses of this AOP in handling transactions, logging, security, etc.

You can get the source code for this app, on Google's code repository: licenceCheck

Disclaimer: This sample project was created for fun and to learn new techniques. Use the code, at your own risk! :P

3 comments:

  1. Bad use of dependency injection for Licence in LicenceChecker. If Licence is a specific impl, why would you bother using dependency injection? The notion of DI is that you don't know in advance the impl, so you leave it out until later (perhaps until wiring time).

    You're mixing Guice's DI and AOP in this article, which can confuse people. Since this is an example of AOP, you should get rid of ServiceAdapter, then change LicenceApp to have these:

    Service origService = new ServiceImpl();
    origService.doSomething2();
    Service modifiedService = injector.getInstance(Service.class);
    modifiedService.doSomething2();

    Now, reader sees exactly how AOP works, and LicenceModule is Guice's way to enable it.

    ReplyDelete
  2. I would further change it to:

    LicenceModule:
    //bind(Service.class).to(ServiceImpl.class);

    LicenceApp:
    Service modifiedService = injector.getInstance(ServiceImpl.class);

    to be even more concrete. Here you inject at exactly when you need it, and with Guice's help, AOP works.

    ReplyDelete
  3. Thach, I couldn't reply back immediately, as I wasn't around. Anyways, sorry. I do agree that the above post is not a great example. I just coded a fast example and posted it. Also, I'm still working on improving my not-so-great designing skills. I will work on this and edit my post. Thanks for the suggestion. :)

    ReplyDelete

Note: Only a member of this blog may post a comment.