设计模式——代理模式
一、代理模式介绍
代理模式(Proxy Pattern),是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,被代理对象可以是远程对象、创建开销大的对象以及需要安全控制的对象。
使用代理模式的目的主要有两个:
1)保护目标对象;
2)增强目标对象(代理一般被理解成代码增强,实际上是在原代码逻辑前后增加一些代码逻辑,而这一过程调用者无察觉)
静态代理模式的UML类结构图如下所示:
其中Subject是顶层接口,RealSubject是真实对象,即被代理对象,Porxy是代理对象。代理对象持有被代理对象的引用,再由客户端调用代理对象的方法,同时调用被代理对象的方法,并在代理对象前后增加一些处理代码。
二、代理模式的分类
(1)静态代理
(2)动态代理:1)JDK实现动态代理;2)Cglib实现动态代理
三、模板模式代码实例
场景实例
编写教师类,要求实现:
(1)用代理方法实现
(2)教师类中含teach()方法
1、静态代理实现
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者集成相同父类。
//接口类
public interface ITeacherDao {
void teach();
}
//被代理对象
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("教书");
}
}
//代理对象
public class TeacherDaoProxy implements ITeacherDao
{
private ITeacherDao target;//目标对象,通过接口来聚合
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理");
target.teach();
System.out.println("提交。。。");//
}
}
//客户端调用
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
teacherDaoProxy.teach();
}
}
(1)优点:在不修改目标对象功能的前提下能够通过代理对象对目标功能进行扩展
(2)缺点:代理对象需要与目标对象实现一样的接口,所以会产生大量代理类;一旦接口增加方法,目标对象与代理对象都需要维护。
2、动态代理
(1)动态代理的代理对象无须实现接口,但是目标对象需要实现接口
(2)代理对象的生成利用JDK的API,动态的在内存中构建代理对象
(3)与静态代理的区别:
- 静态代理只能手动完成代理操作,如果被代理类增加新的方法,代理类需要相应增加,违背OCP原则
- 动态代理采用在运行时动态生成代码的形式,取消了对被代理类的扩展限制,遵守OCP原则
- 若动态代理都需要对目标类的增强逻辑进行扩展,可结合策略模式,新增相应策略类编可完成,无须修改代理类源码。
1)JDK实现动态代理
(1)JDK代理类所在包:java.lang.reflect.Proxy
(2)JDK实现动态代理需要实现newProxyInstance方法,重写invoke方法具体形式如下所示
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
public Object invoke(Object proxy, Method method, Object[] args)
JDK动态代理生成步骤
- 获取被代理对象的引用,并且获取它的所有接口,通过反射获取。
- JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类所实现的所有接口。
- 动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用。
- 编译新生成的java代码.class文件
- 重新加载到JVM中运行
实现案例
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("JDK代理开始");
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target,args);//此处调取了target类(TeacherDao)中的teach方法
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
public class Client {
public static void main(String[] args) {
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象,可以转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
//通过代理对象调用方法
proxyInstance.teach();
}
}
//结果
JDK代理开始
教书
JDK代理提交
2)Cglib实现代理
(1)与静态代理与JDK代理模式的**区别**:前两个代理中的目标对象均须实现一个接口,而Cglib代理的目标对象仅是一个单独的对象,**无须实现接口**,它通过动态继承目标对象实现动态代理。
(2)Cglib代理,是在内存中构建一个子类对象从而实现对目标对象功能扩展。
(3)实现步骤:
1)导入Cglib的jar文件
2)在内存年动态中构建子类(代理类不能为final)
3)若目标方法为final或static方法,则不会执行目标方法额外的业务方法。
4)Cglib实现动态代理需要重写intercept方法,具体形式如下所示
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//传入被代理对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象,是target对象的代理对象
public Object getProxyInstance() {
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.设置子类对象,即代理对象
return enhancer.create();
}
//重写intercept对象,会调用目标对象的方法
//可看作是Jdk代理中的Invoke方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB代理模式开始!");
Object returnVal = method.invoke(target,args);
System.out.println("CGLIB代理提交");
return returnVal;
}
}
public class Client {
public static void main(String[] args) {
TeacherDao target = new TeacherDao();
//获取到代理对象,并且将目标对象传递个给代理对象
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象方法,出发intecept方法,从而实现对目标对象的调用
proxyInstance.teach();
}
}
3)Jdk动态代理与Cglib动态代理的对比
- Jdk动态代理实现了被代理对象的接口,Cglib代理继承了被代理对象;
- Jdk动态代理和Cglib代理都在运行期生成字节码,Jdk动态代理直接写Class字节码,Cglib代理使用ASM框架写Class字节码,故Cglib代理实现更复杂,生成代理类比Jdk动态代理效率低;
- Jdk动态代理调用代理方法是通过反射机制调用的,Cglib代理是通过FastClass机制直接调用方法,故Cglib代理的执行效率更高
4)FastClass机制
为代理类和被代理类各生成一个类,该类为代理类或被代理类分配一个int类型的index。当该index当做一个入参,FastClass就可以直接定位要调用的方法并直接进行调用,可省去反射调用