在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
介绍
意图:为其他对象提供一种代理以控制对这个对象的访问(类似明星和经纪人)。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理实现
接口IUserDao :
public interface IUserDao {
void findAll();
}
实际对象 类UserDao:
public class UserDao implements IUserDao {
@Override
public void findAll() {
System.out.println("find all Users...");
}
}
代理对象 类 UserDaoProxy:
public class UserDaoProxy implements IUserDao {
// 代理对象是具有现有对象的对象
private UserDao userDao;
// 通过构造函数初始化
public UserDaoProxy(UserDao userDao) {
super();
this.userDao = userDao;
}
@Override
public void findAll() {
System.out.println("记录日志:userdao.findAll()...");
userDao.findAll();
}
}
测试类:
public class Test {
public static void main(String[] args) {
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy udp = new UserDaoProxy(new UserDao());
//执行的是代理对象的方法
udp.findAll();
}
}
输出:
记录日志:userdao.findAll()...
find all Users...
代理模式 和装饰模式的区别:
装饰模式:不一定要对所有的功能都进行装饰。更强调对方法的加强
代理模式:不一定所有功能都要拦截。更强调的是方法的拦截
静态代理总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
动态代理(JDK代理,接口代理):
动态代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces
,:目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
JDK动态代理代码:
接口类IUserDao.java以及接口实现类,目标对象UserDao是一样的,没有做修改.在这个基础上,增加一个代理工厂类(ProxyFactory.java),将代理类写在这个地方,然后在测试类(需要使用到代理的代码)中先建立目标对象和代理对象的联系,然后代用代理对象的中同名方法
代理工厂类:ProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private Object object;
public ProxyFactory(Object object) {
super();
this.object = object;
}
// 给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这种写法:System.out.println("记录日志:"+proxy+"执行"+method+"方法...");
// 会报错:System.out.println("记录日志:"+proxy+"执行"+method+"方法...");
// 原因:字符串连接符号 +, 自动调用toString(),proxy.toString()递归调用InvocationHandler的invoke方法,导致堆栈溢出。
System.out.println("记录日志:"+proxy.getClass()+"执行"+method.getName()+"方法...");
//object是method 所属的对象,args是method接收的参数
Object invoke = method.invoke(object, args);
return invoke;
}
});
}
}
注意这段代码:及其原因
//这种写法:System.out.println("记录日志:"+proxy+"执行"+method+"方法...");
// 会报错:System.out.println("记录日志:"+proxy+"执行"+method+"方法...");
// 原因:字符串连接符号 +, 自动调用toString(),proxy.toString()递归调用InvocationHandler的invoke方法,导致堆栈溢出。
System.out.println("记录日志:"+proxy.getClass()+"执行"+method.getName()+"方法...");
测试类 TestProxyFactory :
public class TestProxyFactory {
public static void main(String[] args) {
//目标对象
IUserDao userDao = new UserDao();
ProxyFactory pf = new ProxyFactory(userDao);
//代理对象
IUserDao proxyInstance = (IUserDao)pf.getProxyInstance();
proxyInstance.findAll();
}
}
输出:
记录日志:class com.sun.proxy.$Proxy0执行findAll方法...
find all Users...
Cglib代理(子类代理)
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理.
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码示例:
目标对象类:UserDao.java
public class UserDao {
public void findAll(){
System.out.println("findAll...");
}
}
代理类ProxyFactory:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor {
// 维护目标对象
private Object object;
public ProxyFactory(Object object) {
this.object = object;
}
// 给目标对象创建一个代理对象
public Object getProxyInstance() {
// 1.工具类
Enhancer en = new Enhancer();
// 2.设置父类
en.setSuperclass(object.getClass());
// 3.设置回调函数
en.setCallback(this);
// 4.创建子类(代理对象)
return en.create();
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("记录日志....");
//此处的参数Object是实际拥有method的对象
Object invoke = method.invoke(object, args);
return invoke;
}
}
测试类:
public class Test {
public static void main(String[] args) {
ProxyFactory pf = new ProxyFactory(new UserDao());
UserDao proxyInstance = (UserDao)pf.getProxyInstance();
proxyInstance.findAll();
}
}
输出:
记录日志....
findAll...