设计模式-代理模式及应用

352 阅读9分钟

"代理"反应到生活中,有很多类似的案例。例如之前互联网没有那么发达的时候我们买火车票,除了去火车站买,往往每个区域或者街道会有火车票代售点。各个代售点,就类似于我们的代理模式中的代理对象。完成被代理对象的功能操作(这里就是买车票),代理点的剩余车票数量和火车站买的都是事实的。我从代理售卖窗口也能买到我需要的车票。

在程序的设计过程中,我们往往需要一些额外的功能来统计或者做一些其他的业务操作。比如我们记录每个方法的执行时间,如果在每个方法的开始和执行完业务操作的末尾,统计时间并记录下来。这里的冗余代码会很多,而且都是重复的。所以代理模式就可以解决我们类似的问题。后面会通过具体的案例来说明,在实际代码中的使用场景。

代码模式的定义与结构

1.代理模式的定义:

  • (Proxy Design Pattern)代理模式的使得在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

2.代理模式结构 代理模式的主要角色如下。

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式结构图: 在这里插入图片描述

此外代理模式有动态代理和静态代理之分;

  • 静态代理:在设计程序代码时,代理对象就已经将逻辑写完;
  • 动态代理:在程序运行时,运用反射机制动态创建而成,无需关注代理对象的内部实现;

就Java开发而言,JDK默认已经提供了动态代理,还有第三方框架cglib提供的动态代理,其实现方式有些差异,下面通过案例来说明一下静态代理、动态代理(JDK、Cglib)的使用。

代码案例: 所有的代码案例,都模拟看电影,代理对象对看电影前后所有准备的事情做操作。 1.静态代理

  • 抽象主题 这里定义一个看电影的方法
public interface Movie {
	// 看电影
	void watchMovie();
}
  • 真实主题
public class ConcreteMovie implements Movie{

	@Override
	public void watchMovie() {
		System.out.println("正在观影《阿凡达》");
	}
}
  • 代理对象 (静态代理)
public class ProxyConcreteMovie implements Movie{
	
	private Movie movie;

	public ProxyConcreteMovie(Movie movie) {
		this.movie = movie;
	}

	@Override
	public void watchMovie() {
		preRequest();
		movie.watchMovie();
		postRequest();
	}
	
	public void preRequest() {
        System.out.println("看电影之前,买票买3D眼镜");
    }
    public void postRequest() {
        System.out.println("看完之后,归还眼镜");
    }
}
  • 使用方
public class Client {
	public static void main(String[] args) {
		Movie movie = new ConcreteMovie();
		Movie proxy = new ProxyConcreteMovie(movie);
		proxy.watchMovie();
	}

}

执行结果:

看电影之前,买票买3D眼镜
正在观影《阿凡达》
看完之后,归还眼镜

这个案例中的ProxyConcreteMovie为代理类,实现和真实对象一致的接口,利用对被代理对象的引用,在调用目标对象时的前后做相关的业务操作。 静态代理的缺点是,如果我的目标对象很多,抽象主体的方法也很多的情况下,代理对象就需要实现全部的接口,而且每个方法,都要去加相同的重复代码,对程序的侵入性也很大。

并不推荐对于整个系统设计过程中,大量业务需要使用的非业务功能代码,通过静态代理来实现。那么解决这个问题,最好的方式就是下面要说的动态代理方式。

2.动态代理(JDK)

  • 抽象主题 这里定义一个看电影的方法

  • 真实主题

抽象对象和真实对象这俩个构件的代码与静态代码相同,这里就不重复写了;着重看下代理对象的实现。

  • 代理对象
public class JdkProxy implements InvocationHandler{
	
	private Object object;
	
	public JdkProxy(Object object) {
		this.object = object;
	}
	
	 public Object getProxyInstance(){
		return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);	 
	 }
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		preRequest();
		method.invoke(object, args);
		postRequest();
		return null;
	}
	
	public void preRequest() {
        System.out.println("看电影之前,买票买3D眼镜");
    }
    public void postRequest() {
        System.out.println("看完之后,归还眼镜");
    }
}
  • 使用方
public class JdkClient {
	public static void main(String[] args) {
		ConcreteMovie movie = new ConcreteMovie();
		JdkProxy jdkProxy = new JdkProxy(movie);
		Movie newProxyInstance = (Movie)jdkProxy.getProxyInstance();
		newProxyInstance.watchMovie();
	}
}

执行结果:

看电影之前,买票买3D眼镜
正在观影《阿凡达》
看完之后,归还眼镜

我们看完JDK动态代理的方式和静态代理的区别,无需代理类与真实真实主题同时实现相同的接口,只需要代理类实现InvocationHandler接口,并实现invoker()方法,而且附加的功能我们可以只写一次,如果定义了其他方法,只需要通过代理对象,调用方法附件的模块会添加到每个方法上。

JDK动态代理的案例结构图: 在这里插入图片描述

3.动态代理(cglib) 和Jdk代理抽象对象和真实对象这俩个构件的代码与静态代码相同,不重复写。

  • 代理对象
public class CglibProxy implements MethodInterceptor{
	
	private Object object;
	
