代理模式

143 阅读4分钟

为什么要用代理

代理其实就是一种通过代理对象访问被代理对象的一种设计模式,这样做的好处就是可以在不侵入被代理对象的同时,可以添加一些想要的功能,比如日志等这种横向业务。

1.静态代理

静态代理需要定义接口或者父类,代理对象和目标对象都需要实现或者继承。 所谓静态就是在编译期确定相应的关系。

这里用一个简单的代码例子解释

public interface IUserDao {
    void save();
}
public class UserDao implements IUserDao {
    public void save() {
        //TODO
    }
}
public class UserDaoProxy implements IUserDao {
    //接受目标对象
    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    
    public void save() {
        //TODO
        target.save();
        //TODO
    }
}

测试类

public class App {
    public static void main(String[] args) {
        UserDao target = new UserDao();
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();
    }
}

优点就是逻辑简单。缺点就是用代码的重复换取的逻辑的简单,维护困难。

2. 动态代理

在运行期,通过反射机制创建一个实现了一组给定接口的新类

JDK实现代理只需要使用newProxyInstance方法

static Object newProxyInstance(ClassLoder loder, Class[] interfaces, InvocationHandler handler)

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,用null表示默认类加载器

  • Class [] interfaces:需要实现的接口数组

  • InvocationHandler handler:调用处理器,执行目标对象的方法时,会触发调用处理器的方法,从而把当前执行目标对象的方法作为参数传入

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

/ 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。
Object invoke(Object proxy, Method method, Object[] args)

代码

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInstances(),
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //todo
                    Object returnValue = method.invoke(target, args);
                    //todo
                    return returnValue;
                }
            }
        );
    }
}

测试

pubilc class App {
    public static void main(String[] args) {
    // 目标对象
    IUserDao target = new UserDao();
    // 【原始的类型 class cn.itcast.b_dynamic.UserDao】
    System.out.println(target.getClass());

    // 给目标对象,创建代理对象
    IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
    // class $Proxy0   内存中动态生成的代理对象
    System.out.println(proxy.getClass());

    // 执行方法   【代理对象】
    proxy.save();
}

虽然动态代理不用代理类实现接口,但是目标对象,也就是被代理类必须实现接口,否则不能用动态代理

3.cglib代理

上面的静态代理和动态代理模式都是要求目标对象实现一个接口或者多个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用构建目标对象子类的方式实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的子类.
  • 代理的类不能为final,否则报错;目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

代码

/**
 * 目标对象,没有实现任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

代理工厂

/**
 * Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
     //维护目标对象
    private Object target;

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

   //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
       //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }
 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始事务...");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("提交事务...");
        return returnValue;
    }
}

测试

/**
 * 测试类
 */
public class App {

    @Test
    public void test(){
        //目标对象
        UserDao target = new UserDao();

        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //执行代理对象的方法
        proxy.save();
    }
}

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,用JDK代理 如果目标对象没有实现接口,用Cglib代理。

代理和装饰的区别

装饰基本就是加功能,扩展类的方法;代理更像是限制,控制对这个类的访问。