动态代理

来源:互联网 发布:mr消音软件 编辑:程序博客网 时间:2024/06/05 06:08
代理的意思:本来应该自己做的事情,因为没有某种原因不能直接做,只能请别人代理做。被请的人就是代理。比如春节买票回家,由于没有时间,只能找票务中介来买,这就是代理模式。

2、以下的例子将简单的模拟代理模式

     比如一个信息管理系统,有的用户有浏览的权限,有的用户有修改和编辑的权限,有的用户除了以上的权限,还有删除的权限。

(1) 最普通的做法:

    public class ViewAction {
   
        public void doAction(){
            String permission = "view";
            if(permission.equals(Constants.PRIMISSION_VIEW)){
                System.out.println("you could view information");

                 //........做view的动作
            }
        }
    }

    其他的动作和浏览的动作差不多。但这样做有两个缺点:(一)它把权限的控制的执行的动作放在一起,两者的功能混在一起,造成了维护和修改的不方便。不满足单一职责原则。(二)客户端调用的是具体的实现类,造成了扩展和运行时调用的困难,不满足依赖颠倒原则(最下面补充说明)。

(2)初步代理

    因此对以上的类做了重新设计,通过代理实现。和买车票一样,代理类必须先检测权限,再执行动作。修改后的密码如下。

     接口类(首先设计一个接口,满足依赖颠倒原来):

    public interface IAction {
        public void doAction();
    }

      动作执行类(相当于买车票的动作),动作的真正执行者:

    public class ViewAction implements IAction {
        public void doAction() {
            System.out.println("you could view the information");
            //............做view的动作
        }
    }

      代理类(ViewActionProxy )除了做了客户要做的doAction()动作,还做了权限的判断。而ViewAction只做了核心动作,满足了单一原则。

    public class ActionProxy implements IAction {
        private ViewAction viewAction = new ViewAction();
        public void doAction() {
            if(Permission.getPermission(userid).equals
              (Constants.PERMISSION_VIEW)){
                viewAction.doAction();
             }
        }
    }

       总之,客户段通过调用这个代理类执行动作,这个代理类通过将权限判断和具体的动作执行分开。实现了单一原则。代理类又叫委派,即代理类并没有亲自执行,而是委派给另一个类来执行。ProxyViewAction并没有亲自执行,而是给ViewAction执行。

     再看看ViewActionProxy,他不仅依赖于抽象接口IAction,而且依赖具体类ViewAction。这样对系统的扩展性很不好。因为我们还有add,delete,update动作等。(1)我们可以为每个动作都写一个代理类,可是每个代理类都做相同的动作:先进行权限的判断,再执行具体的动作。所以我们对ViewAction具体类再一次抽象,使得代理类(ViewActionProxy)只依赖于接口IAction,而不依赖具体类ViewAction。

(3)代理改进:将代理类脱离动作的具体执行类。

   要实现这个功能,必须让代理类的具体实现去掉(private ViewAction viewAction = new ViewAction();),而让代理类的使用者,再使用的时候提供具体的实现类,这个就是依赖注入。

    public class ViewActionProxy implements IAction {
        private IAction action ;
        public void setAction(IAction action) {
            this.action = action;
        }
       
        public void doAction() {
            if(Permission.getPermission(userid).
              equals(Constants.PERMISSION_VIEW)){
                action.doAction();
            }
        }
    }

     这样我们能就将所有使用了IAction接口的具体类都通过这个代理类来代理。比如AddAction、DeleteAction、 UpdateAction都可以用这个代理类来代理。

(4)动态代理:没有实现接口的类的某些方法也可以使用代理。分别添加增加add,delete,update方法,可是每个方法体内都做相同的动作:先进行权限的判断,再执行具体的动作。

        如果我们没办法将所有的类抽象出一个统一的接口,我们可能有多个接口。但是按照上面的方法,每个代理都必须为每个接口写一个相应的代理类。没有统一的接口情况下,对一些零散的对象的某些动作使用代理模式。

       动态代理的核心是InvocationHandler,要实现动态代理,就必须实现这个接口。这个接口的委派任务是在public Object invoke(Object proxy, Method method, Object[] args)方法中实现的。invoke的主要流程如下:

     ...........................//在调用核心动作前的作一些动作。

      m.invoke(obj, args); //调用核心动作。

     ............................//在调用核心动作后的做一些动作。

       我们看到动态代理是通过反射机制来调用核心功能m.invoke(obj, args); 这个反射机制使得我们不依赖任何一个具体的接口,而是依赖具体的类。

动态代理类的具体实现如下:

    public class ActionProxy implements InvocationHandler {
        private Object action;
       
        public ActionProxy(Object action){
            this.action = action;
        }
           
        public static Object getInstance(Object action){
            return Proxy.newProxyInstance(
                                     action.getClass().getClassLoader(),
                                     action.getClass().getInterfaces(),
                                     new ActionProxy(action));

             //自动生成相应的代理类,且类里面有个变量是ActionProxy对象,当客户端调用这个代理类的方法时候,代理类会调用它的ActionProxy 变量的invoke方法,因此每次调用了代理类,就可以在方法执行前和方法执行后加入特定的执行内容(事务或日志等)。
         }
        
         public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
              Object result;
              System.out.println("befor method:"+method.getName());
              result = method.invoke(action, args);
              System.out.println("after method:"+method.getName());
              return result;
         }
    }

      代理类必须实现InvocationHandler接口,getInstance()方法获得代理类的实例(实现了IAction接口)的对应代理,invoke方法实现了简单的代理方法。

客户段代码如下:

    public class TrasasactionTest {
        public static void main(String[] args) {
            IAction action = (IAction) ActionProxy.getInstance(new ViewAction());
            action.doAction();
        }
    }

     由上得出,代理对于接口的依赖转移到客户端,代理不必再实现特定的接口。因此ActionProxy也可以用于任何的接口。

            ITestService service = (ITestService ) ActionProxy.getInstance(new TestServiceImpl());
            service .doTest();//只要TestServiceImpl实现ITest,就可以。

(5)在委派前的动作和委派后的动作在不同的代理类不用,可是代理类的生成和其他的内容都是相同的。所以我们可以用Template模式对代理类进行进一步的优化。

      生成代理类的基类的代码如下:

public abstract class BaseProxy implements InvocationHandler {

private Object target;

public Object getTarget() {
   return target;
}

public BaseProxy(Object target){
   this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        Object result;
        doBefore();
        result = method.invoke(target, args);
        doAfter();
   return result;
}
public abstract void doBefore();
public abstract void doAfter();
}

代理类基类的具体实现如下:

    public class ProxyImpl extends BaseProxy {
        public ProxyImpl(Object target) {
            super(target);
        }

        //委派前的动作
        public void doAfter() {
            System.out.println("do before method");
        }

       //委派后的动作
        public void doBefore() {
            System.out.println("do after method");
        }
    }

      因此,在OOP中,doBefore(),核心动作,doAfter()可能这3个动作在所有的类中都存在,但是doBefore,doAfter的内容都是相同的,而核心动作确实不相同的。因此把doBefore和doAfter提取出来,共同设计,这是AoP的基础。AoP的一个重要特点是找出哪些方法需要执行doBefore和doAfter,哪些不用。
原创粉丝点击