杀鸡不想用牛刀-用代理

  大家好,我是小赵,求职的路虽然难,但最终还是有个着落,我现在进了藏剑山庄任职铸剑师,不过没意思,因为活都是低级的活,批量铸些普通的匕首、短剑之类,一天到晚忙个没完,这藏剑山庄果然是个大厂,订单超级多。

  做着做着我就没动力了,没啥技术含量,虽然是计件,但还不如我的打印机业务赚钱来的多,于是我就私底下请一些有空的同事帮我干活,而我就每天打个卡,然后就在家发展我的打印事业。

  其实主要原因呢,还是因为我暂时不打算离职,先静观其变,等待机会,毕竟这是个大企业。

  现在,是写日记的时候,我要记录一下我请同事干活的过程。

  首先,同事给我干活,那就必须要有和我一样的技能,比如调剂、熔炼、浇铸、加工这些铸剑流程,所以铸剑的流程应该抽象出来。而同事给我干活,天知地知,虽然是他干的活,但这些剑的创建者名字必须是我。

  思考完毕之后,我画下了类图:

代理模式-LMLPHP

  接下来我就根据这个类图来写程序

铸剑流程抽象:

public interface IMakeSword {
    //调剂
    void adjust();

    //熔炼
    void smelt();

    //浇铸
    void casting();

    //加工
    void process();
} 

我:

public class User implements IMakeSword {
    //名字
    private String name = "";

    public User(String name) {
        this.name = name;
    }
    @Override
    public void adjust() {
        System.out.println(this.name + "正在调剂...");
    }
    @Override
    public void smelt() {
        System.out.println(this.name + "正在熔炼...");
    }
    @Override
    public void casting() {
        System.out.println(this.name + "正在浇铸...");
    }
    @Override
    public void process() {
        System.out.println(this.name + "正在加工...");
    }
} 

同事:

public class Colleague implements IMakeSword{
    private IMakeSword user;

    //构造函数把我传进来
    public Colleague(IMakeSword user) {
        this.user = user;
    }
    @Override
    public void adjust() {
        this.user.adjust();
    }
    @Override
    public void smelt() {
        this.user.smelt();
    }
    @Override
    public void casting() {
        this.user.casting();
    }
    @Override
    public void process() {
        this.user.process();
    }
}

  差不多了,现在我的同事要开始给我干活了:

    public static void main(String[] args) {
        IMakeSword xiaoZhao = new User("小赵");
        IMakeSword colleague = new Colleague(xiaoZhao);
        colleague.adjust();
        colleague.smelt();
        colleague.casting();
        colleague.process();
    }

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

  这个活干的是非常的好,工资我当然会转给同事,但是我的工作量在山庄里还不至于难看。

  随手一招代理模式,暂时保着这份工作,赚个五险一金。

严查之下-强制代理

  后来,不知道是哪里传出的风声,山庄高层领导好像知道了什么,安排了专案小组暗地里对一些不正之风进行严查严打。

  我不怕顶风作案,就怕做的不严实。

  如果,专案小组假扮我的同事,那我不就嗝屁了吗?于是,我决定把我和我同事的关系隐藏到内部,不让人看到,也不接受外面传入。

  来看看我更改后的程序:

铸剑流程抽象:

public interface IMakeSword {
    //调剂
    void adjust();

    //熔炼
    void smelt();

    //浇铸
    void casting();

    //加工
    void process();

    //获取我的代理
    IMakeSword getProxy();
}

  加一个getProxy方法,获取我的代理,就是我的同事嘛,增加这个方法是为了防止外人假冒。

我:

public class User implements IMakeSword {
    //名字
    private String name = "";

    //我的同事
    private IMakeSword proxy = null;

    public User(String name) {
        this.name = name;
    }
    @Override
    public void adjust() {
        if(null == this.proxy){
            System.out.println("你好,"+this.name+"在休息。");
            return;
        }
        System.out.println(this.name + "正在调剂...");
    }
    @Override
    public void smelt() {
        if(null == this.proxy){
            System.out.println("你好,"+this.name+"在休息。");
            return;
        }
        System.out.println(this.name + "正在熔炼...");
    }
    @Override
    public void casting() {
        if(null == this.proxy){
            System.out.println("你好,"+this.name+"在休息。");
            return;
        }
        System.out.println(this.name + "正在浇铸...");
    }
    @Override
    public void process() {
        if(null == this.proxy){
            System.out.println("你好,"+this.name+"在休息。");
            return;
        }
        System.out.println(this.name + "正在加工...");
    }

    @Override
    public IMakeSword getProxy() {
        //我和同事的小黑屋
        this.proxy = new Colleague(this);
        return this.proxy;
    }
}

在getProxy方法里,我在小黑屋里自己联系我的同事,并且在每个动作执行的时候,先检测我有没有代理,没代理的话千万别乱动。

同事:

public class Colleague implements IMakeSword{
    private IMakeSword user;

    //构造函数把我传进来
    public Colleague(IMakeSword user) {
        this.user = user;
    }
    @Override
    public void adjust() {
        this.user.adjust();
    }
    @Override
    public void smelt() {
        this.user.smelt();
    }
    @Override
    public void casting() {
        this.user.casting();
    }
    @Override
    public void process() {
        this.user.process();
    }

    @Override
    public IMakeSword getProxy() {
        return this;
    }
}

同事类则几乎没变化,getProxy方法也不需要的,返回同事自己就可以了。

  来,现在再试一下合作:

    public static void main(String[] args) {
        IMakeSword xiaoZhao = new User("小赵");
        IMakeSword colleague = xiaoZhao.getProxy();
        colleague.adjust();
        colleague.smelt();
        colleague.casting();
        colleague.process();
    }

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

  这是新的合作方式,谁也不知道我的代理是谁。

