动态代理
定义:
现在有很多微商代理,厂家委托代理销售其产品。我们可以将微商代理和生产厂家进一步抽象,前者抽象为代理类,后者抽象成委托类。而我们买商品时,大多是不需要知道生产厂家究竟是谁,也就是说生产厂家(委托类)对我们来说是不可见的。
优点:
- 可以隐藏委托类中的具体实现
- 可以实现客户端与委托类之间的解耦,在不修改委托类的情况下做一些额外处理。
什么是静态代理?
若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。通常情况下静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。下面我们用FactoryImpl类代表生产厂家,MicroBusiness类代表微商代理,来介绍下静态代理的简单实现。
代码如下:
/**
* 生产厂家接口
*/
public interface IFactoryService {
void sell();
}
/**
* 生产厂家实现
*/
@Service
public class FactoryImpl implements IFactoryService {
@Override
public void sell() {
System.out.println("In sell method");
}
}
/**
* 微商代理类
*/
public class MicroBusiness implements IFactoryService {
private IFactoryService factoryService;
public MicroBusiness( IFactoryService factoryService) {
this.factoryService = factoryService;
}
@Override
public void sell() {
factoryService.sell();
}
}
需求变更
厂商生产出的产品只供应学生购买。
思路:
在不改变委托类原代码情况下实现此功能,需添加一个代理类,在代理类中加一个过滤条件,实现客户端与委托类之间的解耦。代码如下:
/**
* 微商代理类
*/
public class MicroBusiness implements IFactoryService {
private IFactoryService factoryService;
public MicroBusiness( IFactoryService factoryService) {
this.factoryService = factoryService;
}
@Override
public void sell() {
if(isStudent){ //是否是学生
factoryService.sell();
}
}
}
什么是动态代理?
代理类在程序运行时创建的代理方式被称为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
现在有个需求要求在销售产品之前记录产品信息,在销售之后自动打印订单,先来看下静态代理怎么实现的,代码如下。
静态代理实现:
/**
* 代理类
*/
public class MicroBusiness implements IFactoryService {
private IFactoryService factoryService;
public MicroBusiness( IFactoryService factoryService) {
this.factoryService = factoryService;
}
@Override
public void sell() {
System.out.println("记录产品信息");
factoryService.sell();
System.out.println("打印产品订单");
}
}
从以上代码中我们可以了解到,通过静态代理实现我们的需求,需要我们在每个方法中都添加相应的逻辑,这里只存在一个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。
动态代理实现:
在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出"记录产品信息"。下面我们来一步一步具体实现它。
中介类
上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:
/**
* 中介类
*/
@Service
public class DynamicProxy implements InvocationHandler {
//obj为委托类对象;
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("记录产品信息");
Object result = method.invoke(object, args);
System.out.println("打印产品订单");
return result;
}
}
从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法,看到这里是不是觉得似曾相识?
通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?
实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;
代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。
也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。
客户端调用代码如下:
/**
* 客户端
*/
public class Client {
public static void main(String[] args){
//静态代理调用
MicroBusiness microBusiness = new MicroBusiness(new FactoryImpl());
microBusiness.sell();
//动态代理调用
DynamicProxy dynamicProxy = new DynamicProxy(new FactoryImpl());
//获取代理类实例factoryService
IFactoryService factoryService = (IFactoryService)(Proxy.newProxyInstance(IFactoryService.class.getClassLoader(), new Class[] {IFactoryService.class}, dynamicProxy));
//通过代理类对象调用代理类方法,实际上会转到invoke方法调用
factoryService.sell();
}
}
在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
方法的三个参数介绍:
loader:定义了代理类的ClassLoder; interfaces:代理类实现的接口列表 h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例
控制台输出如下:
总结:
上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。