Wednesday, 11 July 2018

Java: Dynamic proxies

Proxies are used to achieve below things.
a.   Control access to the object
b.   Remove the overhead of cross cutting concerns.

In any typical enterprise application authentication, logging, database connection etc., are compulsory and these are implemented or used by almost all the service classes. By using dynamic proxies, we can get rid of the duplication of cross cutting concerns.

What is Dynamic Proxy?
A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface.

For example, as you see below image, the calls to actual object are done via InvocationHandler.




Let me explain with an example.

How to create a proxy object?
'java.lang.reflect.Procy' class is used to create a proxy of an object.

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Returns an instance of a proxy class for the specified interfaces

Example
ArithmeticServiceImpl arthObj = new ArithmeticServiceImpl();

ArithmeticService arhServiceProxy = (ArithmeticService) Proxy.newProxyInstance(ArithmeticServiceImpl.class.getClassLoader(), new Class[] { ArithmeticService.class }, new MyInvocationHandler(arthObj));

After running this code, the proxy variable 'arhServiceProxy' contains a dynamic implementation of the ArithmeticService interface. All calls to the arhServiceProxy will be forwarded to the handler implementation of the general InvocationHandler interface.

MyInvocationHandler : Implementation of InvocationHandler interface
All calls to the arhServiceProxy will be forwarded to the invoke() method of MyInvocationHandler class.

Find the below working application.

ArithmeticService.java
package com.sample.app.interfaces;

public interface ArithmeticService {
 public int add(int a, int b);
}

ArithmeticServiceImpl.java

package com.sample.app.interfaces.impl;

import com.sample.app.interfaces.ArithmeticService;

public class ArithmeticServiceImpl implements ArithmeticService {

 @Override
 public int add(int a, int b) {
  System.out.println("Calculating sum of " + a + " and " + b);
  return a + b;
 }

}

MyInvocationHandler.java

package com.sample.app.invocationhandlers;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;

public class MyInvocationHandler implements InvocationHandler {

 private Object obj;

 public MyInvocationHandler(Object obj) {
  this.obj = obj;
 }

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  System.out.println(method.getName() + " of " + method.getDeclaringClass() + " is getting executed"
    + " with arguments : " + Arrays.toString(args));

  Object result = method.invoke(obj, args);

  System.out.println(method.getName() + " of " + method.getDeclaringClass() + " execution finished");
  return result;
 }

}

Test.java

package com.sample.app;

import java.lang.reflect.Proxy;

import com.sample.app.interfaces.ArithmeticService;
import com.sample.app.interfaces.impl.ArithmeticServiceImpl;
import com.sample.app.invocationhandlers.MyInvocationHandler;

public class Test {

 public static void main(String args[]) {
  ArithmeticServiceImpl arthObj = new ArithmeticServiceImpl();

  ArithmeticService arhServiceProxy = (ArithmeticService) Proxy.newProxyInstance(ArithmeticServiceImpl.class.getClassLoader(),
    new Class[] { ArithmeticService.class }, new MyInvocationHandler(arthObj));

  int result = arhServiceProxy.add(10, 20);
  System.out.println(result);
 }
}


When you ran Test.java, you can able to see below messages in console.

add of interface com.sample.app.interfaces.ArithmeticService is getting executed with arguments : [10, 20]
Calculating sum of 10 and 20
add of interface com.sample.app.interfaces.ArithmeticService execution finished
30

Let’s enhance above application further like below.

LoggingFilter : Log the information about the method execution
PerformanceFilter: Log the execution time of the method execution


Find the below working application.

Filter.java

package com.sample.app.filters;

public interface Filter {
 
 /**
  * Called before execution of the actual method
  */
 public void beforeExecution();
 
 /**
  * Called after execution of the actual method
  */
 public void afterExecution();

}

LoggingFilter.java

package com.sample.app.filters;

import java.lang.reflect.Method;
import java.util.Arrays;

public class LoggingFilter implements Filter {

 private Method method;
 private Object[] args;

 public LoggingFilter(Method method, Object[] args) {
  this.method = method;
  this.args = args;
 }

