Monday, April 25, 2016

Static Proxy and Dynamic Proxy

Normally like security check, transaction, log, these commonly used functions are not related to business logic, we can use proxy to separate these code from business codes

Think an interface has some functions
And before running each function in this interface, you need to call checkSecurity() first
To make the implementation cleaner
And which does not change when requirement for checkSecurity() changes
What solutions we have?

Example Interface:
package com.gvace.inter;

public interface UserManager {
 public void addUser(String username,String password);
 public void delUser(int userId);
 public String findUserById(int userId);
 public void modifyUser(int userId, String username,String password);
}

Clear Implementation example:
package com.gvace.impl;

import com.gvace.inter.UserManager;

// each method have to call checkSecurity()
// and you don't want to change it based on requirement changes(like the codes being comment out)
public class UserManagerImpl implements UserManager {

 @Override
 public void addUser(String username, String password) {
  //checkSecurity();
  System.out.println("addUser:"+username+" "+password);
 }

 @Override
 public void delUser(int userId) {
  //checkSecurity();
  System.out.println("delUser:"+userId+" ");
 }

 @Override
 public String findUserById(int userId) {
  //checkSecurity();
  System.out.println("findUserById:"+userId+" ");
  return "findUserById"+userId;
 }

 @Override
 public void modifyUser(int userId, String username, String password) {
  //checkSecurity();
  System.out.println("modifyUser:"+userId+" "+username+" "+password);
 }
 /*
 public void checkSecurity(){
  System.out.println("Runned checkSecurity");
 }
 */
}

Solution 1: Static Proxy
To simplify target implementation class, we build this proxy
We do checkSecurity() in proxy
So target implementation will not changed when requirement change
But we still have to call checkSecurity() in every method in this proxy
And we still have to change this proxy when requirement change

package com.gvace.proxy.static_;

import com.gvace.inter.UserManager;

/*
 * To simplify target implementation class, we build this proxy
 * We do checkSecurity() in proxy
 * So target implementation will not changed when requirement change
 * But we still have to call checkSecurity() in every method in this proxy
 * And we still have to change this proxy when requirement change
 * 
 * Thinking:
 * Actually checkSecurity() is separate logic from the target functions.
 * We need to find a way to separate it
 */
public class UserManagerImplProxy implements UserManager {
 UserManager userManager;
 public UserManagerImplProxy(UserManager userManager){
  this.userManager = userManager;
 }
 @Override
 public void addUser(String username, String password) {
  checkSecurity();
  userManager.addUser(username, password);
 }

 @Override
 public void delUser(int userId) {
  checkSecurity();
  userManager.delUser(userId);
 }

 @Override
 public String findUserById(int userId) {
  checkSecurity();
  return userManager.findUserById(userId);
 }

 @Override
 public void modifyUser(int userId, String username, String password) {
  checkSecurity();
  userManager.modifyUser(userId, username, password);
 }
 private void checkSecurity(){
  System.out.println("Runned checkSecurity");
 }
}


Thinking:
Actually checkSecurity() is separate logic from the target functions.
We need to find a way to separate it, so we have Solution 2



Solution 2: Dynamic Proxy

Create a Handler which implements InvocationHandler
This handler will register all functions from the target interfaces
So every-time the registered functions runs, it needs to go through the invoke() function in this handler
In this way, we can control the registered functions



package com.gvace.proxy.dynamic;

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


 //To use Dynamic Proxy
 //We build a InvocationHandler only for the function checkSecurity()


public class SecurityHandler implements InvocationHandler {
 private Object targetObject;

 public Object createProxyInstance(Object targetObject){
  //Save the targetObject as a reference
  this.targetObject = targetObject;
  
  
   // By creating this proxy
   // all interfaces' functions(targetObject.getClass().getInterfaces())
   // will be registered to this handler(filter) 
  return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
        targetObject.getClass().getInterfaces(),
        this);
 }

 
  // see java.lang.reflect.InvocationHandler
  // invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
  // 
  // invoke() like a filter of the target function
  // This will run every-time when the registered interfaces' functions runs
  

 @Override
 public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable {
  //do the checkSecurity()
  checkSecurity();
  
  //after checkSecurity() the filter is finished
  //we can invoke targetObject's function now.(like a filter chain)
  //or we can choose to return null, if it's not pass the checkSecurity() requirement
  return method.invoke(targetObject, args);
 }

 /*
  * checkSecurity() runs like invisible
  */
 private void checkSecurity() {
  System.out.println("Runned checkSecurity");
 }
}




And to run the target through this Handler
You can also use factory pattern to hide the procedure of creating handler and proxy instance

package com.gvace.test;

import com.gvace.impl.UserManagerImpl;
import com.gvace.inter.UserManager;
import com.gvace.proxy.dynamic.SecurityHandler;

public class Client {
 public static void main(String[] args){
  //Create a proxy instance
  //You can also hidden this by using a factory pattern
  SecurityHandler handler = new SecurityHandler();
  UserManager userManager = (UserManager)handler.createProxyInstance(new UserManagerImpl());
  
  userManager.addUser("aaa", "123");
 }
}

Static Proxy and Dynamic Proxy
Since dynamic proxy is dynamic registering the function, it's not efficient as static proxy.

Solution 3: Spring AOP
http://gvace.blogspot.com/2016/03/aop.html

No comments:

Post a Comment