生活中的代理
例子:比如一家美国大学,可以对全世界招生,留学中介就是代理,帮美国大学招生。
代理的特点
- 中介和他们代理的做事是一样:招生。
- 中介是学校的代理,学校是目标。
- 流程为,家长->中介->学校。
- 中介是代理,需要收取费用。
为什么找代理
- 中介是专业的。
- 家长没有办法接触到学校。
开发中的代理
例子1:A类想调用C类,但是C类禁止访问,只能在AC中间创建B类作为代理。
C的结果给B,B给A。
例子2:登录需要手机验证码,我们没有能力发送短信,移动联通有能力发短信。
移动联通有子公司提供发送短信的业务,于是我们->子公司->移动联通发短信。
代理模式
代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的作用
- 功能增强:在你原有的功能上新增加功能。
- 控制访问:代理类不让你访问目标类。
代理的实现方式
静态代理
- 代理类是自己手工实现的。
- 代理的目标是确定的。
- 特点:实现简单,容易理解。
例子:用户购买U盘,商家代理某个品牌的U盘。商家卖U盘,厂家提供。用户(客户端)->代理类(商家)->目标(厂家)。
步骤:
- 创建接口实现卖U盘的方法,即厂家和商家要做的事情。
- 创建商家类实现卖U盘的方法。
- 创建厂家类实现卖U盘的方法。
- 创建一个客户端类,调用商家方法去买U盘。
代码:
//厂家商家都要实现的功能
public interface UsbSaleService {
//提供买U盘的数量,返回价格
float sell(int i);
}
//厂家,不接受用户直接购买
class UsbFactory implements UsbSaleService{
@Override
public float sell(int i) {
//厂家发货,一个2GU盘20元
return i*20f;
}
}
//商家,代理U盘的销售
class Taobao implements UsbSaleService{
//指定代理的厂家
private UsbFactory usbFactory=new UsbFactory();
@Override
public float sell(int i) {
//向厂家发送订单,厂家发货
//代理加价,一个U盘加10元,增强功能
return usbFactory.sell(i)+i*10f;
}
}
//用户购买
class shop{
public static void main(String[] args) {
//控制访问
Taobao taobao=new Taobao();
System.out.println(taobao.sell(5));
}
}
静态代理优缺点:
缺点:
- 目标类增加,代理类会成倍的增加,即代理类数量过多。
- 功能类增加方法后导致目标类、代理类都需要修改,影响较大。
优点:
- 功能增强。
- 访问控制,解耦。
动态代理
在程序执行过程中,使用jdk反射机制,创建代理类对象,并动态的指定要代理的目标类,无需用户创建对象。
总结
复习反射
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InflectTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/*创建对象执行
helloImpl hello=new helloImpl();
hello.sayHello("张三");
*/
/*通过反射执行
Hello h=new HelloImpl();
Method method=Hello.class.getMethod("sayHello", String.class);
Object res=method.invoke(h,"李四");
*/
}
}
class HelloImpl implements Hello{
@Override
public void sayHello(String name) {
System.out.println("你好,"+name);
}
}
interface Hello{
public void sayHello(String name);
}
如何实现动态代理
//参数1:jdk提供的代理对象无需赋值
//参数2:jdk提供的目标类的方法
//参数3:目标类方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
案例一:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
interface Moveable {
void move();
}
public class Car implements Moveable {
@Override
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class TimeHandler implements InvocationHandler {
public TimeHandler(Object target) {
super();
this.target = target;
}
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starttime = System.currentTimeMillis();
method.invoke(target);
long endtime = System.currentTimeMillis();
System.out.println("汽车行驶时间:" +(endtime - starttime) + "毫秒!");
return null;
}
}
class Test {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Class<?> cls = car.getClass();
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
m.move();
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface UsbSell{
float sell(int sum);
}
class Factory implements UsbSell{
@Override
public float sell(int sum) {
return sum*100f;
}
}
class MySellHandle implements InvocationHandler{
private Object target;
MySellHandle(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res=method.invoke(target,args);
return (Float)res+(int)args[0]*5;
}
}
public class JDKModel {
public static void main(String[] args) {
UsbSell usbSell=new Factory();
InvocationHandler handler=new MySellHandle(usbSell);
UsbSell proxy=(UsbSell) Proxy.newProxyInstance(usbSell.getClass().getClassLoader(),usbSell.getClass().getInterfaces(),handler);
System.out.println(proxy.sell(10));
}
}
实际开发中动态代理的应用
JDK动态代理,必须有接口。
CGLib动态代理,必须能被继承,即class不能是 final。
我在校大二期间接过私活,一元秒杀抢购系统。
这个系统有两个特点,一没有文档,二没有源码。只有一堆运行在Tomcat上的class文件和html文件。除了新增的CUID等功能外,还要调用之前人写的代码。因为不敢动之前人写的代码,所以就先反编译,然后继承重写指定方法,测试没问题后在修改AJAX的URL。上一个人不开发了,所以不用考虑代码变动问题。最主要的是发现要重写的太多了,评估了一下工作量,要继承重写几十个ServiceImpl,后来就直接用动态代理来做的。