Java 动态代理
github:动态代理
1 普通代理
代理模式是经典的设计模式,主要是:在客户不能够直接访问到目标对象的时候,设计一个代理对象(该代理对象可以直接访问真实对象),客户借助于代理对象去访问真实对象;或者是在调用目标方法的时候,想添加一些额外的操作,但是有不想改变原有的业务逻辑。代理模式实际上屏蔽了内部访问的细节,客户不管你内不是如何运行的,只要返回给我最终的结果即可。代理模式应用还是非常广泛的,Spring AOP的基础就是代理模式,RPC的基础之一也是代理模式。
经典代理模式:有一个真实类,代理类,分别表示客户想要真正要访问的类和模拟客户访问真实类的类。他们都实现于同一个接口。
例子:
定义一个接口:
public interface ObjectInterface {
void operation();
}
定义真实类和代理类,都实现接口ObjectInterface:
//真实类
public class RealObject implements ObjectInterface{
@Override
public void operation(){
System.out.println("真实对象执行的真实操作");
}
}
//代理类
public class ProxyObject implements ObjectInterface{
//代理类中会持有一个代表真实类的引用
private ObjectInterface realObject;
public ProxyObject(ObjectInterface realObject){
this.realObject = realObject;
}
@Override
public void operation() {
//他会调用真实类中的方法
realObject.operation();
}
}
用于测试:
public class Test {
public static void main(String[] args) {
ObjectInterface realObject = new RealObject();
ObjectInterface object = new ProxyObject(realObject);
object.operation();
}
}
代理模式可以解决一些问题,如想在调用目标方法的前后,织入一些横切逻辑,那么我们就可以在代理类中调用方法前后将这些逻辑加进去,这样的话,在不改变原有业务逻辑的基础上,就可以对目标类就行扩展,非常方便,Spring AOP就是基于代理模式实现的。
普通代理模式虽然很经典模式,但是存在一个问题就是不具有普适性,也就是说这个代理类仅仅能够代理这一个类,它与真实类存在紧耦合关系,如果我想为另外一个类提供一个代理类,就不得不重新建立一个新的代理类。为了解决这个问题,人们提出了动态代理模式,所谓动态代理,就是说这一个代理类可以代理多个真实对象,实现了代理类的可复用性。动态代理目前主流的的有jdk动态代理和CGLib动态代理。
2 JDK动态代理
定义一个接口,和真实类。JDK动态代理不必我们去创建代理类。
//接口
public interface ObjectInterface {
void operation();
}
//真实类
public class RealObject implements ObjectInterface{
@Override
public void operation(){
System.out.println("真实对象执行的真实操作");
}
}
如果我们想在调用真实方法之前织入一些横切逻辑的话,那么我们可以编写一个处理器,专门负责这些事情,jdk动态代理为我们提供了InvocationHandler接口,只要实现这个接口,并重写其中的invoke方法,即可加入我们的横切逻辑:
public class DynamicHandler implements InvocationHandler {
private ObjectInterface object;
public DynamicHandler(){}
public DynamicHandler(ObjectInterface object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 动态代理类的引用proxy,通常情况下不需要它。
* 但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,
* 如方法列表,annotation等。
*/
//调用真实对象的方法,object代表真实对象,args表示代用方法的参数,method代表要调用的真实对象的方法
Object result = method.invoke(object,args);
return result;
}
}
测试jdk动态代理:
public class Test {
public static void main(String[] args) {
ObjectInterface object = new RealObject();
/**
* 利用Proxy的静态方法newProxyInstance创建一个代理类,
* 该方法有三个参数:
* ClassLoader loader 指定代表动态代理类由哪个类加载器加载
* Class<?>[] interfaces 指定动态代理类要实现的接口,可以是多个
* InvocationHandler h,代表动态处理器,但是表示我们打算在方法执行方程中需要执行那些动作
*/
ObjectInterface proxyObject = (ObjectInterface) Proxy.newProxyInstance(ObjectInterface.class.getClassLoader(),
new Class[]{ObjectInterface.class},
new DynamicHandler(object));
proxyObject.operation();
}
}
jdk动态代理解决了一个代理类仅仅能够代理一种接口的限制,提高了代理类的可复用性,我们只需要定义要动态处理器,那么在执行目标方法的时候,就会自动调到InvocationHandler的invoke方法执行,在这里我们可以添加一些自定义的业务逻辑。
但是jdk动态代理也存在一定的问题,就是如果要为目标对象创建一个jdk动态代理,那么就必须为该目标对象创建一个接口,然后让目标类实现于该接口才行,因为jdk动态代理是面向于接口进行代理的,从那个获取代理对象的方法中也可以看出。但是这有时候会很不方便,虽然提倡面向接口编程,但并不是所有的类都需要创建一个接口,因为有时候这些提高编程复杂性,但是如果没有接口,那么jdk动态代理就无法使用,基于此问题,人们提出了CGLib动态代理。
3 CGLib动态代理
CGLib动态代理是基于底层的字节码实现的,具体实现机制,不需要太过于了解。我们只需要知道,使用CGLib动态代理,不需要为目标类创建一个接口。CGLib动态代理会为需要需要代理的类自动创建一个继承此类的子类,然后通过子类类调用目标类的目标方法,再次基础上,顺势织入自己的横切逻辑,非常方便。使用CGLib动态代理需要添加一个架包:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.8</version>
</dependency>
首先创建一个真实类:
public class RealObject{
public void operation(){
System.out.println("真实对象执行的真实操作");
}
}
然后创建一个类似于jdk动态代理的动态处理器,用于织如横切逻辑:
public class CGLibProxy implements MethodInterceptor{
private Object target;
//获取目标对象,在调用该方法的时候,会被拦截,执行下面的intercept方法
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
//织入横切逻辑
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("开始调用方法");
Object result = methodProxy.invoke(target, args);
System.out.println("方法执行结束");
return null;
}
}
测试:
public class Test {
public static void main(String[] args) {
RealObject target = new RealObject();
CGLibProxy cgLibproxy = new CGLibProxy();
ObjectInterface proxy = (ObjectInterface) cgLibproxy.getInstance(target);
proxy.operation();
}
}
CGLib动态代理是基于底层的字节码实现的,具体机制没有看,但是学会使用了CGLib动态代理。CGLib动态代理代理目标对象的时候不需要为真实类创建一个接口,这在有时候是很方便的。
4 总结
动态代理在实际应用中非常广泛,普通代理模式可能就是基础,我们更多的是应用jdk动态代理和CGLib动态代理。这两者的区别在于jdk动态代理需要为真实类创建一个接口,它是面向接口进行代理的;CGLib动态代理不需要为真实类创建一个接口,它实际上会自动为真实类创建一个子类,通过拦截调用方法的方式织入横切逻辑。
Spring AOP的基础就是jdk动态代理和CGLib动态代理;
RPC框架的基础之一是jdk动态代理和CGLib动态代理。
在初步学习dubbo的时候,看到内部会使用动态代理,因此先将动态代理学习一下。