代理模式
代理(proxy)是一种常用的设计模式,其目的就是为其他对象提供一个代理来控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理
优点一:可以隐藏委托类的实现。
就像房屋中介一样,或者说二手房东更为贴切。二手房东通常是房东的亲戚,经常直接以自己的名义出租,你可能连真正的房东都不知道。
优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
我们设计类有单一性原则,使每个类(方法)功能尽量单一,是啥做啥。但我们经常需要做日志和权限,你当然能每次都写在方法里,100个方法写100遍吗?所以日志、权限就可以充分展示了这个优点。
代理我们通常分为静态代理和动态代理。
静态代理
由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
实现
代理类需要和委托类使用相同的接口, 然后在代理类的方法内穿插执行委托类的方法,因为代理真正调用的还得是委托类的方法
统一接口:UserDao
public interface UserDao {
// 添加用户
int add(String user);
}
实现类UserDaoImpl(委托类)
public class UserDaoImpl implements UserDao {
@Override
public int add(String user) {
System.out.println("添加用户" + user);
return 1;
}
}
UserDaoProxy(代理类)
public class UserDaoProxy implements UserDao {
private UserDao userDao;
public UserDaoProxy(UserDao userDao) {
this.userDao = userDao;
}
@Override
public int add(String user) {
System.out.println("开启事务");
int i = userDao.add(user);
System.out.println("关闭事务");
return i;
}
}
测试
public static void main(String[] args) {
UserDaoProxy proxy = new UserDaoProxy(new UserDaoImpl());
proxy.add("money");
}

