在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
代理模式的定义与特点
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。
其主要缺点是(可以使用动态代理方式解决这些缺点):
- 代理模式会造成系统设计中类的数量增加;
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度。
代理模式的结构与实现
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
模式的结构
代理模式的主要角色如下。
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态:在程序运行时,运用反射机制动态创建而成
模式的实现
1.静态代理
// 抽象主题(Subject)
public interface Subject {
void request();
}
// 真实主题(Real Subject)
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("访问真实主题方法..");
}
}
// 代理(Proxy)
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("访问真实主题之前的预处理。");
realSubject.request();
System.out.println("访问真实主题之后的后续处理。");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
}
}
// 测试结果
访问真实主题之前的预处理。
访问真实主题方法..
访问真实主题之后的后续处理。
2.动态代理
使用jdk的反射机制,创建对象的能力,创建的是代理类的对象。而不用创建类文件。不用写java文件。
动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。
jdk动态代理的实现:
反射包: java.lang.reflect,里面有三个类:InvcationHandler,Method,Proxy
1.InvcationHandler接口(调用处理器):就一个invoke()
invoke():表示代理对象要调用执行的功能代码。你的代理类要完成的功能就写在invoke()方法中。
代理类完成的功能:
1.调用目标方法,执行目标方法的功能
2.功能增强,在目标方法调用时,增强功能
方法原型:
参数:Object proxy: jdk创建的代理对象,无需赋值
Method method:目标类中的方法,jdk提供method对象
Object[] args: 目标类中方法的参数,jdk提供的
public Object invoke(Object proxy,Metjod,Object[] args)
InvcationHandler接口:表示你的代理要干什么
1.创建类实现接口InvcationHandler
2.重写invoke()方法,把原来静态代理中代理类要完成的功能,写在这里
Method类:表示方法的,确切的说就是目标类中的方法
作用:通过Method可以执行某个目标类的方法,Method.invoke();
Method.invoke(目标对象,方法的参数);
Proxy类:核心对象,创建代理对象;使用Proxy类型代替之前的new
1.静态方法 newProxyInstance()
2.作用:创建代理对象,等同于new操作
3.参数:public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader loader:类加载器,负责向内存中加载对象(目标对象的类加载器)
Class<?>[] interfaces:,目标对象实现的接口,也是反射获取
InvocationHandler h:需要自己写的,主要描述代理对象主要完成的功能
// 抽象主题(Subject)
public interface Subject {
void request();
}
// 真实主题(Real Subject)
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("访问真实主题方法..");
}
}
// 代理对象需要完成的功能
public class ProxyHandler implements InvocationHandler {
private Subject tar;
public ProxyHandler(Subject tar) {
this.tar = tar;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
tar.request();
System.out.println("After invoke " + method.getName());
return null;
}
}
// 测试类
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxyHandler realSubjectProxy = new ProxyHandler(realSubject);
// 创建代理对象
Subject o = (Subject) java.lang.reflect.Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), realSubjectProxy);
o.request();
}
}
// 测试结果
Before invoke request
访问真实主题方法..
After invoke request
工作中的实践
比如你所在的项目中,有一个功能是其他人(公司的其他部门,其他小组的人)写好的,你可以使用。
GoNong.java ,
GoNong gn = new GoNong();
gn.print();
你发现这个功能,现在还缺点,不能满足自己的项目的需要,需要在gn.print() 执行后,需要自己在增加代码。
用代理实现gn.print()调用时,增加自己的代码,而不用去修改原来的GoNong文件。
扩展
CGLIB代理
1.原理
代理为控制要访问的目标对象提供了一种途径。当访问对象时,它引入了一个间接的层。JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。JDK的动态代理用起来非常简单,当它有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的继承的类,该怎么办?现在我们可以使用CGLIB包。
2.什么是cglib
CGLIB是一个强大的高性能的代码生成包。
1>它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception(拦截);
2>hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的);
3>EasyMock和jMock是通过使用模仿(moke)对象来测试java代码的包。
它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
3.底层
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。所以cglib包要依赖于asm包,需要一起导入。下图为cglib与一些框架和语言的关系(CGLIB Library and ASM Bytecode Framework)
Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。
4.实例场景模拟
// 目标对象实现接口
public interface Subject {
void request();
}
// 目标对象
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("访问真实主题方法..");
}
}
// 代理对象
//public class CGLibProxy extends RealSubject {
// private RealSubject realSubject;
// public CGLibProxy(RealSubject realSubject) {
// this.realSubject = realSubject;
// }
@Override
// public void request() {
// System.out.println("Before invoke ");
// realSubject.request();
// System.out.println("After invoke ");
// }
//}
public class CGLibProxy implements MethodInterceptor{
public Object getProxyOfTarget(Class cls){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Before invoke");
methodProxy.invokeSuper(proxy, args);
System.out.println("After invoke");
return null;
}
}
// 测试类
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
RealSubject CgLibProxy = (RealSubject)(new CglibProxy().getProxyOfTarget(RealSubject.class));
CgLibProxy.request();
}
}
// 测试结果
Before invoke
访问真实主题方法..
After invoke
5.缺点:
- CGLib通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况(final修饰的方法不能被复写。)
- cglib动态代理需要导入相应jar包,它是基于字节码的,是生成了代理真实类的子类,无论代理真实类有没有实现接口都可以使用。