James带你透彻分析三大代理模式
关于代理的实战课,我有专讲过
AOP实现插件热插拔实战,点这也可以看到录播
1.代理模式
Java最经典的设计模式代理模式(Proxy), 在我们开发及框架源码中无处不在,其实说白了, 就是我们使用另外一种方式来访问A对象的方法, 其本质还是访问了A对象的方法, 只是对A对象方法访问的时候做了些扩展, 把A对象封装成代理对象AA, 即通过AA代理对象来访问目标对象. 这样就可以在A对象的基础上,实现其它的额外附加功能;
AA对象也叫做代理对象, 在不动A对象的前提下, 实现了对A对象的扩展, 程序开发过程中, 不能经过动别人的代码,用代理对别人的代码进行扩展.
A目标对象, AA为A的代理对象proxy, 如下图:
注意: 代理对象AA与目标对象A.其实就是AA对A对象的调用前后加了扩展,加了图上的第2 第4步
1.1.快速掌握静态代理
我们先要搞清什么是静态代理?
看看代码操作吧, 首先我们需要定义接口或父类,然后目标对象与代理对象需要继承相同父类或实现相同的接口.
废话不多说, 翠花, 上代码!!!
先建接口:IOrderDao.java

/**
* 接口
*/
public interface IOrderDao {
void createOrder();
}
目标对象:OrderDao.java
/**
* 接口实现
* 目标对象
*/
public class OrderDao implements IOrderDao {
public void createOrder() {
System.out.println("----创建订单成功!----");
}
}
对OrderDao进行代理, 即对其进行包装(也要代理对象)
新建 OrderDaoProxy.java
/**
* 代理对象,静态代理,与目标对象实现相同的接口或继承相同的父类
*/
public class OrderDaoProxy implements IOrderDao{
//接收从外部传进来的目标对象
private IOrderDao target;
public OrderDaoProxy(IOrderDao target){
this.target=target;
}
public void createOrder() {
System.out.println("James下订单前,记录日志...");
target.createOrder();//真正的执行从外部传进来的目标方法
System.out.println("James下订单后,记录日志...");
}
}
我们测试一下,新建类:JamesApp.java
/**
* test
*/
public class JamesApp {
public static void main(String[] args) {
//一定要创建目标对象
OrderDao target = new OrderDao();
//看到没?我执行的是proxy的createOrder,加了些日志扩展,但真正还是调了目标对象的createOrder
OrderDaoProxy proxy = new OrderDaoProxy(target);
proxy.createOrder();//执行的是代理的方法
}
}
执行结果如下
James下订单前,记录日志...
----创建订单成功!----
James下订单后,记录日志...
看到没, 订单创建成功, 但在前后都被记录了日志,这就是代理对象对目标对象进行增强了.
静态代理总结下:
1.优点: 不修改目标对象的任何一行代码, 完成了对目标对象的扩展.
2.缺点: 代理对象会随着目标对象的修改, 也要做相应的修改,比如目标对象加了个订单查询 orderQuery方法,OrderDaoProxy代理类也要做代码开发, 才能完成这个方法的扩展, 很明显, 不方便 .
那么如何解决这个问题呢? 很明显啦 ,动态代理嘛
1.2.JDK动态代理
那么动态代理有哪些优点呢?
1.首先代理对象肯定是不需要实现接口或继承同一父类, 不然又回到了静态代理模式
2.代理对象它内部调用JDK的接口,可以动态在我们内存中生成代理对象, 这个代理对象和上面讲代理对象的作用是一样的, 对目标对象进行增强, 进行扩展, 只是实现方式更灵活更方便
OK, 那么如何调用JDK API来生成代理对象呢?
代理类所在包:java.lang.reflect.Proxy
很简单, 调用JDK的newProxyInstance方法即可对目标对象生成对应的代理对象, 语法格式如下:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
newProxyInstance静态方法,需要专稿三个参数:
ClassLoader loader,
:目标对象使用的类加载器, target.getClass().getClassLoader()Class<?>[] interfaces,
:目标对象实现接口类型,使用泛型方式类型,target.getClass().getInterfaces()InvocationHandler h
: 当执行目标对象的方法时,会先调用InvocationHandler的invoke方法, 同时把当前执行目标对象的方法作为参数传入
废话不多说, 翠花, 上代码, 一看就懂:
新建IOrderDao接口:
package com.enjoy.james;
public interface IOrderDao {
void createOrder();
void queryOrder();
void cancelOrder();
}
目标对象OrderDao实现类:
package com.enjoy.james;
public class OrderDao implements IOrderDao {
public void createOrder() {
System.out.println("订单创建成功.......");
}
public void queryOrder() {
System.out.println("订单查询成功.......");
}
public void cancelOrder() {
System.out.println("订单取消成功.......");
}
}
一定要有代理工厂类:ProxyFactory.java
后面使用的时候, 直接向工厂申请,通过ProxyFactory类传入目标对象, 生成代理对象
package com.enjoy.james;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 创建动态代理对象的步骤
* 注:动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//对目标对象包装成代理对象
public Object genProxyBean() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("James订单操作前,记录日志...");
//运用反射执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("James订单操作前,记录日志...");
return returnValue; //返回已经扩展后的代理对象
}
}
);
}
}
来,测试一下:
package com.enjoy.james;
public class TestJDKProxy {
public static void main(String[] args) {
// 目标对象
OrderDao target = new OrderDao();
// 给目标target对象,创建代理对象(这个对象对目标对象进行了增强)
IUserDao proxy = (IUserDao) new ProxyFactory(target).genProxyBean();
proxy.createOrder();
System.out.println("===1创建订单完成===");
proxy.queryOrder();
System.out.println("===2查询订单完成===");
proxy.cancelOrder();
System.out.println("===3取消订单完成===");
}
}
运行结果:
James订单操作前,记录日志...
订单创建成功.......
James订单操作前,记录日志...
===1创建订单完成===
James订单操作前,记录日志...
订单查询成功.......
James订单操作前,记录日志...
===2查询订单完成===
James订单操作前,记录日志...
订单取消成功.......
James订单操作前,记录日志...
===3取消订单完成===
内容总结:
JDK的方式生成代理对象, 如果我们把代理对象生成 p r o x y 0. c l a s s 文 件 ( 怎 么 生 成 , 请 百 度 ) , 保 存 到 本 地 磁 盘 , 通 过 对 proxy0.class文件(怎么生成, 请百度),保存到本地磁盘, 通过对 proxy0.class文件(怎么生成,请百度),保存到本地磁盘,通过对proxy0.class类反编译查看, 最终是这个样子
public class $proxy0 extends Proxy implements IOrderDao
$proxy0代理对象已经继承了jdk的Proxy, 所以JDK动态代理只能以实现IOrderDao接口的方式完成
1.3.Cglib动态代理
我们不难发现, 刚以上写的静态代理和动态代理模式它们都要求: 目标对象是一定要实现一个接口,但在我们写业务代码有时候只是一个对象, 没有实现接口,那我们可以Cglib代理方式来完成代理对象
首先我们的pom.xml要引入cglib:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
业务代码略:OrderDao和IOrderDao与上面完全一样。
首先建立Cglib代理工厂类
package com.enjoy.james;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactoryByCglib implements MethodInterceptor {
public ProxyFactoryByCglib() {
}
/**
* 1、代理对象;
* 2、委托类方法;
* 3、方法参数;
* 4、代理方法的MethodProxy对象。
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(method.getName()+"方法开始调用");
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println(method.getName()+"方法结束调用");
return obj;
}
}
开始测试:
package com.enjoy.james;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class TestCglib {
public static void main(String[] args) {
//将动态代理类保存到磁盘D:/cglib下,方便我们反编译查看它的结构
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/cglib");
ProxyFactoryByCglib cglibProxy = new ProxyFactoryByCglib();
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(OrderDao.class);
//设置回调对象
enhancer.setCallback(cglibProxy);
OrderyDao proxy = (OrderyDao) enhancer.create();
proxy.createOrder();
System.out.println("===1订单创建===");
proxy.queryOrder();
System.out.println("===2订单查询===");
proxy.cancelOrder();
System.out.println("===3订单取消===");
}
}
**测试结果: **
createOrder()方法开始调用
订单创建成功…
createOrder()方法开始调用
=1订单创建=
queryOrder()方法开始调用
订单查询成功…
queryOrder()方法开始调用
=2订单查询=
cancelOrder()方法开始调用
订单取消成功…
cancelOrder()方法开始调用
=3订单取消=
使用总结:
若目标对象有实现接口,我们可以用JDK代理; 若目标对象没有实现接口,则用Cglib代理;若目标对象实现了接口,并且强制用cglib, 最终还是会使用cglib代理.