接之前介绍的策略模式,今天我们学习另一种设计模式:代理模式。
代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
它的uml示意图如下所示:
我理解的代理模式是为了让编码更符合单一职责的设计,一些不符合类设计初衷的方法通过代理模式来实现。
举个例子,在我们生活中,每个歌手都会有自己的经纪公司,歌手要做的就是创作作品,其他演出,活动等安排都交由经纪公司来安排,公司也了解自己旗下的歌手,不会安排超出能力以外的工作。这就是现实中一个很好的代理例子。
我们用面向对象的思想来分析下:
歌手对象Singer,它提供一个方法create(),沟通演出不属于他的职责,所以有了一个代理对象company,它持有singer,还额外提供arrangePerformance().通过这个company,我们既可以安排演出,还可以欣赏歌手创作的作品,这也就是代理模式设计的初衷:
- 隐藏被代理对象的实现细节:
- 调用者和被代理对象之间解耦
- 方便扩展
静态代理和动态代理
代理模式主要分为两种:静态代理和动态代理,其中动态代理根据实现方式不同也分为jdk动态代理和cglib动态代理。
我们分别用静态代理和动态代理来实现上面的小例子
静态代理:
编译时代理类就已经被创建。
Singer:
public interface Singer {
/**
* 创作
*/
void create();
}
SingerImpl:
public class SingerImpl implements Singer {
@Override
public void create() {
System.out.println("创作作品");
}
}
SingerProxy:
public class SingerProxy {
SingerImpl singer;
public SingerProxy(SingerImpl singer) {
this.singer = singer;
}
public void proxy(){
before();
singer.create();
after();
}
private void before(){
System.out.println("经纪公司沟通活动");
}
private void after(){
System.out.println("经纪公司安排散场");
}
}
Test:
public class TestSingerProxy {
public static void main(String[] args) {
SingerProxy proxy = new SingerProxy(new SingerImpl());
proxy.proxy();
}
}
运行结果:
可以看到我们通过SingerProxy在实现singer的功能基础上,还进行了扩展。这样看起来静态代理效果很明显,实现也很简单,那么它会不会存在什么缺点还没有被我们发现呢?
动态代理:
运行时动态的创建代理类。动态代理有两种实现方式:jdk动态代理和cglib动态代理。
Jdk
改用jdk动态代理,我们需要重新创建一个代理类来实现InvocationHandler接口,既然是动态的,我们这里面就不需要指定目标对象了。
DynamicProxy:
public class DynamicProxy implements InvocationHandler {
Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
private void before(){
System.out.println("经纪公司沟通活动");
}
private void after(){
System.out.println("经纪公司安排散场");
}
}
test:
public class TestDynamicProxy {
public static void main(String[] args) {
SingerImpl singerimpl = new SingerImpl();
DynamicProxy proxy = new DynamicProxy(singerimpl);
Singer singer = (Singer) Proxy.newProxyInstance(singerimpl.getClass().getClassLoader(), singerimpl.getClass().getInterfaces(), proxy);
singer.create();
}
}
运行结果:
可以看到动态代理在使用时,需要通过Proxy.newProxyInstance来获取代对象,在执行目标方法时,通过反射来实现。
上面虽然我们已经实现了代理的功能,但这样子的写法实在是太不友好了,要是直接通过代理对象就能实现所有的操作就好了。
DynamicProxy2.0:
public class DynamicProxy2 implements InvocationHandler {
Object target;
public DynamicProxy2(Object target) {
this.target = target;
}
public <T>T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
private void before(){
System.out.println("经纪公司沟通活动");
}
private void after(){
System.out.println("经纪公司安排散场");
}
}
test:
public class TestDynamicProxy {
public static void main(String[] args) {
Singer singer = new DynamicProxy2(new SingerImpl()).getProxy();
singer.create();
}
}
运行结果:
可以看到经过上面的改造,现在动态代理使用起来已经很简单了,我们要做的就是将需要代理的对象放进去,然后正常调用我们的目标方法即可。
现在看来jdk动态代理比静态代理好很多,我们貌似有它就足够了,但事实上我们还有一种动态代理的实现方式:通过cglib来实现动态代理,这又是为什么呢?
静态代理和动态代理优缺点分析:
静态代理的优点就是代理模式的优点:隐藏细节,解耦,扩展功能,但是它的缺点同样明显,如果最顶层的内容发生变化,那么我们需要修改它的实现类和代理类,如果这种情况很多的话,那么恐怖修改的工作量就够受的了。
在这个基础上,动态代理很好的解决了这个问题,不管你的目标对象做什么修改都不会影响到我们的代理类,但是细心的你在使用jdk动态代理创建代理实例的时候肯定发现他有一个方法入参是:
Class<?>[] interfaces,
这不就是接口的意思么,难道jdk动态代理只能针对接口来进行代理么? 答案是肯定的,jdk动态代理只能针对接口。
如果没有接口又需要代理那该怎么办呢?不要急,cglib该登场了。
Cglib
有关cglib的说明网上有很多,我们这里不加赘述,只需要知道他可以操作字节码生成新的类,我们用它来实现动态代理即可。
Cglib的代理类需要实现MethodInterceptor
CglibProxy:
public class CglibProxy implements MethodInterceptor {
Object target;
public CglibProxy(Object target) {
this.target = target;
}
public <T>T getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = method.invoke(target, objects);
after();
return result;
}
private void before(){
System.out.println("经纪公司沟通活动");
}
private void after(){
System.out.println("经纪公司安排散场");
}
}
test:
public class TestCglibProxy {
public static void main(String[] args) {
Singer singer = new CglibProxy(new SingerImpl()).getProxy();
singer.create();
}
}
运行结果:
其中获取代理的方式需要通过Enhancer对象来创建,主要参数就是superClass 和 代理类对象。
代理模式在spring中的应用
SpringAop中通过动态代理来实现aop的功能,其中针对接口使用jdk动态代理来实现,其他的实现类则通过cglib来实现动态代理,在spring中基本都是面向接口编程,所以其实jdk动态代理是里面的默认实现方式。
总结
代理模式分为静态代理和动态代理,动态代理根据实现方式又可以分为jdk动态代理和cglib动态代理,其中jdk动态代理针对接口,cglib通过动态创建子类的方式生成代理对象(所以无法对目标类种的final和private方法代理)。这也是spring aop的选择逻辑。