面试官: 你了解 Spring AOP 吗?
我: 了解
面试官: 那你知道 Spring AOP 的实现原理是什么吗?
我: 动态代理
面试官:那 Java 的代理有几种不同的方式, 他们有什么区别呢?
我:不就是 Java 动态代理吗?
面试官: 出门右拐,不送, 谢谢
开篇
上面的面试场景是不是特别熟悉, 不瞒大家说,我就是其中的一员。动态代理到底是什么?网上也是各种说法不一, 直到我第三次学习了设计模式--代理模式。我才恍然大悟,这不就是 Spring AOP 的动态代理吗? 为什么这么说呢,请听我慢慢道来。
在《设计模式》这本说中对代理模式的定义是“为另一个对象提供一个替身或占位符,以控制对这个对象的访问”, 我学习极客时间的小争哥的《设计模式专栏》时, 对代理模式的定义是:“在不改变原始类的代码的情况下,通过引入代理类来给原始类附加功能”。所以通过定义我们可以看出这不就是和我们在写 Spring AOP 是干的事情是一样的吗, 我们会在 AOP 中进行权限校验, 也会在 AOP 中打印日志。这不就是代理模式的典型吗。所以掌握了代理模式的是想原理, 你就掌握了 Spring AOP 的实现原理。那代理模式有几种实现方式呢?对, 你说对了, 静态代理和动态代理,那二者有什么区别呢?让我们一起学习下。
为了说明不同的代理方式的不同实现, 我们先定义一个 UserServiceImpl 类,且该类实现接口 UserService, 具体代码如下:
public interface UserService {
String createUser(String username);
}
复制代码
public class UserServiceImpl implements UserService {
@Override
public String createUser(String username) {
return "[create user]: " + username;
}
}
复制代码
Java 静态代理
静态代理是通过对被代理类的方法进行包装改写的方式完成的, 主要的实现方式是实现接口或继承的方式实现, 下面我们就看一下两种方式的实现有啥不同的。
实现方式一: 实现接口
public class InterfaceStaticProxy implements UserService {
private final UserService userService;
public InterfaceStaticProxy(UserService echoService) {
this.userService = echoService;
}
@Override
public String createUser(String username) {
long startTime = System.currentTimeMillis();
String result = userService.createUser(username);
long costTime = System.currentTimeMillis() - startTime;
System.out.println("基于接口实现的静态代理, 创建用户耗时:" + costTime + " ms.");
return result;
}
}
复制代码
实现方式二:继承
基于接口的实现方式适用于有接口的情况下, 如果我们使用的第三方库,而要代理的对象并没有定义接口则无法使用, 需要通过继承类来实现。
public class ExtendStaticProxy extends UserServiceImpl {
private final UserServiceImpl userService;
public ExtendStaticProxy(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public String createUser(String username) {
long startTime = System.currentTimeMillis();
String result = super.createUser(username);
long costTime = System.currentTimeMillis() - startTime;
System.out.println("基于继承实现的静态代理, 创建用户耗时:"+ costTime +"ms");
return result;
}
}
复制代码
以上两种方式的具体使用方式如下:
public class StaticProxyDemo {
public static void main(String[] args) {
InterfaceStaticProxy interfaceStaticProxy = new InterfaceStaticProxy(new UserServiceImpl());
String userOne = interfaceStaticProxy.createUser("user one");
System.out.println(userOne);
ExtendStaticProxy extendStaticProxy = new ExtendStaticProxy(new UserServiceImpl());
String userTwo = extendStaticProxy.createUser("user two");
System.out.println(userTwo);
}
}
复制代码
静态代理存在问题
会发现, 静态代理我们需要将被代理类中的所以方法都实现一边, 并且需要为被代理类格外定义一个类来代理, 这导致如果我们有很多个类需要代理,则会出现类的个数成倍的增长。为了解决这个问题就出现了动态代理。
Java 动态代理
动态代理不需要提前定义代理类,而是在运行时动态的为原始类创建代理类。 根据实现的机制不同,Java 的动态代理主要分为基于反射机制的动态代理(JDK 动态代理)和基于字节码的动态代理(如 cglib, javassist 和 Byte Buddy)。接下来,我们会逐一分析不同的方式实现的动态代理的原理, 并给出简单的 demo。
JDK 动态代理(基于反射原理实现)
原理: 反射 + 接口
针对 JDK 动态代理创建代理对象,需要使用 Proxy 类的 newInstance 方法。这个方法有三个参数:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {}
复制代码
ClassLoader loader: 类加载器,用于定义该代理类。
Class<?>[] interfaces: 一个 Class 对象数组,都是该代理类需要实现的接口。
InvocationHandler h: 一个调用处理器,实现该代理器只有一个方法,无论何时调用代理对象的方法,该方法都会被调用,并根据 Method 和原始调用参数, 调用合适的接口方法。
下面我们就看看 JDK 动态代理的实现, 首先最重要的是要实现 InvocationHandler,具体代码如下:
public class DynamicProxyHandler implements InvocationHandler {
private final Object proxyObject;
public DynamicProxyHandler(Object proxyObject) {
this. = proxyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(proxyObject, args);
long costTime = System.currentTimeMillis() - startTime;
System.out.println("JDK 动态代理实现, 创建用户耗时:" + costTime + "ms");
return result;
}
}
复制代码
JDK 动态代理的具体使用方式如下:
public class JDKDynamicProxy {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
Class<?>[] interfaces = userService.getClass().getInterfaces();
DynamicProxyHandler proxyHandler = new DynamicProxyHandler(userService);
UserService proxyInstance = (UserService)Proxy.newProxyInstance(JDKDynamicProxy.class.getClassLoader(), interfaces, proxyHandler);
String userThree = proxyInstance.createUser("user three");
System.out.println(userThree);
}
}
复制代码
JDK 代理的特性
所有代理类都扩展于 Proxy 类。
所有代理类都覆盖了 Object 类中的方法 toString, equals 和 hashCode.
没有定义代理类的名字, 会生成一个以字符串 $Proxy 开头的类名
对于特定的类加载器和预设的一组接口来说,只能生成一个代理类,也就是说如果使用同一个类加载器和接口数组调用两次 newProxyInstance()方法,只能得到同一个类的两个对象。
代理类只能是 public 和 final
被代理类必须实现了接口
cglib 动态代理(字节码增强)
Github 代码仓库: https://github.com/cglib/cglib
原理: 继承机制
学习完 JDK 动态代理后, 你可能会有一个疑问,既然有了 JDK 的动态代理, 为什么还需要 Cglib 动态代理呢?前面我们总结了 JDK 动态代理的特性时说到被代理的类必须实现了接口, 但是大家都知道,现实怎么会永远遂人愿呢。为了完成对没有实现接口的代理, 我们就需要从字节码提升的的手段, 在运行时进行代理类的创建。
这里为了表明 cglib 动态代理不需要实现接口, 我们重新定义一个 ActivityService, 代码如下:
public class ActivityService {
public String crateActivity(String message) {
return "create activity" + message;
}
}
复制代码
那使用 cglib 实现的动态代理的实现如下:
public class CglibDynamicProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 使用类创建代理
Class<ActivityService> superClass = ActivityService.class;
enhancer.setSuperclass(superClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object source, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
// Source -> CGLIB 子类
// 目标类 -> ActivityService
// 错误使用
// Object result = method.invoke(source, args);
// 正确的方法调用
Object result = methodProxy.invokeSuper(source, args);
long costTime = System.currentTimeMillis() - startTime;
System.out.println("[CGLIB 字节码提升]动态代理, 创建活动耗时:" + costTime + " ms.");
return args[0];
}
});
ActivityService o = (ActivityService)enhancer.create();
String userFour = o.crateActivity("activity one");
System.out.println(userFour);
}
}
复制代码
总结
今天的 Java 代理相关的内容就讲完了, Spring AOP 的实现原理就是通过 JDK 动态代理和 cglib 动态代理来实现的。下次面试官再问题这个问题的时候, 你就不用紧张了, 至少可以战斗 300 回合。
评论