Background
We know proxy design pattern involves a representative object that controls access to it's subject that may be -
- Remote
- Expensive to create (Virtual Proxy) or
- Need secure access (Protection Proxy)
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 -
Now lets create a concrete implementation of this profile interface.
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 -
We are now going to use Dynamic proxies to implement above checks -
and
and finally lets do a demo -
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 -
- Owner of a Question
- Non Owner
- Owner can ask question, answer question but cannot do upvote or downvote.
- Non owner cannot ask question but he can answer, upvote or downvote.
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.