一、代理模式
代理模式是一种常用的设计模式。代理模式为其对象提供了一种代理以控制对这个对象的访问。代理模式可以将主要业务与次要业务进行松耦合的组装。
根据代理类的创建时机和创建方式的不同,可以将其分为静态代理和动态代理两种形式:
- 在程序运行前就已经存在的编译好的代理类是为静态代理,
- 在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能是为动态代理。
静态代理是代理模式的实现方式之一,是相对于动态代理而言的。所谓静态代理是指,在程序运行前,由程序员创建或特定工具类自动生成源代码并对其编译生成.class文件。代理模式的介绍与静态代理的实现详见我的另一篇博文:智能的代理模式,让职责变得更加清晰!本文主要介绍动态代理。
二、动态代理
在动态代理中,代理类是在运行时期生成的。因此,相比静态代理,动态代理可以很方便地对委托类的相关方法进行统一增强处理,如添加方法调用次数、添加日志功能等。
除此之外,对代理模式而言,一般来说,具体主题类与其代理类是一一对应的,这也是静态代理的特点。但是,也存在这样的情况:有N个主题类,但是代理类中的“预处理、后处理”都是相同的,仅仅是调用主题不同。那么,若采用静态代理,必然需要手动创建N个代理类,这显然让人相当不爽。动态代理则可以简单地为各个主题类分别生成代理类,共享“预处理,后处理”功能,这样可以大大减小程序规模,这也是动态代理的一大亮点。
在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。我们一般所熟知的Spring AOP原理就是基于动态代理实现的。
Java动态代理的实现分为两种
- 基于JDK的动态代理
- 基于CGLib的动态代理
三、基于JDK的动态代理
基于JDK的动态代理是使用JRE提供给我们的类库,可以直接使用,不依赖第三方。
因为JDK动态代理生成的class文件已经继承了Proxy,而Java是单继承的,所以是基于JDK动态代理是基于接口的。
基于JDK的动态代理由两个重要的成员组成,分别是Proxy、InvocationHandler。
- Proxy:是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例。
- InvocationHandler:每个动态代理实例都有一个关联的InvocationHandler,在代理实例上调用方法是,方法调用将被转发到InvocationHandler的invoke方法。
代理过程的操作步骤如下:
(1)创建一个接口
public interface Subject {
void hello(String param);
}
(2)实现接口
public class SubjectImpl implements Subject {
@Override
public void hello(String param) {
System.out.println("Hello " + param) ;
}
}
(3)创建代理类,并根据业务需要重写invoke方法
public class SubjectProxy implements InvocationHandler {
private Subject subject;
public SubjectProxy(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---begin---");
//利用反射调用类里面的实际方法
Object invoke = method.invoke(subject, args);
System.out.println("---end---");
return invoke;
}
}
(4)编写代理类实际的调用,利用Proxy类创建被代理类
public class Client {
public static void main(String[] args) {
Subject subject = new SubjectImpl();
InvocationHandler subjectProxy = new SubjectProxy(subject);
Subject proxyInstance = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), subjectProxy);
proxyInstance.hello("world");
}
}
四、基于CGLib的动态代理
基于JDK的动态代理是使用CGLib框架,它是一个高性能的,底层基于 ASM 框架的一个代码生成框架,它完美地解决了基于JDK的动态代理只能为接口方法代理的不足。
CGLIB的实现也有两个重要的成员组成,Enhancer、MethodInterceptor,其实这两个的使用和jdk实现的动态代理的Proxy、InvocationHandler非常相似。
- Enhancer:来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象、对这个对象所有的非final方法的调用都会转发给MethodInterceptor。
- MethodInterceptor:动态代理对象的方法调用都会转发到intercept方法进行增强。
代理过程的操作步骤如下:
(1)引入CGLib的Jar包 点击下载
(2)创建代理类
public class CGSubject {
public void sayHello() {
System.out.println("hello world!");
}
}
(3)实现MethodInterceptor接口,对方法进行拦截处理
public class HelloInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("---begin---");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("---end---");
return o1;
}
}
(4)编写代理类实际的调用,利用Enhancer来生产被代理类
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CGSubject.class);
enhancer.setCallback(new HelloInterceptor());
CGSubject cgSubject = (CGSubject) enhancer.create();
cgSubject.sayHello();
}
}
总结:两种代理方法的区别:
基于JDK的动态代理:
- Java原声支持的、不需要任何外部依赖
- 只能基于接口进行代理
基于CGLib的动态代理:
- 无论目标对象有没有实现接口都可以代理
- 无法处理final的情况(final修饰的方法不能被覆写)
除此之外,基于JDK的动态代理创建对象的速度远大于基于CGLib的动态代理,这是由于CGLib创建对象时需要操作字节码。基于CGLib的动态代理执行速度略大于JDK的动态代理,所以它比较适合单例模式。另外由于CGLib的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。