代理模式:为其他对象提供一种代理以控制对这个对象的访问
一个简单的加减乘除功能
class Test{ interface Calculator{ int add(int a,int b); int sub(int a,int b); int mul(int a,int b); int div(int a,int b); } static class CalImpl implements Calculator{ @Override public int add(int a, int b) { return a+b; } @Override public int sub(int a, int b) { return a-b; } @Override public int mul(int a, int b) { return a*b; } @Override public int div(int a, int b) { return a/b; } } public static void main(String[] args) { CalImpl c = new CalImpl(); System.out.println(c.add(4,2)); System.out.println(c.sub(4,2)); System.out.println(c.mul(4,2)); System.out.println(c.div(4,2)); } }
变化来了,要求为每个方法调用前后,都需要打印出一个日志信息
class Test{ interface Calculator{ int add(int a,int b); int sub(int a,int b); int mul(int a,int b); int div(int a,int b); } static class CalImpl implements Calculator{ @Override public int add(int a, int b) { System.out.println("add方法开始!" +"a="+a+"b="+b); int r = a+b; System.out.println("add方法结束!" +"r="+r); return r; } @Override public int sub(int a, int b) { System.out.println("sub方法开始!" +"a="+a+"b="+b); int r = a-b; System.out.println("sub方法结束!" +"r="+r); return r; } @Override public int mul(int a, int b) { System.out.println("mul方法开始!" +"a="+a+"b="+b); int r = a*b; System.out.println("mul方法结束!" +"r="+r); return r; } @Override public int div(int a, int b) { System.out.println("div方法开始!" +"a="+a+"b="+b); int r = a/b; System.out.println("div方法结束!" +"r="+r); return r; } } public static void main(String[] args) { CalImpl c = new CalImpl(); System.out.println(c.add(4,2)); System.out.println(c.sub(4,2)); System.out.println(c.mul(4,2)); System.out.println(c.div(4,2)); } }
我们发现,这样完成业务根本不是一个好办法,代码在重复,业务(加减)和非核心业务(打印日志)在不断的重复,需求如果变化要加入开方,求余的过程,或者,需要上午需要日志,下午不需要日志的需求,那我们就需要不断地改写这段代码
我们应该使用动态代理来完成
动态代理:在内存中写入的字节码,直接被类加载器使用
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; interface Calculator { int add(int a, int b); int sub(int a, int b); int mul(int a, int b); int div(int a, int b); } class CalImpl implements Calculator { @Override public int add(int a, int b) { int r = a + b; return r; } @Override public int sub(int a, int b) { int r = a - b; return r; } @Override public int mul(int a, int b) { int r = a * b; return r; } @Override public int div(int a, int b) { int r = a / b; return r; } } class MyHandler implements InvocationHandler { private Calculator calculator; public MyHandler(Calculator calculator) { this.calculator = calculator; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"开始,参数:"+ Arrays.toString(args)); System.out.println(method.getName()+"结束,结果是:"+method.invoke(calculator,args)); return 0; } } public class AppTest { public static void main(String[] args) { Calculator calculator = new CalImpl(); ClassLoader classLoader = AppTest.class.getClassLoader(); //创建代理对象 Calculator cal = (Calculator) Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator)); cal.add(3, 2); cal.sub(3, 2); cal.mul(3, 2); cal.div(3, 2); } }
动态代理api
Proxy.newProxyInstance(classLoader, new Class[]{Calculator.class}, new MyHandler(calculator)
第一个参数:实例化一个对象,必然会调用类的构造器,运行第一次调用构造器,必定导致类的加载,而加载类的时候,就是jvm拿着classloader去加载类的字节码的,把字节码加载到内存中,才能进一步实例化对象
简单来说:只要实例化的对象,一定要加载类的字节码,加载字节码就一定要类的加载器。
使用动态代理的api实例化对象是一种不常用的方式,但这也是一种实例化,需要我们手动把类的加载器传入
第二个参数:第三个参数:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
第一个参数:第二个参数:第三个参数:调用的接口方法的参数
}
动态代理具体步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。