 @Override
 public void beforeExecution() {
  System.out.println("Method " + method.getName() + " of " + method.getDeclaringClass() + " is getting executed"
    + " with arguments : " + Arrays.toString(args));
 }

 @Override
 public void afterExecution() {
  System.out.println("Method " + method.getName() + " of " + method.getDeclaringClass() + " execution finished");
 }

}

PerformanceFilter.java

package com.sample.app.filters;

import java.lang.reflect.Method;

public class PerformanceFilter implements Filter {
 private Method method;

 private long startTime;

 public PerformanceFilter(Method method) {
  this.method = method;
 }

 @Override
 public void beforeExecution() {
  startTime = System.nanoTime();
 }

 @Override
 public void afterExecution() {
  System.out.println("Method " + method.getName() + " takes " + (System.nanoTime() - startTime) + " nanoSeconds");
 }

}


ArithmeticService.java

package com.sample.app.interfaces;

public interface ArithmeticService {
 public int add(int a, int b);
}

ArithmeticServiceImpl.java

package com.sample.app.interfaces.impl;

import com.sample.app.interfaces.ArithmeticService;

public class ArithmeticServiceImpl implements ArithmeticService {

 @Override
 public int add(int a, int b) {
  System.out.println("Calculating sum of " + a + " and " + b);
  return a + b;
 }

}

MyInvocationHandler.java

package com.sample.app.invocationhandlers;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

import com.sample.app.filters.Filter;
import com.sample.app.filters.LoggingFilter;
import com.sample.app.filters.PerformanceFilter;

public class MyInvocationHandler implements InvocationHandler {

 private Object obj;
 private boolean loggingFilter = false;
 private boolean perfFilter = false;

 private List<Filter> filters = new ArrayList<>();
 private Method method;
 private Object[] args;

 public MyInvocationHandler(Object obj) {
  this.obj = obj;
 }

 public void addLoggingFilter() {
  loggingFilter = true;
 }

 public void addPerformanceFilter() {
  perfFilter = true;
 }

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  this.method = method;
  this.args = args;

  addFilters();
  execBeforeFilters();

  Object result = method.invoke(obj, args);
  execAfterFilters();

  return result;
 }

 private void addFilters() {
  if (loggingFilter) {
   filters.add(new LoggingFilter(method, args));
  }

  if (perfFilter) {
   filters.add(new PerformanceFilter(method));
  }

 }

 private void execBeforeFilters() {
  for (int i = 0; i < filters.size(); i++) {
   Filter filter = filters.get(i);
   filter.beforeExecution();
  }
 }

 private void execAfterFilters() {
  for (int i = filters.size() - 1; i > -1; i--) {
   Filter filter = filters.get(i);
   filter.afterExecution();
  }
 }

}


Test.java

package com.sample.app;

import java.lang.reflect.Proxy;

import com.sample.app.interfaces.ArithmeticService;
import com.sample.app.interfaces.impl.ArithmeticServiceImpl;
import com.sample.app.invocationhandlers.MyInvocationHandler;

public class Test {

 public static void main(String args[]) {
  ArithmeticServiceImpl arthObj = new ArithmeticServiceImpl();

  MyInvocationHandler invocationhandler = new MyInvocationHandler(arthObj);
  invocationhandler.addLoggingFilter();
  invocationhandler.addPerformanceFilter();

  ArithmeticService arhServiceProxy = (ArithmeticService) Proxy.newProxyInstance(
    ArithmeticServiceImpl.class.getClassLoader(), new Class[] { ArithmeticService.class },
    invocationhandler);

  int result = arhServiceProxy.add(987654, 201801810);
  System.out.println("Result is : " + result);
 }
}

When you ran Test.java, you can able to see below messages in console.

Method add of interface com.sample.app.interfaces.ArithmeticService is getting executed with arguments : [987654, 201801810]
Calculating sum of 987654 and 201801810
Method add takes 327546 nanoSeconds
Method add of interface com.sample.app.interfaces.ArithmeticService execution finished
Result is : 202789464


Previous                                                 Next                                                 Home

No comments:

Post a Comment