代理模式
实现代理模式,需要以下几个步骤:
- 定义真实对象和代理对象的公共接口
- 代理对象内部保存对真实目标对象的引用
- 访问者仅能通过代理对象访问真实目标对象,不可直接访问目标对象
代理模式的定义:给目标对象提供一个代理对象,代理对象包含该目标对象,并控制对该目标对象的访问
代理模式的目的:
- 通过代理对象的隔离,可以在对目标对象访问前后增加额外的业务逻辑,实现功能增强
- 通过代理对象访问目标对象,可以防止系统大量地直接对目标对象进行不正确的访问,出现不可预测的后果
静态代理与动态代理
静态代理
- 优点:代码结构简单,较容易实现
- 缺点:无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则
动态代理
- 优点:能够动态适配特定的代理场景,扩展性较好,符合软件工程的开闭原则
- 缺点:动态代理需要利用到反射机制和动态生成字节码,导致其性能会比静态代理稍差一些
相同点
- 都能够实现代理模式
- 无论是静态代理还是动态代理,代理对象和目标对象都需要实现一个公共接口
动态代理在静态代理的基础上做了改进,极大地提高了程序的可维护性和可扩展性
- 动态代理产生代理对象的时机是运行时动态生成,它没有Java源文件,直接生成字节码文件实例化代理对象;而静态代理的代理对象,在程序编译时已经写好Java文件了,直接new一个代理对象即可
- 动态代理比静态代理更加稳健,对程序的可维护性和可扩展性更加友好
静态代理违反了开闭原则,原因是:面对新的需求时,需要修改代理类,增加实现新的接口和方法,导致代理类越来越庞大,变得难以维护
常见的动态代理实现
动态代理解决的问题是面对新的需求时,不需要修改代理对象的代码,只需要新增接口和真实对象,在客户端调用即可完成新的代理
JDK Proxy
JDK Proxy是JDK提供的一个动态代理机制,它涉及到两个核心类,分别是Proxy和InvocationHandler
代理对象是在程序运行过程中,由代理工厂动态生成
首先,代理工厂需要实现InvocationHandler
接口并实现其invoke()方法
invoke()
方法有三个参数:
- Object proxy:代理对象
- Method method:真正执行的方法
- Object[] args:调用method时传入的参数列表值
然后,通过代理工厂动态生成代理对象需要用到Proxy
类,它可以帮助我们生成任意一个代理对象,里面提供了一个静态方法newProxyInstance()
newProxyInstance()
有三个参数:
- ClassLoader loader:加载动态代理类的类加载器
- Class<?>[] interfaces:代理类实现的接口,可以传入多个接口
- InvocationHandler h:指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法
JDK动态代理的使用方法
- 代理工厂需要实现
InvocationHandler
接口,调用代理方法时会转向执行invoke()方法 - 生成代理对象需要使用Proxy类的newProxyInstance()方法,返回对象可强转成传入的其中一个接口,然后调用接口方法即可实现代理
public interface VIPMovie {
void vipPlay();
}
public class IronManVIPMovie implements VIPMovie{
@Override
public void vipPlay() {
System.out.println("vip影厅正在播放《钢铁侠》");
}
}
public class JdkProxyExample implements InvocationHandler{
private Object target;
//建立代理对象和真实对象的关系
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//实现代理逻辑方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("电影开始前播放广告");
Object obj = method.invoke(target, args);
System.out.println("电影结束后播放广告");
return obj;
}
}
public class testJdkProxy {
public static void main(String[] args) {
JdkProxyExample jdkProxyExample = new JdkProxyExample();
VIPMovie proxy = (VIPMovie) jdkProxyExample.bind(new IronManVIPMovie());
proxy.vipPlay();
}
}
CGGLIB
需要导入第三方依赖,它是一个字节码生成类库,能够在运行时动态生成代理类对Java类和Java接口扩展
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
CGLIB代理中有两个核心的类
MethodInterceptor
接口:实现一个代理工厂的根接口- intercept()方法有四个参数
- Object o:被代理对象
- Method method:被拦截的方法
- Object[] objects:被拦截方法的所有入参值
- MethodProxy methodProxy:方法代理,用于调用原始的方法
- intercept()方法有四个参数
- Enhancer类:创建动态代理对象的类
CGLIB的使用方法
- 代理工厂需要实现
MethodInterceptor
接口,并重写方法,内部关联真实对象,控制第三者对真实对象的访问;代理工厂内部暴露一个方法用于从代理工厂中获取一个代理对象实例 Enhancer
类用于从代理工厂中实例化一个代理对象,给调用者提供代理服务
public class CaptainAmericaMovie {
public void play() {
System.out.println("正在播放《美国队长22》");
}
}
public class CglibProxyExample implements MethodInterceptor{
private Object readObject;
public Object getProxy(Object readObject) {
this.readObject = realObject;
Enhancer enhancer = new Enhancer();
// 设置需要增强类的类加载器
enhancer.setClassLoader(realObject.getClass().getClassLoader());
// 设置被代理类,目标对象
enhancer.setSuperclass(realObject.getClass());
// 设置方法拦截器,代理工厂
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("电影开始前播放广告");
Object obj = methodProxy.invokeSuper(proxy, args);
System.out.println("电影结束后播放广告");
return obj;
}
}
public static void main(String[] args) {
CglibProxyExample cglibProxyExample = new CglibProxyExample();
CaptainAmericaMovie proxy = (CaptainAmericaMovie) cglibProxyExample.getProxy(new CaptainAmericaMovie());
proxy.play();
}
JDK Proxy和CGLIB的对比
JDK Proxy | CGLIB | |
---|---|---|
代理工厂实现接口 | InvocationHandler | MethodInterceptor |
构造代理对象给Client服务 | Proxy | Enhancer |
不同之处
- CGLIB可以代理大部分类;JDK Proxy仅能够代理实现了接口的类
- CGLIB采用动态创建被代理类的子类实现方法拦截,子类内部重写被拦截的方法,所以CGLIB不能代理被final关键字修饰的类和方法
动态代理的实际应用
在Spring的AOP编程中
-
如果加入容器的目标对象有实现接口,用JDK代理
-
如果目标对象没有实现接口,用Cglib代理