在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
其主要缺点是:
代理模式会造成系统设计中类的数量增加
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
增加了系统的复杂度;
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理。
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
动态:在程序运行时,运用反射机制动态创建而成
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
前面分析了代理模式的结构与特点,现在来分析以下的应用场景。
远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
接口
package StaticProxy;
public interface IUserDao {
public void save();
}
被代理类
package StaticProxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
代理类
package StaticProxy;
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}
@Override
public void save() {
//扩展了额外功能
System.out.println("开启事务");
target.save();
System.out.println("提交事务");
}
}
测试
package StaticProxy;
public class StaticUserProxy {
public static void main(String[] args) {
//目标对象
IUserDao target = new UserDao();
//代理对象
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();
}
}
在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。
真实主题与代理主题一一对应,增加真实主题也要增加代理。
设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP。
代理类在程序运行时创建的代理方式被成为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指令”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
动态代理其实是一种方便运行时候动态的处理代理方法的调用机制,通过代理可以让调用者和实现者之间解耦。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
静态代理,工程师编辑代理类代码,实现代理模式;在编译期就生成了代理类。
基于 JDK 实现动态代理,通过jdk提供的工具方法Proxy.newProxyInstance动态构建全新的代理类(继承Proxy类,并持有InvocationHandler接口引用 )字节码文件并实例化对象返回。(jdk动态代理是由java内部的反射机制来实例化代理对象,并代理的调用委托类方法)
基于CGlib 动态代理模式 基于继承被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可。(cglib动态代理底层是借助asm字节码技术
基于 Aspectj 实现动态代理(修改目标类的字节,织入代理的字节,在程序编译的时候 插入动态代理的字节码,不会生成全新的Class )
基于 instrumentation 实现动态代理(修改目标类的字节码、类装载的时候动态拦截去修改,基于javaagent) -javaagent:spring-instrument-4.3.8.RELEASE.jar (类装载的时候 插入动态代理的字节码,不会生成全新的Class )
他的具体实现机制主要两个类:
InvocationHandler
Proxy
根据注解描述可知,InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
InvocationHandler接口,它就只有一个方法invoke。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用,我们看到invoke方法一共接受三个参数,那么这三个参数分别代表什么呢?
proxy - 在其上调用方法的代理实例也就是代理的真实对象
method - 指的是我们所要调用真实对象的某个方法的Method对象
args - 指的是调用真实对象某个方法时接受的参数
实例:
接口
package JDKProxy;
public interface IUserDao {
public void save();
}
被代理类
package JDKProxy;
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}
代理类
package JDKProxy;
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 getProxyInstance() {
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 null;
}
});
}
}
测试代码
package JDKProxy;
public class TestProxy {
public static void main(String[] args) {
IUserDao target = new UserDao();
//输出目标对象信息
System.out.println(target.getClass());
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
//输出代理对象信息
System.out.println(proxy.getClass());
//执行代理方法
proxy.save();
}
}
CGLIB创建动态代理类过程
查找目标类上的所有非final的public类型的方法定义;
将符合条件的方法定义转换成字节码;
将组成的字节码转换成相应的代理的class对象;
实现MethodInterceptor接口,用来处理对代理类上所有方法的请求。
实例:
1、引入依赖
cglib
cglib-nodep
3.2.5
2、具体实例
被代理类
package Proxy;
/**
* 被代理类(目标类)
*/
public class Service {
/**
* final 方法不能被子类覆盖
*/
public final void finalMethod() {
System.out.println("Service.finalMethod 执行了");
}
public void publicMethod() {
System.out.println("Service.publicMethod 执行了");
}
}
package Proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 代理类
*/
public class CglibDynamicProxy implements MethodInterceptor {
/**
* 目标对象(也被称为被代理对象)
*/
private Object target;
public CglibDynamicProxy(Object target) {
this.target = target;
}
/**
* 如何增强
* @param obj 代理对象引用
* @param method 被代理对象的方法的描述引用
* @param args 方法参数
* @param proxy 代理对象 对目标对象的方法的描述
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CglibDynamicProxy intercept 方法执行前-------------------------------");
System.out.println("obj = " + obj.getClass());
System.out.println("method = " + method);
System.out.println("proxy = " + proxy);
Object object = proxy.invoke(target, args);
System.out.println("CglibDynamicProxy intercept 方法执行后-------------------------------");
return object;
}
/**
* 获取被代理接口实例对象
*
* 通过 enhancer.create 可以获得一个代理对象,它继承了 target.getClass() 类
*
* @param
* @return
*/
public T getProxy() {
Enhancer enhancer = new Enhancer();
//设置被代理类
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// create方法正式创建代理类
return (T) enhancer.create();
}
public static void main(String[] args) {
// 1. 构造目标对象
Service target = new Service();
// 2. 根据目标对象生成代理对象
CglibDynamicProxy proxy = new CglibDynamicProxy(target);
// 获取 CGLIB 代理类
Service proxyObject = proxy.getProxy();
// 调用代理对象的方法
proxyObject.finalMethod();
proxyObject.publicMethod();
}
}
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。
CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy的优势:
最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。
基于CGLIB的优势:
无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。