代理模式
概念
通过代理对象来访问具体的目标对象。可以把代理模式理解成一种中介的作用。
优点
- 职责清晰。
- 高扩展性。
- 智能化。
缺点
- 由于代理对象处于客户端和真正的主题之间,所以有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景
- 远程代理。
- 虚拟代理。
- Copy-on-Write代理。
- 保护(Protect or Access)代理。
- Cache代理。
- 防火墙(Firewall)代理。
- 同步化(Synchronization)代理。
- 智能引用(Smart Reference)代理。
注意
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
分类
按照代理创建的时期来分的话,分为静态代理、动态代理。
静态代理
由程序员创建或特定工具自动生成源码,再对其编译。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类。
- 优点
可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 缺点
要为每一个服务创建代理类,工作量大,不易管理。接口变化,代理类也要相应的改变。
- 实例
首先要有一个服务接口,被代理类和代理类都要去实现这个服务接口,并重写服务接口中的方法。
首先创建一个服务接口:
package com.spring.proxy;
/**
* 服务接口
* @author 17610
*
*/
public interface DoWork {
/**
* 定义一个接口方法
*/
public void work();
}
然后创建一个服务接口的具体实现类,其实就是被代理的类。实现服务接口。
package com.spring.proxy;
/**
* 服务接口的具体实现类(被代理类)
* @author 17610
*
*/
public class DoWorkImpl implements DoWork{
/**
* 重写父类接口的方法
*/
@Override
public void work() {
// TODO Auto-generated method stub
System.out.println("我要工作了。");
}
}
然后创建一个代理类,也要实现服务接口。
package com.spring.proxy;
/**
* 代理类
* @author 17610
*
*/
public class DoWorkProxy implements DoWork{
private DoWork doWork;
/**
* 构造方法(在实例化的时候初始化的)
* @param doWork
*/
DoWorkProxy(DoWork doWork){
this.doWork = doWork;
}
/**
* 重写实现的父类接口方法
*/
@Override
public void work() {
// TODO Auto-generated method stub
System.out.println("工作前的准备。");
doWork.work();
System.out.println("工作后的准备。");
}
}
最后是创建一个测试类。模拟客户端调用。
package com.spring.proxy;
/**
* 模拟客户端测试类
* @author 17610
*
*/
public class ProxyTest {
public static void main(String[] args) {
//实例化具体的接口实现类(被代理类)
DoWorkImpl doWorkImpl = new DoWorkImpl();
//实例化代理类
DoWorkProxy doWorkProxy = new DoWorkProxy(doWorkImpl);
//执行具体的方法
doWorkProxy.work();
}
}
运行效果:
工作前的准备。
我要工作了。
工作后的准备。
动态代理
不再需要手动创建代理类,只需要编写一个动态处理器。在程序运行时通过反射机制动态创建。
JDK动态代理(基于接口)
Jdk通过java.lang.reflect.Proxy包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance,
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler) throws IllegalArgumentException
- loader,表示类加载器,对于不同来源(系统库或网络等)的类需要不同的类加载器来加载,这是Java安全模型的一部分。可以使用null来使用默认的加载器;
- interfaces,表示接口或对象的数组,它就是前述代理对象和真实对象都必须共有的父类或者接口;
- handler,表示调用处理器,它必须是实现了InvocationHandler接口的对象,其作用是定义代理对象中需要执行的具体操作。
InvocationHandler之于Proxy,就如Runnable之于Thread。InvocationHandler接口中只有一个方法invoke,它的作用就跟Runnable中的run方法类似,定义了代理对象在执行真实对象的方法时所希望执行的动作。其原型如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
- proxy,表示执行这个方法的代理对象(要代理的对象);
- method,表示真实对象实际需要执行的方法;
- args,表示真实对象实际执行方法时所需的参数。
在实际的编程中,需要优先定义一个实现InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数。(或者在调用newProxyInstance方法时使用匿名内部类。)这样就得到了代理对象。
真实对象本身的实例化在调用处理器对象内部完成,实例化时需要的参数也应该及时传入调用处理器对象中。这样一来就完成了代理对象对真实对象的包装,而代理对象需要执行的额外操作也在invoke方法中处理。
其后,在客户端中,如果需要使用真实对象时,就可以用代理对象来替代它了(有时需要类型强制转化)。
- 特点
- 被代理类(实现类)通过接口定义业务方法。
- 代理对象,不需要实现接口。
- 代理对象的生成是利用了JDK的API,动态的在内存中构建代理对象(需要创建一个动态处理器)。
- 实例代码:
JDK动态代理是基于接口的。
创建一个主题接口,即创建一个被代理类的接口。
package com.spring.proxy.dynamic2;
/**
* 主题接口
* @author 17610
*
*/
public interface Subject {
//定义一个接口方法
String doWork();
}
创建实现了主题接口的真正主题,也就是我们真正的被代理类。
package com.spring.proxy.dynamic2;
/**
* 具体的主题(被代理的真正的类)
* @author 17610
*
*/
public class StudySubject implements Subject{
//重写父类接口的方法
@Override
public String doWork() {
System.out.println("先学习,再工作。");
return "StudySubject-doWork方法";
}
}
另一个被代理的类。
package com.spring.proxy.dynamic2;
/**
* 具体的主题(真正的被代理的类)
* @author 17610
*
*/
public class DoWorkSubject implements Subject{
//重写父类接口的方法
@Override
public String doWork() {
System.out.println("先工作,再学习。");
return "DoWorkSubject-doWork方法";
}
}
创建我们的代理类。
package com.spring.proxy.dynamic2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类处理器类
* @author 17610
*
*/
public class ProxyHandler implements InvocationHandler{
//维护一个当前类私有的目标对象
private Object obj;
//新创建代理实例的方法(当前方法的本质就是调用了Proxy的newProxyInstance方法)
public Object newProxyInstance(Object realObj){
this.obj = realObj;
Class<? extends Object> classType = this.obj.getClass();
//Proxy.newProxyInstance(目标对象使用的类加载器,目标对象实现的接口类型,使用的事务处理器)
Object newProxyInstance = Proxy.newProxyInstance(classType.getClassLoader(), classType.getInterfaces(), this);
return newProxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("这里是代理类的invoke方法");
//执行目标对象的方法
Object invoke = method.invoke(obj, args);
System.out.println("执行了什么方法:" + invoke);
return null;
}
}
最后就是新建一个测试类,来看看效果
package com.spring.proxy.dynamic2;
import sun.nio.cs.Surrogate;
/**
* 具体的测试类
* @author 17610
*
*/
public class ProxyDynamicTest {
public static void main(String[] args) {
//创建目标对象的代理对象
Subject subject = (Subject) new ProxyHandler().newProxyInstance(new StudySubject());
//创建目标对象的代理对象
Subject subject2 = (Subject) new ProxyHandler().newProxyInstance(new DoWorkSubject());
//通过代理对象执行具体的方法
subject.doWork();
System.out.println("-----------------------");
//通过代理对象执行具体的方法
subject2.doWork();
}
}
运行结果就是:
这里是代理类的invoke方法
先学习,再工作。
执行了什么方法:StudySubject-doWork方法
-----------------------
这里是代理类的invoke方法
先工作,再学习。
执行了什么方法:DoWorkSubject-doWork方法
CGLIB动态代理(子类代理)
- 介绍
在内存中构建一个子类对象从实现对目标对象功能的扩展。
采用了非常底层的字节码技术,原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑。
- 特点
- 不能对final修饰的类进行代理,因为是采用的是继承。
- 是实现Spring AOP的基础。
- CGLIB与JDK的对比:
- CGLIB创建的动态代理对象比JDK创建的动态代理的对象性能更高。
- CGLIB创建代理对象时所花费的时间比JDK创建代理对象的多。
- 对于单例的对象,无需频繁创建对象,用CGLIB合适;反之,使用JDK合适。
- CGLIB对于final修饰的方法不能进行代理,因为采用的是动态创建子类得方法。
- 实例:
首先创建目标对象,不需要实现任何接口
package com.spring.proxy.cglib.dynamic2;
/**
* 目标对象(不需要实现任何接口)
* @author 17610
*
*/
public class WorkSubject {
//在目标对象中定义具体的方法
public void doTest(){
System.out.println("CGLIB代理目标对象——无参方法");
}
//定义一个有参方法
public String doTest(String name){
System.out.println("CGLIB代理目标对象——有参方法" + name);
return "WorkSubject-doTest方法";
}
}
然后创建代理类,这里有一个创建目标对象的代理对象的方法,这个方法中的步骤是固定的,就是:
1、实例化工具类:Enhancer en = new Enhancer();
2、设置父类:en.setSuperclass(obj.getClass());
3、设置回调函数:en.setCallback(this);
4、创建子类(代理对象):Object create = en.create();
package com.spring.proxy.cglib.dynamic2;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* CGLIB的子类代理工厂
* @author 17610
*
*/
public class ProxyDynamic implements MethodInterceptor{
//维护一个目标对象
private Object obj;
//创建一个有参构造方法
public ProxyDynamic(Object object){
this.obj = object;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//先实例化工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(obj.getClass());
//设置回调函数
en.setCallback(this);
//创建子类(代理对象)
Object create = en.create();
return create;
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("方法执行前");
//执行目标对象的方法
Object invoke = arg1.invoke(obj, arg2);
System.out.println("方法执行后");
return invoke;
}
}
最后就是测试效果了
package com.spring.proxy.cglib.dynamic2;
/**
* 具体的测试类
* @author 17610
*
*/
public class ProxyDynamicTest {
public static void main(String[] args) {
//实例化代理对象
WorkSubject work = (WorkSubject) new ProxyDynamic(new WorkSubject()).getProxyInstance();
//执行具体的方法
work.doTest();
System.out.println("------------");
work.doTest("CGLIB");
}
}
运行结果是:
方法执行前
CGLIB代理目标对象——无参方法
方法执行后
------------
方法执行前
CGLIB代理目标对象——有参方法CGLIB
方法执行后
补充
装饰模式和代理模式的区别
- 装饰器模式关注于在一个对象上动态的添加方法,然而代理对象关注于控制对 对象的访问。换句话说,用代理模式,代理类可以对它的客户隐藏一个对象的具体信息。
- 当使用代理模式的时候,尝尝在一个类中创建一个对象的实例。当我们使用装饰器模式时,通常的做法是将原始对象作为一个参数传递给装饰者的构造器。
外观模式和代理模式的区别
代理与外观的主要区别在于,代理对象代表一个单一对象而外观对象代表一个子系统,代理的客户对象无法直接访问对象,由代理提供单独的目标对象的访问,而通常外观对象提供对子系统各元件功能的简化的共同层次的调用接口。代理是一种原来对象的代表,其他需要与这个对象打交道的操作都是和这个代表交涉的。
适配器模式和代理模式的区别
适配器模式改变所考虑的对象的接口,代理模式不能改变所代理对象的接口。