Monday 16 February 2015

Proxy Design Pattern in Java

Background

We know proxy design pattern involves a representative object that controls access to it's subject that may be - 
  1. Remote
  2. Expensive to create (Virtual Proxy) or
  3. Need secure access (Protection Proxy)
In last post on

Understanding Java Remote Method Invocation (RMI)

We say example of Remote proxy. Stub is essentially the proxy that controls access of actual remote object.

Here is the summary of what happens in Proxy design pattern -

Proxy class implements the same interface as that of the Subject. Client crates a instance of proxy class (Typically using Factory pattern.) rather than the actual subject and calls methods on proxy (this is possible since both proxy and subject implement same interface). Now proxy internally instantiates Subject and calls methods on it depending on the purpose of the proxy.




We will see example of both Virtual as well as Protection proxy in this example. Later we will also see how proxies are dynamically created (Java has in built support for it).

 Virtual Proxy

Lets say we are creating a news website. Lets design Interface for it - 

public interface NewsSiteInterface {
    void showNews();
}

Now lets write the actual implementation - 
public class NewsSiteImpl implements NewsSiteInterface {
    
    String news;
    
    public NewsSiteImpl() {
        //simulate blocking data transfer
        try {
            Thread.sleep(5000);
            System.out.println("Data Received");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        news = "Hello World";
    }
    
    public void showNews() {
        System.out.println(news);
    }

  
}


Notice the constructor simulates fetching data which is a block call. If you directly make instance of NewsSiteImpl and call showNews() on it site may possible hang as instance creation will take time. Lets now create a proxy that would avoid this blocking call.


public class NewsSiteProxy implements NewsSiteInterface {
    
    NewsSiteInterface newsSite;
    
    public NewsSiteProxy() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                newsSite = new NewsSiteImpl();
                showNews();
            }
        }).start();
    }

    @Override
    public void showNews() {
        if(newsSite == null) {
            System.out.println("Loading....");]
        }
        else {
            newsSite.showNews();
        }
        
    }

}

So now we create instance of  NewsSiteInterface in another thread asynchronously. So user can go ahead and call showNews(). Obviously data may not be loaded by then, So we just show Loading...
Important point out program will not get hanged. Lets test out setup.

public class NewsDemo {
    public static void main(String args[])
    {
        NewsSiteInterface newSite  = new NewsSiteProxy();
        newSite.showNews();
        newSite.showNews();
    }
    
}

and the output is -

Loading....
Loading....
Data Received
Hello World

So that was our virtual proxy that helped us tackle creating on expensive time consuming instance.

Now lets see our protection proxy. Well the concept remains the same. Instead of doing task asynchronously we do some validation.

 In our next protection proxy demo we will use Java Dynamic proxies.

Protection Proxy and Java Dynamic proxy

Lets say we are designing a programming QnA site like Stack Overflow.  When user create a account on Stack Overflow he essentially created a profile. Lets write a class for that -


public interface StackOverflowProfile {
    String askQuestion(String question);
    String writeAnswer(String answer);
    void upVote();
    void downVote();
}


Now lets create a concrete implementation of this profile interface.

public class StackOverflowProfileImpl implements StackOverflowProfile {

    @Override
    public void askQuestion(String question) {
        //Ask Question
    }

    @Override
    public void writeAnswer(String answer) {
        //Provide Answer to a question
    }

    @Override
    public void upVote() {
        //Up vote an Answer or Question
        
    }

    @Override
    public void downVote() {
        //Down vote an Answer or Question
    }

}


So far so good. Program would work but we did a major mistake here. We do not have verification check in the code which means a user can keep asking questions, answer them and keep upvoting the same thereby increasing his or her reputation.  Only if we knew proxy pattern before :)

Lets divide out auth checks / Proxy into two parts -
  1. Owner of a Question
  2. Non Owner
Our use case is -
  • Owner can ask question, answer question but cannot do upvote or downvote.
  • Non owner cannot ask question but he can answer, upvote or downvote.
Note : Each profile can use either of the proxy depending on if he or she is the owner of the Question. If owner then Owner Proxy will be used and upvote or downvote operations will not be allowed. If non owner proxy is used then question cannot be asked (as someone else is the owner) but answer, upvote, dowvote operations are allowed.

We are now going to use Dynamic proxies to implement above checks -

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

public class OwnerSOProfile implements InvocationHandler {
    
    StackOverflowProfile soProfile;
    
    public OwnerSOProfile(StackOverflowProfile soProfile)
    {
        this.soProfile = soProfile;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        try
        {
            if(method.getName().equalsIgnoreCase("askQuestion"))
            {
                method.invoke(proxy, args);
            }
            else if(method.getName().equalsIgnoreCase("writeAnswer"))
            {
                method.invoke(proxy, args);
            }
            else if(method.getName().equalsIgnoreCase("upVote"))
            {
                throw new IllegalAccessException("Cannot Up vote own question or answer");
            }
            else if(method.getName().equalsIgnoreCase("downVote"))
            {
                throw new IllegalAccessException("Cannot down vote own question or answer");
            }
        }
        catch(InvocationTargetException ex) {
            ex.printStackTrace();
        }
        return null;
    }
}




and

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

public class NonOwnerSoProfile implements InvocationHandler {
    
    StackOverflowProfile soProfile;
    
    public NonOwnerSoProfile(StackOverflowProfile soProfile)
    {
        this.soProfile = soProfile;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        
        try
        {
            if(method.getName().equalsIgnoreCase("askQuestion"))
            {
                throw new IllegalAccessException("Cannot edit question as you are not the owner");
            }
            else if(method.getName().equalsIgnoreCase("writeAnswer"))
            {
                method.invoke(proxy, args);
            }
            else if(method.getName().equalsIgnoreCase("upVote"))
            {
                method.invoke(proxy, args);
            }
            else if(method.getName().equalsIgnoreCase("downVote"))
            {
                method.invoke(proxy, args);
            }
        }
        catch(InvocationTargetException ex) {
            ex.printStackTrace();
        }
        return null;
    }
}



and finally lets do a demo -

import java.lang.reflect.Proxy;

public class SODemo {
    
    public static void main(String args[])
    {
        //general profile
        StackOverflowProfile profile = new StackOverflowProfileImpl();
        //allowed
        (profile).askQuestion("What is proxy pattern?");
        //not allowed
        getOwnerSoProxy(profile).upVote();
        //not allowed
        getNonOwnerSoProxy(profile).askQuestion("Java anti patterns?");
        //alowed
        getNonOwnerSoProxy(profile).upVote();
    }
    
    public static StackOverflowProfile getOwnerSoProxy(StackOverflowProfile profile)
    {
        return (StackOverflowProfile) Proxy.newProxyInstance(profile.getClass().getClassLoader(), profile.getClass().getInterfaces(), new OwnerSOProfile(profile));
    }
    
    public static StackOverflowProfile getNonOwnerSoProxy(StackOverflowProfile profile)
    {
        return (StackOverflowProfile) Proxy.newProxyInstance(profile.getClass().getClassLoader(), profile.getClass().getInterfaces(), new NonOwnerSoProfile(profile));
    }

}

Note : For above demo code. Try one call at a time because we have not added any Exception handling so whenever IllegalAccess Exception is thrown JVM will terminate.

Related Links

t> UA-39527780-1 back to top