代理模式
1. 介绍
1.1 功能
为其他对象提供一种代理以控制对这个对象的访问
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式
1.2 角色
-
Subject抽象主题角色
定义代理要做的工作
-
RealSubject具体主题角色
是业务逻辑的具体执行者,活都让他来干。
-
Proxy代理主题角色
它负责对真实角色的应用,把所有抽象主题类定义的方法委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
2. 静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
2.1 具体要求
- 定义一个接口
ITeacherDao
- 目标对象实现接口
ITeacherDAO
- 使用静态代理方式,就需要在代理对象
TeacherDAOProxy
中也实现ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来 调用目标对象的方法。
2.2 示例
public class TeacherDaoProxy implements ITeacherDao {
// 目标对象,被代理的目标
private ITeacherDao target;
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
// 前置操作...
// 执行方法,由于实现了同一个接口ITeacherDao,所以方法一致,都是teach
target.teach();
// 后置操作...
}
}
public static void main(String[] args) {
// 创建目标对象,即被代理的对象
TeacherDao teacherDao = new TeacherDao();
// 创建代理对象,并传入被代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
// 执行代理对象的方法,代理对象再调用目标对象的方法
teacherDaoProxy.teach();
}
2.3 优缺点
优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
缺点:
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
3. 动态代理
又叫JDK
代理、接口代理。
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
是AOP的核心
3.1 动态代理基本知识
特点:字节码谁用谁创建,谁用谁加载
作用:不修改源码的基础上增强方法
分类:基于接口的动态代理、基于子类的动态代理
3.2 基于接口的动态代理
设计的类:Proxy
(JDK官方提供)
如何创建代理对象:使用Proxy
类中的newProxyInstance()
方法
创建代理对象的要求:被代理的对象最少实现一个接口
3.2.1 方法参数
newProxyInstance(ClassLoader,Class[],InvocationHandler)
ClassLoader
类加载器:- 用于加载代理对象的字节码的,和被代理对象使用相同的类加载器,固定写法
Class[]
字节码数组:- 它是用于让代理对象和被代理对象有相同的方法,固定写法
InvocationHandler
增强控制器:- 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是用匿名内部类(但不是必须的)
- 约定了执行目标对象的方法时。会触发的代理行为
3.2.2 代码例子:
在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。 而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪人来找了。
艺人接口:
public interface IActor {
/**
* 基本演出
* @param money
*/
public void basicAct(float money);
/**
* 危险演出
* @param money
*/
public void dangerAct(float money);
}
演员实现类:
/**
* 一个演员
*实现了接口,就表示具有接口中的方法实现。即:符合经纪公司的要求
*/
public class Actor implements IActor{
public void basicAct(float money){
System.out.println("拿到钱,开始基本的表演:"+money);
}
public void dangerAct(float money){
System.out.println("拿到钱,开始危险的表演:"+money);
}
}
动态代理:
核心就是Proxy.newProxyInstance
方法和InvocationHandler
内部类invoke
方法的实现
public class Client {
public static void main(String[] args) {
//一个剧组找演员:
final Actor actor = new Actor();//直接
//代理:间接。
IActor proxyActor = (IActor) Proxy.newProxyInstance(
actor.getClass().getClassLoader(),
actor.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 参数:
* proxy:代理对象的引用。不一定每次都用得到
* method:当前执行的方法对象
* args:执行方法所需的参数
* 返回值:当前执行方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
String name = method.getName();
Float money = (Float) args[0]; Object rtValue = null;
//每个经纪公司对不同演出收费不一样,此处开始判断
if("basicAct".equals(name)){
//基本演出,没有2000不演
if(money > 2000){
//看上去剧组是给了8000,实际到演员手里只有4000
//这就是我们没有修改原来basicAct方法源码,对方法进行了增强
rtValue = method.invoke(actor, money/2);
}
}
if("dangerAct".equals(name)){
//危险演出,没有5000不演
if(money > 5000){
//看上去剧组是给了50000,实际到演员手里只有25000
//这就是我们没有修改原来dangerAct方法源码,对方法进行了增强
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
} // invoke方法声明完毕
} // InvocationHandler匿名内部类对象声明完毕
}); // Proxy.newProxyInstance方法调用完毕
//没有经纪公司的时候,直接找演员。
// actor.basicAct(1000f);
// actor.dangerAct(5000f);
//剧组无法直接联系演员,而是由经纪公司找的演员
proxyActor.basicAct(8000f);
proxyActor.dangerAct(50000f);
}
}
4. Cglib代理/基于子类动态代理
可以直接在内存中动态地创建对象,而不需要实现接口。属于动态代理的范畴
静态代理和
JDK
动态代理都要求目标对象实现了一个接口,但是有时候目标对象仅仅是一个单独的对象,此时可以使用Cglib代理
要求:被代理对象不能是final类
用到的类:Enhancer
用到的方法:create(Class, Callback)
4.1 方法参数
Class
:被代理对象的字节码Callback
:用于提供增强的代码- 我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类
- 一般都实现该接口的子接口实现类:
MethodInterceptor
4.2 代码例子
演员类,不实现接口
public class Actor{
//没有实现任何接口
public void basicAct(float money){
System.out.println("拿到钱,开始基本的表演:"+money);
}
public void dangerAct(float money){
System.out.println("拿到钱,开始危险的表演:"+money);
}
}
动态代理
使用Enhancer类
public class Client {
// 基于子类的动态代理
public static void main(String[] args) {
final Actor actor = new Actor();
// 声明,并获取代理对象
Actor cglibActor =
(Actor) Enhancer.create(actor.getClass(), // 设置父类
new MethodInterceptor() { // 设置回调函数
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 在此方法内部就可以对被代理对象的任何方法进行增强。
* 参数:
* 前三个和基于接口的动态代理是一样的。
* MethodProxy:当前执行方法的代理对象。
* 返回值:当前执行方法的返回值
*/
@Override
public Object intercept(Object proxy,
Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
if("basicAct".equals(name)){
//基本演出
if(money > 2000){
rtValue = method.invoke(actor, money/2);
}
}
if("dangerAct".equals(name)){
//危险演出 if(money > 5000){
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
});
cglibActor.basicAct(10000); cglibActor.dangerAct(100000);
}
}
5. 优点和应用
5.1 优点
-
职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务。同时也使得编程简洁清晰。
-
高拓展性
具体主题角色是随时都会发生变化的,只要它实现了接口,就可以动态替换实现。
5.2 应用
Spring AOP
Struts将表单元素映射到对象上
5.3 拓展
防火墙代理、缓存代理、远程代理、同步代理、反向代理