在程序中,我们要实例化代码有两种方式:
一是直接 new 出来,这种方式称为静态实例化,也是我们代码中用的最多的一种;
另一种则是通过动态代理来创建对象,这种方式在运行期间动态的创建它的实例对象,并且我们可以对目标方法做一些特殊处理。
1、静态代理
在了解动态代理之前,我们先来看一下什么是静态代理。
首先,创建一个接口:
public interface Session {
void send(String message);
}
然后去实现这个接口:
public class DefaultSession implements Session {
@Override
public void send(String message) {
System.out.printf("Send message: %s\n", message);
}
}
手动创建一个代理类:
public class SessionProxy implements Session {
private final Session session;
public SessionProxy(Session session) {
this.session = session;
}
@Override
public void send(String message) {
System.out.println("前置通知");
try {
session.send(message);
} catch (Exception e) {
System.out.println("异常处理");
}
System.out.println("后置通知");
}
}
最后,创建实例并调用:
// 创建实例并调用方法
Session session = new SessionProxy(new DefaultSession());
session.send("Hello World");
// "前置通知"
// "Hello World"
// "异常处理"(如果抛出异常的话)
// "后置通知。"
所谓的静态代理,说白了就是由我们手动创建的实现类,在编译之前就已经存在了,可以直接被编译成对应的.class字节码。
2、动态代理
在 JDK 标准库中提了一个动态代理机制(Dynamic Proxy),可以让我们在运行期间动态创建对应接口的实例,为什么我这里要强调接口呢?因为 JDK 库的动态代理只适用于接口的代理,如果要实现普通类的代理,像是Spring中的AOP一样,则需要使用cglib这个库,后面我们会专门说一下这个库的简单使用方式,这里我们先看一下动态代理的基本原理。
在动态代理中有几个比较重要的类:
- InvocationHandler - 代理的最终实现类(凡是对象的实例化,无论静态还是动态,都会存在一个目标实例对象),也是我们需要着重关注的一个类;
- Proxy - JDK 提供的标准代理对象,通过它可以为我们创建代理后的对象;
- ProxyGenerator - 我们不需要直接操作它,但是我们需要知道动态代理就是是通过它来创建运行时的class字节码的;
使用姿势:
Object instance = Proxy.newProxyInstance(
, // 类加载器
[]>, // 需要被代理的接口
// 调用实现类
);
还是先整一个默认实现:
public class DefaultSessionImpl implements Session {
@Override
public void send(String message) {
System.out.printf("Send message: %s\n", message);
}
}
然后创建一个类去实现InvocationHandler,我们的拦截逻辑主要就在这里面去做实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyInvocationHandler implements InvocationHandler {
private final Session session;
public SessionInvocationHandler(Session session) {
this.session = session;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
Exception exception = null;
System.out.println("前置通知");
try {
if (Object.class.equals(method.getDeclaringClass())) {
result = method.invoke(this, args); // Object 内部的方法
} else {
result = method.invoke(session, args); // 交给默认实现类处理
}
} catch (Exception e) {
exception = e;
System.out.println("统一异常处理");
}
System.out.println("后置通知");
if (exception != null) throw exception;
return result;
}
}
创建代理对象并调用:
// 获取动态代理的实例对象
Session session = (Session) Proxy.newProxyInstance(
Session.class.getClassLoader(), // ClassLoader
new Class>[ Session.class ], // 代理的接口
new ProxyInvocationHandler(new DefaultSessionImpl()) // 代理执行类
);
// 调用实例方法
session.send("Hello"); // 走默认实现类中的方法
session.toString(); // 走代理的对象中的方法
session.hashCode(); // 走代理的对象中的方法
上面就是 JDK 动态代理的基本实现,使用起来也很简单,只需要着重注意InvocationHandler的一个逻辑就好了。如果我们要做像SpringAop那样的前置、后置、环绕处理,则只需要在InvocationHandler的invoke方法中做相应的逻辑处理即可。代码其实不多,但是要理解其中地逻辑和使用方法,并且灵活运用,你也可以合理的运用到项目中去。
3、CGLIB 代理
对于简单的接口代理,我们可以使用 JDK 自带的动态代理,但是如果我们要对普通类做一些代理实现,那我们应该怎么做呢?这时候就需要用到cglib这个库了。
CGLIB 是一个强大的、高性能的代码生成库。采用非常底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强,其被广泛应用于AOP框架(像是Spring)中,用以提供方法拦截操作。CGLIB 是一个托管在 github 的开源项目,其地址为:
https://github.com/cglib/cglib。
在 CGLIB 中有两个比较重要的类:
- Enhancer - CGLIB 用于创建代理类的主要实现;
- MethodInterceptor - 方法拦截回调接口,我们需要在代理实现中去实现此接口(拦截逻辑就主要在这里面实现);
首先,我们来创建一个普通类:
注意:这个类不能是final的,因为 cglib 是通过创建子类的方式去生成对应字节码的,使用了 final 的类无法被继承!
public class Handler {
public void handle() {
System.out.println("handle()");
}
}
然后创建一个代理实现类:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibDynamicProxy implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result = null;
Exception exception = null;
System.out.println("前置通知");
try {
result = proxy.invokeSuper(target, args);
} catch (Exception e) {
exception = e;
System.out.println("统一异常处理");
}
System.out.println("后置通知");
if (exception != null) throw exception;
return result;
}
}
最后来通过Enhancer来创建它的代理类:
// 调试 CGLIB 保存 class 文件到指定目录
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://cglib");
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 构建 CGLIB 的对象实例
Enhancer enhancer = new Enhancer();
enhancer.setUseCache(true);
enhancer.setSuperclass(Handler.class); // 被代理的类的 class(也可以是接口)
enhancer.setCallback(new CglibDynamicProxy()); // 代理实现类
// 创建代理类
Handler handler = (Handler) enhancer.create();
handler.handle();
最后别忘了添加 Maven 坐标:
cglib
cglib
3.3.0
4、那我们应该如何选择?
相对来说 JDK 提供的动态代理使用上比较方便,不需要导入第三方包,且可以随着 JDK 版本的更新而及时调整,但是仅针对接口产生代理类,无法对普通类生效,并且InvocationHandler中的invoke方法使用了反射进行目标方法的调用,在一定程度上会有一些效率上的问题;
而 CGLIB 属于第三包,在使用的时候需要进行导入,它不仅可以对接口进行代理,也可以对普通类进行代理,生成对应的代理类,在调用上可以直接调用代理类中的方法,而无需进行反射调用,效率上相对较高,但生成代理类的过程相对较慢,所以使用CGLIB的时候最好将动态代理放在程序启动过程中,这样不会影响执行的效率。