什么是动态代理:不管我们的对象是什么,我都可以通过这样一个类去生成一个相应的代理对象,帮助开发者去完成功能,和目标对象(被代理者)没有任何关系
动态代理有两种写法,一种匿名内部类实现InvocationHandler,一种写一个处理器类继承InvocationHandler,本文选用第一种
准备步骤:
1.创建有一个接口,里面定义了一个或多个抽象方法
package com.atguigu.spring.proxy; public interface Math { int add(int a,int b); int sub(int a,int b); int mul(int a,int b); int div(int a,int b); }
2.创建一个实现类(即我们的被代理类),实现这个接口,并重写了方法
package com.atguigu.spring.proxy; public class MathImpl implements Math { @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; } }
3.创建一个日志log类
package com.atguigu.spring.proxy; public class MyLogger { public static void before(String methodName,String args){ System.out.println("method:"+methodName+",arguments:"+args); } public static void after(String methodName,Object result){ System.out.println("method:"+methodName+",result:"+result); } }
开始写代理类步骤:
1.创建一个能够获取代理类的工具类
思路分析:动态代理的目的是获取一个代理类来完成工作
获取代理类java有一个通用的方法就是使用Proxy类的静态方法newProxyInstance
该方法返回一个代理类,所以是有返回值的,一般为Object,和本文首行的动态代理的定义相呼应,但是需要传入三个参数
参数1:类加载器 参数2:被代理者接口的Class对象数组 参数3:一个代理类处理器
参数1分析:我们要加载一个类需要类加载器,而代理类就是我们需要加载的类,所以我们需要传入一个类加载器
那么这个类加载器传什么比较好,因为工具类也是由类加载器加载的,所以我们直接this.getClass().getClassLoader()来获取类加载器,
其实这个类加载器属于应用程序类加载器,如果你使用工具类中存在的被代理者对象的getClass().getClassLoader()也是一样的效果
参数2分析:代理类需要完成的是和被代理者相同的事,只不过加了一些其他的业务逻辑,那么被代理者继承了接口,重写了方法,同理,我代理类
也需要继承它继承过的接口,这样被代理者重写过的方法,代理类也需要重写,就能达到调用相同业务逻辑的目的
参数3分析:一个代理类处理器,这个处理器就是帮助我们完成业务逻辑的,我们一般使用匿名内部类来完成传参,匿名内部类中需要重写一个invoke
方法,该方法有三个形参,分别会传入代理类对象,重写的方法对象和方法的参数。第一个参数是代理类对象,这个很好理解,因为代理类继承了接口,
那么肯定也需要重写接口方法,并且我们需要调用代理类重写的方法,所以传入代理类这个对象是必要的。并且在这里是底层自己传入,
因为我们创建的是代理类是匿名内部类,开发者是无法传过去的,是底层自己传过去。第二个可能会疑问为什么这里的方法对象不是数组呢,因为
我们调用一个方法是一个个调用的,不能一下子把所有方法全调用了,就比如使用方法是mathImpl.add(),总不能mathimpl.add[]()或者
mathimpl.add().sub().div()这样的传。第三个参数就是接口方法中可能需要传递的参数了。invoke()方法的形参传完之后,invoke()方法的方法体
就是进行方法调用,也就是实现的是代理者对接口的方法重写,这里是反射的知识,即method.invoke(被调用的方法所属的对象,参数),代理者的接
口方法重写里面调用被代理者写好的业务逻辑,有返回值或者没返回值都return一下,保证和被代理类的返回结果一样
回归以上,在上面这个方法调用获取代理类的时候,我们有一个点没有提到,那就是被代理者,在上面的newProxyInstance()的参数二和method.invoke()里面的参数
都会用到被代理者的对象,这也说明了工具类中需要持有被代理者的对象,用于调用它的方法,因为动态代理的核心业务逻辑仍是被代理类的方法完成的。所以工具
类中需要定义一个被代理对象的属性,并写出有参构造,才能在创建代理工具类的同时赋值一个被代理者对象
代码如下
package com.atguigu.spring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; public class ProxyUtil { //注意一下这里写的是MathImpl,其实应该写Object,因为是动态代理,对所有的对象都适用 private MathImpl mathImpl; public ProxyUtil(MathImpl mathImpl) { this.mathImpl = mathImpl; } public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), mathImpl.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MyLogger.before(method.getName(), Arrays.toString(args)); Object result = method.invoke(mathImpl, args); MyLogger.after(method.getName(),result); return result; } }); } }
2.测试类
package com.atguigu.spring.proxy; public class ProxyTest { public static void main(String[] args) { ProxyUtil proxyUtil = new ProxyUtil(new MathImpl()); Math proxy = (Math)proxyUtil.getProxy(); System.out.println(proxy.add(1,1)); } }
代码分析,首先创建了一个代理类对象,给对象中的属性:被代理者对象 赋值,之后调用获取代理类对象的方法,这个代理类对象是实现了和被代理类对象MathImpl相同的接口Math的,因此需要进行类型转换,而且只能转换成接口类型,多态的方式进行变量引用。之后通过变量调用方法就可以
原理如下,看上去有些乱,其实理解了很简单,画的不好见谅