Java高级——Java中的代理模式

185 阅读5分钟

一、代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

常用来代理类通过调用被代理类的相关方法,并对相关方法进行增强。加入一些非业务性代码,比如事务、日志、报警发邮件等操作。

代理模式可以理解为中间商:顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。 所以,代理就有一种中间人的味道。

代理模式的关键点:代理对象与目标对象,代理对象是对目标对象的扩展,并会调用目标对象

代理技术是AOP(面向切面编程)技术实现的关键技术

二、Java中的代理实现方式

Java中代理分为静态代理、动态代理和Cglib代理
首先说明各个代理的特点:

  • 静态代理
    • 代理类通过实现与目标对象相同的接口,并在类中维护一个代理对象。通过构造器塞入目标对象,赋值给代理对象,进而执行代理对象实现的接口方法,并实现前拦截,后拦截等所需的业务功能
    • 优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截
    • 缺点:因为代理对象,需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护
  • 动态代理
    • 动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理
    • 优点:代理对象无需实现接口,免去了编写很多代理类的烦恼,同时接口增加方法也无需再维护目标对象和代理对象,只需在事件处理器中添加对方法的判断即可
    • 缺点:代理对象不需要实现接口,但是目标对象一定要实现接口,否则无法使用JDK动态代理
  • Cglib代理
    • 运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,从而在调用方法之前做处理
    • 优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快
    • 缺点:对于被代理类中的final方法,无法进行代理,因为final修饰的方法无法被子类重写

静态代理

模拟一下用户下单的业务场景

//下单接口
public interface OrderService {
    public void order();
}
//用户下单类
public class UserAction implements OrderService {
    @Override
    public void order() {
        System.out.println("用户下单操作...");
    }
}
//用户下单代理类
public class UserAction implements OrderService {
    @Override
    public void order() {
        System.out.println("用户下单操作...");
    }
}

测试:

分析:

  • 静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
  • 静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

动态代理(jdk代理)

OrderService和UserAction不变,添加UserActionProxyFactory

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserActionProxyFactory {
    private Object target;

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

    public Object getProxyInstance() {
        Object obj = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("判断用户是否登录");
                        Object invoke = method.invoke(target);
                        System.out.println("记录用户下单操作");
                        return invoke;
                    }
                }
        );
        return obj;
    }
}

测试:

分析:

  • 代理对象不需要实现接口
  • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
  • 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

动态代理(Cglib代理)

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

创建Login、LoginProxyFactory

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoginProxyFactory implements MethodInterceptor {

    private Object target;

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

    /**
     * 给目标对象创建一个代理对象
     * @return
     */
    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("验证用户名密码是否正确...");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("登录成功跳转页面...");
        return returnValue;
    }
}

测试:

分析:

  • Cglib代理不需要目标对象实现接口,它是在内存中动态构建一个子类对象从而实现对目标对象功能的扩展
  • 代理的类不能为final,否则报错
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

总结

理解上述Java代理后,也就明白Spring AOP的代理实现模式,即加入Spring中的target是接口的实现时,就使用JDK动态代理,否是就使用Cglib代理。Spring也可以通过<aop:config proxy-target-class="true">强制使用Cglib代理,使用Java字节码编辑类库ASM操作字节码来实现,直接以二进制形式动态地生成 stub 类或其他代理类,性能比JDK更强。