1. 什么是设计模式?
- 设计模式:一些通用的解决固定问题的方式
2. 为什么需要代理模式?
可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。
3. 什么是代理模式
代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。java中有静态代理、JDK动态代理、CGLib动态代理的方式。静态代理指的是代理类是在编译期就存在的,相反动态代理则是在程序运行期动态生成的。
概念:
- 真实对象:被代理的对象
- 代理对象:增强真实对象
- 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
4. 实现方式
1. 静态代理
有一个类文件描述代理模式
1. 实现步骤:
- 代理对象与真实对象实现相同的接口
- 代理对象中创建真实对象
- 使用真实对象调用方法
- 增强方法
2. 具体实例
接口:
package site.qujq.staticProxy;
/**
* 代理类和被代理的接口
* Created by qjq on 2020/2/23 10:51
*/
public interface SaleComputer {
public String sale(double money);
public void show();
}
被代理类(联想电脑厂商):
package site.qujq.staticProxy;
import site.qujq.proxy.SaleComputer;
/**
* 联想电脑厂商
* Created by qjq on 2020/2/23 10:53
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑");
}
}
代理类(经销商):
package site.qujq.staticProxy;
/**
* 代理 / 经销商
* Created by qjq on 2020/2/23 10:55
*/
public class StaticProxy implements SaleComputer {
// 真实对象 / 被代理对象
private Lenovo factory = new Lenovo();
@Override
public String sale(double money) {
money = money*0.8;//经销商从联想买电脑的价格
String product = factory.sale(8000);//得到的产品
return product;
}
@Override
public void show() {
factory.show();//展示的产品
System.out.println("小新pro13");
System.out.println("ThinkPad");
System.out.println("....");
}
}
在不使用代理的情况下测试:
package site.qujq.staticProxy;
/**
* Created by qjq on 2020/2/23 13:33
*/
public class Test {
public static void main(String[] args) {
Lenovo factory = new Lenovo();
factory.sale(8000);//相当于工厂直销
System.out.println("---------------");
factory.show();
}
}
结果是:
卖出一台电脑,价值:8000.0
---------------
展示电脑
使用代理的情况下进行测试:
package site.qujq.staticProxy;
/**
* 使用静态代理测试
* Created by qjq on 2020/2/23 13:17
*/
public class StaticProxyTest {
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy();
String product = staticProxy.sale(10000);
System.out.println("得到的产品:"+product);
System.out.println("-------------");
staticProxy.show();
}
}
结果是:
卖出一台电脑,价值:8000.0
得到的产品:联想电脑
-------------
展示电脑
小新pro13
ThinkPad
....
对比,使用静态代理和不使用静态代理,可以发现使用了代理之后,可以在被代理方法的执行前或后加入别的代码,实现诸如权限及日志的操作。
但,静态代理也存在一定的问题,如果被代理方法很多,就要为每个方法进行代理,增加了代码维护的成本。有没有其他的方式可以减少代码的维护,那就是动态代理。
2. JDK动态代理
在内存中形成代理类
- 特点:字节码随用随创建,随用随加载
- 作用:不修改源码的基础上对方法增强
- 动态代理分类:
- 基于接口的动态代理(本节)
- 基于子类的动态代理(下一小节)
1. 实现步骤:
- 代理对象和真实对象实现相同的接口
- 代理对象 = Proxy.newProxyInstance();
- 使用代理对象调用方法。
- 增强方法
2. 增强方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
3. 具体实现
接口:(与静态代理一样)
package site.qujq.staticProxy;
/**
* 代理类和被代理的接口
* Created by qjq on 2020/2/23 10:51
*/
public interface SaleComputer {
public String sale(double money);
public void show();
}
被代理对象(联想电脑厂商):
package site.qujq.proxy;
/**
* Created by qjq on 2020/2/23 10:53
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑");
}
}
代理对象(经销商):
package site.qujq.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by qjq on 2020/2/23 10:55
*/
public class ProxyTest {
public static void main(String[] args) {
//1. 创建真实对象
Lenovo company = new Lenovo();
//2. 创建动态代理
/*
三个参数:
1. 类加载器:真实对象.getClass().getClassLoader()
2. 接口数组:真实对象.getClass().getInterfaces()
3. 处理器:new InvocationHandler(),处理和被代理对象的方法,即方法增强的地方
*/
SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(company.getClass().getClassLoader(), company.getClass().getInterfaces(),
new InvocationHandler() {
/*代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法的执行
参数:
1. proxy:代理对象(一般不用)
2. method:代理对象调用的方法,被封装为的对象
3. args:代理对象调用的方法时,传入的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj =null;
System.out.println("这里是代理。。。。");
if ("sale".equals(method.getName())){
//增强参数
Double money = (Double) args[0];//用户买电脑付的钱
money = money*0.8;//给电脑厂商的钱
System.out.println("你被增强啦");
//使用compay对象执行此方法,返回值就是真实对象方法的返回值
obj = method.invoke(company, money);
//增强返回值
obj = obj+"_鼠标垫";
}else {
obj = method.invoke(company, args);
}
return obj;
}
});
String computer = proxy_lenovo.sale(8000);//用户付钱
System.out.println(computer);//用户得到的产品
System.out.println("---------------------");
proxy_lenovo.show();//展示商品
}
}
不使用代理的测试和上一节不使用静态代理的测试一样,不再叙述。
测试结果:
这里是代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是代理。。。。
展示电脑
小新pro13
ThinkPad
....
从上面可以看出代理类是由Proxy这个类通过newProxyInstance方法动态生成的,生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。
JDK的动态代理也存在不足,即被代理类必须要有实现的接口,如没有接口则无法使用动态代理(从newProxyInstance方法的第二个参数可得知,必须传入被代理类的实现接口),那么需要使用CGLib动态代理。
3. CGLib动态代理
CGLib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了JDK代理需要被代理对象实现某个接口的问题。对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调用时直接调用子类对象的实例,也可以达到代理的效果。CGLib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是CGLib代理为什么不需要为每个被代理类编写代理逻辑的原因。这里需要注意的是,根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。
1)细节
-
基于子类的动态代理:
- 涉及的类:Enhancer
- 提供者:第三方cglib库
-
如何创建代理对象:
- 使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
-
create方法的参数:
-
Class:字节码
它是用于指定被代理对象的字节码。
-
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor
-
2)具体实现
被代理对象(电脑厂商):此时不需要实现接口
package site.qujq.cglibProxy;
/**
* 电脑生产商
* Created by qjq on 2020/2/23 12:53
*/
public class Lenovo {
public String sale(double money) {
System.out.println("卖出一台电脑,价值:"+money);
return "联想电脑";
}
public void show() {
System.out.println("展示电脑");
}
}
代理对象:
package site.qujq.cglibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLib动态代理
* Created by qjq on 2020/2/24 9:10
*/
public class CglibProxy {
public static void main(String[] args) {
final Lenovo factory = new Lenovo();
Lenovo proxy_CGLib = (Lenovo) Enhancer.create(factory.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
*
* @param proxy
* @param method
* @param args 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return 调用方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = null;
System.out.println("这里是CGLib动态代理。。。。");
if ("sale".equals(method.getName())) {//当是sale方法时
//增强参数
Double money = (Double) args[0];//用户买电脑付的钱
money = money * 0.8;//给电脑厂商的钱
System.out.println("你被增强啦");
//使用factory对象执行此方法,返回值就是真实对象方法的返回值
obj = method.invoke(factory, money);
//增强返回值
obj = obj + "_鼠标垫";
} else {//因为这里只有两个方法,所以使用else,也就是指show方法
obj = method.invoke(factory, args);//这里执行打印,有无返回值一样
System.out.println("小新pro13");
System.out.println("ThinkPad");
System.out.println("....");
}
return obj;
}
});
//测试
String computer = proxy_CGLib.sale(8000);//用户付钱
System.out.println(computer);//用户得到的产品
System.out.println("---------------------");
proxy_CGLib.show();//展示商品
}
}
结果是:
这里是CGLib动态代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是CGLib动态代理。。。。
展示电脑
小新pro13
ThinkPad
....
5.总结
对静态代理、JDK动态代理、CGLib动态代理做一个总结,静态代理的维护成本比较高,有一个被代理类就需要创建一个代理类,而且需要实现相同的接口。动态代理模式和CGLib动态代理的区别是JDK动态代理需要被代理类实现接口,而CGLib则是生成被代理类的子类,要求被代理类不能是final的,因为final类无法被继承。