写在前面
相信每当想起有关动态代理的时候大家都会脱口而出的就是:cglib
动态 和 JDK
动态代理。
再细一点的话也就是 cglib
动态代理底层使用的是继承,JDK
动态代理使用的实现。
那么,为什么 JDK
动态代理一定要是实现接口的形式?使用继承不行吗?或者说为什么 JDK
动态代理只能为接口生成代理对象不能为普通类生成代理对象?
不知道诸君有没有想过这个问题?
下面就开始解答该问题:
准备环境
为了解答该问题我们首先来构建一个程序复现该问题。
现在就基于 spring V5.0.7.RELEASE
构建一个 maven
实例,项目名称随意!
包名:com.mingrn.proxy
在 pom
文件中引入如下依赖:
org.springframework
spring-framework-bom
5.0.7.RELEASE
pom
import
org.springframework
spring-context
org.springframework
spring-aop
org.springframework
spring-aspects
现在来看下项目结构:
.
└── com.mingrn.proxy
├── App.java
├── aop
│ └── AspectProxy.java
├── config
│ └── AppConfig.java
└── service
├── UserService.java
└── impl
└── UserServiceImpl.java
UserService.java:
public interface UserService {
List find();
}
UserServiceImpl.java:
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public List find() {
System.out.println("find");
return null;
}
}
AppConfig.java:
@Configuration
@ComponentScan("com.mingrn.proxy")
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig {
}
AspectProxy.java:
@Component
@Aspect
public class AspectProxy {
@Pointcut("execution(* *com.mingrn.proxy.service.*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before() {
System.out.println("before");
}
}
现在我们再 App.java
中编写测试,保证 AOP
正常运行:
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.find();
}
}
输出结果如下,表示我们的 AOP 没什么问题!
before
find
现在开始进行问题复现:
复现问题
我们知道,SpringAOP
默认使用的是 JDK 动态代理,那么来看下下面这条输出:
UserService userService = (UserService) context.getBean("userService");
System.out.println("userService instanceof UserServiceImpl ? " + (userService instanceof UserServiceImpl));
为了演示需要,特意在UserServiceImpl
上为bean
定义了一个名称,如果直接使用context.getBean(UserServiceImpl.class);
根据类型获取会出现启动报错问题。
不知道诸君觉得输出的是 true
还是 false
?答案利索当然的是 false
了,对不对。因为 JDK
动态代理会为目标对象在内存中生成一个新的实现代理类,这个新的代理类跟我们源代码中的 UserServiceImpl.java
没有任何关系,唯一的关系是:都实现了 UserService
接口类。
那么,将 AppConfig
的代理设置为 true
即 @EnableAspectJAutoProxy(proxyTargetClass = true)
。
现在再次打印相信都知道结果为 true
了,原因就是生成的代理对象与源代码中的 UserServiceImpl.java
是父子关系。
现在我们代理还原为 false
即 @EnableAspectJAutoProxy(proxyTargetClass = false)
再看下如下输出:
UserService userService = (UserService) context.getBean("userService");
System.out.println("userService instanceof Proxy ? " + (userService instanceof Proxy));
诸君现在知道输出结果是什么吗?如果对 JDK 代理有些了解的话就会知道输出结果为:TRUE。那么,为什么会这样?知道该问题后我们自然而然的就知道了:为什么 JDK 动态代理一定是代理接口类!
为什么 JDK 动态代理一定是代理接口类!
接着上面,不知道诸君有没有看过 JDK 动态代理的源码,如果看过了就应该知道 java.lang.reflect.Proxy
类中有一个内部类 ProxyClassFactory
。该内部类有如下一个方法:
public Class> apply(ClassLoader loader, Class>[] interfaces) {}
在该方法中调用了 sun.misc.ProxyGenerator
类的如下方法:
public static byte[] generateProxyClass(final String var0, Class>[] var1, int var2){}
即代码如下:
/**
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
这条代码就是为目标类在内存中生成一个代理类,可以看到返回的类型是 byte[]
。所以,现在要做的就是利用该语句为 UserService
生成一个代理对象,并将二进制数据生成为一个 class
文件!我们只需要利用反编译工具查看一个该代码即可一幕了然了。
现在开始:
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
Class>[] interfaces = new Class[]{UserService.class};
byte[] bytes = ProxyGenerator.generateProxyClass("UserService", interfaces);
File file = new File("//UserService.class");
try {
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后,就会在我们的磁盘中生成一个 UserService.class
字节码文件,我们只需要反编译即可(可以直接利用 IDE 查看,如 IntelliJ IDEA)。打开字节码文件(省略无关内容)如下所示:
public final class UserService extends Proxy implements com.mingrn.proxy.service.UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public UserService(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
// ...
}
public final List find() throws {
// ...
}
public final String toString() throws {
// ...
}
public final int hashCode() throws {
// ...
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.mingrn.proxy.service.UserService").getMethod("find");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们需要关心的仅仅是生成的 UserService
类的继承与实现关系即可:
class UserService extends Proxy implements com.mingrn.proxy.service.UserService {}
现在明白为什么 JDK 动态代理一定是只能为接口生成代理类而不是使用继承了吗?
总结一句话
JDK 动态代理是为接口生成代理对象,该代理对象继承了 JAVA 标准类库 Proxy.java
类并且实现了目标对象。由于 JAVA 遵循单继承多实现原则所以 JDK 无法利用继承来为目标对象生产代理对象。
最后,以后如果再由面试官问题该问题即可将上面这句话砸他脸上,狠狠的那种!