【小威哥の设计模式系列-3】代理模式

552 阅读3分钟

1:概述

说起代理模式,特别是动态代理,大家第一反应就是spring的aop,那么这种模式到底如何使用,怎么使用呢,下面小威哥就为大家详解。



2:使用场景

通俗来讲,很多时候,我们会想要在特定的一个方法前后加上一些自己定制的前置行为和后置行为(比如打个日志啊,统计个耗时什么的),这个时候我们可以尝试使用代理模式来解决



3:静态代理

我们来直接给出代码案例

public interface Subject {
     void doCall();
}

//这是我们的被代理类
public class StaticSubject implements Subject {
    @Override
    public void doCall() {
        System.out.println("do call");
    }
}

//这是我们的代理类
//这样我们等于用了一个灵活的代理类去加强了被代理类的功能
public class RealStaticSubject implements Subject {

    private StaticSubject staticSubject;

    public RealStaticSubject(StaticSubject staticSubject) {
        this.staticSubject = staticSubject;
    }
    @Override
    public void doCall() {
        doBefore();
        staticSubject.doCall();
        doAfter();
    }
    private void doBefore(){
        System.out.println("do before");
    }
    private void doAfter(){
        System.out.println("do after");
    }
}

然后我们来测试一下:

    private static void doStaticProxy(){
        Subject subject = new RealStaticSubject(new StaticSubject());
        subject.doCall();
    }

看到这里有的小伙伴会想:这tm不就是模板模式吗(模板模式我后面也会讲),的确!这基本就和模板模式真没啥区别,换汤不换药的,所以这就是java设计模式有时候真的很臃肿不实用。

所以静态代理太麻烦又不灵活,大家没事还是别用。


3:动态代理

3.1:jdk动态代理

jdk的proxy类只能使用接口

//被代理类
public class CallSubject implements Subject {
    @Override
    public void doCall(){
        System.out.println("do call");
    }
}

//代理类逻辑和生成代理类方法
public class ProxyHandler implements InvocationHandler {

    private Object tar;

    //并返回代理类
    public Object bind(Object tar)
    {
        this.tar = tar;
        //绑定该类实现的所有接口,取得代理类
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
                tar.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
    {
        Object result = null;
        //这里就可以进行所谓的AOP编程了
        //在调用具体函数方法前,执行功能处理
        System.out.println(method.getDeclaringClass());
        System.out.println("do method:"+method.getName()+" start"+" class="+tar.getClass().getName());
        result = method.invoke(tar,args);
        //在调用具体函数方法后,执行功能处理
        System.out.println("do method:"+method.getName()+" end");
        return result;
    }
}

然后我们来测试一下:

        ProxyHandler proxy = new ProxyHandler();
        //绑定该类实现的所有接口 (jdk的代理只能使用接口,cglib可以使用具体类)
        Subject sub = (Subject) proxy.bind(new CallSubject());
        sub.doCall();

3.2:cglib动态代理

cglib的proxy类不同于jdk代理可以使用类,效率上相比jdk的也可能只是稍快一点,意义不大

一般spring框架都会带cglib包,不需要单独引入

//被代理类
public class CglibSubject {
    public void call(){
        System.out.println("cglib do call");
    }
}

//代理类逻辑和获取代理类方法
public class CglibProxy implements MethodInterceptor {
    /**
     * 获取代理类
     */
    public CglibSubject getProxyClass() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CglibSubject.class);
        enhancer.setCallback(this);
        CglibSubject proxyClass = (CglibSubject) enhancer.create();
        return proxyClass;
    }
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        methodProxy.invokeSuper(o, objects);
        System.out.println("after");
        return null;
    }
}

然后我们来测试一下:

        CglibProxy cglibProxy = new CglibProxy();
        CglibSubject cglibSubject = cglibProxy.getProxyClass();
        cglibSubject.call();

动态代理看着虽然很灵活很帅,但小威哥觉得其实用处也不大,除非是用来设计通用的框架,正式开发中难道spring-aop不香吗?



4:函数式编程实现代理(野蛮强力之推荐)

小威哥推荐这种方法,利用lambda,更帅更函数式,更加次时代,与众不同!

//被代理执行接口
public interface SubjectInvoker<T> {
    T invoke(String name);
}

//加强执行器
public class SubjectClient {
    public <T> T invoke(String names, SubjectInvoker<T> clients) {
        System.out.println("do before");
        String name = names + "xx";
        T obj = clients.invoke(name);
        System.out.println("do after");
        return obj;
    }
}

然后我们来测试一下:

        SubjectClient subjectClient = new SubjectClient();
        String result = subjectClient.invoke("sjw", (name) -> {
            System.out.println("do real name = " + name);
            return null;
        });

怎么样,是不是很酷这样,参数也很灵活传递


5:总结

总的来说代理模式感觉非常华而不实,除了写的乱一点让同事看不懂,平时开发中实战功能不大,多写几个类啊方法啊也能解决,代码也更加线性。