1. 代理模式概述
通过代理对象访问目标对象,这样可以在不修改目前对象的前提下,提供额外的功能操作,达到扩展目标对象功能的目的。 代理模式是设置一个中间代理来控制对目标对象的访问,以增强目标对象功能和简化访问 的方式 比如,明星的本职工作是演出,这时需要一个经纪人为他接洽演出活动。经纪人就是明星的代理,联系经纪人就可以邀请明星进行表演。
代理模式的三种类型: 静态代理、动态代理(JDK动态代理或接口代理)、CGLIB动态代理。代理模式有三种角色:
抽象对象(AbstractObject): 声明了目标对象和代理对象的共同接口 ,这样在任何可以使用目标对象的地方都可以使用代理对象。目标对象(RealObject): 定义了代理对象所代理的对象。代理对象(ProxyObject): 代理对象内部含有目标对象的引用 ,可以在任何时候操作目标对象;代理对象提供与目标对象相同的接口 ,可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某些额外的操作,而不是单纯将调用传递给目标对象。
代理模式的类图
2. 静态代理
① 静态代理的概述及编程实例
静态类提前定义好了代理类和目标类的关系(二者实现同一个接口),编译完成后代理类是一个实际的class文件 。如,腾讯的代理律师是老张,一旦腾讯遇到法律纠纷,都由老张负责处理这些纠纷。 静态代理建立的步骤:
定义一个接口作为抽象角色 。 创建目标类,实现接口作为目标角色 。 创建代理类,实现与目标类相同的接口作为代理角色
考虑为每个同学记录1000米跑的时间,同学应该只负责跑步,而老师去进行时间记录。这时,同学就是目标对象,老师就是代理对象。 以下代码通过聚合实现了静态类,目标对象是代理对象的内部对象。
import java. util. Random;
interface Run {
void run ( ) ;
}
class Student implements Run {
private String name;
public Student ( String name) {
this . name = name;
}
@Override
public void run ( ) {
System. out. println ( this . name + "正在跑1000米..." ) ;
try {
Thread. sleep ( new Random ( ) . nextInt ( 3000 ) ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
class Teacher implements Run {
private Student target;
public Teacher ( Student target) {
this . target = target;
}
@Override
public void run ( ) {
System. out. println ( "开始1000米计时..." ) ;
long start = System. currentTimeMillis ( ) ;
target. run ( ) ;
long end = System. currentTimeMillis ( ) ;
System. out. println ( "停止计时,一共跑了" + ( end - start) + "秒" ) ;
}
}
public class StaticProxy {
public static void main ( String[ ] args) {
Student targetObj= new Student ( "张三" ) ;
Teacher proxyObj= new Teacher ( targetObj) ;
proxyObj. run ( ) ;
}
}
通过聚合 实现静态代理,运行结果如下: 静态代理的编程实例2:
通过继承实现静态代理:代理类继承目标类,重写目标类中的方法。 继承的方式并不灵活,一般使用聚合方式实现静态代理。
import java. util. Random;
interface Run {
void run ( ) ;
}
class Student implements Run {
private String name;
public Student ( String name) {
this . name = name;
}
@Override
public void run ( ) {
System. out. println ( this . name + "正在跑1000米..." ) ;
try {
Thread. sleep ( new Random ( ) . nextInt ( 3000 ) ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
class Teacher extends Student {
public Teacher ( String name) {
super ( name) ;
}
@Override
public void run ( ) {
System. out. println ( "开始1000米计时..." ) ;
long start = System. currentTimeMillis ( ) ;
super . run ( ) ;
long end = System. currentTimeMillis ( ) ;
System. out. println ( "停止计时,一共跑了" + ( end - start) + "秒" ) ;
}
}
public class StaticProxy {
public static void main ( String[ ] args) {
Teacher proxyObj= new Teacher ( "李四" ) ;
proxyObj. run ( ) ;
}
}
通过继承 实现静态代理,运行结果如下:
② 静态代理的优缺点
静态代理要求代理类和目标类实现同一个接口,编译完成后代理类是一个实际的class文件。 优点: 可以在不修改目标对象的前提下,通过代理对象扩展目标对象的功能。缺点1: 由于要求代理类和目标类实现相同的接口,使得目标类和代理类是一对一的,会产生过多的目标类并导致代码冗余 。缺点2: 一旦接口增加方法,代理类和目标类都要进行修改,使得代码不易维护 。静态代理的缺点,可以使用动态代理来解决。
3. JDK动态代理
① 动态代理的重要接口和类
动态代理:代理类不需要实现与目标类实现相同的接口,使用JDK API
,利用反射机制 ,在运行时动态生成 代理类的字节码并加载到JVM中。 动态代理由java.lang.reflect
包实现,主要使用了其中的Proxy类
和InvocationHandler接口
。
java.lang.reflect.InvocationHandler
接口:调用处理器接口,自定义了一个invoke()
方法,用于集中处理在Proxy
类对象上的方法调用,在该方法中实现了对目标对象的代理访问 。java.lang.reflect.Proxy
类:提供了一组静态方法,最常使用的是newProxyInstance()
静态方法,返回一个Proxy类对象。
public static Object newProxyInstance ( ClassLoader loader, Class< ? > [ ] interfaces, InvocationHandler h)
ClassLoader loader:
指定目标对象使用的类加载器 ,获取类加载器的方法是固定的。Class<?>[] interfaces:
指定目标对象使用的接口类型 。InvocationHandler h:
指定调用处理器,执行目标对象的方法时,会触发调用处理器的invoke()
方法,并把目标对象的方法作为参数传入。
newProxyInstance()
的常见使用方法:
如何获取ClassLoader
?
Mouse. class . getClassLoader ( )
mouse. getClass ( ) . getClassLoader ( )
如何获取目标对象的接口?
new Class [ ] { Mouse. class }
mouse. getClass ( ) . getInterfaces ( )
如何指定调用处理器?
Mouse mouse = new Mouse ( "Tom" ) ;
MyInvocationHandler handler = new MyInvocationHandler ( mouse) ;
Animal iAnimal = ( Animal) Proxy. newProxyInstance ( Mouse. class . getClassLoader ( ) ,
new Class [ ] { Mouse. class } , handler) ;
InvocationHandler
的invoke()
方法:
Object proxy:
代理对象Method method:
被代理对象调用的目标对象上的方法Object[] args:
方法的调用参数
Object invoke ( Object proxy, Method method, Object[ ] args)
② 动态代理编程实例
定义抽象对象接口 及实现接口的目标类 。 自定义实现了InvocationHandler
接口的调用处理器类 ,重写其中的invoke(代理对象,被调用的方法,方法的调用参数)
方法。 通过Proxy类的静态方法newProxyInstance(目标对象的类加载器,目标对象的接口,调用处理器)
方法生成代理对象 。 通过代理对象调用方法,实现对目标对象中方法的代理。
编程实例: 使用动态代理,同时为不同的对象进行计时代理。
import java. lang. reflect. InvocationHandler;
import java. lang. reflect. Method;
import java. lang. reflect. Proxy;
import java. util. Random;
interface EatLunch {
void eat ( ) ;
}
class Customer implements EatLunch {
private String name;
public Customer ( String name) {
this . name = name;
}
@Override
public void eat ( ) {
System. out. println ( name + "正在全聚德吃烤鸭..." ) ;
try {
Thread. sleep ( new Random ( ) . nextInt ( 200 ) ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
class TimerInvocationHandler implements InvocationHandler {
private Object target;
public TimerInvocationHandler ( Object target) {
this . target = target;
}
@Override
public Object invoke ( Object proxy, Method method, Object[ ] args) throws Throwable {
System. out. println ( "开始计时!" ) ;
long start = System. currentTimeMillis ( ) ;
Object result = method. invoke ( target, args) ;
long end = System. currentTimeMillis ( ) ;
System. out. println ( "计时结束,一共花费了" + ( end - start) + "分钟!" ) ;
return result;
}
}
public class DynamicProxy {
public static void main ( String[ ] args) {
Runnable child = new Child ( "星星" ) ;
TimerInvocationHandler handler = new TimerInvocationHandler ( child) ;
Runnable runProxy = ( Runnable) Proxy. newProxyInstance ( child. getClass ( ) . getClassLoader ( ) ,
child. getClass ( ) . getInterfaces ( ) , handler) ;
runProxy. run ( ) ;
Customer customer = new Customer ( "李四" ) ;
handler = new TimerInvocationHandler ( customer) ;
EatLunch eatProxy = ( EatLunch) Proxy. newProxyInstance ( Customer. class . getClassLoader ( ) ,
new Class [ ] { EatLunch. class } , handler) ;
eatProxy. eat ( ) ;
}
}
interface Runnable {
void run ( ) ;
}
class Child implements Runnable {
private String name;
public Child ( String name) {
this . name = name;
}
@Override
public void run ( ) {
System. out. println ( name + "正在操场上跑步..." ) ;
try {
Thread. sleep ( new Random ( ) . nextInt ( 10 ) ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
动态代理运行结果截图:
③ 动态代理与静态代理的区别
编译时与运行时: 静代理在编译时实现 ,编译完成后代理类是一个实际的class文件;而动态代理在运行时实现 ,编译完成代理类没有实际的class文件,在运行时动态生成代理类的字节码 并加载到JVM中。接口的实现: 静态代理要求代理类实现与目标类相同的接口,动态代理则不需要,可以解决静态代理中代理类太多、代码冗余且不易维护的缺点。
4. CGLIB动态代理
① CGLIB动态代理概述
使用JDK API
实现动态代理,要求目标类必须实现接口,若目标类不存在接口,则无法使用该方式。这时,可以使用CGLIB(Code Generation Library
)实现动态代理。 CGLIB是一个强大的、高性能的代码生成类库 ,底层使用小而快的字节码处理框架ASM 来转换字节码生成新的子类。子类中使用方法拦截技术 拦截父类调用的方法,从而实现对父类方法的动态代理。 在CGLIB动态代理中,看出子类是增强过的,子类对象就是代理对象。 注意:
目标类不能是final类型 。如过目标类是final类型,则无法创建其子类,会报错。目标对象的方法不能是final和static类型。 如果为final或static类型,则无法被拦截,也就无法执行额外的功能。
Spring AOP
和dynaop
都使用到了CGLIB实现动态代理,JDK代理与CGLib动态代理均是实现Spring AOP
的基础
② CGLIB动态代理编程实例
创建目标类 创建实现MethodInterceptor
接口的代理工厂类: ① 目标对象作为其内置对象; ② 定义一个方法:该方法利用工具类Enhancer
创建目标对象的子类对象(代理对象); ③ 重写intercept()
方法实现具体的代理业务。 动态代理的使用: ① 创建目标对象 ② 创建代理对象,需要将目标对象作为参数传入 ③ 通过代理对象调用目标对象的方法,实现动态代理。
import net. sf. cglib. proxy. Enhancer;
import net. sf. cglib. proxy. MethodInterceptor;
import net. sf. cglib. proxy. MethodProxy;
import java. lang. reflect. Method;
class Star {
private String name;
public Star ( ) { }
public Star ( String name) {
this . name = name;
}
public void sing ( ) {
System. out. println ( name + "唱了《忘情水》" ) ;
}
}
class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory ( Object target) {
this . target = target;
}
public Object getProxyInstance ( ) {
Enhancer enhancer = new Enhancer ( ) ;
enhancer. setSuperclass ( target. getClass ( ) ) ;
enhancer. setCallback ( this ) ;
return enhancer. create ( ) ;
}
@Override
public Object intercept ( Object o, Method method, Object[ ] objects, MethodProxy methodProxy) throws Throwable {
System. out. println ( "表演即将开始..." ) ;
Object result= method. invoke ( target, objects) ;
System. out. println ( "表演结束,谢谢观看!" ) ;
return result;
}
}
public class CGLIBProxy {
public static void main ( String[ ] args) {
Star star= new Star ( "刘德华" ) ;
Star startProxy= ( Star) new ProxyFactory ( star) . getProxyInstance ( ) ;
startProxy. sing ( ) ;
}
}
CGLIB动态代理程序运行结果截图: 注意:
需要同时添加cglib.jar
和asm.jar
,如果只添加了cglib.jar
报错如下:
Exception in thread "main" java. lang. NoClassDefFoundError: org/ objectweb/ asm/ Type
. . .
Star startProxy= ( Star) new ProxyFactory ( star) . getProxyInstance ( ) ;
如果目标对象定义了有参构造函数,必须显示定义无参构造函数。否则,报错如下:
Exception in thread "main" java. lang. IllegalArgumentException: Superclass has
no null constructors but no arguments were given