代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。
简单点理解:
目标对象:原对象,我们需要通过代理对象控制它的访问,扩展其功能。
代理对象:代理模式产生的对象,是原对象的替身,在原有基础上进行修改。
在不改变原对象代码的基础上对原对象的功能进行扩展
再简单点理解:
比如点外卖事件
你想吃麻辣烫,自己又不想去店里吃,所以你点外卖,此时的外卖小哥,可以看作为代理对象。而你又想在吃完麻辣烫后喝一杯珍珠奶茶,所以你又联系外卖小哥帮你去店里买一杯。这个过程可以理解为加的扩展功能。
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
用图表示如下:
代理模式的关键点是:代理对象与目标对象。代理对象是对目标对象的扩展,并会调用目标对象。
一般的代理模式都会有一个接口,代理对象和目标对象共同实现这个接口。静态代理(继承,实现接口)和动态代理(基于接口的jdk动态代理,不基于接口的cglib动态代理)。
降低了系统的耦合度,扩展性好
可以起到保护目标对象的作用
例子:
顾客想要点一份鱼香肉丝,让外卖员送过来,并且想要帮忙带一份珍珠奶茶。
分析:
顾客:目标对象
外卖员:代理对象
点珍珠奶茶:扩展功能
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
需要我们手写代理类。两种形式:
让目标对象和代理对象都实现一个共同接口。那么这两个类就有了公共的方法,就可以在代理对象中实现对目标对象功能的扩展。
代码示例:
OrderInterface接口:
public interface OrderInterface {
public String order(String foodName);
}
Customer类:
public class Customer implements OrderInterface{
public String order(String foodName){
return "已下单点了"+foodName;
}
}
DeliveryClerk类:
public class DeliveryClerk implements OrderInterface{
//把原来的对象传入并保存到成员位置。也就是目标类对象
private OrderInterface source;
public DeliveryClerk(OrderInterface source) {
this.source=source;
}
@Override
public String order(String foodName) {
String result = source.order(foodName);
System.out.println("已经接订单在送去的路上");
return result+"买了一杯珍珠奶茶";
}
}
测试类:
public class Test {
public static void main(String[] args) {
//1.创建顾客对象
Customer customer = new Customer();
//2.创建代理对象
DeliveryClerk deliveryClerk = new DeliveryClerk(customer);
//3.调用方法
String result = deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
让代理对象继承目标对象,那么代理对象就拥有目标对象的方法,同时又可以对其功能进行扩展。
代码示例:
Customer类:
public class Customer {
public String order(String foodName){
return "已下单点了"+foodName;
}
}
DeliveryClerk类:
public class DeliveryClerk extends Customer{
@Override
public String order(String foodName) {
String result = super.order(foodName);
System.out.println("已经接订单在送去的路上");
return result+"买了一杯珍珠奶茶";
}
}
优点:在不改变原来的实现类的情况下对方法实现了增强。
缺点:维护复杂。每一个目标对象,都需要匹配一个代理类,并且目标对象方法变更,代理类必须同步更新。
是在内存中生成代理对象的一种技术
无需手写代理类,也不会存在代码编译的过程。运用在内存中生产代理类的技术在JVM的运行区造一个代理对象,只需对需要修改的部分进行编辑。
实际上就是在内存中生产一个对象,该对象实现了指定的目标对象的所有接口,代理对象和目标对象是兄弟关系。
jdk自带动态代理技术,需要使用一个静态方法来创建代理对象,它需要目标对象必须实现接口,生产的代理对象和原对象都实现相同的接口。
基于接口的动态代理
1.提供者:JDK
2.使用JDK官方的Proxy类创建代理对象
3.注意:代理的目标对象必须实现接口
编写流程:
1.准备一个目标类对象,也就是顾客对象
2.使用jdk的API动态的生成代理对象
3.调用代理对象执行相应的方法
参数解释:
1.ClassLoader loader:
固定写法,指定目标类对象的类的加载器即可,用于加载目标类及其接口的字节码文件,通常使用目标类的字节码文件调用getClassLoader()方法可得到。
2.Class>[] interfaces:
固定写法,指定目标类的所以接口的字节码对象的数组,通常使用目标类的字节码文件调用getinterfaces()方法可得到。
3.InvocationHander h:
这个参数是一个接口,主要关注它里面的一个方法,它会在代理类调用方法时执行,也就是说,在代理类对象中调用的任何方法都会执行invoke()方法。所以在此方法中进行代码的扩展。invoke()方法中参数的含义:
1.proxy:就是代理类对象的一个引用也就是Proxy.newProxyInstance()方法的返回值;此引用几乎不会用到。
2.method:对应的就是触发invoke执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
3.args:代理对象调用方法时传入的实际参数
OrderInterface接口:
public interface OrderInterface {
public String order(String foodName);
public void test1();
public void test2();
}
Customer类:
public class Customer implements OrderInterface {
public String order(String foodName){
return "已下单点了"+foodName;
}
@Override
public void test1() {
}
@Override
public void test2() {
}
}
Test 类:
public class Test {
public static void main(String[] args) {
//1. 准备一个目标类对象,也就是顾客对象
Customer customer = new Customer();
//2. 使用jdk的API动态的生成代理对象
OrderInterface deliveryClerk = (OrderInterface) Proxy.newProxyInstance(customer.getClass().getClassLoader(),
customer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接订单在送去的路上");
return result + "买了一杯珍珠奶茶";
} else {
return method.invoke(customer, args);
//使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
}
}
}
);
//3. 调用代理对象执行相应的方法
String result=deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
第三方cglib动态代理技术也是可以使用一个静态代理方法来创建代理对象,它不要求目标类实现接口,但是要求目标类不能是最终类,也就是说不能被final修饰。因为cglib是基于目标类生成该类的一个子类作为代理类,所以目标对象必须可以被继承。
基于父类的动态代理是在内存中生成一个对象,该对象继承了原对象,所以代理对象实际上就是原对象的子类。
基于类的动态代理
1.提供者:第三方 CGLib
2.使用CGLib的Enhancer类创建代理对象
3.注意:如果报 asmxxxx 异常,需要导入 asm.jar包
编写流程:
1.准备一个目标类对象,也就是顾客对象
2.使用cjlib创建代理对象
3.调用代理对象执行相应的方法
参数解释:
1.Class type:
指定我们要代理的目标类的字节码对象,也就是指定目标类的类型。
1.callback:
也是一个接口,只是名称定义的作用。只是让别的接口去继承它,提供一个方法它会在合适的时候回来调用它。通常使用其子类
它的子类MethodInterceptor(方法拦截器)接口,其只有一个方法,叫做intercept()方法intercept()方法中的参数:
1.proxy:就是代理类对象的一个引用也就是Enharcer.create()方法的返回值;此引用几乎不会用到。
2.method:对应的就是触发intercept执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
3.args:代理对象调用方法时传入的实际参数
4.methodProxy:方法的代理对象,一般不做处理,暂时忽略。
Customer类:
public class Customer {
public String order(String foodName){
return "已下单点了"+foodName;
}
public void test1() {
}
public void test2() {
}
}
Test类:
public class Test {
public static void main(String[] args) {
// 创建一个目标类对象,也就是顾客对象
Customer customer = new Customer();
// 使用CGLIB创建代理对象
Customer deliveryClerk = (Customer) Enhancer.create(customer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 判断,如果是order方法,则增强
if ("order".equals(method.getName())) {
Object result = method.invoke(customer, args);
System.out.println("已经接订单在送去的路上");
return result + "买了一杯珍珠奶茶";
} else {
return method.invoke(customer, args);
//使用method反射调用,在原对象中执行方法,并不改变逻辑,也就是说原封不动调用原来的逻辑
}
}
}
);
//3. 调用代理对象执行相应的方法
String result=deliveryClerk.order("鱼香肉丝盖饭");
System.out.println(result);
}
}
兄弟关系
并列关系,不能互相转换,包容性比较差,在spring框架中,如果配置jdk的动态代理方式,一定要用接口类型接收代理类。
父子关系
代理对象是可以用父类的引用接收的。
JDK Proxy 和 CGLib 的区别主要体现在以下方面:
1.代理模式在Java开发中是广泛应用的,特别是在框架中,底层原理经常设计到。
2.静态代理需要手写代码且维护,修改非常繁琐,会引入很多工作量。所以常用动态代理。
动态代理中
3.在spring框架中默认支持了两种动态代理。如果指定的目标类实现了接口,spring中就会自动用jdk动态代理,如果没有实现接口就会自动用cglib方式。
4.我们在开发时,由于基于jdk的动态代理要求比较多,更不容易实现。所以很多人配置使用cglib动态代理。
5.如果使用dubbo+zookeeper,底层进行代理时最好配置使用cglib的方式进行代理。