Java有三个动态特性:
- 反射
- 注解
- 动态代理
本文主要介绍动态代理的知识。
代理是什么
代理背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的,用户与代理打交道,不直接接触实际对象,虽然外部功能和实际对象一样,但代理有它存在的价值:
- 节省成本比较高的实际对象的创建开销,按需延迟加载,创建代理时并不真正创建实际对象,而只是保存实际对象的地址,在需要时再加载或创建
- 执行权限检查,代理检查权限后,再调用实际对象
- 屏蔽网络差异和复杂性,代理在本地,而实际对象在其他服务器上,调用本地代理时,本地代理请求其他服务器
动态代理是什么
首先我们来看看动态代理的定义。
动态代理:
动态代理可以在运行时动态地创建一个类,这个类实现一个或多个接口,在不修改原有类的基础上动态地为该类获取的对象添加方法,修改行为。
用大白话解释下就是,你在家外面想有刀削苹果,但是刀在你家里面。怎么办呢?这时候动态代理就可以在你需要刀的时候(anywhere,anytime)为你变出一把刀(但是你用这把变出来的刀削平果的时候其实是在使用家里的刀,这样有点超现实了,但是一时没想出更好的例子)。
动态代理应用
为什么要介绍动态代理呢?因为动态代理是面向切面编程(AOP)的基础。是不是很耳熟,没错,相信你在学习Spring的时候已经接触到了Spring AOP的概念了。
什么是Spring AOP呢?
在Java编程中,有很多切面如日志,权限,数据库事务等在程序中都会用到,代码都差不多,但是与具体的业务代码关系不大。
如果在每个地方都写这些代码会显得很冗余,难以维护。
Spring AOP将切面与主体逻辑的业务代码分离。
大白话介绍下就是:
比方说有个很爱你的男朋友,跟你异地,想知道 你的吃饭情况,当你准备去吃饭的时候。
不使用Spring AOP的情况:
不使用Spring AOP的情况就是当你吃饭的时候主动发短信给你男朋友说句“我吃饭了”,吃完饭了再发个短信给你男朋友说句“我吃完饭了”,这样你男朋友就知道你吃饭情况了。这个过程是你的目的是吃饭,但是却在吃饭前后发短信进行汇报,有点惹人怒。要是你男朋友是个女友控,要你随时汇报你的一举一动,时时都要发短信进行汇报,你是不是会疯掉?
使用Spring AOP的情况:
有了Spring AOP的思想,就可以帮你解决上述烦恼。当你准备吃饭的时候,Spring AOP就能立马检测到并向你的男朋友发送一条短信,在你吃完饭后也会发送短信。这样你就可以安心吃饭而无需担心这些恼人的短信操作了。
Spring AOP就是动态代理的应用之一,实际上动态代理广泛应用于各种系统程序、框架和库中,如Spring,Hibernate,Mybatis,Guice等。
动态代理实现的两种方式
动态代理有两种实现方式,一种是Java SDK提供的,另外一种是第三方库如cglib提供的。
介绍这两种方式之前先介绍下静态代理。
静态代理
静态代理代码结构比较简单,需要有三个要素:
- 共同的接口:定义方法不提供实现,供外部调用;
- 实现类:实现上述接口,提供上述方法的具体实现;
- 代理类:注入的是实现类,调用接口的方法实际是调用实现类的方法的实现。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 | package com.wgs.静态代理;/** * 静态代理 * @author GenshenWang.nomico * @time 2017/05/28 * */public class StaticProxyDemo { //公共接口,没有任何实现,必须. static interface CommonService{ public void sayHello(); } //公共接口的具体实现 static class RealService implements CommonService{ @Override public void sayHello() { System.out.println("hello,静态代理!"); } } //产生一个代理类,此类必须实现公共接口,作用:具有sayHello功能的类 static class StaticProxy implements CommonService{ private CommonService realService; //通过构造器注入接口,实际注入接口的是RealService public StaticProxy(CommonService service){ this.realService = service; } //此处模拟的即为AOP编程思想。sayHello()方法实现不变,只需在其上下加上一些诸如日志事务等代码, //无需在sayHello()中加上与业务代码无关的代码 @Override public void sayHello() { System.out.println("模拟日志输出1....."); realService.sayHello(); System.out.println("模拟日志输出2....."); } } //测试类 public static void main(String[] args) { CommonService service = new RealService(); CommonService proxy = new StaticProxy(service); proxy.sayHello(); }} |
输出:
123 | 模拟日志输出1.....hello,静态代理!模拟日志输出2..... |
在上述例子中我们可以看到,在不修改sayHello()方法源码的情况下即可在其上下加上调试语句,这个过程是通过代理类实现的。
为什么叫静态代理呢?
可以看到我们在程序中固定的为RealService类造出了一个代理类StaticProxy。
缺点就是如果再想为RealServiceB类,RealServiceC类…实现代理类,必须得另建代理类,就会显得很麻烦,这时候就需要动态代理去实现它了。
动态代理实现方式一—Java SDK动态代理
在静态代理中,代理类Proxy是直接定义在代码中的,而在动态代理中,代理是动态生成的,代码实现如下:
首先需要一个公共接口:
1234567
package com.wgs.动态代理;public interface CommonService { void sayHello();}公共接口的实现类,是被代理的实际对象:
12345678910
package com.wgs.动态代理;public class CommonServiceImpl implements CommonService { @Override public void sayHello() { System.out.println("hello 动态代理!"); }}动态产生代理类:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
package com.wgs.动态代理;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 动态代理类作用: 为一个真实对象产生一个类。 * 这个类具有真实对象的某个或者所有的功能 * 每一个动态代理类都要实现一个InvocationHandler * * ------------------------------------- * 拿移动营业厅来说。我们每个人都需要交话费,不能直接去移动总公司去交吧, * 所以每个地方都有个移动营业厅,其功能跟移动总公司是一样的.他负责与我们打交道。 * * @author GenshenWang.nomico * @time 2017/05/28 */public class DynamicProxyHanlder implements InvocationHandler{ //要代理的真实对象 private Object obj; //注入的是实际对象 public DynamicProxyHanlder(Object realObj){ this.obj = realObj; } /** * @proxy: Object proxy没有太大作用 * @method: 想要调用的方法 * @args:传入的参数值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("模拟日志输出1....."); //实际执行的业务代码,可能有返回值.注入实际接口与参数值 method.invoke(obj, args); System.out.println("模拟日志输出2....."); return null; } }产生动态代理对象并测试Proxy.newProxyInstance:
1234567891011121314151617181920212223242526272829303132
package com.wgs.动态代理;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;/** * 测试类 * @author GenshenWang.nomico * @time 2017/05/28 * */public class Client { public static void main(String[] args) { //代理的真实对象 CommonService realService = new CommonServiceImpl(); //产生一个代理类 InvocationHandler proxyHandler = new DynamicProxyHanlder(realService); //产生一个代理对象 CommonService proxyService = (CommonService) Proxy.newProxyInstance( CommonService.class.getClassLoader(), new Class<?>[]{CommonService.class}, proxyHandler); proxyService.sayHello(); }}
这里有两个地方要详细解释下。
1. InvocationHandler
第一个就是生成动态代理类的时候类需要实现InvocationHandler,它的构造方法接受一个参数realObj表示被代理的对象,
invoke方法处理所有的接口调用,它有三个参数:
- proxy:表示代理对象本身,需要注意,它不是被代理的对象,这个参数一般用处不大
- method:表示正在被调用的方法
- args :表示方法的参数
2. Proxy.newProxyInstance
使用java.lang.reflect包中的Proxy类的静态方法newProxyInstance来创建代理对象,方法定义如下:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
三个参数:
loader:表示类加载器
interfaces:表示代理类要实现的接口列表,是一个数组,元素的类型只能是接口,不能是普通的类,例子中只有一个IService
h:h的类型为InvocationHandler,它是一个接口,也定义在java.lang.reflect包中,它只定义了一个方法invoke,对代理接口所有方法的调用都会转给该方法
注:newProxyInstance的返回值类型为Object,可以强制转换为interfaces数组中的某个接口类型,这里我们强制转换为了IService类型,需要注意的是,它不能强制转换为某个类类型,比如RealService,即使它实际代理的对象类型为RealService。
动态代理实现方式二—cglib动态代理
Java SDK动态代理的局限在于:
它只能为接口创建代理,返回的代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理非接口中定义的方法,那就没有办法了。
有一个第三方的类库cglib(https://github.com/cglib/cglib)可以做到这一点,Spring,Hibernate等都使用该类库。
cglib:
cglib实现机制与Java SDK不同,它是通过继承实现的。
cglib动态地创建了一个类,但这个类的父类是被代理的类,代理类重写了父类所有public非final得方法,改为调用callback中的相关方法。
代码实现:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 | package com.wgs.动态代理;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;/** * 动态代理实现方式二---cglib * @author GenshenWang.nomico * @time 2017/5/30 * */public class CGLibDemo { /** * 被代理的类,不需要实现接口 * @author GenshenWang.nomico * */ static class RealService{ public void sayHello(){ System.out.println("hello,cglib"); } } static class SimpleInterceptor implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("模拟日志输出1.。。。"); Object result = proxy.invokeSuper(object, args); System.out.println("模拟日志输出2.。。。"); return result; } } /** * 为一个类生产代理对象 * @param clazz * @return */ @SuppressWarnings("unchecked") private static <T> T getProxy(Class<T> clazz){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(new SimpleInterceptor()); return (T)enhancer.create(); } /** * 测试 */ public static void main(String[] args) throws Exception { RealService proxyService = getProxy(RealService.class); proxyService.sayHello(); } } |
RealService:被代理的类,没有接口。
getProxy() : 为一个类(RealService)生成代理对象;
cglib的Enhancer类:
setSuperclass设置被代理的类;
setCallback设置被代理类的public非final方法被调用时的处理类
Enhancer支持多种类型,这里使用的类实现了MethodInterceptor接口,它与Java SDK中的InvocationHandler有点类似,方法名称变成了intercept,多了一个MethodProxy类型的参数。
与前面的InvocationHandler不同,SimpleInterceptor中没有被代理的对象,它通过MethodProxy的invokeSuper方法调用被代理类的方法:Object result = proxy.invokeSuper(object, args);
Java SDK代理与cglib代理比较
Java SDK代理:面向的是一组接口,
它为这些接口动态创建了一个实现类,接口的具体实现逻辑是通过自定义的InvocationHandler实现的,这个实现是自定义的,也就是说,其背后都不一定有真正被代理的对象,也可能多个实际对象,根据情况动态选择。
cglib代理:面向的是一个具体的类,它动态创建了一个新类,继承了该类,重写了其方法。