一、定义
代理模式是一种很常见的设计模式。它使用代理对象完成用户请求,屏蔽用户对真是对象的访问。就如同现实生活中的代理一样,代理人被授权执行当事人的一些事宜,而无需当事人亲自出面,从第三方的角度看,似乎当事人并不存在,因为第三方只和代理人直接通信。
二、分类
(1)静态代理:
由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
(2)动态代理:
在程序运行时,运用反射机制动态创建代理类。
三、目的
在软件设计中,使用代理的意图很多,主要有三个:
(1)安全原因:屏蔽第三方直接与真是对象进行通信。
(2)RMI中:需要使用代理类处理远程方法调用的技术细节。
(3)提升系统性能,延迟加载。
这里重点讲一下延迟加载是怎么体现的。假设一个客户端软件,有根据用户请求进行数据库查询的功能。那么在查询数据之前,必须得获得数据库连接。那么这个获取数据库连接的操作应该放在什么时候进行呢?假如放在软甲开启时,就去获取数据库的连接,有些人可能会说获取一个连接而已,并不是什么耗时的操作,没有影响。但是,软件并不是这么简单的,它有大量类似的初始化操作,比如xml的解析等等,单独一个数据库连接不耗时,但是大量的这种操作呢,如果都放在软件启动时,那么势必会影响软件的性能和启动速度。因此,我们引入了代理模式,使用代理对当事人进行封装,当系统启动时,仅仅初始化代理类,并不进行数据库连接等初始化操作,仅当进行真正的数据库查询操作时,才会去获取数据库连接。
在系统启动时,将消耗资源最多的方法都使用代理模式分离,就可以加快系统的启动速度,减少用户的等待时间。而在用户真正做查询操作时,再由代理类,单独去加载真实的数据库查询类,完成用户请求。这里说的可能比较抽象,下面会举例说明。
四、四个角色
(1)主题接口:定义代理类和真实主题的公共接口
(2)真实主题:真正实现业务逻辑的类
(3)代理类:用来代理和封装真实主题
(4)Main:客户端,第三方,与代理类进行直接通信,完成一些工作。
五、实现
(1)静态代理:
/*
* 静态代理
* 每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理
*/
public class LearnProxy0 {
public static void main(String[] args) {
IDBQuery dbQuery=new DBQueryProxy();
dbQuery.select();
}
}
interface IDBQuery{
public void select();
}
class DBQuery implements IDBQuery{
public DBQuery() {
try {
//可能包含数据库连接等耗时操作,这里仅仅是模拟
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void select() {
// TODO Auto-generated method stub
System.out.println("进行数据库查询操作...");
}
}
class DBQueryProxy implements IDBQuery{
//初始值设置为空,即在软件启动时,不进行初始化操作
private IDBQuery dbQuery=null;
@Override
public void select() {
// TODO Auto-generated method stub
//只有在真正需要的时候,才创建真正的对象
if (dbQuery==null) {
dbQuery=new DBQuery();
}
System.out.println("查询操作前...");
dbQuery.select();
System.out.println("查询操作后...");
}
}
(2)JDK动态代理:
/*
* JDK的动态代理
*/
public class LearnProxy1 {
public static void main(String[] args) {
DynamicProxy proxy=new DynamicProxy();
Sport sportProxy=(Sport)proxy.bind(new Basketball());
sportProxy.play();
}
}
interface Sport{
public void play();
}
class Basketball implements Sport{
public void play() {
System.out.println("i am playing basketball");
}
}
class DynamicProxy implements InvocationHandler{
//要代理的接口,因此动态代理可以代理多个不同接口
private Object target;
public Object bind(Object target){ //设置目标代理对象,并且返回代理
this.target=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), //注意第二个参数是接口的列表,代理实例实现了所有接口
target.getClass().getInterfaces(), this); //因此,我们可以像使用接口的方式一样使用代理来调用方法
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("方法执行前");
method.invoke(target, args); //通过反射机制调用真实主题的目标方法
System.out.println("方法执行后");
return null;
}
}
(3)cglib动态代理:
/*
* JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了
*
* cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的
* 是继承,所以不能对final修饰的类进行代理
*/
public class LearnProxy2 implements MethodInterceptor{
//
private Object target;
//设置被代理的对象,并且返回一个动态代理
public Object bind(Object target){
this.target=target;
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback((Callback) this);
return enhancer.create();
}
@Override
public Object intercept(Object target, Method method, Object[] arg2,
MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("方法执行前...");
proxy.invokeSuper(target, arg2);
System.out.println("方法执行后");
return null;
}
public static void main(String[] args) {
LearnProxy2 proxy=new LearnProxy2();
Actor actorProxy=(Actor)proxy.bind(new Actor());
actorProxy.play();
}
}
class Actor{
public void play() {
System.out.println("表演....");
}
}
使用JDK创建代理有一个限制,即他只能为接口创建代理实例。但是现实情况是,有的类并没有实现接口(现实中存在一些并没有使用接口的项目),但是它也需要动态代理,那怎么办呢?这个时候就需要使用CGLIB啦。
CGLIB采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并且顺势织入横切逻辑。
另外,CGLIB创建的动态代理的性能要比JDK创建的代理的性能好很多。但是,CGLIB创建代理对象花费的时间要比JDK多。因此,我们建议,对于singleton的代理对象,因为不需要频繁创建,最好使用CGLIB技术创建代理,反之,适合使用JDK代理技术。还要注意的是,由于CGLIB采用动态创建子类的方式生成代理对象,所以不能对目标类中的final和private方法进行代理。
下面篇,我会讲解一下动态代理在AOP中的应用。
六、上述实现方法存在的问题
(1)我们为所有的方法都添加了横切逻辑,但是,有时候这并不是我们想要的,我们只希望对业务类的某几个特定方法加上横切逻辑。
(2)我们通过硬编码的方法指定了织入横切逻辑的织入点,即在业务方法的执行前和执行后织入了代码,不具有灵活性。
这几个问题都会在Spring AOP中得到解决。