生活中的场景比如房屋中介、卖票黄牛、经纪人、快递等,代码中的场景比如事物代理、非侵入的日志监听等,这些都是代理模式的实际体现。
代理模式(Proxy Pattern)是指为其他对象提供代理,从而控制对这个对象的访问。 代理对象在客户端和被代理对象之间起到中介作用。 使用代理模式主要目的有二:1.保护被代理对象 2.增强目标对象 代理模式属于结构型设计模式,有静态代理和动态代理。例如,人到了适婚年龄,父母希望早点抱孙子,而现在的人各种原因导致晚婚。着急的父母就导出为自己的子女相亲。
这个相亲的过程就是代理。
顶层接口Preson:
/** * @Auther: jesses * @Description: 人的行为,谈恋爱、买房、购物、工作 */ public interface Person { public void findLove(); //more behavior }儿子要找对象,实现Son类:
/** * @Auther: jesses * @Description: */ public class Son implements Person{ //儿子没有时间 //工作忙 @Override public void findLove() { System.out.println("儿子要求:肤白貌美"); } }父亲要帮儿子相亲,实现Father类:
/** * @Auther: jesses * @Description: */ public class Father { private Son son; public Father(Son son) { this.son = son; } public void findLove(){ System.out.println("父母物色对象"); this.son.findLove(); System.out.println("双方同意交往,确定关系"); } } /** * @Auther: jesses */ public class StatsicProxyTest { public static void main(String[] args) { //这种方式只能帮儿子相亲,不能帮其他亲戚。可拓展性低。 Father father = new Father(new Son()); father.findLove(); } }
代理模式到底如何应用到业务场景中?
再举个例子。在分布式业务场景中,我们常会对数据库进行分库分表。
分库分表之后,在java中就需要配置多个数据源。可以通过设置数据源路由来动态切换数据源。
先创建订单实体:
/** * @Auther: jesses * @Description: 订单实体 */ public class Order { private Object orderInfo; private Long createTime; private String id; public Object getOrderInfo() { return orderInfo; } public void setOrderInfo(Object orderInfo) { this.orderInfo = orderInfo; } public Long getCreateTime() { return createTime; } public void setCreateTime(Long createTime) { this.createTime = createTime; } public String getId() { return id; } public void setId(String id) { this.id = id; } }创建持久层DAO操作类:
/** * @Auther: jesses * @Description: 持久层操作类 */ public class OrderDao { public int insert(Order order){ System.out.println("OrderDao创建订单成功。"); return 1; } }创建OrderServicec接口
public interface OrderService { int createOrder(Order order); }创建OrderServiceImpl实现
/** * @Auther: jesses */ public class OrderServiceImpl implements OrderService { private OrderDao orderDao; public OrderServiceImpl() { //如果使用 Spring 应该是自动注入的 // 这里为了使用方便,就在构造方法中将 orderDao 直接初始化了 orderDao = new OrderDao(); } @Override public int createOrder(Order order) { System.out.println("OrderService调用OrderDao创建订单。"); return orderDao.insert(order); } }之后使用静态代理,根据订单创建时间自动按年份进行分库。
根据开闭原则,写好的逻辑不去修改,通过代理对象实现。
先创建数据源路由对象,使用ThreadLocal的单例实现:
/** * @Auther: jesses * @Description: 动态切换数据源 */ public class DynamicDataSourceEntry { //默认数据源 private final static String DEFAULT_SOURCE = null; private final static ThreadLocal<String> local=new ThreadLocal<String>(); public DynamicDataSourceEntry() { } /** * 清空数据源 */ public static void clear(){ local.remove(); } /** * 获取当前正在使用的数据源名 */ public static void get(){ local.get(); } /** * 还原当前切面的数据源 */ public static void restore(){ local.set(DEFAULT_SOURCE); } /** * 设置已知名字的数据源 */ public static void set(String source){ local.set(source); } /** * 根据年份设置数据源 */ public static void set(int year){ local.set("DB_"+year); } }创建OrderService的切换数据源的代理类:
/** * @Auther: jesses * @Date: 2020/7/4 * @Description: OrderService的数据源切换代理类 */ public class OrderServiceStaticProxy implements OrderService { private SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); private OrderService orderService; public OrderServiceStaticProxy(OrderService orderService) { this.orderService = orderService; } @Override public int createOrder(Order order) { before(); Long createTime=order.getCreateTime(); Integer yearDBRouter = Integer.valueOf(sdf.format(new Date(createTime))); System.out.println("静态代理类自动分配到【DB_"+yearDBRouter+"】数据源处理数据"); DynamicDataSourceEntry.set(yearDBRouter); orderService.createOrder(order); after(); return 0; } private void before(){ System.out.println("Proxy before method"); } private void after(){ System.out.println("Proxy after method"); } }测试:
public class StaticProxyTest2 { public static void main(String[] args) { try { Order order = new Order(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date createTime = sdf.parse("2008-02-01"); order.setCreateTime(createTime.getTime()); OrderService orderService = new OrderServiceStaticProxy(new OrderServiceImpl()); orderService.createOrder(order); } catch (Exception e) { e.printStackTrace(); } } }在这个例子中,OrderServiceImpl只实现了简单的调用Dao层创建订单逻辑,OrderServiceStaticProxy 则代理OrderServiceImpl,增加了before处理和after处理、以及动态切换数据源的业务逻辑。
这就是静态代理的业务实现。
动态代理和静态代理基本思路是一致的,但是动态代理的拓展性更强。
还是以找对象举例,如果不仅是父亲给儿子找对象,找对象这项业务发展成一个产业,出现了媒婆、婚介所。
如果还用静态代理,成本就太大了,使用动态代理可以适应更复杂的业务场景。
先看看找对象的升级版,使用JDK的实现方式。
接口Person:
/** * @Auther: jesses * @Description: 人的行为,谈恋爱、买房、购物、工作 */ public interface Person { public void findLove(); }创建被代理对象-单身客户类Customer:
/** * @Auther: jesses * @Description: 单身客户 Customer 类 */ public class Customer implements Person { @Override public void findLove() { //三个要求 System.out.println("高富帅"); System.out.println("身高180cm"); System.out.println("胸大,六块腹肌"); } }创建代理对象-媒婆类JDKMeipo:
/** * @Auther: jesses * @Date: 2020/7/7 * @Description: 媒婆(婚介)JDKMeipo 类 */ public class JDKMeipo implements InvocationHandler { //被代理的对象引用 private Object target; public Object getInstance(Person person) throws Exception { this.target = person; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target, args); after(); return obj; } private void before() { System.out.println("我是媒婆,我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); } private void after() { System.out.println("如果合适的话,就办事吧"); } } /** * @Auther: jesses * @Description: 测试类 */ public class JDKProxyTest { public static void main(String[] args) { try{ Person person = (Person) new JDKMeipo().getInstance(new Customer()); person.findLove(); }catch (Exception e){ e.printStackTrace(); } } }使用动态代理实现这个Demo,优势在于不再仅限于Father为Son找对象,而是可以为任何实现了 Person 的角色去代理做找对象这件事。
让我们来思考一个问题,person所引用的对象是由哪个类生成的?
在person.findLove()的执行中,输出了如上图所示内容。
可以想到person所引用的对象并不是Customer类的对象,因为Customer中只是实现了findLove()方法,只会输出它的三个要求。
同样也不是JDKMeipo类的对象,媒婆类中只有before()、after(),没有findLove()。
那么这些输出结果是怎么来的呢?
来打断点看看:
可以看到person引用的对象实际是一个{$Proxy@667},从名字可以猜测,这是一个代理对象。
知其然知其所以然,接下来就来深入分析一下 这个代理对象是如何产生的。
JDK动态代理的原理
在测试方法中增加代码,将该对象的class文件输出到E盘下:
/** * @Auther: jesses */ public class JDKProxyTest { public static void main(String[] args) { try { Person person = (Person) new JDKMeipo().getInstance(new Customer()); person.findLove(); //通过反编译工具可以查看源代码 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class}); FileOutputStream os = new FileOutputStream("E://$Proxy0.class"); os.write(bytes); os.close(); } catch (Exception e) { e.printStackTrace(); } } }使用Jad工具对该class文件进行反编译,可以得到如下内容:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://kpdus.tripod.com/jad.html // Decompiler options: packimports(3) fieldsfirst ansi space import com.jesses.testlumda.proxy.dynamic.jdk.Person; import java.lang.reflect.*; public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void findLove() { try { super.h.invoke(this, m3, null); return; } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("com.jesses.testlumda.proxy.dynamic.jdk.Person").getMethod("findLove", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } } 阅读代码,可以看到,$Proxy0 继承了 Proxy 类,还实现了Person接口,而且对findLove()方法进行了重写。 在静态代码块中还通过反射获取了目标对象的所有方法,保存了所有方法的引用, 之后在重写时用反射调用目标对象的方法。 注意在它重写findLove()时使用了super.h.invoke( this, m3, nulll ): 这个h是怎么来的呢?查看Proxy类源码得知 h 就是InvocationHandler的引用: 在调用JDKmeipo中getInstance时, 调用了Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 将this也就是JDKMeipo的自身实例作为参数传进去,因此h实际上就是JDKMeipo的对象实例: 那么 super.h.invoke():也就是调用了JDKMeipo中的invoke()方法,该invoke中我们组合了被代理对象的方法和代理对象的增强方法。分析到这里,终于明白了代理对象是如何产生的,以及如何调用到它的invoke方法实现了功能的拓展。
$Proxy0 继承了 Proxy 类,同时还实现了我们的 Person 接口,而且重写了 findLove()等方法。 而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。 这些代码其实是 JDK 帮我们自动生成的。 现在,我们不依赖 JDK 自己来 动态生成源代码、动态完成编译,然后,替代目标对象并执行。
被代理对象-单身客户Customer类:
/** * @Auther: jesses * 被代理对象 单身客户 Customer 类 */ public class Customer { public void findLove() { System.out.println("高富帅"); System.out.println("身高180cm"); System.out.println("胸大,六块腹肌"); } }代理对象-媒婆类CglibMeipo:
/** 代理对象 媒婆类CglibMeipo * @Auther: jesses */ public class CglibMeipo implements MethodInterceptor { public Object getInstance(Class<?> clazz) throws Exception { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o, objects); after(); return obj; } private void before() { System.out.println("我是媒婆,我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); } private void after() { System.out.println("如果合适的话,就办事吧"); } }测试:
/** * @Auther: jesses */ public class CglibProxyTest { public static void main(String[] args) { try { //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://cglib_proxy_class/"); Customer obj = (Customer) new CglibMeipo().getInstance(Customer.class); obj.findLove(); } catch (Exception e) { e.printStackTrace(); } } } JDK动态代理的目标对象需要实现接口(Person),它是通过获取被代理对象实现的接口,从而获取到要增强的方法。 CGLib 动态代理的目标对象Customer不需要实现任何接口,它是通过继承目标对象,继承到目标对象的方法来获取到要增强的方法。
在ProxyFactoryBean中,要么调用getSingletonInstance(),要么调用newPrototypeInstance()。
在Spring的配置中如果不做任何设置,那么Spring返回的对象都是单例对象。
如果修改scope属性,则每次创建一个原型对象。
Spring利用动态代理实现AOP有两个非常重要的类,JdkDynamicAopProxy类和CglibAopProxy类:
1.当Bean有实现接口时,Spring就会采用JDK的方式实现。
2.当Bean没有实现接口时,Spring会选择Cglib的方式实现。
3.Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码: <aop:aspectj-autoproxy proxy-target-class="true"/>