内容简介:《猪弟拱Java》连载番外篇:Java代理(中)
在上一篇中我用一个比较详细的案例描述了一下代理的概念,在这篇文章中,主要来看一下JDK动态代理和cblib子类代理
JDK动态代理
首先我们以一个简单的案例来说一下
案例:
现在有这样一个需求,为一个短信功能提供入参日志打印、异常打印和处理、返回结果打印、方法调用结束打印。先来看一下短信功能的代码:
首先是短信接口 ISmsSupport
package proxy; /** * 短信功能支持 * * @author 猪弟 * @since v1.0.0 */ public interface ISmsSupport { boolean sendMsg(String content, String phoneNumber); }
然后是一个短信功能的实现类
package proxy; /** * 短信功能实现 * * @author 猪弟 * @since v1.0.0 */ public class SmsSupportImpl implements ISmsSupport { @Override public boolean sendMsg(String content, String phoneNumber) { //模拟异常 int temp = 1 / 0; System.out.println(content + "," + phoneNumber); return true; } }
在实现类中,我们没有做任何的日志和异常处理 接下来我们用动态代理实现上面的需求,以及为什么要用动态代理实现? 首先我们要建一个代理类 SmsProxy
package proxy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 短信功能代理类 * * @author 猪弟 * @since v1.0.0 */ public class SmsProxy implements InvocationHandler { /** * 日志实例 */ private final static Logger LOGGER = LoggerFactory.getLogger(SmsProxy.class); /** * 被代理对象 */ private Object realSubject; /** * 构造器 * * @param realSubject 被代理对象 */ public SmsProxy(Object realSubject) { this.realSubject = realSubject; } /** * 获取代理对象的方法 * * @return 代理对象 */ public Object getProxy() { Class<?> subjectClass = realSubject.getClass(); return Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this); } /** * @param proxy 代理对象 * @param method 执行的目标方法 * @param args 方法参数 * @return Object 目标方法执行的结果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //目标方法的方法名 String methodName = method.getName(); //打印入参(前置通知) LOGGER.info("{} 方法的入参:{}", methodName, args); try { //反射Reflect执行核心方法 Object result = method.invoke(realSubject, args); //打印执行结果(返回后通知) LOGGER.info("{}方法的返回结果为:{}", methodName,result); return result; } catch (Throwable e) { //打印异常日志(异常通知) LOGGER.error("执行目标方法发生异常,异常:", e); return Boolean.FALSE; } finally { //方法执行完打印(后置通知) LOGGER.info("方法执行完成"); } } }
简单解释一下这个代理类
① 首先使用jdk动态代理要实现一个 java 为我们提供的 InvocationHandler
接口,并实现 invoke
方法,下面是接口的源码。
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
② 我们要在代理类中定义一个 Object
类型的属性用于引用被代理的对象,并通过构造器初始化。
③ 在类中定义一个函数 getProxy
用于获取代理对象,这里用到了 Proxy
类的静态方法 newInstance
,下面是方法的源码内容:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //要求传入的handler不能为空 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. * 使用指定的invocation handler 反射调用代理类的构造器 */ try { if (sm != null) { //检查访问权限 checkNewProxyPermission(Reflection.getCallerClass(), cl); } //获取代理类的构造器对象 final Constructor<?> cons = cl.getConstructor(constructorParams); //传入的invocation handler final InvocationHandler ih = h; //如果类不是public的,先赋予权限 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); } }
④ 最后就是实现我们的 invoke
方法了,先看一下方法的结构和我们的实现:
/** * @param proxy 代理对象 * @param method 执行的目标方法 * @param args 方法参数 * @return Object 目标方法执行的结果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //目标方法的方法名 String methodName = method.getName(); //打印入参(前置通知) LOGGER.info("{} 方法的入参:{}", methodName, args); try { //反射Reflect执行核心方法 Object result = method.invoke(realSubject, args); //打印执行结果(返回后通知) LOGGER.info("{}方法的返回结果为:{}", methodName,result); return result; } catch (Throwable e) { //打印异常日志(异常通知) LOGGER.error("执行目标方法发生异常,异常:", e); return Boolean.FALSE; } finally { //方法执行完打印(后置通知) LOGGER.info("方法执行完成"); } }
是不是很简单呢,下面是入口程序:
package proxy; /** * 程序入口 * * @author 猪弟 * @since v1.0.0 */ public class Bootstrap { public static void main(String[] args){ demo1(); } public static void demo1(){ ISmsSupport smsSupport = new SmsSupportImpl(); ISmsSupport proxy = (ISmsSupport) new SmsProxy(smsSupport).getProxy(); proxy.sendMsg("hello world", "110"); } }
我们先看一下运行结果 先把 SmsSupportImpl
中的 int i = 1/ 0;
注释掉正常运行:
17:12:26.976 [main] INFO proxy.SmsProxy - sendMsg 方法的入参:[hello world, 110] hello world,110 17:12:26.980 [main] INFO proxy.SmsProxy - sendMsg方法的返回结果为:true 17:12:26.980 [main] INFO proxy.SmsProxy - 方法执行完成
再取消注释,发生异常:
17:13:31.169 [main] INFO proxy.SmsProxy - sendMsg 方法的入参:[hello world, 110] 17:13:31.175 [main] ERROR proxy.SmsProxy - 执行目标方法发生异常,异常: java.lang.reflect.InvocationTargetException: null at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at proxy.SmsProxy.invoke(SmsProxy.java:63) at com.sun.proxy.$Proxy0.sendMsg(Unknown Source) at proxy.Bootstrap.demo1(Bootstrap.java:19) at proxy.Bootstrap.main(Bootstrap.java:12) Caused by: java.lang.ArithmeticException: / by zero at proxy.SmsSupportImpl.sendMsg(SmsSupportImpl.java:13) ... 8 common frames omitted 17:13:31.175 [main] INFO proxy.SmsProxy - 方法执行完成
从上面的运行结果可以看出,打印了入参和返回结果,正确拦截了异常并打印了异常信息,还有方法调用完成的日志。好啦!到这里动态代理的实现就结束了,接下来看看为什么要使用动态代理。
在这个案例中其实体现不出来动态代理的优势,为什么呢,因为要打印日志的类太少了,完全可以采用硬编码的方式去实现,那么想象一下有一万个类,每个类中有10个方法,每个方法之前都没有打日志,现在要你去实现为每个方法都实现入参和返回结果打印,你还会采用硬编码的方式吗?此时动态代理就发挥了作用,你只需要写一个日志代理类,专门用来完成这个打印功能,上代码:
package proxy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 日志代理打印类 * * @author 猪弟 * @since v1.0.0 */ public class LogProxy<T> implements InvocationHandler { /** * 日志实例 */ private final static Logger LOGGER = LoggerFactory.getLogger(LogProxy.class); /** * 被代理对象 */ private T realSubject; /** * 构造器 * * @param realSubject */ public LogProxy(T realSubject) { this.realSubject = realSubject; } /** * 获取代理对象 * * @return 代理 */ public T getProxy() { Class<?> subjectClass = realSubject.getClass(); return (T) Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this); } /** * @param proxy 代理对象 * @param method 执行的目标方法 * @param args 方法参数 * @return Object 目标方法执行结果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //目标方法的方法名 String methodName = method.getName(); //打印入参(前置通知) LOGGER.info("{} 方法的入参:{}", methodName, args); try { //执行核心方法 Object result = method.invoke(realSubject, args); //打印执行结果(返回后通知) LOGGER.info("{}方法的返回结果为:{}", methodName,result); return result; } catch (Throwable e) { //打印异常日志(异常通知) LOGGER.error("执行目标方法发生异常,异常:", e); return Boolean.FALSE; } finally { //方法执行完打印(后置通知) LOGGER.info("方法执行完成"); } } }
其实这个类和前面的类区别不大,只是加入了泛型和把 getProxy
的返回类型改成了泛型。 调用方式和前面的类似,结果也一样,入口程序:
package proxy; /** * 程序入口 * * @author 猪弟 * @since v1.0.0 */ public class Bootstrap { public static void main(String[] args){ demo2(); } public static void demo2() { ISmsSupport smsSupport = new SmsSupportImpl(); LogProxy<ISmsSupport> proxy = new LogProxy<>(smsSupport); ISmsSupport smsProxy = proxy.getProxy(); smsProxy.sendMsg("hello world", "110"); } }
其实你会发现,虽然书写方便了,但是去修改调用方法的代码也是一件工作量很大的事情,我们可以采用AOP来处理,在AOP中采用切入点表达式可以不用修改调用方法的代码就能实现使用代理打印日志。关于AOP留到下一篇中去讲述。
由于时间原因,没有把反编译代码和字节码展示、解读,有时间再回来补
cglib子类代理
在上一篇文章末尾我们介绍了三种代理的使用条件和使用限制,来回顾一下
① 静态代理可以代理某一类对象,这一类对象必须实现同一接口,所以它的使用条件就是被代理类要实现接口。上面的各种场景短信都实现了ISmsService接口,所以代理类可以代理所有场景短信实现类,并调用真正的短信发送方法去发送正确的场景短信。 ① 动态代理的使用条件也是被代理类要实现接口,但是动态代理能够代理所有实现了接口的类,强大必然也会有缺点:动态代理依赖Java反射机制,反射是一个比较影响性能的功能,所以动态代理性能上会比静态代理低。 ③ cglib子类代理,首先需要依赖第三方库,然后它是基于字节码来生成子类代理的,没有特定的使用条件,所以也不需要实现接口,它可以代理所有的类,所以论性能是比不上静态代理的。
从上面可以看出,前两种代理都需要实现接口才能使用,而 CGLIB
不用
此处留一个面试中遇到的问题:
jdk代理中为什么一定要实现接口呢,用抽象类不可以吗?用心思考一下,相信和我一样聪明的你一定能得到答案
好的,我们把问题留到下一篇中解答
OK,接下来我们还是同样的一个需求,使用CGLIB代理的方式实现,如果你理解了上面动态代理的内容, CGLIB
的内容就能轻松的理解了,这里我们只学习使用方式,关于原理他是根据字节码动态生成的子类,猪弟的水平还达不到就不具体解释了,待日后我猪拱神功练成之时在来讲述原理。
先看一下短信发送类,没有实现任何接口:
package xin.sun.proxy.cglib; /** * 短信功能实现 * * @author 猪弟 * @since v1.0.0 */ public class SmsSupport { public boolean sendMsg(String content, String phoneNumber) { //模拟异常 //int temp = 1 / 0; System.out.println(content + "," + phoneNumber); return true; } }
再看一下代理工厂类:
引入了 spring 的 maven 依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.10.RELEASE</version> </dependency>
package xin.sun.proxy.cglib; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @author 猪弟 */ public class ProxyFactory implements MethodInterceptor { /** * 被代理对象 */ private Object target; /** * 构造器 * * @param target */ public ProxyFactory(Object target) { this.target = target; } /** * 获取代理对象实例 * * @return Object 代理对象实例 */ public Object getProxyInstance() { /** * 工具类 */ Enhancer enhancer = new Enhancer(); /** * 设置父类 */ enhancer.setSuperclass(target.getClass()); /** * 设置回调对象 */ enhancer.setCallback(this); /** * 创建子类代理对象,并返回 */ return enhancer.create(); } /** * 重写拦截方法 * * @param o 代理对象 * @param method 委托类方法 * @param objects 方法参数 * @param methodProxy 代理方法的MethodProxy对象 * @return Object 目标方法执行结果 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //目标方法的方法名 String methodName = method.getName(); //打印入参(前置通知) LOGGER.info("{} 方法的入参:{}", methodName, objects); try { //执行核心方法 Object result = method.invoke(realSubject, objects); //打印执行结果(返回后通知) LOGGER.info("{}方法的返回结果为:{}", methodName, result); return result; } catch (Throwable e) { //打印异常日志(异常通知) LOGGER.error("执行目标方法发生异常,异常:", e); return Boolean.FALSE; } finally { //方法执行完打印(后置通知) LOGGER.info("方法执行完成"); } } }
简单解释一下这个代理类
① 首先使用cglib代理要实现一个框架为我们提供的 MethodInterceptor
接口,并实现 intercept
方法。
② 我们要在代理类中定义一个 Object
类型的属性用于引用被代理的对象,并通过构造器初始化。
③ 在类中定义一个函数 getProxyInstance
用于获取代理对象,这里和jdk动态代理有点区别,这里使用的是 Enhancer
这个类,看一下方法的实现:
public Object getProxyInstance() { /** * Enhancer工具类 */ Enhancer enhancer = new Enhancer(); /** * 设置父类 */ enhancer.setSuperclass(realSubject.getClass()); /** * 设置回调对象,我们实现的这个类本身就是实现了接口的,所以传本类的实例就好了 */ enhancer.setCallback(this); /** * 创建子类代理对象,并返回 */ return enhancer.create(); }
④ 最后就是实现我们的 intercept
方法了,先看一下方法的结构和我们的实现:
/** * 重写拦截方法 * * @param o 代理对象 * @param method 委托类方法 * @param objects 方法参数 * @param methodProxy 代理方法的MethodProxy对象 * @return Object 目标方法执行结果 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //目标方法的方法名 String methodName = method.getName(); //打印入参(前置通知) LOGGER.info("{} 方法的入参:{}", methodName, objects); try { //执行核心方法 Object result = method.invoke(realSubject, objects); //打印执行结果(返回后通知) LOGGER.info("{}方法的返回结果为:{}", methodName, result); return result; } catch (Throwable e) { //打印异常日志(异常通知) LOGGER.error("执行目标方法发生异常,异常:", e); return Boolean.FALSE; } finally { //方法执行完打印(后置通知) LOGGER.info("方法执行完成"); } }
同样的,我们写一个入口程序来检测一下:
package xin.sun.proxy.cglib; /** * @author 猪弟 */ public class Bootstrap { public static void main(String[] args) { demo(); } public static void demo(){ ProxyFactory proxyFactory = new ProxyFactory(new SmsSupport()); SmsSupport proxyInstance = (SmsSupport) proxyFactory.getProxyInstance(); proxyInstance.sendMsg("CGLIB 代理","4008008820"); } }
看看正常运行的结果:
22:06:26.439 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入参:[CGLIB 代理, 4008008820] CGLIB 代理,4008008820 22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg方法的返回结果为:true 22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法执行完成
同样的,我们人为制造一个异常 int temp = 1 / 0;
,看看异常能否被捕获:
22:10:36.454 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入参:[CGLIB 代理, 4008008820] 22:10:36.460 [main] ERROR xin.sun.proxy.cglib.ProxyFactory - 执行目标方法发生异常,异常: java.lang.reflect.InvocationTargetException: null at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at xin.sun.proxy.cglib.ProxyFactory.intercept(ProxyFactory.java:80) at xin.sun.proxy.cglib.SmsSupport$$EnhancerByCGLIB$$8593c815.sendMsg(<generated>) at xin.sun.proxy.cglib.Bootstrap.demo(Bootstrap.java:15) at xin.sun.proxy.cglib.Bootstrap.main(Bootstrap.java:9) Caused by: java.lang.ArithmeticException: / by zero at xin.sun.proxy.cglib.SmsSupport.sendMsg(SmsSupport.java:13) ... 8 common frames omitted 22:10:36.461 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法执行完成
从结果可以看到,完美的捕获到了异常
可以看到,在cglib中的短信发送类没有实现接口,同样的也实现了代理的功能,所以cglib才是真的能代理所有的类,这也是对jdk动态代理的一个补充吧,但是在Spring的AOP中默认是使用jdk动态代理的,如果被代理类没有实现接口,Spring会自动为我们切换cglib子类代理,是不是觉得Spring很人性化,其实 Spring AOP 为我们提供了更加方便的方式去做代理,下一篇文章会讲述Spring中的AOP编程。
Spring AOP 把代理做了一次封装,用起来当然更加方便了,但是要想成为java大神,还是要追究其实质的,这也是为什么我要写这两篇文章的原因,所有的框架都是基于Java基础+编程思想来实现的,其实明白了框架的设计,我们自己也能实现Spring IOC DI AOP,等有机会为大家手撸一波Spring框架,当然Spring是高度的抽象,所以源码阅读起来没那么容易,但是其思想还是很容易懂的,学会思想才是真的叼,毕竟思想语言无关。
磊叔是Spring专家,感兴趣的多看看磊叔的博客哦!!!
为了提供更加方便的阅读,小伙伴们可以关注我们的公众微信号哦...
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Java代码审计连载之XSS
- Java代码审计连载之SQL注入
- 大咖连载|可扩展性设计(二)
- 连载四:PyCon2018|恶意域名检测实例(附源码)
- CAD控件CAD .NET问题解答集锦(连载一)
- 【技术小说连载】我在JVM公司的那些年(四)——工位调整
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML 5 与 CSS 3 权威指南
陆凌牛 / 机械工业出版社华章公司 / 2011-4-7 / 69.00
如果你是一位有前瞻性的web前端工作者,那么你一定会从本书中受益,因为它就是专门为你打造的。 《HTML 5与CSS 3权威指南》内容系统而全面,详尽地讲解了html 5和css 3的所有新功能和新特性;技术新颖,所有知识点都紧跟html 5与css 3的最新发展动态(html 5和css 3仍在不断完善之中);实战性强(包含246个示例页面),不仅每个知识点都配有精心设计的小案例(便于动手......一起来看看 《HTML 5 与 CSS 3 权威指南》 这本书的介绍吧!