静态代理、JDK代理、CGLIB代理学习笔记

1,614 阅读4分钟

0. 前言

  代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

1. 静态代理

实现

  第一步,写一个接口。

public interface Mobileable {
    void updateMobile(String mobile) throws Exception;
}

  第二步,写一个我们要增强的类并实现Mobileable接口。

public class Person implements Mobileable{
    private String name;
    private String mobile;

    @Override
    public void updateMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }
}

  第三步,写一个代理类,也实现Mobileable接口并把要增强的对象作为成员变量。

public class PersonProxy implements Mobileable{

    private Person person;
    Pattern mobilePattern = Pattern.compile("1\\d{10}");

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void updateMobile(String mobile) throws Exception {
        System.out.println("执行目标方法之前,模拟开始事务 ...");
        if(mobile == null || "".equals(mobile)){
            throw new Exception("电话号码为空");
        }
        Matcher m = mobilePattern.matcher(mobile);
        if (m.matches()){
            System.out.println("电话号码格式正确");
        }else{
            throw new Exception("电话号码格式错误");
        }
// 执行目标方法,并保存目标方法执行后的返回值
        person.updateMobile(mobile);
        System.out.println("执行目标方法之后,模拟结束事务 ...");
    }
}

  第四步,测试。

public class TestCase {
    public static void main(String[] args){
        Person person = new Person();
        PersonProxy personProxy = new PersonProxy(person);
        person.setName("123");
        try {
//            personProxy.updateMobile(null);
//            personProxy.updateMobile("123");
            personProxy.updateMobile("12345678901");
        } catch (Exception e) {
        e.printStackTrace();
        }
        System.out.println(person.getName());
        System.out.println(person.getMobile());
    }
}

优点

  1. 可以在不修改目标对象的前提下扩展目标对象的功能。
  2. 静态代理在编译时产生class字节码文件,可以直接使用,效率高。

缺点

  1. 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  2. 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

2. JDK代理

  JDK代理又称接口代理。JDK代理主要是通过JDK自带的API实现的。用的是reflect包下的Proxy类的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法。
  利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

实现

  第一步,写一个接口。

public interface IAnimal {
    void eat(String food);
    void speak(String language);
}

  第二步,实现这个接口。

public class Cat implements IAnimal{
    @Override
    public void eat(String food) {
        System.out.println(this.getClass().getSimpleName() + " eats " + food);
    }

    @Override
    public void speak(String language) {
        System.out.println(this.getClass().getSimpleName() + " speaks " + language);
    }
}
public class Dog implements IAnimal{
    @Override
    public void eat(String food) {
        System.out.println(this.getClass().getSimpleName() + " eats " + food);
    }

    @Override
    public void speak(String language) {
        System.out.println(this.getClass().getSimpleName() + " speaks " + language);
    }
}

  第三步,实现一个自定义拦截器,实现InvocationHandler接口。

public class AnimalActionHandler implements InvocationHandler {

    private Object object;

    public AnimalActionHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(object.getClass().getSimpleName()+"."+method.getName()+"()执行之前");
        Object invoke = method.invoke(object, args);
        System.out.println(object.getClass().getSimpleName()+"."+method.getName()+"()执行之后");
        return invoke;
    }
}

  第四步,测试。

public class TestCase {
    public static void main(String[] args) {
        IAnimal cat = (IAnimal) Proxy.newProxyInstance(Cat.class.getClassLoader(),
                new Class[]{IAnimal.class},
                new AnimalActionHandler(new Cat()));
        IAnimal dog = (IAnimal) Proxy.newProxyInstance(Dog.class.getClassLoader(),
                new Class[]{IAnimal.class},
                new AnimalActionHandler(new Dog()));
        cat.eat("fish");
        cat.speak("miao");
        dog.eat("bone");
        dog.speak("wang");
    }
}

优点

  1. 代理类只需实现InvocationHandler接口。。

缺点

  1. 被代理对象必须要实现接口,否则不能使用JDK代理。

3. CGLIB代理

  CGLIB代理又称子类代理。利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

实现

  第一步,写需要被代理的类。

public class Person {
    private String name;
    private String mobile;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}

  第二步,写一个实现了MethodInterceptor接口的代理类。

public class PersonProxyFactory implements MethodInterceptor{

    private final static String SET_MOBILE = "setMobile(Ljava/lang/String;)V";
    Pattern mobilePattern = Pattern.compile("1\\d{10}");

    private Object target;

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

>     public Object newProxyInstance(){
        Enhancer en = new Enhancer();
// 设置要代理的目标类
        en.setSuperclass(target.getClass());
// 设置要代理的拦截器
        en.setCallback(this);
// 生成代理类的实例
        return (Person) en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String name = methodProxy.getSignature().toString();
        Object rvt;
        if (SET_MOBILE.equals(name)){
            System.out.println("执行目标方法之前,模拟开始事务 ...");
            if(objects[0] == null || "".equals(objects[0])){
                throw new Exception("电话号码为空");
            }
            Matcher m = mobilePattern.matcher(objects[0].toString());
            if (m.matches()){
                System.out.println("电话号码格式正确");
            }else{
                throw new Exception("电话号码格式错误");
            }
// 执行目标方法,并保存目标方法执行后的返回值
            rvt = methodProxy.invokeSuper(o, objects);
            System.out.println("执行目标方法之后,模拟结束事务 ...");
        }else {
// 执行目标方法,并保存目标方法执行后的返回值
            rvt = methodProxy.invokeSuper(o, objects);
        }
        return rvt;
    }
}

  第三步,测试。

public class TestCase {
    public static void main(String[] args) {
        Object o = new Person();
>         PersonProxyFactory personProxyFactory = new PersonProxyFactory(o);
        Person person = (Person) personProxyFactory.newProxyInstance();
> //        person.setName("123");
//        person.setName("yellowdoge");
        person.setMobile(null);
//        person.setMobile("12345678901");
//        System.out.println(person.getName());
        System.out.println(person.getMobile());
//        System.out.println(person.getClass());
    }
}

优点

  1. cglib代理的对象无需实现接口。

缺点

  1. cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
  2. 使用cglib需要引入cglib的jar包,spring3.2以后不需要单独引入。

参考文献: