写在前面
相信每当想起有关动态代理的时候大家都会脱口而出的就是:cglib动态 和 JDK 动态代理。
再细一点的话也就是 cglib 动态代理底层使用的是继承,JDK 动态代理使用的实现。
那么,为什么 JDK 动态代理一定要是实现接口的形式?使用继承不行吗?
不知道诸君有没有想过这个问题?
下面就开始解答该问题:
准备环境
为了解答该问题我们首先来构建一个程序复现该问题。
现在就基于 spring V5.0.7.RELEASE 构建一个 maven 实例,项目名称随意!
包名:com.mingrn.proxy
在 pom 文件中引入如下依赖:
<dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-framework-bom</artifactId><version>5.0.7.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency></dependencies>
现在来看下项目结构:
.└── com.mingrn.proxy├── App.java├── aop│ └── AspectProxy.java├── config│ └── AppConfig.java└── service├── UserService.java└── impl└── UserServiceImpl.java
- UserService:
public interface UserService {List find();}
- UserServiceImpl:
@Service("userService")public class UserServiceImpl implements UserService {@Overridepublic List find() {System.out.println("find");return null;}}
- AppConfig:
@Configuration@ComponentScan("com.mingrn.proxy")@EnableAspectJAutoProxy(proxyTargetClass = false)public class AppConfig {}
- AspectProxy:
@Component@Aspectpublic 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 没什么问题!
beforefind
现在开始进行问题复现:
复现问题
我们知道,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("/<path>/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 无法利用继承来为目标对象生产代理对象。
最后,以后如果再由面试官问题该问题即可将上面这句话砸他脸上,狠狠的那种!
完结,撒花~
