代理模式
一、代理模式
1、代理简介
代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。
客户类真正的想要访问的对象是目标对象但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。
例如:有A、B、C三个类,A原来可以调用C类的方法,现在因为某种原因C类不允许A类调用其方法,但B类可以调用C类的方法。A类通过B类调用C类的方法。这里B是C的代理。A通过代理B访问C。
原来的访问关系:
通过代理的访问关系:
A类:【客户端】 B类:【代理】 C类:【目标类】
2、实际例子
登录,注册有验证码,验证码是手机短信
- 中国移动,联通能发短信
- 中国移动,联通能有子公司,或者关联公司,他们面向社会提供短信的发送功能
张三项目发送短信 --------> 子公司,或者关联公司 --------> 中国移动,联通
3、代理模式的作用
(1)功能增强
在你原有的功能上,增加了额外的功能。新增加的功能,叫做功能增强
(2)控制访问
代理类不让你访问目标,例如商家不让用户访问厂家
4、实现代理的方式
(1)静态代理
- 代理类是自己手工实现的,自己创建一个java类, 表示代理类
- 同时你所要代理的目标类是确定的
(2)动态代理
在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。
动态代理中目标类即使很多
-
代理类数量可以很少
-
当你修改了接口中的方法时,不会影响代理类
在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理目标类。换句话说:动态代理是一种创建java对象的能力,不用创建代理类,就能创建代理类对象。
二、静态代理
1、特点
- 实现简单
- 容易理解
2、实例
模拟一个用户购买u盘的行为
- 用户:客户端类
- 商家:代理,代理某个品牌的u盘
- 厂家:目标类
- 三者的关系:用户 (客户端)-------> 商家(代理) ---------> 厂家(目标)
商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘
3、实现步骤
(1)创建一个接口,定义卖u盘的方法,表示功能的,厂家、商家都要完成的功能
package cn.edu.huat.service;
//表示功能的,厂家、商家都要完成的功能
public interface UsbSell {
/**
* 定义方法
* @param amount 表示一次购买的数量
* @return 表示一个u盘的价格
*/
float sell(int amount);
}
(2)创建厂家类,实现1步骤的接口
package cn.edu.huat.factory;
import cn.edu.huat.service.UsbSell;
//目标类,金士顿厂家,不用户的单独购买接受
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
System.out.println("目标类中的方法调用,UsbKingFactory中的Sell");
//一个128G的U盘是85元
//后期可以根据amount,可以实现不同的价格,例如单价85,50000个是75
return 85.0f;
}
}
(3)创建商家,就是代理,也需要实现1步骤中的接口
package cn.edu.huat.merchant;
import cn.edu.huat.factory.UsbKingFactory;
import cn.edu.huat.service.UsbSell;
//TaoBao是一个商家,代理金士顿U盘的销售
public class TaoBao implements UsbSell {
//声明商家代理的厂家具体是谁
private UsbKingFactory factory = new UsbKingFactory();
//实现销售U盘的功能
public float sell(int amount) {
//向厂家发送订单,告诉厂家,我买了U盘,厂家发货
float price = factory.sell(amount); //厂家的价格
//商家需要加价,也就是代理要增加价格
price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能
//在目标类的方法调用后,你做的其他功能,都是增强的意思
System.out.println("淘宝商家,给你一个优惠券,或者红包");
//增加的价格,也就是用户购买的价格
return price;
}
}
(4)创建客户端类,调用商家的方法买一个u盘
package cn.edu.huat;
import cn.edu.huat.merchant.TaoBao;
public class ShopMain {
public static void main(String[] args) {
//创建代理的商家TaoBao对象
TaoBao taoBao = new TaoBao();
//通过代理类,实现购买U盘,增加了优惠券,红包等等
float price = taoBao.sell(1);
System.out.println("通过淘宝的商家,购买U盘单价:" + price);
}
}
4、代理类完成的功能
- 目标类中方法的调用
- 功能增强
5、缺点
- 当目标类增加了,代理类可能也需要成倍的增加。代理类数量过多
- 当你的接口中功能增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。
三、JDK动态代理(AOP)(掌握)
1、动态代理
动态代理是指代理类对象在程序运行时由【JVM】根据反射机制动态生成的。动态代理不需要定义代理类的【.java源文件】。
动态代理其实就是jdk运行期间,动态创建class字节码并加载到【JVM】。
动态代理的实现方式常用的有两种
- 使用【JDK代理代理】
- 通过【CGLIB动态代理】
2、jdk的动态代理
jdk动态代理是基于Java的反射机制实现的。使用jdk中接口和类实现代理对象的动态创建。
Jdk的动态要求目标对象必须实现接口,这是java设计上的要求。
从jdk1.3以来,java语言通过java.lang.reflect包提供三个类支持代理模式Proxy,Method和InovcationHandler
(1)InvocationHandler接口
InvocationHandler 接口叫做调用处理器,负责完调用目标方法,并增强功能。
通过代理对象执行目标接口中的方法,会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke() 方法,我们需要把功能代理写在 invoke() 方法中。
代理类完成的功能:
- 调用目标方法,执行目标方法的功能
- 功能增强,在目标方法调用时,增加功能
怎么用:
- 创建类实现接口 InvocationHandler
- 重写 invoke() 方法,把原来静态代理中代理类要完成的功能,写在这里
源码:
- object proxy:jdk创建的代理对象,无需赋值
- Method method:目标类中的方法,jdk提供method对象
- object[] args:目标类中方法的参数,jdk提供的
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
(2)method
反射,Method类, 表示方法(类中的方法)。通过Method可以执行某个方法。
- HelloService(接口)
package cn.edu.huat.method;
public interface HelloService {
void sayHello(String name);
}
- HelloServiceImpl(实现类)
package cn.edu.huat.method.impl;
import cn.edu.huat.method.HelloService;
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("你好," + name);
}
}
- test(测试)
package cn.edu.huat.test;
import cn.edu.huat.method.HelloService;
import cn.edu.huat.method.impl.HelloServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test01 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//使用反射机制执行sayHello方法。核心Method (类中的方法)
HelloService target = new HelloServiceImpl();
//获取sayHello名称对于的Method类对象
Method method = HelloService.class.getMethod("sayHello", String.class);
//通过Method可以执行sayHello方法调用
//执行target对象sayHelld,参数是李四
Object ret = method.invoke(target,"李四");
}
}
注意:invoke是Method类中的一个方法。表示执行方法的调用
参数:
- object:表示对象的,要执行这个对象的方法
- object… args:方法执行时的参数值
返回值:
- object:方法执行后的返回值
作用:通过Method可以执行某个目标类的方法
method.invoke (目标对象,方法的参数);
说明:method.invoke() 就是用来执行目标方法的
(3)Proxy类
核心的对象,创建代理对象。之前创建对象都是new类的构造方法()。现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance ()
作用: 创建代理对象
源码:
参数:
- ClassLoader loader:类加载器,负责向内存中加载对象的。使用反射获取对象的ClassLoader
//类a,目标对象的类加载器
a.getCalss().getClassLoader();
- Class<?>[] interfaces:目标对象实现的接口,也是反射获取的
- InvocationHandler h:我们自己写的,代理类要完成的功能
- 返回值:代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
3、实现动态代理的步骤
(1)创建接口,定义目标类要完成的功能
package cn.edu.huat.service;
//目标接口
public interface UsbSell {
float sell(int amount);
}
(2)创建目标类实现接口
package cn.edu.huat.factory;
import cn.edu.huat.service.UsbSell;
//目标类
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
//目标方法
System.out.println("目标类中,执行sell目标方法");
return 85.0f;
}
}
(3)创建 InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
- 调用目标方法
- 增强功能
package cn.edu.huat.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//必须实现InvocationHandler接口
public class MySellHandler implements InvocationHandler {
private Object target = null;
//动态代理:目标对象是活动的,不是固定的,需要传入进来
public MySellHandler(Object target){
//给目标对象赋值
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//执行目标方法
//向厂家发送订单,告诉厂家,我买了U盘,厂家发货
result = method.invoke(target,args);
//商家需要加价,也就是代理要增加价格
if (result != null){
Float price = (Float) result;
price = price + 25;
result = price;
}
//在目标类的方法调用后,你做的其他功能,都是增强的意思
System.out.println("淘宝商家,给你一个优惠券,或者红包");
//增加的价格,也就是用户购买的价格
return result;
}
}
(4)使用Proxy类的静态方法,创建代理对象。并把返回值转为接口类型
package cn.edu.huat;
import cn.edu.huat.factory.UsbKingFactory;
import cn.edu.huat.handler.MySellHandler;
import cn.edu.huat.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MainShop {
public static void main(String[] args) {
//1、创建目标对象
UsbSell factory = new UsbKingFactory();
//2、创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
//3、创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
//4、通过代理执行方法
float price = proxy.sell(1);
System.out.println("通过代理对象,调用方法:" + price);
}
}
4、执行流程
5、总结
(1)什么是动态代理?
使用jdk的反射机制,创建对象的能力,创建的是代理类的对象。而不用你创建类文件。不用写java文件
动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象
jdk动态代理,必须有接口,目标类必须实现接口,没有接口时,需要使用cgLib动态代理
(2)知道动态代理能做什么?
可以在不改变原来目标方法功能的前提下,可以在代理中增强自己的功能代码(程序开发中的意思)。
比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。
//GoNong.class
GoNong gn = new GoNong();
gn.print();
你发现这个功能,现在还存在缺点,不能完全满足我项目的需要。需要在 gn.print() 执行后,需要自己在增加代码。用代理实现 gn.print() 调用时,增加自己代码,而不用去改原来的 GoNong 文件。此时即可使用动态代理。
四、cgLib 代理(了解)
CGLIB(Code Generation Library) 是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP。
使用JDK的Proxy实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。
但对于无接口的类,要为其创建动态代理,就要使用CGLIB来实现。CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final的类。
cglib经常被应用在框架中,例如Spring,Hibernate 等。Cglib 的代理效率高于Jdk。对于cglib一般的开发中并不使用。做一个了解就可以。