- 代理模式:为其他对象提供一种代理以控制对这个对象的访问,在某种情况下,一个对象不适合或者不能够直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。
- 可以这么理解:使用代理对象,是为了在不修改目标对象的基础上,增强主业务的逻辑。就相当于某个普通人(目标对象),他现在需要打官司,那么他可以自己学习法律,为自己辩护(相当于把业务代码逻辑自己来实现),这就是修改了目标对象,那么当然有一种更好的方法啦,那就是请律师(也相当于代理对象),业务代码(为自己辩护)可以由律师来实现。
代理一般可以分为三种:静态代理,动态代理,cglib代理;
1.静态代理
静态代理使用的时候,一般是定义接口或者父类,目标对象(被代理的对象)与代理对象都要实现相同的接口或者继承同样的父类。
下面实现静态代理
代码结构:
创建一个接口类 (IBuyDao.calss)买东西:
public interface IBuyDao { public void buySomething(); }
然后创建一个实现了接口的目标类(BuyDao.calss )即要买东西的客户:
public class BuyDao implements IBuyDao { @Override public void buySomething() { System.out.println("我是客户,我想买东西"); } }
代理类(BuyDaoProxy):将目标对象当成属性传进去,对目标对象进行增强
public class BuyDaoProxy implements IBuyDao{ private IBuyDao target; public BuyDaoProxy(IBuyDao target){ this.target = target; } @Override public void buySomething() { System.out.println("开始代理方法(购物)"); target.buySomething(); System.out.println("结束代理方法"); } }
测试方法(Test.class):
public class Test { public static void main(String [] args){ IBuyDao target = new BuyDao(); //应该写成这样 IBuyDao proxy = new BuyDaoProxy(target); //下面的这样写就不算严格意义的代理了,代理应该是返回目标对象或接口对象(java一切皆对象) //BuyDaoProxy proxy = new BuyDaoProxy(target); proxy.buySomething(); } }
结果如下:
- 在这里有一个疑惑,就是如果BuyDaoProxy.class 没有实现接口的话,也是可以跑起来,而且结果一样。假如改成这样子:
public class BuyDaoProxy { private IBuyDao target; public BuyDaoProxy(IBuyDao target){ this.target = target; } public void buySomething() { System.out.println("开始代理方法(购物)"); target.buySomething(); System.out.println("结束代理方法"); } }
个人理解:如果没有实现接口的话,也是可以实现的,这就相当于接口调用,但是一般我们使用代理都会是相同方法名字,使用接口的话,可以强制性使用相同的方法名,而不是随意起一个名字,不使用接口时使用相同方法名也是没有问题的,只是容易写错名字,特别是同一个代理有很多方法的时候。但是这样写就不能用 IBuyDao proxy = new BuyDaoProxy(target); ,那么这意义也就不能算是代理了,代理应该返回接口或目标对象
实现多个接口的例子(新增加了一个学生买书的接口,以及实现类):
接口:
public interface IStudent { public void Buybook(); }
接口实现类:
public class Student implements IStudent{ @Override public void Buybook() { System.out.println("我是学生,我想买书"); } }
代理类:
public class BuyDaoProxy implements IStudent,IBuyDao{ private IBuyDao target; public Student student; public BuyDaoProxy(IBuyDao target){ this.target = target; } public BuyDaoProxy(Student student){ this.student =student; } @Override public void buySomething() { System.out.println("开始代理方法(购物)"); target.buySomething(); System.out.println("结束代理方法"); } @Override public void Buybook() { System.out.println("开始代理方法(买书)"); student.Buybook(); System.out.println("结束代理方法"); } }
测试类:
public class Test { public static void main(String [] args){ Student target = new Student(); BuyDaoProxy proxy = new BuyDaoProxy(target); proxy.Buybook(); } }
结果:
个人理解:实现多个接口的时候,要是没有去实现多个接口,就很容易把名字写错,所以强制性使用接口,实现一致的名字,对目标类进行功能增强(在目标类方法之前或者之后处理)。
缺点:代理对象需要和目标对象实现一样的接口,所以目标类多了,或者接口增加方法,目标类以及代理的类都要维护。
2.动态代理(即JDK代理,接口代理)
- 代理对象不需要实现接口,但是目标对象一定要实现接口
- 使用的是jdk的API,动态的创建代理对象
我们来看代理的方法源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
从return cons.newInstance(new Object[]{h}); 这句我们可以知道,我们需要去操作h,h其实就是 InvocationHandler h 这个参数,那么我们需要重新定义 InvocationHandler h
- ClassLoader loader:是一个类加载器,这个获取类加载器的方法是固定的,我们不能坐任何改变
- Class<?>[] interfaces :这是接口类的数组,使用泛型确认接口类型,这时候接口参数就只能是目标对象所实现的接口
- InvocationHandler h:重要的是这个参数,重写它的invoke()方法,就可以实现对目标对象的接口的增强。
类的结构如下(之所以实现两个接口,是因为多接口的时候更容易分清):
![](https://uploadfiles.nowcoder.com/files/20210305/8319609_1614877406351/eb3e45b8bf52a425958ea616ff7acf8b.png)
代码如下:
IBuyDao.java(买东西的接口)
public interface IBuyDao { public void buySomething(); }
IPlayDao.java(玩的接口)
public interface IPlayDao { void play(); }
StudentDao.java(实现了买东西,玩的接口的学生类)
public class StudentDao implements IBuyDao,IPlayDao { @Override public void buySomething() { System.out.println("我是学生,我想买东西"); } @Override public void play() { System.out.println("我是学生,我想出去玩"); } }
MyProxy.java 代理类:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyProxy { private Object target; public MyProxy(Object target){ this.target=target; } public Object getProxyInstance(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { //一个接口可能很多方法,要是需要针对某一个方法,那么需要在函数里判断method @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始事务2"); //执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务2"); return returnValue; } } ); } }
测试类(Test.java)
public class Test { public static void main(String [] args){ StudentDao studentDao =new StudentDao(); IBuyDao target = studentDao; System.out.println(target.getClass()); IBuyDao proxy = (IBuyDao) new MyProxy(target).getProxyInstance(); System.out.println(proxy.getClass()); // 执行方法 【代理对象】 proxy.buySomething(); System.out.print("========================================================="); IPlayDao target2 = studentDao; System.out.println(target2.getClass()); IPlayDao proxy2 = (IPlayDao) new MyProxy(target2).getProxyInstance(); System.out.println(proxy2.getClass()); // 执行方法 【代理对象】 proxy2.play(); } }
结果如下:
个人理解:代理对象类不需要实现接口,通过对象的增强返回一个接口类对象(实际上是代理后产生的),然后再调用接口方法即可。缺点:目标对象一定要实现接口,否则就无法使用动态代理,因为方法参数有一个是接口名。
3.cglib代理
Student.class:
package test; public class Student { public void buy() { System.out.println("我是学生,我想买东西"); } }
MyProxy.class(代理类)
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class MyProxy implements MethodInterceptor { public Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub System.out.println("代理前-------"); proxy.invokeSuper(obj, args); System.out.println("代理后-------"); return null; } }
测试类(Test.class)
public class Test { public static void main(String[] args){ MyProxy myProxy =new MyProxy(); Student student = (Student)myProxy.getInstance(new Student()); student.buy(); } }
结构结果:
- cgilib 可以实现没有接口的目标类的增强,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,采用的是继承,所以不能对final修饰的类进行代理。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。
平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~