如何分类Java语言?
Java是静态的强类型语言,但是因为提供了类似反射等机制,也具备了部分动态语言的能力。
一、动态代理的简单描述
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似的机制做到的,比如用来包装RPC调用、面向切面的编程(AOP)。
实现动态代理的方式也有很多种,比如JDK自身提供的动态代理,也就是主要利用JDK的反射机制。还有一些更高性能的字节码操作机制,类似ASM、cglib(基于ASM)、Javassist等。
如果熟悉设计模式中的代理模式,代理可以看作是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成的。
通过代理可以让调用者与实现者之间解耦合。比如要进行RPC调用,框架内部的寻址、序列化、反序列化,对于调用者往往是没有太大的意义的,通过代理,可以提供更加友善的界面。
二、举个栗子:Jdk Proxy实现代理
1、JDK Proxy原理
代理还是在基于反射的原理上实现的。
代理类Proxy
和调用处理器接口InvocationHandler
,都位于java.lang.reflect
反射包中。
2、代码实现
package com.newframe.controllers.api;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author:wangdong
* @description:写一个基于动态代理的栗子
*/
public class TestMyDynamicProxy {
public static void main(String[] args) {
HelloImpl hello = new HelloImpl();
//获取我自己实现的带有特殊功能的MyInvocationHandler
//为调用目标Hello创建代理对象
//进而程序就可以使用代理对象间接运行调用目标的逻辑
MyInvocationHandler handler = new MyInvocationHandler(hello);
//构造动态代理代码实例
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),HelloImpl.class.getInterfaces(),handler);
//调用代理方法
proxyHello.sayHello();
}
}
/**
* 定一个Hello的接口
*/
interface Hello{
void sayHello();
}
/**
* 实现这个Hello的接口
*/
class HelloImpl implements Hello{
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
/**
* 定一个我的执行类,调用处理器
* 1、做一个动态代理,首先得实现一个InvocationHandler
*/
class MyInvocationHandler implements InvocationHandler{
//定一个成员变量
private Object target;
//定义一个有参构造
public MyInvocationHandler(Object target) {
this.target = target;
}
//2、重写执行的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target,args);
return result;
}
}
上述JDK Proxy的例子,非常简单地实现了动态代理的构建和代理操作。
3、局限性
从API的设计和实现的角度,上述基于JDK Proxy的动态代理,有他的局限性。因为它是以接口为中心的,相当于添加了一种对于调用者没有太大意义的限制。实例化的是Proxy对象,而不是真正的被调用对象,这在实践中可能会带来各种不便和能力的退化。
如果使用的是Cglib的方式实现动态代理,我们对接口的依赖就会被克服了。
三、Cglib动态代理的分析
1、基于cglib框架的优势
Jdk Proxy。有的时候调用目标可能不便于实现额外的接口,从某种角度看,限定调用者必须实现接口,才可以使用动态代理是有些侵入性的。
类似于cglib动态代理就没有这种限制。只操作我们关心的类,而不必为其他相关类增加工作量。
高性能
如果从性能角度讲。有人曾经得出结论说JDK Proxy比Cglib或者Javassist慢几十倍。坦白说,经过Jdk自身的不断发展,在典型的场景下可以提供对等的性能水平,数量级别的巨大差距基本上是广泛不存在的。
而且反射机制性能在现代JDK中,自身已经得到了极大的改进和优化。同时JDK很多功能并不完全是反射,同样使用了ASM进行字节码操作。
2、认识分析Spring框架的Cglib包
这个是Spring框架自身有的。这个也可以,但是我们经常用的还是真正的cglib包,见下一步
。
位于这个包下package org.springframework.cglib.proxy
,比较常用的几个类:Enhancer、InvocationHandler、MethodInterceptor
关于这个Enhancer
可以理解为一个类增强器,他和Jdk Proxy
不同的是它允许为非接口的类创建一个动态代理的对象。
它主要有两种回调的方式:InvocationHandler
和MethodInterceptor。
官方文档API地址:Enhancer
3、认识真正的Cglib包
如果你是Maven
项目,你需要导入一个Cglib包。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.8</version>
</dependency>
看一下真正的Cglib包:
同样,和Spring框架中的cglib包一样。有常用的类和接口。
关于这个Enhancer
可以理解为一个类增强器,他和Jdk Proxy
不同的是它允许为非接口的类创建一个动态代理的对象。
它主要有两种回调的方式:InvocationHandler
和MethodInterceptor。
四、cglib基于MethodInterceptor的实现
1、实现代码:
package com.newframe.controllers.api;
/**
* @author:wangdong
* @description:基于cglib动态代理的实例
* 当然你可以将不同的写在不同的包中,我这里为了方便就都写在了一个里面
*/
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 定义一个Persion类
*/
class Persion{
//定义几个方法
public void eat(){
System.out.println("我叫小明,我在吃饭呢!");
}
public void study(){
System.out.println("我是小明,我正在学习呢,请不要打扰我");
}
}
/**
* 定一个我的拦截器实现方法拦截器MethodInterceptor
*/
class MyInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("动态代理开始,我现在正要开始进行方法拦截了!");
//Note: 此处一定要使用proxy的invokeSuper方法来调用目标类的方法
//methodProxy.invoke(o,objects);报错:Exception in thread "main" java.lang.StackOverflowError
methodProxy.invokeSuper(o,objects);
System.out.println("动态代理结束,我已经完成方法拦截了!");
return null;
}
}
public class CglibInterceptorTest {
public static void main(String[] args) {
//实例化一个增强器
Enhancer enhancer = new Enhancer();
//设置目标类
enhancer.setSuperclass(Persion.class);
//设置拦截对象
enhancer.setCallback(new MyInterceptor());
//生成代理类并返回一个实例
Persion persion = (Persion) enhancer.create();
persion.eat();
persion.study();
}
}
2、输出结果
动态代理开始,我现在正要开始进行方法拦截了!
我叫小明,我在吃饭呢!
动态代理结束,我已经完成方法拦截了!
动态代理开始,我现在正要开始进行方法拦截了!
我是小明,我正在学习呢,请不要打扰我
动态代理结束,我已经完成方法拦截了!
五、cglib基于InvocationHandler的实现
这个和JDK Proxy不同的是,它既可以动态代理处理纯类,也可以处理实现了接口的类。
1、实现代码:
package com.newframe.controllers.api;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;
import java.lang.reflect.Method;
/**
* 定义一个接口类
*/
interface Animal{
void run();
}
/**
* 定义一个狗类,实现了Animal接口
*/
class Dog implements Animal{
@Override
public void run(){
System.out.println("我是一只狗,我正在快乐的奔跑呢!");
}
}
/**
* 定义一个猫类
*/
class Cat {
public void eat(){
System.out.println("我是一只猫,我正在玩游戏!");
}
}
class MyHandler implements InvocationHandler{
private Object target;
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//在这边可以对一个类中的多个方法,做不同的操作
//符合Aop面向切面编程的思想
String m = method.getName();
System.out.println(m + "方法执行开始");
System.out.println("给" + m + "方法加个日志");
System.out.println("给" + m + "方法加个拦截器");
Object result = method.invoke(target,objects);
return result;
}
}
/**
* @author:wangdong
* @description:cglib基于InvocationHandler的实现
*/
public class CglibInvocationHandlerTest {
public static void main(String[] args) {
//动态代理纯Class类
Cat cat = new Cat();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Cat.class);
//这个是关键,需要加载类
enhancer.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());
MyHandler myHandler = new MyHandler(cat);
enhancer.setCallback(myHandler);
Cat c = (Cat) enhancer.create();
c.eat();
//动态代理处理,实现了接口的实现类
Dog dog = new Dog();
Enhancer en = new Enhancer();
en.setSuperclass(Dog.class);
en.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());
MyHandler handler = new MyHandler(dog);
en.setCallback(handler);
Dog d = (Dog) en.create();
d.run();
}
}
2、输出结果
我是一只猫,我正在玩游戏!
我是一只狗,我正在快乐的奔跑呢!
六、结语
相信通过自己写一次代码,对整个动态代理应该有了非常清晰的认识了吧。
凡事光看是不足够的,自己动手一次,各种查询资料。能够有非常深入的思考。
最后附一个知乎大神讨论较多的关于动态代理的帖子:
动态代理