	public CglibProxy(Object object) {
		this.object = object;
	}

    public Object getProxyInstance(){
    	Enhancer enhancer = new Enhancer();
    	//设置父类
    	enhancer.setSuperclass(object.getClass());
    	//设置回调
    	enhancer.setCallback(this);
    	return enhancer.create();
    }

	@Override
	public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
		preRequest();
		arg1.invoke(object, arg2);
		postRequest();
		return null;
	}
	
	public void preRequest() {
        System.out.println("看电影之前,买票买3D眼镜");
    }
    public void postRequest() {
        System.out.println("看完之后,归还眼镜");
    }
}
  • 使用方
public class CglibClient {
	
	public static void main(String[] args) {
		ConcreteMovie concreteMovie = new ConcreteMovie();
		//设置被代理对象
		CglibProxy cglibProxy = new CglibProxy(concreteMovie);
		//获取代理对象
		Movie proxyInstance = (Movie)cglibProxy.getProxyInstance();
		//调用目标方法
		proxyInstance.watchMovie();
	}
}

执行结果:

看电影之前,买票买3D眼镜
正在观影
看完之后,归还眼镜

cglib实现代理模式的方式与JDK提供的方式差不多,这里的代理类需实现cglib提供的接口MethodInterceptor,并且实现intercept()方法。另外一个区别是创建代理对象的方式不同。

根据不同的使用场景,这两种动态代理的使用场景也有些区别。1.JDK动态代理,需要目标对象实现接口,如果目标对象是一个类,并非接口JDK动态代理就无法使用那就需要使用到cglib代理; 2.cglib也叫子类代理,cglib是在内存中构建一个子类对象从而实现目标对象的拓展功能。 3.spring框架的AOP,实现对目标方法的拦截,就是基于动态代理的方式。那它在底层实现是两种方式都会用到,如果目标对象需要实现接口,用JDK代理,反之用cglib代理。

cglib案例结构图: 在这里插入图片描述

代理模式的优缺点及应用场景

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

缺点:

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

使用场景: 1.一般我们使用spring aop的场景,通常是一些非功能业务模块。一般使用鉴权、限流、事务、监控、日志等; 2.RPC框架,通过远程代理的方式,使客户端在使用远程方法时,只需要知道其接口定义,无需知道具体实现;

代理模式在源码中的使用

1.spring代理模式的使用 spring框架中的AOP实现,代理对象的获取都是通过ProxyFactoryBean类中的getObject() 方法,看下这块的源码

   public Object getObject() throws BeansException {
        this.initializeAdvisorChain();
        if (this.isSingleton()) {
            return this.getSingletonInstance();
        } else {
            if (this.targetName == null) {
                this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
            }

            return this.newPrototypeInstance();
        }
    }

返回有单例对象和原型对象两种方式,spring代理默认生成单例对象。这里就以单例对象为例,首先往下更getSingletonInstance()方法

private synchronized Object getSingletonInstance() {
        if (this.singletonInstance == null) {
            this.targetSource = this.freshTargetSource();
            if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {
                Class targetClass = this.getTargetClass();
                if (targetClass == null) {
                    throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
                }

                this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
            }

            super.setFrozen(this.freezeProxy);
            this.singletonInstance = 
 //获取代理对象           this.getProxy(this.createAopProxy());
        }

        return this.singletonInstance;
    }

看下this.createAopProxy()这个方法,是ProxyFactoryBean的父类ProxyCreatorSupportcreateAopProxy()方法,

protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            this.activate();
        }

        return 
 // 获取代理类       this.getAopProxyFactory().createAopProxy(this);
    }

ProxyCreatorSupport类中的getAopProxyFactory,AOP代理工程,职责就是创建代理类对象,返回AopProxy接口对象,再往下跟看下AopProxy接口的实现类 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FpdnvkeR-1619572486476)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1460)] 说明spring实现动态代理有JDK和Cglib两种方式。 具体使用哪一种方式,我们可以看下,AopProxyFactory子类DefaultAopProxyFactory,方法createAopProxy()的实现

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                return (AopProxy)(targetClass.isInterface() ? new JdkDynamicAopProxy(config) : DefaultAopProxyFactory.CglibProxyFactory.createCglibProxy(config));
            }
        }
    }

可以通过配置指定使用何种方式

ProxyTargetClass (是否强制使用CGLIB来实现代理) (true : 强制使用CGLIB来实现代理) (false : 不强制使用CGLIB来实现代理,首选JDK来实现代理)(默认值)

isOptimize (是否对生成代理策略进行优化) (true : 进行优化,如果有接口就代理接口(使用JDK动态代理),没有接口代理类(CGLIB代理)) (false : 不进行优化) (默认值)

可以通过配置来指定使用的代理方式。

获取具体代理类后,回到ProxyFactoryBean类中的 this.singletonInstance = this.getProxy(this.createAopProxy());方法,看下getProxy()操作, 这里就是通过前面获取的具体代理方式,获取代理对象

protected Object getProxy(AopProxy aopProxy) {
        return aopProxy.getProxy(this.proxyClassLoader);
    }

以上案例分析结构图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3FyHPKf-1619572486544)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1459)]