代理模式(Proxy Pattern)
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
- 是AOP(Aspect Oriented Programming面向切面编程)的核心实现机制!
核心作用
- 通过代理,控制对对象的访问!
- 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即:AOP的微观实现!)
图示代理模式的一个例子
代理模式的UML类图
抽象角色
- 定义代理角色和真实角色的公共对外方法
真实角色
- 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
- 关注真正的业务逻辑!
代理角色
- 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加
自己的操作。
将统一的流程控制放到代理角色中处理!
代理模式的分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
• JDK自带的动态代理
• javaassist字节码操作库实现
• CGLIB
• ASM(底层使用指令,可维护性较差)
应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
开发框架中应用场景:
- struts2中拦截器的实现
- 数据库连接池关闭处理
- Hibernate中延时加载的实现
- mybatis中实现拦截器插件
- AspectJ的实现
- spring中AOP的实现
• 日志拦截
• 声明式事务处理 - web service
- RMI远程方法调用
实际上,随便选择一个技术框架都会用到代理模式!!
实例分析
代课老师代理授课。
1.静态代理实现
- 定义一个接口:ITeacherDao
- 目标对象 TeacherDAO 实现接口 ITeacherDAO
- 使用静态代理方式,就需要在代理对象TeacherDAOProxy 中也实现 ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
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() {
// TODO Auto-generated method stub
System.out.println("开始代理(前置操作)");
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();
}
}
开始代理(前置操作)
代课老师准备上课
老师授课中 。。。。。
提交(后置操作)
在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。一旦接口增加方法,目标对象与代理对象都要维护。这就是所谓的静态即写死的形式,一旦接口增加方法,目标对象与代理对象都需要增加这个方法的重写。
2.动态代理(JDK代理)实现
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
java.lang.reflect.Proxy
• 作用:动态生成代理类和对象java.lang.reflect.InvocationHandler
(处理器接口)
• 可以通过invoke方法实现对真实角色的代理访问。
• 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象
public interface ITeacherDao {
void teach(); // 授课方法
}
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老师授课中.... ");
}
}
public class ProxyFactory {
//维护一个目标对象 , Object
private Object target;
//构造器 , 对target 进行初始化
public ProxyFactory ( Object target ) {
this.target = target;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance () {
/* 说明
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入。这里可以使用匿名调用,也可以重新实例化一个类来使用(这个类需要实现InvocationHandler接口,重写invoke方法即可)
*/
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代理开始~~");
System.out.println("代理老师准备上课");
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target, args);
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=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
proxyInstance.teach();
}
}
proxyInstance=class com.sun.proxy.$Proxy0
JDK代理开始~~
代理老师准备上课
老师授课中....
JDK代理提交
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,即便接口中再增加新的方法,我们也不用修改代理对象的类。我们可以更加灵活和统一的处理众多的方法。
静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理。
3.动态代理(Cglib代理)实现
- Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展。
- Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
- Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
可以直接在Maven仓库中下载jar包:
https://mvnrepository.com/
public class TeacherDao {
public String teach() {
System.out.println(" 老师授课中 , 我是cglib代理,不需要实现接口 ");
return "Cglib代理";
}
}
需要实现MethodInterceptor
接口,重写intercept
方法
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 方法,会调用目标对象的方法 intercept,拦截的意思,interceptor拦截器
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理模式 ~~ 开始");
System.out.println("代课老师准备上课");
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 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
代课老师准备上课
老师授课中 , 我是cglib代理,不需要实现接口
Cglib代理模式 ~~ 提交
res=Cglib代理
- 在内存中动态构建子类,注意代理的类不能为 final,否则报错
java.lang.IllegalArgumentException:
- 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
几种代理模式的变体
-
防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。 -
缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据库取,然后缓存。 -
远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。 -
同步代理:主要使用在多线程编程中,完成多线程间同步工作同步代理:主要使用在多线程编程中,完成多线程间同步工作。
面向切面的编程AOP简介
AOP(Aspect-Oriented Programming,面向切面的编程)
它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。
- 切面(Aspect):其实就是共有功能的实现。
- 通知(Advice):是切面的具体实现。
- 连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。
- 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。
- 目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象
- 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。
- 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。
开源的AOP框架
– AspectJ
关于AOP的具体使用,后续再进行总结。
问:静态代理里边,为什么不把实例化真实对象放在代理对象里边?
静态代理里边,为什么不把实例化真实对象放在代理对象里边?
public class TeacherDaoProxy implements ITeacherDao{
// 为什么不在代理对象里边直接实例化????????
private ITeacherDao target = new TeacherDao();
@Override
public void teach() {
// TODO Auto-generated method stub
System.out.println("开始代理(前置操作)");
System.out.println("代课老师准备上课");
target.teach();
System.out.println("提交(后置操作)");
}
}
public class Client {
public static void main(String[] args) {
//把实例化,直接放在代理类里边,不就不用在客户端实例化,然后再传给代理类了??
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy();
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
答:
因为代理对象,重点就在“代理”两个字上。如果直接在代理对象里面实例化被代理的对象,那么被代理的对象就会被“定死了(写死了)”。然而通过构造器传入接口对象,在客户端调用时传入真实的被代理对象,这样TeacherDaoProxy就可以代理所有实现了ITeacherDao接口的所有类。 从面向抽象编程,而不是面向实际编程考虑: 你下面的这种写法,private ITeacherDao target = new TeacherDao(); 就只能代理TeacherDao,如果还有别的实现了ITeacherDao的类,则就不能代理了。 这样写,应该会有更广泛的使用性。 从聚合与合成关系考虑: 这也是聚合和组合关系的一种体现,你的方式是直接在成员变量直接实例化,属于类之间的组合关系,意味着代理对象与被代理对象是不可分开的。代理模式的其中一个作用是对被代理对象功能的一种扩展,所以我觉得聚合关系要比合成关系好一点。
转载自:https://blog.csdn.net/qq_42937522/article/details/105067563