在Java中,动态代理使用的频率是很高的,比如Spring的AOP实现中就使用到了动态代理。作为一名有追求的码农,把这些基础搞清楚也是应该的。动态代理的实现方式有多种,各有特点,从JDK自带的动态代理,到CGLib、Javassist等。通常,动态代理用来给已有的接口实现增加通用的强化逻辑。
概要
本文主要涉及JDK动态代理的相关话题,包含以下内容:
- 什么是JDK动态代理?
- 如何使用JDK动态代理?
- 动态代理是如何实现的?
- JDK动态代理的使用场景和优缺点是什么?
什么是动态代理
所谓动态代理是相对于静态代理而言的。静态代理需要从代码层面为每种需要代理的类编写静态代理类,使用起来很不灵活。如果代理的类方法很多,会让你崩溃。而动态代理则更加灵活,它会在代码运行时或者编译时自动封装被代理类,大大降低编码量。
如何使用JDK动态代理
JDK为我们提供了一套自动生成代理Class的机制,以此来实现动态代理。JDK首先定义了接口InvocationHandler,这个接口有两个重要的作用。
- 该接口唯一的方法提供了强化接口功能的入口。方法定义如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
在此方法中,强化功能的逻辑被定义。比如:在某个方法调用前统一打印日志等。一旦调用该方法,被代理对象的功能和强化功能都会被实现。实现该接口的实现类中,在创建对象时,是需要传入原始的被代理对象的,以便在invoke方法中触发被代理对象的实现逻辑。 - 为JDK自动生成的代理类提供统一的调用。也就是说,自动生成的代理类中,是调用这个接口的方法真正触发了接口本身功能及其强化功能。实际该接口隔离了动态代理类对被代理对象的耦合,这样和动态代理类耦合的仅仅是被代理的接口(可能是多个接口)和功能强化接口InvocationHandler。
实例
- 定义两个服务接口。
public interface UserService {
UserInfo getUserById(int id);
String getUserNameById(int id);
}
public interface SchoolService {
String getSchoolName();
}
- 服务接口的实现类,注意这里实现了上面两个接口。
public class UserServiceImpl implements UserService, SchoolService {
// 用户信息列表
static final List<UserInfo> USERS = new ArrayList<UserInfo>();
static {
// 模拟三个用户
USERS.add(createUser(1, 30, "user1", "male"));
USERS.add(createUser(2, 28, "user2", "female"));
USERS.add(createUser(3, 20, "user3", "male"));
}
static private UserInfo createUser(int id, int age, String name, String sex) {
UserInfo info = new UserInfo();
info.setId(id);
info.setAge(age);
info.setName(name);
info.setSex(sex);
return info;
}
public UserInfo getUserById(int id) {
for (UserInfo info : USERS) {
if (info.getId() == id) {
System.out.println(info.toString());
return info;
}
}
return null;
}
public String getUserNameById(int id) {
UserInfo userInfo = getUserById(id);
if (userInfo != null) {
return userInfo.getName();
}
return "User id is invalid!";
}
public String getSchoolName() {
System.out.println("QingHua University");
return "QingHua University";
}
}
- 功能强化类
/**
* 使用JDK的动态代理
*/
public class UserServiceJdkProxy implements InvocationHandler {
// 被代理对象,这里就是UserServiceImpl对象。
private Object target;
// 通过构造函数传入被代理对象
public UserServiceJdkProxy(Object target) {
this.target = target;
}
// proxy是自动生成的代理类对象;
// method是代理的接口方法,调用哪个就传入哪个;由于这里每个接口方法都会被传入,所以如果只针对某个方法做强化,就需要根据方法名区分。比如,本例中只对方法getUserById做强化,在其调用前做before(),调用后做after()。
// args是方法参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if ("getUserById".equals(method.getName())) {
before();//功能强化
result = method.invoke(target, args);
after();//功能强化
} else {
result = method.invoke(target, args);
}
return result;
}
// 方法调用前的强化逻辑
private void before() {
System.out.println("before");
}
// 方法调用后的强化逻辑
private void after() {
System.out.println("after");
}
}
- 客户端代码
public class MainApp {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
userService.getClass().getInterfaces(),
new UserServiceJdkProxy(userService));
userServiceProxy.getUserById(1);
SchoolService schoolService = (SchoolService) userServiceProxy;
schoolService.getSchoolName();
}
}
这里,Proxy.newProxyInstance方法是关键,该方法中会动态生成代理类$Proxy0,并实例化。userServiceProxy实际就是这个动态代理类对象。代理类同时实现了被代理对象实现的两个接口,所以可以强制转换成UserService和SchoolService。类似如下的定义:
public class $Proxy0 implements UserService, SchoolService {
...略...
}
看来,如果要想知道动态代理的实现机制,研究Proxy类是关键。
动态代理是如何实现的
JDK实际是定义了一套动态代理的套路给我们,有了这个套路,我们就只需要实现InvocationHandler接口即可。下面来说说这个套路。
- 首先我们实现接口InvocationHandler,其中定义了我们的强化逻辑;
- 然后,根据ClassLoader、被代理类实现的接口数组(可能是多个)以及实现了接口InvocationHandler的对象来生成统一的代理类$Proxy0。这正是Proxy.newProxyInstance方法的三个参数。
- 最终返回给客户端的就是JDK自动生成的动态类对象,它代理了原始实现类,加上了强化逻辑。
自动生成的动态代理类$Proxy0源码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.ws.springframework.aop.model.UserInfo;
import com.ws.springframework.aop.service.SchoolService;
import com.ws.springframework.aop.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserService, SchoolService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m5;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String getUserNameById(int var1) throws {
try {
return (String)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String getSchoolName() throws {
try {
return (String)super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final UserInfo getUserById(int var1) throws {
try {
return (UserInfo)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.ws.springframework.aop.service.UserService").getMethod("getUserNameById", Integer.TYPE);
m5 = Class.forName("com.ws.springframework.aop.service.SchoolService").getMethod("getSchoolName");
m3 = Class.forName("com.ws.springframework.aop.service.UserService").getMethod("getUserById", Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
这里有几点需要说明下:
- 除了被代理对象的接口方法之外,还有Object类下的三个函数也可能同时被强化。(这里如果没有“getUserById”的限制,那么所有的方法都会被强化)
- 代理类的基本逻辑就是遍历被代理对象所实现接口的所有method(外加Object的三个方法),自动生成代理类的Method。这些Method中统一调用InvocationHandler接口的方法,以触发强化后的接口逻辑。
- 从代理类的定义可以看出来,因为Java不支持多重继承,所以只能代理实现接口的实现类。
- Proxy中封装了生成代理class的逻辑。具体在ProxyClassFactory.apply方法中,最终的class文件是在ProxyGenerator.generateProxyClass中生成,有兴趣的可以去研究下。提醒下,ProxyClassFactory实现了BiFunction,其基本语意是输入ClassLoader、Class数组(接口数组),生成Class文件。
JDK动态代理的使用场景和优缺点
如果被代理类实现了接口,我们就使用JDK自带的代理机制。
- 优点:不再依赖其他的类库,比如:CGlib等。据说,随着JDK版本的升级,JDK动态代理的效率越来越好,甚至高于CGlib的方式。
- 缺点:只能代理实现了接口的类。
网友评论