1、简介
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子,假如我想租一个房子,虽然我可以到处自己转转看看房子,然后再和房东商量价钱啊、水电啊等事情,但是这有点浪费时间了,这个时候呢,我可以找一个中介,由他来负责为我处理找房子,和房东商量等事情,我只需要挑选你想租的房子,然后付钱就可以了。
]
- Subject
抽象主题角色:可以是抽象类,也可以是接口。抽象主题是一个普通的业务类型,无特殊要求。 - RealSubject
具体主题角色:也叫做被委托角色或被代理角色,是业务逻辑的具体执行者。 - Proxy
代理主题角色:也叫做委托类或代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后处理工作。
使用场景:
代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
2、静态代理
//抽象接口
interface BuyHouse{
public void buyHouse();
}
//具体实现类
class BuyHouseImpl implements BuyHouse{
@Override
public void buyHouse() {
System.out.println("我想买房子");
}
}
//代理实现类
class BuyHouseProxy implements BuyHouse{
private BuyHouse buyHouse;
public BuyHouseProxy(BuyHouse buyHouse){
this.buyHouse = buyHouse;
}
@Override
public void buyHouse() {
System.out.println("买房前准备");
buyHouse.buyHouse();
System.out.println("装修");
}
}
public class Proxy {
public static void main(String[] args) {
BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHouse();
BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHouse();
}
}
静态代理总结:
- 优点
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。对于如上的客户端代码,Subject subject = new ConcreteSubject(); Proxy proxy = new Proxy(subject);可以应用工厂方法将它隐藏。 - 缺点
代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为Subject类的访问提供了代理,但是如果还要为其他类如AnotherSubject类提供代理的话,就需要我们再次添加代理AnotherSubject的代理类。
由于静态代理的这个缺点,就需要使用动态代理。
3、动态代理
从静态代理会发现——每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类。所以我们想办法通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。
动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。
在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类的支持。代码如下:
//抽象接口
interface BuyHouse{
public void buyHouse();
}
//具体实现类
class BuyHouseImpl implements BuyHouse{
@Override
public void buyHouse() {
System.out.println("我想买房子");
}
}
//具体实现类
//具体实现类
class BuyBigHouseImpl implements BuyHouse{
@Override
public void buyHouse() {
System.out.println("我想买大房子");
}
}
//动态代理实现类
class DynamicBuyHouseProxy implements InvocationHandler{
private BuyHouse buyHouse;
public Object DynamicBuyHouseProxy(BuyHouse buyHouse){
this.buyHouse = buyHouse;
Object result = java.lang.reflect.Proxy.newProxyInstance(buyHouse.getClass().getClassLoader(),
buyHouse.getClass().getInterfaces(),this);
return result;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买房前准备");
Object o = method.invoke(buyHouse,args);
System.out.println("装修");
return o;
}
}
public class Proxy {
public static void main(String[] args) {
DynamicBuyHouseProxy dynamicBuyHouseProxy = new DynamicBuyHouseProxy();
//一个代理类可以代理多个具体实现类
BuyHouse buyHouse = (BuyHouse) dynamicBuyHouseProxy.DynamicBuyHouseProxy(new BuyHouseImpl());
buyHouse.buyHouse();
BuyHouse buyBigHouse = (BuyHouse) dynamicBuyHouseProxy.DynamicBuyHouseProxy(new BuyBigHouseImpl());
buyBigHouse.buyHouse();
}
}
由上述代码可以看到,针对不同的实现类,只需要使用一个代理类即可。
补充说明:
关于动态代理类中的三个参数invoke(Object proxy, Method method, Object[] args),
- proxy: 就是Proxy 的一个动态实例, 这个参数在invoke方法体内可以完全不用,所以很难理解它的作用,甚至知乎、Stackoverflow 上的回答都感觉不太对。Stackoverflow
上一个回答说作用是可以将代理对象返回以进行连续调用,并写了demo(https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca),难道不需要连续调用的话就没用了吗?又查了很多资料,终于找到了一个比较符合逻辑的解释:invoke 第二个参数是method 怎么来的?
P r o x y 动 态 实 例 ( 即 第 一 个 参 数 p r o x y ) 生 成 后 ( 是 c l a s s 文 件 ) , 通 过 反 向 编 译 , 可 以 看 到 里 面 有 如 下 一 段 静 态 代 码 块 ( 来 源 : h t t p : / / r e j o y . i t e y e . c o m / b l o g / 1627405 ) , 也 就 是 说 只 有 p r o x y 实 例 在 I n v o c a t i o n H a n d l e r 实 现 类 里 加 载 才 能 产 生 第 二 个 参 数 m e t h o d ( 静 态 代 码 块 是 虚 拟 机 加 载 类 的 时 候 执 行 的 , 而 且 只 执 行 一 次 ) , 所 以 Proxy 动态实例 (即第一个参数proxy) 生成后(是class 文件),通过反向编译, 可以看到里面有如下一段静态代码块(来源:http://rejoy.iteye.com/blog/1627405),也就是说只有proxy实例在InvocationHandler 实现类里加载才能产生第二个参数method(静态代码块是虚拟机加载类的时候执行的,而且只执行一次),所以 Proxy动态实例(即第一个参数proxy)生成后(是class文件),通过反向编译,可以看到里面有如下一段静态代码块(来源:http://rejoy.iteye.com/blog/1627405),也就是说只有proxy实例在InvocationHandler实现类里加载才能产生第二个参数method(静态代码块是虚拟机加载类的时候执行的,而且只执行一次),所以Proxy 实例要把自己传给InvocationHandler的invoke 方法。 m3=Class.forName(“dynamic.proxy.UserService”).getMethod(“add”, new
Class[0]);
InvocationHandler 源码中对proxy 的注释:the proxy instance thatthe method was invoked on- method: 真实对象要实现的业务方法,由$Proxy 实例的静态代码块得到。
- args: 第二个参数method 的参数。
- 优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。 - 缺点
Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处——它始终无法摆脱仅支持 interface代理的桎梏,因为它的设计注定了这个遗憾。动态生成的代理类的继承关系图,已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
4、CGLIB动态代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法interception(拦截)。
- Cglib包的底层是通过使用一个小块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入Spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码如下:
//具体的类,没有实现任何接口
class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
public class Cglib {
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
一个字,牛x!
扩展:
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理