1、什么是代理模式
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
2、代理模式的作用
通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性。
同时通过代理对象的隔离,可以在目标对象访问后增加额外的业务逻辑,实现功能增强。
3、代码例子

小红是一个香水代购,而小明是一个购买者,小明托小红买一瓶香水。
/*** 售卖香水的接口*/public interface SellPerfume {void sellPerfume(double price);}
/*** 香水工厂*/public class ChannelFactory implements SellPerfume{@Overridepublic void sellPerfume(double price) {System.out.println("成功购买香奈儿的香水,价格是:" + price + "元");}}
/*** 小红代购*/public class XiaohongSellProxy implements SellPerfume{private SellPerfume sellPerfumeFactory;public XiaohongSellProxy (SellPerfume sellPerfumeFactory) {this.sellPerfumeFactory = sellPerfumeFactory;}@Overridepublic void sellPerfume(double price) {doSomethingBeforeSell();sellPerfumeFactory.sellPerfume(price);doSomethingAfterSell();}private void doSomethingBeforeSell() {System.out.println("小红代理买香水前的操作");}private void doSomethingAfterSell() {System.out.println("小红代理购买香水后的操作");}}
/*** 实际购买者*/public class XiaoMing {public static void main(String[] args) {ChannelFactory factory = new ChannelFactory();XiaohongSellProxy proxy = new XiaohongSellProxy(factory);proxy.sellPerfume(199999);}}
实现代理模式,需要走一下几个步骤:
- 定义真实对象和代理对象的公共接口(售卖香水接口)
- 代理对象内部保存对真实目标对象的引用(小红引用提供商)
- 访问者仅能通过代理对象访问真实目标对象,不可直接访问目标对象(小明只能通过小红去购买香水,不能直接到香奈儿提供商购买)
代理模式的一个误区:代理对象并不提供服务,以本次的例子来说,小红并不出售香水,她只是一个中间人
4、改进-JDK Proxy
继续上面的例子,如果小明还想要买红酒,小红也刚好在法国,那么在程序中就需要创建售卖红酒的接口,售卖红酒提供商和小红也需要实现实现该接口
上面的静态代理违背了开闭原则,当有不停地有新的需求时,代理类就会越来越庞大,变得难以维护
下面使用JDK Proxy 实现动态代理,关键是先实现_InvocationHandler_接口和其方法invoke
public class SellProxyFactory implements InvocationHandler {//代理的真是对象private Object realObject;public SellProxyFactory (Object realObject) {this.realObject = realObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {doSomethingBeforeSell();Object object = method.invoke(realObject, args);doSomethingAfterSell();return object;}private void doSomethingBeforeSell() {System.out.println("小红代理买东西前的操作");}private void doSomethingAfterSell() {System.out.println("小红代理购买东西后的操作");}}
看invoke方法可以看出就是反射里的Method类的invoke,在这里就是一个代理方法,即最后客户端请求代理时,执行的就是该方法
public class Xiaoming {public static void main(String[] args) {ChannelFactory channelFactory = new ChannelFactory();SellProxyFactory sellProxyFactory = new SellProxyFactory(channelFactory);SellPerfume sellPerfume = (SellPerfume) Proxy.newProxyInstance(channelFactory.getClass().getClassLoader(), channelFactory.getClass().getInterfaces(), sellProxyFactory);sellPerfume.sellPerfume(199888.9);}}
生成代理对象需要Proxy类,它可以帮助我们生成任意一个代理对象,里面提供一个静态方法newProxyInstance,需要传入三个参数:
- ClassLoader loader:加载动态代理类的类加载器
- Class<?> [ ] interfaces:代理类实现的接口,可以传入多个接口
- InvocationHandler h:指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法
以后想要扩展时,比如代购红酒,只需要增加一个出售红酒的接口和红酒提供商。这样就符合了开闭原则。
4.1、总结
JDK动态代理的使用方法
- 代理工厂需要实现 InvocationHandler接口,调用代理方法时会转向执行invoke()方法
- 生成代理对象需要使用Proxy 对象中的newProxyInstance()方法,返回对象可强转成传入的其中一个接口,然后调用接口方法即可实现代理
JDK动态代理的特点
- 目标对象强制需要实现一个接口,否则无法使用JDK动态代理
5、CGLIB实现代理
CGLIB(Code generation Library)不是JDK自带的动态代理,它需要导入第三方依赖,它是一个字节码生成类库,能够在运行时动态生成代理类对Java类和Java接口扩展。
CGLIB不仅能够为Java接口做代理,而且能够为普通的Java类做代理,而JDK Proxy 只能为实现了接口的Java类做代理,所以CGLIB为Java的代理做了很好的扩展。如果需要代理的类没有实现接口,可以选择Cglib作为实现动态代理的工具。
使用步骤如下:
1)导入依赖
<dependency><groupId>cglib</groupId><artifactId>cglib-nodep</artifactId><version>3.3.0</version></dependency>
还有另外一个CGLIB包,二者的区别是带有-nodep的依赖内部已经包括了ASM字节码框架的相关代码,无需额外依赖ASM
2)CGLIB代理中有两个核心的类:MethodInterceptor 接口和Enhancer类,前者是实现一个代理工厂的根接口,后者是创建动态代理对象的类
public class SellProxyFactory implements MethodInterceptor {//关联真实对象,控制对真实对象的访问private Object realObject;public Object getProxyInstance(Object realObject) {this.realObject = realObject;Enhancer enhancer = new Enhancer();//设置需要增强类的类加载器enhancer.setClassLoader(realObject.getClass().getClassLoader());//设置被代理类,真实对象enhancer.setSuperclass(realObject.getClass());//设置方法拦截器,代理工厂enhancer.setCallback(this);//创建代理类return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {doSomethingBeforeSell();Object object = methodProxy.invokeSuper(o, objects);doSomethingAfterSell();return object;}private void doSomethingBeforeSell() {System.out.println("代理买东西前的操作");}private void doSomethingAfterSell() {System.out.println("代理购买东西后的操作");}}
intercept0方法涉及到4个参数:
- ·Object o:被代理对象
- Method method:被拦截的方法
- Objectl objects:被拦截方法的所有入参值
- MethodProxy methodProxy:方法代理,用于调用原始的方法
在getInstance()方法中,利用Enhancer类实例化代理对象(可以看作是小红)返回给调用者小明,即可完成代理操作。
public class Xiaoming {public static void main(String[] args) {SellProxyFactory sellProxyFactory = new SellProxyFactory();//获取一个代理实例ChannelFactory proxyInstance = (ChannelFactory) sellProxyFactory.getProxyInstance(new ChannelFactory());proxyInstance.sellPerfume(1999.77);}}
6、CGLIB与JDK Proxy
最明显的不同:CGLIB可以代理大部分类(第二点说到);而JDKProxy仅能够代理实现了接口的类
CGLIB采用动态创建被代理类的子类实现方法拦截,子类内部重写被拦截的方法,所以CGLlB不能代理被final关键字修饰的类和方法
