设计模式之代理模式
代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。代理模式属于结构性设计模式。
代理类的作用类似于租房的中介、售票黄牛、快递小哥、经纪人、事务代理、非入侵式的日志监控。
代理模式包括静态代理和动态代理。
静态代理
静态代理是指代理类B对服务类A进行代理,代理A去提供服务,并对A提供的服务进行增强,而且,代理类B只能代理服务类A。下面用两个例子来讲解静态代理。
例子一
比如儿子要找对象,要求是肤白貌美大长腿,父亲帮儿子找。
父亲和儿子都是人,所以我们先写一个人的接口,他有一个方法,找对象。
public interface Person {
int findLove();
}
接下来写儿子类,他要实现人的接口,同时实现找对象方法。
public class Son implements Person {
public int findLove() {
System.out.println("儿子要求:肤白貌美大长腿");
return 0;
}
public void findJob() {
}
public void eat() {
}
}
然后是父亲类,他有一个自己儿子的引用,并且实现了找对象方法,他帮他儿子找对象。
public class Father implements Person {
private Son person;
public Father(Son person) {
this.person = person;
}
public int findLove() {
System.out.println("父亲物色对象");
this.person.findLove();
System.out.println("双方父母同意,确立关系");
return 0;
}
public void findJob() {
}
}
这种就是典型的静态代理,儿子要找对象,父亲帮儿子找对象,而且只给自己的儿子找对象,这也是
静态代理的局限性,只给特定的对象做代理。下面我们写一个测试类看看运行结果。
public class FatherProxyTest {
public static void main(String[] args) {
Father father = new Father(new Son());
father.findLove();
// 输出:
// 父亲物色对象
// 儿子要求:肤白貌美大长腿
// 双方父母同意,确立关系
}
}
结果输出了“父亲物色对象,儿子要求:肤白貌美大长腿,双方父母同意,确立关系”,我们能看出,儿子并没有去找对象,是父亲去找对象的,而且父亲在儿子找对象之前和之后做了其他的一些操作,对儿子找对象进行了增强,这种自己的事情A不去做,交给其他人B去做,这个其他人B能对A要做的事情进行增强,并且只能专门为A做增强的情况,就是静态代理。
例子二
接下来进行一个数据源动态切换的场景,比如一个订单系统,要完成下单的操作,要求能根据订单时间进行分库。
首先是订单实体:
public class Order {
private Object orderInfo;
// 订单创建时间进行按年分库
private Long createTime;
private String id;
public Object getOrderInfo() {
return orderInfo;
}
public void setOrderInfo(Object orderInfo) {
this.orderInfo = orderInfo;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
然后是生成订单的DAO:
public class OrderDao {
public int insert(Order order) {
System.out.println("OrderDao创建Order成功!");
return 1;
}
}
然后是提供生成订单的service接口和实现:
public interface IOrderService {
int createOrder(Order order);
}
public class OrderService implements IOrderService {
private OrderDao orderDao;
public OrderService() {
// 如果使用Spring应该是自动注入的
// 我们为了使用方便,在构造方法中将orderDao直接初始化了
orderDao = new OrderDao();
}
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
return orderDao.insert(order);
}
}
然后我们写一个动态数据源实体:
public class DynamicDataSourceEntity {
public final static String DEFAULE_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DynamicDataSourceEntity() {
}
public static String get() {
return local.get();
}
public static void restore() {
local.set(DEFAULE_SOURCE);
}
// DB_2018
// DB_2019
public static void set(String source) {
local.set(source);
}
public static void set(int year) {
local.set("DB_" + year);
}
}
这个数据源实体用了单例模式的ThreadLocal写法,以保证线程里获取到的数据源实例是同一个。
然后我们对这个数据源进行代理:
public class OrderServiceStaticProxy implements IOrderService {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private IOrderService orderService;
public OrderServiceStaticProxy(IOrderService orderService) {
this.orderService = orderService;
}
public int createOrder(Order order) {
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
DynamicDataSourceEntity.set(dbRouter);
this.orderService.createOrder(order);
DynamicDataSourceEntity.restore();
return 0;
}
}
这样我们就能对不同的订单根据订单时间来进行分库查询,根据订单时间获取到数据源名称,然后用订单代理类调用数据源实体的方法实时切换数据源。
这也是静态代理,订单代理类对订单服务进行代理,代替订单服务去进行创建订单,并对订单服务进行增强,根据订单时间进行分库。
动态代理
动态代理相对于静态代理来说更加灵活,动态代理同样是代理类代替服务类去提供服务,同时对服务类提供的服务进行增强,但是动态代理不仅仅只能对一种服务进行增强,他对要增强的服务没有要求。比如上面的例子父亲给儿子找对象,他只能给儿子找对象,要是他相帮儿子找工作的话,父亲类就得增加找工作的方法,儿子要干什么,父亲也得有相应的方法才行,儿子增加了方法,父亲类也得修改,这不符合开闭原则。
动态代理的实现有两种,分别是JDK的动态代理,和cglib提供的动态代理。
JDK动态代理
回到找对象的例子,现在有个女孩要找对象,她不自己去找,她通过媒婆去找,媒婆是可以给任何人找对象的,他可以给女孩找,也可以给儿子找,等等,下面用JDK的动态代理实现这个过程。
首先是女孩类,实现人的接口,同样有找对象方法:
public class Girl implements Person {
public int findLove() {
System.out.println("高富帅");
System.out.println("身高180cm");
System.out.println("有6块腹肌");
return 0;
}
}
然后是媒婆类,实现了JDK反射包下面的InvocationHandler接口:
public class JDKMeipo implements InvocationHandler {
private Object target;
public Object getInstance(Object person) throws Exception {
this.target = person;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object obj = method.invoke(this.target, args);
after();
return obj;
}
private void before() {
System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after() {
System.out.println("OK的话,准备办事");
}
}
到这里先不讲媒婆类里的方法都是干什么用的,先写个测试类运行看下结果:
public class JDKProxyTest {
public static void main(String[] args) {
try {
Object obj = new JDKMeipo().getInstance(new Girl());
Method method = obj.getClass().getMethod("findLove", null);
method.invoke(obj);
// Person obj = (Person) new JDKMeipo().getInstance(new Girl());
// obj.findLove();
// $Proxy0
// byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
// FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
// os.write(bytes);
// os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
一运行,神奇的事情发生了,结果输出:
我是媒婆,我要给你找对象,现在已经确认你的需求
开始物色
高富帅
身高180cm
有6块腹肌
OK的话,准备办事
Object obj = new JDKMeipo().getInstance(new Girl());这行代码生成的obj是什么?如果它是媒婆的话,为什么他会有findLove方法?如果它是个Girl的话,它调用findLove方法为什么会输出“我是媒婆XXX”这些?
重点来了,在我们打断点想看看这个obj是什么的时候看到:
这个obj是个 $Proxy0,我们没有一个叫做 $Proxy0的类啊,它为什么会有findLove方法,而且前后多输出了东西?接下来我们把这个 $Proxy0这个对象从内存中输出出来,写到磁盘上,变成一个class文件,再反编译出来,看看它到底是什么鬼。
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
os.write(bytes);
os.close();
再执行完注释掉的这段代码后,E盘生成了一个叫$Proxy0的class文件,反编译一下,看看里面是什么:
public final class $Proxy0 extends Proxy
implements Person
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int findLove()
throws
{
try
{
return ((Integer)this.h.invoke(this, m3, null)).intValue();
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.lmj.study.pattern.proxy.Person").getMethod("findLove", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
我们能看到,这个 $Proxy0继承了Proxy,又实现了Person接口,它为什么要实现Person接口呢,就是为了重写findLove方法,然后在findLove里执行了h.invoke方法,这个h对象就是媒婆实现的InvocationHandler 接口的对象,这个媒婆是从那里传进来的呢,是从媒婆的getInstance方法里把this传进去的,Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this)这个方法把this赋值给了h,然后就执行了媒婆的invoke方法,媒婆的invoke方法里调用了before和after方法。
往下看 $Proxy0这个类的话会发现,它下面有一个静态代码块,通过反射,把代理目标对象的方法全部扫描出来,上面findLove方法第二个参数传的是m3,h.invoke(this, m3, null),m3就是Girl的findLove方法,然后在媒婆的invoke方法里调用这个Girl的findLove方法,至此一切真相大白。
总结起来就是,媒婆类并不是真正做代理的代理类,媒婆类通过Proxy.newProxyInstance生成了一个新的类,然后用类加载器动态加载到JVM中,这个才是真正的代理类,$Proxy0这个代理类继承了JDK的Proxy类,又实现了引用的被代理类实现的接口,调用被代理类的方法时,实际上是调用了 $Proxy0里的相对应的方法,里面调用了h.invoke方法,h.invoke就是代理工具类里的invoke方法。
- 拿到被代理类的引用,并且获取到它的所有接口(反射获取);
- JDK的Proxy类重新生成一个新的类,实现了被代理类的所有接口的方法;
- 动态生成JAVA代码,把增强逻辑加到新生成的代码中;
- 编译生成新的JAVA代码的class文件;
- 加载并重新运行新的class,得到的类就是新的代理类。
CgLib动态代理
CgLib的动态代理和JDK动态代理的思想一样,都是通过生成字节码文件,重新生成一个类。他们二者的区别是:
- CgLib采用继承的方式,覆盖父类的方法,JDK Proxy采用实现接口的方式,要求被代理的目标对象必须实现一个接口;
- JDK Proxy对用户而言,依赖性更强,调用也更复杂,CgLib对目标类没有任何要求;
- CgLib效率更高,性能也更高,底层没有用到反射,JDK Proxy生成逻辑较为简单,每次都要用反射,执行效率低;
- CgLib有个坑,目标代理类不能有final方法,会忽略final修饰的方法;
例子:
import java.lang.reflect.Method;
import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目标对象
//重写拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
//定义获取代理对象方法
public Object getCglibProxy(Object objectTarget){
//为目标对象target赋值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 设置回调
Object result = enhancer.create();//创建并返回代理对象
return result;
}
public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();//实例化CglibProxy对象
UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());//获取代理对象
user.delUser("admin");//执行删除方法
}
}