Explore the Dynamic Proxy API

Use dynamic proxies to bring strong typing to abstract data types

1 2 Page 2
Page 2 of 2

In that example, the first interface lines are simply for implementing the Person/Employee/Manager objects in the inheritance example above. The real magic occurs in the invoke method of the ViewProxy class.

The ViewProxy implements the java.lang.reflect.InvocationHandler interface, which is used by the java.lang.reflect.Proxy class to provide the actual proxy implementation. The view proxy provides a builder method, newProxyInstance(), which creates the java.lang.reflect.Proxy and ViewProxy objects necessary for the whole proxy implementation.

For that to work, you would write code such as:

HashMap identity = new HashMap();
IPerson person = (IPerson)ViewProxy.newInstance(identity, new Class[]
 IPerson.class });
person.setName("Bob Jones");

You can then just as easily convert your identity to an employee:

IEmployee employee = (IEmployee)ViewProxy.newInstance(identity, new Class[]
{ IEmployee.class });
employee.setSSN("111-11-1111");

You can subsequently call IPerson methods on your IEmployee object:

System.out.println(employee.getName())

That would print the line:

Bob Jones

Obviously, the code needed to actually create the proxy instance isn't pretty, so it might be a good idea to use some sort of factory to create your actual objects (making the proxy creation transparent to the client code). By doing that, you can simply add or change interfaces as needed in future programming, without having to make changes to other code.

Let's make a little change to the ViewProxy class. There are cases in which I have an object that has some of the same methods of an interface but doesn't directly implement that interface. For example, I could have a concrete Person object, and I want to treat it as an IPerson interface. It just so happens that the Person class has all the methods of IPerson. So to convert the Person class to an IPerson interface, my new ViewProxy would look like this:

public class ViewProxy implements InvocationHandler
{
  private Map map;
  private Object obj;
  public static Object newInstance(Map map, Object obj, Class[] interfaces)
  {
    return Proxy.newProxyInstance(map.getClass().getClassLoader(),
                                  interfaces,
                                  new ViewProxy(new map, obj));
  }
  public ViewProxy(Map map, Object obj)
  {
    this.map = map;
    this.obj = obj;
  }
  public Object invoke(Object proxy, Method m, Object[] args) throws
Throwable
  {
    try {
      return m.invoke(obj, args);
    } catch (NoSuchMethodException e)
    { // ignore }
    Object result;
    String methodName = m.getName();
    if (methodName.startsWith("get"))
    {
      String name = methodName.substring(methodName.indexOf("get")+3);
      return map.get(name);
    }
    else if (methodName.startsWith("set"))
    {
      String name = methodName.substring(methodName.indexOf("set")+3);
      map.put(name, args[0]);
      return null;
    }
    else if (methodName.startsWith("is"))
    {
      String name = methodName.substring(methodName.indexOf("is")+2);
      return(map.get(name));
    }
    return null;
  }
}

In the modified code, I've added a try-catch block that tries to call the method on the underlying object that is passed in. If that fails, then you can switch over to the normal HashMap method. Now, if you want to convert a Person class to an IPerson class, you just need to write code like this:

Person person;
HashMap map;
IPerson ip = (IPerson)ViewProxy.newInstance(map,
                                            person,
                                            new Class[] { IPerson.class });

Now, you can treat the person like an IPerson object, and the underlying person is changed accordingly. Similarly, you can treat this Person class like an IEmployee object as well:

Person person;
HashMap map;
IEmployee ie = (IEmployee)ViewProxy.newInstance(map,
                                                person,
                                                new Class[]
 IEmployee.class});

Now, whenever you call IPerson methods, the underlying person is changed. However, the IEmployee calls will be changed in the HashMap of your ViewProxy. A little dangerous, perhaps, but then again, so is me driving a bus.

Authorization implementation

Access control is often hard to implement, especially when it is added after the code has already been written and running. I've had many sleepless nights because of access control problems. However, by using the dynamic proxy, you can easily implement access control.

Access control is usually discussed in terms of coarse-grained and fine-grained access control. For coarse-grained access control, you allow a certain level of access to a whole object or object group. Fine-grained access control is usually handled at the method or property level. For example, allowing read-only access to a file is an example of coarse-grained control. Allowing access to write to only certain lines of a file is an example of fine-grained control. A good access-control implementation allows both coarse-grained and fine-grained control. With the dynamic proxy, it is easy to implement both.

To implement coarse-grained control, it is simply a matter of having a read-only interface for the implementation:

public interface IpersonRO {
  public String getName();
  public String getAddress();
  public String getPhoneNumber();
}
public interface IemployeeRO extends IPersonRO {
  public String getSSN();
  public String getDepartment();
  public Float getSalary();
}
public interface IManagerRO extends IEmployeeRO {
  public String getTitle();
  public String[] getDepartments();
}

That can be limiting in that the client code has to specifically know that it is getting a read-only object. Perhaps a better way to handle that is through the InvocationHandler, which gives the developer the ability to grant or deny access by using the java.lang.security.acl package, and enables the implementation of fine-grained access control:

public class ViewProxy implements InvocationHandler
{
  public static final Permission READ= new PermissionImpl("READ");
  public static final Permission WRITE = new PermissionImpl("WRITE");
  private Map map;
  public static Object newInstance(Map map, Class[] interfaces)
  {
    return Proxy.newProxyInstance(map.getClass().getClassLoader(),
                                  interfaces,
                                 new ViewProxy(map));
  }
  public ViewProxy(Map map)
  {
    this.map = map;
  }
  public Object invoke(Object proxy, Method m, Object[] args) throws
Throwable
  {
    Object result;
    String methodName = m.getName();
    if (methodName.startsWith("get"))
    {
      if (!acl.checkPermission(p1, read)) return null;
      String name = methodName.substring(methodName.indexOf("get")+3);
      return map.get(name);
    }
    else if (methodName.startsWith("set"))
    {
      if (!acl.checkPermission(p1, write)) return null;
      String name = methodName.substring(methodName.indexOf("set")+3);
      map.put(name, args[0]);
      return null;
    }
    else if (methodName.startsWith("is"))
    {
      if (!acl.checkPermission(p1, read)) return null;
      String name = methodName.substring(methodName.indexOf("is")+2);
      return(map.get(name));
    }
    return null;
  }
}

Conclusion

By using the Dynamic Proxy API introduced in Java 1.3, you're presented with a whole range of possibilities. Not only in the typical uses of a dynamic proxy, such as debugging code or writing event handlers for Swing, but also in the underlying principles of abstract data types and the ability to map a view onto another object.

There are many other uses for the dynamic proxy that I haven't touched in this article, such as virtual proxies, in which the actual class isn't loaded until it is needed. You could also use that in conjunction with a remote proxy, which disguises the fact that the object is located remotely across the network. Use your imagination, and the possibilities are endless.

Jeremy Blosser has been programming in Java for five years. He has also written "JavaTip 98: Reflect on the Visitor Design Pattern". Jeremy works for XTRA Online, specifically on www.mytrip.com. Throughout his career, Jeremy has written both client- and server-side frameworks, including a JMS implementation, Java-based databases, proprietary interpreters, and various advanced architectures. His Website www.blosser.org includes some of the code he has written.

Learn more about this topic

This story, "Explore the Dynamic Proxy API" was originally published by JavaWorld.

Related:

Copyright © 2000 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2