How many times we
a) put log statements in the beginning and end of the method to indicate that this method invocation was successful.
b) May be quite few times we have a requirement that we need to perform some actions like doing some audit events when entering any method and storing few information like who invoked the method etc...
Hence we may solve all this by implementing Interpreter + Delegate Pattern which helps us to re-use the code.
I would like to thank John Humble for implementing this wonderful pattern in our project.
1) Create a Delegate class which acts as the InvocationHandler i.e implementing this interface and handling low level details for invoking methods,create object etc...
public abstract class Delegate implements InvocationHandler {
private static Logger logger = Logger.getLogger(Delegate.class);
protected Object object;
private Map methods;
public Delegate(){
methods = new HashMap();
}
public static Object newProxyInstance(Class interfaceClass,
InvocationHandler invocationHandler) {
return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[] { interfaceClass }, invocationHandler);
}
/**
*
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
createProcessorObject();
return invokeProcessorObject(method, args, object);
}
/**
*
*/
protected Object invokeProcessorObject(Method method, Object[] args, Object processorObj) throws Throwable {
return invokeProxy(method, args, processorObj);
}
/**
*
*/
protected final Object invokeProxy(Method method, Object[] args, Object processorInstance) throws Throwable {
Object returnObj = null;
String methodName = null;
try {
methodName = getMethodName(method);
Method toCallMethod = (Method)methods.get(methodName);
if (null==toCallMethod) {
throw new RuntimeException( "Method not found: " + toCallMethod);
}
returnObj = toCallMethod.invoke(processorInstance, args);
}
catch ( InvocationTargetException i ) {
Throwable t = i.getCause();
if ( t instanceof RemoteException ) {
RemoteException r = ( RemoteException ) t;
t = r.getCause();
}
logger.debug("Problem Invoking Bean Method = " + StackTraceUtil.getStackTrace(i));
throw t;
}
return returnObj;
}
/**
*
*/
protected Object getProcessorInstance(Object processorObj) {
return processorObj;
}
private synchronized void createProcessorObject() {
if(object == null) {
object = createObject();
}
}
private Object createObject() {
Object processorObject = null;
Object processorInstance = null;
try {
processorObject = create();
processorInstance = getProcessorInstance(processorObject);
BeanInfo beanInfo = Introspector.getBeanInfo(processorInstance.getClass());
MethodDescriptor interfaceMethods[] = beanInfo.getMethodDescriptors();
for(int i=0;i<interfaceMethods.length;i++) {
methods.put(getMethodName(interfaceMethods[i].getMethod()),
interfaceMethods[i].getMethod());
}
return processorObject;
}
catch( IntrospectionException i ) {
throw new RuntimeException( i );
}
}
private String getMethodName(Method method) {
StringBuffer methodName = new StringBuffer();
methodName.append(method.getName());
methodName.append('(');
Class[] parameterTypes = method.getParameterTypes();
for(int i=0;i<parameterTypes.length;i++){
methodName.append(parameterTypes[i].getName());
if(i<parameterTypes.length){
methodName.append(',');
}
}
methodName.append(')');
return methodName.toString();
}
protected abstract Object create();
}
2) Create an Interceptor interface which to be used by classes that do processing 'around' another call.
Interceptors are called from various 'join-points' in the code, notably the
web business delegate proxy and the session bean to impl proxy.
All interceptor implementations should be stateless both in terms of between
invocations to the object (i.e. one doBeforeCall to the next) and between
invocations in a business call (i.e. between a call to doBeforeCall and the
corresponding call to doAfterCall).
public interface Interceptor
{
//Implement this function to do processing before the target call.
public void doBeforeCall(final Method method, final Object[] argv);
// Implement this function to do processing after the business call.
public void doAfterCall(final Method method, final Object[] argv,
final Object result);
}
3) Create an abstract class InterceptableDelegate which extends Delegate and which override invoke() method so as to provide the 'join-points' for doBeforeCall method which will be invoked before the target method and doAfterCall which will be invoked after the target method has been invoked.
public abstract class InterceptableDelegate extends Delegate {
private static Logger logger = Logger.getLogger(InterceptableDelegate.class);
private final List<Interceptor> interceptors = new LinkedList<Interceptor>();
protected InterceptableDelegate()
{
super();
}
protected final void addInterceptor(final Interceptor name)
{
if (logger.isDebugEnabled())
{
logger.debug("Adding interceptor " + name.getClass().getCanonicalName());
}
this.interceptors.add(name);
}
protected final void removeInterceptor(final Interceptor name)
{
this.interceptors.remove(name);
}
@Override
public Object invoke(Object obj, Method method, Object[] argv) throws Throwable
{
if (logger.isDebugEnabled())
{
logger.debug( "Calling interceptors PRE business call.");
}
for (final Interceptor i: this.interceptors)
{
i.doBeforeCall(method, argv);
}
Object o = null;
if (logger.isDebugEnabled())
{
logger.debug("Calling business method: " + method.toGenericString());
}
// Any exceptions thrown by this are propogated up.
// The super class will have unwrapped them from InvocationTargetException to Throwable
o = super.invoke(obj, method, argv);
if (logger.isDebugEnabled())
{
logger.debug("Calling interceptors POST business call.");
}
for (final Interceptor i: this.interceptors)
{
i.doAfterCall(method, argv, o);
}
return o;
}
}
4) Provide an implementation of Intercepter Interface.
final class UsernameInterceptor implements Interceptor {
private static Logger logger = Logger.getLogger(UsernameInterceptor.class);
UsernameInterceptor()
{
super();
}
public void doBeforeCall(Method method, Object[] argv)
{
logger.debug("doBeforeCall" + argv);
logger.debug("exit doBeforeCall");
System.out.println("doBeforeCall");
}
public void doAfterCall(Method method, Object[] argv, Object result)
{
logger.debug("doAfterCall" + argv);
logger.debug("exit doAfterCall");
System.out.println("doAfterCall");
}
}
5) Create an InterceptorFactory class which provides static factory method to return the intercepter object.
public final class InterceptorFactory
{
public static final String SYSTEM_ID = "SearchSystemId";
private InterceptorFactory()
{
super();
}
public static Interceptor createUsernameInterceptor()
{
return new UsernameInterceptor();
}
}
6) Provide an interface and an implementation class whose class methods wants to be invoked via Intercepter.
public interface CommonInterFace {
public void dummy();
}
public class CommonBD implements CommonInterFace {
public CommonBD() {}
public void dummy() {
System.out.println("Entered");
}
}
7) Provide a factory class which provides a static factory method for returning the object for the interface whose class methods wants to be invoked via Intercepter.
public class CommonFactory {
private static Logger logger = Logger.getLogger(CommonFactory.class);
private static String implClass = "CommonBD";
public static CommonInterFace newService() {
return (CommonInterFace) Delegate.newProxyInstance(CommonInterFace.class,new SomeDelegate());
}
final static class SomeDelegate extends InterceptableDelegate {
private SomeDelegate() {
super();
this.addInterceptors();
}
private void addInterceptors() {
this.addInterceptor(InterceptorFactory.createUsernameInterceptor());
}
protected Object create() {
Object toReturn = null;
// pojo mode, so create an instance of the implementation class
try {
toReturn = Class.forName(implClass).newInstance();
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException("Impl class:" + implClass
+ "not found", cnfe);
} catch (IllegalAccessException iae) {
throw new RuntimeException("Can't access class:" + implClass,
iae);
} catch (InstantiationException ie) {
throw new RuntimeException("Can't create class:" + implClass,
ie);
}
return toReturn;
}
}
}
8) Finally,create a test class for testing this pattern.
public class TestIntercepter {
public static void main(String[] args) {
CommonInterFace commonBD=CommonFactory.newService();
commonBD.dummy();
}
}
Please note that as known you may even override invoke() method
once again in Step (7).
Hope this explanation helps if any.
7 comments:
Hello,
This is an interesting pattern, but I think that in the use cases you've defined a simpler approch would be to use AOP with library like AspectJ.
However thanks for presenting this pattern implementation.
Actually, Spring AOP would be an even easier choice than AspectJ, since Spring AOP is handled at runtime, not compile time.
Create a class that implements the Method Interceptor interface to do the logging at the method invocation joinpoint.
AspectJ is more powerful than Spring AOP, but Spring will work just fine for this.
Does this really need a new "pattern name"? Interceptor pattern?
This is simply a Template Method pattern with two instances of the Observer pattern.
I like design patterns because they give developers good vocabulary. That advantage gets lost when every combination of two or more design patterns is assigned a new name.
Like Jason said, use AOP. Much less code.
And there is really no difference in using Spring AOP or AspectJ. Spring fully supports AspectJ, and the written aspect will run with "just" Spring proxies, or if you enable Load Time Weaving with AspectJ. Seamless. :)
Jason,greyWolf,Mathias Ricken & Anders
Thanks for every one for passing one few comments.
greyWolf: I agree with you that this can be implemented using AOP quite effectively.
jason: Sorry,not sure about spring as we are using beehieve here.Need to play with Spring in free time.
Mathias Ricken: You are right and yes this is really not a complete new pattern but probably this name is more opt in this case?
Andres: Thanks.Now i think its high time i need to encourage to use aspectj in this project.
Dude, I'm interested in this but I'm not going to bother reading code with a max width of 300px.
This is useful for people still needing to use older JDKs that dont support AOP.
Post a Comment