代理模式
代理模式是一种结构型设计模式,它可以为其他对象提供一种代理以控制对这个对象的访问。
所谓代理,是指具有与被代理对象相同的接口的类,客户端必须通过代理与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理。
代理模式中的结构图如下:
代理的模式在平时生活中也很常见,比如买火车票这件小事,黄牛相当于是火车站的代理,我们可以通过黄牛或者代售点进行买票行为,但只能去火车站进行改签和退票,因为只有火车站才有改签和退票的方法。
在代码实现中相当于为一个委托对象realSubject提供一个代理对象proxy,通过proxy可以调用 realSubject的部分功能(买票),并添加一些额外的业务处理(收取手续费),同时可以屏蔽 realSubject中未开放的接口(改签和退票)。
代理模式有分为两种:静态代理和动态代理。
静态代理
指代理对象和目标对象(委托对象)在代理之前是确定的,他们都实现相同的接口或者继承相同的抽象类。
现在模拟一个场景,一辆汽车有一个行驶的方法。
1、定义一个行驶方法的接口 Moveable
public interface Moveable {
void move();
}
2、定义 Car 类并实现 Moveable 接口
public class Car implements Moveable {
@Override
public void move() {
//实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
现在需求是,通过代理的方式实现记录汽车行驶的时间。
静态代理实现方法
(1)继承法:代理类直接【继承】被代理类,实现其原有方法,并添加一些额外功能
public class CarProxyExtends extends Car {
@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶....");
super.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶.... 汽车行驶时间:"
+ (endtime - starttime) + "毫秒!");
}
}
(2)聚合方法:目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。
public class CarProxyPolymer implements Moveable {
public CarProxyPolymer (Car car) {
super();
this.car = car;
}
private Car car;
@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶....");
car.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶.... 汽车行驶时间:"
+ (endtime - starttime) + "毫秒!");
}
}
public static void main(String[] args) {
//使用继承方式
Moveable m1 = new CarProxyExtends();
m1.move();
//使用聚合方式实现
Car car = new Car();
Moveable m2 = new CarProxyPolymer(car);
m2.move();
}
通过上面的实现进行两种方式的对比(继承方式和聚合方式)
聚合的方式实现静态代理是合适的更好的方法,也就是代理类和被代理类同样实现同一个接口,然后代理类中存入一个被代理类的成员变量,真正调用的是这个成员变量的方法,这样可以实现多种功能的叠加,而且调整功能的顺序操作也会很简单,只需要客户端调整调用功能的顺序即可,如果采用继承的方式,就必须要实现多个功能顺序不同的代理类,这样代理类的数量会越来越多,不利于后面的维护工作。
实际应用中很少采用静态代理的实现模式,因为一个委托类对应一个代理类,代理类在编译期间就已经确定,随着委托类方法数量越来越多,代理类的代码量是十分庞大的并且代码的重复性高。所以引入动态代理来解决此类问题。
动态代理
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理。
JDK 动态代理
代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。
JDK动态代理只能针对实现了接口的类生成代理。
下面通过jdk动态代理实现方法去记录车行驶的时间。
1、利用 java.lang.reflect.Proxy类和 java.lang.reflect.InvocationHandler接口定义代理类的实现。
public class TimeHandlerProxy implements InvocationHandler {
public TimeHandler(Object target) {
super();
this.target = target;
}
private Object target;
/*
* 参数:
* proxy 被代理对象
* method 被代理对象的方法
* args 方法的参数
*
* 返回值:
* Object 方法的返回值
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶....");
method.invoke(target);
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶.... 汽车行驶时间:"
+ (endtime - starttime) + "毫秒!");
return null;
}
// 生成代理对象
public Object getProxy (){
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(loader,interfaces,this);
}
}
2、代理类调用方法
public static void main(String [] agrs){
Car c = new Car();
TimeHandlerProxy thp = new TimeHandlerProxy(c);
Moveable m = (Moveable)thp.getProxy();
m.move();
}
//输出结果:
汽车开始行驶....
汽车结束行驶.... 汽车行驶时间:744
代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:
1、ProxyGenerator.generateProxyClass方法负责生成代理类的字节码
2、native方法 Proxy.defineClass0负责字节码加载的实现,并返回对应的Class对象。
3、利用 clazz.newInstance反射机制生成代理类的对象;
4、接口类型引用代理对象,接口调用的方法(接口实现的多态)
也就是说,所谓动态代理(Dynamic Proxy)是这样一种class
他是在运行时生成的class
该class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.调用Proxy的静态方法,创建一个代理类---newProxyInstance(ClassLoader loader,Class[]interfaces,InvocationHandler h)
4.通过代理调用类方法
JDK动态代理局限性
通过反射类 Proxy 和 InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用JDK动态代理,但是我们可以使用CGLIB动态代理来实现。
CGLIB动态代理
CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。
使用CgLib动态代理需要引入cglib-nodep.jar包 https://github.com/cglib/cglib/releases
定义没有实现接口的一个Train的类,通过动态代理实现日志记录的功能。
class Train{
public void move() {
//实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("火车行驶中....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面通过一个例子看看如何实现动态代理。
1、实现 MethodInterceptor接口,定义方法的拦截器,2、利用 Enhancer类生成代理类;
public class CglibProxy implements MethodInterceptor {
/**
* 定义方法的拦截器
* 拦截所有目标类方法的调用
* obj 目标类的实例
* m 目标方法的反射对象
* args 方法的参数
* proxy代理类的实例
*/
@Override
public Object intercept(Object obj, Method m, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("日志开始...");
//代理类调用父类的方法
proxy.invokeSuper(obj, args);
System.out.println("日志结束...");
return null;
}
private Enhancer enhancer = new Enhancer();
//Enhancer类生成代理类
public Object getProxy(Class clazz){
//设置创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
}
3、通过代理类调用方法执行
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Train t = (Train)proxy.getProxy(Train.class);
t.move();
}
代理对象的生成过程由Enhancer类实现,大概步骤如下:
1、生成代理类Class的二进制字节码;
2、通过 Class.forName加载二进制字节码,生成Class对象;
3、通过反射机制获取实例构造,并初始化代理类对象。
4、委托类类型引用代理对象,调用方法(继承实现的多态)
总结:
JDK和CGLIB动态代理的区别
1、JDK动态代理生成的代理类和委托类实现了相同的接口;
2、CGLIB动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被 final
关键字修饰的方法;
3、JDK采用反射机制调用委托类的方法,CGLIB采用类似索引的方式直接调用委托类方法;
扩展:
Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的。Spring本身含有两大特性,一个是IOC,一个是AOP的支持。
IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对。依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现。
AOP是Aspect Oriented Programming的缩写,即面向切面编程。与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。 IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?——动态代理技术
目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术。Java自带的JDK动态代理技术是需要接口的,而CGLIB则是直接修改字节码。