它可以在不修改目标对象的前提下扩展目标对象的功能,如果只有UserDaoImpl一种实现类还可以用工厂模式直接把new UserDaoImpl()隐藏掉,但缺点也很明显:
- 委托类和代理类都要实现同样的接口,这个接口一旦加个方法,除了委托类连代理类也要进行实现,不易维护。
- 一个代理只服务一种类型对象,也就是一个接口一个代理。
Java中的静态代理代表就是创建线程
// 实现Runable接口的类
MyThread myThread = new MyThread(); // 相当于被代理类
// Thread本身也实现了Runable接口
Thread thread = new Thread(myThread); // 相当于代理类
thread.start(); // 启动线程;调用run()方法
动态代理
静态代理在大规模需要代理的情况下是很不抗揍的,所以引入了动态代理。在Java说到动态,那肯定和反射有关,所以至少得了解些常用API吧。
这里讲的是基于JDK的动态代理
不同:
动态代理是在程序运行时根据需要动态创建代理类,也就是编译完并没有.class文件,是当需要的时候临时生成载入JVM。
动态代理类不需要实现接口,但是委托类需要实现相应接口。
实现
要想创建一个代理类,需要使用到java.lang.reflect.Proxy
类的newProxyInstance
方法。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
//方法体省略...
}
我们就是通过这个方法创建一个代理类,它有三个参数:
ClassLoader
:类加载器,可以通过反射中getClass().getClassLoader()获得Class<?>[]
:一个Class类数组,每个元素代表委托类需要实现的接口。InvocationHandler
: 一个调用处理器。
所以我们只要搞定这个调用处理器就可以使用这个方法创建代理了。ctrl+左键点进去,发现InvocationHandler是一个接口,并且只有一个接口方法invoke
public interface InvocationHandler {
/**
*
* @param proxy newProxyInstance生成的代理类
* @param method 被执行的方法
* @param args 被执行方法的参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
既然是接口我们就实现它:
- 需要维护一个委托类,因为动态创建,并不知道到程序员要我们代理哪个类,使用Object
- 实现 InvocationHandler接口的invoke,在里面使用反射执行方法。
public class UserDaoHandler implements InvocationHandler {
private Object target; // 委托类
public UserDaoHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("委托类:" + target.getClass().getName());
System.out.println("代理类:" + obj.getClass().getName());
System.out.println("调用方法:" + method.getName());
System.out.println("传入参数:" + Arrays.toString(args));
//---------------------代理---------------------------
System.out.println("开启事务");
// 调用target的当前调用方法,并填入参数args
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return returnValue;
}
}
好了,三个参数集齐了,try it
public static void main(String[] args) {
// 委托类
UserDao userDao = new UserDaoImpl();
// 获取第一个参数:类加载器,如果为系统类可以直接写null的
ClassLoader classLoader = userDao.getClass().getClassLoader();
// 获取第二个参数:需要实现的接口,就刚才的UserDao接口
Class[] needInterface = new Class[]{UserDao.class};
// 获取第三个参数:刚才写的调用处理器
InvocationHandler handler = new UserDaoHandler(userDao);
// 动态生成代理
UserDao proxy = (UserDao)Proxy.newProxyInstance(classLoader, needInterface, handler);
proxy.add("Money");
}
结果:

分析
根据代码来看,我们不用自己写UserProxy了,但是写了一个看起来似乎有点类似的调用处理器UserDaoHandler。
首先我们看结果的代理类,很明显不是我们写的,它是由虚拟机中的proxy类帮我们创建的以&proxy开头的类名,其实他就代表了代理类UserProxy。
然后我们针对静态代理的缺点来看:
委托类和代理类都要实现同样的接口,多一个接口方法,代理类也得跟着修改。
比如我们加一个
void delete(String user)
的方法,那静态代理的代理类UserProxy也要手动添加,而调用处理器我们并不做需要任何更改。

一个接口对应一个代理
我们在写调用处理器的时候的委托类类型是Object,也就是为谁代理是取决于程序员传入的委托类。所以一个调用处理器就可以应对多个接口,除非你的前后操作内容需要更换。比如我加一个ProductDao和他的实现,调用处理器代码不变,修改main
public static void main(String[] args) { ProductDao productDao = new ProductDaoImpl(); // 甚至你可以将这两句封装到一个工厂 InvocationHandler handler = new UserDaoHandler(productDao); ProductDao proxy = (ProductDao) Proxy.newProxyInstance(productDao.getClass().getClassLoader() , new Class[]{ProductDao.class}, handler); proxy.add("可口可乐"); }

第三方动态代理cglib
刚才说基于JDK的动态代理需要实现一个或以上接口,如果不实现接口的话,那就得用cglib。其实它就是一个字节码生成和转换的库。直接上才艺
先添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
其实总共需要一个cglib.jar还有一个asm.jar,但maven会传递下载,而我是自己导包结构少了asm.jar还报错了,在解析字节码和生成都需要用到它。
委托类:
public class PersonDao {
int add(String person) {
System.out.println("添加一个人:" + person);
return 1;
}
}
cglib类代理的基本思想就是给委托类生成一个子类(proxy),并添加一些前后操作,类似于添加一个调用处理器(InvocationHandler)。不过它不止一种,我们这里使用最常用的MethodInterceptor
接口,它只有一个接口方法intercept
和调用处理器实现的invoke
有点类似。
/**
*
* @param obj 代理类
* @param method 被执行的方法
* @param args 参数
* @param proxy 这个也有invoke函数用来使用方法,不知道具体是干啥的
* @return
* @throws Throwable
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
所以主要两步:
1.使用cglib的工具类生成委托类的子类
- 给他安排一个回调接口
intercept
的实现。
之前一直说工厂,那就整个工厂
public class ProxyFactory implements MethodInterceptor {
private Object target; // 委托类
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
// cglib的工具
Enhancer enhancer = new Enhancer();
// 设置继承委托类
enhancer.setSuperclass(target.getClass());
// 设置代理操作, 需要传入一个实现MethodInterceptor的类,本工厂继承了
enhancer.setCallback(this);
// 创建并返回这个代理类
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
System.out.println("委托类:" + target.getClass().getName());
// 这里代理类参数名变为obj了
System.out.println("代理类:" + object.getClass().getName());
System.out.println("调用方法:" + method.getName());
System.out.println("传入参数:" + Arrays.toString(args));
System.out.println("开启事务");
// 调用target的当前调用方法,并填入参数args
Object returnValue = method.invoke(target, args);
// 也可以用第四个参数调用方法
//Object returnValue = proxy.invoke(target, args);
System.out.println("关闭事务");
return returnValue;
}
}
测试:
public static void main(String[] args) {
PersonDao personDao = new PersonDao();
PersonDao proxy = (PersonDao) new ProxyFactory(personDao).getProxyInstance();
proxy.add("Money");
System.out.println(proxy.getClass().getName());
}
结果:

总结
静态代理:
- 优缺点
实现简单,但只能为一种委托类进行代理,委托类类型太多,会产生过多代理类。如果接口方法增加,还需要改动代理类。
代理类编写步骤:
- 一个存储委托类的实例域
- 带委托类的构造器
- 实现和委托类一样的接口方法
- 在方法中穿插调用委托类的同方法
- new代理类使用
动态代理:
优缺点
修复了静态代理的弊端,但委托类必须实现一个或多个接口,使用反射耗系统资源。
代理类编写步骤:完成Proxy.newProxyInstance的三个参数
获取第一个参数:类加载器,使用反射a.getClass().getClassLoader()
获取第二个参数:Class数组,委托类实现什么,我们需要用的是哪些,以A.class的形式创建成Class数组
写一个调用处理器(实现InvocationHandler接口)
- 一个存储委托类的实例域
- 带委托类的构造器
- 在实现的invoke方法中穿插调用
method.invoke(target,args)
执行委托类方法
Proxy.newProxyInstance
返回Object强转为要使用的接口(必须是在Class数组的)
cglib
优缺点
不需要实现接口了,但是它原理是用委托类创建子类重写方法,所以委托类不能被final修饰。
实现步骤
- 创建一个继承MethodInterceptor的类,并实现接口方法
intercept
- 创建cglib的工具类Enhancer的对象en
- 使用
en.setSuperclass(Class)
设置继承委托类 - 使用
en.setCallback(Callback)
设置代理操作的回调类, 如传入一个实现MethodInterceptor的类 - 使用
en.create()
返回代理类强制转换为委托类类型使用
- 创建一个继承MethodInterceptor的类,并实现接口方法

错了不要打我,偷偷告诉我,谢谢。
参考:
cglib代理的使用:更详细的cglib用法
本文使用 mdnice 排版