如果专案小组来假冒同事呢:

    public static void main(String[] args) {
        IMakeSword xiaoZhao = new User("小赵");
        IMakeSword colleague = new Colleague(xiaoZhao);
        colleague.adjust();
        colleague.smelt();
        colleague.casting();
        colleague.process();
    }

输出:

你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。

  这就是强制代理的手法,代理由自己管理,从此以后,我又可以高枕无忧了。

降低合作门槛-动态代理

  我没想到,我那个同事不仅接我的活,还同时接别人的活,可别人就没我这么聪明啊,天天对着他new、new、new!结果没过几天就被专案小组给逮着了,直接翻船,同事也被供出来了,被劝退。

  

  随着我同事的牺牲,山庄的严打行动逐渐结束,不久后专案小组解散,紧张的氛围舒缓而开。

  但是我得从新找合作人啊,私底下接触了几个有意向的同事,都觉得我的合作方式太过麻烦,必须要有代理类,就是Colleague类,要实现一箩筐方法,而且创建了类就是留下了证据,将来就有被查到的风险。

  

  在我的一阵子研究之后,发现JDK有一个InvocationHandler接口可以用,于是乎,我重新写了一个程序。

铸剑流程抽象:

public interface IMakeSword {
    //调剂
    void adjust();

    //熔炼
    void smelt();

    //浇铸
    void casting();

    //加工
    void process();
}

我:

public class User implements IMakeSword {
    //名字
    private String name = "";

    public User(String name) {
        this.name = name;
    }
    @Override
    public void adjust() {
        System.out.println(this.name + "正在调剂...");
    }
    @Override
    public void smelt() {
        System.out.println(this.name + "正在熔炼...");
    }
    @Override
    public void casting() {
        System.out.println(this.name + "正在浇铸...");
    }
    @Override
    public void process() {
        System.out.println(this.name + "正在加工...");
    }
}

动态代理类:

public class ColleagueHandler implements InvocationHandler {
    Object object = null;

    //代理的目标
    public ColleagueHandler(Object object) {
        this.object = object;
    }

    //调用目标方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.object,args);
    }
}

开始干活:

    public static void main(String[] args) throws Throwable{
        IMakeSword xiaoZhao = new User("小赵");
        InvocationHandler handler = new ColleagueHandler(xiaoZhao);
        handler.invoke(null,xiaoZhao.getClass().getMethod("adjust", null),null);
        handler.invoke(null,xiaoZhao.getClass().getMethod("smelt", null),null);
        handler.invoke(null,xiaoZhao.getClass().getMethod("casting", null),null);
        handler.invoke(null,xiaoZhao.getClass().getMethod("process", null),null);
    }

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...

  很好,程序实现了,从始至终都没有创建代理类,虽然很多人都会说这是动态代理,但实际上却不是,这只是反射而已。

  对于一个优秀的架构师来讲,这简直就是渣渣玩法,一箩筐方法名都写死在外面了,去他大爷的JDK。

  在寻求优化的过程中,听说JDK还提供了Proxy类,里面有个newProxyInstance方法用来创建一个对象的代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。

  于是乎,我又舔着大逼脸去使用JDK的接口了。

增加一个动态代理类:

public class MyProxy {
    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
        T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h);
        return newProxyInstance;
    }
}

开始干活:

    public static void main(String[] args) throws Throwable{
        IMakeSword xiaoZhao = new User("小赵");
        ClassLoader cl = xiaoZhao.getClass().getClassLoader();
        IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao));
        proxy.adjust();
        proxy.smelt();
        proxy.casting();
        proxy.process();
        System.out.println(xiaoZhao.equals(proxy));
    }

输出:

小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false

  最后一个比较两个对象是否相同,证明了proxy对象并不是我,但实际上却又是我在干活,这才叫动态代理。

通知机制-切面

  其实我们细看之下,已经发现了一个情况,就是铸剑相关的类已经和代理解耦了。

  第一条线:IMakeSword接口->User类。

  第二条线:InvocationHandler接口->ColleagueHandler类->MyProxy类。

  动态代理实现代理的职责,业务逻辑负责功能实现。

  现在,为了安全起见,我希望做一件事情,就是每次干活的时候,要给我发一个通知,让我知道。

  其实非常的简单,我们在创建动态代理的时候发这个通知就行了。

  先创建一个通知抽象,因为以后可能会有前置通知、后置通知、各种切入的通知等等,所以通知也是一条独立发展的线。

通知抽象:

public interface IAdvice {
    void exec();
}

前置通知:

public class BeforeAdvice implements IAdvice {
    @Override
    public void exec() {
        System.out.println("准备要干活了");
    }
}

动态代理类:

public class MyProxy {
    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
        new BeforeAdvice().exec();
        T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h);
        return newProxyInstance;
    }
}

开始干活:

public static void main(String[] args) throws Throwable{
        IMakeSword xiaoZhao = new User("小赵");
        ClassLoader cl = xiaoZhao.getClass().getClassLoader();
        IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao));
        proxy.adjust();
        proxy.smelt();
        proxy.casting();
        proxy.process();
        System.out.println(xiaoZhao.equals(proxy));
    }

输出:

准备要干活了
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false

  如果有的代理需要通知,有的时候不需要通知呢?很简单,传个参数,写个if条件即可,如果不想传参数呢?一般AOP的做法是使用注解的方式标记切入位置,然后在动态代理类里面对注解进行判断。

  获取注解也可以使用反射机制实现,类头上的注解、方法头上的注解、参数上的注解都可以获取,有兴趣的朋友可以自行研究。

最后声明

本故事写到后面有点怪,原因是动态代理和静态代理的使用场景是不同的,用同一个故事延续下来导致需求逻辑上出现了一些bug,在此致歉。

04-23 07:59