代理模式(静态代理、动态代理)

代理模式的由来:当调用某个对象时,不关心是否准确得到该对象,而是只要一个能提供对应功能的对象即可,这时我们可以为该对象提供一个代理对象,由代理对象控制对源对象的引用。

第一.静态代理

常见静态代理模式:一个接口,两个实现类,分别为被代理类和代理类,被代理类执行正常业务逻辑,代理类中进行如下操作:
1.创建接口类型成员变量
2.构造方法中创建被代理类对象()
3.实现的接口方法中调用被代理类的实现的接口方法
示例代码如下:

// 被代理类
class SubjectImpl implements Subject {
    @Override
    public void action() {
        System.out.println("执行action方法体");
    }
}

// 代理类
class ProxyClass implements Subject {
    private Subject subject = new SubjectImpl();

    private void beforeHandle() {
        System.out.println("业务方法执行前执行前置增强处理");
    }

    private void afterHandle() {
        System.out.println("业务方法执行后执行后置增强处理");
    }

    @Override
    public void action() {
        this.beforeHandle();
        subject.action();
        this.afterHandle();
    }

}

public class ProxyTest {
    public static void main(String[] args) {
        ProxyClass obj = new ProxyClass();
        obj.action();
    }
}

要使用被代理类对象的方法时,只需简单的实例化代理类对象,调用此代理类对象的方法即可,其实是方法内部调用了被代理类的方法。

第二.动态代理

动态代理分为基于接口的动态代理和基于子类的动态代理,基于接口的动态代理有JDK动态代理,基于子类的动态代理有Cglib动态代理、Javassist动态代理、ByteBubby动态代理。

1、JDK动态代理

JDK动态代理主要用到了Proxy类和InvocationHandler接口,两者都在java.lang.reflect包下。

首先介绍一下Proxy类,这个类是所有动态代理类的父类,主要用到这个类的newProxyInstance()静态方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

这个方法直接生成一个动态代理类对象,方法需要传三个参数,第一个参数是被代理类对应的类加载器对象,第二个参数是被代理类实现的一系列接口对应的Class对象组成的数组一个数组,第三个参数是一个InvocationHandler接口实例

InvocationHander接口就只有一个方法:

public Object invoke(Object proxy, Method method, Object[] args)

我们得自定义一个InvocationHander接口实现类,重写invoke()方法。

JDK动态代理代码示例:

//接口
interface Subject {
    public void doSomething(String str);
}

// 接口实现类
class RealSubject implements Subject {
    @Override
    public void doSomething(String str) {
        System.out.println("Subject接口实现类RealSubject实现doSomething方法()");
    }
}

// InvocationHandler接口实现类
class MyIncovationHandler implements InvocationHandler {
    private Object proxied;

    // 构造器参数是被代理类对象
    public MyIncovationHandler(Object proxied) {
        this.proxied = proxied;
    }

    public MyIncovationHandler() {
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        method.invoke(proxied, args);
        after();
        return null;
    }

    // 增强处理方法,可以抽出到一个单独的工具类里面
    private void before() {
        System.out.println("Before处理");
    }

    private void after() {
        System.out.println("After处理");
    }
}

public class JdkDynamicProxyTest {
    public static void main(String args[]) {
        Subject s = new RealSubject();
        Object obj = Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(),
                new MyIncovationHandler(s));
        Subject subject = (Subject) obj;
        subject.doSomething("kou");
    }
}

     上例中,有一个RealSubject类,实现了接口Subject。InvocationHander接口的实现类MyInvocationHander,重写了invoke方法,在方法体中调用第二个参数method的invoke方法,method的invoke方法需要两个参数,第一个参数是被代理类的实例,第二个参数是参数列表,是invoke方法的第三个参数。这里被代理类的实例不建议直接new出来一个实例然后传进去,而是建议在创建InvocationHander实例时把被代理类实例传进来,这样比较解耦。

      编译上Proxy.newProxyInstance()的返回值类型是Object,但其实运行时类型是动态代理类的类型($Proxy开头的类),又因为实现了代理类所实现的全部接口,所以可以强转为任意一个所实现的接口类型。这个时候调用该接口的方法,底层就会执行接口实现类对应的实现。

      Proxy的newProxyInstance()方法的第二个参数是接口对应的Class对象组成的数组,而不是被代理类的Class对象。所以说,JDK动态代理建立在接口之上的。那么,如果被代理类没有实现任何接口,就不能用JDK动态代理了,需要用到cglib动态代理。cglib动态代理其实是对被代理类创建一个子类,让这个子类去代理父类,所以要求被代理类不能是final的。

2、Cglib动态代理

需要引入cglib包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

我们需要开发一个代理类,去实现cglib的方法拦截器接口MethodInteceptor,在代理类中自定义一个方法,返回值是被代理类的子类实例。

我们调用这个方法,拿到被代理类子类的实例后,强转为被代理类类型,再调用被增强方法即可。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    private final Enhancer enhancer = new Enhancer();

    private final Object bean;

    public CglibProxy(Object bean) {
        this.bean = bean;
    }

    public Object getProxy() {
        //设置需要创建子类的类
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")) {
            System.out.println("早安~~~");
        } else if (methodName.equals("sleep")) {
            System.out.println("晚安~~~");
        }

        //调用原bean的方法
        return method.invoke(bean, args);
    }

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy(new Student("张三"));
        Student s = (Student) cglibProxy.getProxy();
        s.wakeup();
        s.sleep();
    }
}

class Student {

    public String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    public void wakeup() {
        System.out.println("学生" + name + "早晨醒来啦");
    }

    public void sleep() {
        System.out.println("学生" + name + "晚上睡觉啦");
    }
}

3、Javassist、ByteBuddy就不再阐述了,面试中不会细问,提一嘴即可。

https://segmentfault.com/a/1190000040680716

posted on 2015-08-07 17:41  koushr  阅读(197)  评论(0编辑  收藏  举报

导航