代理模式
Java最经典的设计模式代理模式(Proxy),在我们开发及框架源码中无处不在,其实说白了,就是我们使用另外一种方法来访问A对象的方法,其本质还是访问了A对象的方法,只是对A对象方法访问的时候做了些扩展,把A对象封装成代理对象AA,即通过AA代理对象来访问目标对象,这样就可以在A对象的基础上,实现其它的额外附件功能;
AA对象叫做代理对象,在不动A对象的前提下,实现了对A对象的扩展,程序开发过程中,如果不能动别人的代码,可以用代理对别人的代码进行扩展
A目标对象,AA为A的代理对象proxy,如下图:
注意:代理对象AA与目标对象A,其实就是AA对A对象的调用前后加了扩展,加了图上的第2步和第4步。
静态代理
定义接口或者父类,然后目标对象与代理对象需要继承相同父类或者实现相同的接口.
- 建立接口:IOrderDao.java
public interface IOrderDao {
void createOrder();
}
- 目标对象:OrderDao.java
public class OrderDao implements IOrderDao {
public void createOrder() {
System.out.println("----订单创建成功!-----");
}
}
- 代理对象:OrderDaoProxy.java
public class OrderDaoProxy implements IOrderDao {
//接受从外部传过来的目标对象
private IOrderDao target;
public OrderDaoProxy(IOrderDao target) {
this.target = target;
}
}
public void createOrder() {
System.out.println("------------------下单前-------------------------");
target.createOrder(); //目标方法
System.out.println("------------------下单后-------------------------");
}
- 测试
public class JamesApp {
public static void main(String[] args) {
//一定要创建目标对象
OrderDao target = new OrderDao();
//看到没?我执行的是proxy的createOrder,加了些日志扩展,但真正还是调了目标对象的createOrder
OrderDaoProxy proxy = new OrderDaoProxy(target);
proxy.createOrder();//执行的是代理的方法
}
}
创建订单的前后都加上了日志打印,这就是代理对象对目标对象进行增强。
静态代理总结:
- 优点:不修改目标对象的任何一行代码,完成了对目标对象的扩展
- 缺点:代理对象会随着目标对象的修改,也要做相应的修改,不如目标对象加了个订单查询orderQuery方法,OrderDaoProxy代理类也要做代码开发,才能完成这个方法的扩展,很明显,不方便。
那么如何解决这个问题呢?很明显,动态代理!
JDK动态代理
那么动态代理有哪些优点呢
- 首先代理对象肯定是不需要实现接口或者继承同一个父类的,不然又回到了静态代理模式
- 代理对象它内部调用JDK的接口,可以动态在我们内存中生成代理对象,这个代理对象和上面讲代理的作用是一样的,对目标对象进行增强,进行扩展,只是实现方式更灵活更方便
OK,那么如何调用JDK API来生成代理对象呢?
代理类所在包:java.lang.reflect.Proxy
调用JDK的newProxyInstance
方法即可对目标对象生成对应的代理对象,语法格式如下:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
newProxyInstance静态方法,需要传3个参数:
-
ClassLoader loader: 目标对象使用的类加载器,target.getClass().getClassLoader()
-
Class<?> interfaces: 目标对象实现接口类型,使用泛型方式类
-
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类传入目标对象,生成代理对象
/**
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
//维护一个目标对象
private Object target;
public ProxyFactory(Object object) {
this.target = object;
}
//对目标对象包装成代理对象,返回已经扩展后的代理对象
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("-----------------创建订单前--------------------");
//运用反射执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("-----------------创建订单后--------------------");
return returnValue;
}
}
);
}
}
来测试一下
public class TestJDKProxy {
public static void main(String[] args) {
//目标对象
OrderDao target = new OrderDao();
//给目标target对象,创建代理对象
IOrderDao proxy = new ProxyFactory(target).genProxyBean();
proxy.createOrder();
}
}
内容总结:
JDK的方式生成代理对象,如果我们把代理对象生成
P
r
o
x
y
0.
c
l
a
s
s
文
件
,
保
存
至
本
地
磁
盘
,
通
过
对
Proxy0.class文件,保存至本地磁盘,通过对
Proxy0.class文件,保存至本地磁盘,通过对Proxy0.class类反编译查看,最终是这个样子
public class $Proxy0 extents Proxy implements IOrderDao
$Proxy0代理对象已经继承了JDK的Proxy,所以JDK动态代理只能以实现IOrderDao接口的方式完成
因为代理类是运行时生成的,我们把它的字节码从内存中获取出来,用下面的方式:
public static void createProxyClassFile() {
byte[] $Proxy0s = ProxyGenerator.generateProxyClass(
"$Proxy0", new Class[]{IOrderDao.class});
try {
FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
fileOutputStream.write($Proxy0s);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
CGLIB动态代理
我们不难发现,刚以上写的静态代理和动态代理模式它们都有要求:目标对象是一定要实现一个接口,但是在我们些业务代码的时候这是一个对象,没有实现接口,那我们可以用CGLIB代理方式来完成代理对象。
首先我们的pom.xml要引入cglib
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
业务代理省略,OrderDao和IOrderDao与上面完全一样
首先建立cglib代理工厂类
public class ProxyFactoryByCglib implements MethodInterceptor {
public ProxyFactoryByCglib() {
}
@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;
}
}
开始测试:
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订单取消===");
}
}
使用总结:
若目标对象有实现接口,我们可以用JDK代理,若目标对象没有实现接口,则用CGLIB代理;
若目标对象实现了接口,并且强制用CGLIB,最终还是会使用CGLIB代理。