书接上文,在分析注解的原理时说道:“通过getAnnotation()方法获取一个注解的时候,JDK会通过动态代理生成注解的代理类$Proxy1”,但没有继续分析JDK动态代理,动态代理在各种框架中也经常被使用,比较典型的是Spring AOP,其中有两种实现动态代理的方式,分别是基于JDK的动态代理和基于CGLib的动态代理,本篇先分析基于JDK的动态代理。
【一】实验:实现基于JDK的动态代理
【第一步】定义一个接口和实现类
public interface IPerson {
void sayHello();
}
@Service
public class PersonServiceImpl implements IPerson {
@Override
public void sayHello() {
System.out.println("Hello~~~");
}
}
public class PersonInvocationHandler implements InvocationHandler {
private Object target;
public PersonInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------前置逻辑-------------");
// 执行相应的目标方法
Object result = method.invoke(target,args);
System.out.println("------后置逻辑-------------");
return result;
}
}
【第二步】两种使用JDK动态代理的方式
方式一:分为五个步骤
- 实现InvocationHandler接口
- 获得动态代理类:Proxy.getProxyClass
- 获得代理类的构造方法:getConstructor(InvocationHandler.class)
- 获得代理对象,传入自定义的InvocationHandler
- 通过代理对象调用目标方法
方式二 - 提供了一种封装好的方法:
java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
三个参数代表的含义分别是:
ClassLoader loader:接口的类加载器
Class<?>[] interfaces:接口(可以是一组接口)
InvocationHandler h:自定义的InvocationHandler
下面是两种方式的示例:
public class DynamicProxyClient {
public static void main(String[] args) throws Exception{
// 方式一
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Class proxyClazz = Proxy.getProxyClass(IPerson.class.getClassLoader(),IPerson.class);
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
IPerson iPerson = (IPerson) constructor.newInstance(new PersonInvocationHandler(new PersonServiceImpl()));
iPerson.sayHello();
// 方式二
IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), new Class[]{IPerson.class}, new PersonInvocationHandler(new PersonServiceImpl())); proxyInstance.sayHello();
}
}
综上所述,基于JDK的动态代理的实现方式:实现InvocationHandler接口,重写invoke()方法。
【二】源码分析
首先要说明一点,JDK动态代理只能代理接口,因为代理类本身已经继承了Proxy,而Java不允许多重继承,但是允许实现多个接口。
我们先来看那个封装好的newProxyInstance()方法源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 生成代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造方法,其中constructorParams是InvocationHandler.class
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回代理类
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 组合成为代理类名字
private static final String proxyClassNamePrefix = "$Proxy";
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成代理类文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// native方法,返回代理类
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
依稀记得,当年看《深入理解Java虚拟机时》,第七章有这样一段话,当时懵懵懂懂的我只是把它背了下来,
在类加载的加载阶段,虚拟机完成三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
- 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
- 从网络中获取,典型的应用是 Applet
- 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
- 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
- 从数据库中获取等等
其中的“运行时计算生成 - ProxyGenerator.generateProxyClass”不就在这里吗~~
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
如果继续深入研究,下面的这些方法,基本都是一些IO文件写入的操作,就不粘贴源码了。
sun.misc.ProxyGenerator#generateClassFile
sun.misc.ProxyGenerator#addProxyMethod
......
至此,基于JDK的动态代理的分析就到此为止,其本质上是基于反射机制,在运行时创建代理类。所以下一步的研究对象,理所当然的就是Java的反射机制了。