代理模式是创建一个代理对象以控制对目标对象的访问的一种设计思想。
其组成分为3部分:1.抽象接口 2.目标对象 3.代理对象
一.代理模式的简单实现-----静态代理
抽象接口由目标对象和代理对象共同实现
//抽象接口
interface UserDao{
public void save();
}
目标对象则是我们想控制其访问的对象
//目标对象
class StudentDao implements UserDao{
public void save() {
System.out.println("一名学生已被储存");
}
}
代理对象则含有目标对象的向上转型,重写抽象接口的方法,并在方法中加入访问控制和目标对象的方法调用
//代理对象
class StudentDaoProxy{
private UserDao target;
StudentDaoProxy(UserDao target){
this.target = target;
}
public void save() {//对目标方法进行增强
System.out.println("储存前准备");
target.save();
System.out.println("储存后功能");
}
}
然后就可用实现静态代理
public class MyProxy {
public static void main(String[] args) {
//静态代理
StudentDaoProxy sdp = new StudentDaoProxy(new StudentDao());
sdp.save();
}
但是,静态代理具有相当大的缺点。其一是相当繁琐,需要对接口中的每个方法进行重写,其二则是如果接口方法起了变化,便需要对目标对象和代理对象的源码进行改动,为了克服这个缺点,就有了动态代理。
二.代理模式的进一步实现-----动态代理
动态代理的实现中,抽象接口和目标对象是不变的,变化的是创建代理对象的方式。动态代理通过Java类库中的Proxy类在运行时创建代理对象,具体静态方法是
这个方法会返回代理对象,但是他把访问控制(方法增强)的实现交给了InvocationHandler接口
//动态代理的方法调用处理器
class MyInvocationHandler implements InvocationHandler{
private UserDao userDao;
MyInvocationHandler(UserDao userDao){
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("储存前准备");
Object returnValue = method.invoke(userDao, args);//调用目标对象的方法
System.out.println("储存后功能");
return returnValue;
}
}
下列代码是动态代理的实现方法
public class MyProxy {
public static void main(String[] args) {
//动态代理
UserDao studentDao = new StudentDao();
UserDao userDao = (UserDao)Proxy.newProxyInstance(studentDao.getClass().getClassLoader(),
studentDao.getClass().getInterfaces(),
new MyInvocationHandler(studentDao));
userDao.save();
}
}
以上的静态代理和动态代理的目标对象都需要实现一个抽象接口,当目标对象没有抽象接口有该怎么办呢?CGLib包为我们提供了这个功能
三.动态代理模式的进一步实现-----CGLib动态代理
生成动态代理的方法很多,不止jdk自带的动态代理这一种,还有CGLIB,Javassist或者ASM。Jdk的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用jdk代理,这就要用到CGLIB代理了。CGLIB是针对类来实现的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB创建某个类A的动态代理类的模式是:
- 查找A上的所有非final的public类型的方法定义
- 将这些方法的定义转换成字节码
- 将组成的字节码转换成相应的代理的class对象
- 实现MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和Jdk动态代理InvocationHandler的功能和角色是一样的)
先看实现源码,需要引入cglib-nodep-3.2.6.jar包(版本可变),如果引入了cglib-3.2.6.jar包,怎会抛出ClassNotFoundException异常
//CGLib动态代理,没有实现接口的类的代理方法
class TeacherDao{
public void save() {
System.out.println("一名教师已被储存");
}
}
//CGLib动态代理的方法调用拦截器
class TeacherDaoProxy implements MethodInterceptor{
//得到代理对象
public Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();//CGLib中的增强器类,我们会设置一个类是被代理类的子类,实现代理
enhancer.setSuperclass(clazz);//设置父类为被代理类
enhancer.setCallback(this);//设置拦截器,相当于每次调用父类方法就会转到调用此类的方法,类似重写
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("教师写入前准备");
Object returnValue = proxy.invokeSuper(object, args);
System.out.println("教师已经写入");
return returnValue;
}
}
测试代码
public class MyProxy {
public static void main(String[] args) {
TeacherDaoProxy tdp = new TeacherDaoProxy();
TeacherDao td = (TeacherDao)tdp.getProxy(TeacherDao.class);
td.save();
}
}
总结:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。
应用场景
当需要为一个对象在不同的地址空间提供局部的代表时
此时的代理模式称为远程代理:为一个对象在不同的地址空间提供局部代表。目的:
- 隐藏一个对象存在于不同地址空间的事实;
- 远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
当需要创建开销非常大的对象时
此时的代理模式称为虚拟代理:通过使用过一个小的对象代理一个大对象。目的:减少系统的开销。
- 当需要控制对原始对象的访问时
此时的代理模式称为保护代理:控制目标对象的访问,给不同用户提供不同的访问权限
目的:用来控制对真实对象的访问权限
- 当需要在访问对象时附加额外操作时
此时的代理模式称为智能引用代理,额外操作包括耗时操作、计算访问次数等等
目的:在不影响对象类的情况下,在访问对象时进行更多的操作
以上是最常用的使用场景,其他还包括:
- 防火墙代理:保护目标不让恶意用户靠近
- Cache代理:为结果提供临时的存储空间,以便其他客户端调用
装饰模式和代理模式的区别
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
外观模式和代理模式的区别
代理与外观的主要区别在于,代理对象代表一个单一对象而外观对象代表一个子系统,代理的客户对象无法直接访问对象,由代理提供单独的目标对象的访问,而通常外观对象提供对子系统各元件功能的简化的共同层次的调用接口。代理是一种原来对象的代表,其他需要与这个对象打交道的操作都是和这个代表交涉的。
适配器模式和代理模式的区别
适配器模式改变所考虑的对象的接口,代理模式不能改变所代理对象的接口。