什么是代理模式
代理模式用于控制和管理访问。
举个例子,在没有经纪人的情况下,导演、编剧、粉丝A、粉丝B都想和演员见面,演员不得不直接和他们沟通:或拒绝、或接受,严重干扰了自己的生活。因此,他为自己找了一个经纪人,现在所有的请求都要通过经纪人才能到达演员,经纪人会对这些请求先进行处理,比如导演编剧可以和演员见面、粉丝不能和演员见面。这个经纪人就相当与代理模式中的保护代理。(保护代理、远程代理、虚拟代理等都属于代理模式)
保护代理的实现方式有两种:静态代理和动态代理。
先体验静态代理
静态代理的实现很简单。以汽车行驶为例,Car 实现了Moveable接口,拥有move方法。
public interface MoveAble {
void move();
}
public class Car implements MoveAble {
@Override
public void move() {
System.out.println("汽车行驶中..");
}
}
现在我希望外界调用car的move方法前后做一些其他的行为(如权限验证、输出日志等),那么,我将这些工作交由car的代理者CarProxy处理,car仍然只处理核心的move方法。
public class CarProxy implements MoveAble{
MoveAble car;
public CarProxy(MoveAble car) {
this.car = car;
}
@Override
public void move() {
System.out.println("权限控制");
car.move();
System.out.println("输出日志");
}
}
有了代理者之后,我们就可以通过CarProxy来执行move方法了,因为Car和carProxy都实现了Moveable接口(接口的作用就是为了保持类型一致)。
public class Test {
public static void main(String[] args) {
Car car = new Car();
CarProxy carProxy = new CarProxy(car);
carProxy.move();
}
}
运行结果
权限控制
汽车行驶中..
输出日志
好了,静态代理就这么简单。
静态代理存在的隐患
我们知道,唯一不变的就是变化。当你春风得意之时,任务又来了:你要为实现了Flyable接口的Dark也实现以上一样的功能(权限控制,输出日志)。那还不简单,我再写一个DarkProxy不就好了。嗯,后来领导又说,你这个权限控制、输出日志做的很不错嘛,现在你把这个项目中大大小小100个类都做个权限控制、输出日志吧??
由此可见,静态代理固然简单明了,但由于在静态代理中,代理对象(CarProxy)和被代理对象(Car)直接绑定(因为我们在CarProxy中直接声明了一个Car的引用),在编译时这种关系就已经确定下来,耦合程度太高,不利于代码复用。因此,我们需要动态代理。
动态代理登场
动态代理的动态指的是什么呢?
动态并非指在运行时才实例化代理对象,而是指在运行时才将代理类创建出来,也就是说,在运行时创建一个不存在的类!正是因为如此,代理类才能和被代理类解耦,在编写代码的时候,我并不关心被代理的对象到底是什么(Car也好、Duck也好,无所谓,在代理类中统统都是Object类型的),我只关心在调用原有的方法前后要做什么。
怎么做呢?
我只要在一个实现了InvocationHandler接口的类(比如叫MyHandler)的invoke方法中写这些需要添加的功能就够了。需要注意的是,MyHandler并不是代理类,MyHandler关注的是原有方法被调用时要做些什么(如权限控制、日志输出等),它是辅助代理的类。而代理类是在运行时通过Proxy.newProxyInstance创建的。
具体实现方法如下:
MyHandler :
public class MyHandler implements InvocationHandler {
//被代理对象
private Object target;
public MyHandler(Object object) {
this.target = object;
}
/**
*
* @param proxy 代理对象
* @param method 被代理方法
* @param args 被代理方法参数
* @return null
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限控制");
method.invoke(target);
System.out.println("输出日志");
return null;
}
}
测试样例:
public class Test {
public static void main(String[] args) {
Car car = new Car();
Dark dark = new Dark();
//创建handler,供JDK的Proxy.newProxyInstance在运行时创建代理类
InvocationHandler moveHandler = new MyHandler(car);
InvocationHandler flyHandler = new MyHandler(dark);
//car的代理对象
MoveAble moveProxy = (MoveAble) Proxy.newProxyInstance(
car.getClass().getClassLoader(),
car.getClass().getInterfaces(),
moveHandler);
//dark的代理对象
Flyable flyProxy = (Flyable) Proxy.newProxyInstance(
dark.getClass().getClassLoader(),
dark.getClass().getInterfaces(),
flyHandler);
moveProxy.move();
flyProxy.fly();
}
}
运行结果
权限控制
汽车行驶中..
输出日志
权限控制
鸭子飞了..
输出日志
源码
查看全部源码点击这里,看高兴了赏个star。
有种似曾相识的感觉吗
Java有很多框架都提供或者利用了面向切面编程的方法。
当我接触到Spring boot ,用Aspect、PointCut 等注解做了一个Http请求的日志输出时,我惊叹于Spring boot 简单又强大的魅力,因为我明明没有在Controller层添加任何的代码啊。。
当我接触到Mybatis时,我很困惑为什么我在DAO层的接口能自动映射到xml文件,需要进行数据库操作时,竟然可以直接调用接口!接口被实例化了?
现在有点豁然开朗的感觉了,这些强大的功能都是用动态代理实现的!