Spring源码解析(三)静态代理与动态代理

目录

静态代理

动态代理

JDK动态代理

CGLIB动态代理


代理也称“委托”,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势。

代理的设计理念是限制对象的直接访问,即不能通过 new 的方式得到想要的对象,而是访问该对象的代理类。这样的话,我们就保护了内部对象,如果有一天内部对象因为某个原因换了个名或者换了个方法字段等等,那对访问者来说一点不影响,因为他拿到的只是代理类而已,从而使该访问对象具有高扩展性。然而,代理类可以实现拦截方法,修改原方法的参数和返回值,满足了代理自身需求和目的,也就是是代理的方法增强性。为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

 

使用场合举例:

如果需要委托类处理某一业务,那么我们就可以先在代理类中统一处理然后在调用具体实现类

 

按照代理的创建时期,代理类可以分为两种: 

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态:在程序运行时运用反射机制动态创建而成。

静态代理

 接口

public interface ISubject {
    void doAction(String action);
}

 真正的类:

public class RealSubject implements ISubject {
    @Override
    public void doAction(String action) {
        System.out.println("I am RealSubject, do action "+ action);
    }
}

代理类及使用 

public class ProxySubject implements ISubject {
    ISubject mRealSubject;

    public ProxySubject(ISubject mRealSubject) {
        super();
        this.mRealSubject = mRealSubject;
    }


    @Override
    public void doAction(String action) {
        preRequest();
        mRealSubject.doAction(action);
        postRequest();
    }
    protected void postRequest(){
        System.out.println("postRequest");

    }

    protected void preRequest() {
        System.out.println("preRequest");

    }
    public static void main(String[] args) {
        // 被代理对象
        RealSubject realSubject = new RealSubject();
        // 代理类持有被代理对象
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.doAction("play");
    }

}

优点:

 

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。

 

缺点:

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

 

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)

即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

动态代理

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

 

在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

 

在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持

JDK动态代理

 Proxy类提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理的父类,如果在程序中为一个或多个接口动态的生成实现类,就可以使用Proxy来创建动态代理类,如果需要为一个或多个接口动态的创建实例,也可以使用Proxy来创建动态代理实例。

下面用程序示范如何生成动态代理对象。(https://github.com/lizhjian/crazyJava MyDynamicProxy 类)

public class MyDynamicProxy {
    public static void main(String[] args) {
        XiaoMing xiaoMing = new XiaoMing();
        InvocationHandler handler = new MyInvocationHandler(xiaoMing);
        Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        //Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), XiaoMing.class.getInterfaces(), handler);
        p.eat();
        p.say("abc");
    }
}

interface Person {
    void say(String word);

    void eat();
}


class MyInvocationHandler implements InvocationHandler {
    private Person p;

    public MyInvocationHandler(Person p) {
        this.p = p;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("方法名称:" + method);
        method.invoke(p, args);
        return null;
    }
}

class XiaoMing implements Person {
    @Override
    public void say(String word) {
        System.out.println("小明说话");
    }

    @Override
    public void eat() {
        System.out.println("小明吃饭");
    }
}

上面程序首先提供了一个Person接口,该接口中包含两个抽象方法,接着定义了一个简单的InvocationHandler实现类,定义该实现类时需要重写invoke()方法--调用代理对象的所有方法时都会被替换成调用invoke()方法。JDK动态代理的具体实现后面会详细说明。

CGLIB动态代理

“代理”模式的本质是构造一个和被代理对象具有相同行为的对象,达到“以假乱真”的效果,对象的行为在类中定义,不一定非要通过持有原类对象的方式进行代理。可以通过"继承"原类的公开方法进行重写,在重写时进行增强操作,这就是cglib的思想,代码如下。

public class MyInterceptor implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 增强
        System.out.println("=== BEFORE ===");
        // 这里的invokeSuper是调用父类方法
        Object re = methodProxy.invokeSuper(o, objects);

        System.out.println("=== AFTER ===");

        return re;
    }

    public static void main(String[] args) {
        Enhancer eh = new Enhancer();

        eh.setSuperclass(Department.class);

        eh.setCallback(new MyInterceptor());

        Department r = (Department)eh.create();

        r.add(1, 2);
    }
}

可以看出,cglib与jdk动态代理不同,它只需要一个类就可以产生一个代理对象即"类的代理",而jdk动态代理要求对象必须实现接口。

cglib的原理是这样,它生成一个继承A的类型B(代理类),这个代理类持有一个MethodInterceptor,setCallback时传入的。 B重写所有A中的方法(方法名一致),然后在B中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

这样的话,B中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

B的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值