1.为什么要使用动态代理
我们先创建一个接口
public interface ICalc {
int add(int a, int b);
int sub(int a, int b);
}
创建接口的实现类,并在方法里加入一些日志
public class ICalcImpl implements ICalc {
@Override
public int add(int a, int b) {
System.out.println("这是一个加法运算");
int r = a + b;
System.out.println("add...");
System.out.println("加法运算结束,结果是" + r);
return r;
}
@Override
public int sub(int a, int b) {
System.out.println("这是一个减法运算");
int r = a - b;
System.out.println("sub....");
System.out.println("加法运算结束,结果是" + r);
return r;
}
}
创建App类,调用这些方法
public class App {
public static void main(String[] args) {
ICalc iCalc = new ICalcImpl();
iCalc.add(2, 3);
iCalc.sub(3, 2);
}
}
看起来没什么问题,程序正常执行,而且日志也打印出来了。
问题是:如果需求更改,需要打印其它内容的日志怎么办呢?以上代码硬编码,就显得缺乏灵活性,此时就需要动态代理
出厂。
2.动态代理的实现
将实现类中的日志信息全部删除
public class ICalcImpl implements ICalc {
@Override
public int add(int a, int b) {
int r = a + b;
System.out.println("add...");
return r;
}
@Override
public int sub(int a, int b) {
int r = a - b;
System.out.println("sub....");
return r;
}
}
在App类中实现动态代理
public class App {
public static void main(String[] args) {
//要被动态代理的目标对象
ICalc target = new ICalcImpl();
/**
* 第一个参数:ClassLoader 通过本类的字节码对象创建类加载器
* 在创建一个类时,如执行new Person(),会创建一个类加载器将Person.class加载到JVM中
* 而使用动态代理创建对象时,虽然是不常规的创建对象方式,但是毕竟也是在创建对象,所以同样也需要类加载器
* 所以需要我们把一个类加载器传入进去
*/
ClassLoader classLoader = App.class.getClassLoader();
/**
* 第二个参数:确定字节码实现的接口及字节码中的方法
* 动态代理机制会在程序运行时,动态生成一个class文件,这个class文件并不是凭空生成的,而是根据第二个参数
* 中的字节码生成,target.getClass()得到ICalcImpl的字节码,getInterfaces()获取ICalcImpl对象实现的
* 所有接口的的字节码,所以要用字节码数组,此处ICalcImpl只实现了ICalc接口,自动生成的class文件会自动实现
* 第二个参数所传入的接口。如下,第二个参数传入ICalc.class,自动生成的class就是下面这个样子,由于class文件
* 可读性差,此处用对应的java文件的形式表示。
* class Proxy0 implements ICalc{
* int add(int a,intb){
*
* }
* int sub(int a,int b){
*
* }
* }
*/
Class[] interfaces = target.getClass().getInterfaces();
/**
* 第三个参数:确定字节码中方法的方法体
* 第三个参数传入后会在方法体中加入h.invoke()方法,在执行动态字节码中的h.invoke()方法时,会执行MyHandler
* 中的invoke()方法
* class Proxy0 implements ICalc{
* int add(int a,intb){
* return h.invoke();
* }
* int sub(int a,int b){
* return h.invoke();
* }
* }
*
*/
InvocationHandler h = new MyHandler(target);
//在缓存中生成一个实现ICalc接口的代理对象Proxy0
ICalc proxy = (ICalc) Proxy.newProxyInstance(classLoader, interfaces, h);
//调用缓存中代理对象中的add方法,执行其中的h.invoke()方法,此时调用MyHandler类中的invoke()方法
//代理对象proxy作为MyHandler类中的invoke()方法的第一个参数,通过反射add()方法生成Method对象作为
//第二个参数,两个参数(1,2)生成Object数组作为第三个参数
proxy.add(1,2);
}
}
class MyHandler implements InvocationHandler {
private Object target;
//在实例化时传入目标对象
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置通知
System.out.println(method.getName() + "方法开始,参数是" + Arrays.toString(args));
//通过反射机制,调用动态代理proxy对象所调用的方法,add()或者sub(),方法参数是args中的内容,如果有执行结果,就返回执行结果
Object result = method.invoke(target, args);
//后置通知
System.out.println(method.getName() + "执行完毕,结果是" + result);
return result;
}
}
执行结果:
可见,我们并未在方法体写任何日志,但是通过动态代理在方法加入日志